private static void addAuditErrorsToDropBox(
      DropBoxManager db, SharedPreferences prefs, String headers, int maxSize, String tag)
      throws IOException {
    if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled
    Slog.i(TAG, "Copying audit failures to DropBox");

    File file = new File("/proc/last_kmsg");
    long fileTime = file.lastModified();
    if (fileTime <= 0) {
      file = new File("/sys/fs/pstore/console-ramoops");
      fileTime = file.lastModified();
    }

    if (fileTime <= 0) return; // File does not exist

    if (prefs != null) {
      long lastTime = prefs.getLong(tag, 0);
      if (lastTime == fileTime) return; // Already logged this particular file
      // TODO: move all these SharedPreferences Editor commits
      // outside this function to the end of logBootEvents
      prefs.edit().putLong(tag, fileTime).apply();
    }

    String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
    StringBuilder sb = new StringBuilder();
    for (String line : log.split("\n")) {
      if (line.contains("audit")) {
        sb.append(line + "\n");
      }
    }
    Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox");
    db.addText(tag, headers + sb.toString());
  }
  private static void addFileWithFootersToDropBox(
      DropBoxManager db,
      SharedPreferences prefs,
      String headers,
      String footers,
      String filename,
      int maxSize,
      String tag)
      throws IOException {
    if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled

    File file = new File(filename);
    if (file.isDirectory()) return; // Skip subdirectories (likely vendor-specific)
    long fileTime = file.lastModified();
    if (fileTime <= 0) return; // File does not exist

    if (prefs != null) {
      long lastTime = prefs.getLong(filename, 0);
      if (lastTime == fileTime) return; // Already logged this particular file
      // TODO: move all these SharedPreferences Editor commits
      // outside this function to the end of logBootEvents
      prefs.edit().putLong(filename, fileTime).apply();
    }

    Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");
    db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n") + footers);
  }
  private static void addFsckErrorsToDropBox(
      DropBoxManager db, SharedPreferences prefs, String headers, int maxSize, String tag)
      throws IOException {
    boolean upload_needed = false;
    if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled
    Slog.i(TAG, "Checking for fsck errors");

    File file = new File("/dev/fscklogs/log");
    long fileTime = file.lastModified();
    if (fileTime <= 0) return; // File does not exist

    String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
    StringBuilder sb = new StringBuilder();
    for (String line : log.split("\n")) {
      if (line.contains("FILE SYSTEM WAS MODIFIED")) {
        upload_needed = true;
        break;
      }
    }

    if (upload_needed) {
      addFileToDropBox(db, prefs, headers, "/dev/fscklogs/log", maxSize, tag);
    }

    // Remove the file so we don't re-upload if the runtime restarts.
    file.delete();
  }
  private static void addFileToDropBox(
      DropBoxManager db,
      SharedPreferences prefs,
      String headers,
      String filename,
      int maxSize,
      String tag)
      throws IOException {
    if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled

    File file = new File(filename);
    long fileTime = file.lastModified();
    if (fileTime <= 0) return; // File does not exist

    if (prefs != null) {
      long lastTime = prefs.getLong(filename, 0);
      if (lastTime == fileTime) return; // Already logged this particular file
      prefs.edit().putLong(filename, fileTime).commit();
    }

    Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");
    db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"));
  }
  private void logBatteryStatsLocked() {
    IBinder batteryInfoService = ServiceManager.getService(BatteryStats.SERVICE_NAME);
    if (batteryInfoService == null) return;

    DropBoxManager db = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
    if (db == null || !db.isTagEnabled("BATTERY_DISCHARGE_INFO")) return;

    File dumpFile = null;
    FileOutputStream dumpStream = null;
    try {
      // dump the service to a file
      dumpFile = new File(DUMPSYS_DATA_PATH + BatteryStats.SERVICE_NAME + ".dump");
      dumpStream = new FileOutputStream(dumpFile);
      batteryInfoService.dump(dumpStream.getFD(), DUMPSYS_ARGS);
      FileUtils.sync(dumpStream);

      // add dump file to drop box
      db.addFile("BATTERY_DISCHARGE_INFO", dumpFile, DropBoxManager.IS_TEXT);
    } catch (RemoteException e) {
      Slog.e(TAG, "failed to dump battery service", e);
    } catch (IOException e) {
      Slog.e(TAG, "failed to write dumpsys file", e);
    } finally {
      // make sure we clean up
      if (dumpStream != null) {
        try {
          dumpStream.close();
        } catch (IOException e) {
          Slog.e(TAG, "failed to close dumpsys output stream");
        }
      }
      if (dumpFile != null && !dumpFile.delete()) {
        Slog.e(TAG, "failed to delete temporary dumpsys file: " + dumpFile.getAbsolutePath());
      }
    }
  }
  private void logBootEvents(Context ctx) throws IOException {
    final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE);
    final SharedPreferences prefs = ctx.getSharedPreferences("log_files", Context.MODE_PRIVATE);
    final String headers =
        new StringBuilder(512)
            .append("Build: ")
            .append(Build.FINGERPRINT)
            .append("\n")
            .append("Hardware: ")
            .append(Build.BOARD)
            .append("\n")
            .append("Bootloader: ")
            .append(Build.BOOTLOADER)
            .append("\n")
            .append("Radio: ")
            .append(Build.RADIO)
            .append("\n")
            .append("Kernel: ")
            .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n"))
            .append("\n")
            .toString();

    String recovery = RecoverySystem.handleAftermath();
    if (recovery != null && db != null) {
      db.addText("SYSTEM_RECOVERY_LOG", headers + recovery);
    }

    if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) {
      String now = Long.toString(System.currentTimeMillis());
      SystemProperties.set("ro.runtime.firstboot", now);
      if (db != null) db.addText("SYSTEM_BOOT", headers);

      // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile())
      addFileToDropBox(db, prefs, headers, "/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG");
      addFileToDropBox(db, prefs, headers, "/cache/recovery/log", -LOG_SIZE, "SYSTEM_RECOVERY_LOG");
      addFileToDropBox(
          db, prefs, headers, "/data/dontpanic/apanic_console", -LOG_SIZE, "APANIC_CONSOLE");
      addFileToDropBox(
          db, prefs, headers, "/data/dontpanic/apanic_threads", -LOG_SIZE, "APANIC_THREADS");
    } else {
      if (db != null) db.addText("SYSTEM_RESTART", headers);
    }

    // Scan existing tombstones (in case any new ones appeared)
    File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
    for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
      addFileToDropBox(
          db, prefs, headers, tombstoneFiles[i].getPath(), LOG_SIZE, "SYSTEM_TOMBSTONE");
    }

    // Start watching for new tombstone files; will record them as they occur.
    // This gets registered with the singleton file observer thread.
    sTombstoneObserver =
        new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) {
          @Override
          public void onEvent(int event, String path) {
            try {
              String filename = new File(TOMBSTONE_DIR, path).getPath();
              addFileToDropBox(db, prefs, headers, filename, LOG_SIZE, "SYSTEM_TOMBSTONE");
            } catch (IOException e) {
              Slog.e(TAG, "Can't log tombstone", e);
            }
          }
        };

    sTombstoneObserver.startWatching();
  }
  private void logBootEvents(Context ctx) throws IOException {
    final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE);
    final SharedPreferences prefs = ctx.getSharedPreferences("log_files", Context.MODE_PRIVATE);
    final String headers =
        new StringBuilder(512)
            .append("Build: ")
            .append(Build.FINGERPRINT)
            .append("\n")
            .append("Hardware: ")
            .append(Build.BOARD)
            .append("\n")
            .append("Revision: ")
            .append(SystemProperties.get("ro.revision", ""))
            .append("\n")
            .append("Bootloader: ")
            .append(Build.BOOTLOADER)
            .append("\n")
            .append("Radio: ")
            .append(Build.RADIO)
            .append("\n")
            .append("Kernel: ")
            .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n"))
            .append("\n")
            .toString();
    final String bootReason = SystemProperties.get("ro.boot.bootreason", null);

    String recovery = RecoverySystem.handleAftermath();
    if (recovery != null && db != null) {
      db.addText("SYSTEM_RECOVERY_LOG", headers + recovery);
    }

    String lastKmsgFooter = "";
    if (bootReason != null) {
      lastKmsgFooter =
          new StringBuilder(512)
              .append("\n")
              .append("Boot info:\n")
              .append("Last boot reason: ")
              .append(bootReason)
              .append("\n")
              .toString();
    }

    if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) {
      if ("encrypted".equals(SystemProperties.get("ro.crypto.state"))
          && "trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))) {
        // Encrypted, first boot to get PIN/pattern/password so data is tmpfs
        // Don't set ro.runtime.firstboot so that we will do this again
        // when data is properly mounted
      } else {
        String now = Long.toString(System.currentTimeMillis());
        SystemProperties.set("ro.runtime.firstboot", now);
      }
      if (db != null) db.addText("SYSTEM_BOOT", headers);

      // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile())
      addFileWithFootersToDropBox(
          db, prefs, headers, lastKmsgFooter, "/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG");
      addFileWithFootersToDropBox(
          db,
          prefs,
          headers,
          lastKmsgFooter,
          "/sys/fs/pstore/console-ramoops",
          -LOG_SIZE,
          "SYSTEM_LAST_KMSG");
      addFileToDropBox(db, prefs, headers, "/cache/recovery/log", -LOG_SIZE, "SYSTEM_RECOVERY_LOG");
      addFileToDropBox(
          db, prefs, headers, "/cache/recovery/last_kmsg", -LOG_SIZE, "SYSTEM_RECOVERY_KMSG");
      addFileToDropBox(
          db, prefs, headers, "/data/dontpanic/apanic_console", -LOG_SIZE, "APANIC_CONSOLE");
      addFileToDropBox(
          db, prefs, headers, "/data/dontpanic/apanic_threads", -LOG_SIZE, "APANIC_THREADS");
      addAuditErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_AUDIT");
      addFsckErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_FSCK");
    } else {
      if (db != null) db.addText("SYSTEM_RESTART", headers);
    }

    // Scan existing tombstones (in case any new ones appeared)
    File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
    for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
      if (tombstoneFiles[i].isFile()) {
        addFileToDropBox(
            db, prefs, headers, tombstoneFiles[i].getPath(), LOG_SIZE, "SYSTEM_TOMBSTONE");
      }
    }

    // Start watching for new tombstone files; will record them as they occur.
    // This gets registered with the singleton file observer thread.
    sTombstoneObserver =
        new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) {
          @Override
          public void onEvent(int event, String path) {
            try {
              File file = new File(TOMBSTONE_DIR, path);
              if (file.isFile()) {
                addFileToDropBox(db, prefs, headers, file.getPath(), LOG_SIZE, "SYSTEM_TOMBSTONE");
              }
            } catch (IOException e) {
              Slog.e(TAG, "Can't log tombstone", e);
            }
          }
        };

    sTombstoneObserver.startWatching();
  }