Skip to content

Commit

Permalink
Add (NOT_)EXPORTED to registerReceiver for Android 14 target (#4559)
Browse files Browse the repository at this point in the history
- For dynamically registered receivers, add whether or not they are exported to prevent a crash due to a behavior change when we start targeting Android 14: https://developer.android.com/about/versions/14/behavior-changes-14#runtime-receivers-exported. Exported or not depends on the list of system intents (not on list = exported to be safe): https://developer.android.com/about/versions/12/reference/broadcast-intents-31.
  • Loading branch information
jpelgrom authored Sep 6, 2024
1 parent 5b82c20 commit 241d724
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.nfc.NfcAdapter
import android.os.Build
import android.os.PowerManager
import android.telephony.TelephonyManager
import androidx.core.content.ContextCompat
import dagger.hilt.android.HiltAndroidApp
import io.homeassistant.companion.android.common.data.keychain.KeyChainRepository
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
Expand Down Expand Up @@ -63,15 +64,17 @@ open class HomeAssistantApplication : Application() {
languagesManager.applyCurrentLang()

// This will make sure we start/stop when we actually need too.
registerReceiver(
ContextCompat.registerReceiver(
this,
WebsocketBroadcastReceiver(),
IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_OFF)
addAction(Intent.ACTION_SCREEN_ON)
addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION)
addAction(WifiManager.WIFI_STATE_CHANGED_ACTION)
}
},
ContextCompat.RECEIVER_EXPORTED
)

ioScope.launch {
Expand All @@ -82,120 +85,148 @@ open class HomeAssistantApplication : Application() {
// 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. Updates will also be triggered when the system reports low battery and when it recovers.
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter().apply {
addAction(Intent.ACTION_BATTERY_LOW)
addAction(Intent.ACTION_BATTERY_OKAY)
addAction(Intent.ACTION_POWER_CONNECTED)
addAction(Intent.ACTION_POWER_DISCONNECTED)
}
},
ContextCompat.RECEIVER_NOT_EXPORTED
)

// This will cause interactive and power save to update upon a state change
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_OFF)
addAction(Intent.ACTION_SCREEN_ON)
addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
}
},
ContextCompat.RECEIVER_NOT_EXPORTED
)

// Update Quest only sensors when the device is a Quest
if (Build.MODEL == "Quest") {
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter().apply {
addAction("com.oculus.intent.action.MOUNT_STATE_CHANGED")
}
},
ContextCompat.RECEIVER_EXPORTED
)
}

// Update doze mode immediately on supported devices
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter().apply {
addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
}
},
ContextCompat.RECEIVER_NOT_EXPORTED
)
}

// This will trigger an update any time the wifi state has changed
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter().apply {
addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION)
addAction(WifiManager.WIFI_STATE_CHANGED_ACTION)
addAction("android.net.wifi.WIFI_AP_STATE_CHANGED")
}
},
ContextCompat.RECEIVER_EXPORTED
)

// This will cause the phone state sensor to be updated every time the OS broadcasts that a call triggered.
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter().apply {
addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED)
}
},
ContextCompat.RECEIVER_NOT_EXPORTED
)

// Listen for bluetooth state changes
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED),
ContextCompat.RECEIVER_NOT_EXPORTED
)

// Listen for NFC state changes
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED),
ContextCompat.RECEIVER_NOT_EXPORTED
)

// Listen to changes to the audio input/output on the device
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter().apply {
addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
addAction(AudioManager.ACTION_HEADSET_PLUG)
addAction(AudioManager.RINGER_MODE_CHANGED_ACTION)
addAction(AudioSensorManager.VOLUME_CHANGED_ACTION)
}
},
ContextCompat.RECEIVER_EXPORTED
)

// Listen for microphone mute changes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED)
IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED),
ContextCompat.RECEIVER_NOT_EXPORTED
)
}

// Listen for speakerphone state changes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED),
ContextCompat.RECEIVER_NOT_EXPORTED
)
}

// Add receiver for DND changes on devices that support it
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)
IntentFilter(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED),
ContextCompat.RECEIVER_NOT_EXPORTED
)
}

registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter("androidx.car.app.connection.action.CAR_CONNECTION_UPDATED")
IntentFilter("androidx.car.app.connection.action.CAR_CONNECTION_UPDATED"),
ContextCompat.RECEIVER_EXPORTED
)

// Add a receiver for the shutdown event to attempt to send 1 final sensor update
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter(Intent.ACTION_SHUTDOWN)
IntentFilter(Intent.ACTION_SHUTDOWN),
ContextCompat.RECEIVER_NOT_EXPORTED
)

// Register for all saved user intents
Expand All @@ -204,36 +235,42 @@ open class HomeAssistantApplication : Application() {
for (setting in allSettings) {
if (setting.value != "" && setting.value != "SensorWorker") {
val settingSplit = setting.value.split(',')
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter().apply {
addAction(settingSplit[0])
if (settingSplit.size > 1) {
val categories = settingSplit.minus(settingSplit[0])
categories.forEach { addCategory(it) }
}
}
},
ContextCompat.RECEIVER_EXPORTED
)
}
}

// Register for changes to the managed profile availability
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter().apply {
addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
}
},
ContextCompat.RECEIVER_NOT_EXPORTED
)
}

