/* * Copyright (C) 2020 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.systemui.log import android.content.ContentResolver import android.database.ContentObserver import android.net.Uri import android.os.Handler import android.os.Looper import android.provider.Settings /** * Version of [LogcatEchoTracker] for debuggable builds * * The log level of individual buffers or tags can be controlled via global settings: * * ``` * # Echo any message to of or higher * $ adb shell settings put global systemui/buffer/ * * # Echo any message of and of or higher * $ adb shell settings put global systemui/tag/ * ``` */ class LogcatEchoTrackerDebug private constructor( private val contentResolver: ContentResolver ) : LogcatEchoTracker { private val cachedBufferLevels: MutableMap = mutableMapOf() private val cachedTagLevels: MutableMap = mutableMapOf() companion object Factory { @JvmStatic fun create( contentResolver: ContentResolver, mainLooper: Looper ): LogcatEchoTrackerDebug { val tracker = LogcatEchoTrackerDebug(contentResolver) tracker.attach(mainLooper) return tracker } } private fun attach(mainLooper: Looper) { contentResolver.registerContentObserver( Settings.Global.getUriFor(BUFFER_PATH), true, object : ContentObserver(Handler(mainLooper)) { override fun onChange(selfChange: Boolean, uri: Uri) { super.onChange(selfChange, uri) cachedBufferLevels.clear() } }) contentResolver.registerContentObserver( Settings.Global.getUriFor(TAG_PATH), true, object : ContentObserver(Handler(mainLooper)) { override fun onChange(selfChange: Boolean, uri: Uri) { super.onChange(selfChange, uri) cachedTagLevels.clear() } }) } /** * Whether [bufferName] should echo messages of [level] or higher to logcat. */ @Synchronized override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean { return level.ordinal >= getLogLevel(bufferName, BUFFER_PATH, cachedBufferLevels).ordinal } /** * Whether [tagName] should echo messages of [level] or higher to logcat. */ @Synchronized override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { return level >= getLogLevel(tagName, TAG_PATH, cachedTagLevels) } private fun getLogLevel( name: String, path: String, cache: MutableMap ): LogLevel { return cache[name] ?: readSetting("$path/$name").also { cache[name] = it } } private fun readSetting(path: String): LogLevel { return try { parseProp(Settings.Global.getString(contentResolver, path)) } catch (_: Settings.SettingNotFoundException) { DEFAULT_LEVEL } } private fun parseProp(propValue: String?): LogLevel { return when (propValue?.toLowerCase()) { "verbose" -> LogLevel.VERBOSE "v" -> LogLevel.VERBOSE "debug" -> LogLevel.DEBUG "d" -> LogLevel.DEBUG "info" -> LogLevel.INFO "i" -> LogLevel.INFO "warning" -> LogLevel.WARNING "warn" -> LogLevel.WARNING "w" -> LogLevel.WARNING "error" -> LogLevel.ERROR "e" -> LogLevel.ERROR "assert" -> LogLevel.WTF "wtf" -> LogLevel.WTF else -> DEFAULT_LEVEL } } } private val DEFAULT_LEVEL = LogLevel.WARNING private const val BUFFER_PATH = "systemui/buffer" private const val TAG_PATH = "systemui/tag"