Impl to persist dumpsys/logcat in Telephony
Collect and write diagnostic data to dropbox service
Bug: 271900116
Test: atest DiagnosticDataCollectorTest, manual test
Change-Id: Ib5d793a8c1e51f7fff7cdb5bc2a35e22e80556a5
diff --git a/src/com/android/phone/DataCollectorConfig.java b/src/com/android/phone/DataCollectorConfig.java
new file mode 100644
index 0000000..00f2fce
--- /dev/null
+++ b/src/com/android/phone/DataCollectorConfig.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.provider.DeviceConfig;
+
+public final class DataCollectorConfig {
+ public static final long LOGCAT_READ_TIMEOUT_MILLIS_VALUE = 500L;
+ public static final long DUMPSYS_READ_TIMEOUT_MILLIS_VALUE = 100L;
+ public static final long LOGCAT_PROC_TIMEOUT_MILLIS_VALUE = 500L;
+ public static final long DUMPSYS_PROC_TIMEOUT_MILLIS_VALUE = 100L;
+ public static final int MAX_LOGCAT_LINES_LOW_MEM_DEVICE_VALUE = 2000;
+ public static final int MAX_LOGCAT_LINES_VALUE = 8000;
+ private static String LOGCAT_READ_TIMEOUT_MILLIS = "logcat_read_timeout_millis";
+ private static String DUMPSYS_READ_TIMEOUT_MILLIS = "dumpsys_read_timeout_millis";
+ private static String LOGCAT_PROC_TIMEOUT_MILLIS = "logcat_proc_timeout_millis";
+ private static String DUMPSYS_PROC_TIMEOUT_MILLIS = "dumpsys_proc_timeout_millis";
+ private static String MAX_LOGCAT_LINES_LOW_MEM = "max_logcat_lines_low_mem";
+ private static String MAX_LOGCAT_LINES = "max_logcat_lines";
+
+ public static int getMaxLogcatLinesForLowMemDevice() {
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+ MAX_LOGCAT_LINES_LOW_MEM, MAX_LOGCAT_LINES_LOW_MEM_DEVICE_VALUE);
+ }
+
+ public static int getMaxLogcatLines() {
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+ MAX_LOGCAT_LINES, MAX_LOGCAT_LINES_VALUE);
+ }
+
+ public static long getLogcatReadTimeoutMillis() {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+ LOGCAT_READ_TIMEOUT_MILLIS, LOGCAT_READ_TIMEOUT_MILLIS_VALUE);
+ }
+
+ public static long getDumpsysReadTimeoutMillis() {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+ DUMPSYS_READ_TIMEOUT_MILLIS, DUMPSYS_READ_TIMEOUT_MILLIS_VALUE);
+ }
+
+ public static long getLogcatProcTimeoutMillis() {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+ LOGCAT_PROC_TIMEOUT_MILLIS, LOGCAT_PROC_TIMEOUT_MILLIS_VALUE);
+ }
+
+ public static long getDumpsysProcTimeoutMillis() {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+ DUMPSYS_PROC_TIMEOUT_MILLIS, DUMPSYS_PROC_TIMEOUT_MILLIS_VALUE);
+ }
+
+ public static class Adapter {
+ public Adapter() {
+ }
+
+ public int getMaxLogcatLinesForLowMemDevice() {
+ return DataCollectorConfig.getMaxLogcatLinesForLowMemDevice();
+ }
+
+ public int getMaxLogcatLines() {
+ return DataCollectorConfig.getMaxLogcatLines();
+ }
+
+ public long getLogcatReadTimeoutMillis() {
+ return DataCollectorConfig.getLogcatReadTimeoutMillis();
+ }
+
+ public long getDumpsysReadTimeoutMillis() {
+ return DataCollectorConfig.getDumpsysReadTimeoutMillis();
+ }
+
+ public long getLogcatProcTimeoutMillis() {
+ return DataCollectorConfig.getLogcatProcTimeoutMillis();
+ }
+
+ public long getDumpsysProcTimeoutMillis() {
+ return DataCollectorConfig.getDumpsysProcTimeoutMillis();
+ }
+ }
+
+
+}
diff --git a/src/com/android/phone/DiagnosticDataCollector.java b/src/com/android/phone/DiagnosticDataCollector.java
new file mode 100644
index 0000000..d7ebe3d
--- /dev/null
+++ b/src/com/android/phone/DiagnosticDataCollector.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.os.DropBoxManager;
+import android.os.SystemClock;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class to help collect dumpsys/logcat and persist it to the
+ * on-device dropbox service. It is purely a utility and does
+ * not make decisions on if/when to collect.
+ */
+public class DiagnosticDataCollector {
+
+ //error msg that is appended to output if cmd execution results in error
+ public static final String ERROR_MSG = "DiagnosticDataCollector error executing cmd";
+ private static final String TAG = "DDC";
+ private static final String[] TELECOM_DUMPSYS_COMMAND =
+ {"/system/bin/dumpsys", "telecom", "EmergencyDiagnostics"};
+ private static final String[] TELEPHONY_DUMPSYS_COMMAND =
+ {"/system/bin/dumpsys", "telephony.registry", "EmergencyDiagnostics"};
+ private static final String LOGCAT_BINARY =
+ "/system/bin/logcat";
+ private static final String LOGCAT_BUFFERS = "system,radio";
+ private static final long LOG_TIME_OFFSET_MILLIS = 75L;
+ private static final String DUMPSYS_BINARY = "/system/bin/dumpsys";
+ private final Runtime mJavaRuntime;
+ private final Executor mAsyncTaskExecutor;
+ private final DropBoxManager mDropBoxManager;
+ private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MM-dd HH:mm:ss.mmm",
+ Locale.US);
+ private final boolean mIsLowRamDevice;
+
+ public DiagnosticDataCollector(Runtime javaRuntime, Executor asyncTaskExecutor,
+ DropBoxManager dropBoxManager, boolean isLowRamDevice) {
+ mJavaRuntime = javaRuntime;
+ mAsyncTaskExecutor = asyncTaskExecutor;
+ mDropBoxManager = dropBoxManager;
+ mIsLowRamDevice = isLowRamDevice;
+ }
+
+ public void persistEmergencyDianosticData(@NonNull DataCollectorConfig.Adapter dc,
+ @NonNull TelephonyManager.EmergencyCallDiagnosticParams edp, @NonNull String tag) {
+
+ if (edp.isTelephonyDumpSysCollectionEnabled()) {
+ persistTelephonyState(dc, tag);
+ }
+ if (edp.isTelecomDumpSysCollectionEnabled()) {
+ persistTelecomState(dc, tag);
+ }
+ if (edp.isLogcatCollectionEnabled()) {
+ persistLogcat(dc, tag, edp.getLogcatStartTime());
+ }
+ }
+
+
+ @SuppressWarnings("JavaUtilDate") //just used for DateFormatter.format (required by logcat)
+ private void persistLogcat(DataCollectorConfig.Adapter dc, String tag, long logcatStartTime) {
+ String startTime = mDateFormat.format(new Date(logcatStartTime - LOG_TIME_OFFSET_MILLIS));
+ Log.d(TAG, "Persisting Logcat");
+ int maxLines;
+ if (mIsLowRamDevice) {
+ maxLines = dc.getMaxLogcatLinesForLowMemDevice();
+ } else {
+ maxLines = dc.getMaxLogcatLines();
+ }
+ DiagnosticRunnable dr = new DiagnosticRunnable(
+ new String[]{LOGCAT_BINARY, "-t", startTime, "-b", LOGCAT_BUFFERS},
+ dc.getLogcatReadTimeoutMillis(), dc.getLogcatProcTimeoutMillis(),
+ tag, dc.getMaxLogcatLinesForLowMemDevice());
+ mAsyncTaskExecutor.execute(dr);
+ }
+
+ private void persistTelecomState(DataCollectorConfig.Adapter dc, String tag) {
+ Log.d(TAG, "Persisting Telecom state");
+ DiagnosticRunnable dr = new DiagnosticRunnable(TELECOM_DUMPSYS_COMMAND,
+ dc.getDumpsysReadTimeoutMillis(), dc.getDumpsysProcTimeoutMillis(),
+ tag, dc.getMaxLogcatLines());
+ mAsyncTaskExecutor.execute(dr);
+ }
+
+ private void persistTelephonyState(DataCollectorConfig.Adapter dc, String tag) {
+ Log.d(TAG, "Persisting Telephony state");
+ DiagnosticRunnable dr = new DiagnosticRunnable(TELEPHONY_DUMPSYS_COMMAND,
+ dc.getDumpsysReadTimeoutMillis(),
+ dc.getDumpsysProcTimeoutMillis(),
+ tag, dc.getMaxLogcatLines());
+ mAsyncTaskExecutor.execute(dr);
+ }
+
+ private class DiagnosticRunnable implements Runnable {
+
+ private static final String TAG = "DDC-DiagnosticRunnable";
+ private final String[] mCmd;
+ private final String mDropBoxTag;
+ private final int mMaxLogcatLines;
+ private long mStreamTimeout;
+ private long mProcTimeout;
+
+ DiagnosticRunnable(String[] cmd, long streamTimeout, long procTimeout, String dropboxTag,
+ int maxLogcatLines) {
+ mCmd = cmd;
+ mStreamTimeout = streamTimeout;
+ mProcTimeout = procTimeout;
+ mDropBoxTag = dropboxTag;
+ mMaxLogcatLines = maxLogcatLines;
+ Log.d(TAG, "Runnable created with cmd: " + Arrays.toString(cmd));
+ }
+
+ @Override
+ @WorkerThread
+ public void run() {
+ Log.d(TAG, "Running async persist for tag" + mDropBoxTag);
+ getProcOutputAndPersist(mCmd,
+ mStreamTimeout, mProcTimeout, mDropBoxTag, mMaxLogcatLines);
+ }
+
+ @WorkerThread
+ private void getProcOutputAndPersist(String[] cmd, long streamTimeout, long procTimeout,
+ String dropboxTag, int maxLogcatLines) {
+ Process process = null;
+ StringBuilder output = new StringBuilder();
+ long startProcTime = SystemClock.elapsedRealtime();
+ int outputSizeFromErrorStream = 0;
+ try {
+ process = mJavaRuntime.exec(cmd);
+ readStreamLinesWithTimeout(
+ new BufferedReader(new InputStreamReader(process.getInputStream())), output,
+ streamTimeout, maxLogcatLines);
+ int outputSizeFromInputStream = output.length();
+ readStreamLinesWithTimeout(
+ new BufferedReader(new InputStreamReader(process.getErrorStream())), output,
+ streamTimeout, maxLogcatLines);
+ Log.d(TAG, "[" + cmd[0] + "]" + "streams read in " + (SystemClock.elapsedRealtime()
+ - startProcTime) + " milliseconds");
+ process.waitFor(procTimeout, TimeUnit.MILLISECONDS);
+ outputSizeFromErrorStream = output.length() - outputSizeFromInputStream;
+ } catch (InterruptedException e) {
+ output.append(ERROR_MSG + e.toString() + System.lineSeparator());
+ } catch (IOException e) {
+ output.append(ERROR_MSG + e.toString() + System.lineSeparator());
+ } finally {
+ if (process != null) {
+ process.destroy();
+ }
+ }
+ Log.d(TAG, "[" + cmd[0] + "]" + "output collected in " + (SystemClock.elapsedRealtime()
+ - startProcTime) + " milliseconds. Size:" + output.toString().length());
+ if (outputSizeFromErrorStream > 0) {
+ Log.w(TAG, "Cmd ran with errors");
+ output.append(ERROR_MSG + System.lineSeparator());
+ }
+ mDropBoxManager.addText(dropboxTag, output.toString());
+ }
+
+ @WorkerThread
+ private void readStreamLinesWithTimeout(
+ BufferedReader inReader, StringBuilder outLines, long timeout, int maxLines)
+ throws IOException {
+ long startTimeMs = SystemClock.elapsedRealtime();
+ int totalLines = 0;
+ while (SystemClock.elapsedRealtime() < startTimeMs + timeout) {
+ // If there is a burst of data, continue reading without checking for timeout.
+ while (inReader.ready() && (totalLines < maxLines)) {
+ String line = inReader.readLine();
+ if (line == null) return; // end of stream.
+ outLines.append(line);
+ totalLines++;
+ outLines.append(System.lineSeparator());
+ }
+ SystemClock.sleep(timeout / 10);
+ }
+ }
+ }
+}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index b90d288..4d4abb5 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -51,6 +51,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.DropBoxManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.ICancellationSignal;
@@ -12032,7 +12033,17 @@
boolean enableLogcat,
long logcatStartTimestampMillis, boolean enableTelecomDump,
boolean enableTelephonyDump) {
- //TODO: next CL
+ DropBoxManager db = mApp.getSystemService(DropBoxManager.class);
+ TelephonyManager.EmergencyCallDiagnosticParams edp =
+ new TelephonyManager.EmergencyCallDiagnosticParams();
+ edp.setLogcatCollection(enableLogcat, logcatStartTimestampMillis);
+ edp.setTelephonyDumpSysCollection(enableTelephonyDump);
+ edp.setTelecomDumpSysCollection(enableTelecomDump);
+ Log.d(LOG_TAG, "persisting with Params " + edp.toString());
+ DiagnosticDataCollector ddc = new DiagnosticDataCollector(Runtime.getRuntime(),
+ Executors.newCachedThreadPool(), db,
+ mApp.getSystemService(ActivityManager.class).isLowRamDevice());
+ ddc.persistEmergencyDianosticData(new DataCollectorConfig.Adapter(), edp, dropboxTag);
}
/**