/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.wifi.aware;

import android.hardware.wifi.V1_0.NanStatusType;
import android.net.wifi.aware.WifiAwareNetworkSpecifier;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wifi.Clock;
import com.android.server.wifi.proto.nano.WifiMetricsProto;
import com.android.server.wifi.util.MetricsUtils;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Wi-Fi Aware metric container/processor.
 */
public class WifiAwareMetrics {
    private static final String TAG = "WifiAwareMetrics";

    // Histogram: 8 buckets (i=0, ..., 7) of 9 slots in range 10^i -> 10^(i+1)
    // Buckets:
    //    1 -> 10: 9 @ 1
    //    10 -> 100: 9 @ 10
    //    100 -> 1000: 9 @ 10^2
    //    10^3 -> 10^4: 9 @ 10^3
    //    10^4 -> 10^5: 9 @ 10^4
    //    10^5 -> 10^6: 9 @ 10^5
    //    10^6 -> 10^7: 9 @ 10^6
    //    10^7 -> 10^8: 9 @ 10^7 --> 10^8 ms -> 10^5s -> 28 hours
    private static final MetricsUtils.LogHistParms DURATION_LOG_HISTOGRAM =
            new MetricsUtils.LogHistParms(0, 1, 10, 9, 8);

    // Histogram for ranging limits in discovery. Indicates the following 5 buckets (in meters):
    //   < 10
    //   [10, 30)
    //   [30, 60)
    //   [60, 100)
    //   >= 100
    private static final int[] RANGING_LIMIT_METERS = { 10, 30, 60, 100 };

    private final Object mLock = new Object();
    private final Clock mClock;

    // enableUsage/disableUsage data
    private long mLastEnableUsageMs = 0;
    private long mLastEnableUsageInThisSampleWindowMs = 0;
    private long mAvailableTimeMs = 0;
    private SparseIntArray mHistogramAwareAvailableDurationMs = new SparseIntArray();

    // enabled data
    private long mLastEnableAwareMs = 0;
    private long mLastEnableAwareInThisSampleWindowMs = 0;
    private long mEnabledTimeMs = 0;
    private SparseIntArray mHistogramAwareEnabledDurationMs = new SparseIntArray();

    // attach data
    private static class AttachData {
        boolean mUsesIdentityCallback; // do any attach sessions of the UID use identity callback
        int mMaxConcurrentAttaches;
    }
    private Map<Integer, AttachData> mAttachDataByUid = new HashMap<>();
    private SparseIntArray mAttachStatusData = new SparseIntArray();
    private SparseIntArray mHistogramAttachDuration = new SparseIntArray();

    // discovery data
    private int mMaxPublishInApp = 0;
    private int mMaxSubscribeInApp = 0;
    private int mMaxDiscoveryInApp = 0;
    private int mMaxPublishInSystem = 0;
    private int mMaxSubscribeInSystem = 0;
    private int mMaxDiscoveryInSystem = 0;
    private SparseIntArray mPublishStatusData = new SparseIntArray();
    private SparseIntArray mSubscribeStatusData = new SparseIntArray();
    private SparseIntArray mHistogramPublishDuration = new SparseIntArray();
    private SparseIntArray mHistogramSubscribeDuration = new SparseIntArray();
    private Set<Integer> mAppsWithDiscoverySessionResourceFailure = new HashSet<>();

    // discovery with ranging data
    private int mMaxPublishWithRangingInApp = 0;
    private int mMaxSubscribeWithRangingInApp = 0;
    private int mMaxPublishWithRangingInSystem = 0;
    private int mMaxSubscribeWithRangingInSystem = 0;
    private SparseIntArray mHistogramSubscribeGeofenceMin = new SparseIntArray();
    private SparseIntArray mHistogramSubscribeGeofenceMax = new SparseIntArray();
    private int mNumSubscribesWithRanging = 0;
    private int mNumMatchesWithRanging = 0;
    private int mNumMatchesWithoutRangingForRangingEnabledSubscribes = 0;

    // data-path (NDI/NDP) data
    private int mMaxNdiInApp = 0;
    private int mMaxNdpInApp = 0;
    private int mMaxSecureNdpInApp = 0;
    private int mMaxNdiInSystem = 0;
    private int mMaxNdpInSystem = 0;
    private int mMaxSecureNdpInSystem = 0;
    private int mMaxNdpPerNdi = 0;
    private SparseIntArray mInBandNdpStatusData = new SparseIntArray();
    private SparseIntArray mOutOfBandNdpStatusData = new SparseIntArray();

    private SparseIntArray mNdpCreationTimeDuration = new SparseIntArray();
    private long mNdpCreationTimeMin = -1;
    private long mNdpCreationTimeMax = 0;
    private long mNdpCreationTimeSum = 0;
    private long mNdpCreationTimeSumSq = 0;
    private long mNdpCreationTimeNumSamples = 0;

    private SparseIntArray mHistogramNdpDuration = new SparseIntArray();

    public WifiAwareMetrics(Clock clock) {
        mClock = clock;
    }

