/*
 * Copyright (C) 2011 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 android.compat.annotation.UnsupportedAppUsage;
import android.graphics.FrameInfo;
import android.os.Build;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.WeaklyReferencedCallback;

import dalvik.annotation.optimization.FastNative;

import libcore.util.NativeAllocationRegistry;

import java.lang.ref.WeakReference;

/**
 * Provides a low-level mechanism for an application to receive display events
 * such as vertical sync.
 *
 * The display event receive is NOT thread safe.  Moreover, its methods must only
 * be called on the Looper thread to which it is attached.
 *
 * @hide
 */
@WeaklyReferencedCallback
public abstract class DisplayEventReceiver {

    /**
     * When retrieving vsync events, this specifies that the vsync event should happen at the normal
     * vsync-app tick.
     * <p>
     * Keep in sync with frameworks/native/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
     */
    public static final int VSYNC_SOURCE_APP = 0;

    /**
     * When retrieving vsync events, this specifies that the vsync event should happen whenever
     * Surface Flinger is processing a frame.
     * <p>
     * Keep in sync with frameworks/native/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
     */
    public static final int VSYNC_SOURCE_SURFACE_FLINGER = 1;

    /**
     * Specifies to generate mode changed events from Surface Flinger.
     * <p>
     * Keep in sync with frameworks/native/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
     */
    public static final int EVENT_REGISTRATION_MODE_CHANGED_FLAG = 0x1;

    /**
     * Specifies to generate frame rate override events from Surface Flinger.
     * <p>
     * Keep in sync with frameworks/native/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
     */
    public static final int EVENT_REGISTRATION_FRAME_RATE_OVERRIDE_FLAG = 0x2;

    private static final String TAG = "DisplayEventReceiver";

    @UnsupportedAppUsage
    private long mReceiverPtr;

    // We keep a reference message queue object here so that it is not
    // GC'd while the native peer of the receiver is using them.
    private MessageQueue mMessageQueue;

    private final VsyncEventData mVsyncEventData = new VsyncEventData();

