Android Package Manager install not showing - android

I'm trying to build an auto updater via github. The app so far detects new available versions and downloads the apk file. I however cannot get it to install the apk file.
There are no crashes or Log messages indicating an error. The install dialog just does not show up.
This is my code:
fun downloadAndInstall(link: Uri, fileName: String){
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadUri = link
val request = DownloadManager.Request(downloadUri)
request.setMimeType(MIME_TYPE)
val destination = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/" + fileName
val destinationUri = Uri.parse("file://$destination")
request.setDestinationUri(destinationUri)
fun showInstallOption(destination: String) {
val onComplete = object : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
val contentUri = FileProvider.getUriForFile(
context,
BuildConfig.APPLICATION_ID + ".provider",
File(destination)
)
val installer = context.packageManager.packageInstaller
val resolver = context.contentResolver
resolver.openInputStream(contentUri)?.use { apkStream ->
val length =
DocumentFile.fromSingleUri(context, contentUri)?.length() ?: -1
val params =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = installer.createSession(params)
val session = installer.openSession(sessionId)
session.openWrite("INSTALL", 0, length).use { sessionStream ->
apkStream.copyTo(sessionStream)
session.fsync(sessionStream)
}
val intent = Intent(context, InstallReceiver::class.java)
intent.action = "com.blazecode.tsviewer.util.updater.SESSION_API_PACKAGE_INSTALLED"
val pi = PendingIntent.getBroadcast(
context,
3,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
session.commit(pi.intentSender)
session.close()
}
Toast.makeText(context, "done", Toast.LENGTH_LONG).show()
}
}
context.registerReceiver(onComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
showInstallOption(destination)
downloadManager.enqueue(request)
Toast.makeText(context, context.getString(R.string.downloading), Toast.LENGTH_LONG).show()
}
I tested this on Android 13 and 8, both with the same result. Android 13 is a physical device. Android 8 is an emulator.

Related

Android INSTALL_FAILED_UPDATE_INCOMPATIBLE error on Package installer

I can install the application without the play store with the apk I downloaded from the server, but I want to do this without asking for read/write permission.
In Firebase app tester we can install apk without getting read/write permissions. I want to do the same in my own application and I decided to use Package installer for this, but after the APK is downloaded, the update dialog appears on the screen and then I get the error below.
INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package com.xxx.xxx signatures do not match previously installed version; ignoring!
Soloution reference : https://commonsware.com/Q/pages/chap-pkg-001
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun installPackage(destination: String) {
val onComplete = object : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent1: Intent
) {
try {
context.registerReceiver(mInstallReceiver, IntentFilter(ACTION_INSTALL_COMPLETE))
factory.dismiss()
val installer = context.packageManager.packageInstaller
val resolver = context.contentResolver
val contentUri = FileProvider.getUriForFile(
context,
BuildConfig.APPLICATION_ID + PROVIDER_PATH,
File(destination)
)
resolver.openInputStream(contentUri)?.use { apkStream ->
val length = DocumentFile.fromSingleUri(context, contentUri)?.length() ?: -1
val params = SessionParams(SessionParams.MODE_FULL_INSTALL)
params.setAppPackageName(context.packageName)
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(ACTION_INSTALL_COMPLETE)
val pendingIntent = PendingIntent.getBroadcast(context, PI_INSTALL, intent, PendingIntent.FLAG_UPDATE_CURRENT)
session.commit(pendingIntent.intentSender)
session.close()
}
}
catch (e :Exception){
e.printStackTrace()
context.unregisterReceiver(mInstallReceiver)
}
}
}
context.registerReceiver(onComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
private val mInstallReceiver = object : BroadcastReceiver() {
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onReceive(context: Context, intent: Intent) {
if (ACTION_INSTALL_COMPLETE != intent.action) {
return
}
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val install = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
if (install?.resolveActivity(context.packageManager) != null) {
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(install)
}
}
PackageInstaller.STATUS_SUCCESS -> ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100)
.startTone(ToneGenerator.TONE_PROP_ACK)
else -> {
val msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
context.showToast("$msg")
Log.e("Installer app", "received $status and $msg")
}
}
}
}

Android package installer failed silently

