diff --git a/app/build.gradle b/app/build.gradle index 6db0af9e4..588a961fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -181,6 +181,9 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + + //WorkManager + implementation "androidx.work:work-runtime-ktx:2.8.1" } Properties props = new Properties() diff --git a/app/src/main/java/com/aurora/Constants.kt b/app/src/main/java/com/aurora/Constants.kt index d0e04a808..ab0e4f89f 100644 --- a/app/src/main/java/com/aurora/Constants.kt +++ b/app/src/main/java/com/aurora/Constants.kt @@ -37,6 +37,7 @@ object Constants { const val NOTIFICATION_CHANNEL_ALERT = "NOTIFICATION_CHANNEL_ALERT" const val NOTIFICATION_CHANNEL_GENERAL = "NOTIFICATION_CHANNEL_GENERAL" + const val NOTIFICATION_CHANNEL_UPDATES = "NOTIFICATION_CHANNEL_UPDATES" const val NOTIFICATION_CHANNEL_UPDATER_SERVICE = "NOTIFICATION_CHANNEL_UPDATER_SERVICE" const val URL_DISPENSER = "https://auroraoss.com/api/auth" @@ -53,4 +54,7 @@ object Constants { const val PAGE_TYPE = "PAGE_TYPE" const val TOP_CHART_TYPE = "TOP_CHART_TYPE" const val TOP_CHART_CATEGORY = "TOP_CHART_CATEGORY" + + //NAVIGATION + const val NAVIGATION_UPDATES = "NAVIGATION_UPDATES" } diff --git a/app/src/main/java/com/aurora/store/AuroraApplication.kt b/app/src/main/java/com/aurora/store/AuroraApplication.kt index 89bf57e69..4b9d170ad 100644 --- a/app/src/main/java/com/aurora/store/AuroraApplication.kt +++ b/app/src/main/java/com/aurora/store/AuroraApplication.kt @@ -113,6 +113,13 @@ class AuroraApplication : MultiDexApplication() { NotificationManager.IMPORTANCE_MIN ) ) + channels.add( + NotificationChannel( + Constants.NOTIFICATION_CHANNEL_UPDATES, + getString(R.string.notification_channel_updates), + NotificationManager.IMPORTANCE_DEFAULT + ) + ) notificationManager.createNotificationChannels(channels) } } diff --git a/app/src/main/java/com/aurora/store/MainActivity.kt b/app/src/main/java/com/aurora/store/MainActivity.kt index 3d02cb1b9..28aea8e6c 100644 --- a/app/src/main/java/com/aurora/store/MainActivity.kt +++ b/app/src/main/java/com/aurora/store/MainActivity.kt @@ -165,6 +165,12 @@ class MainActivity : BaseActivity() { B.drawerLayout.close() } } + + // Handle intents + when (intent?.action) { + Constants.NAVIGATION_UPDATES -> B.navView.selectedItemId = R.id.navigation_updates + else -> Log.i("Unhandled intent action: ${intent.action}") + } } private fun attachToolbar() { diff --git a/app/src/main/java/com/aurora/store/data/work/UpdateWorker.kt b/app/src/main/java/com/aurora/store/data/work/UpdateWorker.kt new file mode 100644 index 000000000..681598659 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/work/UpdateWorker.kt @@ -0,0 +1,239 @@ +package com.aurora.store.data.work + +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE +import androidx.core.content.pm.PackageInfoCompat +import androidx.work.Constraints +import androidx.work.CoroutineWorker +import androidx.work.ExistingPeriodicWorkPolicy.KEEP +import androidx.work.ForegroundInfo +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkerParameters +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.AppDetailsHelper +import com.aurora.gplayapi.helpers.AuthValidator +import com.aurora.store.MainActivity +import com.aurora.store.R +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.data.providers.BlacklistProvider +import com.aurora.store.util.Log +import com.aurora.store.util.PackageUtil +import com.aurora.store.util.Preferences +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.util.concurrent.TimeUnit.HOURS +import java.util.concurrent.TimeUnit.MINUTES + +class UpdateWorker(private val appContext: Context, workerParams: WorkerParameters) : + CoroutineWorker(appContext, workerParams) { + + companion object { + private const val WORK_NAME_CHECK = "WORK_NAME_CHECK" + + fun cancelAutomatedCheck(context: Context) { + Log.i("Cancelling periodic app updates check!") + WorkManager.getInstance(context) + .cancelUniqueWork(WORK_NAME_CHECK) + } + + fun scheduleAutomatedCheck(context: Context) { + Log.i("Scheduling periodic app updates check!") + + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiresDeviceIdle(true) + .setRequiresBatteryNotLow(true) + .build() + val workRequest = PeriodicWorkRequestBuilder( + repeatInterval = 3, + repeatIntervalTimeUnit = HOURS, + flexTimeInterval = 30, + flexTimeIntervalUnit = MINUTES + ).setConstraints(constraints).build() + + val workManager = WorkManager.getInstance(context) + workManager.enqueueUniquePeriodicWork(WORK_NAME_CHECK, KEEP, workRequest) + } + } + + private val notificationID = 100 + private val workerID = 101 + private val gapps: MutableSet = hashSetOf( + "com.chrome.beta", + "com.chrome.canary", + "com.chrome.dev", + "com.android.chrome", + "com.niksoftware.snapseed", + "com.google.toontastic" + ) + + private val authData = AuthProvider.with(appContext) + .getAuthData() + private val blackList = BlacklistProvider.with(appContext) + .getBlackList() + + override suspend fun getForegroundInfo(): ForegroundInfo { + return ForegroundInfo(workerID, getOngoingNotification()) + } + + override suspend fun doWork(): Result { + withContext(Dispatchers.IO) { + + if (!isValid(authData)) { + Log.i("AuthData is not valid, retrying later!") + return@withContext Result.retry() + } + + Log.i("Checking for app updates") + + val appDetailsHelper = AppDetailsHelper(authData) + .using(HttpClient.getPreferredClient()) + + val isGoogleFilterEnabled = Preferences.getBoolean( + appContext, + Preferences.PREFERENCE_FILTER_GOOGLE + ) + val packageInfoMap = PackageUtil.getPackageInfoMap(appContext) + + packageInfoMap.keys.let { packages -> + /*Filter black list*/ + var filtersPackages = packages.filter { !blackList.contains(it) } + + /*Filter google apps*/ + if (isGoogleFilterEnabled) { + filtersPackages = filtersPackages + .filter { !it.startsWith("com.google") } + .filter { !gapps.contains(it) } + } + + val appList = appDetailsHelper.getAppByPackageName(filtersPackages) + .filter { it.displayName.isNotEmpty() } + + val updatesList = appList.filter { + val packageInfo = packageInfoMap[it.packageName] + if (packageInfo != null) { + it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo) + } else { + false + } + } + + if (updatesList.isNotEmpty()) { + Log.i("Found updates, notifying!") + val notifyManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) + as NotificationManager + notifyManager.notify(notificationID, getUpdateNotification(updatesList)) + return@withContext Result.success() + } + + Log.i("No updates found!") + return@withContext Result.success() + } + } + return Result.failure() + } + + private fun isValid(authData: AuthData): Boolean { + return try { + AuthValidator(authData) + .using(HttpClient.getPreferredClient()) + .isValid() + } catch (e: Exception) { + false + } + } + + private fun getUpdateNotification(updatesList: List): Notification { + val contentIntent = PendingIntent.getActivity( + appContext, + 0, + Intent(appContext, MainActivity::class.java).apply { + action = Constants.NAVIGATION_UPDATES + }, + PendingIntent.FLAG_IMMUTABLE + ) + + return NotificationCompat.Builder(appContext, Constants.NOTIFICATION_CHANNEL_UPDATES) + .setSmallIcon(R.drawable.ic_logo) + .setContentTitle( + appContext.getString( + R.string.notification_updates_available, + updatesList.size + ) + ) + .setContentText( + when (updatesList.size) { + 1 -> { + appContext.getString( + R.string.notification_updates_available_desc_1, + updatesList[0].displayName + ) + } + + 2 -> { + appContext.getString( + R.string.notification_updates_available_desc_2, + updatesList[0].displayName, + updatesList[1].displayName + ) + } + + 3 -> { + appContext.getString( + R.string.notification_updates_available_desc_3, + updatesList[0].displayName, + updatesList[1].displayName, + updatesList[2].displayName + ) + } + + else -> { + appContext.getString( + R.string.notification_updates_available_desc_4, + updatesList[0].displayName, + updatesList[1].displayName, + updatesList[2].displayName, + updatesList.size - 3 + ) + } + } + ) + .setContentIntent(contentIntent) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setCategory(NotificationCompat.CATEGORY_RECOMMENDATION) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setAutoCancel(true) + .build() + } + + private fun getOngoingNotification(): Notification { + val contentIntent = PendingIntent.getActivity( + appContext, + 0, + Intent(appContext, MainActivity::class.java), + PendingIntent.FLAG_IMMUTABLE + ) + + return NotificationCompat.Builder(appContext, Constants.NOTIFICATION_CHANNEL_UPDATES) + .setSmallIcon(R.drawable.ic_logo) + .setContentTitle(appContext.getString(R.string.checking_for_updates)) + .setContentIntent(contentIntent) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setForegroundServiceBehavior(FOREGROUND_SERVICE_IMMEDIATE) + .setProgress(100, 0, true) + .setOngoing(true) + .build() + } +} diff --git a/app/src/main/java/com/aurora/store/util/Preferences.kt b/app/src/main/java/com/aurora/store/util/Preferences.kt index 5a0cb7308..cbf018176 100644 --- a/app/src/main/java/com/aurora/store/util/Preferences.kt +++ b/app/src/main/java/com/aurora/store/util/Preferences.kt @@ -58,6 +58,7 @@ object Preferences { const val PREFERENCE_INSECURE_ANONYMOUS = "PREFERENCE_INSECURE_ANONYMOUS" const val PREFERENCE_UPDATES_EXTENDED = "PREFERENCE_UPDATES_EXTENDED" + const val PREFERENCE_UPDATES_CHECK = "PREFERENCE_UPDATES_CHECK" const val PREFERENCE_ADVANCED_SEARCH_IN_CTT = "PREFERENCE_ADVANCED_SEARCH_IN_CTT" diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt index aca60e6e3..06ca56850 100644 --- a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt @@ -30,6 +30,7 @@ import com.aurora.Constants import com.aurora.extensions.isSAndAbove import com.aurora.extensions.open import com.aurora.store.R +import com.aurora.store.data.work.UpdateWorker import com.aurora.store.databinding.ActivityOnboardingBinding import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences.PREFERENCE_AUTO_DELETE @@ -48,6 +49,7 @@ import com.aurora.store.util.Preferences.PREFERENCE_QUICK_EXIT import com.aurora.store.util.Preferences.PREFERENCE_SIMILAR import com.aurora.store.util.Preferences.PREFERENCE_THEME_ACCENT import com.aurora.store.util.Preferences.PREFERENCE_THEME_TYPE +import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_EXTENDED import com.aurora.store.util.save import com.aurora.store.view.ui.commons.BaseActivity @@ -152,6 +154,7 @@ class OnboardingActivity : BaseActivity() { B.btnForward.isEnabled = true B.btnForward.setOnClickListener { save(PREFERENCE_INTRO, true) + UpdateWorker.scheduleAutomatedCheck(this) open(SplashActivity::class.java, true) } } else { @@ -191,6 +194,7 @@ class OnboardingActivity : BaseActivity() { /*Updates*/ save(PREFERENCE_UPDATES_EXTENDED, false) + save(PREFERENCE_UPDATES_CHECK, true) } internal class PagerAdapter(fragmentActivity: FragmentActivity) : diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/UpdatesPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/UpdatesPreference.kt index 1fb0d135c..148e95b83 100644 --- a/app/src/main/java/com/aurora/store/view/ui/preferences/UpdatesPreference.kt +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/UpdatesPreference.kt @@ -20,16 +20,25 @@ package com.aurora.store.view.ui.preferences import android.os.Bundle -import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat -import com.aurora.extensions.runOnUiThread -import com.aurora.extensions.toast +import androidx.preference.SwitchPreferenceCompat import com.aurora.store.R -import com.aurora.store.util.CommonUtil -import com.aurora.store.util.Preferences +import com.aurora.store.data.work.UpdateWorker +import com.aurora.store.util.Log +import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK class UpdatesPreference : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences_updates, rootKey) + + findPreference(PREFERENCE_UPDATES_CHECK) + ?.setOnPreferenceChangeListener { _, newValue -> + if (newValue.toString().toBoolean()) { + UpdateWorker.scheduleAutomatedCheck(requireContext()) + } else { + UpdateWorker.cancelAutomatedCheck(requireContext()) + } + true + } } } \ 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 4782e8067..959f80997 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -355,4 +355,13 @@ Requesting new session Server down for maintenance Downloading additional files for %1$s + Updates notifications + %1$d updates available + A new version of %1$s is available + %1$s and %2$s + %1$s, %2$s and %3$s + %1$s, %2$s, %3$s and %4$d more + Checking for updates + Automated updates check + Automatically check & notify for new app updates periodically diff --git a/app/src/main/res/xml/preferences_updates.xml b/app/src/main/res/xml/preferences_updates.xml index 7fa4ade2f..d579a7fde 100644 --- a/app/src/main/res/xml/preferences_updates.xml +++ b/app/src/main/res/xml/preferences_updates.xml @@ -27,4 +27,11 @@ app:summary="@string/pref_updates_extended_desc" app:title="@string/pref_updates_extended" /> + +