/******************************************************************************* * 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> mList = new LinkedList>(); /** 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(System.currentTimeMillis(), message)); } @Override public String toString() { final StringBuilder builder = new StringBuilder(); for (final Pair 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 sLogs = new HashMap(); @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); } }