1/*
2 * Copyright (C) 2010 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;
18
19import android.content.Context;
20import android.util.Base64;
21import android.util.Log;
22
23import com.android.internal.annotations.VisibleForTesting;
24import com.android.internal.R;
25import com.android.server.wifi.util.ByteArrayRingBuffer;
26import com.android.server.wifi.util.StringUtil;
27
28import java.io.BufferedReader;
29import java.io.ByteArrayOutputStream;
30import java.io.FileDescriptor;
31import java.io.IOException;
32import java.io.InputStreamReader;
33import java.io.PrintWriter;
34import java.lang.StringBuilder;
35import java.nio.charset.Charset;
36import java.util.ArrayList;
37import java.util.Calendar;
38import java.util.Collections;
39import java.util.Comparator;
40import java.util.HashMap;
41import java.util.zip.Deflater;
42
43/**
44 * Tracks various logs for framework.
45 */
46class WifiLogger extends BaseWifiLogger {
47    /**
48     * Thread-safety:
49     * 1) All non-private methods are |synchronized|.
50     * 2) Callbacks into WifiLogger use non-private (and hence, synchronized) methods. See, e.g,
51     *    onRingBufferData(), onWifiAlert().
52     */
53
54    private static final String TAG = "WifiLogger";
55    private static final boolean DBG = false;
56
57    /** log level flags; keep these consistent with wifi_logger.h */
58
59    /** No logs whatsoever */
60    public static final int VERBOSE_NO_LOG = 0;
61    /** No logs whatsoever */
62    public static final int VERBOSE_NORMAL_LOG = 1;
63    /** Be careful since this one can affect performance and power */
64    public static final int VERBOSE_LOG_WITH_WAKEUP  = 2;
65    /** Be careful since this one can affect performance and power and memory */
66    public static final int VERBOSE_DETAILED_LOG_WITH_WAKEUP  = 3;
67
68    /** ring buffer flags; keep these consistent with wifi_logger.h */
69    public static final int RING_BUFFER_FLAG_HAS_BINARY_ENTRIES     = 0x00000001;
70    public static final int RING_BUFFER_FLAG_HAS_ASCII_ENTRIES      = 0x00000002;
71    public static final int RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES = 0x00000004;
72
73    /** various reason codes */
74    public static final int REPORT_REASON_NONE                      = 0;
75    public static final int REPORT_REASON_ASSOC_FAILURE             = 1;
76    public static final int REPORT_REASON_AUTH_FAILURE              = 2;
77    public static final int REPORT_REASON_AUTOROAM_FAILURE          = 3;
78    public static final int REPORT_REASON_DHCP_FAILURE              = 4;
79    public static final int REPORT_REASON_UNEXPECTED_DISCONNECT     = 5;
80    public static final int REPORT_REASON_SCAN_FAILURE              = 6;
81    public static final int REPORT_REASON_USER_ACTION               = 7;
82
83    /** number of bug reports to hold */
84    public static final int MAX_BUG_REPORTS                         = 4;
85
86    /** number of alerts to hold */
87    public static final int MAX_ALERT_REPORTS                       = 1;
88
89    /** minimum wakeup interval for each of the log levels */
90    private static final int MinWakeupIntervals[] = new int[] { 0, 3600, 60, 10 };
91    /** minimum buffer size for each of the log levels */
92    private static final int MinBufferSizes[] = new int[] { 0, 16384, 16384, 65536 };
93
94    @VisibleForTesting public static final String FIRMWARE_DUMP_SECTION_HEADER =
95            "FW Memory dump";
96    @VisibleForTesting public static final String DRIVER_DUMP_SECTION_HEADER =
97            "Driver state dump";
98
99    private final int RING_BUFFER_BYTE_LIMIT_SMALL;
100    private final int RING_BUFFER_BYTE_LIMIT_LARGE;
101    private int mLogLevel = VERBOSE_NO_LOG;
102    private boolean mIsLoggingEventHandlerRegistered;
103    private WifiNative.RingBufferStatus[] mRingBuffers;
104    private WifiNative.RingBufferStatus mPerPacketRingBuffer;
105    private WifiStateMachine mWifiStateMachine;
106    private final WifiNative mWifiNative;
107    private final BuildProperties mBuildProperties;
108    private int mMaxRingBufferSizeBytes;
109
110    public WifiLogger(Context context, WifiStateMachine wifiStateMachine, WifiNative wifiNative,
111                      BuildProperties buildProperties) {
112        RING_BUFFER_BYTE_LIMIT_SMALL = context.getResources().getInteger(
113                R.integer.config_wifi_logger_ring_buffer_default_size_limit_kb) * 1024;
114        RING_BUFFER_BYTE_LIMIT_LARGE = context.getResources().getInteger(
115                R.integer.config_wifi_logger_ring_buffer_verbose_size_limit_kb) * 1024;
116
117        mWifiStateMachine = wifiStateMachine;
118        mWifiNative = wifiNative;
119        mBuildProperties = buildProperties;
120        mIsLoggingEventHandlerRegistered = false;
121        mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_SMALL;
122    }
123
124    @Override
125    public synchronized void startLogging(boolean verboseEnabled) {
126        mFirmwareVersion = mWifiNative.getFirmwareVersion();
127        mDriverVersion = mWifiNative.getDriverVersion();
128        mSupportedFeatureSet = mWifiNative.getSupportedLoggerFeatureSet();
129
130        if (!mIsLoggingEventHandlerRegistered) {
131            mIsLoggingEventHandlerRegistered = mWifiNative.setLoggingEventHandler(mHandler);
132        }
133
134        if (verboseEnabled) {
135            mLogLevel = VERBOSE_LOG_WITH_WAKEUP;
136            mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_LARGE;
137        } else {
138            mLogLevel = VERBOSE_NORMAL_LOG;
139            mMaxRingBufferSizeBytes = enableVerboseLoggingForDogfood()
140                    ? RING_BUFFER_BYTE_LIMIT_LARGE : RING_BUFFER_BYTE_LIMIT_SMALL;
141            clearVerboseLogs();
142        }
143
144        if (mRingBuffers == null) {
145            fetchRingBuffers();
146        }
147
148        if (mRingBuffers != null) {
149            /* log level may have changed, so restart logging with new levels */
150            stopLoggingAllBuffers();
151            resizeRingBuffers();
152            startLoggingAllExceptPerPacketBuffers();
153        }
154
155        if (!mWifiNative.startPktFateMonitoring()) {
156            Log.e(TAG, "Failed to start packet fate monitoring");
157        }
158    }
159
160    @Override
161    public synchronized void startPacketLog() {
162        if (mPerPacketRingBuffer != null) {
163            startLoggingRingBuffer(mPerPacketRingBuffer);
164        } else {
165            if (DBG) Log.d(TAG, "There is no per packet ring buffer");
166        }
167    }
168
169    @Override
170    public synchronized void stopPacketLog() {
171        if (mPerPacketRingBuffer != null) {
172            stopLoggingRingBuffer(mPerPacketRingBuffer);
173        } else {
174            if (DBG) Log.d(TAG, "There is no per packet ring buffer");
175        }
176    }
177
178    @Override
179    public synchronized void stopLogging() {
180        if (mIsLoggingEventHandlerRegistered) {
181            if (!mWifiNative.resetLogHandler()) {
182                Log.e(TAG, "Fail to reset log handler");
183            } else {
184                if (DBG) Log.d(TAG, "Reset log handler");
185            }
186            // Clear mIsLoggingEventHandlerRegistered even if resetLogHandler() failed, because
187            // the log handler is in an indeterminate state.
188            mIsLoggingEventHandlerRegistered = false;
189        }
190        if (mLogLevel != VERBOSE_NO_LOG) {
191            stopLoggingAllBuffers();
192            mRingBuffers = null;
193            mLogLevel = VERBOSE_NO_LOG;
194        }
195    }
196
197    @Override
198    synchronized void reportConnectionFailure() {
199        mPacketFatesForLastFailure = fetchPacketFates();
200    }
201
202    @Override
203    public synchronized void captureBugReportData(int reason) {
204        BugReport report = captureBugreport(reason, isVerboseLoggingEnabled());
205        mLastBugReports.addLast(report);
206    }
207
208    @Override
209    public synchronized void captureAlertData(int errorCode, byte[] alertData) {
210        BugReport report = captureBugreport(errorCode, isVerboseLoggingEnabled());
211        report.alertData = alertData;
212        mLastAlerts.addLast(report);
213    }
214
215    @Override
216    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
217        super.dump(pw);
218
219        for (int i = 0; i < mLastAlerts.size(); i++) {
220            pw.println("--------------------------------------------------------------------");
221            pw.println("Alert dump " + i);
222            pw.print(mLastAlerts.get(i));
223            pw.println("--------------------------------------------------------------------");
224        }
225
226        for (int i = 0; i < mLastBugReports.size(); i++) {
227            pw.println("--------------------------------------------------------------------");
228            pw.println("Bug dump " + i);
229            pw.print(mLastBugReports.get(i));
230            pw.println("--------------------------------------------------------------------");
231        }
232
233        dumpPacketFates(pw);
234        pw.println("--------------------------------------------------------------------");
235
236        pw.println("WifiNative - Log Begin ----");
237        mWifiNative.getLocalLog().dump(fd, pw, args);
238        pw.println("WifiNative - Log End ----");
239    }
240
241    /* private methods and data */
242    class BugReport {
243        long systemTimeMs;
244        long kernelTimeNanos;
245        int errorCode;
246        HashMap<String, byte[][]> ringBuffers = new HashMap();
247        byte[] fwMemoryDump;
248        byte[] mDriverStateDump;
249        byte[] alertData;
250        LimitedCircularArray<String> kernelLogLines;
251        ArrayList<String> logcatLines;
252
253        void clearVerboseLogs() {
254            fwMemoryDump = null;
255            mDriverStateDump = null;
256        }
257
258        public String toString() {
259            StringBuilder builder = new StringBuilder();
260
261            Calendar c = Calendar.getInstance();
262            c.setTimeInMillis(systemTimeMs);
263            builder.append("system time = ").append(
264                    String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)).append("\n");
265
266            long kernelTimeMs = kernelTimeNanos/(1000*1000);
267            builder.append("kernel time = ").append(kernelTimeMs/1000).append(".").append
268                    (kernelTimeMs%1000).append("\n");
269
270            if (alertData == null)
271                builder.append("reason = ").append(errorCode).append("\n");
272            else {
273                builder.append("errorCode = ").append(errorCode);
274                builder.append("data \n");
275                builder.append(compressToBase64(alertData)).append("\n");
276            }
277
278            if (kernelLogLines != null) {
279                builder.append("kernel log: \n");
280                for (int i = 0; i < kernelLogLines.size(); i++) {
281                    builder.append(kernelLogLines.get(i)).append("\n");
282                }
283                builder.append("\n");
284            }
285
286            if (logcatLines != null) {
287                builder.append("system log: \n");
288                for (int i = 0; i < logcatLines.size(); i++) {
289                    builder.append(logcatLines.get(i)).append("\n");
290                }
291                builder.append("\n");
292            }
293
294            for (HashMap.Entry<String, byte[][]> e : ringBuffers.entrySet()) {
295                String ringName = e.getKey();
296                byte[][] buffers = e.getValue();
297                builder.append("ring-buffer = ").append(ringName).append("\n");
298
299                int size = 0;
300                for (int i = 0; i < buffers.length; i++) {
301                    size += buffers[i].length;
302                }
303
304                byte[] buffer = new byte[size];
305                int index = 0;
306                for (int i = 0; i < buffers.length; i++) {
307                    System.arraycopy(buffers[i], 0, buffer, index, buffers[i].length);
308                    index += buffers[i].length;
309                }
310
311                builder.append(compressToBase64(buffer));
312                builder.append("\n");
313            }
314
315            if (fwMemoryDump != null) {
316                builder.append(FIRMWARE_DUMP_SECTION_HEADER);
317                builder.append("\n");
318                builder.append(compressToBase64(fwMemoryDump));
319                builder.append("\n");
320            }
321
322            if (mDriverStateDump != null) {
323                builder.append(DRIVER_DUMP_SECTION_HEADER);
324                if (StringUtil.isAsciiPrintable(mDriverStateDump)) {
325                    builder.append(" (ascii)\n");
326                    builder.append(new String(mDriverStateDump, Charset.forName("US-ASCII")));
327                    builder.append("\n");
328                } else {
329                    builder.append(" (base64)\n");
330                    builder.append(compressToBase64(mDriverStateDump));
331                }
332            }
333
334            return builder.toString();
335        }
336    }
337
338    class LimitedCircularArray<E> {
339        private ArrayList<E> mArrayList;
340        private int mMax;
341        LimitedCircularArray(int max) {
342            mArrayList = new ArrayList<E>(max);
343            mMax = max;
344        }
345
346        public final void addLast(E e) {
347            if (mArrayList.size() >= mMax)
348                mArrayList.remove(0);
349            mArrayList.add(e);
350        }
351
352        public final int size() {
353            return mArrayList.size();
354        }
355
356        public final E get(int i) {
357            return mArrayList.get(i);
358        }
359    }
360
361    private final LimitedCircularArray<BugReport> mLastAlerts =
362            new LimitedCircularArray<BugReport>(MAX_ALERT_REPORTS);
363    private final LimitedCircularArray<BugReport> mLastBugReports =
364            new LimitedCircularArray<BugReport>(MAX_BUG_REPORTS);
365    private final HashMap<String, ByteArrayRingBuffer> mRingBufferData = new HashMap();
366
367    private final WifiNative.WifiLoggerEventHandler mHandler =
368            new WifiNative.WifiLoggerEventHandler() {
369        @Override
370        public void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) {
371            WifiLogger.this.onRingBufferData(status, buffer);
372        }
373
374        @Override
375        public void onWifiAlert(int errorCode, byte[] buffer) {
376            WifiLogger.this.onWifiAlert(errorCode, buffer);
377        }
378    };
379
380    synchronized void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) {
381        ByteArrayRingBuffer ring = mRingBufferData.get(status.name);
382        if (ring != null) {
383            ring.appendBuffer(buffer);
384        }
385    }
386
387    synchronized void onWifiAlert(int errorCode, byte[] buffer) {
388        if (mWifiStateMachine != null) {
389            mWifiStateMachine.sendMessage(
390                    WifiStateMachine.CMD_FIRMWARE_ALERT, errorCode, 0, buffer);
391        }
392    }
393
394    private boolean isVerboseLoggingEnabled() {
395        return mLogLevel > VERBOSE_NORMAL_LOG;
396    }
397
398    private void clearVerboseLogs() {
399        mPacketFatesForLastFailure = null;
400
401        for (int i = 0; i < mLastAlerts.size(); i++) {
402            mLastAlerts.get(i).clearVerboseLogs();
403        }
404
405        for (int i = 0; i < mLastBugReports.size(); i++) {
406            mLastBugReports.get(i).clearVerboseLogs();
407        }
408    }
409
410    private boolean fetchRingBuffers() {
411        if (mRingBuffers != null) return true;
412
413        mRingBuffers = mWifiNative.getRingBufferStatus();
414        if (mRingBuffers != null) {
415            for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
416                if (DBG) Log.d(TAG, "RingBufferStatus is: \n" + buffer.name);
417                if (mRingBufferData.containsKey(buffer.name) == false) {
418                    mRingBufferData.put(buffer.name,
419                            new ByteArrayRingBuffer(mMaxRingBufferSizeBytes));
420                }
421                if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) {
422                    mPerPacketRingBuffer = buffer;
423                }
424            }
425        } else {
426            Log.e(TAG, "no ring buffers found");
427        }
428
429        return mRingBuffers != null;
430    }
431
432    private void resizeRingBuffers() {
433        for (ByteArrayRingBuffer byteArrayRingBuffer : mRingBufferData.values()) {
434            byteArrayRingBuffer.resize(mMaxRingBufferSizeBytes);
435        }
436    }
437
438    private boolean startLoggingAllExceptPerPacketBuffers() {
439
440        if (mRingBuffers == null) {
441            if (DBG) Log.d(TAG, "No ring buffers to log anything!");
442            return false;
443        }
444
445        for (WifiNative.RingBufferStatus buffer : mRingBuffers){
446
447            if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) {
448                /* skip per-packet-buffer */
449                if (DBG) Log.d(TAG, "skipped per packet logging ring " + buffer.name);
450                continue;
451            }
452
453            startLoggingRingBuffer(buffer);
454        }
455
456        return true;
457    }
458
459    private boolean startLoggingRingBuffer(WifiNative.RingBufferStatus buffer) {
460
461        int minInterval = MinWakeupIntervals[mLogLevel];
462        int minDataSize = MinBufferSizes[mLogLevel];
463
464        if (mWifiNative.startLoggingRingBuffer(
465                mLogLevel, 0, minInterval, minDataSize, buffer.name) == false) {
466            if (DBG) Log.e(TAG, "Could not start logging ring " + buffer.name);
467            return false;
468        }
469
470        return true;
471    }
472
473    private boolean stopLoggingRingBuffer(WifiNative.RingBufferStatus buffer) {
474        if (mWifiNative.startLoggingRingBuffer(0, 0, 0, 0, buffer.name) == false) {
475            if (DBG) Log.e(TAG, "Could not stop logging ring " + buffer.name);
476        }
477        return true;
478    }
479
480    private boolean stopLoggingAllBuffers() {
481        if (mRingBuffers != null) {
482            for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
483                stopLoggingRingBuffer(buffer);
484            }
485        }
486        return true;
487    }
488
489    private boolean getAllRingBufferData() {
490        if (mRingBuffers == null) {
491            Log.e(TAG, "Not ring buffers available to collect data!");
492            return false;
493        }
494
495        for (WifiNative.RingBufferStatus element : mRingBuffers){
496            boolean result = mWifiNative.getRingBufferData(element.name);
497            if (!result) {
498                Log.e(TAG, "Fail to get ring buffer data of: " + element.name);
499                return false;
500            }
501        }
502
503        Log.d(TAG, "getAllRingBufferData Successfully!");
504        return true;
505    }
506
507    private boolean enableVerboseLoggingForDogfood() {
508        return false;
509    }
510
511    private BugReport captureBugreport(int errorCode, boolean captureFWDump) {
512        BugReport report = new BugReport();
513        report.errorCode = errorCode;
514        report.systemTimeMs = System.currentTimeMillis();
515        report.kernelTimeNanos = System.nanoTime();
516
517        if (mRingBuffers != null) {
518            for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
519                /* this will push data in mRingBuffers */
520                mWifiNative.getRingBufferData(buffer.name);
521                ByteArrayRingBuffer data = mRingBufferData.get(buffer.name);
522                byte[][] buffers = new byte[data.getNumBuffers()][];
523                for (int i = 0; i < data.getNumBuffers(); i++) {
524                    buffers[i] = data.getBuffer(i).clone();
525                }
526                report.ringBuffers.put(buffer.name, buffers);
527            }
528        }
529
530        report.logcatLines = getLogcat(127);
531        report.kernelLogLines = getKernelLog(127);
532
533        if (captureFWDump) {
534            report.fwMemoryDump = mWifiNative.getFwMemoryDump();
535            report.mDriverStateDump = mWifiNative.getDriverStateDump();
536        }
537        return report;
538    }
539
540    @VisibleForTesting
541    LimitedCircularArray<BugReport> getBugReports() {
542        return mLastBugReports;
543    }
544
545    private static String compressToBase64(byte[] input) {
546        String result;
547        //compress
548        Deflater compressor = new Deflater();
549        compressor.setLevel(Deflater.BEST_SPEED);
550        compressor.setInput(input);
551        compressor.finish();
552        ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length);
553        final byte[] buf = new byte[1024];
554
555        while (!compressor.finished()) {
556            int count = compressor.deflate(buf);
557            bos.write(buf, 0, count);
558        }
559
560        try {
561            compressor.end();
562            bos.close();
563        } catch (IOException e) {
564            Log.e(TAG, "ByteArrayOutputStream close error");
565            result =  android.util.Base64.encodeToString(input, Base64.DEFAULT);
566            return result;
567        }
568
569        byte[] compressed = bos.toByteArray();
570        if (DBG) {
571            Log.d(TAG," length is:" + (compressed == null? "0" : compressed.length));
572        }
573
574        //encode
575        result = android.util.Base64.encodeToString(
576                compressed.length < input.length ? compressed : input , Base64.DEFAULT);
577
578        if (DBG) {
579            Log.d(TAG, "FwMemoryDump length is :" + result.length());
580        }
581
582        return result;
583    }
584
585    private ArrayList<String> getLogcat(int maxLines) {
586        ArrayList<String> lines = new ArrayList<String>(maxLines);
587        try {
588            Process process = Runtime.getRuntime().exec(String.format("logcat -t %d", maxLines));
589            BufferedReader reader = new BufferedReader(
590                    new InputStreamReader(process.getInputStream()));
591            String line;
592            while ((line = reader.readLine()) != null) {
593                lines.add(line);
594            }
595            reader = new BufferedReader(
596                    new InputStreamReader(process.getErrorStream()));
597            while ((line = reader.readLine()) != null) {
598                lines.add(line);
599            }
600            process.waitFor();
601        } catch (InterruptedException|IOException e) {
602            Log.e(TAG, "Exception while capturing logcat" + e);
603        }
604        return lines;
605    }
606
607    private LimitedCircularArray<String> getKernelLog(int maxLines) {
608        if (DBG) Log.d(TAG, "Reading kernel log ...");
609        LimitedCircularArray<String> lines = new LimitedCircularArray<String>(maxLines);
610        String log = mWifiNative.readKernelLog();
611        String logLines[] = log.split("\n");
612        for (int i = 0; i < logLines.length; i++) {
613            lines.addLast(logLines[i]);
614        }
615        if (DBG) Log.d(TAG, "Added " + logLines.length + " lines");
616        return lines;
617    }
618
619    /** Packet fate reporting */
620    private ArrayList<WifiNative.FateReport> mPacketFatesForLastFailure;
621
622    private ArrayList<WifiNative.FateReport> fetchPacketFates() {
623        ArrayList<WifiNative.FateReport> mergedFates = new ArrayList<WifiNative.FateReport>();
624        WifiNative.TxFateReport[] txFates =
625                new WifiNative.TxFateReport[WifiLoggerHal.MAX_FATE_LOG_LEN];
626        if (mWifiNative.getTxPktFates(txFates)) {
627            for (int i = 0; i < txFates.length && txFates[i] != null; i++) {
628                mergedFates.add(txFates[i]);
629            }
630        }
631
632        WifiNative.RxFateReport[] rxFates =
633                new WifiNative.RxFateReport[WifiLoggerHal.MAX_FATE_LOG_LEN];
634        if (mWifiNative.getRxPktFates(rxFates)) {
635            for (int i = 0; i < rxFates.length && rxFates[i] != null; i++) {
636                mergedFates.add(rxFates[i]);
637            }
638        }
639
640        Collections.sort(mergedFates, new Comparator<WifiNative.FateReport>() {
641            @Override
642            public int compare(WifiNative.FateReport lhs, WifiNative.FateReport rhs) {
643                return Long.compare(lhs.mDriverTimestampUSec, rhs.mDriverTimestampUSec);
644            }
645        });
646
647        return mergedFates;
648    }
649
650    private void dumpPacketFates(PrintWriter pw) {
651        dumpPacketFatesInternal(pw, "Last failed connection fates", mPacketFatesForLastFailure,
652                isVerboseLoggingEnabled());
653        dumpPacketFatesInternal(pw, "Latest fates", fetchPacketFates(), isVerboseLoggingEnabled());
654    }
655
656    private static void dumpPacketFatesInternal(PrintWriter pw, String description,
657            ArrayList<WifiNative.FateReport> fates, boolean verbose) {
658        if (fates == null) {
659            pw.format("No fates fetched for \"%s\"\n", description);
660            return;
661        }
662
663        if (fates.size() == 0) {
664            pw.format("HAL provided zero fates for \"%s\"\n", description);
665            return;
666        }
667
668        pw.format("--------------------- %s ----------------------\n", description);
669
670        StringBuilder verboseOutput = new StringBuilder();
671        pw.print(WifiNative.FateReport.getTableHeader());
672        for (WifiNative.FateReport fate : fates) {
673            pw.print(fate.toTableRowString());
674            if (verbose) {
675                // Important: only print Personally Identifiable Information (PII) if verbose
676                // logging is turned on.
677                verboseOutput.append(fate.toVerboseStringWithPiiAllowed());
678                verboseOutput.append("\n");
679            }
680        }
681
682        if (verbose) {
683            pw.format("\n>>> VERBOSE PACKET FATE DUMP <<<\n\n");
684            pw.print(verboseOutput.toString());
685        }
686
687        pw.println("--------------------------------------------------------------------");
688    }
689}
690