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

Commit 595127c6 authored by Vania Januar's avatar Vania Januar
Browse files

Add stylus low battery notification metrics.

Bug: 267815315
Test: StylusUsiPowerUiTest
Change-Id: I35456ac07e1afbb90ac8788939ed6ea7cc363393
parent 85f33be5
Loading
Loading
Loading
Loading
+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.stylus

import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger

enum class StylusUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
    @UiEvent(doc = "UiEvent for USI low battery notification shown")
    STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN(1298),
    @UiEvent(doc = "UiEvent for USI low battery notification clicked")
    STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED(1299),
    @UiEvent(doc = "UiEvent for USI low battery notification dismissed")
    STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED(1300),
    @UiEvent(doc = "UIEvent for Toast shown when stylus started charging")
    STYLUS_STARTED_CHARGING(1302),
    @UiEvent(doc = "UIEvent for Toast shown when stylus stopped charging")
    STYLUS_STOPPED_CHARGING(1303);

    override fun getId() = _id
}
+25 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.stylus

import android.Manifest
import android.app.ActivityManager
import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
@@ -32,6 +33,7 @@ import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -53,6 +55,7 @@ constructor(
    private val notificationManager: NotificationManagerCompat,
    private val inputManager: InputManager,
    @Background private val handler: Handler,
    private val uiEventLogger: UiEventLogger,
) {

    // These values must only be accessed on the handler.
@@ -79,12 +82,13 @@ constructor(

    fun refresh() {
        handler.post refreshNotification@{
            if (!suppressed && !hasConnectedBluetoothStylus() && isBatteryBelowThreshold()) {
            val batteryBelowThreshold = isBatteryBelowThreshold()
            if (!suppressed && !hasConnectedBluetoothStylus() && batteryBelowThreshold) {
                showOrUpdateNotification()
                return@refreshNotification
            }

            if (!isBatteryBelowThreshold()) {
            if (!batteryBelowThreshold) {
                // Reset suppression when stylus battery is recharged, so that the next time
                // it reaches a low battery, the notification will show again.
                suppressed = false
@@ -143,6 +147,7 @@ constructor(
                .setAutoCancel(true)
                .build()

        logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN)
        notificationManager.notify(USI_NOTIFICATION_ID, notification)
    }

@@ -168,8 +173,12 @@ constructor(
        object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                when (intent.action) {
                    ACTION_DISMISSED_LOW_BATTERY -> updateSuppression(true)
                    ACTION_DISMISSED_LOW_BATTERY -> {
                        logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED)
                        updateSuppression(true)
                    }
                    ACTION_CLICKED_LOW_BATTERY -> {
                        logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED)
                        updateSuppression(true)
                        if (inputDeviceId == null) return

@@ -195,6 +204,15 @@ constructor(
            }
        }

    private fun logUiEvent(metricId: StylusUiEvent) {
        uiEventLogger.logWithPosition(
            metricId,
            ActivityManager.getCurrentUser(),
            context.packageName,
            (batteryCapacity * 100.0).toInt()
        )
    }

    companion object {
        // Low battery threshold matches CrOS, see:
        // https://source.chromium.org/chromium/chromium/src/+/main:ash/system/power/peripheral_battery_notifier.cc;l=41
@@ -203,10 +221,14 @@ constructor(
        private val USI_NOTIFICATION_ID = R.string.stylus_battery_low_percentage

        @VisibleForTesting const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss"

        @VisibleForTesting const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click"

        @VisibleForTesting
        const val ACTION_STYLUS_USI_DETAILS = "com.android.settings.STYLUS_USI_DETAILS_SETTINGS"

        @VisibleForTesting const val KEY_DEVICE_INPUT_ID = "device_input_id"

        @VisibleForTesting const val KEY_SETTINGS_FRAGMENT_ARGS = ":settings:show_fragment_args"
    }
}
+53 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.stylus

import android.app.ActivityManager
import android.app.Notification
import android.content.BroadcastReceiver
import android.content.Context
@@ -27,6 +28,7 @@ import android.testing.AndroidTestingRunner
import android.view.InputDevice
import androidx.core.app.NotificationManagerCompat
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
@@ -54,15 +56,23 @@ import org.mockito.MockitoAnnotations
@SmallTest
class StylusUsiPowerUiTest : SysuiTestCase() {
    @Mock lateinit var notificationManager: NotificationManagerCompat

    @Mock lateinit var inputManager: InputManager

    @Mock lateinit var handler: Handler

    @Mock lateinit var btStylusDevice: InputDevice

    @Mock lateinit var uiEventLogger: UiEventLogger

    @Captor lateinit var notificationCaptor: ArgumentCaptor<Notification>

    private lateinit var stylusUsiPowerUi: StylusUsiPowerUI
    private lateinit var broadcastReceiver: BroadcastReceiver
    private lateinit var contextSpy: Context

    private val uid = ActivityManager.getCurrentUser()

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
@@ -80,7 +90,8 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
        whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
        whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES")

        stylusUsiPowerUi = StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler)
        stylusUsiPowerUi =
            StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler, uiEventLogger)
        broadcastReceiver = stylusUsiPowerUi.receiver
    }

@@ -196,6 +207,19 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
        verify(notificationManager).cancel(R.string.stylus_battery_low_percentage)
    }

    @Test
    fun updateBatteryState_showsNotification_logsNotificationShown() {
        stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))

        verify(uiEventLogger, times(1))
            .logWithPosition(
                StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN,
                uid,
                contextSpy.packageName,
                10
            )
    }

    @Test
    fun broadcastReceiver_clicked_hasInputDeviceId_startsUsiDetailsActivity() {
        val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
@@ -219,4 +243,32 @@ class StylusUsiPowerUiTest : SysuiTestCase() {

        verify(contextSpy, never()).startActivity(any())
    }

    @Test
    fun broadcastReceiver_clicked_logsNotificationClicked() {
        val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
        broadcastReceiver.onReceive(contextSpy, intent)

        verify(uiEventLogger, times(1))
            .logWithPosition(
                StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED,
                uid,
                contextSpy.packageName,
                100
            )
    }

    @Test
    fun broadcastReceiver_dismissed_logsNotificationDismissed() {
        val intent = Intent(StylusUsiPowerUI.ACTION_DISMISSED_LOW_BATTERY)
        broadcastReceiver.onReceive(contextSpy, intent)

        verify(uiEventLogger, times(1))
            .logWithPosition(
                StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED,
                uid,
                contextSpy.packageName,
                100
            )
    }
}