WifiAwareMetrics.java revision da389f4b673e0f3fe54dc5b1123221037eb96e42
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi.aware;
18
19import android.hardware.wifi.V1_0.NanStatusType;
20import android.util.Log;
21import android.util.SparseArray;
22import android.util.SparseIntArray;
23
24import com.android.internal.annotations.VisibleForTesting;
25import com.android.server.wifi.Clock;
26import com.android.server.wifi.nano.WifiMetricsProto;
27
28import java.io.FileDescriptor;
29import java.io.PrintWriter;
30import java.util.HashMap;
31import java.util.HashSet;
32import java.util.Map;
33import java.util.Set;
34
35/**
36 * Wi-Fi Aware metric container/processor.
37 */
38public class WifiAwareMetrics {
39    private static final String TAG = "WifiAwareMetrics";
40    private static final boolean DBG = false;
41
42    // Histogram: 8 buckets (i=0, ..., 7) of 9 slots in range 10^i -> 10^(i+1)
43    // Buckets:
44    //    1 -> 10: 9 @ 1
45    //    10 -> 100: 9 @ 10
46    //    100 -> 1000: 9 @ 10^2
47    //    10^3 -> 10^4: 9 @ 10^3
48    //    10^4 -> 10^5: 9 @ 10^4
49    //    10^5 -> 10^6: 9 @ 10^5
50    //    10^6 -> 10^7: 9 @ 10^6
51    //    10^7 -> 10^8: 9 @ 10^7 --> 10^8 ms -> 10^5s -> 28 hours
52    private static final HistParms DURATION_LOG_HISTOGRAM = new HistParms(0, 1, 10, 9, 8);
53
54    private final Object mLock = new Object();
55    private final Clock mClock;
56
57    // enableUsage/disableUsage data
58    private long mLastEnableUsageMs = 0;
59    private long mLastEnableUsageInThisSampleWindowMs = 0;
60    private long mAvailableTimeMs = 0;
61    private SparseIntArray mHistogramAwareAvailableDurationMs = new SparseIntArray();
62
63    // enabled data
64    private long mLastEnableAwareMs = 0;
65    private long mLastEnableAwareInThisSampleWindowMs = 0;
66    private long mEnabledTimeMs = 0;
67    private SparseIntArray mHistogramAwareEnabledDurationMs = new SparseIntArray();
68
69    // attach data
70    private static class AttachData {
71        boolean mUsesIdentityCallback; // do any attach sessions of the UID use identity callback
72        int mMaxConcurrentAttaches;
73    }
74    private Map<Integer, AttachData> mAttachDataByUid = new HashMap<>();
75    private SparseIntArray mAttachStatusData = new SparseIntArray();
76    private SparseIntArray mHistogramAttachDuration = new SparseIntArray();
77
78    // discovery data
79    private SparseIntArray mMaxPublishByUid = new SparseIntArray();
80    private SparseIntArray mMaxSubscribeByUid = new SparseIntArray();
81    private SparseIntArray mMaxDiscoveryByUid = new SparseIntArray();
82    private int mMaxPublishInSystem = 0;
83    private int mMaxSubscribeInSystem = 0;
84    private int mMaxDiscoveryInSystem = 0;
85    private SparseIntArray mPublishStatusData = new SparseIntArray();
86    private SparseIntArray mSubscribeStatusData = new SparseIntArray();
87    private SparseIntArray mHistogramPublishDuration = new SparseIntArray();
88    private SparseIntArray mHistogramSubscribeDuration = new SparseIntArray();
89    private Set<Integer> mAppsWithDiscoverySessionResourceFailure = new HashSet<>();
90
91    public WifiAwareMetrics(Clock clock) {
92        mClock = clock;
93    }
94
95    /**
96     * Push usage stats for WifiAwareStateMachine.enableUsage() to
97     * histogram_aware_available_duration_ms.
98     */
99    public void recordEnableUsage() {
100        synchronized (mLock) {
101            if (mLastEnableUsageMs != 0) {
102                Log.w(TAG, "enableUsage: mLastEnableUsage*Ms initialized!?");
103            }
104            mLastEnableUsageMs = mClock.getElapsedSinceBootMillis();
105            mLastEnableUsageInThisSampleWindowMs = mLastEnableUsageMs;
106        }
107    }
108
109    /**
110     * Push usage stats for WifiAwareStateMachine.disableUsage() to
111     * histogram_aware_available_duration_ms.
112     */
113
114    public void recordDisableUsage() {
115        synchronized (mLock) {
116            if (mLastEnableUsageMs == 0) {
117                Log.e(TAG, "disableUsage: mLastEnableUsage not initialized!?");
118                return;
119            }
120
121            long now = mClock.getElapsedSinceBootMillis();
122            addLogValueToHistogram(now - mLastEnableUsageMs, mHistogramAwareAvailableDurationMs,
123                    DURATION_LOG_HISTOGRAM);
124            mAvailableTimeMs += now - mLastEnableUsageInThisSampleWindowMs;
125            mLastEnableUsageMs = 0;
126            mLastEnableUsageInThisSampleWindowMs = 0;
127        }
128    }
129
130    /**
131     * Push usage stats of Aware actually being enabled on-the-air: start
132     */
133    public void recordEnableAware() {
134        synchronized (mLock) {
135            if (mLastEnableAwareMs != 0) {
136                return; // already enabled
137            }
138            mLastEnableAwareMs = mClock.getElapsedSinceBootMillis();
139            mLastEnableAwareInThisSampleWindowMs = mLastEnableAwareMs;
140        }
141    }
142
143    /**
144     * Push usage stats of Aware actually being enabled on-the-air: stop (disable)
145     */
146    public void recordDisableAware() {
147        synchronized (mLock) {
148            if (mLastEnableAwareMs == 0) {
149                return; // already disabled
150            }
151
152            long now = mClock.getElapsedSinceBootMillis();
153            addLogValueToHistogram(now - mLastEnableAwareMs, mHistogramAwareEnabledDurationMs,
154                    DURATION_LOG_HISTOGRAM);
155            mEnabledTimeMs += now - mLastEnableAwareInThisSampleWindowMs;
156            mLastEnableAwareMs = 0;
157            mLastEnableAwareInThisSampleWindowMs = 0;
158        }
159    }
160
161    /**
162     * Push information about a new attach session.
163     */
164    public void recordAttachSession(int uid, boolean usesIdentityCallback,
165            SparseArray<WifiAwareClientState> clients) {
166        // count the number of clients with the specific uid
167        int currentConcurrentCount = 0;
168        for (int i = 0; i < clients.size(); ++i) {
169            if (clients.valueAt(i).getUid() == uid) {
170                ++currentConcurrentCount;
171            }
172        }
173
174        synchronized (mLock) {
175            AttachData data = mAttachDataByUid.get(uid);
176            if (data == null) {
177                data = new AttachData();
178                mAttachDataByUid.put(uid, data);
179            }
180            data.mUsesIdentityCallback |= usesIdentityCallback;
181            data.mMaxConcurrentAttaches = Math.max(data.mMaxConcurrentAttaches,
182                    currentConcurrentCount);
183            recordAttachStatus(NanStatusType.SUCCESS);
184        }
185    }
186
187    /**
188     * Push information about a new attach session status (recorded when attach session is created).
189     */
190    public void recordAttachStatus(int status) {
191        synchronized (mLock) {
192            mAttachStatusData.put(status, mAttachStatusData.get(status) + 1);
193        }
194    }
195
196    /**
197     * Push duration information of an attach session.
198     */
199    public void recordAttachSessionDuration(long creationTime) {
200        synchronized (mLock) {
201            addLogValueToHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
202                    mHistogramAttachDuration,
203                    DURATION_LOG_HISTOGRAM);
204        }
205    }
206
207    /**
208     * Push information about the new discovery session.
209     */
210    public void recordDiscoverySession(int uid, boolean isPublish,
211            SparseArray<WifiAwareClientState> clients) {
212        // count the number of sessions per uid and overall
213        int numPublishesInSystem = 0;
214        int numSubscribesInSystem = 0;
215        int numPublishesOnUid = 0;
216        int numSubscribesOnUid = 0;
217
218        for (int i = 0; i < clients.size(); ++i) {
219            WifiAwareClientState client = clients.valueAt(i);
220            boolean sameUid = client.getUid() == uid;
221
222            SparseArray<WifiAwareDiscoverySessionState> sessions = client.getSessions();
223            for (int j = 0; j < sessions.size(); ++j) {
224                WifiAwareDiscoverySessionState session = sessions.valueAt(j);
225
226                if (session.isPublishSession()) {
227                    numPublishesInSystem += 1;
228                    if (sameUid) {
229                        numPublishesOnUid += 1;
230                    }
231                } else {
232                    numSubscribesInSystem += 1;
233                    if (sameUid) {
234                        numSubscribesOnUid += 1;
235                    }
236                }
237            }
238        }
239
240        synchronized (mLock) {
241            mMaxPublishByUid.put(uid, Math.max(mMaxPublishByUid.get(uid), numPublishesOnUid));
242            mMaxSubscribeByUid.put(uid, Math.max(mMaxSubscribeByUid.get(uid), numSubscribesOnUid));
243            mMaxDiscoveryByUid.put(uid,
244                    Math.max(mMaxDiscoveryByUid.get(uid), numPublishesOnUid + numSubscribesOnUid));
245            mMaxPublishInSystem = Math.max(mMaxPublishInSystem, numPublishesInSystem);
246            mMaxSubscribeInSystem = Math.max(mMaxSubscribeInSystem, numSubscribesInSystem);
247            mMaxDiscoveryInSystem = Math.max(mMaxDiscoveryInSystem,
248                    numPublishesInSystem + numSubscribesInSystem);
249        }
250    }
251
252    /**
253     * Push information about a new discovery session status (recorded when the discovery session is
254     * created).
255     */
256    public void recordDiscoveryStatus(int uid, int status, boolean isPublish) {
257        synchronized (mLock) {
258            if (isPublish) {
259                mPublishStatusData.put(status, mPublishStatusData.get(status) + 1);
260            } else {
261                mSubscribeStatusData.put(status, mSubscribeStatusData.get(status) + 1);
262            }
263
264            if (status == NanStatusType.NO_RESOURCES_AVAILABLE) {
265                mAppsWithDiscoverySessionResourceFailure.add(uid);
266            }
267        }
268    }
269
270    /**
271     * Push duration information of a discovery session.
272     */
273    public void recordDiscoverySessionDuration(long creationTime, boolean isPublish) {
274        synchronized (mLock) {
275            addLogValueToHistogram(mClock.getElapsedSinceBootMillis() - creationTime,
276                    isPublish ? mHistogramPublishDuration : mHistogramSubscribeDuration,
277                    DURATION_LOG_HISTOGRAM);
278        }
279    }
280
281    /**
282     * Consolidate all metrics into the proto.
283     */
284    public WifiMetricsProto.WifiAwareLog consolidateProto() {
285        WifiMetricsProto.WifiAwareLog log = new WifiMetricsProto.WifiAwareLog();
286        long now = mClock.getElapsedSinceBootMillis();
287        synchronized (mLock) {
288            log.histogramAwareAvailableDurationMs = histogramToProtoArray(
289                    mHistogramAwareAvailableDurationMs, DURATION_LOG_HISTOGRAM);
290            log.availableTimeMs = mAvailableTimeMs;
291            if (mLastEnableUsageInThisSampleWindowMs != 0) {
292                log.availableTimeMs += now - mLastEnableUsageInThisSampleWindowMs;
293            }
294
295            log.histogramAwareEnabledDurationMs = histogramToProtoArray(
296                    mHistogramAwareEnabledDurationMs, DURATION_LOG_HISTOGRAM);
297            log.enabledTimeMs = mEnabledTimeMs;
298            if (mLastEnableAwareInThisSampleWindowMs != 0) {
299                log.enabledTimeMs += now - mLastEnableAwareInThisSampleWindowMs;
300            }
301
302            log.numApps = mAttachDataByUid.size();
303            log.numAppsUsingIdentityCallback = 0;
304            log.maxConcurrentAttachSessionsInApp = 0;
305            for (AttachData ad: mAttachDataByUid.values()) {
306                if (ad.mUsesIdentityCallback) {
307                    ++log.numAppsUsingIdentityCallback;
308                }
309                log.maxConcurrentAttachSessionsInApp = Math.max(
310                        log.maxConcurrentAttachSessionsInApp, ad.mMaxConcurrentAttaches);
311            }
312            log.histogramAttachSessionStatus = histogramToProtoArray(mAttachStatusData);
313            log.histogramAttachDurationMs = histogramToProtoArray(mHistogramAttachDuration,
314                    DURATION_LOG_HISTOGRAM);
315
316            log.maxConcurrentPublishInApp = max(mMaxPublishByUid);
317            log.maxConcurrentSubscribeInApp = max(mMaxSubscribeByUid);
318            log.maxConcurrentDiscoverySessionsInApp = max(mMaxDiscoveryByUid);
319            log.maxConcurrentPublishInSystem = mMaxPublishInSystem;
320            log.maxConcurrentSubscribeInSystem = mMaxSubscribeInSystem;
321            log.maxConcurrentDiscoverySessionsInSystem = mMaxDiscoveryInSystem;
322            log.histogramPublishStatus = histogramToProtoArray(mPublishStatusData);
323            log.histogramSubscribeStatus = histogramToProtoArray(mSubscribeStatusData);
324            log.numAppsWithDiscoverySessionFailureOutOfResources =
325                    mAppsWithDiscoverySessionResourceFailure.size();
326            log.histogramPublishSessionDurationMs = histogramToProtoArray(mHistogramPublishDuration,
327                    DURATION_LOG_HISTOGRAM);
328            log.histogramSubscribeSessionDurationMs = histogramToProtoArray(
329                    mHistogramSubscribeDuration, DURATION_LOG_HISTOGRAM);
330        }
331        return log;
332    }
333
334    /**
335     * clear Wi-Fi Aware metrics
336     */
337    public void clear() {
338        long now = mClock.getElapsedSinceBootMillis();
339        synchronized (mLock) {
340            // don't clear mLastEnableUsage since could be valid for next measurement period
341            mHistogramAwareAvailableDurationMs.clear();
342            mAvailableTimeMs = 0;
343            if (mLastEnableUsageInThisSampleWindowMs != 0) {
344                mLastEnableUsageInThisSampleWindowMs = now;
345            }
346
347            // don't clear mLastEnableAware since could be valid for next measurement period
348            mHistogramAwareEnabledDurationMs.clear();
349            mEnabledTimeMs = 0;
350            if (mLastEnableAwareInThisSampleWindowMs != 0) {
351                mLastEnableAwareInThisSampleWindowMs = now;
352            }
353
354            mAttachDataByUid.clear();
355            mAttachStatusData.clear();
356            mHistogramAttachDuration.clear();
357
358            mMaxPublishByUid.clear();
359            mMaxSubscribeByUid.clear();
360            mMaxDiscoveryByUid.clear();
361            mMaxPublishInSystem = 0;
362            mMaxSubscribeInSystem = 0;
363            mMaxDiscoveryInSystem = 0;
364            mPublishStatusData.clear();
365            mSubscribeStatusData.clear();
366            mHistogramPublishDuration.clear();
367            mHistogramSubscribeDuration.clear();
368            mAppsWithDiscoverySessionResourceFailure.clear();
369        }
370    }
371
372    /**
373     * Dump all WifiAwareMetrics to console (pw) - this method is never called to dump the
374     * serialized metrics (handled by parent WifiMetrics).
375     *
376     * @param fd   unused
377     * @param pw   PrintWriter for writing dump to
378     * @param args unused
379     */
380    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
381        synchronized (mLock) {
382            pw.println("mLastEnableUsage:" + mLastEnableUsageMs);
383            pw.println(
384                    "mLastEnableUsageInThisSampleWindow:" + mLastEnableUsageInThisSampleWindowMs);
385            pw.println("mAvailableTime:" + mAvailableTimeMs);
386            pw.println("mHistogramAwareAvailableDurationMs:");
387            for (int i = 0; i < mHistogramAwareAvailableDurationMs.size(); ++i) {
388                pw.println("  " + mHistogramAwareAvailableDurationMs.keyAt(i) + ": "
389                        + mHistogramAwareAvailableDurationMs.valueAt(i));
390            }
391            pw.println("mAttachDataByUid:");
392            for (Map.Entry<Integer, AttachData> ade: mAttachDataByUid.entrySet()) {
393                pw.println("  " + "uid=" + ade.getKey() + ": identity="
394                        + ade.getValue().mUsesIdentityCallback + ", maxConcurrent="
395                        + ade.getValue().mMaxConcurrentAttaches);
396            }
397            pw.println("mAttachStatusData:");
398            for (int i = 0; i < mAttachStatusData.size(); ++i) {
399                pw.println("  " + mAttachStatusData.keyAt(i) + ": "
400                        + mAttachStatusData.valueAt(i));
401            }
402            pw.println("mHistogramAttachDuration:");
403            for (int i = 0; i < mHistogramAttachDuration.size(); ++i) {
404                pw.println("  " + mHistogramAttachDuration.keyAt(i) + ": "
405                        + mHistogramAttachDuration.valueAt(i));
406            }
407        }
408    }
409
410    // histogram utilities
411
412    /**
413     * Specifies a ~log histogram consisting of two levels of buckets - a set of N big buckets:
414     *
415     * Buckets starts at: B + P * M^i, where i=0, ... , N-1 (N big buckets)
416     * Each big bucket is divided into S sub-buckets
417     *
418     * Each (big) bucket is M times bigger than the previous one.
419     *
420     * The buckets are then:
421     * #0: B + P * M^0 with S buckets each of width (P*M^1-P*M^0)/S
422     * #1: B + P * M^1 with S buckets each of width (P*M^2-P*M^1)/S
423     * ...
424     * #N-1: B + P * M^(N-1) with S buckets each of width (P*M^N-P*M^(N-1))/S
425     */
426    @VisibleForTesting
427    public static class HistParms {
428        public HistParms(int b, int p, int m, int s, int n) {
429            this.b = b;
430            this.p = p;
431            this.m = m;
432            this.s = s;
433            this.n = n;
434
435            // derived values
436            mLog = Math.log(m);
437            bb = new double[n];
438            sbw = new double[n];
439            bb[0] = b + p;
440            sbw[0] = p * (m - 1.0) / (double) s;
441            for (int i = 1; i < n; ++i) {
442                bb[i] = m * (bb[i - 1] - b) + b;
443                sbw[i] = m * sbw[i - 1];
444            }
445        }
446
447        // spec
448        public int b;
449        public int p;
450        public int m;
451        public int s;
452        public int n;
453
454        // derived
455        public double mLog;
456        public double[] bb; // bucket base
457        public double[] sbw; // sub-bucket width
458    }
459
460    /**
461     * Adds the input value to the histogram based on the histogram parameters.
462     */
463    @VisibleForTesting
464    public static int addLogValueToHistogram(long x, SparseIntArray histogram, HistParms hp) {
465        double logArg = (double) (x - hp.b) / (double) hp.p;
466        int bigBucketIndex = -1;
467        if (logArg > 0) {
468            bigBucketIndex = (int) (Math.log(logArg) / hp.mLog);
469        }
470        int subBucketIndex;
471        if (bigBucketIndex < 0) {
472            bigBucketIndex = 0;
473            subBucketIndex = 0;
474        } else if (bigBucketIndex >= hp.n) {
475            bigBucketIndex = hp.n - 1;
476            subBucketIndex = hp.s - 1;
477        } else {
478            subBucketIndex = (int) ((x - hp.bb[bigBucketIndex]) / hp.sbw[bigBucketIndex]);
479            if (subBucketIndex >= hp.s) { // probably a rounding error so move to next big bucket
480                bigBucketIndex++;
481                if (bigBucketIndex >= hp.n) {
482                    bigBucketIndex = hp.n - 1;
483                    subBucketIndex = hp.s - 1;
484                } else {
485                    subBucketIndex = (int) ((x - hp.bb[bigBucketIndex]) / hp.sbw[bigBucketIndex]);
486                }
487            }
488        }
489        int key = bigBucketIndex * hp.s + subBucketIndex;
490
491        // note that get() returns 0 if index not there already
492        int newValue = histogram.get(key) + 1;
493        histogram.put(key, newValue);
494
495        return newValue;
496    }
497
498    /**
499     * Converts the histogram (with the specified histogram parameters) to an array of proto
500     * histogram buckets.
501     */
502    @VisibleForTesting
503    public static WifiMetricsProto.WifiAwareLog.HistogramBucket[] histogramToProtoArray(
504            SparseIntArray histogram, HistParms hp) {
505        WifiMetricsProto.WifiAwareLog.HistogramBucket[] protoArray =
506                new WifiMetricsProto.WifiAwareLog.HistogramBucket[histogram.size()];
507        for (int i = 0; i < histogram.size(); ++i) {
508            int key = histogram.keyAt(i);
509
510            protoArray[i] = new WifiMetricsProto.WifiAwareLog.HistogramBucket();
511            protoArray[i].start = (long) (hp.bb[key / hp.s] + hp.sbw[key / hp.s] * (key % hp.s));
512            protoArray[i].end = (long) (protoArray[i].start + hp.sbw[key / hp.s]);
513            protoArray[i].count = histogram.valueAt(i);
514        }
515
516        return protoArray;
517    }
518
519    /**
520     * Adds the NanStatusType to the histogram (translating to the proto enumeration of the status).
521     */
522    public static void addNanHalStatusToHistogram(int halStatus, SparseIntArray histogram) {
523        int protoStatus = convertNanStatusTypeToProtoEnum(halStatus);
524        int newValue = histogram.get(protoStatus) + 1;
525        histogram.put(protoStatus, newValue);
526    }
527
528    /**
529     * Converts a histogram of proto NanStatusTypeEnum to a raw proto histogram.
530     */
531    @VisibleForTesting
532    public static WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] histogramToProtoArray(
533            SparseIntArray histogram) {
534        WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[] protoArray =
535                new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket[histogram.size()];
536
537        for (int i = 0; i < histogram.size(); ++i) {
538            protoArray[i] = new WifiMetricsProto.WifiAwareLog.NanStatusHistogramBucket();
539            protoArray[i].nanStatusType = histogram.keyAt(i);
540            protoArray[i].count = histogram.valueAt(i);
541        }
542
543        return protoArray;
544    }
545
546    /**
547     * Convert a HAL NanStatusType enum to a Metrics proto enum NanStatusTypeEnum.
548     */
549    public static int convertNanStatusTypeToProtoEnum(int nanStatusType) {
550        switch (nanStatusType) {
551            case NanStatusType.SUCCESS:
552                return WifiMetricsProto.WifiAwareLog.SUCCESS;
553            case NanStatusType.INTERNAL_FAILURE:
554                return WifiMetricsProto.WifiAwareLog.INTERNAL_FAILURE;
555            case NanStatusType.PROTOCOL_FAILURE:
556                return WifiMetricsProto.WifiAwareLog.PROTOCOL_FAILURE;
557            case NanStatusType.INVALID_SESSION_ID:
558                return WifiMetricsProto.WifiAwareLog.INVALID_SESSION_ID;
559            case NanStatusType.NO_RESOURCES_AVAILABLE:
560                return WifiMetricsProto.WifiAwareLog.NO_RESOURCES_AVAILABLE;
561            case NanStatusType.INVALID_ARGS:
562                return WifiMetricsProto.WifiAwareLog.INVALID_ARGS;
563            case NanStatusType.INVALID_PEER_ID:
564                return WifiMetricsProto.WifiAwareLog.INVALID_PEER_ID;
565            case NanStatusType.INVALID_NDP_ID:
566                return WifiMetricsProto.WifiAwareLog.INVALID_NDP_ID;
567            case NanStatusType.NAN_NOT_ALLOWED:
568                return WifiMetricsProto.WifiAwareLog.NAN_NOT_ALLOWED;
569            case NanStatusType.NO_OTA_ACK:
570                return WifiMetricsProto.WifiAwareLog.NO_OTA_ACK;
571            case NanStatusType.ALREADY_ENABLED:
572                return WifiMetricsProto.WifiAwareLog.ALREADY_ENABLED;
573            case NanStatusType.FOLLOWUP_TX_QUEUE_FULL:
574                return WifiMetricsProto.WifiAwareLog.FOLLOWUP_TX_QUEUE_FULL;
575            case NanStatusType.UNSUPPORTED_CONCURRENCY_NAN_DISABLED:
576                return WifiMetricsProto.WifiAwareLog.UNSUPPORTED_CONCURRENCY_NAN_DISABLED;
577            default:
578                Log.e(TAG, "Unrecognized NanStatusType: " + nanStatusType);
579                return WifiMetricsProto.WifiAwareLog.UNKNOWN_HAL_STATUS;
580        }
581    }
582
583    private int max(SparseIntArray array) {
584        int max = 0;
585        for (int i = 0; i < array.size(); ++i) {
586            max = Math.max(max, array.valueAt(i));
587        }
588        return max;
589    }
590}
591