diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/DownloadManager.kt b/vending-app/src/main/kotlin/com/google/android/finsky/DownloadManager.kt index 6e020ef85..f81f23e3b 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/DownloadManager.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/DownloadManager.kt @@ -19,7 +19,6 @@ import android.graphics.Canvas import android.graphics.drawable.BitmapDrawable import android.net.Uri import android.os.Build -import android.os.Bundle import android.util.Log import android.widget.RemoteViews import androidx.core.app.NotificationCompat @@ -55,7 +54,7 @@ class DownloadManager(private val context: Context) { private val downloadingRecord = ConcurrentHashMap>() @Volatile - private var shouldStop = false + private var shouldStops = false private val cancelReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -145,14 +144,18 @@ class DownloadManager(private val context: Context) { NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notifyBuilder.setCustomContentView(notificationLayout).build()) } + @Synchronized + fun shouldStop(shouldStop:Boolean){ + shouldStops = shouldStop + } + @Synchronized fun prepareDownload(downloadData: DownloadData, moduleName: String) { Log.d(TAG, "prepareDownload: ${downloadData.packageName}") - shouldStop = false - downloadData.updateDownloadStatus(moduleName, STATUS_DOWNLOADING) initNotification(moduleName, downloadData.packageName) val future = executor.submit { val packData = downloadData.getModuleData(moduleName) + downloadData.updateDownloadStatus(moduleName, STATUS_DOWNLOADING) for (dataBundle in packData.packBundleList) { val resourcePackageName: String? = dataBundle.getString(KEY_RESOURCE_PACKAGE_NAME) val chunkName: String? = dataBundle.getString(KEY_CHUNK_NAME) @@ -178,7 +181,7 @@ class DownloadManager(private val context: Context) { private fun cancelDownload(moduleName: String) { Log.d(TAG, "Download for module $moduleName has been canceled.") downloadingRecord[moduleName]?.cancel(true) - shouldStop = true + shouldStops = true notifyBuilderMap[moduleName]?.setOngoing(false) NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID) } @@ -205,7 +208,7 @@ class DownloadManager(private val context: Context) { val buffer = ByteArray(4096) var bytesRead: Int while (input.read(buffer).also { bytesRead = it } != -1) { - if (shouldStop) { + if (shouldStops) { Log.d(TAG, "Download interrupted for module: $moduleName") downloadData.updateDownloadStatus(moduleName, CANCELED) return diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/AssetModuleService.kt b/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/AssetModuleService.kt index a006f4751..52660154b 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/AssetModuleService.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/assetmoduleservice/AssetModuleService.kt @@ -15,7 +15,6 @@ import android.util.Log import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleService -import androidx.lifecycle.lifecycleScope import com.google.android.finsky.DownloadManager import com.google.android.finsky.ERROR_CODE_FAIL import com.google.android.finsky.KEY_BYTE_LENGTH @@ -36,7 +35,6 @@ import com.google.android.finsky.KEY_SLICE_ID import com.google.android.finsky.STATUS_COMPLETED import com.google.android.finsky.STATUS_DOWNLOADING import com.google.android.finsky.STATUS_INITIAL_STATE -import com.google.android.finsky.STATUS_NOT_INSTALLED import com.google.android.finsky.TAG_REQUEST import com.google.android.finsky.buildDownloadBundle import com.google.android.finsky.initAssertModuleData @@ -82,34 +80,27 @@ class AssetModuleServiceImpl( callback?.onError(Bundle().apply { putInt(KEY_ERROR_CODE, -5) }) return } - lifecycleScope.launchWhenStarted { - if (downloadData == null || downloadData?.packageName != packageName) { - val requestedAssetModuleNames = list.map { it.getString(KEY_MODULE_NAME) }.filter { !it.isNullOrEmpty() } - val playCoreVersionCode = bundle.getInt(KEY_PLAY_CORE_VERSION_CODE) - downloadData = httpClient.initAssertModuleData(context, packageName, accountManager, requestedAssetModuleNames, playCoreVersionCode) - } - if (list.all { it.getString(KEY_MODULE_NAME) == null } || moduleErrorRequested.contains(packageName)) { - Log.d(TAG, "startDownload: moduleData request error") - val result = Bundle().apply { putStringArrayList(KEY_PACK_NAMES, arrayListOf()) } - callback?.onStartDownload(-1, result) - return@launchWhenStarted - } - list.forEach { - val moduleName = it.getString(KEY_MODULE_NAME) - val packData = downloadData?.getModuleData(moduleName!!) - if (packData?.status != STATUS_DOWNLOADING){ - downloadData?.updateDownloadStatus(moduleName!!, STATUS_INITIAL_STATE) - } + if (downloadData == null || downloadData?.packageName != packageName) { + val requestedAssetModuleNames = list.map { it.getString(KEY_MODULE_NAME) }.filter { !it.isNullOrEmpty() } + val playCoreVersionCode = bundle.getInt(KEY_PLAY_CORE_VERSION_CODE) + downloadData = httpClient.initAssertModuleData(context, packageName, accountManager, requestedAssetModuleNames, playCoreVersionCode) + } + list.forEach { + val moduleName = it.getString(KEY_MODULE_NAME) + val packData = downloadData?.getModuleData(moduleName!!) + if (packData?.status != STATUS_DOWNLOADING && packData?.status != STATUS_COMPLETED){ + downloadData?.updateDownloadStatus(moduleName!!, STATUS_INITIAL_STATE) } - val bundleData = buildDownloadBundle(downloadData!!,list) - Log.d(TAG, "startDownload---${bundleData}") - callback?.onStartDownload(-1, bundleData) - list.forEach { - val moduleName = it.getString(KEY_MODULE_NAME) - val packData = downloadData?.getModuleData(moduleName!!) - if (packData?.status != STATUS_DOWNLOADING){ - DownloadManager.get(context).prepareDownload(downloadData!!, moduleName!!) - } + } + val bundleData = buildDownloadBundle(downloadData!!,list) + Log.d(TAG, "startDownload: $bundleData") + callback?.onStartDownload(-1, bundleData) + list.forEach { + val moduleName = it.getString(KEY_MODULE_NAME) + val packData = downloadData?.getModuleData(moduleName!!) + if (packData?.status == STATUS_INITIAL_STATE){ + DownloadManager.get(context).shouldStop(false) + DownloadManager.get(context).prepareDownload(downloadData!!, moduleName!!) } } } @@ -117,44 +108,11 @@ class AssetModuleServiceImpl( override fun getSessionStates(packageName: String?, bundle: Bundle?, callback: IAssetModuleServiceCallback?) { Log.d(TAG, "Method (getSessionStates) called by packageName -> $packageName") val installedAssetModuleNames = mutableListOf() - bundle?.keySet()?.forEach { key -> - val value = bundle.get(key) - if (key == KEY_INSTALLED_ASSET_MODULE) { - when (value) { - is Bundle -> { - value.keySet().forEach { subKey -> - val item = value.get(subKey) - if (item is String) { - installedAssetModuleNames.add(item) - Log.d(TAG, "installed_asset_module Bundle Value: $item") - } - } - } - is ArrayList<*> -> { - value.forEachIndexed { index, item -> - if (item is Bundle) { - Log.d(TAG, "installed_asset_module Bundle at index $index") - item.keySet().forEach { subKey -> - val subItem = item.get(subKey) - if (subItem is String) { - installedAssetModuleNames.add(subItem) - Log.d(TAG, "installed_asset_module[$index] Value: $subItem") - } - } - } - } - } - else -> { - Log.d(TAG, "installed_asset_module: - ${value?.javaClass?.name}") - } - } - } else { - Log.d(TAG, "Bundle Key: $key, Value: $value") + bundle?.getParcelableArrayList(KEY_INSTALLED_ASSET_MODULE)?.forEach { item -> + item.keySet().forEach { subKey -> + (item.get(subKey) as? String)?.let { installedAssetModuleNames.add(it) } } } - - Log.d(TAG, "getSessionStates installedAssetModuleNames: $installedAssetModuleNames") - if (packageName == downloadData?.packageName) { downloadData?.moduleNames?.forEach { moduleName -> if (installedAssetModuleNames.contains(moduleName)) { return@forEach } @@ -179,7 +137,6 @@ class AssetModuleServiceImpl( } } - override fun notifyChunkTransferred(packageName: String?, bundle: Bundle?, bundle2: Bundle?, callback: IAssetModuleServiceCallback?) { Log.d(TAG, "Method (notifyChunkTransferred) called by packageName -> $packageName") val moduleName = bundle?.getString(KEY_MODULE_NAME) @@ -205,16 +162,15 @@ class AssetModuleServiceImpl( callback?.onError(Bundle().apply { putInt(KEY_ERROR_CODE, -5) }) return } - lifecycleScope.launchWhenStarted { - Log.d(TAG, "notify: moduleName: $moduleName packNames: ${downloadData?.moduleNames}") - downloadData?.updateDownloadStatus(moduleName, STATUS_COMPLETED) - sendBroadcastForExistingFile(context, downloadData!!, moduleName, null, null) - callback?.onNotifyModuleCompleted(bundle, bundle) - } + Log.d(TAG, "notify: moduleName: $moduleName packNames: ${downloadData?.moduleNames}") + downloadData?.updateDownloadStatus(moduleName, STATUS_COMPLETED) + sendBroadcastForExistingFile(context, downloadData!!, moduleName, null, null) + callback?.onNotifyModuleCompleted(bundle, bundle) } override fun notifySessionFailed(packageName: String?, bundle: Bundle?, bundle2: Bundle?, callback: IAssetModuleServiceCallback?) { Log.d(TAG, "Method (notifySessionFailed) called but not implemented by packageName -> $packageName") + callback?.onNotifySessionFailed(bundle) } override fun keepAlive(packageName: String?, bundle: Bundle?, callback: IAssetModuleServiceCallback?) { @@ -249,27 +205,25 @@ class AssetModuleServiceImpl( callback?.onError(Bundle().apply { putInt(KEY_ERROR_CODE, -5) }) return } - lifecycleScope.launchWhenStarted { - if (downloadData == null || downloadData?.packageName != packageName) { - val requestedAssetModuleNames = list.map { it.getString(KEY_MODULE_NAME) }.filter { !it.isNullOrEmpty() } - val playCoreVersionCode = bundle.getInt(KEY_PLAY_CORE_VERSION_CODE) - downloadData = httpClient.initAssertModuleData(context, packageName, accountManager, requestedAssetModuleNames, playCoreVersionCode) - } - if (downloadData?.errorCode == ERROR_CODE_FAIL) { - if (moduleErrorRequested.contains(packageName)) { - callback?.onError(Bundle().apply { putInt(KEY_ERROR_CODE, -5) }) - return@launchWhenStarted - } - moduleErrorRequested.add(packageName) - val result = Bundle().apply { putStringArrayList(KEY_PACK_NAMES, arrayListOf()) } - callback?.onRequestDownloadInfo(result, result) - return@launchWhenStarted + if (downloadData == null || downloadData?.packageName != packageName) { + val requestedAssetModuleNames = list.map { it.getString(KEY_MODULE_NAME) }.filter { !it.isNullOrEmpty() } + val playCoreVersionCode = bundle.getInt(KEY_PLAY_CORE_VERSION_CODE) + downloadData = httpClient.initAssertModuleData(context, packageName, accountManager, requestedAssetModuleNames, playCoreVersionCode) + } + if (downloadData?.errorCode == ERROR_CODE_FAIL) { + if (moduleErrorRequested.contains(packageName)) { + callback?.onError(Bundle().apply { putInt(KEY_ERROR_CODE, -5) }) + return } - moduleErrorRequested.remove(packageName) - val bundleData = buildDownloadBundle(downloadData!!,list) - Log.d(TAG, "requestDownloadInfo---${bundleData}") - callback?.onRequestDownloadInfo(bundleData, bundleData) + moduleErrorRequested.add(packageName) + val result = Bundle().apply { putStringArrayList(KEY_PACK_NAMES, arrayListOf()) } + callback?.onRequestDownloadInfo(result, result) + return } + moduleErrorRequested.remove(packageName) + val bundleData = buildDownloadBundle(downloadData!!,list) + Log.d(TAG, "requestDownloadInfo: $bundleData") + callback?.onRequestDownloadInfo(bundleData, bundleData) } override fun removeModule(packageName: String?, bundle: Bundle?, bundle2: Bundle?, callback: IAssetModuleServiceCallback?) { diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/extensions.kt b/vending-app/src/main/kotlin/com/google/android/finsky/extensions.kt index 0aa525aee..9315462de 100644 --- a/vending-app/src/main/kotlin/com/google/android/finsky/extensions.kt +++ b/vending-app/src/main/kotlin/com/google/android/finsky/extensions.kt @@ -26,6 +26,7 @@ import org.microg.vending.billing.GServices import org.microg.vending.billing.core.HttpClient import java.io.File import java.util.Collections +import kotlinx.coroutines.runBlocking const val STATUS_NOT_INSTALLED = 8 const val CANCELED = 6 @@ -90,46 +91,60 @@ fun getAppVersionCode(context: Context, packageName: String): String? { return runCatching { context.packageManager.getPackageInfo(packageName, 0).versionCode.toString() }.getOrNull() } -suspend fun HttpClient.initAssertModuleData( - context: Context, - packageName: String, - accountManager: AccountManager, - requestedAssetModuleNames: List, - playCoreVersionCode: Int, +fun HttpClient.initAssertModuleData( + context: Context, + packageName: String, + accountManager: AccountManager, + requestedAssetModuleNames: List, + playCoreVersionCode: Int, ): DownloadData { - Log.d(TAG, "initAssertModuleData: requestedAssetModuleNames: $requestedAssetModuleNames") val accounts = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE) var oauthToken: String? = null if (accounts.isEmpty()) { return DownloadData(errorCode = ERROR_CODE_FAIL) - } else for (account: Account in accounts) { - oauthToken = accountManager.getAuthToken(account, AUTH_TOKEN_SCOPE, false).getString(AccountManager.KEY_AUTHTOKEN) - if (oauthToken != null) { - break + } else { + for (account: Account in accounts) { + oauthToken = runBlocking { + accountManager.getAuthToken(account, AUTH_TOKEN_SCOPE, false).getString(AccountManager.KEY_AUTHTOKEN) + } + if (oauthToken != null) { + break + } } } - Log.d(TAG, "initAssertModuleData: oauthToken -> $oauthToken") + if (oauthToken == null) { return DownloadData(errorCode = ERROR_CODE_FAIL) } - val requestPayload = AssetModuleDeliveryRequest.Builder().callerInfo(CallerInfo(getAppVersionCode(context, packageName)?.toInt())).packageName(packageName) - .playCoreVersion(playCoreVersionCode).pageSource(listOf(PageSource.UNKNOWN_SEARCH_TRAFFIC_SOURCE, PageSource.BOOKS_HOME_PAGE)) - .callerState(listOf(CallerState.CALLER_APP_REQUEST, CallerState.CALLER_APP_DEBUGGABLE)).moduleInfo(ArrayList().apply { - requestedAssetModuleNames.forEach { add(AssetModuleInfo.Builder().name(it).build()) } - }).build() - val androidId = GServices.getString(context.contentResolver, "android_id", "0")?.toLong()?:1 - var moduleDeliveryInfo = runCatching { - post( - url = ASSET_MODULE_DELIVERY_URL, - headers = getLicenseRequestHeaders(oauthToken, androidId), - payload = requestPayload, - adapter = AssetModuleDeliveryResponse.ADAPTER - ).wrapper?.deliveryInfo - }.onFailure { - Log.d(TAG, "initAssertModuleData: ", it) - }.getOrNull() - if (moduleDeliveryInfo?.status == 2) { + val requestPayload = AssetModuleDeliveryRequest.Builder() + .callerInfo(CallerInfo(getAppVersionCode(context, packageName)?.toInt())) + .packageName(packageName) + .playCoreVersion(playCoreVersionCode) + .pageSource(listOf(PageSource.UNKNOWN_SEARCH_TRAFFIC_SOURCE, PageSource.BOOKS_HOME_PAGE)) + .callerState(listOf(CallerState.CALLER_APP_REQUEST, CallerState.CALLER_APP_DEBUGGABLE)) + .moduleInfo(ArrayList().apply { + requestedAssetModuleNames.forEach { add(AssetModuleInfo.Builder().name(it).build()) } + }).build() + + val androidId = GServices.getString(context.contentResolver, "android_id", "0")?.toLong() ?: 1 + + var moduleDeliveryInfo = runBlocking { + runCatching { + post( + url = ASSET_MODULE_DELIVERY_URL, + headers = getLicenseRequestHeaders(oauthToken, androidId), + payload = requestPayload, + adapter = AssetModuleDeliveryResponse.ADAPTER + ).wrapper?.deliveryInfo + }.getOrNull() + } + + if (moduleDeliveryInfo?.status != 2) { + return initModuleDownloadInfo(context, packageName, moduleDeliveryInfo) + } + + runBlocking { runCatching { post( url = SYNC_NOCACHE_QOS, @@ -138,20 +153,20 @@ suspend fun HttpClient.initAssertModuleData( adapter = SyncResponse.ADAPTER ) }.onFailure { - Log.d(TAG, "initAssertModuleData: sync -> ", it) + Log.d(TAG, "initAssertModuleData: ", it) } } - moduleDeliveryInfo = runCatching { - post( - url = ASSET_MODULE_DELIVERY_URL, - headers = getLicenseRequestHeaders(oauthToken, androidId), - payload = requestPayload, - adapter = AssetModuleDeliveryResponse.ADAPTER - ).wrapper?.deliveryInfo - }.onFailure { - Log.d(TAG, "initAssertModuleData: ", it) - }.getOrNull() + moduleDeliveryInfo = runBlocking { + runCatching { + post( + url = ASSET_MODULE_DELIVERY_URL, + headers = getLicenseRequestHeaders(oauthToken, androidId), + payload = requestPayload, + adapter = AssetModuleDeliveryResponse.ADAPTER + ).wrapper?.deliveryInfo + }.getOrNull() + } Log.d(TAG, "initAssertModuleData: moduleDeliveryInfo-> $moduleDeliveryInfo") return initModuleDownloadInfo(context, packageName, moduleDeliveryInfo) } @@ -235,11 +250,9 @@ fun buildDownloadBundle(downloadData: DownloadData, list: List? = null) totalBytesToDownload += packData.totalBytesToDownload bytesDownloaded += packData.bytesDownloaded } - bundleData.putStringArrayList(KEY_PACK_NAMES, arrayList) bundleData.putLong(KEY_TOTAL_BYTES_TO_DOWNLOAD, totalBytesToDownload) bundleData.putLong(KEY_BYTES_DOWNLOADED, bytesDownloaded) - return bundleData } @@ -300,7 +313,6 @@ fun sendBroadcastForExistingFile(context: Context, downloadData: DownloadData, m downloadBundle.putString(combineModule(KEY_UNCOMPRESSED_HASH_SHA256, moduleName, chunkName), uncompressedHashSha256) } downloadBundle.putStringArrayList(combineModule(KEY_SLICE_IDS, moduleName), packData.listOfSubcontractNames) - Log.d(TAG, "sendBroadcastForExistingFile: $downloadBundle") sendBroadCast(context, downloadData, downloadBundle) } catch (e: Exception) { Log.w(TAG, "sendBroadcastForExistingFile error:" + e.message)