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:
Konstantin Tuev
2021-06-14 20:58:58 +03:00
parent 4e111fa9f4
commit b7ebb772dd
13 changed files with 329 additions and 216 deletions

View File

@@ -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,

View File

@@ -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"

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
} }
} }

View File

@@ -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
} }

View File

@@ -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 {

View File

@@ -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)
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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()

View File

@@ -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
}
} }

View File

@@ -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()

View File

@@ -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>