I tried to install the APK programmatically using PackageInstaller but it is failing without any error.
The session.commit() executing without any error and calls the callback Intent. But callback intent doesn't receive any extras.
#Throws(IOException::class)
fun installPackage(context: Context, `in`: InputStream, packageName: String): Boolean {
Log.i("install","installer called")
Log.i("install","in iteration")
val packageInstaller: PackageInstaller = context.getPackageManager().getPackageInstaller()
val params = SessionParams(
SessionParams.MODE_FULL_INSTALL
)
params.setAppPackageName(packageName)
// set params
val sessionId = packageInstaller.createSession(params)
val session = packageInstaller.openSession(sessionId)
Log.i("id",""+sessionId)
addApkToInstallSession("india.apk",session);
val intent = Intent(context, InstallResultReceiver::class.java)
intent.setAction("PACKAGE_INSTALLED")
val pendingIntent = PendingIntent.getBroadcast(
context,
sessionId,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
Log.i("t",""+packageInstaller.getSessionInfo(sessionId).toString())
try {
session.commit(pendingIntent.intentSender)
}catch(e:Exception){
Log.i("",""+e.stackTrace)
}
Log.i("Down","install committed")
return true
}
#Throws(IOException::class)
private fun addApkToInstallSession(assetName: String, session: PackageInstaller.Session) {
session.openWrite("package", 0, -1).use { packageInSession ->
assets.open(assetName).use { `is` ->
val buffer = ByteArray(16384)
var n: Int
while (`is`.read(buffer).also { n = it } >= 0) {
packageInSession.write(buffer, 0, n)
}
}
}
}
You have to change the flags on PendingIntent.getBroadcast(). Try the following:
var flags = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
flags = PendingIntent.FLAG_MUTABLE
}
val pendingIntent = PendingIntent.getBroadcast(
context,
sessionId,
intent,
flags
)

How to Open APK File in Android 10

i am trying to open an apk file from within my activity the following way:
File.openAttachment(attachmentName: String, context: Context, activity: Activity, mimeType: String) {
if (attachmentName.isNotEmpty() && attachmentName != "-") {
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
intent.setDataAndType(this.getFileURI(), mimeType.toLowerCase(Locale.ENGLISH))
activity.startActivity(Intent.createChooser(intent, context.getString(R.string.choose_an_app)))
}
}
File(Utils.getAttachmentPathDirectory(MyApplication.applicationContext())+"/apkTest.apk")
.openAttachment("apkTest.apk", this, this, "application/vnd.android.package-archive")
i also tried the following way:
val intent_install = Intent(Intent.ACTION_VIEW)
intent_install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent_install.setDataAndType(FileProvider.getUriForFile(mContext, mContext.packageName+".provider",File(Utils.getAttachmentPathDirectory(MyApplication.applicationContext())+"/"+fileName)), "application/vnd.android.package-archive")
mActivity?.startActivity(intent_install)
there are no errors but the file is not opening
any help is appreciated
thanks to #CommonWare and the provided link
https://gitlab.com/commonsguy/cw-android-q/tree/vFINAL/AppInstaller
i have managed to achieve what i want using the following code
val apkUri = FileProvider.getUriForFile(this, packageName+".provider", File(Utils.getAttachmentPathDirectory(MyApplication.applicationContext())+"/testapk.apk"))
MyApplication.applicationContext().contentResolver.openInputStream(apkUri).use { apkStream ->
val length = DocumentFile.fromSingleUri(application, 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(application, InstallReceiver::class.java)
val pi = PendingIntent.getBroadcast(
application,
PI_INSTALL,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
session.commit(pi.intentSender)
session.close()
}
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)
}
}
}

how to download and install an APK file by an intent?

