How to Get App Install and Uninstall Events in Android?

App Install and Uninstall Events Android

In this article, you will learn how to get the app install and uninstall events in Android.

To get the install and uninstall events, we can use intents like ACTION_PACKAGE_ADDED with the broadcast receivers. But, starting from Oreo (Android 8, API level 26), Google imposed restrictions on the receivers. They introduced getChangedPackages() API in Android 8. So, we should use it for the devices running Android 8 and above, and broadcast receivers for the earlier versions.

App Install and Uninstall Events in Android 8 and Later:

Let’s look at the getChangedPackages() from the PackageManager class:

public abstract ChangedPackages getChangedPackages (int sequenceNumber)

It takes an integer called sequenceNumber and returns the ChangedPackages object. If no packages (apps) have been modified, it returns null.

The ChangedPackages object contains the packages that have been changed (added, removed, or updated) since the given sequenceNumber.

Android sequenceNumber:

The sequence number starts at 0. Every time the user installs a new app, or updates/deletes an existing app, the number will be incremented. It is also reset every boot.

We can get the last known sequence number from the getSequenceNumber() method.

We should save this number in a shared preference file and get its value from the file whenever we query the changed apps.

Here is the complete code:

import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlin.concurrent.thread

class MainActivity : AppCompatActivity() {

    private val tagLog = javaClass.simpleName as String

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

        thread {
            // call the method from a background thread
            checkChangedPackages(this.applicationContext)
        }
    }

    private fun checkChangedPackages(context: Context) {

        val packageManagerApps = context.packageManager
        val sequenceNumber = getSequenceNumber(context)
        Log.d(tagLog, "sequenceNumber = $sequenceNumber")

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

            val changedPackages = packageManagerApps.getChangedPackages(sequenceNumber)

            if (changedPackages != null) {
                // Packages are changed

                // Get the list of changed packages
                // the list includes new, updated and deleted apps
                val changedPackagesNames = changedPackages.packageNames

                var appName: CharSequence

                for (packageName in changedPackagesNames) {
                    try {
                        appName = packageManagerApps.getApplicationLabel(
                            packageManagerApps.getApplicationInfo(
                                packageName, 0,
                            )
                        )

                        // Either a new or an updated app
                        Log.d(
                            tagLog,
                            "New Or Updated App: $packageName , appName = ${appName.toString()}"
                        )
                    } catch (e: PackageManager.NameNotFoundException) {
                        // The app is deleted
                        Log.d(tagLog, "Deleted App: $packageName")
                    }
                }
                saveSequenceNumber(context, changedPackages.sequenceNumber)
            } else {
                // packages not changed
            }
        }
    }

    private fun getSequenceNumber(context: Context): Int {
        val sharedPrefFile = context.getSharedPreferences("your_file_name", MODE_PRIVATE)
        return sharedPrefFile.getInt("sequence_number", 0)
    }

    private fun saveSequenceNumber(context: Context, newSequenceNumber: Int) {
        val sharedPrefFile = context.getSharedPreferences("your_file_name", MODE_PRIVATE)
        val editor = sharedPrefFile.edit()
        editor.putInt("sequence_number", newSequenceNumber)
        editor.apply()
    }
}

Note: Sometimes, getChangedPackages() returns the packages that are not changed. Also, the sequence number is incremented even if there is no change in the apps list.

Broadcast Receiver for App Install and Uninstall Events (Below Android 8):

It is easy to implement a broadcast receiver. Create a class with the name AppEventsBroadcastReceiver.

class AppEventsBroadcastReceiver: BroadcastReceiver() {

    private val tagLog = javaClass.simpleName as String

    override fun onReceive(context: Context?, intent: Intent?) {

    }
}

Every time an app is changed, the onReceive() method gets called. In the manifest file, register the receiver (inside the application tag).

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.semicolonspace.examples">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round">

        <receiver
            android:name=".AppEventsBroadcastReceiver"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
                <action android:name="android.intent.action.PACKAGE_REPLACED" />
                <data android:scheme="package" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </receiver>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" /
            </intent-filter>
        </activity>
    </application>

</manifest>

Add the following code in the AppEventsBroadcastReceiver class:

class AppEventsBroadcastReceiver : BroadcastReceiver() {

    private val tagLog = javaClass.simpleName as String

    override fun onReceive(context: Context?, intent: Intent?) {

        if (intent != null && context != null) {

            // Check this condition because the broadcast receiver
            // is getting triggered on some devices running above Oreo
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {

                val packageManager = context.packageManager
                val appUid = intent.getIntExtra(Intent.EXTRA_UID, 0)

                if (intent.action == "android.intent.action.PACKAGE_FULLY_REMOVED") {
                    Log.d(tagLog, "PACKAGE_FULLY_REMOVED $appUid")
                } else {
                    val applicationInfo = packageManager?.getApplicationInfo(
                        packageManager.getNameForUid(appUid)!!, PackageManager.GET_META_DATA
                    )!!

                    val appName = packageManager.getApplicationLabel(applicationInfo).toString()
                    val appPackageName = applicationInfo.packageName

                    if (intent.action == "android.intent.action.PACKAGE_ADDED") {
                        Log.d(tagLog, "PACKAGE_ADDED $appPackageName , $appName")
                    } else if (intent.action == "android.intent.action.PACKAGE_REPLACED") {
                        Log.d(tagLog, "PACKAGE_REPLACED $appPackageName , $appName")
                    }
                }
            }
        }
    }
}

Note: When the user updates an app, both PACKAGE_ADDED and PACKAGE_REPLACED are getting triggered.

This is how you get the app install and uninstall events in Android. If you have any doubts, comment below.

Related:

Leave a Comment