    private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
            WeakReference<VsyncEventData> vsyncEventData,
            MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle);
    private static native long nativeGetDisplayEventReceiverFinalizer();
    @FastNative
    private static native void nativeScheduleVsync(long receiverPtr);
    private static native VsyncEventData nativeGetLatestVsyncEventData(long receiverPtr);

    private static final NativeAllocationRegistry sNativeAllocationRegistry =
            NativeAllocationRegistry.createMalloced(
                    DisplayEventReceiver.class.getClassLoader(),
                    nativeGetDisplayEventReceiverFinalizer());
    private Runnable mFreeNativeResources;

    /**
     * Creates a display event receiver.
     *
     * @param looper The looper to use when invoking callbacks.
     */
    @UnsupportedAppUsage
    public DisplayEventReceiver(Looper looper) {
        this(looper, VSYNC_SOURCE_APP, /* eventRegistration */ 0, /* layerHandle */ 0L);
    }

    public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration) {
        this(looper, vsyncSource, eventRegistration, /* layerHandle */ 0L);
    }

    /**
     * Creates a display event receiver.
     *
     * @param looper The looper to use when invoking callbacks.
     * @param vsyncSource The source of the vsync tick. Must be on of the VSYNC_SOURCE_* values.
     * @param eventRegistration Which events to dispatch. Must be a bitfield consist of the
     * EVENT_REGISTRATION_*_FLAG values.
     * @param layerHandle Layer to which the current instance is attached to
     */
    public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration,
            long layerHandle) {
        if (looper == null) {
            throw new IllegalArgumentException("looper must not be null");
        }

        mMessageQueue = looper.getQueue();
        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this),
                new WeakReference<VsyncEventData>(mVsyncEventData),
                mMessageQueue,
                vsyncSource, eventRegistration, layerHandle);
        mFreeNativeResources = sNativeAllocationRegistry.registerNativeAllocation(this,
                mReceiverPtr);
    }

    /**
     * Disposes the receiver.
     */
    public void dispose() {
        if (mReceiverPtr != 0) {
            mFreeNativeResources.run();
            mReceiverPtr = 0;
        }
        mMessageQueue = null;
    }

    /**
     * Class to capture all inputs required for syncing events data.
     *
     * @hide
     */
    public static final class VsyncEventData {
        // The max capacity of frame timeline choices.
        // Must be in sync with VsyncEventData::kFrameTimelinesCapacity in
        // frameworks/native/libs/gui/include/gui/VsyncEventData.h
        static final int FRAME_TIMELINES_CAPACITY = 7;

        public static class FrameTimeline {
            FrameTimeline() {
                // Some reasonable values (+10 ms) for default timestamps.
                deadline = System.nanoTime() + 10_000_000;
                expectedPresentationTime = deadline + 10_000_000;
            }

            // Called from native code.
            @SuppressWarnings("unused")
            FrameTimeline(long vsyncId, long expectedPresentationTime, long deadline) {
                this.vsyncId = vsyncId;
                this.expectedPresentationTime = expectedPresentationTime;
                this.deadline = deadline;
            }

            void copyFrom(FrameTimeline other) {
                vsyncId = other.vsyncId;
                expectedPresentationTime = other.expectedPresentationTime;
                deadline = other.deadline;
            }

            // The frame timeline vsync id, used to correlate a frame
            // produced by HWUI with the timeline data stored in Surface Flinger.
            public long vsyncId = FrameInfo.INVALID_VSYNC_ID;

            // The frame timestamp for when the frame is expected to be presented.
            public long expectedPresentationTime;

            // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is
            // allotted for the frame to be completed.
            public long deadline;
        }

        /**
         * The current interval between frames in ns. This will be used to align
         * {@link FrameInfo#VSYNC} to the current vsync in case Choreographer callback was heavily
         * delayed by the app.
         */
        public long frameInterval = -1;

        public final FrameTimeline[] frameTimelines;

        public int preferredFrameTimelineIndex = 0;

        // The default FrameTimeline is a placeholder populated with invalid vsync ID and some
        // reasonable timestamps.
        public int frameTimelinesLength = 1;

        VsyncEventData() {
            frameTimelines = new FrameTimeline[FRAME_TIMELINES_CAPACITY];
            for (int i = 0; i < frameTimelines.length; i++) {
                frameTimelines[i] = new FrameTimeline();
            }
        }

        // Called from native code.
        @SuppressWarnings("unused")
        VsyncEventData(FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex,
                int frameTimelinesLength, long frameInterval) {
            this.frameTimelines = frameTimelines;
            this.preferredFrameTimelineIndex = preferredFrameTimelineIndex;
            this.frameTimelinesLength = frameTimelinesLength;
            this.frameInterval = frameInterval;
        }

        void copyFrom(VsyncEventData other) {
            preferredFrameTimelineIndex = other.preferredFrameTimelineIndex;
            frameTimelinesLength = other.frameTimelinesLength;
            frameInterval = other.frameInterval;
            for (int i = 0; i < frameTimelines.length; i++) {
                frameTimelines[i].copyFrom(other.frameTimelines[i]);
            }
        }

        public FrameTimeline preferredFrameTimeline() {
            return frameTimelines[preferredFrameTimelineIndex];
        }
    }

    /**
     * Called when a vertical sync pulse is received.
     * The recipient should render a frame and then call {@link #scheduleVsync}
     * to schedule the next vertical sync pulse.
     *
     * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
     * timebase.
     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
     * @param frame The frame number.  Increases by one for each vertical sync interval.
     * @param vsyncEventData The vsync event data.
     */
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
            VsyncEventData vsyncEventData) {
    }

    /**
     * Called when a display hotplug event is received.
     *
     * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
     * timebase.
     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
     * @param connected True if the display is connected, false if it disconnected.
     */
    @UnsupportedAppUsage
    public void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
    }

    /**
     * Called when a display hotplug event with connection error is received.
     *
     * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
     * timebase.
     * @param connectionError the hotplug connection error code.
     */
    public void onHotplugConnectionError(long timestampNanos, int connectionError) {
    }

    /**
     * Called when a display mode changed event is received.
     *
     * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
     * timebase.
     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
     * @param modeId The new mode Id
     * @param renderPeriod The render frame period, which is a multiple of the mode's vsync period
     */
    public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
            long renderPeriod) {
    }

    /**
     * Called when a display hdcp levels change event is received.
     *
     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
     * @param connectedLevel the new connected HDCP level
     * @param maxLevel the maximum HDCP level
     */
    public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
    }

    /**
     * Represents a mapping between a UID and an override frame rate
     */
    public static class FrameRateOverride {
        // The application uid
        public final int uid;

        // The frame rate that this application runs at
        public final float frameRateHz;


        @VisibleForTesting
        public FrameRateOverride(int uid, float frameRateHz) {
            this.uid = uid;
            this.frameRateHz = frameRateHz;
        }

        @Override
        public String toString() {
            return "{uid=" + uid + " frameRateHz=" + frameRateHz + "}";
        }
    }

    /**
     * Called when frame rate override event is received.
     *
     * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
     * timebase.
     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
     * @param overrides The mappings from uid to frame rates
     */
    public void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
            FrameRateOverride[] overrides) {
    }

    /**
     * Schedules a single vertical sync pulse to be delivered when the next
     * display frame begins.
     */
    @UnsupportedAppUsage
    public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
            nativeScheduleVsync(mReceiverPtr);
        }
    }

    /**
     * Gets the latest vsync event data from surface flinger.
     */
    VsyncEventData getLatestVsyncEventData() {
        return nativeGetLatestVsyncEventData(mReceiverPtr);
    }

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
        onVsync(timestampNanos, physicalDisplayId, frame, mVsyncEventData);
    }

    // Called from native code.
    @SuppressWarnings("unused")
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private void dispatchHotplug(long timestampNanos, long physicalDisplayId, boolean connected) {
        onHotplug(timestampNanos, physicalDisplayId, connected);
    }

    @SuppressWarnings("unused")
    private void dispatchHotplugConnectionError(long timestampNanos, int connectionError) {
        onHotplugConnectionError(timestampNanos, connectionError);
    }

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
            long renderPeriod) {
        onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
    }

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchFrameRateOverrides(long timestampNanos, long physicalDisplayId,
            FrameRateOverride[] overrides) {
        onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
    }

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchHdcpLevelsChanged(long physicalDisplayId, int connectedLevel,
            int maxLevel) {
        onHdcpLevelsChanged(physicalDisplayId, connectedLevel, maxLevel);
    }

}
