Loading packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt 0 → 100644 +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 } packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt +25 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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. Loading @@ -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 Loading Loading @@ -143,6 +147,7 @@ constructor( .setAutoCancel(true) .build() logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN) notificationManager.notify(USI_NOTIFICATION_ID, notification) } Loading @@ -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 Loading @@ -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 Loading @@ -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" } } packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt +53 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 } Loading Loading @@ -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) Loading @@ -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 ) } } Loading
packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt 0 → 100644 +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 }
packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt +25 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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. Loading @@ -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 Loading Loading @@ -143,6 +147,7 @@ constructor( .setAutoCancel(true) .build() logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN) notificationManager.notify(USI_NOTIFICATION_ID, notification) } Loading @@ -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 Loading @@ -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 Loading @@ -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" } }
packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt +53 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading @@ -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 } Loading Loading @@ -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) Loading @@ -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 ) } }