Migrate AsyncTask to Coroutines

AsyncTask is deprecated in Java. Use Kotlin's coroutines instead

Bug: 182205982
Test: Manual. Sideload an APK and observe the staging dialog
Change-Id: I7e9394417be0ca3f8b5e12b1e31b8b1941290114
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 4fcea5e..326e533 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -60,6 +60,10 @@
 import com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted
 import java.io.File
 import java.io.IOException
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
 
 class InstallRepository(private val context: Context) {
 
@@ -242,6 +246,7 @@
         }
     }
 
+    @OptIn(DelicateCoroutinesApi::class)
     fun stageForInstall() {
         val uri = intent.data
         if (stagedSessionId != SessionInfo.INVALID_ID
@@ -290,13 +295,14 @@
                     return
                 }
             }
-            val listener: SessionStageListener = object : SessionStageListener {
-                override fun onStagingSuccess(info: SessionInfo?) {
-                    //TODO: Verify if the returned sessionInfo should be used anywhere
-                    _stagingResult.value = InstallReady()
-                }
 
-                override fun onStagingFailure() {
+            sessionStager = SessionStager(context, uri, stagedSessionId)
+            GlobalScope.launch(Dispatchers.Main) {
+                val wasFileStaged = sessionStager!!.execute()
+
+                if (wasFileStaged) {
+                    _stagingResult.value = InstallReady()
+                } else {
                     cleanupStagingSession()
                     _stagingResult.value = InstallAborted(
                         ABORT_REASON_INTERNAL_ERROR,
@@ -307,9 +313,6 @@
                     )
                 }
             }
-            sessionStager?.cancel(true)
-            sessionStager = SessionStager(context, uri, stagedSessionId, listener)
-            sessionStager?.execute()
         }
     }
 
@@ -845,10 +848,8 @@
         return generateConfirmationSnippet()
     }
 
-    val stagingProgress: MutableLiveData<Int>
-        get() = if (sessionStager != null) {
-            sessionStager?.progress ?: MutableLiveData(0)
-        } else MutableLiveData(0)
+    val stagingProgress: LiveData<Int>
+        get() = sessionStager?.progress ?: MutableLiveData(0)
 
     companion object {
         const val EXTRA_STAGED_SESSION_ID = "com.android.packageinstaller.extra.STAGED_SESSION_ID"
@@ -857,11 +858,6 @@
         private val LOG_TAG = InstallRepository::class.java.simpleName
     }
 
-    interface SessionStageListener {
-        fun onStagingSuccess(info: SessionInfo?)
-        fun onStagingFailure()
-    }
-
     data class CallerInfo(val packageName: String?, val uid: Int)
     data class AppOpRequestInfo(
         val callingPackage: String?,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
index 015b95f..c9bfa17 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.kt
@@ -20,55 +20,53 @@
 import android.content.pm.PackageInstaller
 import android.content.res.AssetFileDescriptor
 import android.net.Uri
-import android.os.AsyncTask
 import android.util.Log
+import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import com.android.packageinstaller.v2.model.InstallRepository.SessionStageListener
 import java.io.IOException
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
 
 class SessionStager internal constructor(
     private val context: Context,
     private val uri: Uri,
-    private val stagedSessionId: Int,
-    private val listener: SessionStageListener,
-) : AsyncTask<Void, Int, PackageInstaller.SessionInfo?>() {
+    private val stagedSessionId: Int
+) {
 
     companion object {
         private val LOG_TAG = SessionStager::class.java.simpleName
     }
 
-    val progress = MutableLiveData(0)
+    private val _progress = MutableLiveData(0)
+    val progress: LiveData<Int>
+        get() = _progress
 
-    override fun doInBackground(vararg params: Void): PackageInstaller.SessionInfo? {
-        val pi = context.packageManager.packageInstaller
+    suspend fun execute(): Boolean = withContext(Dispatchers.IO) {
+        val pi: PackageInstaller = context.packageManager.packageInstaller
+        var sessionInfo: PackageInstaller.SessionInfo?
         try {
             val session = pi.openSession(stagedSessionId)
             context.contentResolver.openInputStream(uri).use { instream ->
                 session.setStagingProgress(0f)
 
                 if (instream == null) {
-                    return null
+                    return@withContext false
                 }
 
                 val sizeBytes = getContentSizeBytes()
-                progress.postValue(if (sizeBytes > 0) 0 else -1)
+                publishProgress(if (sizeBytes > 0) 0 else -1)
 
                 var totalRead: Long = 0
                 session.openWrite("PackageInstaller", 0, sizeBytes).use { out ->
                     val buffer = ByteArray(1024 * 1024)
                     while (true) {
                         val numRead = instream.read(buffer)
-
                         if (numRead == -1) {
                             session.fsync(out)
                             break
                         }
-
-                        if (isCancelled) {
-                            break
-                        }
-
                         out.write(buffer, 0, numRead)
+
                         if (sizeBytes > 0) {
                             totalRead += numRead.toLong()
                             val fraction = totalRead.toFloat() / sizeBytes.toFloat()
@@ -77,12 +75,21 @@
                         }
                     }
                 }
-                return pi.getSessionInfo(stagedSessionId)!!
+                sessionInfo = pi.getSessionInfo(stagedSessionId)
             }
-
         } catch (e: Exception) {
             Log.w(LOG_TAG, "Error staging apk from content URI", e)
-            return null
+            sessionInfo = null
+        }
+
+        return@withContext if (sessionInfo == null
+            || !sessionInfo?.isActive!!
+            || sessionInfo?.resolvedBaseApkPath == null
+        ) {
+            Log.w(LOG_TAG, "Session info is invalid: $sessionInfo")
+            false
+        } else {
+            true
         }
     }
 
@@ -97,21 +104,7 @@
         }
     }
 
-    override fun onPostExecute(sessionInfo: PackageInstaller.SessionInfo?) {
-        if ((sessionInfo == null)
-            || !sessionInfo.isActive
-            || (sessionInfo.resolvedBaseApkPath == null)
-        ) {
-            Log.w(LOG_TAG, "Session info is invalid: $sessionInfo")
-            listener.onStagingFailure()
-            return
-        }
-        listener.onStagingSuccess(sessionInfo)
-    }
-
-    override fun onProgressUpdate(vararg progressVal: Int?) {
-        if (progressVal.isNotEmpty()) {
-            progress.value = progressVal[0]
-        }
+    private suspend fun publishProgress(progressValue: Int) = withContext(Dispatchers.Main) {
+        _progress.value = progressValue
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
index b141010..072fb2d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt
@@ -19,6 +19,7 @@
 import android.app.Application
 import android.content.Intent
 import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
 import androidx.lifecycle.MediatorLiveData
 import androidx.lifecycle.MutableLiveData
 import com.android.packageinstaller.v2.model.InstallRepository
@@ -56,7 +57,7 @@
         }
     }
 
-    val stagingProgress: MutableLiveData<Int>
+    val stagingProgress: LiveData<Int>
         get() = repository.stagingProgress
 
     private fun checkIfAllowedAndInitiateInstall() {