content: /*
   * Copyright (C) 2014 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.server.hdmi;
  
  import android.annotation.Nullable;
  import android.os.SystemClock;
  import android.util.Pair;
  import android.util.Slog;
  import android.util.Log;
  
  import java.util.HashMap;
  
  /**
   * A logger that prevents spammy log. For the same log message, it logs once every 20seconds.
   * This class is not thread-safe.
   * <p>
   * For convenience, use single character prefix for all messages.
   * Here are common acronyms
   * <ul>
   *   <li>[T]: Timout
   *   <li>[R]: Received message
   *   <li>[S]: Sent message
   *   <li>[P]: Device polling result
   * </ul>
   */
  final class HdmiLogger {
      private static final String TAG = "HDMI";
      // Logging duration for same error message.
      private static final long ERROR_LOG_DURATION_MILLIS = 20 * 1000;  // 20s
  
      private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
  
      private static final ThreadLocal<HdmiLogger> sLogger = new ThreadLocal<>();
  
      // Key (String): log message.
      // Value (Pair(Long, Integer)): a pair of last log time millis and the number of logMessage.
      // Cache for warning.
      private final HashMap<String, Pair<Long, Integer>> mWarningTimingCache = new HashMap<>();
      // Cache for error.
      private final HashMap<String, Pair<Long, Integer>> mErrorTimingCache = new HashMap<>();
  
      private HdmiLogger() {
      }
  
      static final void warning(String logMessage, Object... objs) {
          getLogger().warningInternal(toLogString(logMessage, objs));
      }
  
      private void warningInternal(String logMessage) {
          String log = updateLog(mWarningTimingCache, logMessage);
          if (!log.isEmpty()) {
              Slog.w(TAG, log);
          }
      }
  
      static final void error(String logMessage, Object... objs) {
          getLogger().errorInternal(toLogString(logMessage, objs));
      }
  
      private void errorInternal(String logMessage) {
          String log = updateLog(mErrorTimingCache, logMessage);
          if (!log.isEmpty()) {
              Slog.e(TAG, log);
          }
      }
  
      static final void debug(String logMessage, Object... objs) {
          getLogger().debugInternal(toLogString(logMessage, objs));
      }
  
      private void debugInternal(String logMessage) {
          if (DEBUG) {
              Slog.d(TAG, logMessage);
          }
      }
  
      private static final String toLogString(String logMessage, Object[] objs) {
          if (objs.length > 0) {
              return String.format(logMessage, objs);
          } else {
              return logMessage;
          }
      }
  
      private static HdmiLogger getLogger() {
          HdmiLogger logger = sLogger.get();
          if (logger == null) {
              logger = new HdmiLogger();
              sLogger.set(logger);
          }
          return logger;
      }
  
      private static String updateLog(HashMap<String, Pair<Long, Integer>> cache, String logMessage) {
          long curTime = SystemClock.uptimeMillis();
          Pair<Long, Integer> timing = cache.get(logMessage);
          if (shouldLogNow(timing, curTime)) {
              String log = buildMessage(logMessage, timing);
              cache.put(logMessage, new Pair<>(curTime, 1));
              return log;
          } else {
              increaseLogCount(cache, logMessage);
          }
          return "";
      }
  
      private static String buildMessage(String message, @Nullable Pair<Long, Integer> timing) {
          return new StringBuilder()
                  .append("[").append(timing == null ? 1 : timing.second).append("]:")
                  .append(message).toString();
      }
  
      private static void increaseLogCount(HashMap<String, Pair<Long, Integer>> cache,
              String message) {
          Pair<Long, Integer> timing = cache.get(message);
          if (timing != null) {
              cache.put(message, new Pair<>(timing.first, timing.second + 1));
          }
      }
  
      private static boolean shouldLogNow(@Nullable Pair<Long, Integer> timing, long curTime) {
          return timing == null || curTime - timing.first > ERROR_LOG_DURATION_MILLIS;
      }
  }
  