Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6afa231d authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "SystemServer: Extract ActiveLog management" into main

parents 84d8b49d 4558febc
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ java_library {
    srcs: [
        ":statslog-bluetooth-java-gen",
        "src/**/*.java",
        "src/ActiveLog.kt",
        "src/AdapterBinder.kt",
        "src/AdapterState.kt",
        "src/AutoOnFeature.kt",
@@ -188,6 +189,8 @@ android_robolectric_test {

    srcs: [
        ":statslog-bluetooth-java-gen",
        "src/ActiveLog.kt",
        "src/ActiveLogTest.kt",
        "src/AdapterState.kt",
        "src/AdapterStateTest.kt",
        "src/AutoOnFeature.kt",
@@ -209,6 +212,9 @@ android_robolectric_test {
    static_libs: [
        "androidx.test.core",
        "androidx.test.ext.truth",
        "bluetooth-manager-service-proto-java-gen",
        "bluetooth-nano-protos",
        "bluetooth-proto-enums-java-gen",
        "bluetooth_flags_java_lib",
        "flag-junit",
        "kotlin-test",
+152 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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.
 */

// @file:JvmName("ActiveLogs")

package com.android.server.bluetooth

import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_CRASH
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTARTED
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_SATELLITE_MODE
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_START_ERROR
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH
import android.os.Binder
import android.util.proto.ProtoOutputStream
import androidx.annotation.VisibleForTesting
import com.android.bluetooth.BluetoothStatsLog
import com.android.bluetooth.BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED
import com.android.bluetooth.BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED
import com.android.bluetooth.BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED
import com.android.bluetooth.BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__UNKNOWN
import com.android.server.BluetoothManagerServiceDumpProto as BtProto
import java.io.PrintWriter

private const val TAG = "ActiveLogs"

object ActiveLogs {
    @VisibleForTesting internal const val MAX_ENTRIES_STORED = 20
    @VisibleForTesting
    internal val activeLogs: ArrayDeque<ActiveLog> = ArrayDeque(MAX_ENTRIES_STORED)

    @JvmStatic
    fun dump(writer: PrintWriter) {
        if (activeLogs.isEmpty()) {
            writer.println("Bluetooth never enabled!")
        } else {
            writer.println("Enable log:")
            activeLogs.forEach { writer.println("  $it") }
        }
    }

    @JvmStatic
    fun dumpProto(proto: ProtoOutputStream) {
        val token = proto.start(BtProto.ACTIVE_LOGS)
        activeLogs.forEach { it.dump(proto) }
        proto.end(token)
    }

    @JvmStatic
    @JvmOverloads
    fun add(
        reason: Int,
        enable: Boolean,
        packageName: String = "BluetoothSystemServer",
        isBle: Boolean = false
    ) {
        val last = activeLogs.lastOrNull()
        if (activeLogs.size == MAX_ENTRIES_STORED) {
            activeLogs.removeFirst()
        }
        activeLogs.addLast(ActiveLog(reason, packageName, enable, isBle))
        val state =
            if (enable) BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED
            else BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED
        val lastState: Int
        val timeSinceLastChanged: Long
        if (last != null) {
            lastState =
                if (last.enable) BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED
                else BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED
            timeSinceLastChanged = System.currentTimeMillis() - last.timestamp
        } else {
            lastState = BLUETOOTH_ENABLED_STATE_CHANGED__STATE__UNKNOWN
            timeSinceLastChanged = 0
        }

        BluetoothStatsLog.write_non_chained(
            BLUETOOTH_ENABLED_STATE_CHANGED,
            Binder.getCallingUid(),
            null,
            state,
            reason,
            packageName,
            lastState,
            timeSinceLastChanged
        )
    }
}

@VisibleForTesting
internal class ActiveLog(
    private val reason: Int,
    private val packageName: String,
    val enable: Boolean,
    private val isBle: Boolean,
) {
    val timestamp = System.currentTimeMillis()

    init {
        Log.d(TAG, this.toString())
    }

    override fun toString() =
        Log.timeToStringWithZone(timestamp) +
            " \tPackage [$packageName] requested to [" +
            (if (enable) "Enable" else "Disable") +
            (if (isBle) "Ble" else "") +
            "]. \tReason is " +
            getEnableDisableReasonString(reason)

    fun dump(proto: ProtoOutputStream) {
        proto.write(BtProto.ActiveLog.TIMESTAMP_MS, timestamp)
        proto.write(BtProto.ActiveLog.ENABLE, enable)
        proto.write(BtProto.ActiveLog.PACKAGE_NAME, packageName)
        proto.write(BtProto.ActiveLog.REASON, reason)
    }
}

private fun getEnableDisableReasonString(reason: Int): String {
    return when (reason) {
        ENABLE_DISABLE_REASON_APPLICATION_REQUEST -> "APPLICATION_REQUEST"
        ENABLE_DISABLE_REASON_AIRPLANE_MODE -> "AIRPLANE_MODE"
        ENABLE_DISABLE_REASON_DISALLOWED -> "DISALLOWED"
        ENABLE_DISABLE_REASON_RESTARTED -> "RESTARTED"
        ENABLE_DISABLE_REASON_START_ERROR -> "START_ERROR"
        ENABLE_DISABLE_REASON_SYSTEM_BOOT -> "SYSTEM_BOOT"
        ENABLE_DISABLE_REASON_CRASH -> "CRASH"
        ENABLE_DISABLE_REASON_USER_SWITCH -> "USER_SWITCH"
        ENABLE_DISABLE_REASON_RESTORE_USER_SETTING -> "RESTORE_USER_SETTING"
        ENABLE_DISABLE_REASON_FACTORY_RESET -> "FACTORY_RESET"
        ENABLE_DISABLE_REASON_SATELLITE_MODE -> "SATELLITE MODE"
        else -> "UNKNOWN[$reason]"
    }
}
+151 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.bluetooth.test

import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_CRASH
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTARTED
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_SATELLITE_MODE
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_START_ERROR
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT
import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH
import android.util.proto.ProtoOutputStream
import com.android.server.bluetooth.ActiveLogs
import com.android.server.bluetooth.Log
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestName
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class ActiveLogTest {

    @JvmField @Rule val testName = TestName()

    @Before
    fun setUp() {
        Log.i("ActiveLogTest", "\t--> setup of " + testName.getMethodName())
        ActiveLogs.activeLogs.clear()
    }

    @Test
    fun dump_whenNoActiveLog_indicateNeverEnabled() {
        val stringWriter = StringWriter()
        val writer = PrintWriter(stringWriter)

        ActiveLogs.dump(writer)

        assertThat(stringWriter.toString()).isEqualTo("Bluetooth never enabled!\n")
    }

    @Test
    fun dump_whenActiveLog_indicateAll() {
        val numberOfLogEntry = 3
        for (i in 1..numberOfLogEntry) {
            ActiveLogs.add(ENABLE_DISABLE_REASON_START_ERROR, false)
        }
        val stringWriter = StringWriter()
        val writer = PrintWriter(stringWriter)

        ActiveLogs.dump(writer)

        assertThat(stringWriter.toString()).matches("Enable log:\n(.*\n){$numberOfLogEntry}")
    }

    @Test
    fun dump_overflowQueue_indicateFirstEntries() {
        for (i in 1..ActiveLogs.MAX_ENTRIES_STORED * 2) {
            ActiveLogs.add(ENABLE_DISABLE_REASON_START_ERROR, false)
        }
        val stringWriter = StringWriter()
        val writer = PrintWriter(stringWriter)

        ActiveLogs.dump(writer)

        assertThat(stringWriter.toString())
            .matches("Enable log:\n(.*\n){${ActiveLogs.MAX_ENTRIES_STORED}}")
    }

    @Test
    fun dump_differentState_logsVariation() {
        ActiveLogs.add(ENABLE_DISABLE_REASON_START_ERROR, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_START_ERROR, true, "Foo", true)
        ActiveLogs.add(ENABLE_DISABLE_REASON_START_ERROR, true)
        val stringWriter = StringWriter()
        val writer = PrintWriter(stringWriter)

        ActiveLogs.dump(writer)

        assertThat(stringWriter.toString())
            .matches("Enable log:\n.*Disable.*\n.*EnableBle.*\n.*Enable.*\n")
    }

    @Test
    fun dump_allReason_stringIsKnown() {
        ActiveLogs.add(ENABLE_DISABLE_REASON_APPLICATION_REQUEST, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_AIRPLANE_MODE, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_DISALLOWED, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_RESTARTED, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_START_ERROR, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_SYSTEM_BOOT, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_CRASH, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_USER_SWITCH, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_RESTORE_USER_SETTING, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_FACTORY_RESET, false)
        ActiveLogs.add(ENABLE_DISABLE_REASON_SATELLITE_MODE, false)
        ActiveLogs.add(42, false)
        val stringWriter = StringWriter()
        val writer = PrintWriter(stringWriter)

        ActiveLogs.dump(writer)
        assertThat(stringWriter.toString())
            .matches(
                "Enable log:\n" +
                    ".*APPLICATION_REQUEST\n" +
                    ".*AIRPLANE_MODE\n" +
                    ".*DISALLOWED\n" +
                    ".*RESTARTED\n" +
                    ".*START_ERROR\n" +
                    ".*SYSTEM_BOOT\n" +
                    ".*CRASH\n" +
                    ".*USER_SWITCH\n" +
                    ".*RESTORE_USER_SETTING\n" +
                    ".*FACTORY_RESET\n" +
                    ".*SATELLITE MODE\n" +
                    ".*UNKNOWN\\[\\d+\\]\n"
            )
    }

    @Test
    fun protoDump() {
        ActiveLogs.add(ENABLE_DISABLE_REASON_APPLICATION_REQUEST, false)

        val proto = ProtoOutputStream()
        ActiveLogs.dumpProto(proto)

        assertThat(proto.getRawSize()).isEqualTo(48)
    }
}
+5 −2
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import android.bluetooth.IBluetoothCallback
import android.content.AttributionSource
import android.os.IBinder
import android.os.RemoteException
import com.android.server.bluetooth.BluetoothManagerService.timeToLog

class AdapterBinder(rawBinder: IBinder) {
    private val TAG = "AdapterBinder"
@@ -28,7 +27,11 @@ class AdapterBinder(rawBinder: IBinder) {
    private val createdAt = System.currentTimeMillis()

    override fun toString(): String =
        "[Binder=" + adapterBinder.hashCode() + ", createdAt=" + timeToLog(createdAt) + "]"
        "[Binder=" +
            adapterBinder.hashCode() +
            ", createdAt=" +
            Log.timeToStringWithZone(createdAt) +
            "]"

    @Throws(RemoteException::class)
    fun disable(source: AttributionSource) {
+25 −23
Original line number Diff line number Diff line
@@ -16,38 +16,40 @@
package com.android.server.bluetooth

import android.util.Log
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter

private const val SYSTEM_SERVER_TAG = "BluetoothSystemServer"

public class Log private constructor() {
    companion object {
object Log {

    // Kotlin could shorten below method by having a Throwable? that is default to null but the
    // current implementation of util.Log is behaving differently depending if it is called with
    // 2 or 3 parameters. We do not want to change the behavior in this class, just add a common
    // TAG to all the Bluetooth System Server logs.

        @JvmStatic
        fun v(subtag: String, msg: String) = Log.v(SYSTEM_SERVER_TAG, "${subtag}: ${msg}")
    @JvmStatic fun v(subtag: String, msg: String) = Log.v(SYSTEM_SERVER_TAG, "${subtag}: ${msg}")

        @JvmStatic
        fun d(subtag: String, msg: String) = Log.d(SYSTEM_SERVER_TAG, "${subtag}: ${msg}")
    @JvmStatic fun d(subtag: String, msg: String) = Log.d(SYSTEM_SERVER_TAG, "${subtag}: ${msg}")

        @JvmStatic
        fun i(subtag: String, msg: String) = Log.i(SYSTEM_SERVER_TAG, "${subtag}: ${msg}")
    @JvmStatic fun i(subtag: String, msg: String) = Log.i(SYSTEM_SERVER_TAG, "${subtag}: ${msg}")

        @JvmStatic
        fun w(subtag: String, msg: String) = Log.w(SYSTEM_SERVER_TAG, "${subtag}: ${msg}")
    @JvmStatic fun w(subtag: String, msg: String) = Log.w(SYSTEM_SERVER_TAG, "${subtag}: ${msg}")

    @JvmStatic
    fun w(subtag: String, msg: String, tr: Throwable) =
        Log.w(SYSTEM_SERVER_TAG, "${subtag}: ${msg}", tr)

        @JvmStatic
        fun e(subtag: String, msg: String) = Log.e(SYSTEM_SERVER_TAG, "${subtag}: ${msg}")
    @JvmStatic fun e(subtag: String, msg: String) = Log.e(SYSTEM_SERVER_TAG, "${subtag}: ${msg}")

    @JvmStatic
    fun e(subtag: String, msg: String, tr: Throwable) =
        Log.e(SYSTEM_SERVER_TAG, "${subtag}: ${msg}", tr)
    }

    @JvmStatic
    fun timeToStringWithZone(timestamp: Long) =
        DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS")
            .withZone(ZoneId.systemDefault())
            .format(Instant.ofEpochMilli(timestamp))
}
Loading