content: /*
   * Copyright (C) 2017 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.SurfaceControl.METADATA_OWNER_UID;
  import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
  
  import static com.android.server.wm.AppWindowThumbnailProto.HEIGHT;
  import static com.android.server.wm.AppWindowThumbnailProto.SURFACE_ANIMATOR;
  import static com.android.server.wm.AppWindowThumbnailProto.WIDTH;
  import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
  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.MAX_ANIMATION_DURATION;
  
  import android.graphics.GraphicBuffer;
  import android.graphics.PixelFormat;
  import android.graphics.Point;
  import android.os.Binder;
  import android.util.Slog;
  import android.util.proto.ProtoOutputStream;
  import android.view.Surface;
  import android.view.SurfaceControl;
  import android.view.SurfaceControl.Builder;
  import android.view.SurfaceControl.Transaction;
  import android.view.animation.Animation;
  
  import com.android.server.wm.SurfaceAnimator.Animatable;
  
  /**
   * Represents a surface that is displayed over an {@link AppWindowToken}
   */
  class AppWindowThumbnail implements Animatable {
  
      private static final String TAG = TAG_WITH_CLASS_NAME ? "AppWindowThumbnail" : TAG_WM;
  
      private final AppWindowToken mAppToken;
      private SurfaceControl mSurfaceControl;
      private final SurfaceAnimator mSurfaceAnimator;
      private final int mWidth;
      private final int mHeight;
      private final boolean mRelative;
  
      AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) {
          this(t, appToken, thumbnailHeader, false /* relative */);
      }
  
      /**
       * @param t Transaction to create the thumbnail in.
       * @param appToken {@link AppWindowToken} to associate this thumbnail with.
       * @param thumbnailHeader A thumbnail or placeholder for thumbnail to initialize with.
       * @param relative Whether this thumbnail will be a child of appToken (and thus positioned
       *                 relative to it) or not.
       */
      AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader,
              boolean relative) {
          this(t, appToken, thumbnailHeader, relative, new Surface(), null);
      }
  
      AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader,
              boolean relative, Surface drawSurface, SurfaceAnimator animator) {
          mAppToken = appToken;
          mRelative = relative;
          if (animator != null) {
              mSurfaceAnimator = animator;
          } else {
              // We can't use a delegating constructor since we need to
              // reference this::onAnimationFinished
              mSurfaceAnimator =
                  new SurfaceAnimator(this, this::onAnimationFinished, appToken.mWmService);
          }
          mWidth = thumbnailHeader.getWidth();
          mHeight = thumbnailHeader.getHeight();
  
          // Create a new surface for the thumbnail
          WindowState window = appToken.findMainWindow();
  
          // TODO: This should be attached as a child to the app token, once the thumbnail animations
          // use relative coordinates. Once we start animating task we can also consider attaching
          // this to the task.
          mSurfaceControl = appToken.makeSurface()
                  .setName("thumbnail anim: " + appToken.toString())
                  .setBufferSize(mWidth, mHeight)
                  .setFormat(PixelFormat.TRANSLUCENT)
                  .setMetadata(METADATA_WINDOW_TYPE, appToken.windowType)
                  .setMetadata(METADATA_OWNER_UID,
                          window != null ? window.mOwnerUid : Binder.getCallingUid())
                  .build();
  
          if (SHOW_TRANSACTIONS) {
              Slog.i(TAG, "  THUMBNAIL " + mSurfaceControl + ": CREATE");
          }
  
          // Transfer the thumbnail to the surface
          drawSurface.copyFrom(mSurfaceControl);
          drawSurface.attachAndQueueBuffer(thumbnailHeader);
          drawSurface.release();
          t.show(mSurfaceControl);
  
          // We parent the thumbnail to the task, and just place it on top of anything else in the
          // task.
          t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
          if (relative) {
              t.reparent(mSurfaceControl, appToken.getSurfaceControl());
          }
      }
  
      void startAnimation(Transaction t, Animation anim) {
          startAnimation(t, anim, null /* position */);
      }
  
      void startAnimation(Transaction t, Animation anim, Point position) {
          anim.restrictDuration(MAX_ANIMATION_DURATION);
          anim.scaleCurrentDuration(mAppToken.mWmService.getTransitionAnimationScaleLocked());
          mSurfaceAnimator.startAnimation(t, new LocalAnimationAdapter(
                  new WindowAnimationSpec(anim, position,
                          mAppToken.getDisplayContent().mAppTransition.canSkipFirstFrame(),
                          mAppToken.getDisplayContent().getWindowCornerRadius()),
                  mAppToken.mWmService.mSurfaceAnimationRunner), false /* hidden */);
      }
  
      /**
       * Start animation with existing adapter.
       */
      void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
          mSurfaceAnimator.startAnimation(t, anim, hidden);
      }
  
      private void onAnimationFinished() {
      }
  
      void setShowing(Transaction pendingTransaction, boolean show) {
          // TODO: Not needed anymore once thumbnail is attached to the app.
          if (show) {
              pendingTransaction.show(mSurfaceControl);
          } else {
              pendingTransaction.hide(mSurfaceControl);
          }
      }
  
      void destroy() {
          mSurfaceAnimator.cancelAnimation();
          getPendingTransaction().remove(mSurfaceControl);
          mSurfaceControl = null;
      }
  
      /**
       * Write to a protocol buffer output stream. Protocol buffer message definition is at {@link
       * com.android.server.wm.AppWindowThumbnailProto}.
       *
       * @param proto Stream to write the AppWindowThumbnail object to.
       * @param fieldId Field Id of the AppWindowThumbnail as defined in the parent message.
       * @hide
       */
      void writeToProto(ProtoOutputStream proto, long fieldId) {
          final long token = proto.start(fieldId);
          proto.write(WIDTH, mWidth);
          proto.write(HEIGHT, mHeight);
          if (mSurfaceAnimator.isAnimating()) {
              mSurfaceAnimator.writeToProto(proto, SURFACE_ANIMATOR);
          }
          proto.end(token);
      }
  
      @Override
      public Transaction getPendingTransaction() {
          return mAppToken.getPendingTransaction();
      }
  
      @Override
      public void commitPendingTransaction() {
          mAppToken.commitPendingTransaction();
      }
  
      @Override
      public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
          t.setLayer(leash, Integer.MAX_VALUE);
          if (mRelative) {
              t.reparent(leash, mAppToken.getSurfaceControl());
          }
      }
  
      @Override
      public void onAnimationLeashLost(Transaction t) {
  
          // TODO: Once attached to app token, we don't need to hide it immediately if thumbnail
          // became visible.
          t.hide(mSurfaceControl);
      }
  
      @Override
      public Builder makeAnimationLeash() {
          return mAppToken.makeSurface();
      }
  
      @Override
      public SurfaceControl getSurfaceControl() {
          return mSurfaceControl;
      }
  
      @Override
      public SurfaceControl getAnimationLeashParent() {
          return mAppToken.getAppAnimationLayer();
      }
  
      @Override
      public SurfaceControl getParentSurfaceControl() {
          return mAppToken.getParentSurfaceControl();
      }
  
      @Override
      public int getSurfaceWidth() {
          return mWidth;
      }
  
      @Override
      public int getSurfaceHeight() {
          return mHeight;
      }
  }
  