I am making my app to be updated via my own file server instead of Google Play-store. However, it does not work well. After confirming to "update", APK file is downloaded, it is not opened correctly. Please check the demonstration https://youtu.be/qDSGZ9fQ1Oo
class MainActivity : Activity() {
private fun checkUpdate(){
val jsonObjectRequest = JsonObjectRequest(
Request.Method.GET,
"https://myserver/release.json",
null,
Response.Listener { response ->
if(response.getInt("version") > versionCode){
val builder = AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog_Alert)
builder.setTitle("Update to v" + response.getString("version") + " ?")
builder.setMessage(response.getString("note"))
builder.setPositiveButton("Yes") { _, _ ->
downloadUpdate(response.getString("version"))
}
builder.setNegativeButton("No") { _, _ ->
showUserInteraction()
}
builder.setCancelable(false)
builder.show()
}else{
showUserInteraction()
}
},
Response.ErrorListener{ _ ->
showUserInteraction()
}
)
requestQueue.add(jsonObjectRequest)
}
private fun downloadUpdate(versionCode: String) {
registerReceiver(onDownloadComplete(), IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
val request = DownloadManager
.Request(Uri.parse("https://myserver/app-release.apk"))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "myapp_v" + versionCode + ".apk")
downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
downloadId = downloadManager.enqueue(request)
}
private class onDownloadComplete: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val c = downloadManager.query(DownloadManager.Query().setFilterById(downloadId))
if(c != null){
c.moveToFirst()
val fileUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val mFile = File(Uri.parse(fileUri).path!!)
val fileName = mFile.absolutePath
context.unregisterReceiver(this)
val intent = Intent(Intent.ACTION_VIEW)
var contentUri: Uri
if (SDK_VER >= Build.VERSION_CODES.N) {
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
contentUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", File(fileName))
}else{
contentUri = Uri.fromFile(File(fileName))
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
intent.setDataAndType(contentUri, "application/vnd.android.package-archive")
startActivity(context, intent, null)
}
}
}
}
May anyone please point out my mistake? Thanks.
You need to add below permission in your manifest.xml file.
If an app uses a targetSdkLevel of 26 or above and prompts the user to install other apps, the manifest file needs to include the REQUEST_INSTALL_PACKAGES permission:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
You can see below link why it's needed
Link1
Link2

Second WorkRequest of WorkContinuation do not work

