From 1aa56166c74021c23e7456f4da160c71d51a03f0 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Wed, 22 Nov 2023 10:12:47 -0800 Subject: [PATCH] Catch potential play services errors (#4019) --- .../location/HighAccuracyLocationService.kt | 7 +- .../onboarding/WearOnboardingListener.kt | 16 ++- .../android/sensors/ActivitySensorManager.kt | 78 +++++----- .../sensors/AndroidAutoSensorManager.kt | 8 +- .../android/sensors/GeocodeSensorManager.kt | 15 +- .../android/sensors/LocationSensorManager.kt | 135 +++++++++++------- .../settings/wear/SettingsWearActivity.kt | 43 ++++-- .../settings/wear/SettingsWearViewModel.kt | 46 ++++-- 8 files changed, 224 insertions(+), 124 deletions(-) diff --git a/app/src/full/java/io/homeassistant/companion/android/location/HighAccuracyLocationService.kt b/app/src/full/java/io/homeassistant/companion/android/location/HighAccuracyLocationService.kt index e0f8a6e30..aa499780e 100644 --- a/app/src/full/java/io/homeassistant/companion/android/location/HighAccuracyLocationService.kt +++ b/app/src/full/java/io/homeassistant/companion/android/location/HighAccuracyLocationService.kt @@ -194,7 +194,12 @@ class HighAccuracyLocationService : Service() { .setPriority(Priority.PRIORITY_HIGH_ACCURACY) .build() - fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this) + fusedLocationProviderClient = try { + LocationServices.getFusedLocationProviderClient(this) + } catch (e: Exception) { + Log.e(TAG, "Unable to get fused location provider client", e) + null + } fusedLocationProviderClient?.requestLocationUpdates(request, getLocationUpdateIntent()) } } diff --git a/app/src/full/java/io/homeassistant/companion/android/onboarding/WearOnboardingListener.kt b/app/src/full/java/io/homeassistant/companion/android/onboarding/WearOnboardingListener.kt index 622f32b94..079d2bb66 100644 --- a/app/src/full/java/io/homeassistant/companion/android/onboarding/WearOnboardingListener.kt +++ b/app/src/full/java/io/homeassistant/companion/android/onboarding/WearOnboardingListener.kt @@ -16,6 +16,10 @@ import javax.inject.Inject @SuppressLint("VisibleForTests") // https://issuetracker.google.com/issues/239451111 class WearOnboardingListener : WearableListenerService() { + companion object { + private const val TAG = "WearOnboardingListener" + } + @Inject lateinit var serverManager: ServerManager @@ -41,8 +45,16 @@ class WearOnboardingListener : WearableListenerService() { setUrgent() asPutDataRequest() } - Wearable.getDataClient(this@WearOnboardingListener).putDataItem(putDataReq).addOnCompleteListener { - Log.d("WearOnboardingListener", "sendHomeAssistantInstance: ${if (it.isSuccessful) "success" else "failed"}") + try { + Wearable.getDataClient(this@WearOnboardingListener).putDataItem(putDataReq) + .addOnCompleteListener { + Log.d( + TAG, + "sendHomeAssistantInstance: ${if (it.isSuccessful) "success" else "failed"}" + ) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to send home assistant instance", e) } } } diff --git a/app/src/full/java/io/homeassistant/companion/android/sensors/ActivitySensorManager.kt b/app/src/full/java/io/homeassistant/companion/android/sensors/ActivitySensorManager.kt index bea7a9d62..43c4846e6 100644 --- a/app/src/full/java/io/homeassistant/companion/android/sensors/ActivitySensorManager.kt +++ b/app/src/full/java/io/homeassistant/companion/android/sensors/ActivitySensorManager.kt @@ -227,7 +227,11 @@ class ActivitySensorManager : BroadcastReceiver(), SensorManager { Log.d(TAG, "Registering for activity updates.") val fastUpdate = SensorReceiverBase.shouldDoFastUpdates(context) - actReg.requestActivityUpdates(TimeUnit.MINUTES.toMillis(if (fastUpdate) 1 else 2), pendingIntent) + try { + actReg.requestActivityUpdates(TimeUnit.MINUTES.toMillis(if (fastUpdate) 1 else 2), pendingIntent) + } catch (e: Exception) { + Log.e(TAG, "Unable to register for activity updates", e) + } } if (( isEnabled(context, sleepConfidence) || isEnabled( @@ -238,47 +242,43 @@ class ActivitySensorManager : BroadcastReceiver(), SensorManager { ) { val pendingIntent = getSleepPendingIntent(context) Log.d(TAG, "Registering for sleep updates") - val task = when { - ( - isEnabled(context, sleepConfidence) && !isEnabled( - context, - sleepSegment - ) - ) -> { - Log.d(TAG, "Registering for sleep confidence updates only") - ActivityRecognition.getClient(context).requestSleepSegmentUpdates( - pendingIntent, - SleepSegmentRequest(SleepSegmentRequest.CLASSIFY_EVENTS_ONLY) - ) + try { + val task = when { + (isEnabled(context, sleepConfidence) && !isEnabled(context, sleepSegment)) -> { + Log.d(TAG, "Registering for sleep confidence updates only") + ActivityRecognition.getClient(context).requestSleepSegmentUpdates( + pendingIntent, + SleepSegmentRequest(SleepSegmentRequest.CLASSIFY_EVENTS_ONLY) + ) + } + + (!isEnabled(context, sleepConfidence) && isEnabled(context, sleepSegment)) -> { + Log.d(TAG, "Registering for sleep segment updates only") + ActivityRecognition.getClient(context).requestSleepSegmentUpdates( + pendingIntent, + SleepSegmentRequest(SleepSegmentRequest.SEGMENT_EVENTS_ONLY) + ) + } + + else -> { + Log.d(TAG, "Registering for both sleep confidence and segment updates") + ActivityRecognition.getClient(context).requestSleepSegmentUpdates( + pendingIntent, + SleepSegmentRequest.getDefaultSleepSegmentRequest() + ) + } } - ( - !isEnabled(context, sleepConfidence) && isEnabled( - context, - sleepSegment - ) - ) -> { - Log.d(TAG, "Registering for sleep segment updates only") - ActivityRecognition.getClient(context).requestSleepSegmentUpdates( - pendingIntent, - SleepSegmentRequest(SleepSegmentRequest.SEGMENT_EVENTS_ONLY) - ) + task.addOnSuccessListener { + Log.d(TAG, "Successfully registered for sleep updates") + sleepRegistration = true } - else -> { - Log.d(TAG, "Registering for both sleep confidence and segment updates") - ActivityRecognition.getClient(context).requestSleepSegmentUpdates( - pendingIntent, - SleepSegmentRequest.getDefaultSleepSegmentRequest() - ) + task.addOnFailureListener { + Log.e(TAG, "Failed to register for sleep updates", it) + ActivityRecognition.getClient(context).removeSleepSegmentUpdates(pendingIntent) + sleepRegistration = false } - } - task.addOnSuccessListener { - Log.d(TAG, "Successfully registered for sleep updates") - sleepRegistration = true - } - task.addOnFailureListener { - Log.e(TAG, "Failed to register for sleep updates", it) - ActivityRecognition.getClient(context).removeSleepSegmentUpdates(pendingIntent) - sleepRegistration = false + } catch (e: Exception) { + Log.e(TAG, "Unable to register for sleep updates", e) } } } diff --git a/app/src/full/java/io/homeassistant/companion/android/sensors/AndroidAutoSensorManager.kt b/app/src/full/java/io/homeassistant/companion/android/sensors/AndroidAutoSensorManager.kt index 88b3503f2..2d899c334 100644 --- a/app/src/full/java/io/homeassistant/companion/android/sensors/AndroidAutoSensorManager.kt +++ b/app/src/full/java/io/homeassistant/companion/android/sensors/AndroidAutoSensorManager.kt @@ -2,6 +2,7 @@ package io.homeassistant.companion.android.sensors import android.content.Context import android.os.Build +import android.util.Log import androidx.car.app.connection.CarConnection import androidx.lifecycle.Observer import io.homeassistant.companion.android.common.sensors.SensorManager @@ -56,7 +57,12 @@ class AndroidAutoSensorManager : SensorManager, Observer { } CoroutineScope(Dispatchers.Main + Job()).launch { if (carConnection == null) { - carConnection = CarConnection(context.applicationContext) + carConnection = try { + CarConnection(context.applicationContext) + } catch (e: Exception) { + Log.e(TAG, "Failed to get car connection", e) + null + } } carConnection?.type?.observeForever(this@AndroidAutoSensorManager) } diff --git a/app/src/full/java/io/homeassistant/companion/android/sensors/GeocodeSensorManager.kt b/app/src/full/java/io/homeassistant/companion/android/sensors/GeocodeSensorManager.kt index 725d0eca3..fc2b16647 100644 --- a/app/src/full/java/io/homeassistant/companion/android/sensors/GeocodeSensorManager.kt +++ b/app/src/full/java/io/homeassistant/companion/android/sensors/GeocodeSensorManager.kt @@ -75,7 +75,12 @@ class GeocodeSensorManager : SensorManager { return } - val location = LocationServices.getFusedLocationProviderClient(context).lastLocation.await() + val location = try { + LocationServices.getFusedLocationProviderClient(context).lastLocation.await() + } catch (e: Exception) { + Log.e(TAG, "Failed to get fused location provider client", e) + null + } var address: Address? = null try { if (location == null) { @@ -128,7 +133,13 @@ class GeocodeSensorManager : SensorManager { val prettyAddress = address?.getAddressLine(0) - HighAccuracyLocationService.updateNotificationAddress(context, location, if (!prettyAddress.isNullOrEmpty()) prettyAddress else context.getString(commonR.string.unknown_address)) + if (location != null) { + HighAccuracyLocationService.updateNotificationAddress( + context, + location, + if (!prettyAddress.isNullOrEmpty()) prettyAddress else context.getString(commonR.string.unknown_address) + ) + } onSensorUpdated( context, diff --git a/app/src/full/java/io/homeassistant/companion/android/sensors/LocationSensorManager.kt b/app/src/full/java/io/homeassistant/companion/android/sensors/LocationSensorManager.kt index 88df4aba3..d5d016341 100644 --- a/app/src/full/java/io/homeassistant/companion/android/sensors/LocationSensorManager.kt +++ b/app/src/full/java/io/homeassistant/companion/android/sensors/LocationSensorManager.kt @@ -647,7 +647,13 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager { } Log.d(TAG, "Registering for location updates.") - fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(latestContext) + fusedLocationProviderClient = try { + LocationServices.getFusedLocationProviderClient(latestContext) + } catch (e: Exception) { + Log.e(TAG, "Unable to get fused location provider client", e) + null + } + val intent = getLocationUpdateIntent(false) fusedLocationProviderClient?.requestLocationUpdates( @@ -1109,65 +1115,88 @@ class LocationSensorManager : BroadcastReceiver(), SensorManager { setMaxUpdates(maxRetries) setMinUpdateIntervalMillis(5000) }.build() - LocationServices.getFusedLocationProviderClient(latestContext) - .requestLocationUpdates( - request, - object : LocationCallback() { - val wakeLock: PowerManager.WakeLock? = - latestContext.getSystemService() - ?.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - "HomeAssistant::AccurateLocation" - )?.apply { acquire(10 * 60 * 1000L /*10 minutes*/) } - var numberCalls = 0 - override fun onLocationResult(locationResult: LocationResult) { - numberCalls++ - Log.d( - TAG, - "Got single accurate location update: ${locationResult.lastLocation}" - ) - if (locationResult.equals(null)) { - Log.w(TAG, "No location provided.") - return - } + try { + LocationServices.getFusedLocationProviderClient(latestContext) + .requestLocationUpdates( + request, + object : LocationCallback() { + val wakeLock: PowerManager.WakeLock? = + latestContext.getSystemService() + ?.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + "HomeAssistant::AccurateLocation" + )?.apply { acquire(10 * 60 * 1000L /*10 minutes*/) } + var numberCalls = 0 + override fun onLocationResult(locationResult: LocationResult) { + numberCalls++ + Log.d( + TAG, + "Got single accurate location update: ${locationResult.lastLocation}" + ) + if (locationResult.equals(null)) { + Log.w(TAG, "No location provided.") + return + } - when { - locationResult.lastLocation!!.accuracy <= minAccuracy -> { - Log.d(TAG, "Location accurate enough, all done with high accuracy.") - runBlocking { - locationResult.lastLocation?.let { - getEnabledServers(latestContext, singleAccurateLocation).forEach { serverId -> - sendLocationUpdate(it, serverId, LocationUpdateTrigger.SINGLE_ACCURATE_LOCATION) - } - } - } - if (wakeLock?.isHeld == true) wakeLock.release() - } - numberCalls >= maxRetries -> { - Log.d( - TAG, - "No location was accurate enough, sending our last location anyway" - ) - if (locationResult.lastLocation!!.accuracy <= minAccuracy * 2) { + when { + locationResult.lastLocation!!.accuracy <= minAccuracy -> { + Log.d( + TAG, + "Location accurate enough, all done with high accuracy." + ) runBlocking { - getEnabledServers(latestContext, singleAccurateLocation).forEach { serverId -> - sendLocationUpdate(locationResult.lastLocation!!, serverId, LocationUpdateTrigger.SINGLE_ACCURATE_LOCATION) + locationResult.lastLocation?.let { + getEnabledServers( + latestContext, + singleAccurateLocation + ).forEach { serverId -> + sendLocationUpdate( + it, + serverId, + LocationUpdateTrigger.SINGLE_ACCURATE_LOCATION + ) + } } } + if (wakeLock?.isHeld == true) wakeLock.release() + } + + numberCalls >= maxRetries -> { + Log.d( + TAG, + "No location was accurate enough, sending our last location anyway" + ) + if (locationResult.lastLocation!!.accuracy <= minAccuracy * 2) { + runBlocking { + getEnabledServers( + latestContext, + singleAccurateLocation + ).forEach { serverId -> + sendLocationUpdate( + locationResult.lastLocation!!, + serverId, + LocationUpdateTrigger.SINGLE_ACCURATE_LOCATION + ) + } + } + } + if (wakeLock?.isHeld == true) wakeLock.release() + } + + else -> { + Log.w( + TAG, + "Location not accurate enough on retry $numberCalls of $maxRetries" + ) } - if (wakeLock?.isHeld == true) wakeLock.release() - } - else -> { - Log.w( - TAG, - "Location not accurate enough on retry $numberCalls of $maxRetries" - ) } } - } - }, - Looper.getMainLooper() - ) + }, + Looper.getMainLooper() + ) + } catch (e: Exception) { + Log.e(TAG, "Failed to get location data for single accurate sensor", e) + } } override fun docsLink(): String { diff --git a/app/src/full/java/io/homeassistant/companion/android/settings/wear/SettingsWearActivity.kt b/app/src/full/java/io/homeassistant/companion/android/settings/wear/SettingsWearActivity.kt index 59c3fca90..4bc961d0b 100644 --- a/app/src/full/java/io/homeassistant/companion/android/settings/wear/SettingsWearActivity.kt +++ b/app/src/full/java/io/homeassistant/companion/android/settings/wear/SettingsWearActivity.kt @@ -34,9 +34,9 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC private lateinit var binding: ActivitySettingsWearBinding - private lateinit var capabilityClient: CapabilityClient - private lateinit var nodeClient: NodeClient - private lateinit var remoteActivityHelper: RemoteActivityHelper + private var capabilityClient: CapabilityClient? = null + private var nodeClient: NodeClient? = null + private var remoteActivityHelper: RemoteActivityHelper? = null private var wearNodesWithApp: Set? = null private var allConnectedNodes: List? = null @@ -49,9 +49,24 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) - capabilityClient = Wearable.getCapabilityClient(this) - nodeClient = Wearable.getNodeClient(this) - remoteActivityHelper = RemoteActivityHelper(this) + capabilityClient = try { + Wearable.getCapabilityClient(this) + } catch (e: Exception) { + Log.e(TAG, "Unable to get capability client", e) + null + } + nodeClient = try { + Wearable.getNodeClient(this) + } catch (e: Exception) { + Log.e(TAG, "Unable to get node client", e) + null + } + remoteActivityHelper = try { + RemoteActivityHelper(this) + } catch (e: Exception) { + Log.e(TAG, "Unable to get remote activity helper", e) + null + } binding.remoteOpenButton.setOnClickListener { openPlayStoreOnWearDevicesWithoutApp() @@ -81,13 +96,13 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC override fun onPause() { super.onPause() - capabilityClient.removeListener(this, CAPABILITY_WEAR_APP) + capabilityClient?.removeListener(this, CAPABILITY_WEAR_APP) } override fun onResume() { super.onResume() title = getString(commonR.string.wear_os_settings_title) - capabilityClient.addListener(this, CAPABILITY_WEAR_APP) + capabilityClient?.addListener(this, CAPABILITY_WEAR_APP) } /* @@ -106,11 +121,11 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC private suspend fun findWearDevicesWithApp() { try { val capabilityInfo = capabilityClient - .getCapability(CAPABILITY_WEAR_APP, CapabilityClient.FILTER_ALL) - .await() + ?.getCapability(CAPABILITY_WEAR_APP, CapabilityClient.FILTER_ALL) + ?.await() withContext(Dispatchers.Main) { - wearNodesWithApp = capabilityInfo.nodes + wearNodesWithApp = capabilityInfo?.nodes Log.d(TAG, "Capable Nodes: $wearNodesWithApp") updateUI() } @@ -124,7 +139,7 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC private suspend fun findAllWearDevices() { try { - val connectedNodes = nodeClient.connectedNodes.await() + val connectedNodes = nodeClient?.connectedNodes?.await() withContext(Dispatchers.Main) { allConnectedNodes = connectedNodes @@ -187,11 +202,11 @@ class SettingsWearActivity : AppCompatActivity(), CapabilityClient.OnCapabilityC lifecycleScope.launch { try { remoteActivityHelper - .startRemoteActivity( + ?.startRemoteActivity( targetIntent = intent, targetNodeId = node.id ) - .await() + ?.await() Toast.makeText( this@SettingsWearActivity, diff --git a/app/src/full/java/io/homeassistant/companion/android/settings/wear/SettingsWearViewModel.kt b/app/src/full/java/io/homeassistant/companion/android/settings/wear/SettingsWearViewModel.kt index 7aab8b8dd..4513ea929 100644 --- a/app/src/full/java/io/homeassistant/companion/android/settings/wear/SettingsWearViewModel.kt +++ b/app/src/full/java/io/homeassistant/companion/android/settings/wear/SettingsWearViewModel.kt @@ -84,7 +84,11 @@ class SettingsWearViewModel @Inject constructor( val resultSnackbar = _resultSnackbar.asSharedFlow() init { - Wearable.getDataClient(application).addListener(this) + try { + Wearable.getDataClient(application).addListener(this) + } catch (e: Exception) { + Log.e(TAG, "Unable to get wearable data client", e) + } viewModelScope.launch { try { val capabilityInfo = Wearable.getCapabilityClient(application) @@ -113,7 +117,11 @@ class SettingsWearViewModel @Inject constructor( } override fun onCleared() { - Wearable.getDataClient(getApplication()).removeListener(this) + try { + Wearable.getDataClient(getApplication()).removeListener(this) + } catch (e: Exception) { + Log.e(TAG, "Unable to remove listener from wearable data client", e) + } if (serverId != 0) { CoroutineScope(Dispatchers.Main + Job()).launch { @@ -218,15 +226,18 @@ class SettingsWearViewModel @Inject constructor( } val app = getApplication() - Wearable.getDataClient(app).putDataItem(putDataRequest).apply { - addOnSuccessListener { Log.d(TAG, "Successfully sent auth to wear") } - addOnFailureListener { e -> - Log.e(TAG, "Failed to send auth to wear", e) - _hasData.value = true - viewModelScope.launch { - _resultSnackbar.emit(app.getString(commonR.string.failed_watch_connection)) + try { + Wearable.getDataClient(app).putDataItem(putDataRequest).apply { + addOnSuccessListener { Log.d(TAG, "Successfully sent auth to wear") } + addOnFailureListener { e -> + Log.e(TAG, "Failed to send auth to wear", e) + _hasData.value = true + watchConnectionError(app) } } + } catch (e: Exception) { + Log.e(TAG, "Unable to send auth to wear", e) + watchConnectionError(app) } } @@ -238,9 +249,14 @@ class SettingsWearViewModel @Inject constructor( asPutDataRequest() } - Wearable.getDataClient(getApplication()).putDataItem(putDataRequest).apply { - addOnSuccessListener { Log.d(TAG, "Successfully sent tile template to wear") } - addOnFailureListener { e -> Log.e(TAG, "Failed to send tile template to wear", e) } + try { + Wearable.getDataClient(getApplication()) + .putDataItem(putDataRequest).apply { + addOnSuccessListener { Log.d(TAG, "Successfully sent tile template to wear") } + addOnFailureListener { e -> Log.e(TAG, "Failed to send tile template to wear", e) } + } + } catch (e: Exception) { + Log.e(TAG, "Unable to send template tile to wear", e) } } @@ -348,4 +364,10 @@ class SettingsWearViewModel @Inject constructor( authenticateId = null } + + private fun watchConnectionError(app: HomeAssistantApplication) { + viewModelScope.launch { + _resultSnackbar.emit(app.getString(commonR.string.failed_watch_connection)) + } + } }