1package com.peachpasswords.updates23import android.app.NotificationChannel4import android.app.NotificationManager5import android.app.PendingIntent6import android.content.Context7import android.content.Intent8import android.os.Build9import androidx.core.app.NotificationCompat10import androidx.core.app.NotificationManagerCompat11import androidx.hilt.work.HiltWorker12import androidx.work.BackoffPolicy13import androidx.work.Constraints14import androidx.work.CoroutineWorker15import androidx.work.ExistingPeriodicWorkPolicy16import androidx.work.ExistingWorkPolicy17import androidx.work.NetworkType18import androidx.work.OneTimeWorkRequestBuilder19import androidx.work.PeriodicWorkRequestBuilder20import androidx.work.WorkManager21import androidx.work.WorkerParameters22import com.peachpasswords.MainActivity23import com.peachpasswords.R24import dagger.assisted.Assisted25import dagger.assisted.AssistedInject26import java.util.concurrent.TimeUnit272829const val ACTION_OPEN_UPDATE = "com.peachpasswords.OPEN_UPDATE"30const val EXTRA_OPEN_UPDATE = "peach_open_update"31const val EXTRA_OPEN_UPDATE_TOKEN = "peach_open_update_token"3233private const val UPDATE_INTENT_PREFS = "peach_update_intent"34private const val UPDATE_INTENT_TOKEN = "token"3536fun issueUpdateOpenToken(context: Context): String {37 val token = java.util.UUID.randomUUID().toString()38 check(39 context.getSharedPreferences(UPDATE_INTENT_PREFS, Context.MODE_PRIVATE)40 .edit().putString(UPDATE_INTENT_TOKEN, token).commit(),41 ) { "update_intent_token_persist_failed" }42 return token43}4445fun consumeUpdateOpenToken(context: Context, token: String?): Boolean {46 val prefs = context.getSharedPreferences(UPDATE_INTENT_PREFS, Context.MODE_PRIVATE)47 if (token == null || token != prefs.getString(UPDATE_INTENT_TOKEN, null)) return false48 return prefs.edit().remove(UPDATE_INTENT_TOKEN).commit()49}5051525354555657@HiltWorker58class UpdateCheckWorker @AssistedInject constructor(59 @Assisted appContext: Context,60 @Assisted params: WorkerParameters,61 private val updates: UpdateRepository,62) : CoroutineWorker(appContext, params) {6364 override suspend fun doWork(): Result {65 val outcome = try {66 updates.check()67 } catch (e: Exception) {68 69 70 return Result.retry()71 }72 val manifest = (outcome as? UpdateCheckResult.Accepted)?.manifest ?: return Result.success()73 if (updates.shouldNotifyFor(manifest.version) &&74 postUpdateNotification(applicationContext, manifest.version)75 ) {76 updates.markNotified(manifest.version)77 }78 return Result.success()79 }8081 companion object {82 private const val PERIODIC_WORK = "peach_update_check"83 private const val ONE_SHOT_WORK = "peach_update_check_now"8485 private val NETWORK_ONLY = Constraints.Builder()86 .setRequiredNetworkType(NetworkType.CONNECTED)87 .build()8889 90 fun schedulePeriodic(context: Context) {91 WorkManager.getInstance(context).enqueueUniquePeriodicWork(92 PERIODIC_WORK,93 ExistingPeriodicWorkPolicy.KEEP,94 PeriodicWorkRequestBuilder<UpdateCheckWorker>(1, TimeUnit.DAYS)95 .setConstraints(NETWORK_ONLY)96 .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.MINUTES)97 .build(),98 )99 }100101 102 fun checkNow(context: Context) {103 WorkManager.getInstance(context).enqueueUniqueWork(104 ONE_SHOT_WORK,105 ExistingWorkPolicy.REPLACE,106 OneTimeWorkRequestBuilder<UpdateCheckWorker>()107 .setConstraints(NETWORK_ONLY)108 .build(),109 )110 }111 }112}113114private const val CHANNEL_ID = "peach_updates"115private const val NOTIFICATION_ID = 4301116117118119120121122123fun postUpdateNotification(context: Context, version: String): Boolean {124 val manager = NotificationManagerCompat.from(context)125 if (!manager.areNotificationsEnabled()) return false126127 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {128 val system = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager129 system.createNotificationChannel(130 NotificationChannel(CHANNEL_ID, "Peach updates", NotificationManager.IMPORTANCE_LOW).apply {131 description = "Tells you when a newer version of Peach is available."132 setShowBadge(false)133 },134 )135 }136137 val open = PendingIntent.getActivity(138 context,139 0,140 Intent(context, MainActivity::class.java)141 .setAction(ACTION_OPEN_UPDATE)142 .putExtra(EXTRA_OPEN_UPDATE, true)143 .putExtra(EXTRA_OPEN_UPDATE_TOKEN, issueUpdateOpenToken(context))144 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP),145 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,146 )147148 val notification = NotificationCompat.Builder(context, CHANNEL_ID)149 .setContentTitle("Peach update available — v$version")150 .setContentText("Tap to see what changed and install it.")151 .setSmallIcon(R.drawable.ic_launcher_monochrome)152 .setPriority(NotificationCompat.PRIORITY_LOW)153 .setAutoCancel(true)154 .setContentIntent(open)155 .build()156157 return try {158 manager.notify(NOTIFICATION_ID, notification)159 true160 } catch (e: SecurityException) {161 162 163 false164 }165}