Search code examples
androidapkandroid-package-managersandroid-12

Android 12 breaks installation of APK via the Package Manager?


I was working on an update for my app, a section of which deals with downloading and installing an APK file.

As long as the previous version were targeting SDK 30 everything worked pretty smoothly. But as soon as I incremented the target and compile SDK to 32 it just started behaving queerly.

Here is the code that deals which the package manager and the installation:

private fun installOnClickListener() {
    binding.termuxInstallCard.showProgress()
    var session: PackageInstaller.Session? = null
    try {
        val packageInstaller: PackageInstaller =
            requireContext().packageManager.packageInstaller
        val params = PackageInstaller.SessionParams(
            PackageInstaller.SessionParams.MODE_FULL_INSTALL
        )
        val sessionId = packageInstaller.createSession(params)
        session = packageInstaller.openSession(sessionId)
        viewModel.addApkToSession(session)

        var installBroadcast: PendingIntent? = null
        val intent =
            Intent(PACKAGE_INSTALLED_ACTION).putExtra(
                "packageName",
                "com.termux"
            )

        installBroadcast = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.getBroadcast(
                context,
                0,
                intent,
                FLAG_MUTABLE
            )
        } else {
            PendingIntent.getBroadcast(context, 0, intent, FLAG_UPDATE_CURRENT)
        }
        session.commit(installBroadcast.intentSender)
        session.close()

    } catch (e: IOException) {
        throw RuntimeException("Couldn't install package", e)
    } catch (e: RuntimeException) {
        session?.abandon()
        throw e
    } finally {
        session?.close()
    }

}

Here is what is happening:

As I am targeting SDK 32, I am required to specify the Mutability of PendingIntent

           Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent

When I use:

  1. FLAG_MUTABLE- The installation just fails stating the error code- STATUS_FAILURE_INVALID with no extra message for debugging in EXTRA_STATUS_MESSAGE. The thing is that when I try to install the same downloaded APK via the adb shell, it just installs normally without any issues.
  2. FLAG_IMMUTABLE- The installation succeeds without prompting user with the installation dialog but nothing is actually installed.

More code in case you need it-

fun addApkToInstallSession(
        path: String,
        session: PackageInstaller.Session
    ) {
        val file = File("${context.filesDir.path}/$path")
        val packageInSession: OutputStream = session.openWrite("com.termux", 0, -1)
        val inputStream = FileInputStream(file)
        val byteStream = inputStream.read()
        try {
            var c: Int
            val buffer = ByteArray(16384)
            while (inputStream.read(buffer).also { c = it } >= 0) {
                packageInSession.write(buffer, 0, c)
            }
        } catch (e: IOException) {
            println("IOEX")
        } finally {
            try {
                packageInSession.close()
                inputStream.close()
            } catch (e: IOException) {
                println("IOEX in closing the stream")
            }
        }
    }

private val broadcastReceiverForInstallEvents = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            lifecycleScope.launch(Dispatchers.IO) {
                val extras = intent.extras
                val status = extras!!.getInt(PackageInstaller.EXTRA_STATUS)
                val packageName = extras.getString("packageName")!!
                if (PACKAGE_INSTALLED_ACTION == intent.action) {
                    println("STATUS $status")
                    when (status) {
                        PackageInstaller.STATUS_PENDING_USER_ACTION -> {
                            try {
                                val confirmIntent = extras[Intent.EXTRA_INTENT] as Intent
                                confirmIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                                context.startActivity(confirmIntent)
                            } catch (e: Exception) {
                                lifecycleScope.launch(Dispatchers.Main) {
                                    Toast.makeText(
                                        requireContext(),
                                        "We could not find an application to handle the installation of apps. Please download a package installer.",
                                        Toast.LENGTH_SHORT
                                    ).show()
                                }
                            }
                        }
                        PackageInstaller.STATUS_SUCCESS -> {
                            lifecycleScope.launch(Dispatchers.Main) {
                                println("$packageName Install succeeded!")
                                // todo all done animation

                                binding.termuxInstallCard.markAsComplete()

                                Toast.makeText(requireContext(), "All Done!", Toast.LENGTH_SHORT)
                                    .show()

                                lifecycleScope.launch {
                                    // viewModel.setTermuxSetupDone()
                                }
                                /* redirecting... */
                                Handler(Looper.getMainLooper()).postDelayed({
                                    redirect()
                                }, 2000)

                            }
                        }
                        PackageInstaller.STATUS_FAILURE, PackageInstaller.STATUS_FAILURE_ABORTED, PackageInstaller.STATUS_FAILURE_BLOCKED, PackageInstaller.STATUS_FAILURE_CONFLICT, PackageInstaller.STATUS_FAILURE_INCOMPATIBLE, PackageInstaller.STATUS_FAILURE_INVALID, PackageInstaller.STATUS_FAILURE_STORAGE -> {
                            lifecycleScope.launch(Dispatchers.Main) {
                                println("Extra Status Message${extras.getString("EXTRA_STATUS_MESSAGE")}")
                                "There was an error installing Termux. Please retry.".showSnackbar(
                                    binding.root,
                                    true
                                )
                                binding.termuxInstallCard.hideProgress()
                            }
                        }
                        else -> {
                            lifecycleScope.launch(Dispatchers.Main) {
                                println("$packageName Install failed else!")
                                //  exitActivity("Package failed to install -> Unknown Error!") 
                                binding.termuxInstallCard.hideProgress()
                            }
                        }
                    }
                }
            }
        }
    }

I would really appreciate some help!


Solution

  • On calculating the hash of the file I was committing to the session of the Package Manager, I found out that it didn't write the APK file correctly.

    val inputStream: InputStream = session.openRead("com.termux")
    
            val file = File("${requireContext().filesDir.path}/test2.apk")
            if (!file.exists()) {
                file.createNewFile()
            }
            try {
                inputStream.use { input ->
                    file.outputStream().use { output ->
                        input.copyTo(output)
                    }
                }
            } catch (e: Exception) {
                println("Exception occured $e")
            }
            if (file.length() > 0) {
                val hash = file.getMD5Hash()
                println("HASH of session - $hash")
            }
    

    Fixed that and with the combination of the Mutable pending intent. The package is now installing perfectly.