'How to track the removal of the application and send a notification?

There is a task to make it so that when an application is deleted from the phone, my application sends a notification: "application name" has been deleted.

I do it like this:

Manifest

 <receiver
            android:name=".MyReceiver"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="servicereload" />
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true" >
        </service>

MainActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val notificationManagerCompat = NotificationManagerCompat.from(this)
        notificationManagerCompat.cancelAll()

        val serviceIntent = Intent(this, MyService::class.java)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            ContextCompat.startForegroundService(this, serviceIntent)
        } else {
            startService(Intent(this, MyService::class.java))
        }
        val intentFilter = IntentFilter()
        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED)
        intentFilter.addDataScheme("package")
        registerReceiver(MyReceiver(), intentFilter)

    }
}

MyReceiver

class MyReceiver: BroadcastReceiver() {
    val CHANNEL_ID = "ServiceChannel"
    @SuppressLint("RemoteViewLayout")
    override fun onReceive(context: Context?, intent: Intent?) {
        var packageName = ""
        try {
            packageName = Objects.requireNonNull(intent!!.data)!!.encodedSchemeSpecificPart
            Toast.makeText(context, "USER UNINSTALL : $packageName", Toast.LENGTH_SHORT).show()
            Log.i("MyLog", "USER UNINSTALL : $packageName")
        } catch (ex: Exception) {
            Log.i("MyLog", "Exception: $ex")
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationChannel = NotificationChannel(CHANNEL_ID, "Channel", NotificationManager.IMPORTANCE_DEFAULT)
            val notificationManager = context!!.getSystemService(NotificationManager::class.java)
            notificationManager.createNotificationChannel(notificationChannel)
        }

        val notifyIntent = Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        }
        val notifyPendingIntent = PendingIntent.getActivity(context, 200, notifyIntent, 0)

        val builder: NotificationCompat.Builder = NotificationCompat.Builder(context!!, CHANNEL_ID).apply {
            setSmallIcon(R.mipmap.ic_launcher)
            setContentTitle(context.getString(R.string.app_name))
            setContentText("App ${packageName.substringAfterLast(".")} has been deleted")
            priority = NotificationCompat.PRIORITY_DEFAULT
            setContentIntent(notifyPendingIntent)
        }

        with(NotificationManagerCompat.from(context!!)) {
            notify(200, builder.build())
        }

    }

}

MyService

class MyService : Service() {
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i("MyLog", "onStartCommand()")
        someTask()
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.i("MyLog", "Service onDestroy()")
        val intent = Intent()
        intent.action = "servicereload"
        intent.setClass(this, MyReceiver::class.java)
        this.sendBroadcast(intent)
    }

    override fun onCreate() {
        super.onCreate()
        Log.i("MyLog", "Service onCreate()")
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        val intent = Intent(applicationContext, this.javaClass)
        intent.setPackage(packageName)
        val pendingIntent = PendingIntent.getService(applicationContext, 1, intent, PendingIntent.FLAG_ONE_SHOT)
        val alarmManager = applicationContext.getSystemService(ALARM_SERVICE) as AlarmManager
        alarmManager.set(AlarmManager.ELAPSED_REALTIME,1000,pendingIntent)
        super.onTaskRemoved(rootIntent)
    }

    private fun someTask() {
        Log.i("MyLog", "Service someTask()")
//        val intentFilter = IntentFilter()
//        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED)
//        intentFilter.addDataScheme("package")
//        registerReceiver(MyReceiver(), intentFilter)
        Thread {
            for (i in 1..50) {
                Log.i("MyLog", "Service i = $i")
                try {
                    TimeUnit.SECONDS.sleep(1)
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
            }
//            stopSelf()
        }.start()
    }
}

someTask() method, just to see if the service is up and running. I check on API 29+ emulators, if I manage to delete the application before 10 seconds, it works well but starts the second service in parallel, which is not good, and I don’t understand why?! If there is nothing to do, after 10 seconds it throws android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{ab817c0 u0 com.testappremovel/.MyService} If I remove onDestroy () or onTaskRemoved () in the service, there is no problem with parallel launch, but after 10 seconds it is cut down in any case and, accordingly, does not turn back on. I also have methods for determining the name and icon of the application by package in order to display them in the notification, but of course, when the application is already remote, I cannot get this data. Please tell me, how to do it right, so that everything works as it should?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source