    /**
     * Push usage stats for WifiAwareStateMachine.enableUsage() to
     * histogram_aware_available_duration_ms.
     */
    public void recordEnableUsage() {
        synchronized (mLock) {
            if (mLastEnableUsageMs != 0) {
                Log.w(TAG, "enableUsage: mLastEnableUsage*Ms initialized!?");
            }
            mLastEnableUsageMs = mClock.getElapsedSinceBootMillis();
            mLastEnableUsageInThisSampleWindowMs = mLastEnableUsageMs;
        }
    }

    /**
     * Push usage stats for WifiAwareStateMachine.disableUsage() to
     * histogram_aware_available_duration_ms.
     */

    public void recordDisableUsage() {
        synchronized (mLock) {
            if (mLastEnableUsageMs == 0) {
                Log.e(TAG, "disableUsage: mLastEnableUsage not initialized!?");
                return;
            }

            long now = mClock.getElapsedSinceBootMillis();
            MetricsUtils.addValueToLogHistogram(now - mLastEnableUsageMs,
                    mHistogramAwareAvailableDurationMs, DURATION_LOG_HISTOGRAM);
            mAvailableTimeMs += now - mLastEnableUsageInThisSampleWindowMs;
            mLastEnableUsageMs = 0;
            mLastEnableUsageInThisSampleWindowMs = 0;
        }
    }

    /**
     * Push usage stats of Aware actually being enabled on-the-air: start
     */
    public void recordEnableAware() {
        synchronized (mLock) {
            if (mLastEnableAwareMs != 0) {
                return; // already enabled
            }
            mLastEnableAwareMs = mClock.getElapsedSinceBootMillis();
            mLastEnableAwareInThisSampleWindowMs = mLastEnableAwareMs;
        }
    }

    /**
     * Push usage stats of Aware actually being enabled on-the-air: stop (disable)
     */
    public void recordDisableAware() {
        synchronized (mLock) {
            if (mLastEnableAwareMs == 0) {
                return; // already disabled
            }

            long now = mClock.getElapsedSinceBootMillis();
            MetricsUtils.addValueToLogHistogram(now - mLastEnableAwareMs,
                    mHistogramAwareEnabledDurationMs, DURATION_LOG_HISTOGRAM);
            mEnabledTimeMs += now - mLastEnableAwareInThisSampleWindowMs;
            mLastEnableAwareMs = 0;
            mLastEnableAwareInThisSampleWindowMs = 0;
        }
    }

    /**
     * Push information about a new attach session.
     */
    public void recordAttachSession(int uid, boolean usesIdentityCallback,
            SparseArray<WifiAwareClientState> clients) {
        // count the number of clients with the specific uid
        int currentConcurrentCount = 0;
        for (int i = 0; i < clients.size(); ++i) {
            if (clients.valueAt(i).getUid() == uid) {
                ++currentConcurrentCount;
            }
        }

        synchronized (mLock) {
            AttachData data = mAttachDataByUid.get(uid);
            if (data == null) {
                data = new AttachData();
                mAttachDataByUid.put(uid, data);
            }
            data.mUsesIdentityCallback |= usesIdentityCallback;
            data.mMaxConcurrentAttaches = Math.max(data.mMaxConcurrentAttaches,
                    currentConcurrentCount);
            recordAttachStatus(NanStatusType.SUCCESS);
        }
    }

    /**
     * Push information about a new attach session status (recorded when attach session is created).
     */
    public void recordAttachStatus(int status) {
        synchronized (mLock) {
            addNanHalStatusToHistogram(status, mAttachStatusData);
        }
    }

