1 /*
2  * Copyright 2024 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 
17 // @file:JvmName("ActiveLogs")
18 
19 package com.android.server.bluetooth
20 
21 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE
22 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST
23 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_CRASH
24 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED
25 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET
26 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTARTED
27 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING
28 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_SATELLITE_MODE
29 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_START_ERROR
30 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT
31 import android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH
32 import android.os.Binder
33 import android.util.proto.ProtoOutputStream
34 import androidx.annotation.VisibleForTesting
35 import com.android.bluetooth.BluetoothStatsLog
36 import com.android.bluetooth.BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED
37 import com.android.bluetooth.BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED
38 import com.android.bluetooth.BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED
39 import com.android.bluetooth.BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__UNKNOWN
40 import com.android.server.BluetoothManagerServiceDumpProto as BtProto
41 import java.io.PrintWriter
42 
43 private const val TAG = "ActiveLogs"
44 
45 object ActiveLogs {
46     @VisibleForTesting internal const val MAX_ENTRIES_STORED = 20
47     @VisibleForTesting
48     internal val activeLogs: ArrayDeque<ActiveLog> = ArrayDeque(MAX_ENTRIES_STORED)
49 
50     @JvmStatic
dumpnull51     fun dump(writer: PrintWriter) {
52         if (activeLogs.isEmpty()) {
53             writer.println("Bluetooth never enabled!")
54         } else {
55             writer.println("Enable log:")
56             activeLogs.forEach { writer.println("  $it") }
57         }
58     }
59 
60     @JvmStatic
dumpProtonull61     fun dumpProto(proto: ProtoOutputStream) {
62         val token = proto.start(BtProto.ACTIVE_LOGS)
63         activeLogs.forEach { it.dump(proto) }
64         proto.end(token)
65     }
66 
67     @JvmStatic
68     @JvmOverloads
addnull69     fun add(
70         reason: Int,
71         enable: Boolean,
72         packageName: String = "BluetoothSystemServer",
73         isBle: Boolean = false
74     ) {
75         val last = activeLogs.lastOrNull()
76         if (activeLogs.size == MAX_ENTRIES_STORED) {
77             activeLogs.removeFirst()
78         }
79         activeLogs.addLast(ActiveLog(reason, packageName, enable, isBle))
80         val state =
81             if (enable) BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED
82             else BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED
83         val lastState: Int
84         val timeSinceLastChanged: Long
85         if (last != null) {
86             lastState =
87                 if (last.enable) BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED
88                 else BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED
89             timeSinceLastChanged = System.currentTimeMillis() - last.timestamp
90         } else {
91             lastState = BLUETOOTH_ENABLED_STATE_CHANGED__STATE__UNKNOWN
92             timeSinceLastChanged = 0
93         }
94 
95         BluetoothStatsLog.write_non_chained(
96             BLUETOOTH_ENABLED_STATE_CHANGED,
97             Binder.getCallingUid(),
98             null,
99             state,
100             reason,
101             packageName,
102             lastState,
103             timeSinceLastChanged
104         )
105     }
106 }
107 
108 @VisibleForTesting
109 internal class ActiveLog(
110     private val reason: Int,
111     private val packageName: String,
112     val enable: Boolean,
113     private val isBle: Boolean,
114 ) {
115     val timestamp = System.currentTimeMillis()
116 
117     init {
118         Log.d(TAG, this.toString())
119     }
120 
toStringnull121     override fun toString() =
122         Log.timeToStringWithZone(timestamp) +
123             " \tPackage [$packageName] requested to [" +
124             (if (enable) "Enable" else "Disable") +
125             (if (isBle) "Ble" else "") +
126             "]. \tReason is " +
127             getEnableDisableReasonString(reason)
128 
129     fun dump(proto: ProtoOutputStream) {
130         proto.write(BtProto.ActiveLog.TIMESTAMP_MS, timestamp)
131         proto.write(BtProto.ActiveLog.ENABLE, enable)
132         proto.write(BtProto.ActiveLog.PACKAGE_NAME, packageName)
133         proto.write(BtProto.ActiveLog.REASON, reason)
134     }
135 }
136 
getEnableDisableReasonStringnull137 private fun getEnableDisableReasonString(reason: Int): String {
138     return when (reason) {
139         ENABLE_DISABLE_REASON_APPLICATION_REQUEST -> "APPLICATION_REQUEST"
140         ENABLE_DISABLE_REASON_AIRPLANE_MODE -> "AIRPLANE_MODE"
141         ENABLE_DISABLE_REASON_DISALLOWED -> "DISALLOWED"
142         ENABLE_DISABLE_REASON_RESTARTED -> "RESTARTED"
143         ENABLE_DISABLE_REASON_START_ERROR -> "START_ERROR"
144         ENABLE_DISABLE_REASON_SYSTEM_BOOT -> "SYSTEM_BOOT"
145         ENABLE_DISABLE_REASON_CRASH -> "CRASH"
146         ENABLE_DISABLE_REASON_USER_SWITCH -> "USER_SWITCH"
147         ENABLE_DISABLE_REASON_RESTORE_USER_SETTING -> "RESTORE_USER_SETTING"
148         ENABLE_DISABLE_REASON_FACTORY_RESET -> "FACTORY_RESET"
149         ENABLE_DISABLE_REASON_SATELLITE_MODE -> "SATELLITE MODE"
150         else -> "UNKNOWN[$reason]"
151     }
152 }
153