/*
 * Copyright (C) 2020 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.location.gnss.hal;

import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_ALTITUDE;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_BEARING;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_BEARING_ACCURACY;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_HORIZONTAL_ACCURACY;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_LAT_LONG;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_SPEED;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_SPEED_ACCURACY;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_LOCATION_HAS_VERTICAL_ACCURACY;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_REALTIME_HAS_TIMESTAMP_NS;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_REALTIME_HAS_TIME_UNCERTAINTY_NS;
import static com.android.server.location.gnss.hal.GnssNative.GeofenceCallbacks.GEOFENCE_STATUS_ERROR_ID_EXISTS;
import static com.android.server.location.gnss.hal.GnssNative.GeofenceCallbacks.GEOFENCE_STATUS_ERROR_ID_UNKNOWN;
import static com.android.server.location.gnss.hal.GnssNative.GeofenceCallbacks.GEOFENCE_STATUS_OPERATION_SUCCESS;
import static com.android.server.location.gnss.hal.GnssNative.GeofenceCallbacks.GEOFENCE_TRANSITION_ENTERED;
import static com.android.server.location.gnss.hal.GnssNative.GeofenceCallbacks.GEOFENCE_TRANSITION_EXITED;

import android.annotation.Nullable;
import android.location.GnssAntennaInfo;
import android.location.GnssMeasurementCorrections;
import android.location.GnssMeasurementsEvent;
import android.location.GnssNavigationMessage;
import android.location.Location;

import com.android.server.location.gnss.GnssPowerStats;
import com.android.server.location.gnss.hal.GnssNative.GnssLocationFlags;
import com.android.server.location.gnss.hal.GnssNative.GnssRealtimeFlags;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

/**
 * Fake GNSS HAL for testing.
 */
public final class FakeGnssHal extends GnssNative.GnssHal {

    public static class GnssHalPositionMode {

        public final int Mode;
        public final int Recurrence;
        public final int MinInterval;
        public final int PreferredAccuracy;
        public final int PreferredTime;
        public final boolean LowPowerMode;

        GnssHalPositionMode() {
            Mode = 0;
            Recurrence = 0;
            MinInterval = 0;
            PreferredAccuracy = 0;
            PreferredTime = 0;
            LowPowerMode = false;
        }

        public GnssHalPositionMode(int mode, int recurrence, int minInterval, int preferredAccuracy,
                int preferredTime, boolean lowPowerMode) {
            Mode = mode;
            Recurrence = recurrence;
            MinInterval = minInterval;
            PreferredAccuracy = preferredAccuracy;
            PreferredTime = preferredTime;
            LowPowerMode = lowPowerMode;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            GnssHalPositionMode that = (GnssHalPositionMode) o;
            return Mode == that.Mode
                    && Recurrence == that.Recurrence
                    && MinInterval == that.MinInterval
                    && PreferredAccuracy == that.PreferredAccuracy
                    && PreferredTime == that.PreferredTime
                    && LowPowerMode == that.LowPowerMode;
        }

        @Override
        public int hashCode() {
            return Objects.hash(Recurrence, MinInterval);
        }
    }

    public static class GnssHalBatchingMode {

        public final long PeriodNanos;
        public final float MinUpdateDistanceMeters;
        public final boolean WakeOnFifoFull;

        GnssHalBatchingMode() {
            PeriodNanos = 0;
            MinUpdateDistanceMeters = 0.0f;
            WakeOnFifoFull = false;
        }

        public GnssHalBatchingMode(long periodNanos, float minUpdateDistanceMeters,
                boolean wakeOnFifoFull) {
            PeriodNanos = periodNanos;
            MinUpdateDistanceMeters = minUpdateDistanceMeters;
            WakeOnFifoFull = wakeOnFifoFull;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            GnssHalBatchingMode that = (GnssHalBatchingMode) o;
            return PeriodNanos == that.PeriodNanos
                    && MinUpdateDistanceMeters == that.MinUpdateDistanceMeters
                    && WakeOnFifoFull == that.WakeOnFifoFull;
        }

