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

Commit ad7ef8b1 authored by Marcello Galhardo's avatar Marcello Galhardo
Browse files

Add logging utilities for development with Kotlin

Test: manual
Flag: not needed

Fixes: b/274509846
Change-Id: I5d800d1bf94fbcbee85dfa706d7a43c92bbc4164
parent ec396f01
Loading
Loading
Loading
Loading
+80 −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.systemui.log

import android.os.Build
import android.util.Log
import android.util.Log.LOG_ID_MAIN

/**
 * A simplified debug logger built as a wrapper around Android's [Log]. Internal for development.
 *
 * The main advantages are:
 * - Sensible defaults, automatically retrieving the class name from the call-site (i.e., tag);
 * - The messages are purged from source on release builds (keep in mind they are visible on AOSP);
 * - Lazily evaluate Strings for zero impact in production builds or when disabled;
 *
 * Usage example:
 * ```kotlin
 * // Logging a message:
 * debugLog { "message" }
 *
 * // Logging an error:
 * debugLog(error = exception) { "message" }
 *
 * // Logging the current stack trace, for debugging:
 * debugLog(error = Throwable()) { "message" }
 * ```
 */
object DebugLogger {

    /**
     * Log a debug message, with sensible defaults.
     *
     * For example:
     * ```kotlin
     * val one = 1
     * debugLog { "message#$one" }
     * ```
     *
     * The output will be: `D/NoteTaskController: message#1`
     *
     * Beware, the [debugLog] content is **REMOVED FROM SOURCE AND BINARY** in Release builds.
     *
     * @param enabled: whether or not the message should be logged. By default, it is
     *   [Build.IS_DEBUGGABLE].
     * @param priority: type of this log. By default, it is [Log.DEBUG].
     * @param tag: identifies the source of a log. By default, it is the receiver's simple name.
     * @param error: a [Throwable] to log.
     * @param message: a lazily evaluated message you wish to log.
     */
    inline fun Any.debugLog(
        enabled: Boolean = Build.IS_DEBUGGABLE,
        priority: Int = Log.DEBUG,
        tag: String = this::class.simpleName.orEmpty(),
        error: Throwable? = null,
        message: () -> String,
    ) {
        if (enabled) {
            if (error == null) {
                Log.println(priority, tag, message())
            } else {
                Log.printlns(LOG_ID_MAIN, priority, tag, message(), error)
            }
        }
    }
}
+35 −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.systemui.log

import android.os.Build
import android.util.Log

/** An empty logger for release builds. */
object DebugLogger {

    @JvmName("logcatMessage")
    inline fun Any.debugLog(
        enabled: Boolean = Build.IS_DEBUGGABLE,
        priority: Int = Log.DEBUG,
        tag: String = this::class.simpleName.orEmpty(),
        error: Throwable? = null,
        message: () -> String,
    ) {
        // no-op.
    }
}
+13 −19
Original line number Diff line number Diff line
@@ -31,15 +31,14 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import android.widget.Toast
import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
@@ -92,10 +91,10 @@ constructor(
        if (info.launchMode != NoteTaskLaunchMode.AppBubble) return

        if (isExpanding) {
            logDebug { "onBubbleExpandChanged - expanding: $info" }
            debugLog { "onBubbleExpandChanged - expanding: $info" }
            eventLogger.logNoteTaskOpened(info)
        } else {
            logDebug { "onBubbleExpandChanged - collapsing: $info" }
            debugLog { "onBubbleExpandChanged - collapsing: $info" }
            eventLogger.logNoteTaskClosed(info)
        }
    }
@@ -168,14 +167,14 @@ constructor(
            isKeyguardLocked &&
                devicePolicyManager.areKeyguardShortcutsDisabled(userId = user.identifier)
        ) {
            logDebug { "Enterprise policy disallows launching note app when the screen is locked." }
            debugLog { "Enterprise policy disallows launching note app when the screen is locked." }
            return
        }

        val info = resolver.resolveInfo(entryPoint, isKeyguardLocked, user)

        if (info == null) {
            logDebug { "Default notes app isn't set" }
            debugLog { "Default notes app isn't set" }
            showNoDefaultNotesAppToast()
            return
        }
