From 595127c68b9692a9dff71664420820fbb1f1dcde Mon Sep 17 00:00:00 2001 From: Vania Januar Date: Tue, 31 Jan 2023 16:29:32 +0000 Subject: [PATCH] Add stylus low battery notification metrics. Bug: 267815315 Test: StylusUsiPowerUiTest Change-Id: I35456ac07e1afbb90ac8788939ed6ea7cc363393 --- .../android/systemui/stylus/StylusUiEvent.kt | 35 ++++++++++++ .../systemui/stylus/StylusUsiPowerUI.kt | 28 ++++++++-- .../systemui/stylus/StylusUsiPowerUiTest.kt | 54 ++++++++++++++++++- 3 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt new file mode 100644 index 0000000000000..577292f539b93 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt @@ -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 +} diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt index 9050dad0134c7..89453ad3fff3e 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt @@ -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" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt index e1668e8bfa370..d51c514ac54dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt @@ -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 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 + ) + } }