/*
 * Copyright (C) 2019 The Linux Foundation
 * Copyright (C) 2023 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.bluetooth;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Objects;

/**
 * This class provides the System APIs to access the data of BQR event reported from firmware side.
 * Currently it supports five event types: Quality monitor event, Approaching LSTO event, A2DP
 * choppy event, SCO choppy event and Connect fail event. To know which kind of event is wrapped in
 * this {@link BluetoothQualityReport} object, you need to call {@link #getQualityReportId}.
 *
 * <ul>
 *   <li>For Quality monitor event, you can call {@link #getBqrCommon} to get a {@link
 *       BluetoothQualityReport.BqrCommon} object.
 *   <li>For Approaching LSTO event, you can call {@link #getBqrCommon} to get a {@link
 *       BluetoothQualityReport.BqrCommon} object, and call {@link #getBqrEvent} to get a {@link
 *       BluetoothQualityReport.BqrVsLsto} object.
 *   <li>For A2DP choppy event, you can call {@link #getBqrCommon} to get a {@link
 *       BluetoothQualityReport.BqrCommon} object, and call {@link #getBqrEvent} to get a {@link
 *       BluetoothQualityReport.BqrVsA2dpChoppy} object.
 *   <li>For SCO choppy event, you can call {@link #getBqrCommon} to get a {@link
 *       BluetoothQualityReport.BqrCommon} object, and call {@link #getBqrEvent} to get a {@link
 *       BluetoothQualityReport.BqrVsScoChoppy} object.
 *   <li>For Connect fail event, you can call {@link #getBqrCommon} to get a {@link
 *       BluetoothQualityReport.BqrCommon} object, and call {@link #getBqrEvent} to get a {@link
 *       BluetoothQualityReport.BqrConnectFail} object.
 * </ul>
 *
 * @hide
 */
@SystemApi
public final class BluetoothQualityReport implements Parcelable {
    private static final String TAG = BluetoothQualityReport.class.getSimpleName();

    /**
     * Quality report ID: Monitor.
     *
     * @hide
     */
    @SystemApi public static final int QUALITY_REPORT_ID_MONITOR = 0x01;

    /**
     * Quality report ID: Approaching LSTO.
     *
     * @hide
     */
    @SystemApi public static final int QUALITY_REPORT_ID_APPROACH_LSTO = 0x02;

    /**
     * Quality report ID: A2DP choppy.
     *
     * @hide
     */
    @SystemApi public static final int QUALITY_REPORT_ID_A2DP_CHOPPY = 0x03;

    /**
     * Quality report ID: SCO choppy.
     *
     * @hide
     */
    @SystemApi public static final int QUALITY_REPORT_ID_SCO_CHOPPY = 0x04;

    /**
     * Quality report ID: Connect Fail.
     *
     * @hide
     */
    @SystemApi public static final int QUALITY_REPORT_ID_CONN_FAIL = 0x08;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(
            prefix = {"QUALITY_REPORT_ID"},
            value = {
                QUALITY_REPORT_ID_MONITOR,
                QUALITY_REPORT_ID_APPROACH_LSTO,
                QUALITY_REPORT_ID_A2DP_CHOPPY,
                QUALITY_REPORT_ID_SCO_CHOPPY,
                QUALITY_REPORT_ID_CONN_FAIL,
            })
    public @interface QualityReportId {}

    private String mAddr;
    private int mLmpVer;
    private int mLmpSubVer;
    private int mManufacturerId;
    private String mName;
    private BluetoothClass mBluetoothClass;

    private BqrCommon mBqrCommon;
    private BqrVsLsto mBqrVsLsto;
    private BqrVsA2dpChoppy mBqrVsA2dpChoppy;
    private BqrVsScoChoppy mBqrVsScoChoppy;
    private BqrConnectFail mBqrConnectFail;

    enum PacketType {
        INVALID,
        TYPE_ID,
        TYPE_NULL,
        TYPE_POLL,
        TYPE_FHS,
        TYPE_HV1,
        TYPE_HV2,
        TYPE_HV3,
        TYPE_DV,
        TYPE_EV3,
        TYPE_EV4,
        TYPE_EV5,
        TYPE_2EV3,
        TYPE_2EV5,
        TYPE_3EV3,
        TYPE_3EV5,
        TYPE_DM1,
        TYPE_DH1,
        TYPE_DM3,
        TYPE_DH3,
        TYPE_DM5,
        TYPE_DH5,
        TYPE_AUX1,
        TYPE_2DH1,
        TYPE_2DH3,
        TYPE_2DH5,
        TYPE_3DH1,
        TYPE_3DH3,
        TYPE_3DH5;

        private static PacketType[] sAllValues = values();

        static PacketType fromOrdinal(int n) {
            if (n < sAllValues.length) {
                return sAllValues[n];
            }
            return INVALID;
        }
    }

    enum ConnState {
        CONN_IDLE(0x00),
        CONN_ACTIVE(0x81),
        CONN_HOLD(0x02),
        CONN_SNIFF_IDLE(0x03),
        CONN_SNIFF_ACTIVE(0x84),
        CONN_SNIFF_MASTER_TRANSITION(0x85),
        CONN_PARK(0x06),
        CONN_PARK_PEND(0x47),
        CONN_UNPARK_PEND(0x08),
        CONN_UNPARK_ACTIVE(0x89),
        CONN_DISCONNECT_PENDING(0x4A),
        CONN_PAGING(0x0B),
        CONN_PAGE_SCAN(0x0C),
        CONN_LOCAL_LOOPBACK(0x0D),
        CONN_LE_ACTIVE(0x0E),
        CONN_ANT_ACTIVE(0x0F),
        CONN_TRIGGER_SCAN(0x10),
        CONN_RECONNECTING(0x11),
        CONN_SEMI_CONN(0x12);

        private final int mValue;
        private static ConnState[] sAllStates = values();

        ConnState(int val) {
            mValue = val;
        }

        public static String toString(int val) {
            for (ConnState state : sAllStates) {
                if (state.mValue == val) {
                    return state.toString();
                }
            }
            return "INVALID";
        }
    }

    enum LinkQuality {
        ULTRA_HIGH,
        HIGH,
        STANDARD,
        MEDIUM,
        LOW,
        INVALID;

        private static LinkQuality[] sAllValues = values();

        static LinkQuality fromOrdinal(int n) {
            if (n < sAllValues.length - 1) {
                return sAllValues[n];
            }
            return INVALID;
        }
    }

    enum AirMode {
        uLaw,
        aLaw,
        CVSD,
        transparent_msbc,
        INVALID;

        private static AirMode[] sAllValues = values();

        static AirMode fromOrdinal(int n) {
            if (n < sAllValues.length - 1) {
                return sAllValues[n];
            }
            return INVALID;
        }
    }

    private BluetoothQualityReport(
            String remoteAddr,
            int lmpVer,
            int lmpSubVer,
            int manufacturerId,
            String remoteName,
            BluetoothClass bluetoothClass,
            byte[] rawData) {
        mAddr = remoteAddr;
        mLmpVer = lmpVer;
        mLmpSubVer = lmpSubVer;
        mManufacturerId = manufacturerId;
        mName = remoteName;
        mBluetoothClass = bluetoothClass;

        mBqrCommon = new BqrCommon(rawData, 0);
        int id = mBqrCommon.getQualityReportId();
        if (id == QUALITY_REPORT_ID_MONITOR) return;

        int vsPartOffset = BqrCommon.BQR_COMMON_LEN;
        if (id == QUALITY_REPORT_ID_APPROACH_LSTO) {
            mBqrVsLsto = new BqrVsLsto(rawData, vsPartOffset);
        } else if (id == QUALITY_REPORT_ID_A2DP_CHOPPY) {
            mBqrVsA2dpChoppy = new BqrVsA2dpChoppy(rawData, vsPartOffset);
        } else if (id == QUALITY_REPORT_ID_SCO_CHOPPY) {
            mBqrVsScoChoppy = new BqrVsScoChoppy(rawData, vsPartOffset);
        } else if (id == QUALITY_REPORT_ID_CONN_FAIL) {
            mBqrConnectFail = new BqrConnectFail(rawData, vsPartOffset);
        } else {
            throw new IllegalArgumentException(TAG + ": unknown quality report id:" + id);
        }
    }

