/*
 * 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 android.view;

import static android.graphics.GraphicsProtos.dumpPointProto;
import static android.view.RemoteAnimationTargetProto.CLIP_RECT;
import static android.view.RemoteAnimationTargetProto.CONTENT_INSETS;
import static android.view.RemoteAnimationTargetProto.IS_TRANSLUCENT;
import static android.view.RemoteAnimationTargetProto.LEASH;
import static android.view.RemoteAnimationTargetProto.LOCAL_BOUNDS;
import static android.view.RemoteAnimationTargetProto.MODE;
import static android.view.RemoteAnimationTargetProto.POSITION;
import static android.view.RemoteAnimationTargetProto.PREFIX_ORDER_INDEX;
import static android.view.RemoteAnimationTargetProto.SCREEN_SPACE_BOUNDS;
import static android.view.RemoteAnimationTargetProto.SOURCE_CONTAINER_BOUNDS;
import static android.view.RemoteAnimationTargetProto.START_BOUNDS;
import static android.view.RemoteAnimationTargetProto.START_LEASH;
import static android.view.RemoteAnimationTargetProto.TASK_ID;
import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;

import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.proto.ProtoOutputStream;
import android.window.TaskSnapshot;

import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Describes an activity to be animated as part of a remote animation.
 *
 * @hide
 */
public class RemoteAnimationTarget implements Parcelable {

    /**
     * The app is in the set of opening apps of this transition.
     */
    public static final int MODE_OPENING = 0;

    /**
     * The app is in the set of closing apps of this transition.
     */
    public static final int MODE_CLOSING = 1;

    /**
     * The app is in the set of resizing apps (eg. mode change) of this transition.
     */
    public static final int MODE_CHANGING = 2;

