mirror of
https://github.com/viq/NewsBlur.git
synced 2025-09-18 21:43:31 +00:00
Subscription sync service running every 24 hours to sync if necessary the user's premium subscription state between NewsBlur and Play Store.
This commit is contained in:
parent
09492328ce
commit
bfbaf20c79
5 changed files with 95 additions and 20 deletions
|
@ -147,6 +147,10 @@
|
|||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
<service android:name=".widget.WidgetRemoteViewsService"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
<service
|
||||
android:name=".service.SubscriptionSyncService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver android:name=".service.BootReceiver"
|
||||
android:exported="true">
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
package com.newsblur
|
||||
|
||||
import android.app.Application
|
||||
import android.app.job.JobScheduler
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.newsblur.service.SubscriptionSyncService
|
||||
import com.newsblur.util.Log
|
||||
|
||||
class NbApplication : Application(), DefaultLifecycleObserver {
|
||||
|
||||
override fun onCreate() {
|
||||
super<Application>.onCreate()
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
scheduleSubscriptionSync()
|
||||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
|
@ -22,6 +27,15 @@ class NbApplication : Application(), DefaultLifecycleObserver {
|
|||
isAppForeground = false
|
||||
}
|
||||
|
||||
private fun scheduleSubscriptionSync() {
|
||||
val jobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
|
||||
val scheduledSubscriptionJob = jobScheduler.allPendingJobs.find { it.id == SubscriptionSyncService.JOB_ID }
|
||||
if (scheduledSubscriptionJob == null) {
|
||||
val result: Int = jobScheduler.schedule(SubscriptionSyncService.createJobInfo(this))
|
||||
Log.d(this, "Scheduled subscription result: ${if (result == JobScheduler.RESULT_FAILURE) "failed" else "completed"}")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package com.newsblur.service
|
||||
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobParameters
|
||||
import android.app.job.JobService
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import com.newsblur.subscription.SubscriptionManagerImpl
|
||||
import com.newsblur.util.AppConstants
|
||||
import com.newsblur.util.Log
|
||||
import com.newsblur.util.NBScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Service to sync user subscription with NewsBlur backend.
|
||||
*
|
||||
* Mostly interested in handling the state where there is an active
|
||||
* subscription in Play Store but NewsBlur doesn't know about it.
|
||||
* This could occur when the user has renewed the subscription
|
||||
* via Play Store.
|
||||
*/
|
||||
class SubscriptionSyncService : JobService() {
|
||||
|
||||
override fun onStartJob(params: JobParameters?): Boolean {
|
||||
Log.d(this, "onStartJob")
|
||||
NBScope.launch(Dispatchers.Default) {
|
||||
val subscriptionManager = SubscriptionManagerImpl(this@SubscriptionSyncService, this)
|
||||
val job = subscriptionManager.syncActiveSubscription()
|
||||
job.invokeOnCompletion {
|
||||
Log.d(this, "sync active subscription completed.")
|
||||
// manually trigger jobFinished after work is done
|
||||
jobFinished(params, false)
|
||||
}
|
||||
}
|
||||
|
||||
return true // returning true due to background thread work
|
||||
}
|
||||
|
||||
override fun onStopJob(params: JobParameters?): Boolean = false
|
||||
|
||||
companion object {
|
||||
const val JOB_ID = 2021
|
||||
|
||||
fun createJobInfo(context: Context): JobInfo = JobInfo.Builder(JOB_ID,
|
||||
ComponentName(context, SubscriptionSyncService::class.java))
|
||||
.apply {
|
||||
// sync every 24 hours
|
||||
setPeriodic(AppConstants.BG_SUBSCRIPTION_SYNC_CYCLE_MILLIS)
|
||||
setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
setBackoffCriteria(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
|
||||
setPersisted(true)
|
||||
}.build()
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import kotlinx.coroutines.CompletableDeferred
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.DateFormat
|
||||
|
@ -57,7 +58,7 @@ interface SubscriptionManager {
|
|||
/**
|
||||
* Sync subscription state between NewsBlur and Play Store.
|
||||
*/
|
||||
fun syncActiveSubscription()
|
||||
fun syncActiveSubscription(): Job
|
||||
|
||||
/**
|
||||
* Notify backend of active Play Store subscription.
|
||||
|
@ -159,7 +160,7 @@ class SubscriptionManagerImpl(
|
|||
}
|
||||
|
||||
override fun syncSubscriptionState() {
|
||||
scope.launch {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
if (hasActiveSubscription()) syncActiveSubscription()
|
||||
else syncAvailableSubscription()
|
||||
}
|
||||
|
@ -173,21 +174,21 @@ class SubscriptionManagerImpl(
|
|||
billingClient.launchBillingFlow(activity, billingFlowParams)
|
||||
}
|
||||
|
||||
override fun syncActiveSubscription() {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val hasNewsBlurSubscription = PrefsUtils.getIsPremium(context)
|
||||
val activePlayStoreSubscription = getActiveSubscriptionAsync().await()
|
||||
override fun syncActiveSubscription() = scope.launch(Dispatchers.Default) {
|
||||
val hasNewsBlurSubscription = PrefsUtils.getIsPremium(context)
|
||||
val activePlayStoreSubscription = getActiveSubscriptionAsync().await()
|
||||
|
||||
if (hasNewsBlurSubscription || activePlayStoreSubscription != null) {
|
||||
if (hasNewsBlurSubscription || activePlayStoreSubscription != null) {
|
||||
listener?.let {
|
||||
val renewalString: String? = getRenewalMessage(activePlayStoreSubscription)
|
||||
withContext(Dispatchers.Main) {
|
||||
listener?.onActiveSubscription(renewalString)
|
||||
it.onActiveSubscription(renewalString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasNewsBlurSubscription && activePlayStoreSubscription != null) {
|
||||
saveReceipt(activePlayStoreSubscription)
|
||||
}
|
||||
if (!hasNewsBlurSubscription && activePlayStoreSubscription != null) {
|
||||
saveReceipt(activePlayStoreSubscription)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,15 +211,13 @@ class SubscriptionManagerImpl(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun syncAvailableSubscription() {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val skuDetails = getAvailableSubscriptionAsync().await()
|
||||
withContext(Dispatchers.Main) {
|
||||
skuDetails?.let {
|
||||
Log.d(this, it.toString())
|
||||
listener?.onAvailableSubscription(it)
|
||||
} ?: listener?.onBillingConnectionError()
|
||||
}
|
||||
private suspend fun syncAvailableSubscription() = scope.launch(Dispatchers.Default) {
|
||||
val skuDetails = getAvailableSubscriptionAsync().await()
|
||||
withContext(Dispatchers.Main) {
|
||||
skuDetails?.let {
|
||||
Log.d(this, it.toString())
|
||||
listener?.onAvailableSubscription(it)
|
||||
} ?: listener?.onBillingConnectionError()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,9 @@ public class AppConstants {
|
|||
// to account for the fact that it is approximate, and missing a cycle is bad.
|
||||
public static final long BG_SERVICE_CYCLE_MILLIS = AUTO_SYNC_TIME_MILLIS + 30L * 1000L;
|
||||
|
||||
// how often to trigger the job scheduler to sync subscription state.
|
||||
public static final long BG_SUBSCRIPTION_SYNC_CYCLE_MILLIS = 24L * 60 * 60 * 1000L;
|
||||
|
||||
// how many total attemtps to make at a single API call
|
||||
public static final int MAX_API_TRIES = 3;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue