Added ChargingBroadcastReceiver which can instantly update plugged in… (#525)

* Added ChargingBroadcastReceiver which can instantly update plugged in status

* Couple formatting changes noticed after pushing. Will squash this message

* Addressed PR Comments

* More PR comments!

* Pulled out logic into SensorUpdater

* Couple thigns I noticed after pushing... because of course I did.

* Removed unnecessary Binds
This commit is contained in:
James Yox 2020-03-29 12:23:11 -06:00 committed by GitHub
parent 4a9c6f0283
commit 825dd78f1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 141 additions and 66 deletions

View file

@ -46,6 +46,13 @@
android:resource="@xml/button_widget_info" />
</receiver>
<!-- Start things like SensorWorker on device boot -->
<receiver android:name=".HomeAssistantBootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<activity android:name=".widgets.ButtonWidgetConfigureActivity">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />

View file

@ -1,11 +1,15 @@
package io.homeassistant.companion.android
import android.app.Application
import android.content.Intent
import android.content.IntentFilter
import com.jakewharton.threetenabp.AndroidThreeTen
import com.lokalise.sdk.Lokalise
import io.homeassistant.companion.android.common.dagger.AppComponent
import io.homeassistant.companion.android.common.dagger.Graph
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.sensors.ChargingBroadcastReceiver
import io.homeassistant.companion.android.sensors.SensorWorker
class HomeAssistantApplication : Application(), GraphComponentAccessor {
@ -19,6 +23,18 @@ class HomeAssistantApplication : Application(), GraphComponentAccessor {
AndroidThreeTen.init(this)
graph = Graph(this, 0)
// Start the sensor worker if they start the app. The only other place we start this ia Boot BroadcastReceiver
SensorWorker.start(this)
// This will cause the sensor to be updated every time the OS broadcasts that a cable was plugged/unplugged.
// This should be nearly instantaneous allowing automations to fire immediately when a phone is plugged
// in or unplugged.
registerReceiver(
ChargingBroadcastReceiver(appComponent.integrationUseCase()), IntentFilter().apply {
addAction(Intent.ACTION_POWER_CONNECTED)
addAction(Intent.ACTION_POWER_DISCONNECTED)
}
)
}
override val appComponent: AppComponent

View file

@ -0,0 +1,20 @@
package io.homeassistant.companion.android
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import io.homeassistant.companion.android.sensors.SensorWorker
import io.homeassistant.companion.android.util.PermissionManager
/**
* This class will receive a broadcast intent when the device boots up. This allows us to start the sensor worker
* even if the user never starts the HomeAssistant app.
*/
class HomeAssistantBootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
SensorWorker.start(context)
PermissionManager.restartLocationTracking(context)
}
}
}

View file

@ -0,0 +1,5 @@
package io.homeassistant.companion.android
interface SensorUpdater {
suspend fun updateSensors()
}

View file

