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
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;
26import 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.
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