We want to know when apps are uninstalled from the Android device. We set up broadcast receivers (not in the manifest) concerning the device apps and process them when received. The problem: when uninstalling an app (using Android Settings -> Apps/Notifications -> AppYouWantToUninstall -> tap the trashcan) the broadcast is not received...however this only happens when the app is installed on an Android 10 device.
Of course there are two other main ways to uninstall apps: 1) tap/hold on the app and bring up the "App Info" menu; and 2) go into the Play Store. Those other two methods deliver a broadcast of ACTION_PACKAGE_REMOVED.
All combinations of OS (Android 7, 8, 9 and 10) and the three methods of uninstalling deliver the ACTION_PACKAGE_REMOVED broadcast except for Settings uninstall method when the app is on an Android 10 device. I'd be skeptical, too, so I'll answer a couple of likely probes: "Yes, if we use the Play Store uninstall for an app on Android 10, we do get a broadcast" and "Yes, if we use the Settings uninstall for an app on Android 7, 8, 9 we do get a broadcast"
The definition in the class:
class AppInstallBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null || context == null) return
when (intent.action) {
Intent.ACTION_PACKAGE_ADDED ->
enqueueWork(context, getIntent(context, ACTION_PACKAGE_ADDED, intent.data?.schemeSpecificPart))
Intent.ACTION_PACKAGE_REPLACED ->
enqueueWork(context, getIntent(context, ACTION_PACKAGE_REPLACED, intent.data?.schemeSpecificPart))
Intent.ACTION_PACKAGE_CHANGED ->
enqueueWork(context, getIntent(context, ACTION_PACKAGE_CHANGED, intent.data?.schemeSpecificPart))
Intent.ACTION_PACKAGE_REMOVED ->
enqueueWork(context, getIntent(context, ACTION_PACKAGE_REMOVED, intent.data?.schemeSpecificPart))
Intent.ACTION_PACKAGE_FULLY_REMOVED ->
enqueueWork(context, getIntent(context, ACTION_PACKAGE_FULLY_REMOVED, intent.data?.schemeSpecificPart))
}
}
...and this is where we process received actions
private suspend fun processAction(action:String, packageName: String, context: Context) {
when (action) {
ACTION_PACKAGE_ADDED -> updateAppInfo(context, packageName, false)
ACTION_PACKAGE_REPLACED -> updateAppInfo(context, packageName, true)
ACTION_PACKAGE_REMOVED -> deleteAppInfo(context, packageName)
ACTION_PACKAGE_FULLY_REMOVED -> deleteFullyAppInfo(context, packageName)
}
}
This is where we define (in the application, not the manifest) the receivers:
private val installBroadcastReceiver = AppInstallBroadcastReceiver()
private val installReceiverFilter = IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REMOVED)
addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED)
addAction(Intent.ACTION_PACKAGE_CHANGED)
addAction(Intent.ACTION_PACKAGE_REPLACED)
addDataScheme("package")
}
...and where we register, filter
private fun registerAppChanges() {
Timber.d("Registering the installation receiver ..")
registerReceiver(installBroadcastReceiver, installReceiverFilter)
registerReceiver(unlockReceiver, unlockReceiverFilter)
}