diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5fef1623c24ed4d1c91ab03771aa171a17d32cb2..c9eae0e242fe2767335240c7eb7a6a701113495e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -50,6 +50,7 @@
+
downloadNativeApp(fusedDownload)
- Type.PWA -> pwaManagerModule.installPWAApp(fusedDownload)
+ mutex.withLock {
+ when (fusedDownload.type) {
+ Type.NATIVE -> downloadNativeApp(fusedDownload)
+ Type.PWA -> pwaManagerModule.installPWAApp(fusedDownload)
+ }
}
}
@@ -101,9 +109,13 @@ class FusedManagerImpl @Inject constructor(
parentPathFile.listFiles()?.let { list.addAll(it) }
list.sort()
if (list.size != 0) {
- Log.d(TAG, "installApp: STARTED ${fusedDownload.name} ${list.size}")
- pkgManagerModule.installApplication(list, fusedDownload.packageName)
- Log.d(TAG, "installApp: ENDED ${fusedDownload.name} ${list.size}")
+ try {
+ Log.d(TAG, "installApp: STARTED ${fusedDownload.name} ${list.size}")
+ pkgManagerModule.installApplication(list, fusedDownload.packageName)
+ Log.d(TAG, "installApp: ENDED ${fusedDownload.name} ${list.size}")
+ } catch (e: Exception) {
+ installationIssue(fusedDownload)
+ }
}
}
else -> {
diff --git a/app/src/main/java/foundation/e/apps/manager/pkg/InstallerService.kt b/app/src/main/java/foundation/e/apps/manager/pkg/InstallerService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..236dea7d265907ca38937d91ac9a28bf9643f622
--- /dev/null
+++ b/app/src/main/java/foundation/e/apps/manager/pkg/InstallerService.kt
@@ -0,0 +1,85 @@
+/*
+ * Apps Quickly and easily install Android apps onto your device!
+ * Copyright (C) 2021 E FOUNDATION
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.apps.manager.pkg
+
+import android.app.Service
+import android.content.Intent
+import android.content.pm.PackageInstaller
+import android.os.Build
+import android.os.IBinder
+import android.util.Log
+import androidx.annotation.RequiresApi
+import dagger.hilt.android.AndroidEntryPoint
+import foundation.e.apps.manager.fused.FusedManagerRepository
+import foundation.e.apps.utils.enums.Status
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class InstallerService : Service() {
+
+ @Inject
+ lateinit var fusedManagerRepository: FusedManagerRepository
+
+ @Inject
+ lateinit var pkgManagerModule: PkgManagerModule
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -69)
+ val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
+ val extra = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
+ postStatus(status, packageName, extra)
+ stopSelf()
+ return START_NOT_STICKY
+ }
+
+ private fun postStatus(status: Int, packageName: String?, extra: String?) {
+ Log.d("InstallerService", "postStatus: $status $packageName $extra")
+ if (status != PackageInstaller.STATUS_SUCCESS) {
+ updateInstallationIssue(packageName ?: "")
+ }
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ return null
+ }
+
+ private fun updateDownloadStatus(pkgName: String) {
+ if (pkgName.isEmpty()) {
+ Log.d("PkgManagerBR", "updateDownloadStatus: package name should not be empty!")
+ }
+ GlobalScope.launch {
+ val fusedDownload = fusedManagerRepository.getFusedDownload(packageName = pkgName)
+ pkgManagerModule.setFakeStoreAsInstallerIfNeeded(fusedDownload)
+ fusedManagerRepository.updateDownloadStatus(fusedDownload, Status.INSTALLED)
+ }
+ }
+
+ private fun updateInstallationIssue(pkgName: String) {
+ if (pkgName.isEmpty()) {
+ Log.d("PkgManagerBR", "updateDownloadStatus: package name should not be empty!")
+ }
+ GlobalScope.launch {
+ val fusedDownload = fusedManagerRepository.getFusedDownload(packageName = pkgName)
+ fusedManagerRepository.installationIssue(fusedDownload)
+ }
+ }
+}
diff --git a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt
index 4ed8fff902ac0426739d8d506c979c3e1ac8e759..716a935d51b85858f66d8e85e1ddf131fa32d902 100644
--- a/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt
+++ b/app/src/main/java/foundation/e/apps/manager/pkg/PkgManagerModule.kt
@@ -148,15 +148,18 @@ class PkgManagerModule @Inject constructor(
inputStream.close()
outputStream.close()
}
- val pendingIntent = PendingIntent.getBroadcast(
+
+ val callBackIntent = Intent(context, InstallerService::class.java)
+ val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE else
+ PendingIntent.FLAG_UPDATE_CURRENT
+ val servicePendingIntent = PendingIntent.getService(
context,
sessionId,
- Intent(Intent.ACTION_PACKAGE_ADDED),
- PendingIntent.FLAG_IMMUTABLE
+ callBackIntent,
+ flags
)
-
- session.commit(pendingIntent.intentSender)
- session.close()
+ session.commit(servicePendingIntent.intentSender)
} catch (e: Exception) {
Log.e(TAG, "$packageName: \n${e.stackTraceToString()}")
val pendingIntent = PendingIntent.getBroadcast(
@@ -165,8 +168,10 @@ class PkgManagerModule @Inject constructor(
Intent(ERROR_PACKAGE_INSTALL),
PendingIntent.FLAG_IMMUTABLE
)
-
session.commit(pendingIntent.intentSender)
+ session.abandon()
+ throw e
+ } finally {
session.close()
}
}
diff --git a/app/src/main/java/foundation/e/apps/manager/workmanager/InstallAppWorker.kt b/app/src/main/java/foundation/e/apps/manager/workmanager/InstallAppWorker.kt
index 196950bea8161e0385c89584af96772e1303f016..73438981efd22c6080670aee826de2af08d57ec2 100644
--- a/app/src/main/java/foundation/e/apps/manager/workmanager/InstallAppWorker.kt
+++ b/app/src/main/java/foundation/e/apps/manager/workmanager/InstallAppWorker.kt
@@ -1,17 +1,25 @@
package foundation.e.apps.manager.workmanager
import android.app.DownloadManager
+import android.app.NotificationChannel
+import android.app.NotificationManager
import android.content.Context
+import android.os.Build
import android.util.Log
+import androidx.core.app.NotificationCompat
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
+import androidx.work.ForegroundInfo
+import androidx.work.WorkManager
import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
+import foundation.e.apps.R
import foundation.e.apps.manager.database.DatabaseRepository
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
import foundation.e.apps.manager.fused.FusedManagerRepository
import foundation.e.apps.utils.enums.Status
+import foundation.e.apps.utils.enums.Type
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@@ -19,6 +27,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
+import java.util.concurrent.atomic.AtomicInteger
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@@ -39,20 +48,26 @@ class InstallAppWorker @AssistedInject constructor(
const val INPUT_DATA_FUSED_DOWNLOAD = "input_data_fused_download"
}
+ private val atomicInteger = AtomicInteger()
+
override suspend fun doWork(): Result {
var fusedDownload: FusedDownload? = null
try {
val fusedDownloadString = params.inputData.getString(INPUT_DATA_FUSED_DOWNLOAD) ?: ""
Log.d(TAG, "Fused download name $fusedDownloadString")
-
fusedDownload = databaseRepository.getDownloadById(fusedDownloadString)
fusedDownload?.let {
if (fusedDownload.status != Status.AWAITING) {
return@let
}
+ setForeground(
+ createForegroundInfo(
+ "Installing ${it.name}"
+ )
+ )
startAppInstallationProcess(it)
}
-
+ Log.d(TAG, "doWork: RESULT SUCCESS: ${fusedDownload?.name}")
return Result.success()
} catch (e: Exception) {
Log.e(TAG, "doWork: Failed: ${e.stackTraceToString()}")
@@ -68,14 +83,14 @@ class InstallAppWorker @AssistedInject constructor(
) {
fusedManagerRepository.downloadApp(fusedDownload)
Log.d(TAG, "===> doWork: Download started ${fusedDownload.name} ${fusedDownload.status}")
- isDownloading = true
-
- tickerFlow(3.seconds)
- .onEach {
- checkDownloadProcess(fusedDownload)
- }.launchIn(CoroutineScope(Dispatchers.Default))
-
- observeDownload(fusedDownload)
+ if (fusedDownload.type == Type.NATIVE) {
+ isDownloading = true
+ tickerFlow(1.seconds)
+ .onEach {
+ checkDownloadProcess(fusedDownload)
+ }.launchIn(CoroutineScope(Dispatchers.IO))
+ observeDownload(fusedDownload)
+ }
}
private fun tickerFlow(period: Duration, initialDelay: Duration = Duration.ZERO) = flow {
@@ -99,16 +114,12 @@ class InstallAppWorker @AssistedInject constructor(
cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
val bytesDownloadedSoFar =
cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
-
Log.d(
TAG,
- "checkDownloadProcess: ${fusedDownload.name} $id $status $totalSizeBytes $bytesDownloadedSoFar"
+ "checkDownloadProcess: ${fusedDownload.name} $bytesDownloadedSoFar/$totalSizeBytes $status"
)
- if (status == DownloadManager.STATUS_SUCCESSFUL || status == DownloadManager.STATUS_FAILED) {
- isDownloading = false
- if (status == DownloadManager.STATUS_FAILED) {
- fusedManagerRepository.installationIssue(fusedDownload)
- }
+ if (status == DownloadManager.STATUS_FAILED) {
+ fusedManagerRepository.installationIssue(fusedDownload)
}
}
}
@@ -133,14 +144,13 @@ class InstallAppWorker @AssistedInject constructor(
private fun handleFusedDownloadStatus(fusedDownload: FusedDownload) {
when (fusedDownload.status) {
- Status.DOWNLOADING -> {
+ Status.AWAITING, Status.DOWNLOADING -> {
}
Status.INSTALLING -> {
Log.d(
TAG,
"===> doWork: Installing ${fusedDownload.name} ${fusedDownload.status}"
)
- isDownloading = false
}
Status.INSTALLED, Status.INSTALLATION_ISSUE -> {
isDownloading = false
@@ -158,4 +168,38 @@ class InstallAppWorker @AssistedInject constructor(
}
}
}
+
+ private fun createForegroundInfo(progress: String): ForegroundInfo {
+ val title = applicationContext.getString(R.string.app_name)
+ val cancel = applicationContext.getString(R.string.cancel)
+ // This PendingIntent can be used to cancel the worker
+ val intent = WorkManager.getInstance(applicationContext)
+ .createCancelPendingIntent(getId())
+
+ val notificationManager =
+ context.getSystemService(Context.NOTIFICATION_SERVICE) as
+ NotificationManager
+ // Create a Notification channel if necessary
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val mChannel = NotificationChannel(
+ "applounge_notification",
+ title,
+ NotificationManager.IMPORTANCE_DEFAULT
+ )
+ notificationManager.createNotificationChannel(mChannel)
+ }
+
+ val notification = NotificationCompat.Builder(applicationContext, "applounge_notification")
+ .setContentTitle(title)
+ .setTicker(title)
+ .setContentText(progress)
+ .setSmallIcon(R.drawable.ic_launcher_foreground)
+ .setOngoing(true)
+ // Add the cancel action to the notification which can
+ // be used to cancel the worker
+ .addAction(android.R.drawable.ic_delete, cancel, intent)
+ .build()
+
+ return ForegroundInfo(atomicInteger.getAndIncrement(), notification)
+ }
}
diff --git a/app/src/main/java/foundation/e/apps/manager/workmanager/InstallWorkManager.kt b/app/src/main/java/foundation/e/apps/manager/workmanager/InstallWorkManager.kt
index ffdd9e719b5308112b3d02071913530a07237092..b94135fbdb7ff72c2c3cf5b22b51f2d19f14d798 100644
--- a/app/src/main/java/foundation/e/apps/manager/workmanager/InstallWorkManager.kt
+++ b/app/src/main/java/foundation/e/apps/manager/workmanager/InstallWorkManager.kt
@@ -3,21 +3,22 @@ package foundation.e.apps.manager.workmanager
import android.content.Context
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
-import androidx.work.OneTimeWorkRequest
+import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
object InstallWorkManager {
- private const val INSTALL_WORK_NAME = "APP_LOUNGE_INSTALL_APP"
+ const val INSTALL_WORK_NAME = "APP_LOUNGE_INSTALL_APP"
fun enqueueWork(context: Context, fusedDownload: FusedDownload) {
WorkManager.getInstance(context).enqueueUniqueWork(
INSTALL_WORK_NAME,
ExistingWorkPolicy.APPEND_OR_REPLACE,
- OneTimeWorkRequest.Builder(InstallAppWorker::class.java).setInputData(
+ OneTimeWorkRequestBuilder().setInputData(
Data.Builder()
.putString(InstallAppWorker.INPUT_DATA_FUSED_DOWNLOAD, fusedDownload.id)
.build()
- ).build()
+ ).addTag(fusedDownload.name)
+ .build()
)
}
}
diff --git a/app/src/main/java/foundation/e/apps/utils/modules/PWAManagerModule.kt b/app/src/main/java/foundation/e/apps/utils/modules/PWAManagerModule.kt
index a9fcd823993ee46cb64c7bc035b2bb7667a10163..1ea385d85ac2909da014e8ea11532c3bacf760f2 100644
--- a/app/src/main/java/foundation/e/apps/utils/modules/PWAManagerModule.kt
+++ b/app/src/main/java/foundation/e/apps/utils/modules/PWAManagerModule.kt
@@ -142,7 +142,7 @@ class PWAManagerModule @Inject constructor(
// Update status
fusedDownload.status = Status.INSTALLED
databaseRepository.updateDownload(fusedDownload)
-
+ delay(500)
databaseRepository.deleteDownload(fusedDownload)
}
}