The usual way of installing Android apps is through an app store. On my regular phone I've only used the Google Play Store. The Play Store app has one interesting feature - it can update apps without requiring your confirmation.

If your device is only used for one specific purpose (e.g., as a kiosk) within a limited group of people, it might not make sense to publish your app to an app store. Even in this situation, the device will still need maintenance and software updates. Ideally you should be able to perform these updates remotely without even touching the device for confirmation to install your update.

Android already offers this kind of functionality for device owner apps. If you're not sure how to create and enable a device owner app, check out my previous post.

You can find the complete Android Studio project related to this post on github.

Checking for New Version and Downloading Update

To update your app, you first need an updated APK file stored locally on the Android device that you want to update. This APK file should be signed with the same signing key as the original application. Additionally, the version code of the updated APK should not be lower than the version code of the original app (or version code of the last update).

There are many ways how to get the updated APK to your machine. It depends on your specific situation and on which technologies you decided to use. Therefore I won't show any specific code, but I can still suggest some strategies how to accomplish this.

The first thing you can do is to decide how often you want to check for updates and then set up a service in your Android app that performs this task in the background. For this you can check out how to do Job-Scheduling in the official docs.

On the server side you can set up a web service that can at least (1) give information about current APK version on the server, and (2) serve a download of the updated APK. How this is implemented on the server depends on technologies you use, but the service should at least be able to serve the timestamp or versionCode of the updated APK.

Additionally, if your APK file size too large, you might consider to only supply the diff between versions from your sever, and reconstruct the final APK for the update on the device, similarly to how Android Play Store app is doing this.

The update service in your Android app can compare the versionCode or timestamp with the currently installed APK. Getting the version code from AndroidManifest.xml inside of you updated APK file might require some reverse engineering of the binary xml format used on Android, but it should still be fairly trivial. You can use the lastUpdateTime field of the PackageInfo class to compare the timestamps (for more information about getting app installation info checkout my previous post).

Once you've determined that there is a new version on the server, you can then use the DownloadManager class to download the updated APK file.

Updating the App

Starting from Android 6 it became possible to install, uninstall, and update apps silently without any user interaction. You don't need to be root, but your app still needs to be device owner.

Most of the relevant methods for performing the update are located inside of the PackageInstaller and PackageInstaller.Session class.

The install() method shown in the following code takes a context that can provide a PackageManager instance, the package name, and the path to the actual APK file that you want to install. For a successful update, you need to make sure that the apkPath is readable (and your app has to be device owner, of course).

fun install(context: Context, packageName: String, apkPath: String) {

    // PackageManager provides an instance of PackageInstaller
    val packageInstaller = context.packageManager.packageInstaller

    // Prepare params for installing one APK file with MODE_FULL_INSTALL
    // We could use MODE_INHERIT_EXISTING to install multiple split APKs
    val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
    params.setAppPackageName(packageName)

    // Get a PackageInstaller.Session for performing the actual update
    val sessionId = packageInstaller.createSession(params)
    val session = packageInstaller.openSession(sessionId)

    // Copy APK file bytes into OutputStream provided by install Session
    val out = session.openWrite(packageName, 0, -1)
    val fis = File(apkPath).inputStream()
    fis.copyTo(out)
    session.fsync(out)
    out.close()

    // The app gets killed after installation session commit
    session.commit(PendingIntent.getBroadcast(context, sessionId,
            Intent("android.intent.action.MAIN"), 0).intentSender)
}

Restarting App After Update

If the app that you are updating is currently running, it gets killed once you call Session.commit(). To ensure that your app is restarted after update, you need to set up a BroadcastReceiver that listens for PACKAGE_REPLACED intent. This intent will be triggered for all other apps that got updated, so you need to check which app was updated first. You can extract the package name of actual package that got replaced from the received intent.

For the BroadcastReceiver create the following class

class UpdateReceiver : BroadcastReceiver() {

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

        // First check that your package got updated and not some
        // other package
        if (intent?.dataString == "package:" + context.packageName) {

            // Restart your app here
            val i = Intent(context, MainActivity::class.java)
            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            context.startActivity(i)
        }
    }
}

Additionally, you need to update your AndroidManifest.xml file

    </application>
        ...

        <receiver
            android:name="eu.sisik.devowner.UpdateReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_REPLACED" />
                <data android:scheme="package"/>
            </intent-filter>
        </receiver>

    </application>

Useful Links

If you're not sure how to set up a device owner app, checkout my previous post.

Additionally, if you're doing some Android device maintenance on-the-go, you might checkout my Bugjaeger app, which offers some ADB functionality directly from your phone.

Next Post Previous Post

Add a comment

Comments

Are you getting some error message? What Android version are you using? For Android 7 and higher, you need to set the "testOnly" flag in AndroidManifest.xml to activate device owner with ADB. But the APK file that will be used as update should NOT have testOnly flag set. I never tried this on an emulator, so I'm not sure if that also could not be an issue...
Written on Thu, 26 Jul 2018 16:15:06 by Roman
App is not getting updated. I m trying is emulator. Can you please help me out.
Written on Thu, 26 Jul 2018 15:05:52 by Namita