159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal/*******************************************************************************
259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *      Copyright (C) 2013 Google Inc.
359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *      Licensed to The Android Open Source Project.
459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *
559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *      Licensed under the Apache License, Version 2.0 (the "License");
659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *      you may not use this file except in compliance with the License.
759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *      You may obtain a copy of the License at
859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *
959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *           http://www.apache.org/licenses/LICENSE-2.0
1059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *
1159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *      Unless required by applicable law or agreed to in writing, software
1259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *      distributed under the License is distributed on an "AS IS" BASIS,
1359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *      See the License for the specific language governing permissions and
1559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *      limitations under the License.
1659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *******************************************************************************/
1759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
1859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalpackage com.android.mail;
1959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
2059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport com.android.mail.utils.LogTag;
2159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport com.android.mail.utils.LogUtils;
2259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
2359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport android.app.Service;
2459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport android.content.Intent;
2559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport android.os.IBinder;
2659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport android.util.Pair;
2759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
2859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport java.io.FileDescriptor;
2959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport java.io.PrintWriter;
3059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport java.util.Date;
3159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport java.util.HashMap;
3259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport java.util.LinkedList;
3359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport java.util.Map;
3459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalimport java.util.Queue;
3559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
3659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal/**
3759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal * A write-only device for sensitive logs. Turned on only during debugging.
3859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *
3959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal * Dump valuable system state by sending a local broadcast to the associated activity.
4059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal * Broadcast receivers are responsible for dumping state as they see fit.
4159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal * This service is only started when the log level is high, so there is no risk of user
4259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal * data being logged by mistake.
4359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal *
4459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal * To add logging to this service, call {@link #log(String, String, Object...)} with a tag name,
4559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal * which is a class name, like "AbstractActivityController", which is a unique ID. Then, add to the
4659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal * resulting buffer any information of interest at logging time. This is kept in a ring buffer,
4759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal * which is overwritten with new information.
4859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal */
4959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwalpublic class MailLogService extends Service {
5059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    /**
5159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * This is the top level flag that enables this service.
5259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     */
531fc5b00641a9a506439f3efc0f1063d0bd6167a3Vikram Aggarwal    public static boolean DEBUG_ENABLED = false;
5459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
5559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    /** The tag which needs to be turned to DEBUG to get logging going. */
5659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    protected static final String LOG_TAG = LogTag.getLogTag();
5759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
5859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    /**
5959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * A circular buffer of {@value #SIZE} lines.  To  insert into this buffer,
6059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * call the {@link #put(String)} method.  To retrieve the most recent logs,
6159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * call the {@link #toString()} method.
6259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     */
6359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    private static class CircularBuffer {
6459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        // We accept fifty lines of input.
6559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        public static final int SIZE = 50;
6659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        /** The actual list of strings to be printed. */
6759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        final Queue<Pair<Long, String>> mList = new LinkedList<Pair<Long, String>>();
6859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        /** The current size of the buffer */
6959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        int mCurrentSize = 0;
7059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
7159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        /** Create an empty log buffer. */
7259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        private CircularBuffer() {
7359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            // Do nothing
7459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        }
7559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
7659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        /** Get the current timestamp */
7759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        private static String dateToString(long timestamp) {
7859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            final Date d = new Date(timestamp);
7959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            return String.format("%d-%d %d:%d:%d: ", d.getDay(), d.getMonth(), d.getHours(),
8059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal                    d.getMinutes(), d.getSeconds());
8159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        }
8259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
8359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        /**
8459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal         * Insert a log message into the buffer. This might evict the oldest message if the log
8559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal         * is at capacity.
8659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal         * @param message a log message for this buffer.
8759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal         */
8859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        private synchronized void put(String message) {
8959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            if (mCurrentSize == SIZE) {
9059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal                // At capacity, we'll remove the head, and add to the tail. Size is unchanged.
9159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal                mList.remove();
9259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            } else {
9359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal                // Less than capacity. Adding a new element at the end.
9459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal                mCurrentSize++;
9559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            }
9659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            // Add the current timestamp along with the message.
9759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            mList.add(new Pair<Long, String>(System.currentTimeMillis(), message));
9859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        }
9959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
10059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        @Override
10159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        public String toString() {
10259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            final StringBuilder builder = new StringBuilder();
10359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            for (final Pair<Long, String> s : mList) {
10459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal                // Print the timestamp as an actual date, and then the message.
10559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal                builder.append(dateToString(s.first));
10659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal                builder.append(s.second);
10759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal                // Put a newline at the end of each log line.
10859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal                builder.append("\n");
10959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            }
11059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            return builder.toString();
11159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        }
11259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    }
11359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
11459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    /** Header printed at the start of the dump. */
11559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    private static final String HEADER = "**** MailLogService ***\n";
11659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    /** Map of current tag -> log. */
11759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    private static final Map<String, CircularBuffer> sLogs = new HashMap<String, CircularBuffer>();
11859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
1199e2d407fdafeb874e640eb84017feaf784309075Scott Kennedy    @Override
12059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    public IBinder onBind(Intent intent) {
12159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        return null;
12259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    }
12359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
12459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    /**
12559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * Return the circular buffer associated with this tag, or create a new buffer if none is
12659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * currently associated.
12759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * @param tag a string to identify a unique tag.
12859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * @return a circular buffer associated with a string tag.
12959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     */
13059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    private static CircularBuffer getOrCreate(String tag) {
13159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        if (sLogs.containsKey(tag)) {
13259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            return sLogs.get(tag);
13359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        }
13459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        // Create a new CircularBuffer with this tag
13559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        final CircularBuffer buffer = new CircularBuffer();
13659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        sLogs.put(tag, buffer);
13759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        return buffer;
13859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    }
13959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
14059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    /**
14159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * Return true if the logging level is high enough for this service to function.
14259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * @return true if this service is functioning at the current log level. False otherwise.
14359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     */
14459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    public static boolean isLoggingLevelHighEnough() {
145b184bfe96fa3512af88260fce4f3cee3066fb28dScott Kennedy        return LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG);
14659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    }
14759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
14859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    /**
14959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * Add to the log for the tag given.
15059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * @param tag a unique tag to add the message to
15159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * @param format a string format for the message
15259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     * @param args optional list of arguments for the format.
15359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal     */
15459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    public static void log(String tag, String format, Object... args) {
15559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        if (!DEBUG_ENABLED || !isLoggingLevelHighEnough()) {
15659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            return;
15759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        }
15859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        // The message we are printing.
15959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        final String logMessage = String.format(format, args);
16059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        // Find the circular buffer to go with this tag, or create a new one.
16159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        getOrCreate(tag).put(logMessage);
16259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    }
16359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
16459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    @Override
16559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
16659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        if (!DEBUG_ENABLED) {
16759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            return;
16859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        }
16959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        writer.print(HEADER);
17059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        // Go through all the tags, and write them all out sequentially.
17159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        for (final String tag : sLogs.keySet()) {
17259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            // Write out a sub-header: Logging for tag "MyModuleName"
17359f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            writer.append("Logging for tag: \"");
17459f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            writer.append(tag);
17559f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            writer.append("\"\n");
17659f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal
17759f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal            writer.append(sLogs.get(tag).toString());
17859f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        }
17959f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        // Go through all the buffers.
18059f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal        super.dump(fd, writer,args);
18159f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal    }
18259f741f60e389818a9a56b1fba46eea1c372690dVikram Aggarwal}
183