Optimization/fix to isBatteryLowAndNotCharging
This patch moves the initial setup of isBatteryLowAndNotCharging
earlier which means it can be done in parallel with
createWatchFace. In addition we now listen to ACTION_POWER_DISCONNECTED
Test: Manual testing
Change-Id: I0a2660fffc90a798b6c7c7949714a8281b8a520b
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsObserver.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsObserver.kt
index eead284..34e1442 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsObserver.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsObserver.kt
@@ -31,6 +31,8 @@
private val deferredWatchFaceImpl: Deferred<WatchFaceImpl>,
private val uiThreadCoroutineScope: CoroutineScope
) : BroadcastsReceiver.BroadcastEventObserver {
+ private var batteryLow: Boolean? = null
+ private var charging: Boolean? = null
override fun onActionTimeTick() {
if (!watchState.isAmbient.value!!) {
@@ -51,17 +53,29 @@
}
override fun onActionBatteryLow() {
- updateBatteryLowAndNotChargingStatus(true)
+ batteryLow = true
+ if (charging == false) {
+ updateBatteryLowAndNotChargingStatus(true)
+ }
}
override fun onActionBatteryOkay() {
+ batteryLow = false
updateBatteryLowAndNotChargingStatus(false)
}
override fun onActionPowerConnected() {
+ charging = true
updateBatteryLowAndNotChargingStatus(false)
}
+ override fun onActionPowerDisconnected() {
+ charging = false
+ if (batteryLow == true) {
+ updateBatteryLowAndNotChargingStatus(true)
+ }
+ }
+
override fun onMockTime(intent: Intent) {
uiThreadCoroutineScope.launch {
deferredWatchFaceImpl.await().onMockTime(intent)
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
index 38d60a5..6411050 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
@@ -20,8 +20,10 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.os.BatteryManager
import androidx.annotation.RestrictTo
import androidx.annotation.UiThread
+import androidx.wear.watchface.BroadcastsReceiver.BroadcastEventObserver
/**
* This class decouples [BroadcastEventObserver]s from the actual broadcast event receivers to make
@@ -59,11 +61,23 @@
@UiThread
public fun onActionPowerConnected()
+ /** Called when we receive [Intent.ACTION_POWER_DISCONNECTED]. */
+ @UiThread
+ public fun onActionPowerDisconnected()
+
/** Called when we receive [WatchFaceImpl.MOCK_TIME_INTENT]. */
@UiThread
public fun onMockTime(intent: Intent)
}
+ companion object {
+ // The threshold used to judge whether the battery is low during initialization. Ideally
+ // we would use the threshold for Intent.ACTION_BATTERY_LOW but it's not documented or
+ // available programmatically. The value below is the default but it could be overridden
+ // by OEMs.
+ internal const val INITIAL_LOW_BATTERY_THRESHOLD = 15f
+ }
+
internal val actionTimeTickReceiver: BroadcastReceiver = object : BroadcastReceiver() {
@SuppressWarnings("SyntheticAccessor")
override fun onReceive(context: Context, intent: Intent) {
@@ -104,6 +118,13 @@
}
}
+ internal val actionPowerDisconnectedReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ @SuppressWarnings("SyntheticAccessor")
+ override fun onReceive(context: Context, intent: Intent) {
+ observer.onActionPowerDisconnected()
+ }
+ }
+
internal val mockTimeReceiver: BroadcastReceiver = object : BroadcastReceiver() {
@SuppressWarnings("SyntheticAccessor")
override fun onReceive(context: Context, intent: Intent) {
@@ -127,9 +148,35 @@
actionPowerConnectedReceiver,
IntentFilter(Intent.ACTION_POWER_CONNECTED)
)
+ context.registerReceiver(
+ actionPowerDisconnectedReceiver,
+ IntentFilter(Intent.ACTION_POWER_DISCONNECTED)
+ )
context.registerReceiver(mockTimeReceiver, IntentFilter(WatchFaceImpl.MOCK_TIME_INTENT))
}
+ /** Called to send observers initial battery state in advance of receiving any broadcasts. */
+ internal fun processBatteryStatus(batteryStatus: Intent?) {
+ val status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
+ if (status == BatteryManager.BATTERY_STATUS_CHARGING ||
+ status == BatteryManager.BATTERY_STATUS_FULL) {
+ observer.onActionPowerConnected()
+ } else {
+ observer.onActionPowerDisconnected()
+ }
+
+ val batteryPercent: Float = batteryStatus?.let { intent ->
+ val level: Int = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
+ val scale: Int = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
+ level * 100 / scale.toFloat()
+ } ?: 100.0f
+ if (batteryPercent < INITIAL_LOW_BATTERY_THRESHOLD) {
+ observer.onActionBatteryLow()
+ } else {
+ observer.onActionBatteryOkay()
+ }
+ }
+
public fun onDestroy() {
context.unregisterReceiver(actionTimeTickReceiver)
context.unregisterReceiver(actionTimeZoneReceiver)
@@ -139,4 +186,4 @@
context.unregisterReceiver(actionPowerConnectedReceiver)
context.unregisterReceiver(mockTimeReceiver)
}
-}
\ No newline at end of file
+}
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index c3b16a6..c8131ae 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -21,11 +21,9 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.IntentFilter
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Rect
-import android.os.BatteryManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
@@ -57,7 +55,6 @@
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@@ -417,7 +414,7 @@
@get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
public var complicationSlotsManager: ComplicationSlotsManager,
- private val broadcastsObserver: BroadcastsObserver,
+ internal val broadcastsObserver: BroadcastsObserver,
internal var broadcastsReceiver: BroadcastsReceiver?
) {
internal companion object {
@@ -446,12 +443,6 @@
// clamp the framerate to a maximum of 10fps to conserve power.
internal const val MAX_LOW_POWER_INTERACTIVE_UPDATE_RATE_MS = 100L
- // The threshold used to judge whether the battery is low during initialization. Ideally
- // we would use the threshold for Intent.ACTION_BATTERY_LOW but it's not documented or
- // available programmatically. The value below is the default but it could be overridden
- // by OEMs.
- internal const val INITIAL_LOW_BATTERY_THRESHOLD = 15.0f
-
// Number of milliseconds before the target draw time for the delayed task to run and post a
// choreographer frame. This is necessary when rendering at less than 60 fps to make sure we
// post the choreographer frame in time to for us to render in the desired frame.
@@ -616,12 +607,6 @@
watchFaceHostApi.updateContentDescriptionLabels()
}
- setIsBatteryLowAndNotChargingFromBatteryStatus(
- IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { iFilter ->
- watchFaceHostApi.getContext().registerReceiver(null, iFilter)
- }
- )
-
if (!watchState.isHeadless) {
WatchFace.registerEditorDelegate(componentName, WFEditorDelegate())
}
@@ -735,20 +720,6 @@
}
}
- internal fun setIsBatteryLowAndNotChargingFromBatteryStatus(batteryStatus: Intent?) {
- val status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
- val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
- status == BatteryManager.BATTERY_STATUS_FULL
- val batteryPercent: Float = batteryStatus?.let { intent ->
- val level: Int = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
- val scale: Int = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
- level * 100 / scale.toFloat()
- } ?: 100.0f
- val isBatteryLowAndNotCharging = watchState.isBatteryLowAndNotCharging as MutableStateFlow
- isBatteryLowAndNotCharging.value =
- (batteryPercent < INITIAL_LOW_BATTERY_THRESHOLD) && !isCharging
- }
-
/** Called by the system in response to remote configuration. */
@UiThread
internal fun onSetStyleInternal(style: UserStyle) {
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 5be8275..d814da5 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -20,6 +20,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.IntentFilter
import android.graphics.Canvas
import android.graphics.Rect
import android.os.Build
@@ -1496,7 +1497,13 @@
if (watchState.isHeadless) {
null
} else {
- BroadcastsReceiver(_context, broadcastsObserver)
+ BroadcastsReceiver(_context, broadcastsObserver).apply {
+ processBatteryStatus(
+ IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { iFilter ->
+ _context.registerReceiver(null, iFilter)
+ }
+ )
+ }
}
}
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index b7acfd5..f8a1c6d 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -2432,7 +2432,7 @@
}
@Test
- public fun setIsBatteryLowAndNotChargingFromBatteryStatus() {
+ public fun isBatteryLowAndNotCharging_modified_by_broadcasts() {
initWallpaperInteractiveWatchFaceInstance(
WatchFaceType.ANALOG,
emptyList(),
@@ -2451,7 +2451,41 @@
)
)
- watchFaceImpl.setIsBatteryLowAndNotChargingFromBatteryStatus(
+ watchFaceImpl.broadcastsObserver.onActionPowerConnected()
+ watchFaceImpl.broadcastsObserver.onActionBatteryOkay()
+ assertFalse(watchState.isBatteryLowAndNotCharging.value!!)
+
+ watchFaceImpl.broadcastsObserver.onActionBatteryLow()
+ assertFalse(watchState.isBatteryLowAndNotCharging.value!!)
+
+ watchFaceImpl.broadcastsObserver.onActionPowerDisconnected()
+ assertTrue(watchState.isBatteryLowAndNotCharging.value!!)
+
+ watchFaceImpl.broadcastsObserver.onActionBatteryOkay()
+ assertFalse(watchState.isBatteryLowAndNotCharging.value!!)
+ }
+
+ @Test
+ public fun processBatteryStatus() {
+ initWallpaperInteractiveWatchFaceInstance(
+ WatchFaceType.ANALOG,
+ emptyList(),
+ UserStyleSchema(emptyList()),
+ WallpaperInteractiveWatchFaceInstanceParams(
+ "interactiveInstanceId",
+ DeviceConfig(
+ false,
+ false,
+ 0,
+ 0
+ ),
+ WatchUiState(false, 0),
+ UserStyle(emptyMap()).toWireFormat(),
+ null
+ )
+ )
+
+ watchFaceImpl.broadcastsReceiver!!.processBatteryStatus(
Intent().apply {
putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING)
putExtra(BatteryManager.EXTRA_LEVEL, 0)
@@ -2460,7 +2494,7 @@
)
assertTrue(watchState.isBatteryLowAndNotCharging.value!!)
- watchFaceImpl.setIsBatteryLowAndNotChargingFromBatteryStatus(
+ watchFaceImpl.broadcastsReceiver!!.processBatteryStatus(
Intent().apply {
putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING)
putExtra(BatteryManager.EXTRA_LEVEL, 0)
@@ -2469,7 +2503,7 @@
)
assertFalse(watchState.isBatteryLowAndNotCharging.value!!)
- watchFaceImpl.setIsBatteryLowAndNotChargingFromBatteryStatus(
+ watchFaceImpl.broadcastsReceiver!!.processBatteryStatus(
Intent().apply {
putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING)
putExtra(BatteryManager.EXTRA_LEVEL, 80)
@@ -2478,10 +2512,10 @@
)
assertFalse(watchState.isBatteryLowAndNotCharging.value!!)
- watchFaceImpl.setIsBatteryLowAndNotChargingFromBatteryStatus(Intent())
+ watchFaceImpl.broadcastsReceiver!!.processBatteryStatus(Intent())
assertFalse(watchState.isBatteryLowAndNotCharging.value!!)
- watchFaceImpl.setIsBatteryLowAndNotChargingFromBatteryStatus(null)
+ watchFaceImpl.broadcastsReceiver!!.processBatteryStatus(null)
assertFalse(watchState.isBatteryLowAndNotCharging.value!!)
}