Merge "Open USI details page from USI low battery notification." into tm-qpr-dev am: 54603d457d

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20823971

Change-Id: I5d0eb1157bf974915c14825c20cf7d4a386636de
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Vania Januar
2023-01-12 10:14:35 +00:00
committed by Automerger Merge Worker
5 changed files with 141 additions and 32 deletions

View File

@@ -52,8 +52,8 @@ constructor(
eventTimeMillis: Long, eventTimeMillis: Long,
batteryState: BatteryState batteryState: BatteryState
) { ) {
if (batteryState.isPresent) { if (batteryState.isPresent && batteryState.capacity > 0f) {
stylusUsiPowerUi.updateBatteryState(batteryState) stylusUsiPowerUi.updateBatteryState(deviceId, batteryState)
} }
} }
@@ -61,6 +61,7 @@ constructor(
if (!featureFlags.isEnabled(Flags.ENABLE_USI_BATTERY_NOTIFICATIONS)) return if (!featureFlags.isEnabled(Flags.ENABLE_USI_BATTERY_NOTIFICATIONS)) return
if (!hostDeviceSupportsStylusInput()) return if (!hostDeviceSupportsStylusInput()) return
stylusUsiPowerUi.init()
stylusManager.registerCallback(this) stylusManager.registerCallback(this)
stylusManager.startListener() stylusManager.startListener()
} }

View File

@@ -18,17 +18,21 @@ package com.android.systemui.stylus
import android.Manifest import android.Manifest
import android.app.PendingIntent import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.hardware.BatteryState import android.hardware.BatteryState
import android.hardware.input.InputManager import android.hardware.input.InputManager
import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.UserHandle import android.os.UserHandle
import android.util.Log
import android.view.InputDevice import android.view.InputDevice
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
@@ -53,6 +57,7 @@ constructor(
// These values must only be accessed on the handler. // These values must only be accessed on the handler.
private var batteryCapacity = 1.0f private var batteryCapacity = 1.0f
private var suppressed = false private var suppressed = false
private var inputDeviceId: Int? = null
fun init() { fun init() {
val filter = val filter =
@@ -87,10 +92,12 @@ constructor(
} }
} }
fun updateBatteryState(batteryState: BatteryState) { fun updateBatteryState(deviceId: Int, batteryState: BatteryState) {
handler.post updateBattery@{ handler.post updateBattery@{
if (batteryState.capacity == batteryCapacity) return@updateBattery if (batteryState.capacity == batteryCapacity || batteryState.capacity <= 0f)
return@updateBattery
inputDeviceId = deviceId
batteryCapacity = batteryState.capacity batteryCapacity = batteryState.capacity
refresh() refresh()
} }
@@ -150,23 +157,41 @@ constructor(
} }
private fun getPendingBroadcast(action: String): PendingIntent? { private fun getPendingBroadcast(action: String): PendingIntent? {
return PendingIntent.getBroadcastAsUser( return PendingIntent.getBroadcast(
context, context,
0, 0,
Intent(action), Intent(action).setPackage(context.packageName),
PendingIntent.FLAG_IMMUTABLE, PendingIntent.FLAG_IMMUTABLE,
UserHandle.CURRENT
) )
} }
private val receiver: BroadcastReceiver = @VisibleForTesting
internal val receiver: BroadcastReceiver =
object : BroadcastReceiver() { object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
when (intent.action) { when (intent.action) {
ACTION_DISMISSED_LOW_BATTERY -> updateSuppression(true) ACTION_DISMISSED_LOW_BATTERY -> updateSuppression(true)
ACTION_CLICKED_LOW_BATTERY -> { ACTION_CLICKED_LOW_BATTERY -> {
updateSuppression(true) updateSuppression(true)
// TODO(b/261584943): open USI device details page if (inputDeviceId == null) return
val args = Bundle()
args.putInt(KEY_DEVICE_INPUT_ID, inputDeviceId!!)
try {
context.startActivity(
Intent(ACTION_STYLUS_USI_DETAILS)
.putExtra(KEY_SETTINGS_FRAGMENT_ARGS, args)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} catch (e: ActivityNotFoundException) {
// In the rare scenario where the Settings app manifest doesn't contain
// the USI details activity, ignore the intent.
Log.e(
StylusUsiPowerUI::class.java.simpleName,
"Cannot open USI details page."
)
}
} }
} }
} }
@@ -179,7 +204,11 @@ constructor(
private val USI_NOTIFICATION_ID = R.string.stylus_battery_low_percentage private val USI_NOTIFICATION_ID = R.string.stylus_battery_low_percentage
private const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss" @VisibleForTesting const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss"
private const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click" @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

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2022 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 android.hardware.BatteryState
class FixedCapacityBatteryState(private val capacity: Float) : BatteryState() {
override fun getCapacity() = capacity
override fun getStatus() = 0
override fun isPresent() = true
}

View File

@@ -84,6 +84,13 @@ class StylusUsiPowerStartableTest : SysuiTestCase() {
verifyZeroInteractions(stylusManager) verifyZeroInteractions(stylusManager)
} }
@Test
fun start_initStylusUsiPowerUi() {
startable.start()
verify(stylusUsiPowerUi, times(1)).init()
}
@Test @Test
fun onStylusBluetoothConnected_refreshesNotification() { fun onStylusBluetoothConnected_refreshesNotification() {
startable.onStylusBluetoothConnected(STYLUS_DEVICE_ID, "ANY") startable.onStylusBluetoothConnected(STYLUS_DEVICE_ID, "ANY")
@@ -99,13 +106,21 @@ class StylusUsiPowerStartableTest : SysuiTestCase() {
} }
@Test @Test
fun onStylusUsiBatteryStateChanged_batteryPresent_refreshesNotification() { fun onStylusUsiBatteryStateChanged_batteryPresentValidCapacity_refreshesNotification() {
val batteryState = mock(BatteryState::class.java) val batteryState = FixedCapacityBatteryState(0.1f)
whenever(batteryState.isPresent).thenReturn(true)
startable.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState) startable.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState)
verify(stylusUsiPowerUi, times(1)).updateBatteryState(batteryState) verify(stylusUsiPowerUi, times(1)).updateBatteryState(STYLUS_DEVICE_ID, batteryState)
}
@Test
fun onStylusUsiBatteryStateChanged_batteryPresentInvalidCapacity_noop() {
val batteryState = FixedCapacityBatteryState(0f)
startable.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 123, batteryState)
verifyNoMoreInteractions(stylusUsiPowerUi)
} }
@Test @Test

