Create persistent logger for Satellite SOS
1. Create persistent logging classes with default DropBoxManager backend
2. Add resource config to enable usage of the DropBoxManager logger
Bug: 343478281
Test: make & manual tests
Change-Id: Ibb7d8cb48dee8a574eda625bdd82d3d6e6f51fa3
Merged-In: Ibb7d8cb48dee8a574eda625bdd82d3d6e6f51fa3
diff --git a/core/java/android/telephony/DropBoxManagerLoggerBackend.java b/core/java/android/telephony/DropBoxManagerLoggerBackend.java
new file mode 100644
index 0000000..41f81ac
--- /dev/null
+++ b/core/java/android/telephony/DropBoxManagerLoggerBackend.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2024 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 android.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.DropBoxManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Optional;
+
+/**
+ * A persistent logger backend that stores logs in Android DropBoxManager
+ *
+ * @hide
+ */
+public class DropBoxManagerLoggerBackend implements PersistentLoggerBackend {
+
+ private static final String TAG = "DropBoxManagerLoggerBackend";
+ // Separate tag reference to be explicitly used for dropboxmanager instead of logcat logging
+ private static final String DROPBOX_TAG = "DropBoxManagerLoggerBackend";
+ private static final DateTimeFormatter LOG_TIMESTAMP_FORMATTER =
+ DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS");
+ private static final ZoneId LOCAL_ZONE_ID = ZoneId.systemDefault();
+ private static final int BUFFER_SIZE_BYTES = 500 * 1024; // 500 KB
+ private static final int MIN_BUFFER_BYTES_FOR_FLUSH = 5 * 1024; // 5 KB
+
+ private static DropBoxManagerLoggerBackend sInstance;
+
+ private final DropBoxManager mDropBoxManager;
+ private final Object mBufferLock = new Object();
+ @GuardedBy("mBufferLock")
+ private final StringBuilder mLogBuffer = new StringBuilder();
+ private long mBufferStartTime = -1L;
+ private final HandlerThread mHandlerThread = new HandlerThread(DROPBOX_TAG);
+ private final Handler mHandler;
+ // Flag for determining if logging is enabled as a general feature
+ private final boolean mDropBoxManagerLoggingEnabled;
+
+ /**
+ * Returns a singleton instance of {@code DropBoxManagerLoggerBackend} that will log to
+ * DropBoxManager if the config_dropboxmanager_persistent_logging_enabled resource config is
+ * enabled.
+ * @param context Android context
+ */
+ @Nullable
+ public static synchronized DropBoxManagerLoggerBackend getInstance(@NonNull Context context) {
+ if (sInstance == null) {
+ sInstance = new DropBoxManagerLoggerBackend(context);
+ }
+ return sInstance;
+ }
+
+ private DropBoxManagerLoggerBackend(@NonNull Context context) {
+ mDropBoxManager = context.getSystemService(DropBoxManager.class);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mDropBoxManagerLoggingEnabled = persistentLoggingEnabled(context);
+ }
+
+ private boolean persistentLoggingEnabled(@NonNull Context context) {
+ try {
+ return context.getResources().getBoolean(
+ R.bool.config_dropboxmanager_persistent_logging_enabled);
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Persistent logging config not found");
+ return false;
+ }
+ }
+
+ /**
+ * Persist a DEBUG log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public void debug(@NonNull String tag, @NonNull String msg) {
+ if (!mDropBoxManagerLoggingEnabled) {
+ return;
+ }
+ bufferLog("D", tag, msg, Optional.empty());
+ }
+
+ /**
+ * Persist a INFO log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public void info(@NonNull String tag, @NonNull String msg) {
+ if (!mDropBoxManagerLoggingEnabled) {
+ return;
+ }
+ bufferLog("I", tag, msg, Optional.empty());
+ }
+
+ /**
+ * Persist a WARN log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public void warn(@NonNull String tag, @NonNull String msg) {
+ if (!mDropBoxManagerLoggingEnabled) {
+ return;
+ }
+ bufferLog("W", tag, msg, Optional.empty());
+ }
+
+ /**
+ * Persist a WARN log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param t An exception to log.
+ */
+ public void warn(@NonNull String tag, @NonNull String msg, @NonNull Throwable t) {
+ if (!mDropBoxManagerLoggingEnabled) {
+ return;
+ }
+ bufferLog("W", tag, msg, Optional.of(t));
+ }
+
+ /**
+ * Persist a ERROR log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public void error(@NonNull String tag, @NonNull String msg) {
+ if (!mDropBoxManagerLoggingEnabled) {
+ return;
+ }
+ bufferLog("E", tag, msg, Optional.empty());
+ }
+
+ /**
+ * Persist a ERROR log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param t An exception to log.
+ */
+ public void error(@NonNull String tag, @NonNull String msg, @NonNull Throwable t) {
+ if (!mDropBoxManagerLoggingEnabled) {
+ return;
+ }
+ bufferLog("E", tag, msg, Optional.of(t));
+ }
+
+ private synchronized void bufferLog(
+ @NonNull String level,
+ @NonNull String tag,
+ @NonNull String msg,
+ Optional<Throwable> t) {
+ if (mBufferStartTime == -1L) {
+ mBufferStartTime = System.currentTimeMillis();
+ }
+
+ synchronized (mBufferLock) {
+ mLogBuffer
+ .append(formatLog(level, tag, msg, t))
+ .append("\n");
+
+ if (mLogBuffer.length() >= BUFFER_SIZE_BYTES) {
+ flushAsync();
+ }
+ }
+ }
+
+ private String formatLog(
+ @NonNull String level,
+ @NonNull String tag,
+ @NonNull String msg,
+ Optional<Throwable> t) {
+ // Expected format = "$Timestamp $Level $Tag: $Message"
+ return formatTimestamp(System.currentTimeMillis()) + " " + level + " " + tag + ": "
+ + t.map(throwable -> msg + ": " + Log.getStackTraceString(throwable)).orElse(msg);
+ }
+
+ private String formatTimestamp(long currentTimeMillis) {
+ return Instant.ofEpochMilli(currentTimeMillis)
+ .atZone(LOCAL_ZONE_ID)
+ .format(LOG_TIMESTAMP_FORMATTER);
+ }
+
+ /**
+ * Flushes all buffered logs into DropBoxManager as a single log record with a tag of
+ * {@link #DROPBOX_TAG} asynchronously. Should be invoked sparingly as DropBoxManager has
+ * device-level limitations on the number files that can be stored.
+ */
+ public void flushAsync() {
+ if (!mDropBoxManagerLoggingEnabled) {
+ return;
+ }
+
+ mHandler.post(this::flush);
+ };
+
+ /**
+ * Flushes all buffered logs into DropBoxManager as a single log record with a tag of
+ * {@link #DROPBOX_TAG}. Should be invoked sparingly as DropBoxManager has device-level
+ * limitations on the number files that can be stored.
+ */
+ public void flush() {
+ if (!mDropBoxManagerLoggingEnabled) {
+ return;
+ }
+
+ synchronized (mBufferLock) {
+ if (mLogBuffer.length() < MIN_BUFFER_BYTES_FOR_FLUSH) {
+ return;
+ }
+
+ Log.d(DROPBOX_TAG, "Flushing logs from "
+ + formatTimestamp(mBufferStartTime) + " to "
+ + formatTimestamp(System.currentTimeMillis()));
+
+ try {
+ mDropBoxManager.addText(DROPBOX_TAG, mLogBuffer.toString());
+ } catch (Exception e) {
+ Log.w(DROPBOX_TAG, "Failed to flush logs of length "
+ + mLogBuffer.length() + " to DropBoxManager", e);
+ }
+ mLogBuffer.setLength(0);
+ }
+ mBufferStartTime = -1L;
+ }
+}
diff --git a/core/java/android/telephony/PersistentLogger.java b/core/java/android/telephony/PersistentLogger.java
new file mode 100644
index 0000000..47d15e5
--- /dev/null
+++ b/core/java/android/telephony/PersistentLogger.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 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 android.telephony;
+
+import android.annotation.NonNull;
+
+/**
+ * A persistent logging client. Intended for persisting SOS/emergency related critical debug logs
+ * in situations where standard Android logcat logs may not be retained long enough or available
+ * for access.
+ *
+ * @hide
+ */
+public class PersistentLogger {
+ private final PersistentLoggerBackend mPersistentLoggerBackend;
+
+ public PersistentLogger(@NonNull PersistentLoggerBackend persistentLoggerBackend) {
+ mPersistentLoggerBackend = persistentLoggerBackend;
+ }
+
+ /**
+ * Persist a DEBUG log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public void debug(@NonNull String tag, @NonNull String msg) {
+ mPersistentLoggerBackend.debug(tag, msg);
+ }
+
+ /**
+ * Persist a INFO log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public void info(@NonNull String tag, @NonNull String msg) {
+ mPersistentLoggerBackend.info(tag, msg);
+ }
+
+ /**
+ * Persist a WARN log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public void warn(@NonNull String tag, @NonNull String msg) {
+ mPersistentLoggerBackend.warn(tag, msg);
+ }
+
+ /**
+ * Persist a WARN log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param t An exception to log.
+ */
+ public void warn(@NonNull String tag, @NonNull String msg, @NonNull Throwable t) {
+ mPersistentLoggerBackend.warn(tag, msg, t);
+ }
+
+ /**
+ * Persist a ERROR log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public void error(@NonNull String tag, @NonNull String msg) {
+ mPersistentLoggerBackend.error(tag, msg);
+ }
+
+ /**
+ * Persist a ERROR log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param t An exception to log.
+ */
+ public void error(@NonNull String tag, @NonNull String msg, @NonNull Throwable t) {
+ mPersistentLoggerBackend.error(tag, msg, t);
+ }
+}
diff --git a/core/java/android/telephony/PersistentLoggerBackend.java b/core/java/android/telephony/PersistentLoggerBackend.java
new file mode 100644
index 0000000..e3e72e1
--- /dev/null
+++ b/core/java/android/telephony/PersistentLoggerBackend.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 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 android.telephony;
+
+import android.annotation.NonNull;
+
+/**
+ * Interface for logging backends to provide persistent log storage.
+ *
+ * @hide
+ */
+public interface PersistentLoggerBackend {
+
+ /**
+ * Persist a DEBUG log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ void debug(@NonNull String tag, @NonNull String msg);
+
+ /**
+ * Persist a INFO log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ void info(@NonNull String tag, @NonNull String msg);
+
+ /**
+ * Persist a WARN log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ void warn(@NonNull String tag, @NonNull String msg);
+
+ /**
+ * Persist a WARN log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param t An exception to log.
+ */
+ void warn(@NonNull String tag, @NonNull String msg, @NonNull Throwable t);
+
+ /**
+ * Persist a ERROR log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ void error(@NonNull String tag, @NonNull String msg);
+
+ /**
+ * Persist a ERROR log message.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param t An exception to log.
+ */
+ void error(@NonNull String tag, @NonNull String msg, @NonNull Throwable t);
+}
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 16f0f32..192643b 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -392,4 +392,9 @@
<integer name="config_wait_for_datagram_sending_response_for_last_message_timeout_millis">60000</integer>
<java-symbol type="integer" name="config_wait_for_datagram_sending_response_for_last_message_timeout_millis" />
+ <!-- Boolean indicating whether to enable persistent logging via DropBoxManager.
+ Used in persisting SOS/emergency related log messages.
+ -->
+ <bool name="config_dropboxmanager_persistent_logging_enabled">false</bool>
+ <java-symbol type="bool" name="config_dropboxmanager_persistent_logging_enabled" />
</resources>