        @Override
        public int hashCode() {
            return Objects.hash(PeriodNanos, MinUpdateDistanceMeters, WakeOnFifoFull);
        }
    }

    public static class GnssHalInjectedTime {

        public final long Time;
        public final long TimeReference;
        public final int Uncertainty;

        public GnssHalInjectedTime(long time, long timeReference, int uncertainty) {
            Time = time;
            TimeReference = timeReference;
            Uncertainty = uncertainty;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            GnssHalInjectedTime that = (GnssHalInjectedTime) o;
            return Time == that.Time
                    && TimeReference == that.TimeReference
                    && Uncertainty == that.Uncertainty;
        }

        @Override
        public int hashCode() {
            return Objects.hash(Time);
        }
    }

    public static class GnssHalGeofence {

        public final int GeofenceId;
        public final Location Center;
        public final double Radius;
        public int LastTransition;
        public int MonitorTransitions;
        public final int NotificationResponsiveness;
        public final int UnknownTimer;
        public boolean Paused;

        public GnssHalGeofence(int geofenceId, double latitude, double longitude, double radius,
                int lastTransition, int monitorTransitions, int notificationResponsiveness,
                int unknownTimer, boolean paused) {
            GeofenceId = geofenceId;
            Center = new Location("");
            Center.setLatitude(latitude);
            Center.setLongitude(longitude);
            Radius = radius;
            LastTransition = lastTransition;
            MonitorTransitions = monitorTransitions;
            NotificationResponsiveness = notificationResponsiveness;
            UnknownTimer = unknownTimer;
            Paused = paused;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            GnssHalGeofence that = (GnssHalGeofence) o;
            return GeofenceId == that.GeofenceId
                    && Double.compare(that.Radius, Radius) == 0
                    && LastTransition == that.LastTransition
                    && MonitorTransitions == that.MonitorTransitions
                    && NotificationResponsiveness == that.NotificationResponsiveness
                    && UnknownTimer == that.UnknownTimer
                    && Paused == that.Paused
                    && Center.equals(that.Center);
        }

        @Override
        public int hashCode() {
            return Objects.hash(GeofenceId);
        }
    }

    private static class HalState {
        private boolean mStarted = false;
        private boolean mBatchingStarted = false;
        private boolean mNavigationMessagesStarted = false;
        private boolean mAntennaInfoListeningStarted = false;
        private boolean mMeasurementCollectionStarted = false;
        private boolean mMeasurementCollectionFullTracking = false;
        private boolean mMeasurementCollectionCorrVecOutputsEnabled = false;
        private int mMeasurementCollectionIntervalMillis = 0;
        private GnssHalPositionMode mPositionMode = new GnssHalPositionMode();
        private GnssHalBatchingMode mBatchingMode = new GnssHalBatchingMode();
        private final ArrayList<Location> mBatchedLocations = new ArrayList<>();
        private Location mInjectedLocation = null;
        private Location mInjectedBestLocation = null;
        private GnssHalInjectedTime mInjectedTime = null;
        private GnssMeasurementCorrections mInjectedMeasurementCorrections = null;
        private final HashMap<Integer, GnssHalGeofence> mGeofences = new HashMap<>();
        private GnssPowerStats mPowerStats = new GnssPowerStats(0, 0, 0, 0, 0, 0, 0, 0,
                new double[0]);
    }

    private @Nullable GnssNative mGnssNative;
    private HalState mState = new HalState();

    private boolean mIsNavigationMessageCollectionSupported = true;
    private boolean mIsAntennaInfoListeningSupported = true;
    private boolean mIsMeasurementSupported = true;
    private boolean mIsMeasurementCorrectionsSupported = true;
    private int mBatchSize = 0;
    private boolean mIsGeofencingSupported = true;
    private boolean mIsVisibilityControlSupported = true;

