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

This commit is contained in:
Vania Januar
2023-01-12 10:06:21 +00:00
committed by Android (Google) Code Review
5 changed files with 141 additions and 32 deletions

View File

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

View File

@@ -18,17 +18,21 @@ package com.android.systemui.stylus
import android.Manifest
import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.hardware.BatteryState
import android.hardware.input.InputManager
import android.os.Bundle
import android.os.Handler
import android.os.UserHandle
import android.util.Log
import android.view.InputDevice
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -53,6 +57,7 @@ constructor(
// These values must only be accessed on the handler.
private var batteryCapacity = 1.0f
private var suppressed = false
private var inputDeviceId: Int? = null
fun init() {
val filter =
@@ -87,10 +92,12 @@ constructor(
}
}
fun updateBatteryState(batteryState: BatteryState) {
fun updateBatteryState(deviceId: Int, batteryState: BatteryState) {
handler.post updateBattery@{
if (batteryState.capacity == batteryCapacity) return@updateBattery
if (batteryState.capacity == batteryCapacity || batteryState.capacity <= 0f)
return@updateBattery
inputDeviceId = deviceId
batteryCapacity = batteryState.capacity
refresh()
}
@@ -150,23 +157,41 @@ constructor(
}
private fun getPendingBroadcast(action: String): PendingIntent? {
return PendingIntent.getBroadcastAsUser(
return PendingIntent.getBroadcast(
context,
0,
Intent(action),
Intent(action).setPackage(context.packageName),
PendingIntent.FLAG_IMMUTABLE,
UserHandle.CURRENT
)
}
private val receiver: BroadcastReceiver =
@VisibleForTesting
internal val receiver: BroadcastReceiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
ACTION_DISMISSED_LOW_BATTERY -> updateSuppression(true)
ACTION_CLICKED_LOW_BATTERY -> {
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 const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss"
private const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click"
@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

@@ -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)
}
@Test
fun start_initStylusUsiPowerUi() {
startable.start()
verify(stylusUsiPowerUi, times(1)).init()
}
@Test
fun onStylusBluetoothConnected_refreshesNotification() {
startable.onStylusBluetoothConnected(STYLUS_DEVICE_ID, "ANY")
@@ -99,13 +106,21 @@ class StylusUsiPowerStartableTest : SysuiTestCase() {
}
@Test
fun onStylusUsiBatteryStateChanged_batteryPresent_refreshesNotification() {
val batteryState = mock(BatteryState::class.java)
whenever(batteryState.isPresent).thenReturn(true)
fun onStylusUsiBatteryStateChanged_batteryPresentValidCapacity_refreshesNotification() {
val batteryState = FixedCapacityBatteryState(0.1f)
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

View File

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