Merge "Add stylus low battery notification metrics."

This commit is contained in:
Vania Januar
2023-02-08 14:37:31 +00:00
committed by Android (Google) Code Review
3 changed files with 113 additions and 4 deletions

View File

@@ -0,0 +1,35 @@
/*
* 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
}

View File

@@ -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"
}
}

View File

@@ -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
)
}
}