Browse Source

Battery charging status API

Battery charing checks are currently performed using evernote jobs API.
This ties our power management to rather unrelated 3rd party library.

This change provides battery check API via app power management
abstraction. It will also allow to access native battery API on
Android Lollipop and newer in the future.

This change does not migrate any current code to new API.

Signed-off-by: Chris Narkiewicz <hello@ezaquarii.com>
Chris Narkiewicz 5 năm trước cách đây
mục cha
commit
5923737b29

+ 1 - 0
src/main/java/com/nextcloud/client/device/DeviceModule.kt

@@ -33,6 +33,7 @@ class DeviceModule {
     fun powerManagementService(context: Context): PowerManagementService {
         val platformPowerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
         return PowerManagementServiceImpl(
+            context = context,
             powerManager = platformPowerManager,
             deviceInfo = DeviceInfo()
         )

+ 5 - 0
src/main/java/com/nextcloud/client/device/PowerManagementService.kt

@@ -43,4 +43,9 @@ interface PowerManagementService {
      * @return true if workaround is required, false otherwise
      */
     val isPowerSavingExclusionAvailable: Boolean
+
+    /**
+     * Checks if battery is charging using any hardware supported means.
+     */
+    val isBatteryCharging: Boolean
 }

+ 18 - 0
src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt

@@ -22,10 +22,15 @@
 package com.nextcloud.client.device
 
 import android.annotation.TargetApi
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.BatteryManager
 import android.os.Build
 import android.os.PowerManager
 
 internal class PowerManagementServiceImpl(
+    private val context: Context,
     private val powerManager: PowerManager,
     private val deviceInfo: DeviceInfo = DeviceInfo()
 ) : PowerManagementService {
@@ -50,4 +55,17 @@ internal class PowerManagementServiceImpl(
 
     override val isPowerSavingExclusionAvailable: Boolean
         get() = deviceInfo.vendor in OVERLY_AGGRESSIVE_POWER_SAVING_VENDORS
+
+    override val isBatteryCharging: Boolean
+        get() {
+            val intent: Intent? = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
+            val plugged = intent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) ?: 0
+            return when {
+                plugged == BatteryManager.BATTERY_PLUGGED_USB -> true
+                plugged == BatteryManager.BATTERY_PLUGGED_AC -> true
+                deviceInfo.apiLevel >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
+                    plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS -> true
+                else -> false
+            }
+        }
 }

+ 158 - 55
src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt

@@ -21,84 +21,187 @@
 
 package com.nextcloud.client.device
 
+import android.content.Context
+import android.content.Intent
+import android.os.BatteryManager
 import android.os.Build
 import android.os.PowerManager
-import com.nhaarman.mockitokotlin2.never
-import com.nhaarman.mockitokotlin2.verify
-import com.nhaarman.mockitokotlin2.whenever
+import com.nhaarman.mockitokotlin2.*
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Suite
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
-import org.mockito.junit.MockitoJUnitRunner
 
-@RunWith(MockitoJUnitRunner::class)
+@RunWith(Suite::class)
+@Suite.SuiteClasses(
+    TestPowerManagementService.PowerSaveMode::class,
+    TestPowerManagementService.BatteryCharging::class
+)
 class TestPowerManagementService {
 
-    @Mock
-    lateinit var platformPowerManager: PowerManager
+    abstract class Base {
+        @Mock
+        lateinit var context: Context
 
-    @Mock
-    lateinit var deviceInfo: DeviceInfo
+        @Mock
+        lateinit var platformPowerManager: PowerManager
 
-    private lateinit var powerManagementService: PowerManagementServiceImpl
+        @Mock
+        lateinit var deviceInfo: DeviceInfo
 
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        powerManagementService = PowerManagementServiceImpl(
-            powerManager = platformPowerManager,
-            deviceInfo = deviceInfo
-        )
-    }
+        internal lateinit var powerManagementService: PowerManagementServiceImpl
 
-    @Test
-    fun `power saving queries power manager on API 21+`() {
-        // GIVEN
-        //      API level >= 21
-        //      power save mode is on
-        whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.LOLLIPOP)
-        whenever(platformPowerManager.isPowerSaveMode).thenReturn(true)
-
-        // WHEN
-        //      power save mode is checked
-        // THEN
-        //      power save mode is enabled
-        //      state is obtained from platform power manager
-        assertTrue(powerManagementService.isPowerSavingEnabled)
-        verify(platformPowerManager).isPowerSaveMode
+        @Before
+        fun setUpBase() {
+            MockitoAnnotations.initMocks(this)
+            powerManagementService = PowerManagementServiceImpl(
+                context = context,
+                powerManager = platformPowerManager,
+                deviceInfo = deviceInfo
+            )
+        }
     }
 
-    @Test
-    fun `power saving is not available below API 21`() {
-        // GIVEN
-        //      API level <21
-        whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.KITKAT)
+    class PowerSaveMode : Base() {
 
-        // WHEN
-        //      power save mode is checked
+        @Test
+        fun `power saving queries power manager on API 21+`() {
+            // GIVEN
+            //      API level >= 21
+            //      power save mode is on
+            whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.LOLLIPOP)
+            whenever(platformPowerManager.isPowerSaveMode).thenReturn(true)
 
-        // THEN
-        //      power save mode is disabled
-        //      power manager is not queried
-        assertFalse(powerManagementService.isPowerSavingEnabled)
-        verify(platformPowerManager, never()).isPowerSaveMode
-    }
+            // WHEN
+            //      power save mode is checked
+            // THEN
+            //      power save mode is enabled
+            //      state is obtained from platform power manager
+            assertTrue(powerManagementService.isPowerSavingEnabled)
+            verify(platformPowerManager).isPowerSaveMode
+        }
+
+        @Test
+        fun `power saving is not available below API 21`() {
+            // GIVEN
+            //      API level <21
+            whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.KITKAT)
+
+            // WHEN
+            //      power save mode is checked
+
+            // THEN
+            //      power save mode is disabled
+            //      power manager is not queried
+            assertFalse(powerManagementService.isPowerSavingEnabled)
+            verify(platformPowerManager, never()).isPowerSaveMode
+        }
+
+        @Test
+        fun `power save exclusion is available for flagged vendors`() {
+            for (vendor in PowerManagementServiceImpl.OVERLY_AGGRESSIVE_POWER_SAVING_VENDORS) {
+                whenever(deviceInfo.vendor).thenReturn(vendor)
+                assertTrue("Vendor $vendor check failed", powerManagementService.isPowerSavingExclusionAvailable)
+            }
+        }
 