I am trying to start two work request, one worker sends a request to the server for generating excel file and obtains URL for download. Another work starts after previous and must to download that file. First work starts and returns Result.SUCCESS.
Problem is another WorkRequest just not execute. LoadInvoiceFileWorker do nothing.
What I need to do or what I do wrong?
Here is my code:
InvoiceDetailsViewModel:
class InvoiceDetailsViewModel : ViewModel() {
private val mWorkManager: WorkManager = WorkManager.getInstance()
fun generateAndLoadExcel(invoiceId: Int, invoiceName: String, enterpriseId: Int) {
val genInvoiceWorkerBuilder = OneTimeWorkRequest.Builder(GenerateExcelWorker::class.java)
genInvoiceWorkerBuilder.setInputData(createInputDataForGenerateExcel(invoiceId, invoiceName, enterpriseId))
val constraintBuilder = Constraints.Builder()
//constraintBuilder.setRequiredNetworkType(NetworkType.CONNECTED)
genInvoiceWorkerBuilder.setConstraints(constraintBuilder.build())
val continuation = mWorkManager.beginWith(
genInvoiceWorkerBuilder.build()
)
val loadFileWorkerBuilder = OneTimeWorkRequest.Builder(LoadInvoiceFileWorker::class.java)
//loadFileWorkerBuilder.setConstraints(Constraints.NONE)
continuation.then(loadFileWorkerBuilder.build())
continuation.enqueue()
}
private fun createInputDataForGenerateExcel(invoiceId: Int, invoiceName: String, enterpriseId: Int): Data {
val builder = Data.Builder()
builder.putInt(WorkerConstants.INVOICE_ID, invoiceId)
builder.putString(WorkerConstants.INVOICE_NAME, invoiceName)
builder.putInt(WorkerConstants.ENTERPRISE_ID, enterpriseId)
return builder.build()
}
}
GenerateExcelWorker:
class GenerateExcelWorker : Worker() {
companion object {
private val TAG = GenerateExcelWorker::class.java.simpleName
}
override fun doWork(): Result {
val appCont = applicationContext
val tokenType = PreferenceUtil.getString(TOKEN_TYPE, appCont, R.string.shared_pref_name)
val accessToken = PreferenceUtil.getString(ACCESS_TOKEN, appCont, R.string.shared_pref_name)
val enterpriseId = inputData.getInt(WorkerConstants.ENTERPRISE_ID, 0)
val invoiceId = inputData.getInt(WorkerConstants.INVOICE_ID, 0)
val invoiceName = inputData.getString(WorkerConstants.INVOICE_NAME)
makeStatusNotification(applicationContext, invoiceId, invoiceName
?: ("Invoice ${invoiceId.str()}"))
try {
val rd = RequestData()
rd.putValue("authorization", "$tokenType $accessToken", RequestData.TYPE_HEADER)
rd.putValue(FTUrls.SendingParameters.ENTERPRISE_ID, enterpriseId, RequestData.TYPE_PATH)
rd.putValue(FTUrls.SendingParameters.INVOICE_ID, invoiceId, RequestData.TYPE_PATH)
val excelUrl = InvoiceManager().generateIncomeInvoiceExcel(rd)
outputData = Data.Builder().putString(WorkerConstants.FILE_URL, excelUrl).build()
return Result.SUCCESS
} catch (t: Throwable) {
Log.e(TAG, "Error generating excel file for invoice $invoiceName ($invoiceId)", t)
if (t is UnauthenticatedException) {
outputData = Data.Builder().putBoolean(WorkerConstants.FILE_URL, true).build()
} else {
ExceptionLogger.logException(t)
Toast.makeText(applicationContext, t.message, Toast.LENGTH_SHORT).show()
}
return Result.FAILURE
}
}
private fun makeStatusNotification(context: Context, invoiceId: Int, invoiceTitle: String) {
// Make a channel if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
val name = WorkerConstants.NOTIFICATION_CHANNEL_NAME
val description = WorkerConstants.NOTIFICATION_CHANNEL_DESCRIPTION
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(WorkerConstants.CHANNEL_ID, name, importance)
channel.description = description
// Add the channel
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
val builder = NotificationCompat.Builder(context, WorkerConstants.CHANNEL_ID)
.setSmallIcon(R.drawable.ic_autorenew_blue)
.setContentTitle(WorkerConstants.NOTIFICATION_TITLE)
.setContentText(String.format(WorkerConstants.NOTIFICATION_TEXT, invoiceTitle))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setVibrate(LongArray(0))
NotificationManagerCompat.from(context).notify(invoiceId, builder.build())
}
}
LoadInvoiceFileWorker:
class LoadInvoiceFileWorker : Worker() {
companion object {
private val TAG = LoadInvoiceFileWorker::class.java.simpleName
}
override fun doWork(): Result {
try {
val fileUrl = inputData.getString(WorkerConstants.FILE_URL)
val invoiceId = inputData.getInt(WorkerConstants.INVOICE_ID, 0)
val invoiceName = inputData.getString(WorkerConstants.INVOICE_NAME)
val r = DownloadManager.Request(Uri.parse(fileUrl))
// This put the download in the same Download dir the browser uses
r.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, invoiceName
?: ("Invoice ${invoiceId.str()}"))
// Notify user when download is completed
r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
// Start download
val dm = applicationContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager?
if (dm != null) {
dm.enqueue(r)
} else {
Log.w(TAG, "Download manager not exists for load invoice excel file")
ToastError(applicationContext, R.string.download_manager_not_found, Toast.LENGTH_SHORT)
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(fileUrl))
try {
applicationContext.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Log.e(TAG, "Error open browser for view invoice excel file", e)
ToastError(applicationContext, R.string.browser_not_found, Toast.LENGTH_SHORT)
}
}
clearGenerateFileNotification(invoiceId)
return Result.SUCCESS
} catch (t: Throwable) {
Log.e(TAG, "Error loading excel generated file", t)
ExceptionLogger.logException(t)
ToastError(applicationContext, R.string.error_during_loading_file, Toast.LENGTH_SHORT)
return Result.FAILURE
}
}
private fun clearGenerateFileNotification(invoiceId: Int) {
val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(invoiceId)
}
}
WorkerConstants:
object WorkerConstants {
const val ENTERPRISE_ID = "enterprise_id"
const val INVOICE_ID = "invoice_id"
const val INVOICE_NAME = "invoice_name"
const val FILE_URL = "file_url"
const val UNIQUE_WORK_NAME_FOR_INVOICE = "generate_and_load_excel_for_invoice"
const val NOTIFICATION_CHANNEL_NAME = "GenerateExcelWorker Notifications"
const val NOTIFICATION_CHANNEL_DESCRIPTION = "Shows notifications whenever work starts"
const val NOTIFICATION_TITLE = "Генерація ексель файла"
const val NOTIFICATION_TEXT = "Генерація ексель файла накладної %s"
const val CHANNEL_ID = "GENERATE_INVOICE_NOTIFICATION"
}
Ok, I found my mistake. Instead of this:
...
continuation.then(loadFileWorkerBuilder.build())
continuation.enqueue()
I need make this:
...
continuation = continuation.then(loadFileWorkerBuilder.build())
continuation.enqueue()
I was apllying enqueue() for first continuation of one request. Method WorkContinuation.then() returns new object which contains old continuation with new added request.

Categories

Resources