blob: 12b4a227b3da6c2b7b690c3f490f8b4e9cb0173b [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2013 Google Inc.
* Licensed to 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.mail;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Pair;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
/**
* A write-only device for sensitive logs. Turned on only during debugging.
*
* Dump valuable system state by sending a local broadcast to the associated activity.
* Broadcast receivers are responsible for dumping state as they see fit.
* This service is only started when the log level is high, so there is no risk of user
* data being logged by mistake.
*
* To add logging to this service, call {@link #log(String, String, Object...)} with a tag name,
* which is a class name, like "AbstractActivityController", which is a unique ID. Then, add to the
* resulting buffer any information of interest at logging time. This is kept in a ring buffer,
* which is overwritten with new information.
*/
public class MailLogService extends Service {
/**
* This is the top level flag that enables this service.
*/
public static boolean DEBUG_ENABLED = false;
/** The tag which needs to be turned to DEBUG to get logging going. */
protected static final String LOG_TAG = LogTag.getLogTag();
/**
* A circular buffer of {@value #SIZE} lines. To insert into this buffer,
* call the {@link #put(String)} method. To retrieve the most recent logs,
* call the {@link #toString()} method.
*/
private static class CircularBuffer {
// We accept fifty lines of input.
public static final int SIZE = 50;
/** The actual list of strings to be printed. */
final Queue<Pair<Long, String>> mList = new LinkedList<Pair<Long, String>>();
/** The current size of the buffer */
int mCurrentSize = 0;
/** Create an empty log buffer. */
private CircularBuffer() {
// Do nothing
}
/** Get the current timestamp */
private static String dateToString(long timestamp) {
final Date d = new Date(timestamp);
return String.format("%d-%d %d:%d:%d: ", d.getDay(), d.getMonth(), d.getHours(),
d.getMinutes(), d.getSeconds());
}
/**
* Insert a log message into the buffer. This might evict the oldest message if the log
* is at capacity.
* @param message a log message for this buffer.
*/
private synchronized void put(String message) {
if (mCurrentSize == SIZE) {
// At capacity, we'll remove the head, and add to the tail. Size is unchanged.
mList.remove();
} else {
// Less than capacity. Adding a new element at the end.
mCurrentSize++;
}
// Add the current timestamp along with the message.
mList.add(new Pair<Long, String>(System.currentTimeMillis(), message));
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
for (final Pair<Long, String> s : mList) {
// Print the timestamp as an actual date, and then the message.
builder.append(dateToString(s.first));
builder.append(s.second);
// Put a newline at the end of each log line.
builder.append("\n");
}
return builder.toString();
}
}
/** Header printed at the start of the dump. */
private static final String HEADER = "**** MailLogService ***\n";
/** Map of current tag -> log. */
private static final Map<String, CircularBuffer> sLogs = new HashMap<String, CircularBuffer>();
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* Return the circular buffer associated with this tag, or create a new buffer if none is
* currently associated.
* @param tag a string to identify a unique tag.
* @return a circular buffer associated with a string tag.
*/
private static CircularBuffer getOrCreate(String tag) {
if (sLogs.containsKey(tag)) {
return sLogs.get(tag);
}
// Create a new CircularBuffer with this tag
final CircularBuffer buffer = new CircularBuffer();
sLogs.put(tag, buffer);
return buffer;
}
/**
* Return true if the logging level is high enough for this service to function.
* @return true if this service is functioning at the current log level. False otherwise.
*/
public static boolean isLoggingLevelHighEnough() {
return LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG);
}
/**
* Add to the log for the tag given.
* @param tag a unique tag to add the message to
* @param format a string format for the message
* @param args optional list of arguments for the format.
*/
public static void log(String tag, String format, Object... args) {
if (!DEBUG_ENABLED || !isLoggingLevelHighEnough()) {
return;
}
// The message we are printing.
final String logMessage = String.format(format, args);
// Find the circular buffer to go with this tag, or create a new one.
getOrCreate(tag).put(logMessage);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
if (!DEBUG_ENABLED) {
return;
}
writer.print(HEADER);
// Go through all the tags, and write them all out sequentially.
for (final String tag : sLogs.keySet()) {
// Write out a sub-header: Logging for tag "MyModuleName"
writer.append("Logging for tag: \"");
writer.append(tag);
writer.append("\"\n");
writer.append(sLogs.get(tag).toString());
}
// Go through all the buffers.
super.dump(fd, writer,args);
}
}