Enable fetching of app download urls in the background (AppDetailsActivity.kt), force granting of all permissions in OnboardingActivity.kt by disabling finish until PermissionsFragment.kt notifies successfully granted permissions, handle currently installing apps better by saving them in multi-thread proof list in UpdateService.kt, add own notification group for UpdateService.kt and all required UpdateService.kt strings, add ability to pass file list to service installer for more advanced, non-system service installers, save downloaded files in getExternalFilesDir by default for more useful file list for more advanced, non-system service installers without compromising security because of Android's restrictions on the Android/data directory
This commit is contained in:
@@ -22,6 +22,8 @@ interface IPrivilegedService {
|
|||||||
|
|
||||||
boolean hasPrivilegedPermissions();
|
boolean hasPrivilegedPermissions();
|
||||||
|
|
||||||
|
boolean isMoreMethodImplemented();
|
||||||
|
|
||||||
oneway void installPackage(
|
oneway void installPackage(
|
||||||
in Uri packageURI,
|
in Uri packageURI,
|
||||||
in int flags,
|
in int flags,
|
||||||
@@ -52,6 +54,15 @@ interface IPrivilegedService {
|
|||||||
in IPrivilegedCallback callback
|
in IPrivilegedCallback callback
|
||||||
);
|
);
|
||||||
|
|
||||||
|
oneway void installSplitPackageMore(
|
||||||
|
in String packageName,
|
||||||
|
in List<Uri> uriList,
|
||||||
|
in int flags,
|
||||||
|
in String installerPackageName,
|
||||||
|
in IPrivilegedCallback callback,
|
||||||
|
in List<String> fileList
|
||||||
|
);
|
||||||
|
|
||||||
oneway void deletePackage(
|
oneway void deletePackage(
|
||||||
in String packageName,
|
in String packageName,
|
||||||
in int flags,
|
in int flags,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ object Constants {
|
|||||||
|
|
||||||
const val NOTIFICATION_CHANNEL_ALERT = "NOTIFICATION_CHANNEL_ALERT"
|
const val NOTIFICATION_CHANNEL_ALERT = "NOTIFICATION_CHANNEL_ALERT"
|
||||||
const val NOTIFICATION_CHANNEL_GENERAL = "NOTIFICATION_CHANNEL_GENERAL"
|
const val NOTIFICATION_CHANNEL_GENERAL = "NOTIFICATION_CHANNEL_GENERAL"
|
||||||
|
const val NOTIFICATION_CHANNEL_UPDATER_SERVICE = "NOTIFICATION_CHANNEL_UPDATER_SERVICE"
|
||||||
|
|
||||||
const val URL_DISPENSER = "http://goolag.store:1337/api/auth"
|
const val URL_DISPENSER = "http://goolag.store:1337/api/auth"
|
||||||
|
|
||||||
|
|||||||
@@ -73,8 +73,17 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val fileList = files.map {
|
||||||
|
when (it) {
|
||||||
|
is File -> it.absolutePath
|
||||||
|
is String -> File(it).absolutePath
|
||||||
|
else -> {
|
||||||
|
throw Exception("Invalid data, expecting listOf() File or String")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
xInstall(packageName, uriList)
|
xInstall(packageName, uriList, fileList)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
postError(
|
postError(
|
||||||
@@ -168,7 +177,7 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
private fun xInstall(packageName: String, uriList: List<Uri>) {
|
private fun xInstall(packageName: String, uriList: List<Uri>, fileList: List<String>) {
|
||||||
executor.execute {
|
executor.execute {
|
||||||
var readyWithAction = false
|
var readyWithAction = false
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
@@ -205,17 +214,39 @@ class ServiceInstaller(context: Context) : InstallerBase(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
service.installSplitPackageX(
|
if (service.isMoreMethodImplemented) {
|
||||||
packageName,
|
try {
|
||||||
uriList,
|
service.installSplitPackageMore(
|
||||||
ACTION_INSTALL_REPLACE_EXISTING,
|
packageName,
|
||||||
BuildConfig.APPLICATION_ID,
|
uriList,
|
||||||
callback
|
ACTION_INSTALL_REPLACE_EXISTING,
|
||||||
)
|
BuildConfig.APPLICATION_ID,
|
||||||
} catch (e: RemoteException) {
|
callback,
|
||||||
removeFromInstallQueue(packageName)
|
fileList
|
||||||
readyWithAction = true
|
)
|
||||||
postError(packageName, e.localizedMessage, e.stackTraceToString())
|
} catch (e: RemoteException) {
|
||||||
|
removeFromInstallQueue(packageName)
|
||||||
|
readyWithAction = true
|
||||||
|
postError(packageName, e.localizedMessage, e.stackTraceToString())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception("New method not implemented")
|
||||||
|
}
|
||||||
|
} catch (th: Throwable) {
|
||||||
|
th.printStackTrace()
|
||||||
|
try {
|
||||||
|
service.installSplitPackageX(
|
||||||
|
packageName,
|
||||||
|
uriList,
|
||||||
|
ACTION_INSTALL_REPLACE_EXISTING,
|
||||||
|
BuildConfig.APPLICATION_ID,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
} catch (e: RemoteException) {
|
||||||
|
removeFromInstallQueue(packageName)
|
||||||
|
readyWithAction = true
|
||||||
|
postError(packageName, e.localizedMessage, e.stackTraceToString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
removeFromInstallQueue(packageName)
|
removeFromInstallQueue(packageName)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.aurora.store.data.service
|
||||||
|
|
||||||
|
import com.aurora.gplayapi.data.models.App
|
||||||
|
|
||||||
|
interface AppMetadataStatusListener {
|
||||||
|
fun onAppMetadataStatusError(reason: String, app: App)
|
||||||
|
}
|
||||||
@@ -161,6 +161,13 @@ class NotificationService : Service() {
|
|||||||
NotificationManager.IMPORTANCE_MIN
|
NotificationManager.IMPORTANCE_MIN
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
channels.add(
|
||||||
|
NotificationChannel(
|
||||||
|
Constants.NOTIFICATION_CHANNEL_UPDATER_SERVICE,
|
||||||
|
getString(R.string.notification_channel_updater_service),
|
||||||
|
NotificationManager.IMPORTANCE_MIN
|
||||||
|
)
|
||||||
|
)
|
||||||
notificationManager.createNotificationChannels(channels)
|
notificationManager.createNotificationChannels(channels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,18 @@ import androidx.lifecycle.LifecycleService
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.aurora.Constants
|
import com.aurora.Constants
|
||||||
import com.aurora.extensions.stackTraceToString
|
import com.aurora.extensions.stackTraceToString
|
||||||
import com.aurora.extensions.toast
|
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.data.models.AuthData
|
import com.aurora.gplayapi.data.models.AuthData
|
||||||
|
import com.aurora.gplayapi.exceptions.ApiException
|
||||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.downloader.DownloadManager
|
import com.aurora.store.data.downloader.DownloadManager
|
||||||
import com.aurora.store.data.downloader.RequestBuilder
|
import com.aurora.store.data.downloader.RequestBuilder
|
||||||
|
import com.aurora.store.data.downloader.getGroupId
|
||||||
import com.aurora.store.data.event.InstallerEvent
|
import com.aurora.store.data.event.InstallerEvent
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
import com.aurora.store.data.model.UpdateFile
|
import com.aurora.store.data.model.UpdateFile
|
||||||
|
import com.aurora.store.data.network.HttpClient
|
||||||
import com.aurora.store.data.providers.AuthProvider
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
import com.aurora.store.util.Log
|
import com.aurora.store.util.Log
|
||||||
import com.tonyodev.fetch2.*
|
import com.tonyodev.fetch2.*
|
||||||
@@ -34,7 +36,7 @@ import org.greenrobot.eventbus.EventBus
|
|||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import kotlin.concurrent.timerTask
|
import kotlin.concurrent.timerTask
|
||||||
|
|
||||||
class UpdateService: LifecycleService() {
|
class UpdateService: LifecycleService() {
|
||||||
@@ -44,9 +46,9 @@ class UpdateService: LifecycleService() {
|
|||||||
private lateinit var fetchListener: FetchGroupListener
|
private lateinit var fetchListener: FetchGroupListener
|
||||||
private var fetchActiveDownloadObserver = object : FetchObserver<Boolean> {
|
private var fetchActiveDownloadObserver = object : FetchObserver<Boolean> {
|
||||||
override fun onChanged(data: Boolean, reason: Reason) {
|
override fun onChanged(data: Boolean, reason: Reason) {
|
||||||
if (!data && !installing.get() && listeners.isEmpty()) {
|
if (!data && installing.isEmpty() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) {
|
||||||
Handler(Looper.getMainLooper()).postDelayed ({
|
Handler(Looper.getMainLooper()).postDelayed ({
|
||||||
if (!installing.get() && listeners.isEmpty()) {
|
if (installing.isEmpty() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) {
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
}, 5 * 1000)
|
}, 5 * 1000)
|
||||||
@@ -55,9 +57,13 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
private var hasActiveDownloadObserver = false
|
private var hasActiveDownloadObserver = false
|
||||||
|
|
||||||
private val listeners: ArrayList<FetchGroupListener> = ArrayList()
|
private val fetchListeners: ArrayList<FetchGroupListener> = ArrayList()
|
||||||
|
|
||||||
private val pendingEvents: MutableMap</*groupId: */Int, AppDownloadStatus> = mutableMapOf()
|
private val fetchPendingEvents: MutableMap</*groupId: */Int, AppDownloadStatus> = mutableMapOf()
|
||||||
|
|
||||||
|
private val appMetadataListeners: ArrayList<AppMetadataStatusListener> = ArrayList()
|
||||||
|
|
||||||
|
private val appMetadataPendingEvents: ArrayList<AppMetadataStatus> = ArrayList()
|
||||||
|
|
||||||
var liveUpdateData: MutableLiveData<MutableMap<Int, UpdateFile>> = MutableLiveData()
|
var liveUpdateData: MutableLiveData<MutableMap<Int, UpdateFile>> = MutableLiveData()
|
||||||
|
|
||||||
@@ -67,7 +73,7 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var installing: AtomicBoolean = AtomicBoolean()
|
private var installing = CopyOnWriteArrayList<String>()
|
||||||
|
|
||||||
|
|
||||||
private lateinit var purchaseHelper: PurchaseHelper
|
private lateinit var purchaseHelper: PurchaseHelper
|
||||||
@@ -79,19 +85,21 @@ class UpdateService: LifecycleService() {
|
|||||||
val isCancelled: Boolean = false,
|
val isCancelled: Boolean = false,
|
||||||
val isComplete: Boolean = false)
|
val isComplete: Boolean = false)
|
||||||
|
|
||||||
|
data class AppMetadataStatus(val reason: String, val app: App)
|
||||||
|
|
||||||
@get:RequiresApi(Build.VERSION_CODES.O)
|
@get:RequiresApi(Build.VERSION_CODES.O)
|
||||||
private val notification: Notification
|
private val notification: Notification
|
||||||
get() {
|
get() {
|
||||||
val notificationBuilder =
|
val notificationBuilder =
|
||||||
NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_GENERAL)
|
NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_UPDATER_SERVICE)
|
||||||
return getNotification(notificationBuilder)
|
return getNotification(notificationBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNotification(builder: NotificationCompat.Builder): Notification {
|
private fun getNotification(builder: NotificationCompat.Builder): Notification {
|
||||||
return builder.setAutoCancel(true)
|
return builder.setAutoCancel(true)
|
||||||
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
|
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
|
||||||
.setContentTitle("Updating apps")
|
.setContentTitle(getString(R.string.app_updater_service_notif_title))
|
||||||
.setContentText("Updating apps in the background")
|
.setContentText(getString(R.string.app_updater_service_notif_text))
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setSmallIcon(R.drawable.ic_notification_outlined)
|
.setSmallIcon(R.drawable.ic_notification_outlined)
|
||||||
.build()
|
.build()
|
||||||
@@ -105,22 +113,25 @@ class UpdateService: LifecycleService() {
|
|||||||
val notification = getNotification(
|
val notification = getNotification(
|
||||||
NotificationCompat.Builder(
|
NotificationCompat.Builder(
|
||||||
this,
|
this,
|
||||||
Constants.NOTIFICATION_CHANNEL_GENERAL
|
Constants.NOTIFICATION_CHANNEL_UPDATER_SERVICE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
startForeground(1, notification)
|
startForeground(1, notification)
|
||||||
}
|
}
|
||||||
EventBus.getDefault().register(this)
|
EventBus.getDefault().register(this)
|
||||||
authData = AuthProvider.with(this).getAuthData()
|
authData = AuthProvider.with(this).getAuthData()
|
||||||
purchaseHelper = PurchaseHelper(authData)
|
purchaseHelper = PurchaseHelper(authData).using(HttpClient.getPreferredClient())
|
||||||
downloadManager = DownloadManager.with(this)
|
downloadManager = DownloadManager.with(this)
|
||||||
fetch = downloadManager.fetch
|
fetch = downloadManager.fetch
|
||||||
fetchListener = object : FetchGroupListener {
|
fetchListener = object : FetchGroupListener {
|
||||||
|
|
||||||
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onAdded(groupId, download, fetchGroup)
|
it.onAdded(groupId, download, fetchGroup)
|
||||||
}
|
}
|
||||||
|
if (download.tag != null) {
|
||||||
|
installing.remove(download.tag)
|
||||||
|
}
|
||||||
if (!hasActiveDownloadObserver) {
|
if (!hasActiveDownloadObserver) {
|
||||||
hasActiveDownloadObserver = true
|
hasActiveDownloadObserver = true
|
||||||
fetch.addActiveDownloadsObserver(true, fetchActiveDownloadObserver)
|
fetch.addActiveDownloadsObserver(true, fetchActiveDownloadObserver)
|
||||||
@@ -128,7 +139,7 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onAdded(download: Download) {
|
override fun onAdded(download: Download) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onAdded(download)
|
it.onAdded(download)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,49 +151,49 @@ class UpdateService: LifecycleService() {
|
|||||||
downloadedBytesPerSecond: Long,
|
downloadedBytesPerSecond: Long,
|
||||||
fetchGroup: FetchGroup
|
fetchGroup: FetchGroup
|
||||||
) {
|
) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onProgress(groupId, download, etaInMilliSeconds, downloadedBytesPerSecond, fetchGroup)
|
it.onProgress(groupId, download, etaInMilliSeconds, downloadedBytesPerSecond, fetchGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onProgress(download: Download, etaInMilliSeconds: Long, downloadedBytesPerSecond: Long) {
|
override fun onProgress(download: Download, etaInMilliSeconds: Long, downloadedBytesPerSecond: Long) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onProgress(download, etaInMilliSeconds, downloadedBytesPerSecond)
|
it.onProgress(download, etaInMilliSeconds, downloadedBytesPerSecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueued(groupId: Int, download: Download, waitingNetwork: Boolean, fetchGroup: FetchGroup) {
|
override fun onQueued(groupId: Int, download: Download, waitingNetwork: Boolean, fetchGroup: FetchGroup) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onQueued(groupId, download, waitingNetwork, fetchGroup)
|
it.onQueued(groupId, download, waitingNetwork, fetchGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueued(download: Download, waitingOnNetwork: Boolean) {
|
override fun onQueued(download: Download, waitingOnNetwork: Boolean) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onQueued(download, waitingOnNetwork)
|
it.onQueued(download, waitingOnNetwork)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRemoved(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onRemoved(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onRemoved(groupId, download, fetchGroup)
|
it.onRemoved(groupId, download, fetchGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRemoved(download: Download) {
|
override fun onRemoved(download: Download) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onRemoved(download)
|
it.onRemoved(download)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onResumed(groupId, download, fetchGroup)
|
it.onResumed(groupId, download, fetchGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResumed(download: Download) {
|
override fun onResumed(download: Download) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onResumed(download)
|
it.onResumed(download)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +205,7 @@ class UpdateService: LifecycleService() {
|
|||||||
totalBlocks: Int,
|
totalBlocks: Int,
|
||||||
fetchGroup: FetchGroup
|
fetchGroup: FetchGroup
|
||||||
) {
|
) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onStarted(
|
it.onStarted(
|
||||||
groupId,
|
groupId,
|
||||||
download,
|
download,
|
||||||
@@ -205,7 +216,7 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStarted(download: Download, downloadBlocks: List<DownloadBlock>, totalBlocks: Int) {
|
override fun onStarted(download: Download, downloadBlocks: List<DownloadBlock>, totalBlocks: Int) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onStarted(
|
it.onStarted(
|
||||||
download,
|
download,
|
||||||
downloadBlocks,
|
downloadBlocks,
|
||||||
@@ -214,23 +225,23 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onWaitingNetwork(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onWaitingNetwork(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onWaitingNetwork(groupId, download, fetchGroup)
|
it.onWaitingNetwork(groupId, download, fetchGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWaitingNetwork(download: Download) {
|
override fun onWaitingNetwork(download: Download) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onWaitingNetwork(download)
|
it.onWaitingNetwork(download)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onCompleted(groupId, download, fetchGroup)
|
it.onCompleted(groupId, download, fetchGroup)
|
||||||
}
|
}
|
||||||
if (listeners.isEmpty()) {
|
if (fetchListeners.isEmpty()) {
|
||||||
pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isComplete = true)
|
fetchPendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isComplete = true)
|
||||||
}
|
}
|
||||||
if (fetchGroup.groupDownloadProgress == 100) {
|
if (fetchGroup.groupDownloadProgress == 100) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
@@ -246,35 +257,37 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCompleted(download: Download) {
|
override fun onCompleted(download: Download) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onCompleted(download)
|
it.onCompleted(download)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onCancelled(groupId, download, fetchGroup)
|
it.onCancelled(groupId, download, fetchGroup)
|
||||||
}
|
}
|
||||||
if (listeners.isEmpty()) {
|
if (fetchListeners.isEmpty()) {
|
||||||
pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isCancelled = true)
|
fetchPendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isCancelled = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancelled(download: Download) {
|
override fun onCancelled(download: Download) {
|
||||||
TODO("Not yet implemented")
|
fetchListeners.forEach {
|
||||||
|
it.onCancelled(download)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onDeleted(groupId, download, fetchGroup)
|
it.onDeleted(groupId, download, fetchGroup)
|
||||||
}
|
}
|
||||||
if (listeners.isEmpty()) {
|
if (fetchListeners.isEmpty()) {
|
||||||
pendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isCancelled = true)
|
fetchPendingEvents[groupId] = AppDownloadStatus(download, fetchGroup, isCancelled = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeleted(download: Download) {
|
override fun onDeleted(download: Download) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onDeleted(download)
|
it.onDeleted(download)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,7 +299,7 @@ class UpdateService: LifecycleService() {
|
|||||||
totalBlocks: Int,
|
totalBlocks: Int,
|
||||||
fetchGroup: FetchGroup
|
fetchGroup: FetchGroup
|
||||||
) {
|
) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onDownloadBlockUpdated(
|
it.onDownloadBlockUpdated(
|
||||||
groupId,
|
groupId,
|
||||||
download,
|
download,
|
||||||
@@ -298,7 +311,7 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDownloadBlockUpdated(download: Download, downloadBlock: DownloadBlock, totalBlocks: Int) {
|
override fun onDownloadBlockUpdated(download: Download, downloadBlock: DownloadBlock, totalBlocks: Int) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onDownloadBlockUpdated(
|
it.onDownloadBlockUpdated(
|
||||||
download,
|
download,
|
||||||
downloadBlock,
|
downloadBlock,
|
||||||
@@ -314,7 +327,7 @@ class UpdateService: LifecycleService() {
|
|||||||
throwable: Throwable?,
|
throwable: Throwable?,
|
||||||
fetchGroup: FetchGroup
|
fetchGroup: FetchGroup
|
||||||
) {
|
) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onError(
|
it.onError(
|
||||||
groupId,
|
groupId,
|
||||||
download,
|
download,
|
||||||
@@ -326,7 +339,7 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(download: Download, error: Error, throwable: Throwable?) {
|
override fun onError(download: Download, error: Error, throwable: Throwable?) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onError(
|
it.onError(
|
||||||
download,
|
download,
|
||||||
error,
|
error,
|
||||||
@@ -336,13 +349,13 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onPaused(groupId, download, fetchGroup)
|
it.onPaused(groupId, download, fetchGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPaused(download: Download) {
|
override fun onPaused(download: Download) {
|
||||||
listeners.forEach {
|
fetchListeners.forEach {
|
||||||
it.onPaused(download)
|
it.onPaused(download)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,8 +368,8 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateApp(app: App) {
|
fun updateApp(app: App, removeExisiting: Boolean = false) {
|
||||||
installing.set(true)
|
installing.add(app.packageName)
|
||||||
task {
|
task {
|
||||||
val files = purchaseHelper.purchase(
|
val files = purchaseHelper.purchase(
|
||||||
app.packageName,
|
app.packageName,
|
||||||
@@ -364,27 +377,70 @@ class UpdateService: LifecycleService() {
|
|||||||
app.offerType
|
app.offerType
|
||||||
)
|
)
|
||||||
|
|
||||||
files.map { RequestBuilder.buildRequest(this, app, it) }
|
files.filter { it.url.isNotEmpty() }
|
||||||
} successUi {
|
.map { RequestBuilder.buildRequest(this, app, it) }
|
||||||
val requests = it.filter { request -> request.url.isNotEmpty() }.toList()
|
.toList()
|
||||||
|
} successUi { requests ->
|
||||||
if (requests.isNotEmpty()) {
|
if (requests.isNotEmpty()) {
|
||||||
|
if (removeExisiting) {
|
||||||
|
/*Remove old fetch group if downloaded earlier, mostly in case of updates*/
|
||||||
|
fetch.deleteGroup(app.getGroupId(this))
|
||||||
|
}
|
||||||
fetch.enqueue(requests) {
|
fetch.enqueue(requests) {
|
||||||
Log.i("Updating ${app.displayName}")
|
Log.i("Updating ${app.displayName}")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast("Failed to update ${app.displayName}")
|
installing.remove(app.packageName)
|
||||||
|
Log.e("Failed to download : ${app.displayName}")
|
||||||
|
appMetadataListeners.forEach {
|
||||||
|
it.onAppMetadataStatusError(getString(R.string.purchase_session_expired), app)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appMetadataListeners.isEmpty()) {
|
||||||
|
appMetadataPendingEvents.add(AppMetadataStatus(getString(R.string.purchase_session_expired), app))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} failUi {
|
} failUi { failException ->
|
||||||
Log.e("Failed to update ${app.displayName}")
|
installing.remove(app.packageName)
|
||||||
|
var reason = "Unknown"
|
||||||
|
|
||||||
|
when (failException) {
|
||||||
|
is ApiException.AppNotPurchased -> {
|
||||||
|
reason = getString(R.string.purchase_invalid)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ApiException.AppNotFound -> {
|
||||||
|
reason = getString(R.string.purchase_not_found)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ApiException.AppNotSupported -> {
|
||||||
|
reason = getString(R.string.purchase_unsupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ApiException.EmptyDownloads -> {
|
||||||
|
reason = getString(R.string.purchase_no_file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appMetadataListeners.forEach {
|
||||||
|
it.onAppMetadataStatusError(reason, app)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appMetadataListeners.isEmpty()) {
|
||||||
|
appMetadataPendingEvents.add(AppMetadataStatus(reason, app))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Log.e("Failed to purchase ${app.displayName} : $reason")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var timer: Timer? = null
|
var timer: Timer? = null
|
||||||
val timerTaskRun: Runnable = Runnable {
|
val timerTaskRun: Runnable = Runnable {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
if (!installing.get() && listeners.isEmpty()) {
|
if (installing.isEmpty() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) {
|
||||||
fetch.hasActiveDownloads(true) { hasActiveDownloads ->
|
fetch.hasActiveDownloads(true) { hasActiveDownloads ->
|
||||||
if (!hasActiveDownloads && !installing.get() && listeners.isEmpty()) {
|
if (!hasActiveDownloads && installing.isEmpty() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
@@ -396,8 +452,8 @@ class UpdateService: LifecycleService() {
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun install(packageName: String, files: List<Download>?) {
|
private fun install(packageName: String, files: List<Download>?) {
|
||||||
|
installing.add(packageName)
|
||||||
files?.let { downloads ->
|
files?.let { downloads ->
|
||||||
installing.set(true)
|
|
||||||
var filesExist = true
|
var filesExist = true
|
||||||
|
|
||||||
downloads.forEach { download ->
|
downloads.forEach { download ->
|
||||||
@@ -431,7 +487,16 @@ class UpdateService: LifecycleService() {
|
|||||||
fun onEventBackgroundThreadExec(event: Any) {
|
fun onEventBackgroundThreadExec(event: Any) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is InstallerEvent.Success,
|
is InstallerEvent.Success,
|
||||||
|
is InstallerEvent.Cancelled,
|
||||||
is InstallerEvent.Failed -> {
|
is InstallerEvent.Failed -> {
|
||||||
|
when (event) {
|
||||||
|
is InstallerEvent.Success -> event.packageName
|
||||||
|
is InstallerEvent.Cancelled -> event.packageName
|
||||||
|
is InstallerEvent.Failed -> event.packageName
|
||||||
|
else -> null
|
||||||
|
}?.run {
|
||||||
|
installing.remove(this)
|
||||||
|
}
|
||||||
synchronized(timerLock) {
|
synchronized(timerLock) {
|
||||||
if (timer != null) {
|
if (timer != null) {
|
||||||
timer!!.cancel()
|
timer!!.cancel()
|
||||||
@@ -440,8 +505,7 @@ class UpdateService: LifecycleService() {
|
|||||||
if (timer == null) {
|
if (timer == null) {
|
||||||
timer = Timer()
|
timer = Timer()
|
||||||
}
|
}
|
||||||
installing.set(false)
|
timer!!.schedule(timerTask { timerTaskRun.run() }, 5 * 1000)
|
||||||
timer!!.schedule(timerTask { timerTaskRun.run() }, 10 * 1000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@@ -450,9 +514,9 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun registerListener(listener: FetchGroupListener) {
|
fun registerFetchListener(listener: FetchGroupListener) {
|
||||||
listeners.add(listener)
|
fetchListeners.add(listener)
|
||||||
val iterator = pendingEvents.iterator()
|
val iterator = fetchPendingEvents.iterator()
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
val item = iterator.next()
|
val item = iterator.next()
|
||||||
if (item.value.isCancelled && !item.value.isComplete) {
|
if (item.value.isCancelled && !item.value.isComplete) {
|
||||||
@@ -464,8 +528,22 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregisterListener(listener: AbstractFetchGroupListener) {
|
fun registerAppMetadataListener(listener: AppMetadataStatusListener) {
|
||||||
listeners.remove(listener)
|
appMetadataListeners.add(listener)
|
||||||
|
val iterator = appMetadataPendingEvents.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val item = iterator.next()
|
||||||
|
listener.onAppMetadataStatusError(item.reason, item.app)
|
||||||
|
iterator.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unregisterAppMetadataListener(listener: AppMetadataStatusListener) {
|
||||||
|
appMetadataListeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unregisterFetchListener(listener: AbstractFetchGroupListener) {
|
||||||
|
fetchListeners.remove(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var binder: UpdateServiceBinder = UpdateServiceBinder()
|
private var binder: UpdateServiceBinder = UpdateServiceBinder()
|
||||||
@@ -476,15 +554,16 @@ class UpdateService: LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onUnbind(intent: Intent?): Boolean {
|
override fun onUnbind(intent: Intent?): Boolean {
|
||||||
listeners.clear()
|
fetchListeners.clear()
|
||||||
if (!installing.get()) {
|
appMetadataListeners.clear()
|
||||||
fetch.hasActiveDownloads(true, { hasActiveDownloads ->
|
if (installing.isEmpty()) {
|
||||||
if (!hasActiveDownloads && !installing.get() && listeners.isEmpty()) {
|
fetch.hasActiveDownloads(true) { hasActiveDownloads ->
|
||||||
|
if (!hasActiveDownloads && installing.isEmpty() && fetchListeners.isEmpty() && appMetadataListeners.isEmpty()) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import com.aurora.gplayapi.data.models.App
|
|||||||
import com.aurora.gplayapi.data.models.File
|
import com.aurora.gplayapi.data.models.File
|
||||||
|
|
||||||
fun Context.getInternalBaseDirectory(): String {
|
fun Context.getInternalBaseDirectory(): String {
|
||||||
return filesDir.path
|
return (getExternalFilesDir(null) ?: filesDir).path
|
||||||
}
|
}
|
||||||
|
|
||||||
object PathUtil {
|
object PathUtil {
|
||||||
|
|||||||
@@ -35,21 +35,18 @@ import com.aurora.Constants
|
|||||||
import com.aurora.extensions.*
|
import com.aurora.extensions.*
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.data.models.AuthData
|
import com.aurora.gplayapi.data.models.AuthData
|
||||||
import com.aurora.gplayapi.data.models.File
|
|
||||||
import com.aurora.gplayapi.exceptions.ApiException
|
|
||||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
|
||||||
import com.aurora.store.MainActivity
|
import com.aurora.store.MainActivity
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.State
|
import com.aurora.store.State
|
||||||
import com.aurora.store.data.downloader.DownloadManager
|
import com.aurora.store.data.downloader.DownloadManager
|
||||||
import com.aurora.store.data.downloader.RequestBuilder
|
|
||||||
import com.aurora.store.data.downloader.getGroupId
|
import com.aurora.store.data.downloader.getGroupId
|
||||||
import com.aurora.store.data.event.BusEvent
|
import com.aurora.store.data.event.BusEvent
|
||||||
import com.aurora.store.data.event.InstallerEvent
|
import com.aurora.store.data.event.InstallerEvent
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
import com.aurora.store.data.network.HttpClient
|
import com.aurora.store.data.network.HttpClient
|
||||||
import com.aurora.store.data.providers.AuthProvider
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
|
import com.aurora.store.data.service.AppMetadataStatusListener
|
||||||
import com.aurora.store.data.service.UpdateService
|
import com.aurora.store.data.service.UpdateService
|
||||||
import com.aurora.store.databinding.ActivityDetailsBinding
|
import com.aurora.store.databinding.ActivityDetailsBinding
|
||||||
import com.aurora.store.util.*
|
import com.aurora.store.util.*
|
||||||
@@ -69,7 +66,6 @@ import org.apache.commons.io.FileUtils
|
|||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class AppDetailsActivity : BaseDetailsActivity() {
|
class AppDetailsActivity : BaseDetailsActivity() {
|
||||||
|
|
||||||
@@ -78,14 +74,18 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
|
|
||||||
private lateinit var authData: AuthData
|
private lateinit var authData: AuthData
|
||||||
private lateinit var app: App
|
private lateinit var app: App
|
||||||
|
private var fetch: Fetch? = null
|
||||||
|
private var downloadManager: DownloadManager? = null
|
||||||
|
|
||||||
private var updateService: UpdateService? = null
|
private var updateService: UpdateService? = null
|
||||||
private var pendingAddListener = true
|
private var pendingAddListener = true
|
||||||
private var serviceConnection = object : ServiceConnection {
|
private var serviceConnection = object : ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||||
updateService = (binder as UpdateService.UpdateServiceBinder).getUpdateService()
|
updateService = (binder as UpdateService.UpdateServiceBinder).getUpdateService()
|
||||||
if (::fetchGroupListener.isInitialized) {
|
if (::fetchGroupListener.isInitialized && ::appMetadataListener.isInitialized) {
|
||||||
updateService!!.registerListener(fetchGroupListener)
|
updateService!!.registerFetchListener(fetchGroupListener)
|
||||||
|
// appMetadataListener needs to be initialized after the fetchGroupListener
|
||||||
|
updateService!!.registerAppMetadataListener(appMetadataListener)
|
||||||
pendingAddListener = false
|
pendingAddListener = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,6 +95,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private lateinit var fetchGroupListener: FetchGroupListener
|
private lateinit var fetchGroupListener: FetchGroupListener
|
||||||
|
private lateinit var appMetadataListener: AppMetadataStatusListener
|
||||||
private lateinit var completionMarker: java.io.File
|
private lateinit var completionMarker: java.io.File
|
||||||
private lateinit var inProgressMarker: java.io.File
|
private lateinit var inProgressMarker: java.io.File
|
||||||
|
|
||||||
@@ -448,14 +449,14 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
private fun startDownload() {
|
private fun startDownload() {
|
||||||
when (status) {
|
when (status) {
|
||||||
Status.PAUSED -> {
|
Status.PAUSED -> {
|
||||||
updateService?.fetch?.resumeGroup(app.getGroupId(this@AppDetailsActivity))
|
fetch?.resumeGroup(app.getGroupId(this@AppDetailsActivity))
|
||||||
}
|
}
|
||||||
Status.DOWNLOADING -> {
|
Status.DOWNLOADING -> {
|
||||||
flip(1)
|
flip(1)
|
||||||
toast("Already downloading")
|
toast("Already downloading")
|
||||||
}
|
}
|
||||||
Status.COMPLETED -> {
|
Status.COMPLETED -> {
|
||||||
updateService?.fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) {
|
fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) {
|
||||||
verifyAndInstall(it.downloads)
|
verifyAndInstall(it.downloads)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,91 +467,15 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun purchase() {
|
private fun purchase() {
|
||||||
|
bottomSheetBehavior.isHideable = false
|
||||||
|
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
updateActionState(State.PROGRESS)
|
updateActionState(State.PROGRESS)
|
||||||
|
|
||||||
task {
|
runWithPermissions(
|
||||||
val authData = AuthProvider
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
.with(this)
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
.getAuthData()
|
) {
|
||||||
|
updateService?.updateApp(app, removeExisiting = true)
|
||||||
PurchaseHelper(authData)
|
|
||||||
.using(HttpClient.getPreferredClient())
|
|
||||||
.purchase(app.packageName, app.versionCode, app.offerType)
|
|
||||||
} successUi { files ->
|
|
||||||
if (files.isNotEmpty()) {
|
|
||||||
var hasOBB = false
|
|
||||||
|
|
||||||
files.forEach { file ->
|
|
||||||
if (file.type == File.FileType.OBB || file.type == File.FileType.PATCH) {
|
|
||||||
hasOBB = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasOBB)
|
|
||||||
enqueueWithStoragePermission(files)
|
|
||||||
else
|
|
||||||
enqueue(files)
|
|
||||||
} else {
|
|
||||||
Log.e("Failed to download : ${app.displayName}")
|
|
||||||
updateActionState(State.IDLE)
|
|
||||||
}
|
|
||||||
} failUi {
|
|
||||||
updateActionState(State.IDLE)
|
|
||||||
var reason = "Unknown"
|
|
||||||
|
|
||||||
when (it) {
|
|
||||||
is ApiException.AppNotPurchased -> {
|
|
||||||
reason = getString(R.string.purchase_invalid)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ApiException.AppNotFound -> {
|
|
||||||
reason = getString(R.string.purchase_not_found)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ApiException.AppNotSupported -> {
|
|
||||||
reason = getString(R.string.purchase_unsupported)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ApiException.EmptyDownloads -> {
|
|
||||||
reason = getString(R.string.purchase_no_file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expandBottomSheet(reason)
|
|
||||||
|
|
||||||
Log.e("Failed to purchase ${app.displayName} : $reason")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun enqueueWithStoragePermission(files: List<File>) = runWithPermissions(
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
) {
|
|
||||||
enqueue(files)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun enqueue(files: List<File>) {
|
|
||||||
val requestList = files
|
|
||||||
.filter { it.url.isNotEmpty() }
|
|
||||||
.map {
|
|
||||||
RequestBuilder.buildRequest(this, app, it)
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
if (requestList.isNotEmpty()) {
|
|
||||||
/*Remove old fetch group if downloaded earlier, mostly in case of updates*/
|
|
||||||
updateService?.fetch?.deleteGroup(app.getGroupId(this@AppDetailsActivity))
|
|
||||||
|
|
||||||
/*Enqueue new fetch group*/
|
|
||||||
updateService?.fetch?.enqueue(
|
|
||||||
requestList
|
|
||||||
) {
|
|
||||||
status = Status.ADDED
|
|
||||||
Log.i("Downloading Apks : %s", app.displayName)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updateActionState(State.IDLE)
|
|
||||||
expandBottomSheet(getString(R.string.purchase_session_expired))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,11 +485,18 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
downloadedBytesPerSecond: Long
|
downloadedBytesPerSecond: Long
|
||||||
) {
|
) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
|
if (isInstalled) {
|
||||||
|
return@runOnUiThread
|
||||||
|
}
|
||||||
val progress = if (fetchGroup.groupDownloadProgress > 0)
|
val progress = if (fetchGroup.groupDownloadProgress > 0)
|
||||||
fetchGroup.groupDownloadProgress
|
fetchGroup.groupDownloadProgress
|
||||||
else
|
else
|
||||||
0
|
0
|
||||||
|
|
||||||
|
if (progress == 100) {
|
||||||
|
B.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing))
|
||||||
|
return@runOnUiThread
|
||||||
|
}
|
||||||
B.layoutDetailsInstall.apply {
|
B.layoutDetailsInstall.apply {
|
||||||
txtProgressPercent.text = ("${progress}%")
|
txtProgressPercent.text = ("${progress}%")
|
||||||
|
|
||||||
@@ -662,13 +594,17 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun attachFetch() {
|
private fun attachFetch() {
|
||||||
updateService?.fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { fetchGroup: FetchGroup ->
|
if (fetch == null) {
|
||||||
|
downloadManager = DownloadManager.with(this)
|
||||||
|
fetch = downloadManager!!.fetch
|
||||||
|
}
|
||||||
|
fetch?.getFetchGroup(app.getGroupId(this@AppDetailsActivity)) { fetchGroup: FetchGroup ->
|
||||||
if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.completedDownloads.isNotEmpty()) {
|
if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.completedDownloads.isNotEmpty()) {
|
||||||
status = Status.COMPLETED
|
status = Status.COMPLETED
|
||||||
} else if (updateService?.downloadManager?.isDownloading(fetchGroup) == true) {
|
} else if (downloadManager?.isDownloading(fetchGroup) == true) {
|
||||||
status = Status.DOWNLOADING
|
status = Status.DOWNLOADING
|
||||||
flip(1)
|
flip(1)
|
||||||
} else if (updateService?.downloadManager?.isCanceled(fetchGroup) == true) {
|
} else if (downloadManager?.isCanceled(fetchGroup) == true) {
|
||||||
status = Status.CANCELLED
|
status = Status.CANCELLED
|
||||||
} else if (fetchGroup.pausedDownloads.isNotEmpty()) {
|
} else if (fetchGroup.pausedDownloads.isNotEmpty()) {
|
||||||
status = Status.PAUSED
|
status = Status.PAUSED
|
||||||
@@ -679,6 +615,12 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
|
|
||||||
fetchGroupListener = object : AbstractFetchGroupListener() {
|
fetchGroupListener = object : AbstractFetchGroupListener() {
|
||||||
|
|
||||||
|
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||||
|
if (groupId == app.getGroupId(this@AppDetailsActivity)) {
|
||||||
|
status = download.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStarted(
|
override fun onStarted(
|
||||||
groupId: Int,
|
groupId: Int,
|
||||||
download: Download,
|
download: Download,
|
||||||
@@ -745,9 +687,6 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
ex.printStackTrace()
|
ex.printStackTrace()
|
||||||
}
|
}
|
||||||
runOnUiThread {
|
|
||||||
B.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -774,16 +713,27 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appMetadataListener = object : AppMetadataStatusListener {
|
||||||
|
override fun onAppMetadataStatusError(reason: String, app: App) {
|
||||||
|
if (app.packageName == this@AppDetailsActivity.app.packageName) {
|
||||||
|
updateActionState(State.IDLE)
|
||||||
|
expandBottomSheet(reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getUpdateServiceInstance()
|
getUpdateServiceInstance()
|
||||||
|
|
||||||
B.layoutDetailsInstall.imgCancel.setOnClickListener {
|
B.layoutDetailsInstall.imgCancel.setOnClickListener {
|
||||||
updateService?.fetch?.cancelGroup(
|
fetch?.cancelGroup(
|
||||||
app.getGroupId(this@AppDetailsActivity)
|
app.getGroupId(this@AppDetailsActivity)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (pendingAddListener && updateService != null) {
|
if (pendingAddListener && updateService != null) {
|
||||||
pendingAddListener = false
|
pendingAddListener = false
|
||||||
updateService!!.registerListener(fetchGroupListener)
|
updateService!!.registerFetchListener(fetchGroupListener)
|
||||||
|
// appMetadataListener needs to be initialized after the fetchGroupListener
|
||||||
|
updateService!!.registerAppMetadataListener(appMetadataListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.aurora.store.R
|
|||||||
import com.aurora.store.data.downloader.DownloadManager
|
import com.aurora.store.data.downloader.DownloadManager
|
||||||
import com.aurora.store.data.model.DownloadFile
|
import com.aurora.store.data.model.DownloadFile
|
||||||
import com.aurora.store.databinding.ActivityDownloadBinding
|
import com.aurora.store.databinding.ActivityDownloadBinding
|
||||||
|
import com.aurora.store.util.Preferences
|
||||||
import com.aurora.store.view.epoxy.views.DownloadViewModel_
|
import com.aurora.store.view.epoxy.views.DownloadViewModel_
|
||||||
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
|
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
|
||||||
import com.aurora.store.view.ui.commons.BaseActivity
|
import com.aurora.store.view.ui.commons.BaseActivity
|
||||||
@@ -156,10 +157,12 @@ class DownloadActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
R.id.action_clear_completed -> {
|
R.id.action_clear_completed -> {
|
||||||
fetch.removeAllWithStatus(Status.COMPLETED)
|
fetch.removeAllWithStatus(Status.COMPLETED)
|
||||||
|
Preferences.getPrefs(this).edit().remove(Preferences.PREFERENCE_UNIQUE_GROUP_IDS).apply()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_force_clear_all -> {
|
R.id.action_force_clear_all -> {
|
||||||
fetch.deleteAll()
|
fetch.deleteAll()
|
||||||
|
Preferences.getPrefs(this).edit().remove(Preferences.PREFERENCE_UNIQUE_GROUP_IDS).apply()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,21 +113,8 @@ class OnboardingActivity : BaseActivity() {
|
|||||||
B.viewpager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
B.viewpager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
B.btnBackward.isEnabled = position != 0
|
lastPosition = position
|
||||||
if (position == 4) {
|
refreshButtonState()
|
||||||
B.btnForward.text = getString(R.string.action_finish)
|
|
||||||
B.btnForward.setOnClickListener {
|
|
||||||
save(PREFERENCE_INTRO, true)
|
|
||||||
open(SplashActivity::class.java, true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
B.btnForward.text = getString(R.string.action_next)
|
|
||||||
B.btnForward.setOnClickListener {
|
|
||||||
B.viewpager2.setCurrentItem(
|
|
||||||
B.viewpager2.currentItem + 1, true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -145,6 +132,32 @@ class OnboardingActivity : BaseActivity() {
|
|||||||
B.viewpager2.setCurrentItem(B.viewpager2.currentItem - 1, true)
|
B.viewpager2.setCurrentItem(B.viewpager2.currentItem - 1, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lastPosition = 0
|
||||||
|
|
||||||
|
fun refreshButtonState() {
|
||||||
|
B.btnBackward.isEnabled = lastPosition != 0
|
||||||
|
if (lastPosition == 4) {
|
||||||
|
B.btnForward.text = getString(R.string.action_finish)
|
||||||
|
B.btnForward.setOnClickListener {
|
||||||
|
save(PREFERENCE_INTRO, true)
|
||||||
|
open(SplashActivity::class.java, true)
|
||||||
|
}
|
||||||
|
for (fragment in supportFragmentManager.fragments) {
|
||||||
|
if (fragment is PermissionsFragment) {
|
||||||
|
B.btnForward.isEnabled = fragment.canGoForward()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
B.btnForward.text = getString(R.string.action_next)
|
||||||
|
B.btnForward.setOnClickListener {
|
||||||
|
B.viewpager2.setCurrentItem(
|
||||||
|
B.viewpager2.currentItem + 1, true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (B.viewpager2.currentItem == 0) {
|
if (B.viewpager2.currentItem == 0) {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
|
|||||||
@@ -91,6 +91,21 @@ class PermissionsFragment : BaseFragment() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
B.epoxyRecycler.withModels {
|
B.epoxyRecycler.withModels {
|
||||||
|
val writeExternalStorage = ActivityCompat.checkSelfPermission(
|
||||||
|
requireContext(),
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
val storageManager = if (isRAndAbove()) Environment.isExternalStorageManager() else true
|
||||||
|
val canInstallPackages = if (isOAndAbove()) requireContext().packageManager.canRequestPackageInstalls() else true
|
||||||
|
canGoForward = writeExternalStorage && storageManager && canInstallPackages
|
||||||
|
if (canGoForwardInitial == null) {
|
||||||
|
canGoForwardInitial = canGoForward
|
||||||
|
}
|
||||||
|
if (canGoForward && canGoForwardInitial == false) {
|
||||||
|
if (activity is OnboardingActivity) {
|
||||||
|
(activity!! as OnboardingActivity).refreshButtonState()
|
||||||
|
}
|
||||||
|
}
|
||||||
setFilterDuplicates(true)
|
setFilterDuplicates(true)
|
||||||
installerList.forEach {
|
installerList.forEach {
|
||||||
add(
|
add(
|
||||||
@@ -99,12 +114,9 @@ class PermissionsFragment : BaseFragment() {
|
|||||||
.permission(it)
|
.permission(it)
|
||||||
.isGranted(
|
.isGranted(
|
||||||
when (it.id) {
|
when (it.id) {
|
||||||
0 -> ActivityCompat.checkSelfPermission(
|
0 -> writeExternalStorage
|
||||||
requireContext(),
|
1 -> storageManager
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
2 -> canInstallPackages
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
|
||||||
1 -> if (isRAndAbove()) Environment.isExternalStorageManager() else true
|
|
||||||
2 -> if (isOAndAbove()) requireContext().packageManager.canRequestPackageInstalls() else true
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -164,4 +176,11 @@ class PermissionsFragment : BaseFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var canGoForward = false
|
||||||
|
private var canGoForwardInitial: Boolean? = null
|
||||||
|
|
||||||
|
fun canGoForward(): Boolean {
|
||||||
|
return canGoForward
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -19,9 +19,7 @@
|
|||||||
|
|
||||||
package com.aurora.store.view.ui.updates
|
package com.aurora.store.view.ui.updates
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -32,18 +30,12 @@ import android.view.ViewGroup
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.aurora.Constants
|
import com.aurora.Constants
|
||||||
import com.aurora.extensions.stackTraceToString
|
import com.aurora.extensions.stackTraceToString
|
||||||
import com.aurora.extensions.toast
|
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.data.models.AuthData
|
|
||||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
|
||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.State
|
import com.aurora.store.State
|
||||||
import com.aurora.store.data.downloader.DownloadManager
|
|
||||||
import com.aurora.store.data.downloader.RequestBuilder
|
|
||||||
import com.aurora.store.data.downloader.getGroupId
|
import com.aurora.store.data.downloader.getGroupId
|
||||||
import com.aurora.store.data.installer.AppInstaller
|
import com.aurora.store.data.installer.AppInstaller
|
||||||
import com.aurora.store.data.model.UpdateFile
|
import com.aurora.store.data.model.UpdateFile
|
||||||
import com.aurora.store.data.providers.AuthProvider
|
|
||||||
import com.aurora.store.data.service.UpdateService
|
import com.aurora.store.data.service.UpdateService
|
||||||
import com.aurora.store.databinding.FragmentUpdatesBinding
|
import com.aurora.store.databinding.FragmentUpdatesBinding
|
||||||
import com.aurora.store.util.Log
|
import com.aurora.store.util.Log
|
||||||
@@ -56,11 +48,8 @@ import com.aurora.store.view.ui.sheets.AppMenuSheet
|
|||||||
import com.aurora.store.viewmodel.all.UpdatesViewModel
|
import com.aurora.store.viewmodel.all.UpdatesViewModel
|
||||||
import com.tonyodev.fetch2.AbstractFetchGroupListener
|
import com.tonyodev.fetch2.AbstractFetchGroupListener
|
||||||
import com.tonyodev.fetch2.Download
|
import com.tonyodev.fetch2.Download
|
||||||
import com.tonyodev.fetch2.Fetch
|
|
||||||
import com.tonyodev.fetch2.FetchGroup
|
import com.tonyodev.fetch2.FetchGroup
|
||||||
import nl.komponents.kovenant.task
|
import nl.komponents.kovenant.task
|
||||||
import nl.komponents.kovenant.ui.failUi
|
|
||||||
import nl.komponents.kovenant.ui.successUi
|
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
class UpdatesFragment : BaseFragment() {
|
class UpdatesFragment : BaseFragment() {
|
||||||
@@ -72,7 +61,7 @@ class UpdatesFragment : BaseFragment() {
|
|||||||
private var serviceConnection = object : ServiceConnection {
|
private var serviceConnection = object : ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||||
updateService = (binder as UpdateService.UpdateServiceBinder).getUpdateService()
|
updateService = (binder as UpdateService.UpdateServiceBinder).getUpdateService()
|
||||||
updateService!!.registerListener(fetchListener)
|
updateService!!.registerFetchListener(fetchListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName) {
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
@@ -158,12 +147,12 @@ class UpdatesFragment : BaseFragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
VM.liveUpdateData.observe(viewLifecycleOwner, {
|
VM.liveUpdateData.observe(viewLifecycleOwner) {
|
||||||
updateFileMap = it
|
updateFileMap = it
|
||||||
updateController(updateFileMap)
|
updateController(updateFileMap)
|
||||||
B.swipeRefreshLayout.isRefreshing = false
|
B.swipeRefreshLayout.isRefreshing = false
|
||||||
updateService?.liveUpdateData?.postValue(updateFileMap)
|
updateService?.liveUpdateData?.postValue(updateFileMap)
|
||||||
})
|
}
|
||||||
|
|
||||||
B.swipeRefreshLayout.setOnRefreshListener {
|
B.swipeRefreshLayout.setOnRefreshListener {
|
||||||
VM.observe()
|
VM.observe()
|
||||||
|
|||||||
@@ -366,4 +366,7 @@
|
|||||||
<string name="toast_aas_token_failed">Failed to generate AAS Token</string>
|
<string name="toast_aas_token_failed">Failed to generate AAS Token</string>
|
||||||
<string name="toast_export_success">Device config exported successfully</string>
|
<string name="toast_export_success">Device config exported successfully</string>
|
||||||
<string name="toast_export_failed">Failed to export device config</string>
|
<string name="toast_export_failed">Failed to export device config</string>
|
||||||
|
<string name="notification_channel_updater_service">App background download service</string>
|
||||||
|
<string name="app_updater_service_notif_title">Background app download</string>
|
||||||
|
<string name="app_updater_service_notif_text">Enables background app download</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user