    @IntDef(prefix = { "MODE_" }, value = {
            MODE_OPENING,
            MODE_CLOSING,
            MODE_CHANGING
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Mode {}

    /**
     * The {@link Mode} to describe whether this app is opening or closing.
     */
    @UnsupportedAppUsage
    public final @Mode int mode;

    /**
     * The id of the task this app belongs to.
     */
    @UnsupportedAppUsage
    public final int taskId;

    /**
     * The {@link SurfaceControl} object to actually control the transform of the app.
     */
    @UnsupportedAppUsage
    public final SurfaceControl leash;

    /**
     * The {@link SurfaceControl} for the starting state of a target if this transition is
     * MODE_CHANGING, {@code null)} otherwise. This is relative to the app window.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public final SurfaceControl startLeash;

    /**
     * Whether the app is translucent and may reveal apps behind.
     */
    @UnsupportedAppUsage
    public final boolean isTranslucent;

    /**
     * The clip rect window manager applies when clipping the app's main surface in screen space
     * coordinates. This is just a hint to the animation runner: If running a clip-rect animation,
     * anything that extends beyond these bounds will not have any effect. This implies that any
     * clip-rect animation should likely stop at these bounds.
     */
    @UnsupportedAppUsage
    public final Rect clipRect;

    /**
     * The insets of the main app window.
     */
    @UnsupportedAppUsage
    public final Rect contentInsets;

    /**
     * The index of the element in the tree in prefix order. This should be used for z-layering
     * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to
     * happen.
     * @deprecated WindowManager may set a z-order different from the prefix order, and has set the
     *             correct layer for the animation leash already, so this should not be used for
     *             layer any more.
     */
    @Deprecated
    @UnsupportedAppUsage
    public final int prefixOrderIndex;

    /**
     * The source position of the app, in screen spaces coordinates. If the position of the leash
     * is modified from the controlling app, any animation transform needs to be offset by this
     * amount.
     * @deprecated Use {@link #localBounds} instead.
     */
    @Deprecated
    @UnsupportedAppUsage
    public final Point position;

    /**
     * Bounds of the target relative to its parent.
     * When the app target animating on its parent, we need to use the local coordinates relative to
     * its parent with {@code localBounds.left} & {@code localBounds.top} rather than using
     * {@code position} in screen coordinates.
     */
    public final Rect localBounds;

    /**
     * The bounds of the source container the app lives in, in screen space coordinates. If the crop
     * of the leash is modified from the controlling app, it needs to take the source container
     * bounds into account when calculating the crop.
     * @deprecated Renamed to {@link #screenSpaceBounds}
     */
    @Deprecated
    @UnsupportedAppUsage
    public final Rect sourceContainerBounds;

    /**
     * Bounds of the target relative to the screen. If the crop of the leash is modified from the
     * controlling app, it needs to take the screen space bounds into account when calculating the
     * crop.
     */
    public final Rect screenSpaceBounds;

    /**
     * The starting bounds of the source container in screen space coordinates.
     * For {@link #MODE_OPENING}, this will be equivalent to {@link #screenSpaceBounds}.
     * For {@link #MODE_CLOSING}, this will be equivalent to {@link #screenSpaceBounds} unless the
     * closing container is also resizing. For example, when ActivityEmbedding split pair becomes
     * stacked, the container on the back will be resized to fullscreen, but will also be covered
     * (closing) by the container in the front.
     * For {@link #MODE_CHANGING}, since this is the starting bounds, its size should be equivalent
     * to the bounds of the starting thumbnail.
     *
     * Note that {@link #screenSpaceBounds} is the end bounds of a transition.
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public final Rect startBounds;

    /**
     * The window configuration for the target.
     */
    @UnsupportedAppUsage
    public final WindowConfiguration windowConfiguration;

    /**
     * Whether the task is not presented in Recents UI.
     */
    @UnsupportedAppUsage
    public boolean isNotInRecents;

    /**
     * {@link TaskInfo} to allow the controller to identify information about the task.
     *
     * TODO: add this to proto dump
     */
    public ActivityManager.RunningTaskInfo taskInfo;

    /**
     * {@code true} if picture-in-picture permission is granted in {@link android.app.AppOpsManager}
     */
    @UnsupportedAppUsage
    public boolean allowEnterPip;

    /**
     * The {@link android.view.WindowManager.LayoutParams.WindowType} of this window. It's only used
     * for non-app window.
     */
    public final @WindowManager.LayoutParams.WindowType int windowType;

    /**
     * {@code true} if its parent is also a {@link RemoteAnimationTarget} in the same transition.
     *
     * For example, when a TaskFragment is resizing while one of its children is open/close, both
     * windows will be animation targets. This value will be {@code true} for the child, so that
     * the handler can choose to handle it differently.
     */
    public boolean hasAnimatingParent;

    /**
     * Whether an activity has enabled {@link android.R.styleable#Animation_showBackdrop} for
     * transition.
     */
    public boolean showBackdrop;

    /**
     * The background color of animation in case the task info is not available if the transition
     * is activity level.
     */
    public @ColorInt int backgroundColor;

    /**
     * Whether the activity is going to show IME on the target window after the app transition.
     * @see TaskSnapshot#hasImeSurface() that used the task snapshot during animating task.
     */
    public boolean willShowImeOnTarget;

    public int rotationChange;

    public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
            Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
            Rect localBounds, Rect screenSpaceBounds,
            WindowConfiguration windowConfig, boolean isNotInRecents,
            SurfaceControl startLeash, @Nullable Rect startBounds,
            ActivityManager.RunningTaskInfo taskInfo,
            boolean allowEnterPip) {
        this(taskId, mode, leash, isTranslucent, clipRect, contentInsets, prefixOrderIndex,
                position, localBounds, screenSpaceBounds, windowConfig, isNotInRecents, startLeash,
                startBounds, taskInfo, allowEnterPip, INVALID_WINDOW_TYPE);
    }

    public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
            Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
            Rect localBounds, Rect screenSpaceBounds,
            WindowConfiguration windowConfig, boolean isNotInRecents,
            SurfaceControl startLeash, @Nullable Rect startBounds,
            ActivityManager.RunningTaskInfo taskInfo, boolean allowEnterPip,
            @WindowManager.LayoutParams.WindowType int windowType) {
        this.mode = mode;
        this.taskId = taskId;
        this.leash = leash;
        this.isTranslucent = isTranslucent;
        this.clipRect = new Rect(clipRect);
        this.contentInsets = new Rect(contentInsets);
        this.prefixOrderIndex = prefixOrderIndex;
        this.position = position == null ? new Point() : new Point(position);
        this.localBounds = new Rect(localBounds);
        this.sourceContainerBounds = new Rect(screenSpaceBounds);
        this.screenSpaceBounds = new Rect(screenSpaceBounds);
        this.windowConfiguration = windowConfig;
        this.isNotInRecents = isNotInRecents;
        this.startLeash = startLeash;
        this.taskInfo = taskInfo;
        this.allowEnterPip = allowEnterPip;
        this.windowType = windowType;
        // Same as screenSpaceBounds if the window is not resizing.
        this.startBounds = startBounds == null
                ? new Rect(screenSpaceBounds)
                : new Rect(startBounds);
    }

