diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6bc41341a..684179a22 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -150,6 +150,7 @@
+
listener }
+ background = ColorDrawable(backgroundColor)
+ }.create()
+
+ builder.show()
+ }
+}
+
fun Fragment.showDialog(@StringRes titleId: Int, @StringRes messageId: Int) {
requireContext().showDialog(titleId, messageId)
}
diff --git a/app/src/main/java/com/aurora/store/MainActivity.kt b/app/src/main/java/com/aurora/store/MainActivity.kt
index b8d5d027d..a38a5275d 100644
--- a/app/src/main/java/com/aurora/store/MainActivity.kt
+++ b/app/src/main/java/com/aurora/store/MainActivity.kt
@@ -21,6 +21,7 @@ package com.aurora.store
import android.Manifest
import android.content.Intent
+import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Bundle
import android.os.Environment
@@ -39,13 +40,15 @@ import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.Navigation
import androidx.navigation.ui.NavigationUI
-import com.aurora.extensions.getEmptyActivityBundle
-import com.aurora.extensions.getStyledAttributeColor
-import com.aurora.extensions.load
-import com.aurora.extensions.open
+import com.aurora.Constants
+import com.aurora.extensions.*
import com.aurora.gplayapi.data.models.AuthData
+import com.aurora.store.data.model.SelfUpdate
+import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
+import com.aurora.store.data.service.SelfUpdateService
import com.aurora.store.databinding.ActivityMainBinding
+import com.aurora.store.util.CertUtil.isFDroidApp
import com.aurora.store.util.Log
import com.aurora.store.util.Preferences
import com.aurora.store.view.ui.about.AboutActivity
@@ -60,7 +63,10 @@ import com.aurora.store.view.ui.search.SearchSuggestionActivity
import com.aurora.store.view.ui.spoof.SpoofActivity
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.google.android.material.bottomnavigation.BottomNavigationView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.livinglifetechway.quickpermissions_kotlin.runWithPermissions
+import nl.komponents.kovenant.task
+import nl.komponents.kovenant.ui.successUi
class MainActivity : BaseActivity() {
@@ -116,6 +122,9 @@ class MainActivity : BaseActivity() {
checkExternalStorageAccessPermission()
checkExternalStorageManagerPermission()
}
+
+ /* Check self update */
+ checkSelfUpdate()
}
private fun attachToolbar() {
@@ -237,4 +246,63 @@ class MainActivity : BaseActivity() {
startActivity(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION))
}
}
+
+ private fun checkSelfUpdate() {
+ task {
+ HttpClient.getPreferredClient().get(Constants.UPDATE_URL, mapOf())
+ } successUi { playResponse ->
+ if (playResponse.isSuccessful) {
+ val selfUpdate = gson.fromJson(
+ String(playResponse.responseBytes),
+ SelfUpdate::class.java
+ )
+
+ selfUpdate?.let { it ->
+ if (it.versionCode > BuildConfig.VERSION_CODE) {
+ if (isFDroidApp(this, BuildConfig.APPLICATION_ID)) {
+ if (it.fdroidBuild.isNotEmpty()) {
+ showUpdatesDialog(it)
+ }
+ } else if (it.auroraBuild.isNotEmpty()) {
+ showUpdatesDialog(it)
+ } else {
+ Log.i(getString(R.string.details_no_updates))
+ }
+ } else {
+ Log.i(getString(R.string.details_no_updates))
+ }
+ }
+ } else {
+ Log.e("Failed to check self update")
+ }
+ }
+ }
+
+ private fun showUpdatesDialog(selfUpdate: SelfUpdate) {
+ val messages: List = listOf(
+ selfUpdate.versionCode.toString(),
+ if (selfUpdate.changelog.isEmpty())
+ getString(R.string.details_changelog_unavailable)
+ else
+ selfUpdate.changelog,
+ getString(R.string.dialog_desc_self_update)
+ )
+
+ val builder: MaterialAlertDialogBuilder = MaterialAlertDialogBuilder(this)
+ .setTitle(getString(R.string.dialog_title_self_update))
+ .setMessage(messages.joinToString(separator = "\n"))
+ .setPositiveButton(getString(R.string.action_update)) { _, _ ->
+ val intent = Intent(this, SelfUpdateService::class.java)
+ intent.putExtra(Constants.STRING_EXTRA, gson.toJson(selfUpdate))
+ startService(intent)
+ }
+ .setNegativeButton(getString(R.string.action_later)) { dialog, _ ->
+ dialog.dismiss()
+ }
+
+ val backGroundColor: Int = getStyledAttributeColor(android.R.attr.colorBackground)
+ builder.background = ColorDrawable(backGroundColor)
+ builder.create()
+ builder.show()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt
index c7f825683..9f1860abc 100644
--- a/app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt
+++ b/app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt
@@ -32,6 +32,7 @@ class NativeInstaller(context: Context) : InstallerBase(context) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
} else {
+ Log.i("Received native install request for $packageName")
files.map {
when (it) {
is File -> it
diff --git a/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt
index 63e72e17b..f5883dfed 100644
--- a/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt
+++ b/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt
@@ -42,7 +42,7 @@ class SessionInstaller(context: Context) : InstallerBase(context) {
if (isAlreadyQueued(packageName)) {
Log.i("$packageName already queued")
} else {
- Log.i("Received service install request for $packageName")
+ Log.i("Received session install request for $packageName")
val uriList = files.map {
when (it) {
is File -> getUri(it)
diff --git a/app/src/main/java/com/aurora/store/data/model/SelfUpdate.kt b/app/src/main/java/com/aurora/store/data/model/SelfUpdate.kt
new file mode 100644
index 000000000..475bf1c18
--- /dev/null
+++ b/app/src/main/java/com/aurora/store/data/model/SelfUpdate.kt
@@ -0,0 +1,39 @@
+/*
+ * Aurora Store
+ * Copyright (C) 2021, Rahul Kumar Patel
+ *
+ * Aurora Store is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Aurora Store is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Aurora Store. If not, see .
+ *
+ */
+
+package com.aurora.store.data.model
+
+import com.google.gson.annotations.SerializedName
+
+class SelfUpdate {
+ @SerializedName("version_name")
+ var versionName: String = String()
+
+ @SerializedName("version_code")
+ var versionCode: Int = 0
+
+ @SerializedName("aurora_build")
+ var auroraBuild: String = String()
+
+ @SerializedName("fdroid_build")
+ var fdroidBuild: String = String()
+
+ @SerializedName("changelog")
+ var changelog: String = String()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt
index 90854ac28..48293aa37 100644
--- a/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt
+++ b/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt
@@ -23,12 +23,12 @@ import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
+import com.aurora.store.BuildConfig
import com.aurora.store.data.event.BusEvent.InstallEvent
import com.aurora.store.data.event.BusEvent.UninstallEvent
import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.util.PathUtil
import com.aurora.store.util.Preferences
-import com.aurora.store.util.isExternalStorageEnable
import org.greenrobot.eventbus.EventBus
import java.io.File
@@ -62,6 +62,10 @@ open class PackageManagerReceiver : BroadcastReceiver() {
if (isAutoDeleteAPKEnabled)
clearDownloads(context, packageName)
+
+ //Clear self update apk
+ if (packageName == BuildConfig.APPLICATION_ID)
+ clearDownloads(context, packageName)
}
}
diff --git a/app/src/main/java/com/aurora/store/data/service/SelfUpdateService.kt b/app/src/main/java/com/aurora/store/data/service/SelfUpdateService.kt
new file mode 100644
index 000000000..2b5f85654
--- /dev/null
+++ b/app/src/main/java/com/aurora/store/data/service/SelfUpdateService.kt
@@ -0,0 +1,168 @@
+package com.aurora.store.data.service
+
+import android.app.Notification
+import android.app.Service
+import android.content.Intent
+import android.os.Build
+import android.os.IBinder
+import androidx.annotation.RequiresApi
+import androidx.core.app.NotificationCompat
+import androidx.core.content.ContextCompat
+import com.aurora.Constants
+import com.aurora.gplayapi.data.models.App
+import com.aurora.gplayapi.data.models.File
+import com.aurora.store.BuildConfig
+import com.aurora.store.R
+import com.aurora.store.data.downloader.DownloadManager
+import com.aurora.store.data.downloader.RequestBuilder.buildRequest
+import com.aurora.store.data.installer.AppInstaller
+import com.aurora.store.data.model.SelfUpdate
+import com.aurora.store.util.CertUtil.isFDroidApp
+import com.aurora.store.util.Log
+import com.google.gson.Gson
+import com.google.gson.GsonBuilder
+import com.tonyodev.fetch2.*
+import nl.komponents.kovenant.task
+import org.apache.commons.lang3.StringUtils
+import java.lang.reflect.Modifier
+import java.util.*
+import java.util.concurrent.TimeUnit
+
+class SelfUpdateService : Service() {
+ private lateinit var app: App
+ private lateinit var fetch: Fetch
+ private lateinit var fetchListener: FetchListener
+
+ private var gson: Gson = GsonBuilder()
+ .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT)
+ .create()
+
+ private val hashCode = BuildConfig.APPLICATION_ID.hashCode()
+
+ override fun onBind(intent: Intent): IBinder? {
+ return null
+ }
+
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ val rawSelfUpdate = intent.getStringExtra(Constants.STRING_EXTRA)
+ if (StringUtils.isNotEmpty(rawSelfUpdate)) {
+ val selfUpdate = gson.fromJson(rawSelfUpdate, SelfUpdate::class.java)
+ selfUpdate?.let {
+ downloadAndUpdate(it)
+ }
+ }
+ return START_NOT_STICKY
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForeground(1, notification)
+ } else {
+ val notification = getNotification(
+ NotificationCompat.Builder(
+ this,
+ Constants.NOTIFICATION_CHANNEL_GENERAL
+ )
+ )
+ startForeground(1, notification)
+ }
+ }
+
+ private fun destroyService() {
+ Log.d("Self-update service destroyed")
+ fetch.removeListener(fetchListener)
+ stopForeground(true)
+ stopSelf()
+ }
+
+ private fun downloadAndUpdate(update: SelfUpdate) {
+ app = App(BuildConfig.APPLICATION_ID)
+ app.id = hashCode
+ app.packageName = BuildConfig.APPLICATION_ID
+ app.displayName = getString(R.string.app_name)
+ app.versionName = update.versionName
+ app.versionCode = update.versionCode
+
+ val file = File().apply {
+ name = "AuroraStore.apk"
+ url = if (isFDroidVariant)
+ update.fdroidBuild
+ else
+ update.auroraBuild
+ type = File.FileType.BASE
+ }
+
+ val request = buildRequest(this, app, file)
+ request.enqueueAction = EnqueueAction.REPLACE_EXISTING
+
+ val requestList: MutableList = ArrayList()
+ requestList.add(request)
+
+ fetch = DownloadManager.with(this).fetch
+ fetch.enqueue(requestList) {
+ Log.i("Downloading latest update")
+ }
+
+ fetchListener = getFetchListener()
+ fetch.addListener(fetchListener)
+ }
+
+ private val isFDroidVariant: Boolean
+ get() = isFDroidApp(this, BuildConfig.APPLICATION_ID)
+
+ @get:RequiresApi(Build.VERSION_CODES.O)
+ private val notification: Notification
+ get() {
+ val notificationBuilder =
+ NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_GENERAL)
+ return getNotification(notificationBuilder)
+ }
+
+ private fun getNotification(builder: NotificationCompat.Builder): Notification {
+ return builder.setAutoCancel(true)
+ .setColor(ContextCompat.getColor(this, R.color.colorAccent))
+ .setContentTitle("Self update")
+ .setContentText("Updating Aurora Store in background")
+ .setOngoing(true)
+ .setSmallIcon(R.drawable.ic_notification_outlined)
+ .build()
+ }
+
+ private fun getFetchListener(): FetchListener {
+ return object : AbstractFetchGroupListener() {
+ override fun onError(
+ groupId: Int, download: Download, error: Error,
+ throwable: Throwable?, fetchGroup: FetchGroup
+ ) {
+ if (groupId == app.id) {
+ Log.e("Error self-updating ${app.displayName}")
+ destroyService()
+ }
+ }
+
+ override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
+ if (groupId == app.id && fetchGroup.groupDownloadProgress == 100) {
+ Log.d("Calling installer ${app.displayName}")
+ AppInstaller(this@SelfUpdateService).getPreferredInstaller().install(
+ BuildConfig.APPLICATION_ID,
+ listOf(download.file)
+ )
+
+ task {
+ TimeUnit.SECONDS.sleep(10)
+ } success {
+ destroyService()
+ }
+ }
+ }
+
+ override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
+ if (groupId == app.id) {
+ Log.d("Self-update cancelled ${app.displayName}")
+ destroyService()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1c5f344e5..619ae2f92 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -238,6 +238,11 @@
"Please, disable MIUI optimizations to allow installations, otherwise you can choose Root or Services installer."
"Optionally you can choose Native installer, but then you can not install bundled (split) APKs, so choice is yours."
+
+ "New update available"
+ "Do you wish to update now ?"
+ "update available"
+
"Quick notification"
"General notification"
"Click to install"