Home | History | Annotate | Download | only in mail
      1 /*******************************************************************************
      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 
     18 package com.android.mail;
     19 
     20 import com.android.mail.utils.LogTag;
     21 import com.android.mail.utils.LogUtils;
     22 
     23 import android.app.Service;
     24 import android.content.Intent;
     25 import android.os.IBinder;
     26 import android.util.Pair;
     27 
     28 import java.io.FileDescriptor;
     29 import java.io.PrintWriter;
     30 import java.util.Date;
     31 import java.util.HashMap;
     32 import java.util.LinkedList;
     33 import java.util.Map;
     34 import 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  */
     49 public class MailLogService extends Service {
     50     /**
     51      * This is the top level flag that enables this service.
     52      */
     53     public static boolean DEBUG_ENABLED = false;
     54 
     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 
    119     @Override
    120     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() {
    145         return LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG);
    146     }
    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 }
    183