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"