    /**
     * Push duration information of an attach session.
     */
    public void recordAttachSessionDuration(long creationTime) {
        synchronized (mLock) {
            MetricsUtils.addValueToLogHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
                    mHistogramAttachDuration, DURATION_LOG_HISTOGRAM);
        }
    }

    /**
     * Push information about the new discovery session.
     */
    public void recordDiscoverySession(int uid, SparseArray<WifiAwareClientState> clients) {
        recordDiscoverySessionInternal(uid, clients, false, -1, -1);
    }

    /**
     * Push information about the new discovery session with ranging enabled
     */
    public void recordDiscoverySessionWithRanging(int uid, boolean isSubscriberWithRanging,
            int minRange, int maxRange, SparseArray<WifiAwareClientState> clients) {
        recordDiscoverySessionInternal(uid, clients, isSubscriberWithRanging, minRange, maxRange);
    }

    /**
     * Internal combiner of discovery session information.
     */
    private void recordDiscoverySessionInternal(int uid, SparseArray<WifiAwareClientState> clients,
            boolean isRangingEnabledSubscriber, int minRange, int maxRange) {
        // count the number of sessions per uid and overall
        int numPublishesInSystem = 0;
        int numSubscribesInSystem = 0;
        int numPublishesOnUid = 0;
        int numSubscribesOnUid = 0;

        int numPublishesWithRangingInSystem = 0;
        int numSubscribesWithRangingInSystem = 0;
        int numPublishesWithRangingOnUid = 0;
        int numSubscribesWithRangingOnUid = 0;

        for (int i = 0; i < clients.size(); ++i) {
            WifiAwareClientState client = clients.valueAt(i);
            boolean sameUid = client.getUid() == uid;

            SparseArray<WifiAwareDiscoverySessionState> sessions = client.getSessions();
            for (int j = 0; j < sessions.size(); ++j) {
                WifiAwareDiscoverySessionState session = sessions.valueAt(j);
                boolean isRangingEnabledForThisSession = session.isRangingEnabled();

                if (session.isPublishSession()) {
                    numPublishesInSystem += 1;
                    if (isRangingEnabledForThisSession) {
                        numPublishesWithRangingInSystem += 1;
                    }
                    if (sameUid) {
                        numPublishesOnUid += 1;
                        if (isRangingEnabledForThisSession) {
                            numPublishesWithRangingOnUid += 1;
                        }
                    }
                } else {
                    numSubscribesInSystem += 1;
                    if (isRangingEnabledForThisSession) {
                        numSubscribesWithRangingInSystem += 1;
                    }
                    if (sameUid) {
                        numSubscribesOnUid += 1;
                        if (isRangingEnabledForThisSession) {
                            numSubscribesWithRangingOnUid += 1;
                        }
                    }
                }
            }
        }

        synchronized (mLock) {
            mMaxPublishInApp = Math.max(mMaxPublishInApp, numPublishesOnUid);
            mMaxSubscribeInApp = Math.max(mMaxSubscribeInApp, numSubscribesOnUid);
            mMaxDiscoveryInApp = Math.max(mMaxDiscoveryInApp,
                    numPublishesOnUid + numSubscribesOnUid);
            mMaxPublishInSystem = Math.max(mMaxPublishInSystem, numPublishesInSystem);
            mMaxSubscribeInSystem = Math.max(mMaxSubscribeInSystem, numSubscribesInSystem);
            mMaxDiscoveryInSystem = Math.max(mMaxDiscoveryInSystem,
                    numPublishesInSystem + numSubscribesInSystem);

            mMaxPublishWithRangingInApp = Math.max(mMaxPublishWithRangingInApp,
                    numPublishesWithRangingOnUid);
            mMaxSubscribeWithRangingInApp = Math.max(mMaxSubscribeWithRangingInApp,
                    numSubscribesWithRangingOnUid);
            mMaxPublishWithRangingInSystem = Math.max(mMaxPublishWithRangingInSystem,
                    numPublishesWithRangingInSystem);
            mMaxSubscribeWithRangingInSystem = Math.max(mMaxSubscribeWithRangingInSystem,
                    numSubscribesWithRangingInSystem);
            if (isRangingEnabledSubscriber) {
                mNumSubscribesWithRanging += 1;
            }

            if (minRange != -1) {
                MetricsUtils.addValueToLinearHistogram(minRange, mHistogramSubscribeGeofenceMin,
                        RANGING_LIMIT_METERS);
            }
            if (maxRange != -1) {
                MetricsUtils.addValueToLinearHistogram(maxRange, mHistogramSubscribeGeofenceMax,
                        RANGING_LIMIT_METERS);
            }
        }
    }

    /**
     * Push information about a new discovery session status (recorded when the discovery session is
     * created).
     */
    public void recordDiscoveryStatus(int uid, int status, boolean isPublish) {
        synchronized (mLock) {
            if (isPublish) {
                addNanHalStatusToHistogram(status, mPublishStatusData);
            } else {
                addNanHalStatusToHistogram(status, mSubscribeStatusData);
            }

            if (status == NanStatusType.NO_RESOURCES_AVAILABLE) {
                mAppsWithDiscoverySessionResourceFailure.add(uid);
            }
        }
    }

    /**
     * Push duration information of a discovery session.
     */
    public void recordDiscoverySessionDuration(long creationTime, boolean isPublish) {
        synchronized (mLock) {
            MetricsUtils.addValueToLogHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
                    isPublish ? mHistogramPublishDuration : mHistogramSubscribeDuration,
                    DURATION_LOG_HISTOGRAM);
        }
    }

    /**
     * Push information about Match indication (aka service discovered) for subscribe sessions
     * which enabled ranging. Collect information about whether or not service discovery was
     * triggered with ranging information or without (i.e. ranging disabled for some reason).
     */
    public void recordMatchIndicationForRangeEnabledSubscribe(boolean rangeProvided) {
        if (rangeProvided) {
            mNumMatchesWithRanging++;
        } else {
            mNumMatchesWithoutRangingForRangingEnabledSubscribes++;
        }
    }

    /**
     * Record NDP (and by extension NDI) usage - on successful creation of an NDP.
     */
    public void recordNdpCreation(int uid, String packageName,
            Map<WifiAwareNetworkSpecifier, WifiAwareDataPathStateManager
                    .AwareNetworkRequestInformation> networkRequestCache) {
        int numNdpInApp = 0;
        int numSecureNdpInApp = 0;
        int numNdpInSystem = 0;
        int numSecureNdpInSystem = 0;

        Map<String, Integer> ndpPerNdiMap = new HashMap<>();
        Set<String> ndiInApp = new HashSet<>();
        Set<String> ndiInSystem = new HashSet<>();

        for (WifiAwareDataPathStateManager.AwareNetworkRequestInformation anri :
                networkRequestCache.values()) {
            if (anri.state
                    != WifiAwareDataPathStateManager.AwareNetworkRequestInformation
                    .STATE_CONFIRMED) {
                continue; // only count completed (up-and-running) NDPs
            }

            boolean sameApp = (anri.uid == uid) && TextUtils.equals(anri.packageName, packageName);
            boolean isSecure = !TextUtils.isEmpty(anri.networkSpecifier.passphrase) || (
                    anri.networkSpecifier.pmk != null && anri.networkSpecifier.pmk.length != 0);

            // in-app stats
            if (sameApp) {
                numNdpInApp += 1;
                if (isSecure) {
                    numSecureNdpInApp += 1;
                }

                ndiInApp.add(anri.interfaceName);
            }

            // system stats
            numNdpInSystem += 1;
            if (isSecure) {
                numSecureNdpInSystem += 1;
            }

            // ndp/ndi stats
            Integer ndpCount = ndpPerNdiMap.get(anri.interfaceName);
            if (ndpCount == null) {
                ndpPerNdiMap.put(anri.interfaceName, 1);
            } else {
                ndpPerNdiMap.put(anri.interfaceName, ndpCount + 1);
            }

            // ndi stats
            ndiInSystem.add(anri.interfaceName);
        }

        synchronized (mLock) {
            mMaxNdiInApp = Math.max(mMaxNdiInApp, ndiInApp.size());
            mMaxNdpInApp = Math.max(mMaxNdpInApp, numNdpInApp);
            mMaxSecureNdpInApp = Math.max(mMaxSecureNdpInApp, numSecureNdpInApp);
            mMaxNdiInSystem = Math.max(mMaxNdiInSystem, ndiInSystem.size());
            mMaxNdpInSystem = Math.max(mMaxNdpInSystem, numNdpInSystem);
            mMaxSecureNdpInSystem = Math.max(mMaxSecureNdpInSystem, numSecureNdpInSystem);
            mMaxNdpPerNdi = Math.max(mMaxNdpPerNdi, Collections.max(ndpPerNdiMap.values()));
        }
    }

    /**
     * Record the completion status of NDP negotiation. There are multiple steps in NDP negotiation
     * a failure on any aborts the process and is recorded. A success on intermediate stages is
     * not recorded - only the final success.
     */
    public void recordNdpStatus(int status, boolean isOutOfBand, long startTimestamp) {
        synchronized (mLock) {
            if (isOutOfBand) {
                addNanHalStatusToHistogram(status, mOutOfBandNdpStatusData);
            } else {
                addNanHalStatusToHistogram(status, mInBandNdpStatusData);
            }

            if (status == NanStatusType.SUCCESS) {
                long creationTime = mClock.getElapsedSinceBootMillis() - startTimestamp;
                MetricsUtils.addValueToLogHistogram(creationTime, mNdpCreationTimeDuration,
                        DURATION_LOG_HISTOGRAM);
                mNdpCreationTimeMin = (mNdpCreationTimeMin == -1) ? creationTime : Math.min(
                        mNdpCreationTimeMin, creationTime);
                mNdpCreationTimeMax = Math.max(mNdpCreationTimeMax, creationTime);
                mNdpCreationTimeSum += creationTime;
                mNdpCreationTimeSumSq += creationTime * creationTime;
                mNdpCreationTimeNumSamples += 1;
            }
        }
    }

    /**
     * Record the duration of the NDP session. The creation time is assumed to be the time at
     * which a confirm message was received (i.e. the end of the setup negotiation).
     */
    public void recordNdpSessionDuration(long creationTime) {
        synchronized (mLock) {
            MetricsUtils.addValueToLogHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
                    mHistogramNdpDuration, DURATION_LOG_HISTOGRAM);
        }
    }

    /**
     * Consolidate all metrics into the proto.
     */
    public WifiMetricsProto.WifiAwareLog consolidateProto() {
        WifiMetricsProto.WifiAwareLog log = new WifiMetricsProto.WifiAwareLog();
        long now = mClock.getElapsedSinceBootMillis();
        synchronized (mLock) {
            log.histogramAwareAvailableDurationMs = histogramToProtoArray(
                    MetricsUtils.logHistogramToGenericBuckets(mHistogramAwareAvailableDurationMs,
                            DURATION_LOG_HISTOGRAM));
            log.availableTimeMs = mAvailableTimeMs;
            if (mLastEnableUsageInThisSampleWindowMs != 0) {
                log.availableTimeMs += now - mLastEnableUsageInThisSampleWindowMs;
            }

            log.histogramAwareEnabledDurationMs = histogramToProtoArray(
                    MetricsUtils.logHistogramToGenericBuckets(mHistogramAwareEnabledDurationMs,
                            DURATION_LOG_HISTOGRAM));
            log.enabledTimeMs = mEnabledTimeMs;
            if (mLastEnableAwareInThisSampleWindowMs != 0) {
                log.enabledTimeMs += now - mLastEnableAwareInThisSampleWindowMs;
            }

            log.numApps = mAttachDataByUid.size();
            log.numAppsUsingIdentityCallback = 0;
            log.maxConcurrentAttachSessionsInApp = 0;
            for (AttachData ad: mAttachDataByUid.values()) {
                if (ad.mUsesIdentityCallback) {
                    ++log.numAppsUsingIdentityCallback;
                }
                log.maxConcurrentAttachSessionsInApp = Math.max(
                        log.maxConcurrentAttachSessionsInApp, ad.mMaxConcurrentAttaches);
            }
            log.histogramAttachSessionStatus = histogramToProtoArray(mAttachStatusData);
            log.histogramAttachDurationMs = histogramToProtoArray(
                    MetricsUtils.logHistogramToGenericBuckets(mHistogramAttachDuration,
                            DURATION_LOG_HISTOGRAM));

            log.maxConcurrentPublishInApp = mMaxPublishInApp;
            log.maxConcurrentSubscribeInApp = mMaxSubscribeInApp;
            log.maxConcurrentDiscoverySessionsInApp = mMaxDiscoveryInApp;
            log.maxConcurrentPublishInSystem = mMaxPublishInSystem;
            log.maxConcurrentSubscribeInSystem = mMaxSubscribeInSystem;
            log.maxConcurrentDiscoverySessionsInSystem = mMaxDiscoveryInSystem;
            log.histogramPublishStatus = histogramToProtoArray(mPublishStatusData);
            log.histogramSubscribeStatus = histogramToProtoArray(mSubscribeStatusData);
            log.numAppsWithDiscoverySessionFailureOutOfResources =
                    mAppsWithDiscoverySessionResourceFailure.size();
            log.histogramPublishSessionDurationMs = histogramToProtoArray(
                    MetricsUtils.logHistogramToGenericBuckets(mHistogramPublishDuration,
                            DURATION_LOG_HISTOGRAM));
            log.histogramSubscribeSessionDurationMs = histogramToProtoArray(
                    MetricsUtils.logHistogramToGenericBuckets(mHistogramSubscribeDuration,
                            DURATION_LOG_HISTOGRAM));

            log.maxConcurrentPublishWithRangingInApp = mMaxPublishWithRangingInApp;
            log.maxConcurrentSubscribeWithRangingInApp = mMaxSubscribeWithRangingInApp;
            log.maxConcurrentPublishWithRangingInSystem = mMaxPublishWithRangingInSystem;
            log.maxConcurrentSubscribeWithRangingInSystem = mMaxSubscribeWithRangingInSystem;
            log.histogramSubscribeGeofenceMin = histogramToProtoArray(
                    MetricsUtils.linearHistogramToGenericBuckets(mHistogramSubscribeGeofenceMin,
                            RANGING_LIMIT_METERS));
            log.histogramSubscribeGeofenceMax = histogramToProtoArray(
                    MetricsUtils.linearHistogramToGenericBuckets(mHistogramSubscribeGeofenceMax,
                            RANGING_LIMIT_METERS));
            log.numSubscribesWithRanging = mNumSubscribesWithRanging;
            log.numMatchesWithRanging = mNumMatchesWithRanging;
            log.numMatchesWithoutRangingForRangingEnabledSubscribes =
                    mNumMatchesWithoutRangingForRangingEnabledSubscribes;

            log.maxConcurrentNdiInApp = mMaxNdiInApp;
            log.maxConcurrentNdiInSystem = mMaxNdiInSystem;
            log.maxConcurrentNdpInApp = mMaxNdpInApp;
            log.maxConcurrentNdpInSystem = mMaxNdpInSystem;
            log.maxConcurrentSecureNdpInApp = mMaxSecureNdpInApp;
            log.maxConcurrentSecureNdpInSystem = mMaxSecureNdpInSystem;
            log.maxConcurrentNdpPerNdi = mMaxNdpPerNdi;
            log.histogramRequestNdpStatus = histogramToProtoArray(mInBandNdpStatusData);
            log.histogramRequestNdpOobStatus = histogramToProtoArray(mOutOfBandNdpStatusData);

            log.histogramNdpCreationTimeMs = histogramToProtoArray(
                    MetricsUtils.logHistogramToGenericBuckets(mNdpCreationTimeDuration,
                            DURATION_LOG_HISTOGRAM));
            log.ndpCreationTimeMsMin = mNdpCreationTimeMin;
            log.ndpCreationTimeMsMax = mNdpCreationTimeMax;
            log.ndpCreationTimeMsSum = mNdpCreationTimeSum;
            log.ndpCreationTimeMsSumOfSq = mNdpCreationTimeSumSq;
            log.ndpCreationTimeMsNumSamples = mNdpCreationTimeNumSamples;

            log.histogramNdpSessionDurationMs = histogramToProtoArray(
                    MetricsUtils.logHistogramToGenericBuckets(mHistogramNdpDuration,
                            DURATION_LOG_HISTOGRAM));
        }
        return log;
    }

    /**
     * clear Wi-Fi Aware metrics
     */
    public void clear() {
        long now = mClock.getElapsedSinceBootMillis();
        synchronized (mLock) {
            // don't clear mLastEnableUsage since could be valid for next measurement period
            mHistogramAwareAvailableDurationMs.clear();
            mAvailableTimeMs = 0;
            if (mLastEnableUsageInThisSampleWindowMs != 0) {
                mLastEnableUsageInThisSampleWindowMs = now;
            }

            // don't clear mLastEnableAware since could be valid for next measurement period
            mHistogramAwareEnabledDurationMs.clear();
            mEnabledTimeMs = 0;
            if (mLastEnableAwareInThisSampleWindowMs != 0) {
                mLastEnableAwareInThisSampleWindowMs = now;
            }

            mAttachDataByUid.clear();
            mAttachStatusData.clear();
            mHistogramAttachDuration.clear();

            mMaxPublishInApp = 0;
            mMaxSubscribeInApp = 0;
            mMaxDiscoveryInApp = 0;
            mMaxPublishInSystem = 0;
            mMaxSubscribeInSystem = 0;
            mMaxDiscoveryInSystem = 0;
            mPublishStatusData.clear();
            mSubscribeStatusData.clear();
            mHistogramPublishDuration.clear();
            mHistogramSubscribeDuration.clear();
            mAppsWithDiscoverySessionResourceFailure.clear();

            mMaxPublishWithRangingInApp = 0;
            mMaxSubscribeWithRangingInApp = 0;
            mMaxPublishWithRangingInSystem = 0;
            mMaxSubscribeWithRangingInSystem = 0;
            mHistogramSubscribeGeofenceMin.clear();
            mHistogramSubscribeGeofenceMax.clear();
            mNumSubscribesWithRanging = 0;
            mNumMatchesWithRanging = 0;
            mNumMatchesWithoutRangingForRangingEnabledSubscribes = 0;

            mMaxNdiInApp = 0;
            mMaxNdpInApp = 0;
            mMaxSecureNdpInApp = 0;
            mMaxNdiInSystem = 0;
            mMaxNdpInSystem = 0;
            mMaxSecureNdpInSystem = 0;
            mMaxNdpPerNdi = 0;
            mInBandNdpStatusData.clear();
            mOutOfBandNdpStatusData.clear();

            mNdpCreationTimeDuration.clear();
            mNdpCreationTimeMin = -1;
            mNdpCreationTimeMax = 0;
            mNdpCreationTimeSum = 0;
            mNdpCreationTimeSumSq = 0;
            mNdpCreationTimeNumSamples = 0;

            mHistogramNdpDuration.clear();
        }
    }

    /**
     * Dump all WifiAwareMetrics to console (pw) - this method is never called to dump the
     * serialized metrics (handled by parent WifiMetrics).
     *
     * @param fd   unused
     * @param pw   PrintWriter for writing dump to
     * @param args unused
     */
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        synchronized (mLock) {
            pw.println("mLastEnableUsageMs:" + mLastEnableUsageMs);
            pw.println(
                    "mLastEnableUsageInThisSampleWindowMs:" + mLastEnableUsageInThisSampleWindowMs);
            pw.println("mAvailableTimeMs:" + mAvailableTimeMs);
            pw.println("mHistogramAwareAvailableDurationMs:");
            for (int i = 0; i < mHistogramAwareAvailableDurationMs.size(); ++i) {
                pw.println("  " + mHistogramAwareAvailableDurationMs.keyAt(i) + ": "
                        + mHistogramAwareAvailableDurationMs.valueAt(i));
            }

            pw.println("mLastEnableAwareMs:" + mLastEnableAwareMs);
            pw.println(
                    "mLastEnableAwareInThisSampleWindowMs:" + mLastEnableAwareInThisSampleWindowMs);
            pw.println("mEnabledTimeMs:" + mEnabledTimeMs);
            pw.println("mHistogramAwareEnabledDurationMs:");
            for (int i = 0; i < mHistogramAwareEnabledDurationMs.size(); ++i) {
                pw.println("  " + mHistogramAwareEnabledDurationMs.keyAt(i) + ": "
                        + mHistogramAwareEnabledDurationMs.valueAt(i));
            }

            pw.println("mAttachDataByUid:");
            for (Map.Entry<Integer, AttachData> ade: mAttachDataByUid.entrySet()) {
                pw.println("  " + "uid=" + ade.getKey() + ": identity="
                        + ade.getValue().mUsesIdentityCallback + ", maxConcurrent="
                        + ade.getValue().mMaxConcurrentAttaches);
            }
            pw.println("mAttachStatusData:");
            for (int i = 0; i < mAttachStatusData.size(); ++i) {
                pw.println("  " + mAttachStatusData.keyAt(i) + ": "
                        + mAttachStatusData.valueAt(i));
            }
            pw.println("mHistogramAttachDuration:");
            for (int i = 0; i < mHistogramAttachDuration.size(); ++i) {
                pw.println("  " + mHistogramAttachDuration.keyAt(i) + ": "
                        + mHistogramAttachDuration.valueAt(i));
            }

            pw.println("mMaxPublishInApp:" + mMaxPublishInApp);
            pw.println("mMaxSubscribeInApp:" + mMaxSubscribeInApp);
            pw.println("mMaxDiscoveryInApp:" + mMaxDiscoveryInApp);
            pw.println("mMaxPublishInSystem:" + mMaxPublishInSystem);
            pw.println("mMaxSubscribeInSystem:" + mMaxSubscribeInSystem);
            pw.println("mMaxDiscoveryInSystem:" + mMaxDiscoveryInSystem);
            pw.println("mPublishStatusData:");
            for (int i = 0; i < mPublishStatusData.size(); ++i) {
                pw.println("  " + mPublishStatusData.keyAt(i) + ": "
                        + mPublishStatusData.valueAt(i));
            }
            pw.println("mSubscribeStatusData:");
            for (int i = 0; i < mSubscribeStatusData.size(); ++i) {
                pw.println("  " + mSubscribeStatusData.keyAt(i) + ": "
                        + mSubscribeStatusData.valueAt(i));
            }
            pw.println("mHistogramPublishDuration:");
            for (int i = 0; i < mHistogramPublishDuration.size(); ++i) {
                pw.println("  " + mHistogramPublishDuration.keyAt(i) + ": "
                        + mHistogramPublishDuration.valueAt(i));
            }
            pw.println("mHistogramSubscribeDuration:");
            for (int i = 0; i < mHistogramSubscribeDuration.size(); ++i) {
                pw.println("  " + mHistogramSubscribeDuration.keyAt(i) + ": "
                        + mHistogramSubscribeDuration.valueAt(i));
            }
            pw.println("mAppsWithDiscoverySessionResourceFailure:");
            for (Integer uid: mAppsWithDiscoverySessionResourceFailure) {
                pw.println("  " + uid);

            }

            pw.println("mMaxPublishWithRangingInApp:" + mMaxPublishWithRangingInApp);
            pw.println("mMaxSubscribeWithRangingInApp:" + mMaxSubscribeWithRangingInApp);
            pw.println("mMaxPublishWithRangingInSystem:" + mMaxPublishWithRangingInSystem);
            pw.println("mMaxSubscribeWithRangingInSystem:" + mMaxSubscribeWithRangingInSystem);
            pw.println("mHistogramSubscribeGeofenceMin:");
            for (int i = 0; i < mHistogramSubscribeGeofenceMin.size(); ++i) {
                pw.println("  " + mHistogramSubscribeGeofenceMin.keyAt(i) + ": "
                        + mHistogramSubscribeGeofenceMin.valueAt(i));
            }
            pw.println("mHistogramSubscribeGeofenceMax:");
            for (int i = 0; i < mHistogramSubscribeGeofenceMax.size(); ++i) {
                pw.println("  " + mHistogramSubscribeGeofenceMax.keyAt(i) + ": "
                        + mHistogramSubscribeGeofenceMax.valueAt(i));
            }
            pw.println("mNumSubscribesWithRanging:" + mNumSubscribesWithRanging);
            pw.println("mNumMatchesWithRanging:" + mNumMatchesWithRanging);
            pw.println("mNumMatchesWithoutRangingForRangingEnabledSubscribes:"
                    + mNumMatchesWithoutRangingForRangingEnabledSubscribes);

            pw.println("mMaxNdiInApp:" + mMaxNdiInApp);
            pw.println("mMaxNdpInApp:" + mMaxNdpInApp);
            pw.println("mMaxSecureNdpInApp:" + mMaxSecureNdpInApp);
            pw.println("mMaxNdiInSystem:" + mMaxNdiInSystem);
            pw.println("mMaxNdpInSystem:" + mMaxNdpInSystem);
            pw.println("mMaxSecureNdpInSystem:" + mMaxSecureNdpInSystem);
            pw.println("mMaxNdpPerNdi:" + mMaxNdpPerNdi);
            pw.println("mInBandNdpStatusData:");
            for (int i = 0; i < mInBandNdpStatusData.size(); ++i) {
                pw.println("  " + mInBandNdpStatusData.keyAt(i) + ": "
                        + mInBandNdpStatusData.valueAt(i));
            }
            pw.println("mOutOfBandNdpStatusData:");
            for (int i = 0; i < mOutOfBandNdpStatusData.size(); ++i) {
                pw.println("  " + mOutOfBandNdpStatusData.keyAt(i) + ": "
                        + mOutOfBandNdpStatusData.valueAt(i));
            }

            pw.println("mNdpCreationTimeDuration:");
            for (int i = 0; i < mNdpCreationTimeDuration.size(); ++i) {
                pw.println("  " + mNdpCreationTimeDuration.keyAt(i) + ": "
                        + mNdpCreationTimeDuration.valueAt(i));
            }
            pw.println("mNdpCreationTimeMin:" + mNdpCreationTimeMin);
            pw.println("mNdpCreationTimeMax:" + mNdpCreationTimeMax);
            pw.println("mNdpCreationTimeSum:" + mNdpCreationTimeSum);
            pw.println("mNdpCreationTimeSumSq:" + mNdpCreationTimeSumSq);
            pw.println("mNdpCreationTimeNumSamples:" + mNdpCreationTimeNumSamples);

            pw.println("mHistogramNdpDuration:");
            for (int i = 0; i < mHistogramNdpDuration.size(); ++i) {
                pw.println("  " + mHistogramNdpDuration.keyAt(i) + ": "
                        + mHistogramNdpDuration.valueAt(i));
            }
        }
    }

    // histogram utilities
    /**
     * Convert a generic bucket to Aware HistogramBucket proto.
     */
    @VisibleForTesting
    public static WifiMetricsProto.WifiAwareLog.HistogramBucket[] histogramToProtoArray(
            MetricsUtils.GenericBucket[] buckets) {
        WifiMetricsProto.WifiAwareLog.HistogramBucket[] protoArray =
                new WifiMetricsProto.WifiAwareLog.HistogramBucket[buckets.length];

        for (int i = 0; i < buckets.length; ++i) {
            protoArray[i] = new WifiMetricsProto.WifiAwareLog.HistogramBucket();
            protoArray[i].start = buckets[i].start;
            protoArray[i].end = buckets[i].end;
            protoArray[i].count = buckets[i].count;
        }

        return protoArray;
    }

    /**
     * Adds the NanStatusType to the histogram (translating to the proto enumeration of the status).
     */
    public static void addNanHalStatusToHistogram(int halStatus, SparseIntArray histogram) {
        int protoStatus = convertNanStatusTypeToProtoEnum(halStatus);
        int newValue = histogram.get(protoStatus) + 1;
        histogram.put(protoStatus, newValue);
    }

    /**
     * Converts a histogram of proto NanStatusTypeEnum to a raw proto histogram.
     */
    @VisibleForTesting
    public static WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] histogramToProtoArray(
            SparseIntArray histogram) {
        WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] protoArray =
                new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[histogram.size()];

        for (int i = 0; i < histogram.size(); ++i) {
            protoArray[i] = new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket();
            protoArray[i].nanStatusType = histogram.keyAt(i);
            protoArray[i].count = histogram.valueAt(i);
        }

        return protoArray;
    }

    /**
     * Convert a HAL NanStatusType enum to a Metrics proto enum NanStatusTypeEnum.
     */
    public static int convertNanStatusTypeToProtoEnum(int nanStatusType) {
        switch (nanStatusType) {
            case NanStatusType.SUCCESS:
                return WifiMetricsProto.WifiAwareLog.SUCCESS;
            case NanStatusType.INTERNAL_FAILURE:
                return WifiMetricsProto.WifiAwareLog.INTERNAL_FAILURE;
            case NanStatusType.PROTOCOL_FAILURE:
                return WifiMetricsProto.WifiAwareLog.PROTOCOL_FAILURE;
            case NanStatusType.INVALID_SESSION_ID:
                return WifiMetricsProto.WifiAwareLog.INVALID_SESSION_ID;
            case NanStatusType.NO_RESOURCES_AVAILABLE:
                return WifiMetricsProto.WifiAwareLog.NO_RESOURCES_AVAILABLE;
            case NanStatusType.INVALID_ARGS:
                return WifiMetricsProto.WifiAwareLog.INVALID_ARGS;
            case NanStatusType.INVALID_PEER_ID:
                return WifiMetricsProto.WifiAwareLog.INVALID_PEER_ID;
            case NanStatusType.INVALID_NDP_ID:
                return WifiMetricsProto.WifiAwareLog.INVALID_NDP_ID;
            case NanStatusType.NAN_NOT_ALLOWED:
                return WifiMetricsProto.WifiAwareLog.NAN_NOT_ALLOWED;
            case NanStatusType.NO_OTA_ACK:
                return WifiMetricsProto.WifiAwareLog.NO_OTA_ACK;
            case NanStatusType.ALREADY_ENABLED:
                return WifiMetricsProto.WifiAwareLog.ALREADY_ENABLED;
            case NanStatusType.FOLLOWUP_TX_QUEUE_FULL:
                return WifiMetricsProto.WifiAwareLog.FOLLOWUP_TX_QUEUE_FULL;
            case NanStatusType.UNSUPPORTED_CONCURRENCY_NAN_DISABLED:
                return WifiMetricsProto.WifiAwareLog.UNSUPPORTED_CONCURRENCY_NAN_DISABLED;
            default:
                Log.e(TAG, "Unrecognized NanStatusType: " + nanStatusType);
                return WifiMetricsProto.WifiAwareLog.UNKNOWN_HAL_STATUS;
        }
    }
}
