summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/Event.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/RichEvent.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/SysuiLog.java163
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java69
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java82
5 files changed, 488 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/log/Event.java b/packages/SystemUI/src/com/android/systemui/log/Event.java
new file mode 100644
index 000000000000..92862a2bc74c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/Event.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 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.systemui.log;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Stores information about an event that occurred in SystemUI to be used for debugging and triage.
+ * Every event has a time stamp, log level and message.
+ * Events are stored in {@link SysuiLog} and can be printed in a dumpsys.
+ */
+public class Event {
+ public static final int UNINITIALIZED = -1;
+
+ @IntDef({ERROR, WARN, INFO, DEBUG, VERBOSE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Level {}
+ public static final int VERBOSE = 2;
+ public static final int DEBUG = 3;
+ public static final int INFO = 4;
+ public static final int WARN = 5;
+ public static final int ERROR = 6;
+
+ private long mTimestamp;
+ private @Level int mLogLevel = DEBUG;
+ protected String mMessage;
+
+ public Event(String message) {
+ mTimestamp = System.currentTimeMillis();
+ mMessage = message;
+ }
+
+ public Event(@Level int logLevel, String message) {
+ mTimestamp = System.currentTimeMillis();
+ mLogLevel = logLevel;
+ mMessage = message;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ public @Level int getLogLevel() {
+ return mLogLevel;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/RichEvent.java b/packages/SystemUI/src/com/android/systemui/log/RichEvent.java
new file mode 100644
index 000000000000..89b7a8181c44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/RichEvent.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 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.systemui.log;
+
+/**
+ * Stores information about an event that occurred in SystemUI to be used for debugging and triage.
+ * Every rich event has a time stamp, event type, and log level, with the option to provide the
+ * reason this event was triggered.
+ * Events are stored in {@link SysuiLog} and can be printed in a dumpsys.
+ */
+public abstract class RichEvent extends Event {
+ private final int mType;
+ private final String mReason;
+
+ /**
+ * Create a rich event that includes an event type that matches with an index in the array
+ * getEventLabels().
+ */
+ public RichEvent(@Event.Level int logLevel, int type, String reason) {
+ super(logLevel, null);
+ final int numEvents = getEventLabels().length;
+ if (type < 0 || type >= numEvents) {
+ throw new IllegalArgumentException("Unsupported event type. Events only supported"
+ + " from 0 to " + (numEvents - 1) + ", but given type=" + type);
+ }
+ mType = type;
+ mReason = reason;
+ mMessage = getEventLabels()[mType] + " " + mReason;
+ }
+
+ /**
+ * Returns an array of the event labels. The index represents the event type and the
+ * corresponding String stored at that index is the user-readable representation of that event.
+ * @return array of user readable events, where the index represents its event type constant
+ */
+ public abstract String[] getEventLabels();
+
+ public int getType() {
+ return mType;
+ }
+
+ public String getReason() {
+ return mReason;
+ }
+
+ /**
+ * Builder to build a RichEvent.
+ * @param <B> Log specific builder that is extending this builder
+ */
+ public abstract static class Builder<B extends Builder<B>> {
+ public static final int UNINITIALIZED = -1;
+
+ private B mBuilder = getBuilder();
+ protected int mType = UNINITIALIZED;
+ protected String mReason;
+ protected @Level int mLogLevel;
+
+ /**
+ * Get the log-specific builder.
+ */
+ public abstract B getBuilder();
+
+ /**
+ * Build the log-specific event.
+ */
+ public abstract RichEvent build();
+
+ /**
+ * Optional - set the log level. Defaults to DEBUG.
+ */
+ public B setLogLevel(@Level int logLevel) {
+ mLogLevel = logLevel;
+ return mBuilder;
+ }
+
+ /**
+ * Required - set the event type. These events must correspond with the events from
+ * getEventLabels().
+ */
+ public B setType(int type) {
+ mType = type;
+ return mBuilder;
+ }
+
+ /**
+ * Optional - set the reason why this event was triggered.
+ */
+ public B setReason(String reason) {
+ mReason = reason;
+ return mBuilder;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
new file mode 100644
index 000000000000..a6e10e6b345b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/SysuiLog.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 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.systemui.log;
+
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayDeque;
+import java.util.Locale;
+
+/**
+ * Thread-safe logger in SystemUI which prints logs to logcat and stores logs to be
+ * printed by the DumpController. This is an alternative to printing directly
+ * to avoid logs being deleted by chatty. The number of logs retained is varied based on
+ * whether the build is {@link Build.IS_DEBUGGABLE}.
+ *
+ * To manually view the logs via adb:
+ * adb shell dumpsys activity service com.android.systemui/.SystemUIService \
+ * dependency DumpController <SysuiLogId>
+ */
+public class SysuiLog implements Dumpable {
+ public static final SimpleDateFormat DATE_FORMAT =
+ new SimpleDateFormat("MM-dd HH:mm:ss", Locale.US);
+
+ private final Object mDataLock = new Object();
+ private final String mId;
+ private final int mMaxLogs;
+ private boolean mEnabled;
+
+ @VisibleForTesting protected ArrayDeque<Event> mTimeline;
+
+ /**
+ * Creates a SysuiLog
+ * To enable or disable logs, set the system property and then restart the device:
+ * adb shell setprop sysui.log.enabled.<id> true/false && adb reboot
+ * @param dumpController where to register this logger's dumpsys
+ * @param id user-readable tag for this logger
+ * @param maxDebugLogs maximum number of logs to retain when {@link sDebuggable} is true
+ * @param maxLogs maximum number of logs to retain when {@link sDebuggable} is false
+ */
+ public SysuiLog(DumpController dumpController, String id, int maxDebugLogs, int maxLogs) {
+ this(dumpController, id, sDebuggable ? maxDebugLogs : maxLogs,
+ SystemProperties.getBoolean(SYSPROP_ENABLED_PREFIX + id, DEFAULT_ENABLED));
+ }
+
+ @VisibleForTesting
+ protected SysuiLog(DumpController dumpController, String id, int maxLogs, boolean enabled) {
+ mId = id;
+ mMaxLogs = maxLogs;
+ mEnabled = enabled;
+ mTimeline = mEnabled ? new ArrayDeque<>(mMaxLogs) : null;
+ dumpController.registerDumpable(mId, this);
+ }
+
+ public SysuiLog(DumpController dumpController, String id) {
+ this(dumpController, id, DEFAULT_MAX_DEBUG_LOGS, DEFAULT_MAX_LOGS);
+ }
+
+ /**
+ * Logs an event to the timeline which can be printed by the dumpsys.
+ * May also log to logcat if enabled.
+ * @return true if event was logged, else false
+ */
+ public boolean log(Event event) {
+ if (!mEnabled) {
+ return false;
+ }
+
+ synchronized (mDataLock) {
+ if (mTimeline.size() >= mMaxLogs) {
+ mTimeline.removeFirst();
+ }
+
+ mTimeline.add(event);
+ }
+
+ if (LOG_TO_LOGCAT_ENABLED) {
+ final String strEvent = eventToString(event);
+ switch (event.getLogLevel()) {
+ case Event.VERBOSE:
+ Log.v(mId, strEvent);
+ break;
+ case Event.DEBUG:
+ Log.d(mId, strEvent);
+ break;
+ case Event.ERROR:
+ Log.e(mId, strEvent);
+ break;
+ case Event.INFO:
+ Log.i(mId, strEvent);
+ break;
+ case Event.WARN:
+ Log.w(mId, strEvent);
+ break;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return user-readable string of the given event
+ */
+ public String eventToString(Event event) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(SysuiLog.DATE_FORMAT.format(event.getTimestamp()));
+ sb.append(" ");
+ sb.append(event.getMessage());
+ return sb.toString();
+ }
+
+ /**
+ * only call on this method if you have the mDataLock
+ */
+ private void dumpTimelineLocked(PrintWriter pw) {
+ pw.println("\tTimeline:");
+
+ for (Event event : mTimeline) {
+ pw.println("\t" + eventToString(event));
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(mId + ":");
+
+ if (mEnabled) {
+ synchronized (mDataLock) {
+ dumpTimelineLocked(pw);
+ }
+ } else {
+ pw.print(" - Logging disabled.");
+ }
+ }
+
+ private static boolean sDebuggable = Build.IS_DEBUGGABLE;
+ private static final String SYSPROP_ENABLED_PREFIX = "sysui.log.enabled.";
+ private static final boolean LOG_TO_LOGCAT_ENABLED = sDebuggable;
+ private static final boolean DEFAULT_ENABLED = sDebuggable;
+ private static final int DEFAULT_MAX_DEBUG_LOGS = 100;
+ private static final int DEFAULT_MAX_LOGS = 50;
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java
new file mode 100644
index 000000000000..2f90641775e8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/RichEventTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 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.systemui.log;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class RichEventTest extends SysuiTestCase {
+
+ private static final int TOTAL_EVENT_TYPES = 1;
+
+ @Test
+ public void testCreateRichEvent_invalidType() {
+ try {
+ // indexing for events starts at 0, so TOTAL_EVENT_TYPES is an invalid type
+ new TestableRichEvent(Event.DEBUG, TOTAL_EVENT_TYPES, "msg");
+ } catch (IllegalArgumentException e) {
+ // expected
+ return;
+ }
+
+ Assert.fail("Expected an invalidArgumentException since the event type was invalid.");
+ }
+
+ @Test
+ public void testCreateRichEvent() {
+ final int eventType = 0;
+ RichEvent e = new TestableRichEvent(Event.DEBUG, eventType, "msg");
+ assertEquals(e.getType(), eventType);
+ }
+
+ class TestableRichEvent extends RichEvent {
+ TestableRichEvent(int logLevel, int type, String reason) {
+ super(logLevel, type, reason);
+ }
+
+ @Override
+ public String[] getEventLabels() {
+ return new String[]{"ACTION_NAME"};
+ }
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java
new file mode 100644
index 000000000000..378bba1afda3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SysuiLogTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 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.systemui.log;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.DumpController;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class SysuiLogTest extends SysuiTestCase {
+ private static final String TEST_ID = "TestLogger";
+ private static final int MAX_LOGS = 5;
+
+ @Mock
+ private DumpController mDumpController;
+ private SysuiLog mSysuiLog;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testLogDisabled_noLogsWritten() {
+ mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, false);
+ assertEquals(mSysuiLog.mTimeline, null);
+
+ mSysuiLog.log(new Event("msg"));
+ assertEquals(mSysuiLog.mTimeline, null);
+ }
+
+ @Test
+ public void testLogEnabled_logWritten() {
+ mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
+ assertEquals(mSysuiLog.mTimeline.size(), 0);
+
+ mSysuiLog.log(new Event("msg"));
+ assertEquals(mSysuiLog.mTimeline.size(), 1);
+ }
+
+ @Test
+ public void testMaxLogs() {
+ mSysuiLog = new SysuiLog(mDumpController, TEST_ID, MAX_LOGS, true);
+ assertEquals(mSysuiLog.mTimeline.size(), 0);
+
+ final String msg = "msg";
+ for (int i = 0; i < MAX_LOGS + 1; i++) {
+ mSysuiLog.log(new Event(msg + i));
+ }
+
+ assertEquals(mSysuiLog.mTimeline.size(), MAX_LOGS);
+
+ // check the first message (msg0) is deleted:
+ assertEquals(mSysuiLog.mTimeline.getFirst().getMessage(), msg + "1");
+ }
+}