@@ -184,7 +183,7 @@ constructor(

        try {
            // TODO(b/266686199): We should handle when app not available. For now, we log.
            logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" }
            debugLog { "onShowNoteTask - start: $info on user#${user.identifier}" }
            when (info.launchMode) {
                is NoteTaskLaunchMode.AppBubble -> {
                    val intent = createNoteTaskIntent(info)
@@ -192,7 +191,7 @@ constructor(
                        Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
                    bubbles.showOrHideAppBubble(intent, user, icon)
                    // App bubble logging happens on `onBubbleExpandChanged`.
                    logDebug { "onShowNoteTask - opened as app bubble: $info" }
                    debugLog { "onShowNoteTask - opened as app bubble: $info" }
                }
                is NoteTaskLaunchMode.Activity -> {
                    if (activityManager.isInForeground(info.packageName)) {
@@ -200,20 +199,20 @@ constructor(
                        val intent = createHomeIntent()
                        context.startActivityAsUser(intent, user)
                        eventLogger.logNoteTaskClosed(info)
                        logDebug { "onShowNoteTask - closed as activity: $info" }
                        debugLog { "onShowNoteTask - closed as activity: $info" }
                    } else {
                        val intent = createNoteTaskIntent(info)
                        context.startActivityAsUser(intent, user)
                        eventLogger.logNoteTaskOpened(info)
                        logDebug { "onShowNoteTask - opened as activity: $info" }
                        debugLog { "onShowNoteTask - opened as activity: $info" }
                    }
                }
            }
            logDebug { "onShowNoteTask - success: $info" }
            debugLog { "onShowNoteTask - success: $info" }
        } catch (e: ActivityNotFoundException) {
            logDebug { "onShowNoteTask - failed: $info" }
            debugLog { "onShowNoteTask - failed: $info" }
        }
        logDebug { "onShowNoteTask - completed: $info" }
        debugLog { "onShowNoteTask - completed: $info" }
    }

    @VisibleForTesting
@@ -253,7 +252,7 @@ constructor(
            PackageManager.DONT_KILL_APP,
        )

        logDebug { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
        debugLog { "setNoteTaskShortcutEnabled - completed: $isEnabled" }
    }

    /**
@@ -352,11 +351,6 @@ private fun createNoteTaskIntent(info: NoteTaskInfo): Intent =
        }
    }

/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
private inline fun Any.logDebug(message: () -> String) {
    if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
}

/** Creates an [Intent] which forces the current app to background by calling home. */
private fun createHomeIntent(): Intent =
    Intent(Intent.ACTION_MAIN).apply {
+2 −8
Original line number Diff line number Diff line
@@ -18,12 +18,11 @@ package com.android.systemui.notetask.shortcut

import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import androidx.activity.ComponentActivity
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
import com.android.systemui.settings.UserTracker
@@ -68,7 +67,7 @@ constructor(
        val mainUser: UserHandle? = userManager.mainUser
        if (userManager.isManagedProfile) {
            if (mainUser == null) {
                logDebug { "Can't find the main user. Skipping the notes app launch." }
                debugLog { "Can't find the main user. Skipping the notes app launch." }
            } else {
                controller.startNoteTaskProxyActivityForUser(mainUser)
            }
@@ -89,8 +88,3 @@ constructor(
        }
    }
}

/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
private inline fun Any.logDebug(message: () -> String) {
    if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
}
+11 −17
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import android.content.Context
import android.hardware.BatteryState
import android.hardware.input.InputManager
import android.hardware.input.InputSettings
import android.os.Build
import android.os.Handler
import android.util.ArrayMap
import android.util.Log
@@ -35,6 +34,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.shared.hardware.hasInputDevice
import com.android.systemui.shared.hardware.isInternalStylusSource
import java.util.concurrent.CopyOnWriteArrayList
@@ -81,7 +81,7 @@ constructor(
    fun startListener() {
        handler.post {
            if (hasStarted) return@post
            logDebug { "Listener has started." }
            debugLog { "Listener has started." }

            hasStarted = true
            isInUsiSession =
@@ -109,7 +109,7 @@ constructor(

        val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
        if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
        logDebug {
        debugLog {
            "Stylus InputDevice added: $deviceId ${device.name}, " +
                "External: ${device.isExternal}"
        }
@@ -134,7 +134,7 @@ constructor(

        val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
        if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
        logDebug { "Stylus InputDevice changed: $deviceId ${device.name}" }
        debugLog { "Stylus InputDevice changed: $deviceId ${device.name}" }

        val currAddress: String? = device.bluetoothAddress
        val prevAddress: String? = inputDeviceAddressMap[deviceId]
@@ -155,7 +155,7 @@ constructor(
        if (!hasStarted) return

        if (!inputDeviceAddressMap.contains(deviceId)) return
        logDebug { "Stylus InputDevice removed: $deviceId" }
        debugLog { "Stylus InputDevice removed: $deviceId" }

        unregisterBatteryListener(deviceId)

@@ -180,7 +180,7 @@ constructor(

            val isCharging = String(value) == "true"

            logDebug {
            debugLog {
                "Charging state metadata changed for device $inputDeviceId " +
                    "${device.address}: $isCharging"
            }
@@ -199,7 +199,7 @@ constructor(
        handler.post {
            if (!hasStarted) return@post

            logDebug {
            debugLog {
                "Battery state changed for $deviceId. " +
                    "batteryState present: ${batteryState.isPresent}, " +
                    "capacity: ${batteryState.capacity}"
@@ -247,7 +247,7 @@ constructor(
        if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return
        if (InputSettings.isStylusEverUsed(context)) return

        logDebug { "Stylus used for the first time." }
        debugLog { "Stylus used for the first time." }
        InputSettings.setStylusEverUsed(context, true)
        executeStylusCallbacks { cb -> cb.onStylusFirstUsed() }
    }
@@ -264,7 +264,7 @@ constructor(
        val hasBtConnection = if (inputDeviceBtSessionIdMap.isEmpty()) 0 else 1

        if (batteryStateValid && usiSessionId == null) {
            logDebug { "USI battery newly present, entering new USI session: $deviceId" }
            debugLog { "USI battery newly present, entering new USI session: $deviceId" }
            usiSessionId = instanceIdSequence.newInstanceId()
            uiEventLogger.logWithInstanceIdAndPosition(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
@@ -274,7 +274,7 @@ constructor(
                hasBtConnection,
            )
        } else if (!batteryStateValid && usiSessionId != null) {
            logDebug { "USI battery newly absent, exiting USI session: $deviceId" }
            debugLog { "USI battery newly absent, exiting USI session: $deviceId" }
            uiEventLogger.logWithInstanceIdAndPosition(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
                0,
@@ -291,7 +291,7 @@ constructor(
        btAddress: String,
        btConnected: Boolean
    ) {
        logDebug {
        debugLog {
            "Bluetooth stylus ${if (btConnected) "connected" else "disconnected"}:" +
                " $deviceId $btAddress"
        }
@@ -386,9 +386,3 @@ constructor(
        val TAG = StylusManager::class.simpleName.orEmpty()
    }
}

private inline fun logDebug(message: () -> String) {
    if (Build.IS_DEBUGGABLE) {
        Log.d(StylusManager.TAG, message())
    }
}
Loading