    public RemoteAnimationTarget(Parcel in) {
        taskId = in.readInt();
        mode = in.readInt();
        leash = in.readTypedObject(SurfaceControl.CREATOR);
        if (leash != null) {
            leash.setUnreleasedWarningCallSite("RemoteAnimationTarget[leash]");
        }
        isTranslucent = in.readBoolean();
        clipRect = in.readTypedObject(Rect.CREATOR);
        contentInsets = in.readTypedObject(Rect.CREATOR);
        prefixOrderIndex = in.readInt();
        position = in.readTypedObject(Point.CREATOR);
        localBounds = in.readTypedObject(Rect.CREATOR);
        sourceContainerBounds = in.readTypedObject(Rect.CREATOR);
        screenSpaceBounds = in.readTypedObject(Rect.CREATOR);
        windowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR);
        isNotInRecents = in.readBoolean();
        startLeash = in.readTypedObject(SurfaceControl.CREATOR);
        if (startLeash != null) {
            startLeash.setUnreleasedWarningCallSite("RemoteAnimationTarget[startLeash]");
        }
        startBounds = in.readTypedObject(Rect.CREATOR);
        taskInfo = in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
        allowEnterPip = in.readBoolean();
        windowType = in.readInt();
        hasAnimatingParent = in.readBoolean();
        backgroundColor = in.readInt();
        showBackdrop = in.readBoolean();
        willShowImeOnTarget = in.readBoolean();
        rotationChange = in.readInt();
    }

    public void setShowBackdrop(boolean shouldShowBackdrop) {
        showBackdrop = shouldShowBackdrop;
    }

    public void setWillShowImeOnTarget(boolean showImeOnTarget) {
        willShowImeOnTarget = showImeOnTarget;
    }

    public boolean willShowImeOnTarget() {
        return willShowImeOnTarget;
    }

    public void setRotationChange(int rotationChange) {
        this.rotationChange = rotationChange;
    }

    public int getRotationChange() {
        return rotationChange;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(taskId);
        dest.writeInt(mode);
        dest.writeTypedObject(leash, 0 /* flags */);
        dest.writeBoolean(isTranslucent);
        dest.writeTypedObject(clipRect, 0 /* flags */);
        dest.writeTypedObject(contentInsets, 0 /* flags */);
        dest.writeInt(prefixOrderIndex);
        dest.writeTypedObject(position, 0 /* flags */);
        dest.writeTypedObject(localBounds, 0 /* flags */);
        dest.writeTypedObject(sourceContainerBounds, 0 /* flags */);
        dest.writeTypedObject(screenSpaceBounds, 0 /* flags */);
        dest.writeTypedObject(windowConfiguration, 0 /* flags */);
        dest.writeBoolean(isNotInRecents);
        dest.writeTypedObject(startLeash, 0 /* flags */);
        dest.writeTypedObject(startBounds, 0 /* flags */);
        dest.writeTypedObject(taskInfo, 0 /* flags */);
        dest.writeBoolean(allowEnterPip);
        dest.writeInt(windowType);
        dest.writeBoolean(hasAnimatingParent);
        dest.writeInt(backgroundColor);
        dest.writeBoolean(showBackdrop);
        dest.writeBoolean(willShowImeOnTarget);
        dest.writeInt(rotationChange);
    }

    public void dump(PrintWriter pw, String prefix) {
        pw.print(prefix); pw.print("mode="); pw.print(mode);
        pw.print(" taskId="); pw.print(taskId);
        pw.print(" isTranslucent="); pw.print(isTranslucent);
        pw.print(" clipRect="); clipRect.printShortString(pw);
        pw.print(" contentInsets="); contentInsets.printShortString(pw);
        pw.print(" prefixOrderIndex="); pw.print(prefixOrderIndex);
        pw.print(" position="); printPoint(position, pw);
        pw.print(" sourceContainerBounds="); sourceContainerBounds.printShortString(pw);
        pw.print(" screenSpaceBounds="); screenSpaceBounds.printShortString(pw);
        pw.print(" localBounds="); localBounds.printShortString(pw);
        pw.println();
        pw.print(prefix); pw.print("windowConfiguration="); pw.println(windowConfiguration);
        pw.print(prefix); pw.print("leash="); pw.println(leash);
        pw.print(prefix); pw.print("taskInfo="); pw.println(taskInfo);
        pw.print(prefix); pw.print("allowEnterPip="); pw.println(allowEnterPip);
        pw.print(prefix); pw.print("windowType="); pw.println(windowType);
        pw.print(prefix); pw.print("hasAnimatingParent="); pw.println(hasAnimatingParent);
        pw.print(prefix); pw.print("backgroundColor="); pw.println(backgroundColor);
        pw.print(prefix); pw.print("showBackdrop="); pw.println(showBackdrop);
        pw.print(prefix); pw.print("willShowImeOnTarget="); pw.println(willShowImeOnTarget);
    }

    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
        final long token = proto.start(fieldId);
        proto.write(TASK_ID, taskId);
        proto.write(MODE, mode);
        leash.dumpDebug(proto, LEASH);
        proto.write(IS_TRANSLUCENT, isTranslucent);
        clipRect.dumpDebug(proto, CLIP_RECT);
        contentInsets.dumpDebug(proto, CONTENT_INSETS);
        proto.write(PREFIX_ORDER_INDEX, prefixOrderIndex);
        dumpPointProto(position, proto, POSITION);
        sourceContainerBounds.dumpDebug(proto, SOURCE_CONTAINER_BOUNDS);
        screenSpaceBounds.dumpDebug(proto, SCREEN_SPACE_BOUNDS);
        localBounds.dumpDebug(proto, LOCAL_BOUNDS);
        windowConfiguration.dumpDebug(proto, WINDOW_CONFIGURATION);
        if (startLeash != null) {
            startLeash.dumpDebug(proto, START_LEASH);
        }
        startBounds.dumpDebug(proto, START_BOUNDS);
        proto.end(token);
    }

    private static void printPoint(Point p, PrintWriter pw) {
        pw.print("["); pw.print(p.x); pw.print(","); pw.print(p.y); pw.print("]");
    }

    public static final @android.annotation.NonNull Creator<RemoteAnimationTarget> CREATOR
            = new Creator<RemoteAnimationTarget>() {
        public RemoteAnimationTarget createFromParcel(Parcel in) {
            return new RemoteAnimationTarget(in);
        }

        public RemoteAnimationTarget[] newArray(int size) {
            return new RemoteAnimationTarget[size];
        }
    };
}
