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