    public FakeGnssHal() {}

    public void restartHal() {
        mState = new HalState();
        Objects.requireNonNull(mGnssNative).restartHal();
    }

    public void setIsNavigationMessageCollectionSupported(boolean supported) {
        mIsNavigationMessageCollectionSupported = supported;
    }

    public void setIsAntennaInfoListeningSupported(boolean supported) {
        mIsAntennaInfoListeningSupported = supported;
    }

    public void setIsMeasurementSupported(boolean supported) {
        mIsMeasurementSupported = supported;
    }

    public void setIsMeasurementCorrectionsSupported(boolean supported) {
        mIsMeasurementCorrectionsSupported = supported;
    }

    public void setBatchSize(int batchSize) {
        mBatchSize = batchSize;
    }

    public void setIsGeofencingSupported(boolean supported) {
        mIsGeofencingSupported = supported;
    }

    public void setPowerStats(GnssPowerStats powerStats) {
        mState.mPowerStats = powerStats;
    }

    public void setIsVisibilityControlSupported(boolean supported) {
        mIsVisibilityControlSupported = supported;
    }

    public GnssHalPositionMode getPositionMode() {
        return mState.mPositionMode;
    }

    public void reportLocation(Location location) {
        if (mState.mStarted) {
            Objects.requireNonNull(mGnssNative).reportLocation(true, location);
        }
        if (mState.mBatchingStarted) {
            mState.mBatchedLocations.add(location);
            if (mState.mBatchedLocations.size() >= mBatchSize) {
                if (mState.mBatchingMode.WakeOnFifoFull) {
                    flushBatch();
                } else {
                    mState.mBatchedLocations.remove(0);
                }
            }
        }
        for (GnssHalGeofence geofence : mState.mGeofences.values()) {
            if (!geofence.Paused) {
                if (geofence.Center.distanceTo(location) > geofence.Radius) {
                    if (geofence.LastTransition != GEOFENCE_TRANSITION_EXITED) {
                        geofence.LastTransition = GEOFENCE_TRANSITION_EXITED;
                        if ((geofence.MonitorTransitions & GEOFENCE_TRANSITION_EXITED) != 0) {
                            Objects.requireNonNull(mGnssNative).reportGeofenceTransition(
                                    geofence.GeofenceId, location, GEOFENCE_TRANSITION_EXITED,
                                    location.getTime());
                        }
                    }
                } else {
                    if (geofence.LastTransition != GEOFENCE_TRANSITION_ENTERED) {
                        geofence.LastTransition = GEOFENCE_TRANSITION_ENTERED;
                        if ((geofence.MonitorTransitions & GEOFENCE_TRANSITION_ENTERED) != 0) {
                            Objects.requireNonNull(mGnssNative).reportGeofenceTransition(
                                    geofence.GeofenceId, location, GEOFENCE_TRANSITION_ENTERED,
                                    location.getTime());
                        }
                    }
                }
            }
        }
    }

    public void reportNavigationMessage(GnssNavigationMessage message) {
        if (mState.mNavigationMessagesStarted) {
            Objects.requireNonNull(mGnssNative).reportNavigationMessage(message);
        }
    }

    public void reportAntennaInfo(List<GnssAntennaInfo> antennaInfos) {
        if (mState.mAntennaInfoListeningStarted) {
            Objects.requireNonNull(mGnssNative).reportAntennaInfo(antennaInfos);
        }
    }

    public boolean isMeasurementCollectionFullTracking() {
        return mState.mMeasurementCollectionFullTracking;
    }

    public void reportMeasurement(GnssMeasurementsEvent event) {
        if (mState.mMeasurementCollectionStarted) {
            Objects.requireNonNull(mGnssNative).reportMeasurementData(event);
        }
    }