    private BluetoothQualityReport(Parcel in) {
        mAddr = in.readString();
        mLmpVer = in.readInt();
        mLmpSubVer = in.readInt();
        mManufacturerId = in.readInt();
        mName = in.readString();
        mBluetoothClass = new BluetoothClass(in.readInt());

        mBqrCommon = new BqrCommon(in);
        int id = mBqrCommon.getQualityReportId();
        if (id == QUALITY_REPORT_ID_APPROACH_LSTO) {
            mBqrVsLsto = new BqrVsLsto(in);
        } else if (id == QUALITY_REPORT_ID_A2DP_CHOPPY) {
            mBqrVsA2dpChoppy = new BqrVsA2dpChoppy(in);
        } else if (id == QUALITY_REPORT_ID_SCO_CHOPPY) {
            mBqrVsScoChoppy = new BqrVsScoChoppy(in);
        } else if (id == QUALITY_REPORT_ID_CONN_FAIL) {
            mBqrConnectFail = new BqrConnectFail(in);
        }
    }

    /**
     * Get the quality report id.
     *
     * @hide
     */
    @SystemApi
    @QualityReportId
    public int getQualityReportId() {
        return mBqrCommon.getQualityReportId();
    }

    /**
     * Get the string of the quality report id.
     *
     * @return the string of the id
     * @hide
     */
    @SystemApi
    public static @NonNull String qualityReportIdToString(@QualityReportId int id) {
        return BqrCommon.qualityReportIdToString(id);
    }

    /**
     * Get bluetooth address of remote device in this report.
     *
     * @return bluetooth address of remote device
     * @hide
     */
    @SystemApi
    public @Nullable String getRemoteAddress() {
        return mAddr;
    }

    /**
     * Get LMP version of remote device in this report.
     *
     * @return LMP version of remote device
     * @hide
     */
    @SystemApi
    public int getLmpVersion() {
        return mLmpVer;
    }

    /**
     * Get LMP subVersion of remote device in this report.
     *
     * @return LMP subVersion of remote device
     * @hide
     */
    @SystemApi
    public int getLmpSubVersion() {
        return mLmpSubVer;
    }

    /**
     * Get manufacturer id of remote device in this report.
     *
     * @return manufacturer id of remote device
     * @hide
     */
    @SystemApi
    public int getManufacturerId() {
        return mManufacturerId;
    }

    /**
     * Get the name of remote device in this report.
     *
     * @return the name of remote device
     * @hide
     */
    @SystemApi
    public @Nullable String getRemoteName() {
        return mName;
    }

    /**
     * Get the class of remote device in this report.
     *
     * @return the class of remote device
     * @hide
     */
    @SystemApi
    public @Nullable BluetoothClass getBluetoothClass() {
        return mBluetoothClass;
    }

    /**
     * Get the {@link BluetoothQualityReport.BqrCommon} object.
     *
     * @return the {@link BluetoothQualityReport.BqrCommon} object.
     * @hide
     */
    @SystemApi
    public @Nullable BqrCommon getBqrCommon() {
        return mBqrCommon;
    }

    /**
     * Get the event data object based on current Quality Report Id. If the report id is {@link
     * #QUALITY_REPORT_ID_MONITOR}, this returns a {@link BluetoothQualityReport.BqrCommon} object.
     * If the report id is {@link #QUALITY_REPORT_ID_APPROACH_LSTO}, this returns a {@link
     * BluetoothQualityReport.BqrVsLsto} object. If the report id is {@link
     * #QUALITY_REPORT_ID_A2DP_CHOPPY}, this returns a {@link
     * BluetoothQualityReport.BqrVsA2dpChoppy} object. If the report id is {@link
     * #QUALITY_REPORT_ID_SCO_CHOPPY}, this returns a {@link BluetoothQualityReport.BqrVsScoChoppy}
     * object. If the report id is {@link #QUALITY_REPORT_ID_CONN_FAIL}, this returns a {@link
     * BluetoothQualityReport.BqrConnectFail} object. If the report id is none of the above, this
     * returns {@code null}.
     *
     * @return the event data object based on the quality report id
     * @hide
     */
    @SystemApi
    public @Nullable Parcelable getBqrEvent() {
        if (mBqrCommon == null) {
            return null;
        }
        switch (mBqrCommon.getQualityReportId()) {
            case QUALITY_REPORT_ID_MONITOR:
                return mBqrCommon;
            case QUALITY_REPORT_ID_APPROACH_LSTO:
                return mBqrVsLsto;
            case QUALITY_REPORT_ID_A2DP_CHOPPY:
                return mBqrVsA2dpChoppy;
            case QUALITY_REPORT_ID_SCO_CHOPPY:
                return mBqrVsScoChoppy;
            case QUALITY_REPORT_ID_CONN_FAIL:
                return mBqrConnectFail;
            default:
                return null;
        }
    }

