ExportWorker: Show notification on export status when finished

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-07-10 18:29:13 +07:00
parent 294df7aa41
commit d75f70590a
7 changed files with 120 additions and 21 deletions

View File

@@ -1,5 +1,7 @@
package com.aurora.store.data.work package com.aurora.store.data.work
import android.app.NotificationManager
import android.app.Service
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
@@ -11,6 +13,9 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.aurora.gplayapi.data.models.App
import com.aurora.store.data.room.download.Download
import com.aurora.store.util.NotificationUtil
import com.aurora.store.util.PackageUtil.getPackageInfo import com.aurora.store.util.PackageUtil.getPackageInfo
import com.aurora.store.util.PathUtil import com.aurora.store.util.PathUtil
import dagger.assisted.Assisted import dagger.assisted.Assisted
@@ -35,17 +40,19 @@ class ExportWorker @AssistedInject constructor(
private const val IS_DOWNLOAD = "IS_DOWNLOAD" private const val IS_DOWNLOAD = "IS_DOWNLOAD"
private const val PACKAGE_NAME = "PACKAGE_NAME" private const val PACKAGE_NAME = "PACKAGE_NAME"
private const val VERSION_CODE = "VERSION_CODE" private const val VERSION_CODE = "VERSION_CODE"
private const val DISPLAY_NAME = "DISPLAY_NAME"
/** /**
* Exports the installed package to the given URI * Exports the installed package to the given URI
* @param packageName packageName of the app to export * @param app App to export
* @see [ExportWorker] * @see [ExportWorker]
*/ */
fun exportInstalledApp(context: Context, packageName: String, uri: Uri) { fun exportInstalledApp(context: Context, app: App, uri: Uri) {
val inputData = Data.Builder() val inputData = Data.Builder()
.putBoolean(IS_DOWNLOAD, false) .putBoolean(IS_DOWNLOAD, false)
.putString(URI, uri.toString()) .putString(URI, uri.toString())
.putString(PACKAGE_NAME, packageName) .putString(DISPLAY_NAME, app.displayName)
.putString(PACKAGE_NAME, app.packageName)
.build() .build()
val oneTimeWorkRequest = OneTimeWorkRequestBuilder<ExportWorker>() val oneTimeWorkRequest = OneTimeWorkRequestBuilder<ExportWorker>()
@@ -53,22 +60,22 @@ class ExportWorker @AssistedInject constructor(
.setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST) .setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST)
.build() .build()
Log.i(TAG, "Exporting $packageName") Log.i(TAG, "Exporting $app.packageName")
WorkManager.getInstance(context).enqueue(oneTimeWorkRequest) WorkManager.getInstance(context).enqueue(oneTimeWorkRequest)
} }
/** /**
* Exports the given download to the URI * Exports the given download to the URI
* @param packageName Name of the package to export * @param download Download to export
* @param versionCode version of the package
* @see [ExportWorker] * @see [ExportWorker]
*/ */
fun exportDownloadedApp(context: Context, packageName: String, versionCode: Int, uri: Uri) { fun exportDownloadedApp(context: Context, download: Download, uri: Uri) {
val inputData = Data.Builder() val inputData = Data.Builder()
.putBoolean(IS_DOWNLOAD, true) .putBoolean(IS_DOWNLOAD, true)
.putString(URI, uri.toString()) .putString(URI, uri.toString())
.putString(PACKAGE_NAME, packageName) .putString(DISPLAY_NAME, download.displayName)
.putInt(VERSION_CODE, versionCode) .putString(PACKAGE_NAME, download.packageName)
.putInt(VERSION_CODE, download.versionCode)
.build() .build()
val oneTimeWorkRequest = OneTimeWorkRequestBuilder<ExportWorker>() val oneTimeWorkRequest = OneTimeWorkRequestBuilder<ExportWorker>()
@@ -76,19 +83,27 @@ class ExportWorker @AssistedInject constructor(
.setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST) .setExpedited(OutOfQuotaPolicy.DROP_WORK_REQUEST)
.build() .build()
Log.i(TAG, "Exporting download for ${packageName}/${versionCode}") Log.i(TAG, "Exporting download for ${download.packageName}/${download.versionCode}")
WorkManager.getInstance(context).enqueue(oneTimeWorkRequest) WorkManager.getInstance(context).enqueue(oneTimeWorkRequest)
} }
} }
private lateinit var notificationManager: NotificationManager
private val NOTIFICATION_ID = 500
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
val isDownload = inputData.getBoolean(IS_DOWNLOAD, false) val isDownload = inputData.getBoolean(IS_DOWNLOAD, false)
val uri = Uri.parse(inputData.getString(URI)) val uri = Uri.parse(inputData.getString(URI))
val packageName = inputData.getString(PACKAGE_NAME) val packageName = inputData.getString(PACKAGE_NAME)
val displayName = inputData.getString(DISPLAY_NAME)
val versionCode = inputData.getInt(VERSION_CODE, -1) val versionCode = inputData.getInt(VERSION_CODE, -1)
notificationManager =
appContext.getSystemService(Service.NOTIFICATION_SERVICE) as NotificationManager
if (packageName.isNullOrEmpty() || isDownload && versionCode == -1) { if (packageName.isNullOrEmpty() || isDownload && versionCode == -1) {
Log.e(TAG, "Input data is corrupt, bailing out!") Log.e(TAG, "Input data is corrupt, bailing out!")
notifyStatus(displayName?: String(), uri, false)
return Result.failure() return Result.failure()
} }
@@ -98,14 +113,28 @@ class ExportWorker @AssistedInject constructor(
} else { } else {
copyInstalledApp(packageName, uri) copyInstalledApp(packageName, uri)
} }
notifyStatus(displayName ?: packageName, uri)
} catch (exception: Exception) { } catch (exception: Exception) {
Log.e(TAG, "Failed to export $packageName", exception) Log.e(TAG, "Failed to export $packageName", exception)
notifyStatus(displayName?: packageName, uri, false)
return Result.failure() return Result.failure()
} }
return Result.success() return Result.success()
} }
private fun notifyStatus(packageName: String, uri: Uri, success: Boolean = true) {
notificationManager.notify(
NOTIFICATION_ID,
NotificationUtil.getExportStatusNotification(
appContext,
packageName,
uri,
success
)
)
}
private fun copyInstalledApp(packageName: String, uri: Uri) { private fun copyInstalledApp(packageName: String, uri: Uri) {
val packageInfo = getPackageInfo(appContext, packageName, PackageManager.GET_META_DATA) val packageInfo = getPackageInfo(appContext, packageName, PackageManager.GET_META_DATA)
val fileList: MutableList<File?> = mutableListOf() val fileList: MutableList<File?> = mutableListOf()

View File

@@ -9,6 +9,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.net.Uri
import android.os.Build import android.os.Build
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.PendingIntentCompat import androidx.core.app.PendingIntentCompat
@@ -250,6 +251,64 @@ object NotificationUtil {
.build() .build()
} }
fun getExportStatusNotification(
context: Context,
displayName: String,
uri: Uri,
success: Boolean
): Notification {
val intent = Intent(Intent.ACTION_SEND).apply {
setDataAndType(uri, "application/zip")
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
putExtra(Intent.EXTRA_STREAM, uri)
}
val pendingIntent = PendingIntentCompat.getActivity(
context,
UUID.randomUUID().hashCode(),
Intent.createChooser(intent, null),
PendingIntent.FLAG_CANCEL_CURRENT,
false
)
val content = if (success) {
context.getString(R.string.export_app_summary_success)
} else {
context.getString(R.string.export_app_summary_fail)
}
val builder = NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_ALERT)
.setSmallIcon(R.drawable.ic_file_copy)
.setColor(context.getStyledAttributeColor(R.color.colorAccent))
.setContentTitle(displayName).setContentText(content)
.setContentIntent(getContentIntentForExport(context, uri))
.setAutoCancel(true)
if (success) {
builder.addAction(
NotificationCompat.Action.Builder(
R.drawable.ic_share,
context.getString(R.string.action_share),
pendingIntent
).build()
)
}
return builder.build()
}
private fun getContentIntentForExport(context: Context, uri: Uri): PendingIntent? {
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "application/zip")
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}
return PendingIntentCompat.getActivity(
context,
UUID.randomUUID().hashCode(),
intent,
PendingIntent.FLAG_CANCEL_CURRENT,
false
)
}
private fun getContentIntentForDetails(context: Context, packageName: String): PendingIntent { private fun getContentIntentForDetails(context: Context, packageName: String): PendingIntent {
return NavDeepLinkBuilder(context) return NavDeepLinkBuilder(context)
.setGraph(R.navigation.mobile_navigation) .setGraph(R.navigation.mobile_navigation)

View File

@@ -57,7 +57,7 @@ class AppMenuSheet : BottomSheetDialogFragment(R.layout.sheet_app_menu) {
private val requestDocumentCreation = private val requestDocumentCreation =
registerForActivityResult(ActivityResultContracts.CreateDocument(exportMimeType)) { registerForActivityResult(ActivityResultContracts.CreateDocument(exportMimeType)) {
if (it != null) { if (it != null) {
viewModel.copyInstalledApp(requireContext(), args.app.packageName, it) viewModel.copyInstalledApp(requireContext(), args.app, it)
} else { } else {
toast(R.string.failed_apk_export) toast(R.string.failed_apk_export)
} }

View File

@@ -59,12 +59,7 @@ class DownloadMenuSheet : BottomSheetDialogFragment(R.layout.sheet_download_menu
private val requestDocumentCreation = private val requestDocumentCreation =
registerForActivityResult(ActivityResultContracts.CreateDocument(exportMimeType)) { registerForActivityResult(ActivityResultContracts.CreateDocument(exportMimeType)) {
if (it != null) { if (it != null) {
viewModel.copyDownloadedApp( viewModel.copyDownloadedApp(requireContext(), args.download, it)
requireContext(),
args.download.packageName,
args.download.versionCode,
it
)
} else { } else {
toast(R.string.failed_apk_export) toast(R.string.failed_apk_export)
} }

View File

@@ -9,6 +9,7 @@ import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.helpers.PurchaseHelper import com.aurora.gplayapi.helpers.PurchaseHelper
import com.aurora.store.data.event.BusEvent import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.data.room.download.Download
import com.aurora.store.data.work.ExportWorker import com.aurora.store.data.work.ExportWorker
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
@@ -45,11 +46,11 @@ class SheetsViewModel @Inject constructor(
} }
} }
fun copyInstalledApp(context: Context, packageName: String, uri: Uri) { fun copyInstalledApp(context: Context, app: App, uri: Uri) {
ExportWorker.exportInstalledApp(context, packageName, uri) ExportWorker.exportInstalledApp(context, app, uri)
} }
fun copyDownloadedApp(context: Context, packageName: String, versionCode: Int, uri: Uri) { fun copyDownloadedApp(context: Context, download: Download, uri: Uri) {
ExportWorker.exportDownloadedApp(context, packageName, versionCode, uri) ExportWorker.exportDownloadedApp(context, download, uri)
} }
} }

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M760,760L320,760Q287,760 263.5,736.5Q240,713 240,680L240,120Q240,87 263.5,63.5Q287,40 320,40L600,40L840,280L840,680Q840,713 816.5,736.5Q793,760 760,760ZM560,320L560,120L320,120Q320,120 320,120Q320,120 320,120L320,680Q320,680 320,680Q320,680 320,680L760,680Q760,680 760,680Q760,680 760,680L760,320L560,320ZM160,920Q127,920 103.5,896.5Q80,873 80,840L80,280L160,280L160,840Q160,840 160,840Q160,840 160,840L600,840L600,920L160,920ZM320,120L320,120L320,320L320,320L320,120L320,320L320,320L320,680Q320,680 320,680Q320,680 320,680L320,680Q320,680 320,680Q320,680 320,680L320,120Q320,120 320,120Q320,120 320,120Z"/>
</vector>

View File

@@ -462,4 +462,8 @@
<string name="toast_fav_export_failed">Failed to export favourites!</string> <string name="toast_fav_export_failed">Failed to export favourites!</string>
<string name="toast_fav_import_success">Favourites imported!</string> <string name="toast_fav_import_success">Favourites imported!</string>
<string name="toast_fav_export_success">Favourites exported!</string> <string name="toast_fav_export_success">Favourites exported!</string>
<!-- ExportWorker -->
<string name="export_app_summary_success">Successfully exported app bundle</string>
<string name="export_app_summary_fail">Failed to exported app bundle</string>
</resources> </resources>