Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android PackageInstaller installation result

I use PackageInstaller and PackageInstaller.Session to install an app from my app. I would like to display a message in my app when the installation fails (e.g. in case of bad signature of an apk). How can I get result of the installation session?

like image 764
Martin Dusek Avatar asked Jul 29 '26 03:07

Martin Dusek


1 Answers

When you commit() your Session, you need to supply an IntentSender, which points back to some component, such as a BroadcastReceiver. Here, I do that in installCoroutine():

/*
  Copyright (c) 2019 CommonsWare, LLC

  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  use this file except in compliance with the License. You may obtain   a copy
  of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
  by applicable law or agreed to in writing, software distributed under the
  License is distributed on an "AS IS" BASIS,   WITHOUT WARRANTIES OR CONDITIONS
  OF ANY KIND, either express or implied. See the License for the specific
  language governing permissions and limitations under the License.

  Covered in detail in the book _Elements of Android Q

  https://commonsware.com/AndroidQ
*/

package com.commonsware.q.appinstaller

import android.app.Application
import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageInstaller
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

private const val NAME = "mostly-unused"
private const val PI_INSTALL = 3439

class MainMotor(app: Application) : AndroidViewModel(app) {
  private val installer = app.packageManager.packageInstaller
  private val resolver = app.contentResolver

  fun install(apkUri: Uri) {
    viewModelScope.launch(Dispatchers.Main) {
      installCoroutine(apkUri)
    }
  }

  private suspend fun installCoroutine(apkUri: Uri) =
    withContext(Dispatchers.IO) {
      resolver.openInputStream(apkUri)?.use { apkStream ->
        val length =
          DocumentFile.fromSingleUri(getApplication(), apkUri)?.length() ?: -1
        val params =
          PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
        val sessionId = installer.createSession(params)
        val session = installer.openSession(sessionId)

        session.openWrite(NAME, 0, length).use { sessionStream ->
          apkStream.copyTo(sessionStream)
          session.fsync(sessionStream)
        }

        val intent = Intent(getApplication(), InstallReceiver::class.java)
        val pi = PendingIntent.getBroadcast(
          getApplication(),
          PI_INSTALL,
          intent,
          PendingIntent.FLAG_UPDATE_CURRENT
        )

        session.commit(pi.intentSender)
        session.close()
      }
    }
}

Your component can then look at EXTRA_STATUS to get the status:

/*
  Copyright (c) 2019 CommonsWare, LLC

  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  use this file except in compliance with the License. You may obtain   a copy
  of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
  by applicable law or agreed to in writing, software distributed under the
  License is distributed on an "AS IS" BASIS,   WITHOUT WARRANTIES OR CONDITIONS
  OF ANY KIND, either express or implied. See the License for the specific
  language governing permissions and limitations under the License.

  Covered in detail in the book _Elements of Android Q

  https://commonsware.com/AndroidQ
*/

package com.commonsware.q.appinstaller

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.media.AudioManager
import android.media.ToneGenerator
import android.util.Log

private const val TAG = "AppInstaller"

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

    when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
      PackageInstaller.STATUS_PENDING_USER_ACTION -> {
        val activityIntent =
          intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)

        context.startActivity(activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
      }
      PackageInstaller.STATUS_SUCCESS ->
        ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100)
          .startTone(ToneGenerator.TONE_PROP_ACK)
      else -> {
        val msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)

        Log.e(TAG, "received $status and $msg")
      }
    }
  }
}

It looks like STATUS_FAILURE_INVALID will be the one for an invalid signature.

like image 50
CommonsWare Avatar answered Jul 30 '26 19:07

CommonsWare



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!