    public GnssHalInjectedTime getLastInjectedTime() {
        return mState.mInjectedTime;
    }

    public GnssMeasurementCorrections getLastInjectedCorrections() {
        return mState.mInjectedMeasurementCorrections;
    }

    public Collection<GnssHalGeofence> getGeofences() {
        return mState.mGeofences.values();
    }

    @Override
    protected void classInitOnce() {}

    @Override
    protected boolean isSupported() {
        return true;
    }

    @Override
    protected void initOnce(GnssNative gnssNative, boolean reinitializeGnssServiceHandle) {
        mGnssNative = Objects.requireNonNull(gnssNative);
    }

    @Override
    protected boolean init() {
        return true;
    }

    @Override
    protected void cleanup() {}

    @Override
    protected boolean start() {
        mState.mStarted = true;
        return true;
    }

    @Override
    protected boolean stop() {
        mState.mStarted = false;
        return true;
    }

    @Override
    protected boolean setPositionMode(int mode, int recurrence, int minInterval,
            int preferredAccuracy, int preferredTime, boolean lowPowerMode) {
        mState.mPositionMode = new GnssHalPositionMode(mode, recurrence, minInterval,
                preferredAccuracy, preferredTime, lowPowerMode);
        return true;
    }

    @Override
    protected String getInternalState() {
        return "DebugState";
    }

    @Override
    protected void deleteAidingData(int flags) {}

    @Override
    protected int readNmea(byte[] buffer, int bufferSize) {
        return 0;
    }

    @Override
    protected void injectLocation(@GnssLocationFlags int gnssLocationFlags, double latitude,
            double longitude, double altitude, float speed, float bearing, float horizontalAccuracy,
            float verticalAccuracy, float speedAccuracy, float bearingAccuracy, long timestamp,
            @GnssRealtimeFlags int elapsedRealtimeFlags, long elapsedRealtimeNanos,
            double elapsedRealtimeUncertaintyNanos) {
        mState.mInjectedLocation = new Location("injected");
        mState.mInjectedLocation.setLatitude(latitude);
        mState.mInjectedLocation.setLongitude(longitude);
        mState.mInjectedLocation.setAccuracy(horizontalAccuracy);
    }

    @Override
    protected void injectBestLocation(@GnssLocationFlags int gnssLocationFlags, double latitude,
            double longitude, double altitude, float speed, float bearing, float horizontalAccuracy,
            float verticalAccuracy, float speedAccuracy, float bearingAccuracy, long timestamp,
            @GnssRealtimeFlags int elapsedRealtimeFlags, long elapsedRealtimeNanos,
            double elapsedRealtimeUncertaintyNanos) {
        mState.mInjectedBestLocation = new Location("injectedBest");
        if ((gnssLocationFlags & GNSS_LOCATION_HAS_LAT_LONG) != 0) {
            mState.mInjectedBestLocation.setLatitude(latitude);
            mState.mInjectedBestLocation.setLongitude(longitude);
        }
        if ((gnssLocationFlags & GNSS_LOCATION_HAS_ALTITUDE) != 0) {
            mState.mInjectedBestLocation.setAltitude(altitude);
        }
        if ((gnssLocationFlags & GNSS_LOCATION_HAS_SPEED) != 0) {
            mState.mInjectedBestLocation.setSpeed(speed);
        }
        if ((gnssLocationFlags & GNSS_LOCATION_HAS_BEARING) != 0) {
            mState.mInjectedBestLocation.setBearing(bearing);
        }
        if ((gnssLocationFlags & GNSS_LOCATION_HAS_HORIZONTAL_ACCURACY) != 0) {
            mState.mInjectedBestLocation.setAccuracy(horizontalAccuracy);
        }
        if ((gnssLocationFlags & GNSS_LOCATION_HAS_VERTICAL_ACCURACY) != 0) {
            mState.mInjectedBestLocation.setVerticalAccuracyMeters(verticalAccuracy);
        }
        if ((gnssLocationFlags & GNSS_LOCATION_HAS_SPEED_ACCURACY) != 0) {
            mState.mInjectedBestLocation.setSpeedAccuracyMetersPerSecond(speedAccuracy);
        }
        if ((gnssLocationFlags & GNSS_LOCATION_HAS_BEARING_ACCURACY) != 0) {
            mState.mInjectedBestLocation.setBearingAccuracyDegrees(bearingAccuracy);
        }
        mState.mInjectedBestLocation.setTime(timestamp);
        if ((elapsedRealtimeFlags & GNSS_REALTIME_HAS_TIMESTAMP_NS) != 0) {
            mState.mInjectedBestLocation.setElapsedRealtimeNanos(elapsedRealtimeNanos);
        }
        if ((elapsedRealtimeFlags & GNSS_REALTIME_HAS_TIME_UNCERTAINTY_NS) != 0) {
            mState.mInjectedBestLocation.setElapsedRealtimeUncertaintyNanos(
                    elapsedRealtimeUncertaintyNanos);
        }
    }