-    @Test
-    fun `power save exclusion is available for flagged vendors`() {
-        for (vendor in PowerManagementServiceImpl.OVERLY_AGGRESSIVE_POWER_SAVING_VENDORS) {
-            whenever(deviceInfo.vendor).thenReturn(vendor)
-            assertTrue("Vendor $vendor check failed", powerManagementService.isPowerSavingExclusionAvailable)
+        @Test
+        fun `power save exclusion is not available for other vendors`() {
+            whenever(deviceInfo.vendor).thenReturn("some_other_nice_vendor")
+            assertFalse(powerManagementService.isPowerSavingExclusionAvailable)
         }
     }
 
-    @Test
-    fun `power save exclusion is not available for other vendors`() {
-        whenever(deviceInfo.vendor).thenReturn("some_other_nice_vendor")
-        assertFalse(powerManagementService.isPowerSavingExclusionAvailable)
+    class BatteryCharging : Base() {
+
+        val mockStickyBatteryStatusIntent: Intent = mock()
+
+        @Before
+        fun setUp() {
+            whenever(context.registerReceiver(anyOrNull(), anyOrNull())).thenReturn(mockStickyBatteryStatusIntent)
+        }
+
+        @Test
+        fun `battery charging status on API 17+`() {
+            // GIVEN
+            //      device has API level 17+
+            //      battery status sticky intent is available
+            whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.JELLY_BEAN_MR1)
+            val powerSources = setOf(
+                BatteryManager.BATTERY_PLUGGED_AC,
+                BatteryManager.BATTERY_PLUGGED_USB,
+                BatteryManager.BATTERY_PLUGGED_WIRELESS
+            )
+
+            for (row in powerSources) {
+                // WHEN
+                //      device is charging using supported power source
+                whenever(mockStickyBatteryStatusIntent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any()))
+                    .thenReturn(row)
+
+                // THEN
+                //      charging flag is true
+                assertTrue(powerManagementService.isBatteryCharging)
+            }
+        }
+
+        @Test
+        fun `battery charging status on API 14-16`() {
+            // GIVEN
+            //      device has API level 16 or below
+            //      battery status sticky intent is available
+            whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+            val powerSources = setOf(
+                BatteryManager.BATTERY_PLUGGED_AC,
+                BatteryManager.BATTERY_PLUGGED_USB
+            )
+
+            for (row in powerSources) {
+                // WHEN
+                //      device is charging using AC or USB
+                whenever(mockStickyBatteryStatusIntent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any()))
+                    .thenReturn(row)
+
+                // THEN
+                //      charging flag is true
+                assertTrue(powerManagementService.isBatteryCharging)
+            }
+        }
+
+        @Test
+        fun `wireless charging is not supported in API 14-16`() {
+            // GIVEN
+            //      device has API level 16 or below
+            //      battery status sticky intent is available
+            whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.JELLY_BEAN)
+
+            // WHEN
+            //      spurious wireless power source is returned
+            whenever(mockStickyBatteryStatusIntent.getIntExtra(eq(BatteryManager.EXTRA_PLUGGED), any()))
+                .thenReturn(BatteryManager.BATTERY_PLUGGED_WIRELESS)
+
+            // THEN
+            //      power source value is ignored on this API level
+            //      charging flag is false
+            assertFalse(powerManagementService.isBatteryCharging)
+        }
+
+        @Test
+        fun `battery status sticky intent is not available`() {
+            // GIVEN
+            //      device has API level P or below
+            //      battery status sticky intent is NOT available
+            whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.P)
+            whenever(context.registerReceiver(anyOrNull(), anyOrNull())).thenReturn(null)
+
+            // THEN
+            //     charging flag is false
+            assertFalse(powerManagementService.isBatteryCharging)
+            verify(context).registerReceiver(anyOrNull(), any())
+        }
     }
 }