    /** @hide */
    @SystemApi
    public static final @NonNull Parcelable.Creator<BluetoothQualityReport> CREATOR =
            new Parcelable.Creator<BluetoothQualityReport>() {
                public BluetoothQualityReport createFromParcel(Parcel in) {
                    return new BluetoothQualityReport(in);
                }

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

    /**
     * Describe contents.
     *
     * @return 0
     * @hide
     */
    public int describeContents() {
        return 0;
    }

    /**
     * Write BluetoothQualityReport to parcel.
     *
     * @hide
     */
    @SystemApi
    @Override
    public void writeToParcel(@NonNull Parcel out, int flags) {
        out.writeString(mAddr);
        out.writeInt(mLmpVer);
        out.writeInt(mLmpSubVer);
        out.writeInt(mManufacturerId);
        out.writeString(mName);
        out.writeInt(mBluetoothClass.getClassOfDevice());
        mBqrCommon.writeToParcel(out, flags);
        int id = mBqrCommon.getQualityReportId();
        if (id == QUALITY_REPORT_ID_APPROACH_LSTO) {
            mBqrVsLsto.writeToParcel(out, flags);
        } else if (id == QUALITY_REPORT_ID_A2DP_CHOPPY) {
            mBqrVsA2dpChoppy.writeToParcel(out, flags);
        } else if (id == QUALITY_REPORT_ID_SCO_CHOPPY) {
            mBqrVsScoChoppy.writeToParcel(out, flags);
        } else if (id == QUALITY_REPORT_ID_CONN_FAIL) {
            mBqrConnectFail.writeToParcel(out, flags);
        }
    }

    /** BluetoothQualityReport to String. */
    @Override
    @NonNull
    public String toString() {
        String str;
        str =
                "BQR: {\n"
                        + "  mAddr: "
                        + mAddr
                        + ", mLmpVer: "
                        + String.format("0x%02X", mLmpVer)
                        + ", mLmpSubVer: "
                        + String.format("0x%04X", mLmpSubVer)
                        + ", mManufacturerId: "
                        + String.format("0x%04X", mManufacturerId)
                        + ", mName: "
                        + mName
                        + ", mBluetoothClass: "
                        + mBluetoothClass.toString()
                        + ",\n"
                        + mBqrCommon
                        + "\n";

        int id = mBqrCommon.getQualityReportId();
        if (id == QUALITY_REPORT_ID_APPROACH_LSTO) {
            str += mBqrVsLsto + "\n}";
        } else if (id == QUALITY_REPORT_ID_A2DP_CHOPPY) {
            str += mBqrVsA2dpChoppy + "\n}";
        } else if (id == QUALITY_REPORT_ID_SCO_CHOPPY) {
            str += mBqrVsScoChoppy + "\n}";
        } else if (id == QUALITY_REPORT_ID_CONN_FAIL) {
            str += mBqrConnectFail + "\n}";
        } else if (id == QUALITY_REPORT_ID_MONITOR) {
            str += "}";
        }

        return str;
    }

    /**
     * Builder for new instances of {@link BluetoothQualityReport}.
     *
     * @hide
     */
    @SystemApi
    public static final class Builder {
        private String remoteAddr = "00:00:00:00:00:00";
        private int lmpVer;
        private int lmpSubVer;
        private int manufacturerId;
        private String remoteName = "";
        private BluetoothClass bluetoothClass = new BluetoothClass(0);
        private byte[] rawData;

        /**
         * Creates a new instance of {@link Builder}.
         *
         * @return The new instance
         * @throws NullPointerException if rawData is null
         * @hide
         */
        @SystemApi
        public Builder(@NonNull byte[] rawData) {
            this.rawData = Objects.requireNonNull(rawData);
        }

        /**
         * Sets the Remote Device Address (big-endian) attribute for the new instance of {@link
         * BluetoothQualityReport}.
         *
         * @param remoteAddr the Remote Device Address (big-endian) attribute
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setRemoteAddress(@Nullable String remoteAddr) {
            if (!BluetoothAdapter.checkBluetoothAddress(remoteAddr)) {
                Log.d(TAG, "remote address is not a valid bluetooth address: " + remoteAddr);
            } else {
                this.remoteAddr = remoteAddr;
            }
            return this;
        }

        /**
         * Sets the Link Manager Protocol Version attribute for the new instance of {@link
         * BluetoothQualityReport}.
         *
         * @param lmpVer the Link Manager Protocol Version attribute
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setLmpVersion(int lmpVer) {
            this.lmpVer = lmpVer;
            return this;
        }

        /**
         * Sets the Link Manager Protocol SubVersion attribute for the new instance of {@link
         * BluetoothQualityReport}.
         *
         * @param lmpSubVer the Link Manager Protocol SubVersion attribute
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setLmpSubVersion(int lmpSubVer) {
            this.lmpSubVer = lmpSubVer;
            return this;
        }

        /**
         * Sets the Manufacturer Id attribute for the new instance of {@link
         * BluetoothQualityReport}.
         *
         * @param manufacturerId the Manufacturer Id attribute
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setManufacturerId(int manufacturerId) {
            this.manufacturerId = manufacturerId;
            return this;
        }

        /**
         * Sets the Remote Device Name attribute for the new instance of {@link
         * BluetoothQualityReport}.
         *
         * @param remoteName the Remote Device Name attribute
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setRemoteName(@Nullable String remoteName) {
            if (remoteName == null) {
                Log.d(TAG, "remote name is null");
            } else {
                this.remoteName = remoteName;
            }
            return this;
        }

        /**
         * Sets the Bluetooth Class of Remote Device attribute for the new instance of {@link
         * BluetoothQualityReport}.
         *
         * @param bluetoothClass the Remote Class of Device attribute
         * @hide
         */
        @NonNull
        @SystemApi
        public Builder setBluetoothClass(@Nullable BluetoothClass bluetoothClass) {
            if (bluetoothClass == null) {
                Log.d(TAG, "remote bluetooth class is null");
            } else {
                this.bluetoothClass = bluetoothClass;
            }
            return this;
        }

        /**
         * Creates a new instance of {@link BluetoothQualityReport}.
         *
         * @return The new instance
         * @throws IllegalArgumentException Unsupported Quality Report Id or invalid raw data
         * @hide
         */
        @NonNull
        @SystemApi
        public BluetoothQualityReport build() {
            return new BluetoothQualityReport(
                    remoteAddr,
                    lmpVer,
                    lmpSubVer,
                    manufacturerId,
                    remoteName,
                    bluetoothClass,
                    rawData);
        }
    }

    /**
     * This class provides the System APIs to access the common part of BQR event.
     *
     * @hide
     */
    @SystemApi
    public static final class BqrCommon implements Parcelable {
        private static final String TAG = BluetoothQualityReport.TAG + ".BqrCommon";
        static final int BQR_COMMON_LEN = 55;

        private int mQualityReportId;
        private int mPacketType;
        private int mConnectionHandle;
        private int mConnectionRole;
        private int mTxPowerLevel;
        private int mRssi;
        private int mSnr;
        private int mUnusedAfhChannelCount;
        private int mAfhSelectUnidealChannelCount;
        private int mLsto;
        private long mPiconetClock;
        private long mRetransmissionCount;
        private long mNoRxCount;
        private long mNakCount;
        private long mLastTxAckTimestamp;
        private long mFlowOffCount;
        private long mLastFlowOnTimestamp;
        private long mOverflowCount;
        private long mUnderflowCount;
        private String mAddr;
        private int mCalFailedItemCount;

        private BqrCommon(byte[] rawData, int offset) {
            if (rawData == null || rawData.length < offset + BQR_COMMON_LEN) {
                throw new IllegalArgumentException(TAG + ": BQR raw data length is abnormal.");
            }

            ByteBuffer bqrBuf =
                    ByteBuffer.wrap(rawData, offset, rawData.length - offset).asReadOnlyBuffer();
            bqrBuf.order(ByteOrder.LITTLE_ENDIAN);

            mQualityReportId = bqrBuf.get() & 0xFF;
            mPacketType = bqrBuf.get() & 0xFF;
            mConnectionHandle = bqrBuf.getShort() & 0xFFFF;
            mConnectionRole = bqrBuf.get() & 0xFF;
            mTxPowerLevel = bqrBuf.get() & 0xFF;
            mRssi = bqrBuf.get();
            mSnr = bqrBuf.get();
            mUnusedAfhChannelCount = bqrBuf.get() & 0xFF;
            mAfhSelectUnidealChannelCount = bqrBuf.get() & 0xFF;
            mLsto = bqrBuf.getShort() & 0xFFFF;
            mPiconetClock = bqrBuf.getInt() & 0xFFFFFFFFL;
            mRetransmissionCount = bqrBuf.getInt() & 0xFFFFFFFFL;
            mNoRxCount = bqrBuf.getInt() & 0xFFFFFFFFL;
            mNakCount = bqrBuf.getInt() & 0xFFFFFFFFL;
            mLastTxAckTimestamp = bqrBuf.getInt() & 0xFFFFFFFFL;
            mFlowOffCount = bqrBuf.getInt() & 0xFFFFFFFFL;
            mLastFlowOnTimestamp = bqrBuf.getInt() & 0xFFFFFFFFL;
            mOverflowCount = bqrBuf.getInt() & 0xFFFFFFFFL;
            mUnderflowCount = bqrBuf.getInt() & 0xFFFFFFFFL;
            int currentOffset = bqrBuf.position();
            mAddr =
                    String.format(
                            "%02X:%02X:%02X:%02X:%02X:%02X",
                            bqrBuf.get(currentOffset + 5),
                            bqrBuf.get(currentOffset + 4),
                            bqrBuf.get(currentOffset + 3),
                            bqrBuf.get(currentOffset + 2),
                            bqrBuf.get(currentOffset + 1),
                            bqrBuf.get(currentOffset + 0));
            bqrBuf.position(currentOffset + 6);
            mCalFailedItemCount = bqrBuf.get() & 0xFF;
        }

        private BqrCommon(Parcel in) {
            mQualityReportId = in.readInt();
            mPacketType = in.readInt();
            mConnectionHandle = in.readInt();
            mConnectionRole = in.readInt();
            mTxPowerLevel = in.readInt();
            mRssi = in.readInt();
            mSnr = in.readInt();
            mUnusedAfhChannelCount = in.readInt();
            mAfhSelectUnidealChannelCount = in.readInt();
            mLsto = in.readInt();
            mPiconetClock = in.readLong();
            mRetransmissionCount = in.readLong();
            mNoRxCount = in.readLong();
            mNakCount = in.readLong();
            mLastTxAckTimestamp = in.readLong();
            mFlowOffCount = in.readLong();
            mLastFlowOnTimestamp = in.readLong();
            mOverflowCount = in.readLong();
            mUnderflowCount = in.readLong();
            mAddr = in.readString();
            mCalFailedItemCount = in.readInt();
        }

        int getQualityReportId() {
            return mQualityReportId;
        }

        static String qualityReportIdToString(@QualityReportId int id) {
            switch (id) {
                case QUALITY_REPORT_ID_MONITOR:
                    return "Quality monitor";
                case QUALITY_REPORT_ID_APPROACH_LSTO:
                    return "Approaching LSTO";
                case QUALITY_REPORT_ID_A2DP_CHOPPY:
                    return "A2DP choppy";
                case QUALITY_REPORT_ID_SCO_CHOPPY:
                    return "SCO choppy";
                case QUALITY_REPORT_ID_CONN_FAIL:
                    return "Connect fail";
                default:
                    return "INVALID";
            }
        }

        /**
         * Get the packet type of the connection.
         *
         * @return the packet type
         * @hide
         */
        @SystemApi
        public int getPacketType() {
            return mPacketType;
        }

        /**
         * Get the string of packet type.
         *
         * @param packetType packet type of the connection
         * @return the string of packet type
         * @hide
         */
        @SystemApi
        public static @Nullable String packetTypeToString(int packetType) {
            PacketType type = PacketType.fromOrdinal(packetType);
            return type.toString();
        }

        /**
         * Get the connection handle of the connection.
         *
         * @return the connection handle
         * @hide
         */
        @SystemApi
        public int getConnectionHandle() {
            return mConnectionHandle;
        }

        /**
         * Connection role: central.
         *
         * @hide
         */
        @SystemApi public static final int CONNECTION_ROLE_CENTRAL = 0;

        /**
         * Connection role: peripheral.
         *
         * @hide
         */
        @SystemApi public static final int CONNECTION_ROLE_PERIPHERAL = 1;

        /** @hide */
        @Retention(RetentionPolicy.SOURCE)
        @IntDef(
                prefix = {"CONNECTION_ROLE"},
                value = {
                    CONNECTION_ROLE_CENTRAL,
                    CONNECTION_ROLE_PERIPHERAL,
                })
        public @interface ConnectionRole {}

        /**
         * Get the connection Role of the connection.
         *
         * @return the connection Role
         * @hide
         */
        @SystemApi
        @ConnectionRole
        public int getConnectionRole() {
            return mConnectionRole;
        }

        /**
         * Get the connection Role of the connection, "Central" or "Peripheral".
         *
         * @param connectionRole connection Role of the connection
         * @return the connection Role String
         * @hide
         */
        @SystemApi
        public static @NonNull String connectionRoleToString(int connectionRole) {
            if (connectionRole == CONNECTION_ROLE_CENTRAL) {
                return "Central";
            } else if (connectionRole == CONNECTION_ROLE_PERIPHERAL) {
                return "Peripheral";
            } else {
                return "INVALID:" + connectionRole;
            }
        }

        /**
         * Get the current transmit power level for the connection.
         *
         * @return the TX power level
         * @hide
         */
        @SystemApi
        public int getTxPowerLevel() {
            return mTxPowerLevel;
        }

        /**
         * Get the Received Signal Strength Indication (RSSI) value for the connection.
         *
         * @return the RSSI
         * @hide
         */
        @SystemApi
        public int getRssi() {
            return mRssi;
        }

        /**
         * Get the Signal-to-Noise Ratio (SNR) value for the connection.
         *
         * @return the SNR
         * @hide
         */
        @SystemApi
        public int getSnr() {
            return mSnr;
        }

        /**
         * Get the number of unused channels in AFH_channel_map.
         *
         * @return the number of unused channels
         * @hide
         */
        @SystemApi
        public int getUnusedAfhChannelCount() {
            return mUnusedAfhChannelCount;
        }

        /**
         * Get the number of the channels which are interfered and quality is bad but are still
         * selected for AFH.
         *
         * @return the number of the selected unideal channels
         * @hide
         */
        @SystemApi
        public int getAfhSelectUnidealChannelCount() {
            return mAfhSelectUnidealChannelCount;
        }

        /**
         * Get the current link supervision timeout setting. time_ms: N * 0.625 ms (1 slot).
         *
         * @return link supervision timeout value
         * @hide
         */
        @SystemApi
        public int getLsto() {
            return mLsto;
        }

        /**
         * Get the piconet clock for the specified Connection_Handle. time_ms: N * 0.3125 ms (1
         * Bluetooth Clock).
         *
         * @return the piconet clock
         * @hide
         */
        @SystemApi
        public long getPiconetClock() {
            return mPiconetClock;
        }

        /**
         * Get the count of retransmission.
         *
         * @return the count of retransmission
         * @hide
         */
        @SystemApi
        public long getRetransmissionCount() {
            return mRetransmissionCount;
        }

        /**
         * Get the count of no RX.
         *
         * @return the count of no RX
         * @hide
         */
        @SystemApi
        public long getNoRxCount() {
            return mNoRxCount;
        }

        /**
         * Get the count of NAK(Negative Acknowledge).
         *
         * @return the count of NAK
         * @hide
         */
        @SystemApi
        public long getNakCount() {
            return mNakCount;
        }

        /**
         * Get the timestamp of last TX ACK. time_ms: N * 0.3125 ms (1 Bluetooth Clock).
         *
         * @return the timestamp of last TX ACK
         * @hide
         */
        @SystemApi
        public long getLastTxAckTimestamp() {
            return mLastTxAckTimestamp;
        }

        /**
         * Get the count of flow-off.
         *
         * @return the count of flow-off
         * @hide
         */
        @SystemApi
        public long getFlowOffCount() {
            return mFlowOffCount;
        }

        /**
         * Get the timestamp of last flow-on.
         *
         * @return the timestamp of last flow-on
         * @hide
         */
        @SystemApi
        public long getLastFlowOnTimestamp() {
            return mLastFlowOnTimestamp;
        }

        /**
         * Get the buffer overflow count (how many bytes of TX data are dropped) since the last
         * event.
         *
         * @return the buffer overflow count
         * @hide
         */
        @SystemApi
        public long getOverflowCount() {
            return mOverflowCount;
        }

        /**
         * Get the buffer underflow count (in byte).
         *
         * @return the buffer underflow count
         * @hide
         */
        @SystemApi
        public long getUnderflowCount() {
            return mUnderflowCount;
        }

        /**
         * Get the count of calibration failed items.
         *
         * @return the count of calibration failure
         * @hide
         */
        @SystemApi
        public int getCalFailedItemCount() {
            return mCalFailedItemCount;
        }

        /**
         * Describe contents.
         *
         * @return 0
         * @hide
         */
        public int describeContents() {
            return 0;
        }

        /**
         * Write BqrCommon to parcel.
         *
         * @hide
         */
        @SystemApi
        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeInt(mQualityReportId);
            dest.writeInt(mPacketType);
            dest.writeInt(mConnectionHandle);
            dest.writeInt(mConnectionRole);
            dest.writeInt(mTxPowerLevel);
            dest.writeInt(mRssi);
            dest.writeInt(mSnr);
            dest.writeInt(mUnusedAfhChannelCount);
            dest.writeInt(mAfhSelectUnidealChannelCount);
            dest.writeInt(mLsto);
            dest.writeLong(mPiconetClock);
            dest.writeLong(mRetransmissionCount);
            dest.writeLong(mNoRxCount);
            dest.writeLong(mNakCount);
            dest.writeLong(mLastTxAckTimestamp);
            dest.writeLong(mFlowOffCount);
            dest.writeLong(mLastFlowOnTimestamp);
            dest.writeLong(mOverflowCount);
            dest.writeLong(mUnderflowCount);
            dest.writeString(mAddr);
            dest.writeInt(mCalFailedItemCount);
        }

        /** @hide */
        @SystemApi
        public static final @NonNull Parcelable.Creator<BqrCommon> CREATOR =
                new Parcelable.Creator<BqrCommon>() {
                    public BqrCommon createFromParcel(Parcel in) {
                        return new BqrCommon(in);
                    }

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

        /** BqrCommon to String. */
        @Override
        @NonNull
        public String toString() {
            String str;
            str =
                    "  BqrCommon: {\n"
                            + "    mQualityReportId: "
                            + qualityReportIdToString(getQualityReportId())
                            + "("
                            + String.format("0x%02X", mQualityReportId)
                            + ")"
                            + ", mPacketType: "
                            + packetTypeToString(mPacketType)
                            + "("
                            + String.format("0x%02X", mPacketType)
                            + ")"
                            + ", mConnectionHandle: "
                            + String.format("0x%04X", mConnectionHandle)
                            + ", mConnectionRole: "
                            + getConnectionRole()
                            + "("
                            + mConnectionRole
                            + ")"
                            + ", mTxPowerLevel: "
                            + mTxPowerLevel
                            + ", mRssi: "
                            + mRssi
                            + ", mSnr: "
                            + mSnr
                            + ", mUnusedAfhChannelCount: "
                            + mUnusedAfhChannelCount
                            + ",\n"
                            + "    mAfhSelectUnidealChannelCount: "
                            + mAfhSelectUnidealChannelCount
                            + ", mLsto: "
                            + mLsto
                            + ", mPiconetClock: "
                            + String.format("0x%08X", mPiconetClock)
                            + ", mRetransmissionCount: "
                            + mRetransmissionCount
                            + ", mNoRxCount: "
                            + mNoRxCount
                            + ", mNakCount: "
                            + mNakCount
                            + ", mLastTxAckTimestamp: "
                            + String.format("0x%08X", mLastTxAckTimestamp)
                            + ", mFlowOffCount: "
                            + mFlowOffCount
                            + ",\n"
                            + "    mLastFlowOnTimestamp: "
                            + String.format("0x%08X", mLastFlowOnTimestamp)
                            + ", mOverflowCount: "
                            + mOverflowCount
                            + ", mUnderflowCount: "
                            + mUnderflowCount
                            + ", mAddr: "
                            + mAddr
                            + ", mCalFailedItemCount: "
                            + mCalFailedItemCount
                            + "\n  }";

            return str;
        }
    }

    /**
     * This class provides the System APIs to access the vendor specific part of Approaching LSTO
     * event.
     *
     * @hide
     */
    @SystemApi
    public static final class BqrVsLsto implements Parcelable {
        private static final String TAG = BluetoothQualityReport.TAG + ".BqrVsLsto";

        private int mConnState;
        private long mBasebandStats;
        private long mSlotsUsed;
        private int mCxmDenials;
        private int mTxSkipped;
        private int mRfLoss;
        private long mNativeClock;
        private long mLastTxAckTimestamp;

        private BqrVsLsto(byte[] rawData, int offset) {
            if (rawData == null || rawData.length <= offset) {
                throw new IllegalArgumentException(TAG + ": BQR raw data length is abnormal.");
            }

            ByteBuffer bqrBuf =
                    ByteBuffer.wrap(rawData, offset, rawData.length - offset).asReadOnlyBuffer();
            bqrBuf.order(ByteOrder.LITTLE_ENDIAN);

            mConnState = bqrBuf.get() & 0xFF;
            mBasebandStats = bqrBuf.getInt() & 0xFFFFFFFFL;
            mSlotsUsed = bqrBuf.getInt() & 0xFFFFFFFFL;
            mCxmDenials = bqrBuf.getShort() & 0xFFFF;
            mTxSkipped = bqrBuf.getShort() & 0xFFFF;
            mRfLoss = bqrBuf.getShort() & 0xFFFF;
            mNativeClock = bqrBuf.getInt() & 0xFFFFFFFFL;
            mLastTxAckTimestamp = bqrBuf.getInt() & 0xFFFFFFFFL;
        }

        private BqrVsLsto(Parcel in) {
            mConnState = in.readInt();
            mBasebandStats = in.readLong();
            mSlotsUsed = in.readLong();
            mCxmDenials = in.readInt();
            mTxSkipped = in.readInt();
            mRfLoss = in.readInt();
            mNativeClock = in.readLong();
            mLastTxAckTimestamp = in.readLong();
        }

        /**
         * Get the conn state of sco.
         *
         * @return the conn state
         * @hide
         */
        @SystemApi
        public int getConnState() {
            return mConnState;
        }

        /**
         * Get the string of conn state of sco.
         *
         * @param connectionState connection state of sco
         * @return the string of conn state
         * @hide
         */
        @SystemApi
        public static @Nullable String connStateToString(int connectionState) {
            return ConnState.toString(connectionState);
        }

        /**
         * Get the baseband statistics.
         *
         * @return the baseband statistics
         * @hide
         */
        @SystemApi
        public long getBasebandStats() {
            return mBasebandStats;
        }

        /**
         * Get the count of slots allocated for current connection.
         *
         * @return the count of slots allocated for current connection
         * @hide
         */
        @SystemApi
        public long getSlotsUsed() {
            return mSlotsUsed;
        }

        /**
         * Get the count of Coex denials.
         *
         * @return the count of CXM denials
         * @hide
         */
        @SystemApi
        public int getCxmDenials() {
            return mCxmDenials;
        }

        /**
         * Get the count of TX skipped when no poll from remote device.
         *
         * @return the count of TX skipped
         * @hide
         */
        @SystemApi
        public int getTxSkipped() {
            return mTxSkipped;
        }

        /**
         * Get the count of RF loss.
         *
         * @return the count of RF loss
         * @hide
         */
        @SystemApi
        public int getRfLoss() {
            return mRfLoss;
        }

        /**
         * Get the timestamp when issue happened. time_ms: N * 0.3125 ms (1 Bluetooth Clock).
         *
         * @return the timestamp when issue happened
         * @hide
         */
        @SystemApi
        public long getNativeClock() {
            return mNativeClock;
        }

        /**
         * Get the timestamp of last TX ACK. time_ms: N * 0.3125 ms (1 Bluetooth Clock).
         *
         * @return the timestamp of last TX ACK
         * @hide
         */
        @SystemApi
        public long getLastTxAckTimestamp() {
            return mLastTxAckTimestamp;
        }

        /**
         * Describe contents.
         *
         * @return 0
         * @hide
         */
        public int describeContents() {
            return 0;
        }

        /**
         * Write BqrVsLsto to parcel.
         *
         * @hide
         */
        @SystemApi
        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeInt(mConnState);
            dest.writeLong(mBasebandStats);
            dest.writeLong(mSlotsUsed);
            dest.writeInt(mCxmDenials);
            dest.writeInt(mTxSkipped);
            dest.writeInt(mRfLoss);
            dest.writeLong(mNativeClock);
            dest.writeLong(mLastTxAckTimestamp);
        }

        /** @hide */
        @SystemApi
        public static final @NonNull Parcelable.Creator<BqrVsLsto> CREATOR =
                new Parcelable.Creator<BqrVsLsto>() {
                    public BqrVsLsto createFromParcel(Parcel in) {
                        return new BqrVsLsto(in);
                    }

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

        /** BqrVsLsto to String. */
        @Override
        @NonNull
        public String toString() {
            String str;
            str =
                    "  BqrVsLsto: {\n"
                            + "    mConnState: "
                            + connStateToString(getConnState())
                            + "("
                            + String.format("0x%02X", mConnState)
                            + ")"
                            + ", mBasebandStats: "
                            + String.format("0x%08X", mBasebandStats)
                            + ", mSlotsUsed: "
                            + mSlotsUsed
                            + ", mCxmDenials: "
                            + mCxmDenials
                            + ", mTxSkipped: "
                            + mTxSkipped
                            + ", mRfLoss: "
                            + mRfLoss
                            + ", mNativeClock: "
                            + String.format("0x%08X", mNativeClock)
                            + ", mLastTxAckTimestamp: "
                            + String.format("0x%08X", mLastTxAckTimestamp)
                            + "\n  }";

            return str;
        }
    }

    /**
     * This class provides the System APIs to access the vendor specific part of A2dp choppy event.
     *
     * @hide
     */
    @SystemApi
    public static final class BqrVsA2dpChoppy implements Parcelable {
        private static final String TAG = BluetoothQualityReport.TAG + ".BqrVsA2dpChoppy";

        private long mArrivalTime;
        private long mScheduleTime;
        private int mGlitchCount;
        private int mTxCxmDenials;
        private int mRxCxmDenials;
        private int mAclTxQueueLength;
        private int mLinkQuality;

        private BqrVsA2dpChoppy(byte[] rawData, int offset) {
            if (rawData == null || rawData.length <= offset) {
                throw new IllegalArgumentException(TAG + ": BQR raw data length is abnormal.");
            }

            ByteBuffer bqrBuf =
                    ByteBuffer.wrap(rawData, offset, rawData.length - offset).asReadOnlyBuffer();
            bqrBuf.order(ByteOrder.LITTLE_ENDIAN);

            mArrivalTime = bqrBuf.getInt() & 0xFFFFFFFFL;
            mScheduleTime = bqrBuf.getInt() & 0xFFFFFFFFL;
            mGlitchCount = bqrBuf.getShort() & 0xFFFF;
            mTxCxmDenials = bqrBuf.getShort() & 0xFFFF;
            mRxCxmDenials = bqrBuf.getShort() & 0xFFFF;
            mAclTxQueueLength = bqrBuf.get() & 0xFF;
            mLinkQuality = bqrBuf.get() & 0xFF;
        }

        private BqrVsA2dpChoppy(Parcel in) {
            mArrivalTime = in.readLong();
            mScheduleTime = in.readLong();
            mGlitchCount = in.readInt();
            mTxCxmDenials = in.readInt();
            mRxCxmDenials = in.readInt();
            mAclTxQueueLength = in.readInt();
            mLinkQuality = in.readInt();
        }

        /**
         * Get the timestamp of a2dp packet arrived. time_ms: N * 0.3125 ms (1 Bluetooth Clock).
         *
         * @return the timestamp of a2dp packet arrived
         * @hide
         */
        @SystemApi
        public long getArrivalTime() {
            return mArrivalTime;
        }

        /**
         * Get the timestamp of a2dp packet scheduled. time_ms: N * 0.3125 ms (1 Bluetooth Clock).
         *
         * @return the timestamp of a2dp packet scheduled
         * @hide
         */
        @SystemApi
        public long getScheduleTime() {
            return mScheduleTime;
        }

        /**
         * Get the a2dp glitch count since the last event.
         *
         * @return the a2dp glitch count
         * @hide
         */
        @SystemApi
        public int getGlitchCount() {
            return mGlitchCount;
        }

        /**
         * Get the count of Coex TX denials.
         *
         * @return the count of Coex TX denials
         * @hide
         */
        @SystemApi
        public int getTxCxmDenials() {
            return mTxCxmDenials;
        }

        /**
         * Get the count of Coex RX denials.
         *
         * @return the count of Coex RX denials
         * @hide
         */
        @SystemApi
        public int getRxCxmDenials() {
            return mRxCxmDenials;
        }

        /**
         * Get the ACL queue length which are pending TX in FW.
         *
         * @return the ACL queue length
         * @hide
         */
        @SystemApi
        public int getAclTxQueueLength() {
            return mAclTxQueueLength;
        }

        /**
         * Get the link quality for the current connection.
         *
         * @return the link quality
         * @hide
         */
        @SystemApi
        public int getLinkQuality() {
            return mLinkQuality;
        }

        /**
         * Get the string of link quality for the current connection.
         *
         * @param linkQuality link quality for the current connection
         * @return the string of link quality
         * @hide
         */
        @SystemApi
        public static @Nullable String linkQualityToString(int linkQuality) {
            LinkQuality q = LinkQuality.fromOrdinal(linkQuality);
            return q.toString();
        }

        /**
         * Describe contents.
         *
         * @return 0
         * @hide
         */
        public int describeContents() {
            return 0;
        }

        /**
         * Write BqrVsA2dpChoppy to parcel.
         *
         * @hide
         */
        @SystemApi
        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeLong(mArrivalTime);
            dest.writeLong(mScheduleTime);
            dest.writeInt(mGlitchCount);
            dest.writeInt(mTxCxmDenials);
            dest.writeInt(mRxCxmDenials);
            dest.writeInt(mAclTxQueueLength);
            dest.writeInt(mLinkQuality);
        }

        /** @hide */
        @SystemApi
        public static final @NonNull Parcelable.Creator<BqrVsA2dpChoppy> CREATOR =
                new Parcelable.Creator<BqrVsA2dpChoppy>() {
                    public BqrVsA2dpChoppy createFromParcel(Parcel in) {
                        return new BqrVsA2dpChoppy(in);
                    }

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

        /** BqrVsA2dpChoppy to String. */
        @Override
        @NonNull
        public String toString() {
            String str;
            str =
                    "  BqrVsA2dpChoppy: {\n"
                            + "    mArrivalTime: "
                            + String.format("0x%08X", mArrivalTime)
                            + ", mScheduleTime: "
                            + String.format("0x%08X", mScheduleTime)
                            + ", mGlitchCount: "
                            + mGlitchCount
                            + ", mTxCxmDenials: "
                            + mTxCxmDenials
                            + ", mRxCxmDenials: "
                            + mRxCxmDenials
                            + ", mAclTxQueueLength: "
                            + mAclTxQueueLength
                            + ", mLinkQuality: "
                            + linkQualityToString(mLinkQuality)
                            + "("
                            + String.format("0x%02X", mLinkQuality)
                            + ")"
                            + "\n  }";

            return str;
        }
    }

    /**
     * This class provides the System APIs to access the vendor specific part of SCO choppy event.
     *
     * @hide
     */
    @SystemApi
    public static final class BqrVsScoChoppy implements Parcelable {
        private static final String TAG = BluetoothQualityReport.TAG + ".BqrVsScoChoppy";

        private int mGlitchCount;
        private int mIntervalEsco;
        private int mWindowEsco;
        private int mAirFormat;
        private int mInstanceCount;
        private int mTxCxmDenials;
        private int mRxCxmDenials;
        private int mTxAbortCount;
        private int mLateDispatch;
        private int mMicIntrMiss;
        private int mLpaIntrMiss;
        private int mSprIntrMiss;
        private int mPlcFillCount;
        private int mPlcDiscardCount;
        private int mMissedInstanceCount;
        private int mTxRetransmitSlotCount;
        private int mRxRetransmitSlotCount;
        private int mGoodRxFrameCount;

        private BqrVsScoChoppy(byte[] rawData, int offset) {
            if (rawData == null || rawData.length <= offset) {
                throw new IllegalArgumentException(TAG + ": BQR raw data length is abnormal.");
            }

            ByteBuffer bqrBuf =
                    ByteBuffer.wrap(rawData, offset, rawData.length - offset).asReadOnlyBuffer();
            bqrBuf.order(ByteOrder.LITTLE_ENDIAN);

            mGlitchCount = bqrBuf.getShort() & 0xFFFF;
            mIntervalEsco = bqrBuf.get() & 0xFF;
            mWindowEsco = bqrBuf.get() & 0xFF;
            mAirFormat = bqrBuf.get() & 0xFF;
            mInstanceCount = bqrBuf.getShort() & 0xFFFF;
            mTxCxmDenials = bqrBuf.getShort() & 0xFFFF;
            mRxCxmDenials = bqrBuf.getShort() & 0xFFFF;
            mTxAbortCount = bqrBuf.getShort() & 0xFFFF;
            mLateDispatch = bqrBuf.getShort() & 0xFFFF;
            mMicIntrMiss = bqrBuf.getShort() & 0xFFFF;
            mLpaIntrMiss = bqrBuf.getShort() & 0xFFFF;
            mSprIntrMiss = bqrBuf.getShort() & 0xFFFF;
            mPlcFillCount = bqrBuf.getShort() & 0xFFFF;
            mPlcDiscardCount = bqrBuf.getShort() & 0xFFFF;
            mMissedInstanceCount = bqrBuf.getShort() & 0xFFFF;
            mTxRetransmitSlotCount = bqrBuf.getShort() & 0xFFFF;
            mRxRetransmitSlotCount = bqrBuf.getShort() & 0xFFFF;
            mGoodRxFrameCount = bqrBuf.getShort() & 0xFFFF;
        }

        private BqrVsScoChoppy(Parcel in) {
            mGlitchCount = in.readInt();
            mIntervalEsco = in.readInt();
            mWindowEsco = in.readInt();
            mAirFormat = in.readInt();
            mInstanceCount = in.readInt();
            mTxCxmDenials = in.readInt();
            mRxCxmDenials = in.readInt();
            mTxAbortCount = in.readInt();
            mLateDispatch = in.readInt();
            mMicIntrMiss = in.readInt();
            mLpaIntrMiss = in.readInt();
            mSprIntrMiss = in.readInt();
            mPlcFillCount = in.readInt();
            mPlcDiscardCount = in.readInt();
            mMissedInstanceCount = in.readInt();
            mTxRetransmitSlotCount = in.readInt();
            mRxRetransmitSlotCount = in.readInt();
            mGoodRxFrameCount = in.readInt();
        }

        /**
         * Get the sco glitch count since the last event.
         *
         * @return the sco glitch count
         * @hide
         */
        @SystemApi
        public int getGlitchCount() {
            return mGlitchCount;
        }

        /**
         * Get ESCO interval in slots. It is the value of Transmission_Interval parameter in
         * Synchronous Connection Complete event.
         *
         * @return ESCO interval in slots
         * @hide
         */
        @SystemApi
        public int getIntervalEsco() {
            return mIntervalEsco;
        }

        /**
         * Get ESCO window in slots. It is the value of Retransmission Window parameter in
         * Synchronous Connection Complete event.
         *
         * @return ESCO window in slots
         * @hide
         */
        @SystemApi
        public int getWindowEsco() {
            return mWindowEsco;
        }

        /**
         * Get the air mode. It is the value of Air Mode parameter in Synchronous Connection
         * Complete event.
         *
         * @return the air mode
         * @hide
         */
        @SystemApi
        public int getAirFormat() {
            return mAirFormat;
        }

        /**
         * Get the string of air mode.
         *
         * @param airFormat the value of Air Mode parameter in Synchronous Connection Complete event
         * @return the string of air mode
         * @hide
         */
        @SystemApi
        public static @Nullable String airFormatToString(int airFormat) {
            AirMode m = AirMode.fromOrdinal(airFormat);
            return m.toString();
        }

        /**
         * Get the xSCO instance count.
         *
         * @return the xSCO instance count
         * @hide
         */
        @SystemApi
        public int getInstanceCount() {
            return mInstanceCount;
        }

        /**
         * Get the count of Coex TX denials.
         *
         * @return the count of Coex TX denials
         * @hide
         */
        @SystemApi
        public int getTxCxmDenials() {
            return mTxCxmDenials;
        }

        /**
         * Get the count of Coex RX denials.
         *
         * @return the count of Coex RX denials
         * @hide
         */
        @SystemApi
        public int getRxCxmDenials() {
            return mRxCxmDenials;
        }

        /**
         * Get the count of sco packets aborted.
         *
         * @return the count of sco packets aborted
         * @hide
         */
        @SystemApi
        public int getTxAbortCount() {
            return mTxAbortCount;
        }

        /**
         * Get the count of sco packets dispatched late.
         *
         * @return the count of sco packets dispatched late
         * @hide
         */
        @SystemApi
        public int getLateDispatch() {
            return mLateDispatch;
        }

        /**
         * Get the count of missed Mic interrupts.
         *
         * @return the count of missed Mic interrupts
         * @hide
         */
        @SystemApi
        public int getMicIntrMiss() {
            return mMicIntrMiss;
        }

        /**
         * Get the count of missed LPA interrupts.
         *
         * @return the count of missed LPA interrupts
         * @hide
         */
        @SystemApi
        public int getLpaIntrMiss() {
            return mLpaIntrMiss;
        }

        /**
         * Get the count of missed Speaker interrupts.
         *
         * @return the count of missed Speaker interrupts
         * @hide
         */
        @SystemApi
        public int getSprIntrMiss() {
            return mSprIntrMiss;
        }

        /**
         * Get the count of packet loss concealment filled.
         *
         * @return the count of packet loss concealment filled
         * @hide
         */
        @SystemApi
        public int getPlcFillCount() {
            return mPlcFillCount;
        }

        /**
         * Get the count of packet loss concealment discarded.
         *
         * @return the count of packet loss concealment discarded
         * @hide
         */
        @SystemApi
        public int getPlcDiscardCount() {
            return mPlcDiscardCount;
        }

        /**
         * Get the count of sco instances missed.
         *
         * @return the count of sco instances missed
         * @hide
         */
        @SystemApi
        public int getMissedInstanceCount() {
            return mMissedInstanceCount;
        }

        /**
         * Get the count of slots for Tx retransmission.
         *
         * @return the count of slots for Tx retransmission
         * @hide
         */
        @SystemApi
        public int getTxRetransmitSlotCount() {
            return mTxRetransmitSlotCount;
        }

        /**
         * Get the count of slots for Rx retransmission.
         *
         * @return the count of slots for Rx retransmission
         * @hide
         */
        @SystemApi
        public int getRxRetransmitSlotCount() {
            return mRxRetransmitSlotCount;
        }

        /**
         * Get the count of Rx good packets
         *
         * @return the count of Rx good packets
         * @hide
         */
        @SystemApi
        public int getGoodRxFrameCount() {
            return mGoodRxFrameCount;
        }

        /**
         * Describe contents.
         *
         * @return 0
         * @hide
         */
        public int describeContents() {
            return 0;
        }

        /**
         * Write BqrVsScoChoppy to parcel.
         *
         * @hide
         */
        @SystemApi
        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeInt(mGlitchCount);
            dest.writeInt(mIntervalEsco);
            dest.writeInt(mWindowEsco);
            dest.writeInt(mAirFormat);
            dest.writeInt(mInstanceCount);
            dest.writeInt(mTxCxmDenials);
            dest.writeInt(mRxCxmDenials);
            dest.writeInt(mTxAbortCount);
            dest.writeInt(mLateDispatch);
            dest.writeInt(mMicIntrMiss);
            dest.writeInt(mLpaIntrMiss);
            dest.writeInt(mSprIntrMiss);
            dest.writeInt(mPlcFillCount);
            dest.writeInt(mPlcDiscardCount);
            dest.writeInt(mMissedInstanceCount);
            dest.writeInt(mTxRetransmitSlotCount);
            dest.writeInt(mRxRetransmitSlotCount);
            dest.writeInt(mGoodRxFrameCount);
        }

        /** @hide */
        @SystemApi
        public static final @NonNull Parcelable.Creator<BqrVsScoChoppy> CREATOR =
                new Parcelable.Creator<BqrVsScoChoppy>() {
                    public BqrVsScoChoppy createFromParcel(Parcel in) {
                        return new BqrVsScoChoppy(in);
                    }

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

        /** BqrVsScoChoppy to String. */
        @Override
        @NonNull
        public String toString() {
            String str;
            str =
                    "  BqrVsScoChoppy: {\n"
                            + "    mGlitchCount: "
                            + mGlitchCount
                            + ", mIntervalEsco: "
                            + mIntervalEsco
                            + ", mWindowEsco: "
                            + mWindowEsco
                            + ", mAirFormat: "
                            + airFormatToString(mAirFormat)
                            + "("
                            + String.format("0x%02X", mAirFormat)
                            + ")"
                            + ", mInstanceCount: "
                            + mInstanceCount
                            + ", mTxCxmDenials: "
                            + mTxCxmDenials
                            + ", mRxCxmDenials: "
                            + mRxCxmDenials
                            + ", mTxAbortCount: "
                            + mTxAbortCount
                            + ",\n"
                            + "    mLateDispatch: "
                            + mLateDispatch
                            + ", mMicIntrMiss: "
                            + mMicIntrMiss
                            + ", mLpaIntrMiss: "
                            + mLpaIntrMiss
                            + ", mSprIntrMiss: "
                            + mSprIntrMiss
                            + ", mPlcFillCount: "
                            + mPlcFillCount
                            + ", mPlcDiscardCount: "
                            + mPlcDiscardCount
                            + ", mMissedInstanceCount: "
                            + mMissedInstanceCount
                            + ", mTxRetransmitSlotCount: "
                            + mTxRetransmitSlotCount
                            + ",\n"
                            + "    mRxRetransmitSlotCount: "
                            + mRxRetransmitSlotCount
                            + ", mGoodRxFrameCount: "
                            + mGoodRxFrameCount
                            + "\n  }";

            return str;
        }
    }

    /**
     * This class provides the System APIs to access the Connect fail event.
     *
     * @hide
     */
    @SystemApi
    public static final class BqrConnectFail implements Parcelable {
        private static final String TAG = BluetoothQualityReport.TAG + ".BqrConnectFail";

        /**
         * Connect Fail reason: No error.
         *
         * @hide
         */
        @SystemApi public static final int CONNECT_FAIL_ID_NO_ERROR = 0x00;

        /**
         * Connect Fail reason: Page timeout.
         *
         * @hide
         */
        @SystemApi public static final int CONNECT_FAIL_ID_PAGE_TIMEOUT = 0x04;

        /**
         * Connect Fail reason: Connection timeout.
         *
         * @hide
         */
        @SystemApi public static final int CONNECT_FAIL_ID_CONNECTION_TIMEOUT = 0x08;

        /**
         * Connect Fail reason: ACL already exists.
         *
         * @hide
         */
        @SystemApi public static final int CONNECT_FAIL_ID_ACL_ALREADY_EXIST = 0x0b;

        /**
         * Connect Fail reason: Controller busy.
         *
         * @hide
         */
        @SystemApi public static final int CONNECT_FAIL_ID_CONTROLLER_BUSY = 0x3a;

        /** @hide */
        @Retention(RetentionPolicy.SOURCE)
        @IntDef(
                prefix = {"CONNECT_FAIL_ID"},
                value = {
                    CONNECT_FAIL_ID_NO_ERROR,
                    CONNECT_FAIL_ID_PAGE_TIMEOUT,
                    CONNECT_FAIL_ID_CONNECTION_TIMEOUT,
                    CONNECT_FAIL_ID_ACL_ALREADY_EXIST,
                    CONNECT_FAIL_ID_CONTROLLER_BUSY,
                })
        public @interface ConnectFailId {}

        private int mFailReason;

        private BqrConnectFail(byte[] rawData, int offset) {
            if (rawData == null || rawData.length <= offset) {
                throw new IllegalArgumentException(TAG + ": BQR raw data length is abnormal.");
            }

            ByteBuffer bqrBuf =
                    ByteBuffer.wrap(rawData, offset, rawData.length - offset).asReadOnlyBuffer();
            bqrBuf.order(ByteOrder.LITTLE_ENDIAN);

            mFailReason = bqrBuf.get() & 0xFF;
        }

        private BqrConnectFail(Parcel in) {
            mFailReason = in.readInt();
        }

        /**
         * Get the fail reason.
         *
         * @return the fail reason
         * @hide
         */
        @SystemApi
        @ConnectFailId
        public int getFailReason() {
            return mFailReason;
        }

        /**
         * Describe contents.
         *
         * @return 0
         * @hide
         */
        public int describeContents() {
            return 0;
        }

        /**
         * Write BqrConnectFail to parcel.
         *
         * @hide
         */
        @SystemApi
        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeInt(mFailReason);
        }

        /** @hide */
        @SystemApi
        public static final @NonNull Parcelable.Creator<BqrConnectFail> CREATOR =
                new Parcelable.Creator<BqrConnectFail>() {
                    public BqrConnectFail createFromParcel(Parcel in) {
                        return new BqrConnectFail(in);
                    }

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

        /**
         * Get the string of the Connect Fail ID.
         *
         * @param id the connect fail reason
         * @return the string of the id
         * @hide
         */
        @SystemApi
        public static @NonNull String connectFailIdToString(@ConnectFailId int id) {
            switch (id) {
                case CONNECT_FAIL_ID_NO_ERROR:
                    return "No error";
                case CONNECT_FAIL_ID_PAGE_TIMEOUT:
                    return "Page Timeout";
                case CONNECT_FAIL_ID_CONNECTION_TIMEOUT:
                    return "Connection Timeout";
                case CONNECT_FAIL_ID_ACL_ALREADY_EXIST:
                    return "ACL already exists";
                case CONNECT_FAIL_ID_CONTROLLER_BUSY:
                    return "Controller busy";
                default:
                    return "INVALID";
            }
        }

        /** BqrConnectFail to String. */
        @Override
        @NonNull
        public String toString() {
            String str;
            str =
                    "  BqrConnectFail: {\n"
                            + "    mFailReason: "
                            + connectFailIdToString(mFailReason)
                            + " ("
                            + String.format("0x%02X", mFailReason)
                            + ")"
                            + "\n  }";

            return str;
        }
    }
}