View File

@@ -17,8 +17,11 @@
package com.android.systemui.stylus package com.android.systemui.stylus
import android.app.Notification import android.app.Notification
import android.hardware.BatteryState import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.hardware.input.InputManager import android.hardware.input.InputManager
import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.view.InputDevice import android.view.InputDevice
@@ -27,8 +30,10 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals import junit.framework.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
@@ -37,7 +42,10 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor import org.mockito.ArgumentCaptor
import org.mockito.Captor import org.mockito.Captor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.inOrder import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.verifyNoMoreInteractions
@@ -53,11 +61,16 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
@Captor lateinit var notificationCaptor: ArgumentCaptor<Notification> @Captor lateinit var notificationCaptor: ArgumentCaptor<Notification>
private lateinit var stylusUsiPowerUi: StylusUsiPowerUI private lateinit var stylusUsiPowerUi: StylusUsiPowerUI
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var contextSpy: Context
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
contextSpy = spy(mContext)
doNothing().whenever(contextSpy).startActivity(any())
whenever(handler.post(any())).thenAnswer { whenever(handler.post(any())).thenAnswer {
(it.arguments[0] as Runnable).run() (it.arguments[0] as Runnable).run()
true true
@@ -68,12 +81,20 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true) whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
// whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES") // whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES")
stylusUsiPowerUi = StylusUsiPowerUI(mContext, notificationManager, inputManager, handler) stylusUsiPowerUi = StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler)
broadcastReceiver = stylusUsiPowerUi.receiver
}
@Test
fun updateBatteryState_capacityZero_noop() {
stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0f))
verifyNoMoreInteractions(notificationManager)
} }
@Test @Test
fun updateBatteryState_capacityBelowThreshold_notifies() { fun updateBatteryState_capacityBelowThreshold_notifies() {
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
verify(notificationManager, times(1)) verify(notificationManager, times(1))
.notify(eq(R.string.stylus_battery_low_percentage), any()) .notify(eq(R.string.stylus_battery_low_percentage), any())
@@ -82,7 +103,7 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
@Test @Test
fun updateBatteryState_capacityAboveThreshold_cancelsNotificattion() { fun updateBatteryState_capacityAboveThreshold_cancelsNotificattion() {
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.8f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f))
verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage) verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
verifyNoMoreInteractions(notificationManager) verifyNoMoreInteractions(notificationManager)
@@ -90,8 +111,8 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
@Test @Test
fun updateBatteryState_existingNotification_capacityAboveThreshold_cancelsNotification() { fun updateBatteryState_existingNotification_capacityAboveThreshold_cancelsNotification() {
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.8f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.8f))
inOrder(notificationManager).let { inOrder(notificationManager).let {
it.verify(notificationManager, times(1)) it.verify(notificationManager, times(1))
@@ -103,8 +124,8 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
@Test @Test
fun updateBatteryState_existingNotification_capacityBelowThreshold_updatesNotification() { fun updateBatteryState_existingNotification_capacityBelowThreshold_updatesNotification() {
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.15f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.15f))
verify(notificationManager, times(2)) verify(notificationManager, times(2))
.notify(eq(R.string.stylus_battery_low_percentage), notificationCaptor.capture()) .notify(eq(R.string.stylus_battery_low_percentage), notificationCaptor.capture())
@@ -121,9 +142,9 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
@Test @Test
fun updateBatteryState_capacityAboveThenBelowThreshold_hidesThenShowsNotification() { fun updateBatteryState_capacityAboveThenBelowThreshold_hidesThenShowsNotification() {
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.5f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.5f))
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
inOrder(notificationManager).let { inOrder(notificationManager).let {
it.verify(notificationManager, times(1)) it.verify(notificationManager, times(1))
@@ -145,7 +166,7 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
@Test @Test
fun updateSuppression_existingNotification_cancelsNotification() { fun updateSuppression_existingNotification_cancelsNotification() {
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
stylusUsiPowerUi.updateSuppression(true) stylusUsiPowerUi.updateSuppression(true)
@@ -170,7 +191,7 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
@Test @Test
@Ignore("TODO(b/257936830): get bt address once input api available") @Ignore("TODO(b/257936830): get bt address once input api available")
fun refresh_hasConnectedBluetoothStylus_existingNotification_cancelsNotification() { fun refresh_hasConnectedBluetoothStylus_existingNotification_cancelsNotification() {
stylusUsiPowerUi.updateBatteryState(FixedCapacityBatteryState(0.1f)) stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0)) whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0))
stylusUsiPowerUi.refresh() stylusUsiPowerUi.refresh()
@@ -178,9 +199,27 @@ class StylusUsiPowerUiTest : SysuiTestCase() {
verify(notificationManager).cancel(R.string.stylus_battery_low_percentage) verify(notificationManager).cancel(R.string.stylus_battery_low_percentage)
} }
class FixedCapacityBatteryState(private val capacity: Float) : BatteryState() { @Test
override fun getCapacity() = capacity fun broadcastReceiver_clicked_hasInputDeviceId_startsUsiDetailsActivity() {
override fun getStatus() = 0 val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
override fun isPresent() = true val activityIntentCaptor = argumentCaptor<Intent>()
stylusUsiPowerUi.updateBatteryState(1, FixedCapacityBatteryState(0.15f))
broadcastReceiver.onReceive(contextSpy, intent)
verify(contextSpy, times(1)).startActivity(activityIntentCaptor.capture())
assertThat(activityIntentCaptor.value.action)
.isEqualTo(StylusUsiPowerUI.ACTION_STYLUS_USI_DETAILS)
val args =
activityIntentCaptor.value.getExtra(StylusUsiPowerUI.KEY_SETTINGS_FRAGMENT_ARGS)
as Bundle
assertThat(args.getInt(StylusUsiPowerUI.KEY_DEVICE_INPUT_ID)).isEqualTo(1)
}
@Test
fun broadcastReceiver_clicked_nullInputDeviceId_doesNotStartActivity() {
val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
broadcastReceiver.onReceive(contextSpy, intent)
verify(contextSpy, never()).startActivity(any())
} }
} }