@ -19,7 +19,6 @@ import com.google.android.gms.location.LocationServices
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import io.homeassistant.companion.android.domain.integration.UpdateLocation
import io.homeassistant.companion.android.sensors.SensorWorker
import io.homeassistant.companion.android.util.PermissionManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@ -162,10 +161,6 @@ class LocationBroadcastReceiver : BroadcastReceiver() {
}
private fun sendLocationUpdate(location: Location, context: Context) {
// Anytime we send a location we should start the sensor worker.
// This is cause it to send sensors then restart the 15 minute interval.
SensorWorker.start(context)
Log.d(
TAG, "Last Location: " +
"\nCoords:(${location.latitude}, ${location.longitude})" +

View file

@ -16,7 +16,6 @@ import io.homeassistant.companion.android.onboarding.integration.MobileAppIntegr
import io.homeassistant.companion.android.onboarding.integration.MobileAppIntegrationListener
import io.homeassistant.companion.android.onboarding.manual.ManualSetupFragment
import io.homeassistant.companion.android.onboarding.manual.ManualSetupListener
import io.homeassistant.companion.android.sensors.SensorWorker
import io.homeassistant.companion.android.webview.WebViewActivity
class OnboardingActivity : AppCompatActivity(), DiscoveryListener, ManualSetupListener,
@ -90,7 +89,6 @@ class OnboardingActivity : AppCompatActivity(), DiscoveryListener, ManualSetupLi
}
override fun onIntegrationRegistrationComplete() {
SensorWorker.start(applicationContext)
startWebView()
}

View file

@ -117,7 +117,7 @@ class MobileAppIntegrationFragment : Fragment(), MobileAppIntegrationView {
}
override fun onDestroy() {
PermissionManager.restartLocationTracking(context!!, activity!!)
PermissionManager.restartLocationTracking(context!!)
presenter.onFinish()
super.onDestroy()
}

View file

@ -0,0 +1,52 @@
package io.homeassistant.companion.android.sensors
import android.content.Context
import android.util.Log
import io.homeassistant.companion.android.SensorUpdater
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
class AllSensorsUpdaterImpl(
private val integrationUseCase: IntegrationUseCase,
private val appContext: Context
) : SensorUpdater {
companion object {
private const val TAG = "AllSensorsUpdaterImpl"
}
override suspend fun updateSensors() {
val sensorManagers = mutableListOf(
BatterySensorManager(),
NetworkSensorManager()
)
if (integrationUseCase.isBackgroundTrackingEnabled()) {
sensorManagers.add(GeocodeSensorManager())
}
registerSensors(sensorManagers)
val success = integrationUseCase.updateSensors(
sensorManagers.flatMap { it.getSensors(appContext) }.toTypedArray()
)
// We failed to update a sensor, we should register all the sensors again.
if (!success) {
registerSensors(sensorManagers)
}
}
private suspend fun registerSensors(sensorManagers: List<SensorManager>) {
sensorManagers.flatMap {
it.getSensorRegistrations(appContext)
}.forEach {
// I want to call this async but because of the way we need to store the
// fact we have registered it we can't
try {
integrationUseCase.registerSensor(it)
} catch (e: Exception) {
Log.e(TAG, "Issue registering sensor: ${it.uniqueId}", e)
}
}
}
}

View file

@ -0,0 +1,23 @@
package io.homeassistant.companion.android.sensors
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class ChargingBroadcastReceiver(
private val integrationUseCase: IntegrationUseCase
) : BroadcastReceiver() {
private val ioScope = CoroutineScope(Dispatchers.IO)
private var updateJob: Job? = null
override fun onReceive(context: Context, intent: Intent?) {
updateJob?.cancel()
updateJob = ioScope.launch {
AllSensorsUpdaterImpl(integrationUseCase, context).updateSensors()
}
}
}

View file

@ -1,9 +1,10 @@
package io.homeassistant.companion.android.sensors
import dagger.Component
import io.homeassistant.companion.android.PresenterModule
import io.homeassistant.companion.android.common.dagger.AppComponent
@Component(dependencies = [AppComponent::class])
@Component(dependencies = [AppComponent::class], modules = [PresenterModule::class])
interface SensorComponent {
fun inject(worker: SensorWorker)

View file

@ -1,13 +1,14 @@
package io.homeassistant.companion.android.sensors
import android.content.Context
import android.util.Log
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import io.homeassistant.companion.android.SensorUpdater
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import java.util.concurrent.TimeUnit
@ -15,9 +16,10 @@ import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class SensorWorker(private val appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
class SensorWorker(
appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams) {
companion object {
private const val TAG = "SensorWorker"
fun start(context: Context) {
@ -27,61 +29,27 @@ class SensorWorker(private val appContext: Context, workerParams: WorkerParamete
val sensorWorker =
PeriodicWorkRequestBuilder<SensorWorker>(15, TimeUnit.MINUTES)
.setConstraints(constraints)
.addTag("sensors")
.build()
WorkManager.getInstance(context).cancelAllWorkByTag("sensors")
WorkManager.getInstance(context).enqueue(sensorWorker)
WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, sensorWorker)
}
}
@Inject
lateinit var integrationUseCase: IntegrationUseCase
val allSensorUpdater: SensorUpdater
init {
DaggerSensorComponent.builder()
.appComponent((appContext as GraphComponentAccessor).appComponent)
.build()
.inject(this)
allSensorUpdater = AllSensorsUpdaterImpl(integrationUseCase, applicationContext)
}
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
val sensorManagers = arrayListOf(
BatterySensorManager(),
NetworkSensorManager()
)
if (integrationUseCase.isBackgroundTrackingEnabled()) {
sensorManagers.add(GeocodeSensorManager())
}
registerSensors(sensorManagers)
val success = integrationUseCase.updateSensors(
sensorManagers.flatMap { it.getSensors(appContext) }.toTypedArray()
)
// We failed to update a sensor, we should register all the sensors again.
if (!success) {
registerSensors(sensorManagers)
}
allSensorUpdater.updateSensors()
Result.success()
}
private suspend fun registerSensors(sensorManagers: List<SensorManager>) {
sensorManagers.flatMap {
it.getSensorRegistrations(appContext)
}.forEach {
// I want to call this async but because of the way we need to store the
// fact we have registered it we can't
try {
integrationUseCase.registerSensor(it)
} catch (e: Exception) {
Log.e(TAG, "Issue registering sensor: ${it.uniqueId}", e)
}
}
}
}

View file

@ -14,7 +14,6 @@ import io.homeassistant.companion.android.DaggerPresenterComponent
import io.homeassistant.companion.android.PresenterModule
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.sensors.SensorWorker
import io.homeassistant.companion.android.settings.ssid.SsidDialogFragment
import io.homeassistant.companion.android.settings.ssid.SsidPreference
import io.homeassistant.companion.android.util.PermissionManager
@ -96,7 +95,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SettingsView {
if (!PermissionManager.hasLocationPermissions(context!!)) {
PermissionManager.requestLocationPermissions(this)
}
PermissionManager.restartLocationTracking(context!!, activity!!)
PermissionManager.restartLocationTracking(context!!)
}
override fun disableInternalConnection() {
@ -111,10 +110,6 @@ class SettingsFragment : PreferenceFragmentCompat(), SettingsView {
}
}
override fun restartSensorWorker() {
SensorWorker.start(context!!)
}
override fun onDisplayPreferenceDialog(preference: Preference) {
if (preference is SsidPreference) {
// check if dialog is already showing
@ -138,7 +133,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SettingsView {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (PermissionManager.validateLocationPermissions(requestCode, permissions, grantResults)) {
PermissionManager.restartLocationTracking(context!!, activity!!)
PermissionManager.restartLocationTracking(context!!)
} else {
// If we don't have permissions, don't let them in!
findPreference<SwitchPreference>("location_zone")!!.isChecked = false

View file

@ -6,6 +6,4 @@ interface SettingsView {
fun disableInternalConnection()
fun enableInternalConnection()
fun restartSensorWorker()
}

View file

@ -1,7 +1,6 @@
package io.homeassistant.companion.android.util
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@ -65,11 +64,11 @@ class PermissionManager {
fragment.requestPermissions(getLocationPermissionArray(), LOCATION_REQUEST_CODE)
}
fun restartLocationTracking(context: Context, activity: Activity) {
fun restartLocationTracking(context: Context) {
val intent = Intent(context, LocationBroadcastReceiver::class.java)
intent.action = LocationBroadcastReceiver.ACTION_REQUEST_LOCATION_UPDATES
activity.sendBroadcast(intent)
context.sendBroadcast(intent)
}
}
}

View file

@ -38,7 +38,6 @@ import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.background.LocationBroadcastReceiver
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.onboarding.OnboardingActivity
import io.homeassistant.companion.android.sensors.SensorWorker
import io.homeassistant.companion.android.settings.SettingsActivity
import io.homeassistant.companion.android.util.PermissionManager
import io.homeassistant.companion.android.util.isStarted
@ -89,7 +88,6 @@ class WebViewActivity : AppCompatActivity(), io.homeassistant.companion.android.
val intent = Intent(this, LocationBroadcastReceiver::class.java)
intent.action = LocationBroadcastReceiver.ACTION_REQUEST_LOCATION_UPDATES
sendBroadcast(intent)
SensorWorker.start(applicationContext)
if (BuildConfig.DEBUG) {
WebView.setWebContentsDebuggingEnabled(true)