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 android.view.InsetsState.TYPE_IME;
  import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
  import static android.view.InsetsState.TYPE_TOP_BAR;
  import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
  import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
  import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
  import static android.view.ViewRootImpl.sNewInsetsMode;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.graphics.Point;
  import android.graphics.Rect;
  import android.util.proto.ProtoOutputStream;
  import android.view.InsetsSource;
  import android.view.InsetsSourceControl;
  import android.view.InsetsState;
  import android.view.SurfaceControl;
  import android.view.SurfaceControl.Transaction;
  
  import com.android.internal.util.function.TriConsumer;
  import com.android.internal.util.function.pooled.PooledLambda;
  import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
  
  import java.io.PrintWriter;
  
  /**
   * Controller for a specific inset source on the server. It's called provider as it provides the
   * {@link InsetsSource} to the client that uses it in {@link InsetsSourceConsumer}.
   */
  class InsetsSourceProvider {
  
      private final Rect mTmpRect = new Rect();
      private final @NonNull InsetsSource mSource;
      private final DisplayContent mDisplayContent;
      private final InsetsStateController mStateController;
      private @Nullable InsetsSourceControl mControl;
      private @Nullable WindowState mControllingWin;
      private @Nullable ControlAdapter mAdapter;
      private WindowState mWin;
      private TriConsumer<DisplayFrames, WindowState, Rect> mFrameProvider;
  
      /** The visibility override from the current controlling window. */
      private boolean mClientVisible;
  
      /**
       * Whether the window is available and considered visible as in {@link WindowState#isVisible}.
       */
      private boolean mServerVisible;
  
      private final boolean mControllable;
  
      InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
              DisplayContent displayContent) {
          mClientVisible = InsetsState.getDefaultVisibility(source.getType());
          mSource = source;
          mDisplayContent = displayContent;
          mStateController = stateController;
  
          final int type = source.getType();
          if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
              mControllable = sNewInsetsMode == NEW_INSETS_MODE_FULL;
          } else if (type == TYPE_IME) {
              mControllable = sNewInsetsMode >= NEW_INSETS_MODE_IME;
          } else {
              mControllable = false;
          }
      }
  
      InsetsSource getSource() {
          return mSource;
      }
  
      /**
       * @return Whether the current flag configuration allows to control this source.
       */
      boolean isControllable() {
          return mControllable;
      }
  
      /**
       * Updates the window that currently backs this source.
       *
       * @param win The window that links to this source.
       * @param frameProvider Based on display frame state and the window, calculates the resulting
       *                      frame that should be reported to clients.
       */
      void setWindow(@Nullable WindowState win,
              @Nullable TriConsumer<DisplayFrames, WindowState, Rect> frameProvider) {
          if (mWin != null) {
              mWin.setInsetProvider(null);
          }
          mWin = win;
          mFrameProvider = frameProvider;
          if (win == null) {
              setServerVisible(false);
              mSource.setFrame(new Rect());
          } else {
              mWin.setInsetProvider(this);
              if (mControllingWin != null) {
                  updateControlForTarget(mControllingWin, true /* force */);
              }
          }
      }
  
      /**
       * Called when a layout pass has occurred.
       */
      void onPostLayout() {
          if (mWin == null) {
              return;
          }
  
          mTmpRect.set(mWin.getFrameLw());
          if (mFrameProvider != null) {
              mFrameProvider.accept(mWin.getDisplayContent().mDisplayFrames, mWin, mTmpRect);
          } else {
              mTmpRect.inset(mWin.mGivenContentInsets);
          }
          mSource.setFrame(mTmpRect);
          if (mControl != null) {
              final Rect frame = mWin.getWindowFrames().mFrame;
              if (mControl.setSurfacePosition(frame.left, frame.top)) {
                  mStateController.notifyControlChanged(mControllingWin);
              }
          }
          setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.isVisibleByPolicy()
                  && !mWin.mGivenInsetsPending);
      }
  
      void updateControlForTarget(@Nullable WindowState target, boolean force) {
          if (mWin == null) {
              mControllingWin = target;
              return;
          }
          if (target == mControllingWin && !force) {
              return;
          }
          if (target == null) {
              // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
              mWin.cancelAnimation();
              return;
          }
          mAdapter = new ControlAdapter();
          setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
          mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter,
                  !mClientVisible /* hidden */);
          mControllingWin = target;
          mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash,
                  new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top));
      }
  
      boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) {
          if (mControllingWin != caller || modifiedSource.isVisible() == mClientVisible) {
              return false;
          }
          setClientVisible(modifiedSource.isVisible());
          return true;
      }
  
      private void setClientVisible(boolean clientVisible) {
          if (mClientVisible == clientVisible) {
              return;
          }
          mClientVisible = clientVisible;
          mDisplayContent.mWmService.mH.sendMessage(PooledLambda.obtainMessage(
                  DisplayContent::layoutAndAssignWindowLayersIfNeeded, mDisplayContent));
          updateVisibility();
      }
  
      private void setServerVisible(boolean serverVisible) {
          mServerVisible = serverVisible;
          updateVisibility();
      }
  
      private void updateVisibility() {
          mSource.setVisible(mServerVisible && mClientVisible);
      }
  
      InsetsSourceControl getControl() {
          return mControl;
      }
  
      boolean isClientVisible() {
          return sNewInsetsMode == NEW_INSETS_MODE_NONE || mClientVisible;
      }
  
      private class ControlAdapter implements AnimationAdapter {
  
          private SurfaceControl mCapturedLeash;
  
          @Override
          public boolean getShowWallpaper() {
              return false;
          }
  
          @Override
          public int getBackgroundColor() {
              return 0;
          }
  
          @Override
          public void startAnimation(SurfaceControl animationLeash, Transaction t,
                  OnAnimationFinishedCallback finishCallback) {
              mCapturedLeash = animationLeash;
              final Rect frame = mWin.getWindowFrames().mFrame;
              t.setPosition(mCapturedLeash, frame.left, frame.top);
          }
  
          @Override
          public void onAnimationCancelled(SurfaceControl animationLeash) {
              if (mAdapter == this) {
                  mStateController.notifyControlRevoked(mControllingWin, InsetsSourceProvider.this);
                  setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
                  mControl = null;
                  mControllingWin = null;
                  mAdapter = null;
              }
          }
  
          @Override
          public long getDurationHint() {
              return 0;
          }
  
          @Override
          public long getStatusBarTransitionsStartTime() {
              return 0;
          }
  
          @Override
          public void dump(PrintWriter pw, String prefix) {
          }
  
          @Override
          public void writeToProto(ProtoOutputStream proto) {
          }
      };
  }
  