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:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user