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

Commit e1fc31cc authored by Vania Januar's avatar Vania Januar Committed by Android (Google) Code Review
Browse files

Merge "Add stylus low battery notification metrics."

parents daf93050 595127c6
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
            )
    }
}