DownloadWorker: Trigger app installation on success

* Transparent dummy activity to trigger installation of apps from notification

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2023-10-28 15:25:39 +05:30
parent 6f707b2a37
commit 01dee174bc
7 changed files with 116 additions and 22 deletions

View File

@@ -91,6 +91,11 @@
<nav-graph android:value="@navigation/mobile_navigation" />
</activity>
<activity
android:name=".data.activity.InstallActivity"
android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<service android:name=".data.service.NotificationService" />
<service android:name=".data.installer.InstallerService" />
<service android:name=".data.service.UpdateService" />
@@ -123,5 +128,8 @@
<receiver android:name=".data.receiver.DownloadResumeReceiver" />
<receiver android:name=".data.receiver.DownloadPauseReceiver" />
<receiver android:name=".data.receiver.DownloadCancelReceiver" />
<receiver
android:name=".data.receiver.InstallReceiver"
android:exported="false" />
</application>
</manifest>

View File

@@ -23,6 +23,7 @@ object Constants {
const val INT_EXTRA = "INT_EXTRA"
const val FLOAT_EXTRA = "FLOAT_EXTRA"
const val STRING_APP = "STRING_APP"
const val STRING_VERSION = "STRING_VERSION"
const val STRING_EXTRA = "STRING_EXTRA"
const val BROWSE_EXTRA = "BROWSE_EXTRA"

View File

@@ -0,0 +1,42 @@
package com.aurora.store.data.activity
import android.app.Activity
import android.os.Bundle
import android.util.Log
import com.aurora.Constants
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.util.PathUtil
import java.io.File
import kotlin.io.path.pathString
class InstallActivity : Activity() {
private val TAG = InstallActivity::class.java.simpleName
private lateinit var packageName: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
packageName = intent.extras?.getString(Constants.STRING_APP) ?: String()
val version = intent.extras?.getInt(Constants.STRING_VERSION)
if (packageName.isNotBlank() && version != null) {
try {
val downloadDir =
File(PathUtil.getAppDownloadDir(this, packageName, version).pathString)
AppInstaller.getInstance(this).getPreferredInstaller()
.install(
packageName,
downloadDir.listFiles()!!.filter { it.path.endsWith(".apk") }
)
} catch (exception: Exception) {
Log.e(TAG, "Failed to install $packageName")
}
}
}
override fun onResume() {
super.onResume()
finish()
}
}

View File

@@ -21,16 +21,32 @@ package com.aurora.store.data.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.aurora.Constants
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.util.PathUtil
import java.io.File
import kotlin.io.path.pathString
class InstallReceiver : BroadcastReceiver() {
private val TAG = InstallReceiver::class.java.simpleName
override fun onReceive(context: Context, intent: Intent) {
val extras = intent.extras
if (extras != null) {
/*val packageName = extras.getString(Constants.INTENT_PACKAGE_NAME, "")
val versionString = extras.getString(Constants.DOWNLOAD_VERSION_CODE)
if (!packageName.isEmpty() && versionString != null) {
AuroraApplication.getInstaller().installSplit(packageName, versionString.toInt())
}*/
val packageName = intent.extras?.getString(Constants.STRING_APP) ?: String()
val version = intent.extras?.getInt(Constants.STRING_VERSION)
if (packageName.isNotBlank() && version != null) {
try {
val downloadDir =
File(PathUtil.getAppDownloadDir(context, packageName, version).pathString)
AppInstaller.getInstance(context).getPreferredInstaller()
.install(
packageName,
downloadDir.listFiles()!!.filter { it.path.endsWith(".apk") }
)
} catch (exception: Exception) {
Log.e(TAG, "Failed to install $packageName")
}
}
}
}
}

View File

