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