blob: 12b4a227b3da6c2b7b690c3f490f8b4e9cb0173b [file] [log] [blame]
Vikram Aggarwal59f741f2013-03-01 15:55:40 -08001/*******************************************************************************
2 * Copyright (C) 2013 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *******************************************************************************/
17
18package com.android.mail;
19
20import com.android.mail.utils.LogTag;
21import com.android.mail.utils.LogUtils;
22
23import android.app.Service;
24import android.content.Intent;
25import android.os.IBinder;
Vikram Aggarwal59f741f2013-03-01 15:55:40 -080026import android.util.Pair;
27
28import java.io.FileDescriptor;
29import java.io.PrintWriter;
30import java.util.Date;
31import java.util.HashMap;
32import java.util.LinkedList;
33import java.util.Map;
34import java.util.Queue;
35
36/**
37 * A write-only device for sensitive logs. Turned on only during debugging.
38 *
39 * Dump valuable system state by sending a local broadcast to the associated activity.
40 * Broadcast receivers are responsible for dumping state as they see fit.
41 * This service is only started when the log level is high, so there is no risk of user
42 * data being logged by mistake.
43 *
44 * To add logging to this service, call {@link #log(String, String, Object...)} with a tag name,
45 * which is a class name, like "AbstractActivityController", which is a unique ID. Then, add to the
46 * resulting buffer any information of interest at logging time. This is kept in a ring buffer,
47 * which is overwritten with new information.
48 */
49public class MailLogService extends Service {
50 /**
51 * This is the top level flag that enables this service.
Vikram Aggarwal59f741f2013-03-01 15:55:40 -080052 */
Vikram Aggarwal1fc5b002013-04-08 14:47:01 -070053 public static boolean DEBUG_ENABLED = false;
Vikram Aggarwal59f741f2013-03-01 15:55:40 -080054
55 /** The tag which needs to be turned to DEBUG to get logging going. */
56 protected static final String LOG_TAG = LogTag.getLogTag();
57
58 /**
59 * A circular buffer of {@value #SIZE} lines. To insert into this buffer,
60 * call the {@link #put(String)} method. To retrieve the most recent logs,
61 * call the {@link #toString()} method.
62 */
63 private static class CircularBuffer {
64 // We accept fifty lines of input.
65 public static final int SIZE = 50;
66 /** The actual list of strings to be printed. */
67 final Queue<Pair<Long, String>> mList = new LinkedList<Pair<Long, String>>();
68 /** The current size of the buffer */
69 int mCurrentSize = 0;
70
71 /** Create an empty log buffer. */
72 private CircularBuffer() {
73 // Do nothing
74 }
75
76 /** Get the current timestamp */
77 private static String dateToString(long timestamp) {
78 final Date d = new Date(timestamp);
79 return String.format("%d-%d %d:%d:%d: ", d.getDay(), d.getMonth(), d.getHours(),
80 d.getMinutes(), d.getSeconds());
81 }
82
83 /**
84 * Insert a log message into the buffer. This might evict the oldest message if the log
85 * is at capacity.
86 * @param message a log message for this buffer.
87 */
88 private synchronized void put(String message) {
89 if (mCurrentSize == SIZE) {
90 // At capacity, we'll remove the head, and add to the tail. Size is unchanged.
91 mList.remove();
92 } else {
93 // Less than capacity. Adding a new element at the end.
94 mCurrentSize++;
95 }
96 // Add the current timestamp along with the message.
97 mList.add(new Pair<Long, String>(System.currentTimeMillis(), message));
98 }
99
100 @Override
101 public String toString() {
102 final StringBuilder builder = new StringBuilder();
103 for (final Pair<Long, String> s : mList) {
104 // Print the timestamp as an actual date, and then the message.
105 builder.append(dateToString(s.first));
106 builder.append(s.second);
107 // Put a newline at the end of each log line.
108 builder.append("\n");
109 }
110 return builder.toString();
111 }
112 }
113
114 /** Header printed at the start of the dump. */
115 private static final String HEADER = "**** MailLogService ***\n";
116 /** Map of current tag -> log. */
117 private static final Map<String, CircularBuffer> sLogs = new HashMap<String, CircularBuffer>();
118
Scott Kennedy9e2d4072013-03-21 21:46:01 -0700119 @Override
Vikram Aggarwal59f741f2013-03-01 15:55:40 -0800120 public IBinder onBind(Intent intent) {
121 return null;
122 }
123
124 /**
125 * Return the circular buffer associated with this tag, or create a new buffer if none is
126 * currently associated.
127 * @param tag a string to identify a unique tag.
128 * @return a circular buffer associated with a string tag.
129 */
130 private static CircularBuffer getOrCreate(String tag) {
131 if (sLogs.containsKey(tag)) {
132 return sLogs.get(tag);
133 }
134 // Create a new CircularBuffer with this tag
135 final CircularBuffer buffer = new CircularBuffer();
136 sLogs.put(tag, buffer);
137 return buffer;
138 }
139
140 /**
141 * Return true if the logging level is high enough for this service to function.
142 * @return true if this service is functioning at the current log level. False otherwise.
143 */
144 public static boolean isLoggingLevelHighEnough() {
Scott Kennedyb184bfe2013-05-25 21:29:22 -0700145 return LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG);
Vikram Aggarwal59f741f2013-03-01 15:55:40 -0800146 }
147
148 /**
149 * Add to the log for the tag given.
150 * @param tag a unique tag to add the message to
151 * @param format a string format for the message
152 * @param args optional list of arguments for the format.
153 */
154 public static void log(String tag, String format, Object... args) {
155 if (!DEBUG_ENABLED || !isLoggingLevelHighEnough()) {
156 return;
157 }
158 // The message we are printing.
159 final String logMessage = String.format(format, args);
160 // Find the circular buffer to go with this tag, or create a new one.
161 getOrCreate(tag).put(logMessage);
162 }
163
164 @Override
165 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
166 if (!DEBUG_ENABLED) {
167 return;
168 }
169 writer.print(HEADER);
170 // Go through all the tags, and write them all out sequentially.
171 for (final String tag : sLogs.keySet()) {
172 // Write out a sub-header: Logging for tag "MyModuleName"
173 writer.append("Logging for tag: \"");
174 writer.append(tag);
175 writer.append("\"\n");
176
177 writer.append(sLogs.get(tag).toString());
178 }
179 // Go through all the buffers.
180 super.dump(fd, writer,args);
181 }
182}