    @Override
    protected void injectTime(long time, long timeReference, int uncertainty) {
        mState.mInjectedTime = new GnssHalInjectedTime(time, timeReference, uncertainty);
    }

    @Override
    protected boolean isNavigationMessageCollectionSupported() {
        return mIsNavigationMessageCollectionSupported;
    }

    @Override
    protected boolean startNavigationMessageCollection() {
        mState.mNavigationMessagesStarted = true;
        return true;
    }

    @Override
    protected boolean stopNavigationMessageCollection() {
        mState.mNavigationMessagesStarted = false;
        return true;
    }

    @Override
    protected boolean isAntennaInfoSupported() {
        return mIsAntennaInfoListeningSupported;
    }

    @Override
    protected boolean startAntennaInfoListening() {
        mState.mAntennaInfoListeningStarted = true;
        return true;
    }

    @Override
    protected boolean stopAntennaInfoListening() {
        mState.mAntennaInfoListeningStarted = false;
        return true;
    }

    @Override
    protected boolean isMeasurementSupported() {
        return mIsMeasurementSupported;
    }

    @Override
    protected boolean startMeasurementCollection(boolean enableFullTracking,
            boolean enableCorrVecOutputs, int intervalMillis) {
        mState.mMeasurementCollectionStarted = true;
        mState.mMeasurementCollectionFullTracking = enableFullTracking;
        mState.mMeasurementCollectionCorrVecOutputsEnabled = enableCorrVecOutputs;
        mState.mMeasurementCollectionIntervalMillis = intervalMillis;
        return true;
    }

    @Override
    protected boolean stopMeasurementCollection() {
        mState.mMeasurementCollectionStarted = false;
        mState.mMeasurementCollectionFullTracking = false;
        mState.mMeasurementCollectionCorrVecOutputsEnabled = false;
        mState.mMeasurementCollectionIntervalMillis = 0;
        return true;
    }

    @Override
    protected boolean isMeasurementCorrectionsSupported() {
        return mIsMeasurementCorrectionsSupported;
    }

    @Override
    protected boolean injectMeasurementCorrections(GnssMeasurementCorrections corrections) {
        mState.mInjectedMeasurementCorrections = corrections;
        return true;
    }

    @Override
    protected boolean startSvStatusCollection() {
        return true;
    }

    @Override
    protected boolean stopSvStatusCollection() {
        return true;
    }

    @Override
    public boolean startNmeaMessageCollection() {
        return true;
    }

    @Override
    public boolean stopNmeaMessageCollection() {
        return true;
    }

    @Override
    protected int getBatchSize() {
        return mBatchSize;
    }

    @Override
    protected boolean initBatching() {
        return true;
    }

    @Override
    protected void cleanupBatching() {}

