/*
 * 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.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainerThumbnailProto.HEIGHT;
import static com.android.server.wm.WindowContainerThumbnailProto.SURFACE_ANIMATOR;
import static com.android.server.wm.WindowContainerThumbnailProto.WIDTH;
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.Process;
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.protolog.common.ProtoLog;
import com.android.server.wm.SurfaceAnimator.Animatable;
import com.android.server.wm.SurfaceAnimator.AnimationType;

import java.util.function.Supplier;

/**
 * Represents a surface that is displayed over a subclass of {@link WindowContainer}
 */
class WindowContainerThumbnail implements Animatable {

    private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainerThumbnail" : TAG_WM;

    private final WindowContainer mWindowContainer;
    private SurfaceControl mSurfaceControl;
    private final SurfaceAnimator mSurfaceAnimator;
    private final int mWidth;
    private final int mHeight;
    private final boolean mRelative;

    WindowContainerThumbnail(Supplier<Surface> surfaceFactory, Transaction t,
            WindowContainer container, GraphicBuffer thumbnailHeader) {
        this(surfaceFactory, t, container, thumbnailHeader, false /* relative */);
    }

    /**
     * @param t Transaction to create the thumbnail in.
     * @param container The sub-class of {@link WindowContainer} 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 the container (and thus positioned
     *                 relative to it) or not.
     */
    WindowContainerThumbnail(Supplier<Surface> surfaceFactory, Transaction t,
            WindowContainer container, GraphicBuffer thumbnailHeader, boolean relative) {
        this(t, container, thumbnailHeader, relative, surfaceFactory.get(), null);
    }

    WindowContainerThumbnail(Transaction t, WindowContainer container,
            GraphicBuffer thumbnailHeader, boolean relative, Surface drawSurface,
            SurfaceAnimator animator) {
        mWindowContainer = container;
        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 /* animationFinishedCallback */,
                        container.mWmService);
        }
        mWidth = thumbnailHeader.getWidth();
        mHeight = thumbnailHeader.getHeight();

        // Create a new surface for the thumbnail
        // 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 = mWindowContainer.makeChildSurface(mWindowContainer.getTopChild())
                .setName("thumbnail anim: " + mWindowContainer.toString())
                .setBufferSize(mWidth, mHeight)
                .setFormat(PixelFormat.TRANSLUCENT)
                .setMetadata(METADATA_WINDOW_TYPE, mWindowContainer.getWindowingMode())
                .setMetadata(METADATA_OWNER_UID, Process.myUid())
                .setCallsite("WindowContainerThumbnail")
                .build();

        ProtoLog.i(WM_SHOW_TRANSACTIONS, "  THUMBNAIL %s: CREATE", mSurfaceControl);

        // Transfer the thumbnail to the surface
        drawSurface.copyFrom(mSurfaceControl);
        drawSurface.attachAndQueueBuffer(thumbnailHeader);
        drawSurface.release();
        t.show(mSurfaceControl);

        // We parent the thumbnail to the container, and just place it on top of anything else in
        // the container.
        t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
        if (relative) {
            t.reparent(mSurfaceControl, mWindowContainer.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(mWindowContainer.mWmService.getTransitionAnimationScaleLocked());
        mSurfaceAnimator.startAnimation(t, new LocalAnimationAdapter(
                new WindowAnimationSpec(anim, position,
                        mWindowContainer.getDisplayContent().mAppTransition.canSkipFirstFrame(),
                        mWindowContainer.getDisplayContent().getWindowCornerRadius()),
                mWindowContainer.mWmService.mSurfaceAnimationRunner), false /* hidden */,
                ANIMATION_TYPE_RECENTS);
    }

    /**
     * Start animation with existing adapter.
     */
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden) {
        mSurfaceAnimator.startAnimation(t, anim, hidden, ANIMATION_TYPE_RECENTS);
    }

    private void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
    }

    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.WindowContainerThumbnailProto}.
     *
     * @param proto Stream to write the WindowContainerThumbnailProto object to.
     * @param fieldId Field Id of the WindowContainerThumbnailProto as defined in the parent
     *                message.
     * @hide
     */
    void dumpDebug(ProtoOutputStream proto, long fieldId) {
        final long token = proto.start(fieldId);
        proto.write(WIDTH, mWidth);
        proto.write(HEIGHT, mHeight);
        if (mSurfaceAnimator.isAnimating()) {
            mSurfaceAnimator.dumpDebug(proto, SURFACE_ANIMATOR);
        }
        proto.end(token);
    }

    @Override
    public Transaction getPendingTransaction() {
        return mWindowContainer.getPendingTransaction();
    }

    @Override
    public void commitPendingTransaction() {
        mWindowContainer.commitPendingTransaction();
    }

    @Override
    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
        t.setLayer(leash, Integer.MAX_VALUE);
        if (mRelative) {
            t.reparent(leash, mWindowContainer.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 mWindowContainer.makeChildSurface(mWindowContainer.getTopChild());
    }

    @Override
    public SurfaceControl getSurfaceControl() {
        return mSurfaceControl;
    }

    @Override
    public SurfaceControl getAnimationLeashParent() {
        return mWindowContainer.getAnimationLeashParent();
    }

    @Override
    public SurfaceControl getParentSurfaceControl() {
        return mWindowContainer.getParentSurfaceControl();
    }

    @Override
    public int getSurfaceWidth() {
        return mWidth;
    }

    @Override
    public int getSurfaceHeight() {
        return mHeight;
    }
}
