Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions app/src/main/java/to/bitkit/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.os.PowerManager
import androidx.core.content.ContextCompat
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import coil3.ImageLoader
import coil3.SingletonImageLoader
import dagger.hilt.android.HiltAndroidApp
import to.bitkit.appwidget.AppWidgetRefreshReceiver
import to.bitkit.env.Env
import to.bitkit.services.BluetoothInit
import javax.inject.Inject
Expand All @@ -31,11 +36,25 @@ internal open class App : Application(), Configuration.Provider {
super.onCreate()
SingletonImageLoader.setSafe { imageLoader }
currentActivity = CurrentActivity().also { registerActivityLifecycleCallbacks(it) }
registerAppWidgetRefreshReceiver()
Env.initAppStoragePath(filesDir.absolutePath)
// Initialize btleplug for Bluetooth support (required before any BLE usage)
BluetoothInit.ensureInitialized()
}

private fun registerAppWidgetRefreshReceiver() {
val filter = IntentFilter().apply {
addAction(Intent.ACTION_USER_PRESENT)
addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
}
ContextCompat.registerReceiver(
this,
AppWidgetRefreshReceiver(),
filter,
ContextCompat.RECEIVER_NOT_EXPORTED,
)
}

companion object {
@SuppressLint("StaticFieldLeak") // Should be safe given its manual memory management
internal var currentActivity: CurrentActivity? = null
Expand Down
28 changes: 28 additions & 0 deletions app/src/main/java/to/bitkit/appwidget/AppWidgetRefreshReceiver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package to.bitkit.appwidget

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.PowerManager
import to.bitkit.ext.powerManager
import to.bitkit.utils.Logger

class AppWidgetRefreshReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
Intent.ACTION_USER_PRESENT -> enqueueCatchUp(context, "user_present")
PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED -> {
if (!context.powerManager.isDeviceIdleMode) enqueueCatchUp(context, "device_idle_exit")
}
}
}

private fun enqueueCatchUp(context: Context, reason: String) {
Logger.debug("Enqueued widget refresh for '$reason'", context = TAG)
AppWidgetRefreshWorker.enqueueCatchUp(context)
}

private companion object {
const val TAG = "AppWidgetRefreshReceiver"
}
}
33 changes: 29 additions & 4 deletions app/src/main/java/to/bitkit/appwidget/AppWidgetRefreshWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import androidx.hilt.work.HiltWorker
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
Expand Down Expand Up @@ -41,6 +43,7 @@ class AppWidgetRefreshWorker @AssistedInject constructor(
companion object {
private const val TAG = "AppWidgetRefreshWorker"
private const val WORK_NAME = "appwidget_refresh"
private const val CATCH_UP_WORK_NAME = "appwidget_refresh_catch_up"

fun enqueue(context: Context) {
val constraints = Constraints.Builder()
Expand All @@ -58,14 +61,36 @@ class AppWidgetRefreshWorker @AssistedInject constructor(
)
}

fun enqueueCatchUp(context: Context) {
if (!hasActiveWidgets(context)) return

val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()

val request = OneTimeWorkRequestBuilder<AppWidgetRefreshWorker>()
.setConstraints(constraints)
.build()

WorkManager.getInstance(context).enqueueUniqueWork(
CATCH_UP_WORK_NAME,
ExistingWorkPolicy.KEEP,
request,
)
}

fun cancelIfNoWidgets(context: Context) {
if (!hasActiveWidgets(context)) {
WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME)
WorkManager.getInstance(context).cancelUniqueWork(CATCH_UP_WORK_NAME)
}
}

private fun hasActiveWidgets(context: Context): Boolean {
val manager = AppWidgetManager.getInstance(context)
val hasAny = AppWidgetType.entries.any { type ->
return AppWidgetType.entries.any { type ->
manager.getAppWidgetIds(ComponentName(context, receiverClassFor(type))).isNotEmpty()
}
if (!hasAny) {
WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME)
}
}

private fun receiverClassFor(type: AppWidgetType): Class<out GlanceAppWidgetReceiver> = when (type) {
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/to/bitkit/ext/Context.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.hardware.usb.UsbManager
import android.os.PowerManager
import android.provider.Settings
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
Expand Down Expand Up @@ -39,6 +40,9 @@ val Context.usbManager: UsbManager
val Context.bluetoothManager: BluetoothManager
get() = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager

val Context.powerManager: PowerManager
get() = getSystemService(Context.POWER_SERVICE) as PowerManager

// Permissions

fun Context.requiresPermission(permission: String): Boolean =
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/to/bitkit/ui/ContentView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import to.bitkit.appwidget.AppWidgetRefreshWorker
import to.bitkit.env.Env
import to.bitkit.models.NodeLifecycleState
import to.bitkit.models.Toast
Expand Down Expand Up @@ -257,6 +258,7 @@ fun ContentView(

appViewModel.consumePaymentReceivedInBackground()

AppWidgetRefreshWorker.enqueueCatchUp(context)
currencyViewModel.triggerRefresh()
blocktankViewModel.refreshOrders()
appViewModel.refreshPublicPaykitEndpoints()
Expand Down
1 change: 1 addition & 0 deletions changelog.d/next/os-widget-idle.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Android home-screen widgets now refresh shortly after unlocking the device so stale data catches up after idle periods.
Loading