content: /*
   * Copyright (C) 2018 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 com.android.server.wm.WindowFramesProto.CONTAINING_FRAME;
  import static com.android.server.wm.WindowFramesProto.CONTENT_FRAME;
  import static com.android.server.wm.WindowFramesProto.CONTENT_INSETS;
  import static com.android.server.wm.WindowFramesProto.CUTOUT;
  import static com.android.server.wm.WindowFramesProto.DECOR_FRAME;
  import static com.android.server.wm.WindowFramesProto.DISPLAY_FRAME;
  import static com.android.server.wm.WindowFramesProto.FRAME;
  import static com.android.server.wm.WindowFramesProto.OUTSETS;
  import static com.android.server.wm.WindowFramesProto.OUTSET_FRAME;
  import static com.android.server.wm.WindowFramesProto.OVERSCAN_FRAME;
  import static com.android.server.wm.WindowFramesProto.OVERSCAN_INSETS;
  import static com.android.server.wm.WindowFramesProto.PARENT_FRAME;
  import static com.android.server.wm.WindowFramesProto.STABLE_INSETS;
  import static com.android.server.wm.WindowFramesProto.VISIBLE_FRAME;
  import static com.android.server.wm.WindowFramesProto.VISIBLE_INSETS;
  
  import android.annotation.NonNull;
  import android.graphics.Rect;
  import android.util.proto.ProtoOutputStream;
  import android.view.DisplayCutout;
  
  import com.android.server.wm.utils.InsetUtils;
  import com.android.server.wm.utils.WmDisplayCutout;
  
  import java.io.PrintWriter;
  
  /**
   * Container class for all the window frames that affect how windows are laid out.
   *
   * TODO(b/111611553): Investigate which frames are still needed and which are duplicates
   */
  public class WindowFrames {
      private static final StringBuilder sTmpSB = new StringBuilder();
  
      /**
       * In most cases, this is the area of the entire screen.
       *
       * TODO(b/111611553): The name is unclear and most likely should be swapped with
       * {@link #mDisplayFrame}
       * TODO(b/111611553): In some cases, it also includes top insets, like for IME. Determine
       * whether this is still necessary to do.
       */
      public final Rect mParentFrame = new Rect();
  
      /**
       * The entire screen area of the {@link TaskStack} this window is in. Usually equal to the
       * screen area of the device.
       *
       * TODO(b/111611553): The name is unclear and most likely should be swapped with
       * {@link #mParentFrame}
       */
      public final Rect mDisplayFrame = new Rect();
  
      /**
       * The region of the display frame that the display type supports displaying content on. This
       * is mostly a special case for TV where some displays don’t have the entire display usable.
       * {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_OVERSCAN} flag can be used to
       * allow window display contents to extend into the overscan region.
       */
      public final Rect mOverscanFrame = new Rect();
  
      /**
       * Legacy stuff. Generally equal to the content frame expect when the IME for older apps
       * displays hint text.
       */
      public final Rect mVisibleFrame = new Rect();
  
      /**
       * The area not occupied by the status and navigation bars. So, if both status and navigation
       * bars are visible, the decor frame is equal to the stable frame.
       */
      public final Rect mDecorFrame = new Rect();
  
      /**
       * Equal to the decor frame if the IME (e.g. keyboard) is not present. Equal to the decor frame
       * minus the area occupied by the IME if the IME is present.
       */
      public final Rect mContentFrame = new Rect();
  
      /**
       * The display frame minus the stable insets. This value is always constant regardless of if
       * the status bar or navigation bar is visible.
       */
      public final Rect mStableFrame = new Rect();
  
      /**
       * Frame that includes dead area outside of the surface but where we want to pretend that it's
       * possible to draw.
       */
      final public Rect mOutsetFrame = new Rect();
  
      /**
       * Similar to {@link #mDisplayFrame}
       *
       * TODO: Why is this different than mDisplayFrame
       */
      final Rect mContainingFrame = new Rect();
  
      /**
       * "Real" frame that the application sees, in display coordinate space.
       */
      final Rect mFrame = new Rect();
  
      /**
       * The last real frame that was reported to the client.
       */
      final Rect mLastFrame = new Rect();
  
      private boolean mFrameSizeChanged = false;
  
      // Frame that is scaled to the application's coordinate space when in
      // screen size compatibility mode.
      final Rect mCompatFrame = new Rect();
  
      /**
       * Whether the parent frame would have been different if there was no display cutout.
       */
      private boolean mParentFrameWasClippedByDisplayCutout;
  
      /**
       * Part of the display that has been cut away. See {@link DisplayCutout}.
       */
      WmDisplayCutout mDisplayCutout = WmDisplayCutout.NO_CUTOUT;
  
      /**
       * The last cutout that has been reported to the client.
       */
      private WmDisplayCutout mLastDisplayCutout = WmDisplayCutout.NO_CUTOUT;
  
      private boolean mDisplayCutoutChanged;
  
      /**
       * Insets that determine the area covered by the display overscan region.  These are in the
       * application's coordinate space (without compatibility scale applied).
       */
      final Rect mOverscanInsets = new Rect();
      final Rect mLastOverscanInsets = new Rect();
      private boolean mOverscanInsetsChanged;
  
      /**
       * Insets that determine the area covered by the stable system windows.  These are in the
       * application's coordinate space (without compatibility scale applied).
       */
      final Rect mStableInsets = new Rect();
      final Rect mLastStableInsets = new Rect();
      private boolean mStableInsetsChanged;
  
      /**
       * Outsets determine the area outside of the surface where we want to pretend that it's possible
       * to draw anyway.
       */
      final Rect mOutsets = new Rect();
      final Rect mLastOutsets = new Rect();
      private boolean mOutsetsChanged = false;
  
      /**
       * Insets that determine the actually visible area.  These are in the application's
       * coordinate space (without compatibility scale applied).
       */
      final Rect mVisibleInsets = new Rect();
      final Rect mLastVisibleInsets = new Rect();
      private boolean mVisibleInsetsChanged;
  
      /**
       * Insets that are covered by system windows (such as the status bar) and
       * transient docking windows (such as the IME).  These are in the application's
       * coordinate space (without compatibility scale applied).
       */
      final Rect mContentInsets = new Rect();
      final Rect mLastContentInsets = new Rect();
      private boolean mContentInsetsChanged;
  
      private final Rect mTmpRect = new Rect();
  
      private boolean mHasOutsets;
  
      private boolean mContentChanged;
  
      public WindowFrames() {
      }
  
      public WindowFrames(Rect parentFrame, Rect displayFrame, Rect overscanFrame, Rect contentFrame,
              Rect visibleFrame, Rect decorFrame, Rect stableFrame, Rect outsetFrame) {
          setFrames(parentFrame, displayFrame, overscanFrame, contentFrame, visibleFrame, decorFrame,
                  stableFrame, outsetFrame);
      }
  
      public void setFrames(Rect parentFrame, Rect displayFrame, Rect overscanFrame,
              Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame,
              Rect outsetFrame) {
          mParentFrame.set(parentFrame);
          mDisplayFrame.set(displayFrame);
          mOverscanFrame.set(overscanFrame);
          mContentFrame.set(contentFrame);
          mVisibleFrame.set(visibleFrame);
          mDecorFrame.set(decorFrame);
          mStableFrame.set(stableFrame);
          mOutsetFrame.set(outsetFrame);
      }
  
      public void setParentFrameWasClippedByDisplayCutout(
              boolean parentFrameWasClippedByDisplayCutout) {
          mParentFrameWasClippedByDisplayCutout = parentFrameWasClippedByDisplayCutout;
      }
  
      boolean parentFrameWasClippedByDisplayCutout() {
          return mParentFrameWasClippedByDisplayCutout;
      }
  
      public void setDisplayCutout(WmDisplayCutout displayCutout) {
          mDisplayCutout = displayCutout;
      }
  
      /**
       * @return true if the width or height has changed since last reported to the client.
       */
      private boolean didFrameSizeChange() {
          return (mLastFrame.width() != mFrame.width()) || (mLastFrame.height() != mFrame.height());
      }
  
      /**
       * Calculates the outsets for this windowFrame. The outsets are calculated by the area between
       * the {@link #mOutsetFrame} and the {@link #mContentFrame}. If there are no outsets, then
       * {@link #mOutsets} is set to empty.
       */
      void calculateOutsets() {
          if (mHasOutsets) {
              InsetUtils.insetsBetweenFrames(mOutsetFrame, mContentFrame, mOutsets);
          }
      }
  
      /**
       * Calculate the insets for the type
       * {@link android.view.WindowManager.LayoutParams#TYPE_DOCK_DIVIDER}
       *
       * @param cutoutInsets The insets for the cutout.
       */
      void calculateDockedDividerInsets(Rect cutoutInsets) {
          // For the docked divider, we calculate the stable insets like a full-screen window
          // so it can use it to calculate the snap positions.
          mTmpRect.set(mDisplayFrame);
          mTmpRect.inset(cutoutInsets);
          mTmpRect.intersectUnchecked(mStableFrame);
          InsetUtils.insetsBetweenFrames(mDisplayFrame, mTmpRect, mStableInsets);
  
          // The divider doesn't care about insets in any case, so set it to empty so we don't
          // trigger a relayout when moving it.
          mContentInsets.setEmpty();
          mVisibleInsets.setEmpty();
          mDisplayCutout = WmDisplayCutout.NO_CUTOUT;
      }
  
      /**
       * Calculate the insets for a window.
       *
       * @param windowsAreFloating    Whether the window is in a floating task such as pinned or
       *                              freeform
       * @param inFullscreenContainer Whether the window is in a container that takes up the screen's
       *                              entire space
       * @param windowBounds          The bounds for the window
       */
      void calculateInsets(boolean windowsAreFloating, boolean inFullscreenContainer,
              Rect windowBounds) {
          // Override right and/or bottom insets in case if the frame doesn't fit the screen in
          // non-fullscreen mode.
          boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
                  && mFrame.right > windowBounds.right;
          boolean overrideBottomInset = !windowsAreFloating && !inFullscreenContainer
                  && mFrame.bottom > windowBounds.bottom;
  
          mTmpRect.set(mFrame.left, mFrame.top,
                  overrideRightInset ? windowBounds.right : mFrame.right,
                  overrideBottomInset ? windowBounds.bottom : mFrame.bottom);
  
          InsetUtils.insetsBetweenFrames(mTmpRect, mContentFrame, mContentInsets);
          InsetUtils.insetsBetweenFrames(mTmpRect, mVisibleFrame, mVisibleInsets);
          InsetUtils.insetsBetweenFrames(mTmpRect, mStableFrame, mStableInsets);
      }
  
      /**
       * Scales all the insets by a specific amount.
       *
       * @param scale The amount to scale the insets by.
       */
      void scaleInsets(float scale) {
          mOverscanInsets.scale(scale);
          mContentInsets.scale(scale);
          mVisibleInsets.scale(scale);
          mStableInsets.scale(scale);
          mOutsets.scale(scale);
      }
  
      void offsetFrames(int layoutXDiff, int layoutYDiff) {
          mFrame.offset(layoutXDiff, layoutYDiff);
          mContentFrame.offset(layoutXDiff, layoutYDiff);
          mVisibleFrame.offset(layoutXDiff, layoutYDiff);
          mStableFrame.offset(layoutXDiff, layoutYDiff);
      }
  
      /**
       * Updates info about whether the size of the window has changed since last reported.
       *
       * @return true if info about size has changed since last reported.
       */
      boolean setReportResizeHints() {
          mOverscanInsetsChanged |= !mLastOverscanInsets.equals(mOverscanInsets);
          mContentInsetsChanged |= !mLastContentInsets.equals(mContentInsets);
          mVisibleInsetsChanged |= !mLastVisibleInsets.equals(mVisibleInsets);
          mStableInsetsChanged |= !mLastStableInsets.equals(mStableInsets);
          mOutsetsChanged |= !mLastOutsets.equals(mOutsets);
          mFrameSizeChanged |= didFrameSizeChange();
          mDisplayCutoutChanged |= !mLastDisplayCutout.equals(mDisplayCutout);
          return mOverscanInsetsChanged || mContentInsetsChanged || mVisibleInsetsChanged
                  || mStableInsetsChanged || mOutsetsChanged || mFrameSizeChanged
                  || mDisplayCutoutChanged;
      }
  
      /**
       * Resets the insets changed flags so they're all set to false again. This should be called
       * after the insets are reported to client.
       */
      void resetInsetsChanged() {
          mOverscanInsetsChanged = false;
          mContentInsetsChanged = false;
          mVisibleInsetsChanged = false;
          mStableInsetsChanged = false;
          mOutsetsChanged = false;
          mFrameSizeChanged = false;
          mDisplayCutoutChanged = false;
      }
  
      /**
       * Copy over inset values as the last insets that were sent to the client.
       */
      void updateLastInsetValues() {
          mLastOverscanInsets.set(mOverscanInsets);
          mLastContentInsets.set(mContentInsets);
          mLastVisibleInsets.set(mVisibleInsets);
          mLastStableInsets.set(mStableInsets);
          mLastOutsets.set(mOutsets);
          mLastDisplayCutout = mDisplayCutout;
      }
  
      /**
       * Sets the last content insets as (-1, -1, -1, -1) to force the next layout pass to update
       * the client.
       */
      void resetLastContentInsets() {
          mLastContentInsets.set(-1, -1, -1, -1);
      }
  
      /**
       * Sets whether the frame has outsets.
       */
      public void setHasOutsets(boolean hasOutsets) {
          if (mHasOutsets == hasOutsets) {
              return;
          }
          mHasOutsets = hasOutsets;
          if (!hasOutsets) {
              mOutsets.setEmpty();
          }
      }
  
      /**
       * Sets whether the content has changed. This means that either the size or parent frame has
       * changed.
       */
      public void setContentChanged(boolean contentChanged) {
          mContentChanged = contentChanged;
      }
  
      /**
       * @see #setContentChanged(boolean)
       */
      boolean hasContentChanged() {
          return mContentChanged;
      }
  
      public void writeToProto(@NonNull ProtoOutputStream proto, long fieldId) {
          final long token = proto.start(fieldId);
          mParentFrame.writeToProto(proto, PARENT_FRAME);
          mContentFrame.writeToProto(proto, CONTENT_FRAME);
          mDisplayFrame.writeToProto(proto, DISPLAY_FRAME);
          mOverscanFrame.writeToProto(proto, OVERSCAN_FRAME);
          mVisibleFrame.writeToProto(proto, VISIBLE_FRAME);
          mDecorFrame.writeToProto(proto, DECOR_FRAME);
          mOutsetFrame.writeToProto(proto, OUTSET_FRAME);
          mContainingFrame.writeToProto(proto, CONTAINING_FRAME);
          mFrame.writeToProto(proto, FRAME);
          mDisplayCutout.getDisplayCutout().writeToProto(proto, CUTOUT);
          mContentInsets.writeToProto(proto, CONTENT_INSETS);
          mOverscanInsets.writeToProto(proto, OVERSCAN_INSETS);
          mVisibleInsets.writeToProto(proto, VISIBLE_INSETS);
          mStableInsets.writeToProto(proto, STABLE_INSETS);
          mOutsets.writeToProto(proto, OUTSETS);
  
          proto.end(token);
      }
  
      public void dump(PrintWriter pw, String prefix) {
          pw.println(prefix + "Frames: containing="
                  + mContainingFrame.toShortString(sTmpSB)
                  + " parent=" + mParentFrame.toShortString(sTmpSB));
          pw.println(prefix + "    display=" + mDisplayFrame.toShortString(sTmpSB)
                  + " overscan=" + mOverscanFrame.toShortString(sTmpSB));
          pw.println(prefix + "    content=" + mContentFrame.toShortString(sTmpSB)
                  + " visible=" + mVisibleFrame.toShortString(sTmpSB));
          pw.println(prefix + "    decor=" + mDecorFrame.toShortString(sTmpSB));
          pw.println(prefix + "    outset=" + mOutsetFrame.toShortString(sTmpSB));
          pw.println(prefix + "mFrame=" + mFrame.toShortString(sTmpSB)
                  + " last=" + mLastFrame.toShortString(sTmpSB));
          pw.println(prefix + " cutout=" + mDisplayCutout.getDisplayCutout()
                  + " last=" + mLastDisplayCutout.getDisplayCutout());
          pw.print(prefix + "Cur insets: overscan=" + mOverscanInsets.toShortString(sTmpSB)
                  + " content=" + mContentInsets.toShortString(sTmpSB)
                  + " visible=" + mVisibleInsets.toShortString(sTmpSB)
                  + " stable=" + mStableInsets.toShortString(sTmpSB)
                  + " outsets=" + mOutsets.toShortString(sTmpSB));
          pw.println(prefix + "Lst insets: overscan=" + mLastOverscanInsets.toShortString(sTmpSB)
                  + " content=" + mLastContentInsets.toShortString(sTmpSB)
                  + " visible=" + mLastVisibleInsets.toShortString(sTmpSB)
                  + " stable=" + mLastStableInsets.toShortString(sTmpSB)
                  + " outset=" + mLastOutsets.toShortString(sTmpSB));
      }
  
      String getInsetsInfo() {
          return "ci=" + mContentInsets.toShortString()
                  + " vi=" + mVisibleInsets.toShortString()
                  + " si=" + mStableInsets.toShortString()
                  + " of=" + mOutsets.toShortString();
      }
  
      String getInsetsChangedInfo() {
          return "contentInsetsChanged=" + mContentInsetsChanged
                  + " " + mContentInsets.toShortString()
                  + " visibleInsetsChanged=" + mVisibleInsetsChanged
                  + " " + mVisibleInsets.toShortString()
                  + " stableInsetsChanged=" + mStableInsetsChanged
                  + " " + mStableInsets.toShortString()
                  + " outsetsChanged=" + mOutsetsChanged
                  + " " + mOutsets.toShortString()
                  + " displayCutoutChanged=" + mDisplayCutoutChanged;
      }
  }
  