    @Override
    protected boolean startBatch(long periodNanos, float minUpdateDistanceMeters,
            boolean wakeOnFifoFull) {
        mState.mBatchingStarted = true;
        mState.mBatchingMode = new GnssHalBatchingMode(periodNanos, minUpdateDistanceMeters,
                wakeOnFifoFull);
        return true;
    }

    @Override
    protected void flushBatch() {
        Location[] locations = mState.mBatchedLocations.toArray(new Location[0]);
        mState.mBatchedLocations.clear();
        Objects.requireNonNull(mGnssNative).reportLocationBatch(locations);
    }

    @Override
    protected void stopBatch() {
        mState.mBatchingStarted = false;
        mState.mBatchingMode = new GnssHalBatchingMode();
        mState.mBatchedLocations.clear();
    }

    @Override
    protected boolean isGeofencingSupported() {
        return mIsGeofencingSupported;
    }

    @Override
    protected boolean addGeofence(int geofenceId, double latitude, double longitude, double radius,
            int lastTransition, int monitorTransitions, int notificationResponsiveness,
            int unknownTimer) {
        if (mState.mGeofences.containsKey(geofenceId)) {
            Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
                    GEOFENCE_STATUS_ERROR_ID_EXISTS);
        } else {
            mState.mGeofences.put(geofenceId,
                    new GnssHalGeofence(geofenceId, latitude, longitude, radius, lastTransition,
                            monitorTransitions, notificationResponsiveness, unknownTimer, false));
            Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
                    GEOFENCE_STATUS_OPERATION_SUCCESS);
        }
        return true;
    }

    @Override
    protected boolean resumeGeofence(int geofenceId, int monitorTransitions) {
        GnssHalGeofence geofence = mState.mGeofences.get(geofenceId);
        if (geofence != null) {
            geofence.Paused = false;
            geofence.MonitorTransitions = monitorTransitions;
            Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
                    GEOFENCE_STATUS_OPERATION_SUCCESS);
        } else {
            Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
                    GEOFENCE_STATUS_ERROR_ID_UNKNOWN);
        }
        return true;
    }

    @Override
    protected boolean pauseGeofence(int geofenceId) {
        GnssHalGeofence geofence = mState.mGeofences.get(geofenceId);
        if (geofence != null) {
            geofence.Paused = true;
            Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
                    GEOFENCE_STATUS_OPERATION_SUCCESS);
        } else {
            Objects.requireNonNull(mGnssNative).reportGeofenceAddStatus(geofenceId,
                    GEOFENCE_STATUS_ERROR_ID_UNKNOWN);
        }
        return true;
    }

    @Override
    protected boolean removeGeofence(int geofenceId) {
        if (mState.mGeofences.remove(geofenceId) != null) {
            Objects.requireNonNull(mGnssNative).reportGeofenceRemoveStatus(geofenceId,
                    GEOFENCE_STATUS_OPERATION_SUCCESS);
        } else {
            Objects.requireNonNull(mGnssNative).reportGeofenceRemoveStatus(geofenceId,
                    GEOFENCE_STATUS_ERROR_ID_UNKNOWN);
        }
        return true;
    }

    @Override
    protected boolean isGnssVisibilityControlSupported() {
        return mIsVisibilityControlSupported;
    }

    @Override
    protected void requestPowerStats() {
        Objects.requireNonNull(mGnssNative).reportGnssPowerStats(mState.mPowerStats);
    }

    @Override
    protected void setAgpsServer(int type, String hostname, int port) {}

    @Override
    protected void setAgpsSetId(int type, String setId) {}

    @Override
    protected void setAgpsReferenceLocationCellId(int type, int mcc, int mnc, int lac, long cid,
            int tac, int pcid, int arfcn) {}

    @Override
    protected boolean isPsdsSupported() {
        return true;
    }

    @Override
    protected void injectPsdsData(byte[] data, int length, int psdsType) {}
}