@@ -3,6 +3,7 @@ package com.aurora.store.data.work
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.util.Log
import androidx.work.CoroutineWorker
@@ -13,6 +14,7 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.aurora.Constants
import com.aurora.extensions.copyTo
import com.aurora.extensions.isQAndAbove
import com.aurora.gplayapi.data.models.App
@@ -21,6 +23,7 @@ import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.model.Request
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.data.receiver.InstallReceiver
import com.aurora.store.util.NotificationUtil
import com.aurora.store.util.PathUtil
import com.google.gson.Gson
@@ -63,7 +66,6 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
}
private lateinit var app: App
private lateinit var appDownloadDir: Path
private var downloading = false
private val TAG = DownloadWorker::class.java.simpleName
@@ -81,7 +83,6 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
withContext(Dispatchers.Default) {
try {
app = gson.fromJson(inputData.getString(DOWNLOAD_DATA), App::class.java)
appDownloadDir = Path(PathUtil.getPackageDirectory(appContext, app.packageName))
} catch (exception: Exception) {
Log.e(TAG, "Failed parsing requested app!", exception)
notifyStatus(DownloadStatus.FAILED)
@@ -101,7 +102,7 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
}
// Download and verify all files exists
Path(appDownloadDir.absolutePathString(), app.versionCode.toString()).createDirectories()
PathUtil.getAppDownloadDir(appContext, app.packageName, app.versionCode).createDirectories()
val requestList = getDownloadRequest(files)
requestList.forEach { request ->
@@ -115,6 +116,14 @@ class DownloadWorker(private val appContext: Context, workerParams: WorkerParame
// Mark download as completed
notifyStatus(DownloadStatus.COMPLETED)
Log.i(TAG, "Finished downloading ${app.packageName}")
// Notify for installation
Intent(appContext, InstallReceiver::class.java).also {
it.putExtra(Constants.STRING_APP, app.packageName)
it.putExtra(Constants.STRING_VERSION, app.versionCode)
appContext.sendBroadcast(it)
}
return Result.success()
}

View File

@@ -20,11 +20,11 @@ import com.aurora.extensions.isMAndAbove
import com.aurora.gplayapi.data.models.App
import com.aurora.store.MainActivity
import com.aurora.store.R
import com.aurora.store.data.activity.InstallActivity
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.receiver.DownloadCancelReceiver
import com.aurora.store.data.receiver.DownloadPauseReceiver
import com.aurora.store.data.receiver.DownloadResumeReceiver
import com.aurora.store.data.receiver.InstallReceiver
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.FetchGroup
import com.tonyodev.fetch2.Status
@@ -231,6 +231,13 @@ object NotificationUtil {
builder.setAutoCancel(true)
builder.setCategory(Notification.CATEGORY_STATUS)
builder.setContentIntent(getContentIntentForDetails(context, app))
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.ic_install,
context.getString(R.string.action_install),
getInstallIntent(context, app.packageName, app.versionCode)
).build()
)
}
DownloadStatus.DOWNLOADING, DownloadStatus.QUEUED -> {
@@ -314,17 +321,22 @@ object NotificationUtil {
.createPendingIntent()
}
private fun getInstallIntent(context: Context, packageName: String): PendingIntent {
val intent = Intent(context, InstallReceiver::class.java)
intent.putExtra(Constants.STRING_EXTRA, packageName)
val flags = if (isMAndAbove())
private fun getInstallIntent(
context: Context,
packageName: String,
version: Int
): PendingIntent {
val intent = Intent(context, InstallActivity::class.java).apply {
putExtra(Constants.STRING_APP, packageName)
putExtra(Constants.STRING_VERSION, version)
}
val flags = if (isMAndAbove()) {
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
else PendingIntent.FLAG_CANCEL_CURRENT
return PendingIntent.getBroadcast(
context,
packageName.hashCode(),
intent,
flags
} else {
PendingIntent.FLAG_CANCEL_CURRENT
}
return PendingIntent.getActivity(
context, packageName.hashCode(), intent, flags
)
}
}

View File

@@ -24,7 +24,9 @@ import android.os.Environment
import com.aurora.extensions.isRAndAbove
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.File
import java.nio.file.Path
import java.util.UUID
import kotlin.io.path.Path
fun Context.getInternalBaseDirectory(): String {
return (getExternalFilesDir(null) ?: filesDir).path
@@ -52,6 +54,10 @@ object PathUtil {
return getPackageDirectory(context, packageName) + "/$versionCode"
}
fun getAppDownloadDir(context: Context, packageName: String, versionCode: Int): Path {
return Path(getPackageDirectory(context, packageName), versionCode.toString())
}
fun getApkDownloadFile(context: Context, app: App, file: File): String {
return getVersionDirectory(context, app.packageName, app.versionCode) + "/${file.name}"
}