1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.hdmi;
18
19import android.annotation.Nullable;
20import android.os.Build;
21import android.os.SystemClock;
22import android.util.Pair;
23import android.util.Slog;
24import android.util.Log;
25
26import java.util.HashMap;
27
28/**
29 * A logger that prevents spammy log. For the same log message, it logs once every 20seconds.
30 * This class is not thread-safe.
31 * <p>
32 * For convenience, use single character prefix for all messages.
33 * Here are common acronyms
34 * <ul>
35 *   <li>[T]: Timout
36 *   <li>[R]: Received message
37 *   <li>[S]: Sent message
38 *   <li>[P]: Device polling result
39 * </ul>
40 */
41final class HdmiLogger {
42    private static final String TAG = "HDMI";
43    // Logging duration for same error message.
44    private static final long ERROR_LOG_DURATTION_MILLIS = 20 * 1000;  // 20s
45
46    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
47    private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
48
49    private static final ThreadLocal<HdmiLogger> sLogger = new ThreadLocal<>();
50
51    // Key (String): log message.
52    // Value (Pair(Long, Integer)): a pair of last log time millis and the number of logMessage.
53    // Cache for warning.
54    private final HashMap<String, Pair<Long, Integer>> mWarningTimingCache = new HashMap<>();
55    // Cache for error.
56    private final HashMap<String, Pair<Long, Integer>> mErrorTimingCache = new HashMap<>();
57
58    private HdmiLogger() {
59    }
60
61    static final void warning(String logMessage, Object... objs) {
62        getLogger().warningInternal(toLogString(logMessage, objs));
63    }
64
65    private void warningInternal(String logMessage) {
66        String log = updateLog(mWarningTimingCache, logMessage);
67        if (!log.isEmpty()) {
68            Slog.w(TAG, log);
69        }
70    }
71
72    static final void error(String logMessage, Object... objs) {
73        getLogger().errorInternal(toLogString(logMessage, objs));
74    }
75
76    private void errorInternal(String logMessage) {
77        String log = updateLog(mErrorTimingCache, logMessage);
78        if (!log.isEmpty()) {
79            Slog.e(TAG, log);
80        }
81    }
82
83    static final void debug(String logMessage, Object... objs) {
84        getLogger().debugInternal(toLogString(logMessage, objs));
85    }
86
87    private void debugInternal(String logMessage) {
88        if (DEBUG) {
89            Slog.d(TAG, logMessage);
90        }
91    }
92
93    private static final String toLogString(String logMessage, Object[] objs) {
94        if (objs.length > 0) {
95            return String.format(logMessage, objs);
96        } else {
97            return logMessage;
98        }
99    }
100
101    private static HdmiLogger getLogger() {
102        HdmiLogger logger = sLogger.get();
103        if (logger == null) {
104            logger = new HdmiLogger();
105            sLogger.set(logger);
106        }
107        return logger;
108    }
109
110    private static String updateLog(HashMap<String, Pair<Long, Integer>> cache, String logMessage) {
111        long curTime = SystemClock.uptimeMillis();
112        Pair<Long, Integer> timing = cache.get(logMessage);
113        if (shouldLogNow(timing, curTime)) {
114            String log = buildMessage(logMessage, timing);
115            cache.put(logMessage, new Pair<>(curTime, 1));
116            return log;
117        } else {
118            increaseLogCount(cache, logMessage);
119        }
120        return "";
121    }
122
123    private static String buildMessage(String message, @Nullable Pair<Long, Integer> timing) {
124        return new StringBuilder()
125                .append("[").append(timing == null ? 1 : timing.second).append("]:")
126                .append(message).toString();
127    }
128
129    private static void increaseLogCount(HashMap<String, Pair<Long, Integer>> cache,
130            String message) {
131        Pair<Long, Integer> timing = cache.get(message);
132        if (timing != null) {
133            cache.put(message, new Pair<>(timing.first, timing.second + 1));
134        }
135    }
136
137    private static boolean shouldLogNow(@Nullable Pair<Long, Integer> timing, long curTime) {
138        return timing == null || curTime - timing.first > ERROR_LOG_DURATTION_MILLIS;
139    }
140}
141