// Register for faster sensor updates if enabled
val settingDao = AppDatabase.getInstance(applicationContext).settingsDao().get(0)
if (settingDao != null && (settingDao.sensorUpdateFrequency == SensorUpdateFrequencySetting.FAST_WHILE_CHARGING || settingDao.sensorUpdateFrequency == SensorUpdateFrequencySetting.FAST_ALWAYS)) {
registerReceiver(
ContextCompat.registerReceiver(
this,
sensorReceiver,
IntentFilter(Intent.ACTION_TIME_TICK)
IntentFilter(Intent.ACTION_TIME_TICK),
ContextCompat.RECEIVER_NOT_EXPORTED
)
}

Expand All @@ -247,9 +284,9 @@ open class HomeAssistantApplication : Application() {
screenIntentFilter.addAction(Intent.ACTION_SCREEN_ON)
screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF)

registerReceiver(buttonWidget, screenIntentFilter)
registerReceiver(entityWidget, screenIntentFilter)
registerReceiver(mediaPlayerWidget, screenIntentFilter)
registerReceiver(templateWidget, screenIntentFilter)
ContextCompat.registerReceiver(this, buttonWidget, screenIntentFilter, ContextCompat.RECEIVER_NOT_EXPORTED)
ContextCompat.registerReceiver(this, entityWidget, screenIntentFilter, ContextCompat.RECEIVER_NOT_EXPORTED)
ContextCompat.registerReceiver(this, mediaPlayerWidget, screenIntentFilter, ContextCompat.RECEIVER_NOT_EXPORTED)
ContextCompat.registerReceiver(this, templateWidget, screenIntentFilter, ContextCompat.RECEIVER_NOT_EXPORTED)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.util.Log
import android.widget.Toast
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.BaseActivity
Expand Down Expand Up @@ -82,9 +83,11 @@ class NfcSetupActivity : BaseActivity() {
mNfcAdapter?.let {
NFCUtil.enableNFCInForeground(it, this, javaClass)
}
registerReceiver(
ContextCompat.registerReceiver(
this,
nfcStateChangedReceiver,
IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED),
ContextCompat.RECEIVER_NOT_EXPORTED
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import androidx.core.content.ContextCompat
import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.sensors.SensorManager

Expand Down Expand Up @@ -45,9 +46,11 @@ class QuestSensorManager : SensorManager {
override fun requestSensorUpdate(
context: Context
) {
val intent = context.registerReceiver(
val intent = ContextCompat.registerReceiver(
context,
null,
IntentFilter("com.oculus.intent.action.MOUNT_STATE_CHANGED")
IntentFilter("com.oculus.intent.action.MOUNT_STATE_CHANGED"),
ContextCompat.RECEIVER_EXPORTED
)
if (intent != null) {
updateHeadsetMount(context, intent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.servers.ServerManager
import javax.inject.Inject
Expand Down Expand Up @@ -95,9 +96,11 @@ abstract class BaseWidgetProvider : AppWidgetProvider() {
val allWidgets = getAllWidgetIdsWithEntities(context)
val widgetsWithDifferentEntities = allWidgets.filter { it.value.second != widgetEntities[it.key] }
if (widgetsWithDifferentEntities.isNotEmpty()) {
context.applicationContext.registerReceiver(
ContextCompat.registerReceiver(
context.applicationContext,
this@BaseWidgetProvider,
IntentFilter(Intent.ACTION_SCREEN_OFF)
IntentFilter(Intent.ACTION_SCREEN_OFF),
ContextCompat.RECEIVER_NOT_EXPORTED
)

widgetsWithDifferentEntities.forEach { (id, pair) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,11 @@ class TemplateWidget : AppWidgetProvider() {
val widgetsWithDifferentTemplate = allWidgets.filter { it.template != widgetTemplates[it.id] }
if (widgetsWithDifferentTemplate.isNotEmpty()) {
if (thisSetScope) {
context.applicationContext.registerReceiver(
ContextCompat.registerReceiver(
context.applicationContext,
this@TemplateWidget,
IntentFilter(Intent.ACTION_SCREEN_OFF)
IntentFilter(Intent.ACTION_SCREEN_OFF),
ContextCompat.RECEIVER_NOT_EXPORTED
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.util.STATE_UNAVAILABLE
import io.homeassistant.companion.android.common.util.STATE_UNKNOWN
Expand Down Expand Up @@ -143,7 +144,7 @@ class BatterySensorManager : SensorManager {
}

override fun hasSensor(context: Context): Boolean {
val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val intent = ContextCompat.registerReceiver(context, null, IntentFilter(Intent.ACTION_BATTERY_CHANGED), ContextCompat.RECEIVER_NOT_EXPORTED)
return intent?.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false) == true
}

Expand All @@ -154,7 +155,7 @@ class BatterySensorManager : SensorManager {
override fun requestSensorUpdate(
context: Context
) {
val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val intent = ContextCompat.registerReceiver(context, null, IntentFilter(Intent.ACTION_BATTERY_CHANGED), ContextCompat.RECEIVER_NOT_EXPORTED)
if (intent != null) {
updateBatteryLevel(context, intent)
updateBatteryState(context, intent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.content.res.Configuration
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.common.data.integration.IntegrationException
Expand Down Expand Up @@ -51,7 +52,7 @@ abstract class SensorReceiverBase : BroadcastReceiver() {
SensorUpdateFrequencySetting.FAST_ALWAYS -> true
SensorUpdateFrequencySetting.FAST_WHILE_CHARGING -> {
val batteryStatusIntent =
context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
ContextCompat.registerReceiver(context, null, IntentFilter(Intent.ACTION_BATTERY_CHANGED), ContextCompat.RECEIVER_NOT_EXPORTED)
return batteryStatusIntent?.let { BatterySensorManager.getIsCharging(it) } ?: false
}
else -> false
Expand Down
Loading

0 comments on commit 241d724

Please sign in to comment.