From b2ae8c3e0c5cfa67d871b861b5205582d7995824 Mon Sep 17 00:00:00 2001 From: Egor Date: Thu, 12 Feb 2026 03:47:20 -0800 Subject: [PATCH] migrate to distributor v0.7.x --- app/build.gradle.kts | 2 + app/src/main/AndroidManifest.xml | 21 ++- .../main/java/app/lonecloud/prism/AppStore.kt | 4 +- .../app/lonecloud/prism/DatabaseFactory.kt | 2 +- .../java/app/lonecloud/prism/Distributor.kt | 9 +- .../main/java/app/lonecloud/prism/EventBus.kt | 27 ---- .../java/app/lonecloud/prism/PrismConfig.kt | 18 +++ .../lonecloud/prism/activities/AppAction.kt | 106 --------------- .../prism/activities/ApplicationRowState.kt | 25 ---- .../activities/DistribMigrationViewModel.kt | 72 ---------- .../prism/activities/MainActivity.kt | 30 +---- .../prism/activities/MainViewModel.kt | 32 +++-- .../activities/RegistrationsViewModel.kt | 50 ------- .../prism/activities/SettingsViewModel.kt | 42 ++++-- .../prism/activities/ThemeViewModel.kt | 13 +- .../lonecloud/prism/activities/UiAction.kt | 25 ---- .../prism/activities/ViewModelFactory.kt | 50 ++++--- .../prism/activities/ui/AppScreen.kt | 13 +- .../prism/activities/ui/MainScreen.kt | 41 +++--- .../prism/activities/ui/PrismServerConfig.kt | 10 +- .../prism/activities/ui/SettingsScreen.kt | 8 +- .../lonecloud/prism/api/ApiUrlCandidate.kt | 3 - .../app/lonecloud/prism/api/MessageSender.kt | 1 - .../lonecloud/prism/api/ServerConnection.kt | 8 +- .../prism/callback/BatteryCallbackFactory.kt | 4 +- .../prism/callback/NetworkCallbackFactory.kt | 4 +- .../prism/receivers/PrismConfigReceiver.kt | 31 +++++ .../receivers/RegisterBroadcastReceiver.kt | 10 +- .../app/lonecloud/prism/services/FgService.kt | 4 +- .../prism/services/MainRegistrationCounter.kt | 8 +- .../prism/services/MigrationManager.kt | 2 +- .../prism/services/PrismInternalService.kt | 125 ++++++++++++++++++ .../lonecloud/prism/services/RestartWorker.kt | 2 +- .../lonecloud/prism/services/SourceManager.kt | 4 +- .../lonecloud/prism/utils/Notifications.kt | 25 ++-- .../res/drawable/baseline_content_copy_24.xml | 5 + app/src/main/res/drawable/ic_android_24dp.xml | 5 + app/src/main/res/values/strings.xml | 84 +++++++----- gradle/libs.versions.toml | 9 +- 39 files changed, 439 insertions(+), 495 deletions(-) delete mode 100644 app/src/main/java/app/lonecloud/prism/EventBus.kt create mode 100644 app/src/main/java/app/lonecloud/prism/PrismConfig.kt delete mode 100644 app/src/main/java/app/lonecloud/prism/activities/AppAction.kt delete mode 100644 app/src/main/java/app/lonecloud/prism/activities/ApplicationRowState.kt delete mode 100644 app/src/main/java/app/lonecloud/prism/activities/DistribMigrationViewModel.kt delete mode 100644 app/src/main/java/app/lonecloud/prism/activities/RegistrationsViewModel.kt delete mode 100644 app/src/main/java/app/lonecloud/prism/activities/UiAction.kt create mode 100644 app/src/main/java/app/lonecloud/prism/receivers/PrismConfigReceiver.kt create mode 100644 app/src/main/java/app/lonecloud/prism/services/PrismInternalService.kt create mode 100644 app/src/main/res/drawable/baseline_content_copy_24.xml create mode 100644 app/src/main/res/drawable/ic_android_24dp.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d89597d..46bbd8b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -97,7 +97,9 @@ android { dependencies { implementation(libs.unifiedpush.distributor) + implementation(libs.unifiedpush.distributor.base) implementation(libs.unifiedpush.distributor.ui) + implementation(libs.accompanist.permissions) implementation(libs.tink.android) implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.viewmodel.compose) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 08120d7..8183153 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ android:networkSecurityConfig="@xml/network_security_config"> @@ -33,7 +34,16 @@ android:enabled="true" android:foregroundServiceType="specialUse"> + android:value="This service needs to be constantly connected to the server via WebSocket to receive push notifications."/> + + + + + + + + + + + + = MutableSharedFlow() - val events = mutEvents.asSharedFlow() - - suspend inline fun publish(event: T) { - if (mutEvents.subscriptionCount.value > 0) { - mutEvents.emit(event) - } - } - - suspend inline fun subscribe(crossinline onEvent: (T) -> Unit) { - events.filterIsInstance() - .collectLatest { event -> - coroutineContext.ensureActive() - onEvent(event) - } - } -} diff --git a/app/src/main/java/app/lonecloud/prism/PrismConfig.kt b/app/src/main/java/app/lonecloud/prism/PrismConfig.kt new file mode 100644 index 0000000..3b840a4 --- /dev/null +++ b/app/src/main/java/app/lonecloud/prism/PrismConfig.kt @@ -0,0 +1,18 @@ +package app.lonecloud.prism + +import org.unifiedpush.android.distributor.ui.AppConfig +import org.unifiedpush.android.distributor.ui.InAppNotifsConfig +import org.unifiedpush.android.distributor.ui.MigrationConfig +import org.unifiedpush.android.distributor.ui.NoLoginConfig +import org.unifiedpush.android.distributor.ui.PrivacyPolicy + +object PrismConfig : AppConfig { + override val appName = R.string.app_name + override val restartableService = true + override val privacyPolicy: PrivacyPolicy? = null + override val loginConfig = NoLoginConfig(canChangeUrl = true) + override val migrationConfig = object : MigrationConfig { + override val supportTempFallback = true + } + override val inAppNotifsConfig: InAppNotifsConfig? = null +} diff --git a/app/src/main/java/app/lonecloud/prism/activities/AppAction.kt b/app/src/main/java/app/lonecloud/prism/activities/AppAction.kt deleted file mode 100644 index c201618..0000000 --- a/app/src/main/java/app/lonecloud/prism/activities/AppAction.kt +++ /dev/null @@ -1,106 +0,0 @@ -package app.lonecloud.prism.activities - -import android.content.Context -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import app.lonecloud.prism.AppStore -import app.lonecloud.prism.DatabaseFactory -import app.lonecloud.prism.Distributor -import app.lonecloud.prism.EventBus -import app.lonecloud.prism.PrismServerClient -import app.lonecloud.prism.services.FgService -import app.lonecloud.prism.services.MigrationManager -import app.lonecloud.prism.services.RestartWorker -import app.lonecloud.prism.services.SourceManager -import app.lonecloud.prism.utils.TAG -import kotlinx.coroutines.launch - -class AppAction(private val action: Action) { - sealed class Action { - data object RestartService : Action() - class ShowToasts(val enable: Boolean) : Action() - class DeleteRegistration(val registrations: List) : Action() - data object FallbackIntroShown : Action() - class FallbackDistribSelected(val distributor: String?) : Action() - class MigrateToDistrib(val distributor: String) : Action() - data object ReactivateUnifiedPush : Action() - data object RegisterPrismServer : Action() - } - - fun handle(context: Context) { - when (action) { - is Action.RestartService -> restartService(context) - is Action.ShowToasts -> showToasts(context, action) - is Action.DeleteRegistration -> deleteRegistration(context, action) - is Action.FallbackIntroShown -> fallbackIntroShown(context) - is Action.FallbackDistribSelected -> fallbackDistribSelected(context, action) - is Action.MigrateToDistrib -> migrateToDistrib(context, action) - is Action.ReactivateUnifiedPush -> reactivateUnifiedPush(context) - is Action.RegisterPrismServer -> registerPrismServer(context) - } - } - - private fun restartService(context: Context) { - Log.d(TAG, "Restarting the Listener") - SourceManager.clearFails() - FgService.stopService { - RestartWorker.run(context, delay = 0) - } - } - - private fun showToasts(context: Context, action: Action.ShowToasts) { - AppStore(context).showToasts = action.enable - } - - private fun deleteRegistration(context: Context, action: Action.DeleteRegistration) { - action.registrations.forEach { token -> - val db = DatabaseFactory.getDb(context) - val dbApp = db.listApps().find { it.connectorToken == token } - - if (dbApp?.description?.startsWith("target:") == true) { - val appName = dbApp.title ?: dbApp.packageName - PrismServerClient.deleteApp(context, appName) - } - - Distributor.deleteApp(context, token) - } - } - - private fun fallbackIntroShown(context: Context) { - MigrationManager().setFallbackIntroShown(context) - } - - /** - * Save fallback service - * - * If fallback is disabled and we have already send TEMP_UNAVAILABLE: - * we send the endpoint again - */ - private fun fallbackDistribSelected(context: Context, action: Action.FallbackDistribSelected) { - MigrationManager() - .selectFallbackService( - context, - action.distributor, - SourceManager.shouldSendFallback - ) - } - - private fun migrateToDistrib(context: Context, action: Action.MigrateToDistrib) { - MigrationManager().migrate(context, action.distributor) - } - - private fun reactivateUnifiedPush(context: Context) { - MigrationManager().reactivate(context) - } - - private fun registerPrismServer(context: Context) { - app.lonecloud.prism.PrismServerClient.registerAllApps(context) - } -} - -fun ViewModel.publishAction(action: AppAction) { - viewModelScope.launch { - EventBus.publish(action) - } -} diff --git a/app/src/main/java/app/lonecloud/prism/activities/ApplicationRowState.kt b/app/src/main/java/app/lonecloud/prism/activities/ApplicationRowState.kt deleted file mode 100644 index d7cd7dd..0000000 --- a/app/src/main/java/app/lonecloud/prism/activities/ApplicationRowState.kt +++ /dev/null @@ -1,25 +0,0 @@ -package app.lonecloud.prism.activities - -import android.content.Context -import org.unifiedpush.android.distributor.ui.compose.state.ApplicationRowState -import org.unifiedpush.distributor.utils.appInfoForMetadata -import org.unifiedpush.distributor.utils.getApplicationIcon -import org.unifiedpush.distributor.utils.getApplicationName - -fun Context.applicationRowState(packageName: String, description: String? = null): ApplicationRowState { - val ai = appInfoForMetadata(packageName) - val title = ai?.let { getApplicationName(it) } ?: packageName - val icon = getApplicationIcon(packageName) - val description = if (title == packageName) { - description ?: "" - } else { - description?.let { "$it — $packageName" } - ?: packageName - } - return ApplicationRowState( - icon = icon, - title = title, - packageName = packageName, - description = description - ) -} diff --git a/app/src/main/java/app/lonecloud/prism/activities/DistribMigrationViewModel.kt b/app/src/main/java/app/lonecloud/prism/activities/DistribMigrationViewModel.kt deleted file mode 100644 index a5b86fc..0000000 --- a/app/src/main/java/app/lonecloud/prism/activities/DistribMigrationViewModel.kt +++ /dev/null @@ -1,72 +0,0 @@ -package app.lonecloud.prism.activities - -import android.app.Application -import android.content.Context -import app.lonecloud.prism.AppStore -import org.unifiedpush.android.distributor.ui.compose.DistribMigrationViewModel as UPDistribMigrationViewModel -import org.unifiedpush.android.distributor.ui.compose.state.DistribMigrationState -import org.unifiedpush.distributor.utils.listOtherDistributors - -class DistribMigrationViewModel(state: DistribMigrationState, val application: Application? = null) : UPDistribMigrationViewModel(state) { - constructor(application: Application) : this( - stateFrom(application), - application - ) - - override fun onFallbackDistribSelected(distributor: String?) { - publishAction( - AppAction(AppAction.Action.FallbackDistribSelected(distributor)) - ) - } - - override fun onMigrationDistributorSelected(distributor: String) { - publishAction( - AppAction(AppAction.Action.MigrateToDistrib(distributor)) - ) - } - - override fun onFallbackIntroShown() { - publishAction( - AppAction(AppAction.Action.FallbackIntroShown) - ) - } - - override fun onServiceReactivated() { - publishAction( - AppAction(AppAction.Action.ReactivateUnifiedPush) - ) - } - - override fun refreshDistributors() { - application?.let { context -> - refreshDistributors { - val store = AppStore(context) - val fallbackDistrib = store.fallbackService - return@refreshDistributors context.listOtherDistributors() - .map { packageName -> - context.applicationRowState(packageName).copy( - selected = fallbackDistrib == packageName - ) - }.toSet() - } - } - } - - companion object { - fun stateFrom(context: Context): DistribMigrationState { - val store = AppStore(context) - val fallbackDistrib = store.fallbackService - val distributors = context.listOtherDistributors().map { packageName -> - context.applicationRowState(packageName).copy( - selected = fallbackDistrib == packageName - ) - }.toSet() - return DistribMigrationState( - distributors, - store.fallbackIntroShown, - migrated = store.migrated, - featureEnabled = false - ) - } - } -} diff --git a/app/src/main/java/app/lonecloud/prism/activities/MainActivity.kt b/app/src/main/java/app/lonecloud/prism/activities/MainActivity.kt index 36c2415..b85457e 100644 --- a/app/src/main/java/app/lonecloud/prism/activities/MainActivity.kt +++ b/app/src/main/java/app/lonecloud/prism/activities/MainActivity.kt @@ -1,56 +1,36 @@ package app.lonecloud.prism.activities import android.os.Bundle -import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.lifecycle.viewmodel.compose.viewModel -import app.lonecloud.prism.EventBus -import app.lonecloud.prism.activities.ThemeViewModel import app.lonecloud.prism.activities.ui.App import app.lonecloud.prism.activities.ui.theme.AppTheme -import app.lonecloud.prism.services.RestartWorker -import app.lonecloud.prism.utils.TAG -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch +import org.unifiedpush.android.distributor.ipc.InternalMessenger class MainActivity : ComponentActivity() { - private var jobs: MutableList = emptyList().toMutableList() + private lateinit var messenger: InternalMessenger override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - RestartWorker.startPeriodic(this) + + messenger = InternalMessenger(this) enableEdgeToEdge() setContent { - val factory = ViewModelFactory(this.application) + val factory = ViewModelFactory(this.application, messenger) val themeViewModel = viewModel(factory = factory) AppTheme( dynamicColor = themeViewModel.dynamicColors ) { App(factory, themeViewModel) } - subscribeActions() - } - } - - private fun subscribeActions() { - Log.d(TAG, "Subscribing to actions") - jobs += CoroutineScope(Dispatchers.IO).launch { - EventBus.subscribe { it.handle(this@MainActivity) } } } override fun onDestroy() { - Log.d(TAG, "Destroy") - jobs.removeAll { - it.cancel() - true - } super.onDestroy() } } diff --git a/app/src/main/java/app/lonecloud/prism/activities/MainViewModel.kt b/app/src/main/java/app/lonecloud/prism/activities/MainViewModel.kt index b89b8f4..6ced2a8 100644 --- a/app/src/main/java/app/lonecloud/prism/activities/MainViewModel.kt +++ b/app/src/main/java/app/lonecloud/prism/activities/MainViewModel.kt @@ -25,25 +25,31 @@ import app.lonecloud.prism.utils.VapidKeyGenerator import app.lonecloud.prism.utils.WebPushEncryptionKeys import java.util.UUID import kotlinx.coroutines.launch -import org.unifiedpush.android.distributor.ui.compose.BatteryOptimisationViewModel -import org.unifiedpush.android.distributor.ui.compose.RegistrationsViewModel -import org.unifiedpush.android.distributor.ui.compose.state.RegistrationListState +import org.unifiedpush.android.distributor.data.App +import org.unifiedpush.android.distributor.ipc.InternalMessenger +import org.unifiedpush.android.distributor.ipc.InternalOpcode +import org.unifiedpush.android.distributor.ui.state.RegistrationListState +import org.unifiedpush.android.distributor.ui.vm.BatteryOptimisationViewModel +import org.unifiedpush.android.distributor.ui.vm.RegistrationsViewModel class MainViewModel( mainUiState: MainUiState, val batteryOptimisationViewModel: BatteryOptimisationViewModel, val registrationsViewModel: RegistrationsViewModel, + val messenger: InternalMessenger?, val application: Application? = null ) : ViewModel() { - constructor(application: Application) : this( + constructor(requireBatteryOpt: Boolean, messenger: InternalMessenger?, application: Application) : this( mainUiState = MainUiState( prismServerConfigured = !AppStore(application).prismServerUrl.isNullOrBlank() && !AppStore(application).prismApiKey.isNullOrBlank() ), - batteryOptimisationViewModel = BatteryOptimisationViewModel(application), + batteryOptimisationViewModel = BatteryOptimisationViewModel(requireBatteryOpt, messenger), registrationsViewModel = RegistrationsViewModel( - getRegistrationListState(application) + RegistrationListState(emptyList()), + messenger ), + messenger, application ) @@ -69,9 +75,8 @@ class MainViewModel( fun refreshRegistrations() { viewModelScope.launch { - application?.let { - registrationsViewModel.state = getRegistrationListState(it) - } + val apps = messenger?.sendIMessageL(InternalOpcode.REG_LIST, "apps", App::class.java) + registrationsViewModel.state = RegistrationListState(apps ?: emptyList()) } } @@ -79,9 +84,7 @@ class MainViewModel( viewModelScope.launch { val state = registrationsViewModel.state val tokenList = state.list.filter { it.selected }.map { it.token } - publishAction( - AppAction(AppAction.Action.DeleteRegistration(tokenList)) - ) + messenger?.sendIMessage(InternalOpcode.REG_DELETE, "regs" to tokenList) registrationsViewModel.state = RegistrationListState( list = state.list.filter { !it.selected @@ -130,7 +133,9 @@ class MainViewModel( } fun restartService() { - publishAction(AppAction(AppAction.Action.RestartService)) + viewModelScope.launch { + messenger?.sendIMessage(InternalOpcode.WORKER_RESTART, 0) + } } private fun hasUnifiedPushSupport(pm: PackageManager, packageName: String): Boolean { @@ -238,7 +243,6 @@ class MainViewModel( } } - // Timeout Log.e(TAG, "Endpoint timeout after 30 seconds for token: $connectorToken") } } diff --git a/app/src/main/java/app/lonecloud/prism/activities/RegistrationsViewModel.kt b/app/src/main/java/app/lonecloud/prism/activities/RegistrationsViewModel.kt deleted file mode 100644 index 8066bc7..0000000 --- a/app/src/main/java/app/lonecloud/prism/activities/RegistrationsViewModel.kt +++ /dev/null @@ -1,50 +0,0 @@ -package app.lonecloud.prism.activities - -import android.content.Context -import app.lonecloud.prism.DatabaseFactory -import org.unifiedpush.android.distributor.ui.compose.state.RegistrationListState -import org.unifiedpush.android.distributor.ui.compose.state.RegistrationState -import org.unifiedpush.distributor.Database - -fun getRegistrationListState(context: Context): RegistrationListState = RegistrationListState( - list = DatabaseFactory.getDb(context).listApps().map { app -> - getRegistrationState(context, app) - } -) - -fun getRegistrationState(context: Context, app: Database.App): RegistrationState { - // Parse manual app format: "target:package.name|optional_description" - val isManualApp = app.description?.startsWith("target:") == true - val targetPackage = app.description?.takeIf { isManualApp } - ?.substringAfter("target:")?.substringBefore("|") - ?.takeIf { it.isNotBlank() } - - val cleanDescription = if (isManualApp) { - app.description?.substringAfter("|", "")?.takeIf { it.isNotBlank() } - } else { - app.description - } - - val displayPackage = targetPackage ?: app.packageName - val displayDescription = if (isManualApp && targetPackage == null) null else cleanDescription - - val baseAppState = context.applicationRowState(displayPackage, displayDescription) - - // For manual apps without target, use custom title instead of package-derived one - val displayTitle = if (isManualApp && targetPackage == null) { - app.title ?: baseAppState.title - } else { - baseAppState.title - } - - val finalAppState = baseAppState.copy( - title = displayTitle - ) - - return RegistrationState( - app = finalAppState, - msgCount = app.msgCount, - token = app.connectorToken, - copyable = false - ) -} diff --git a/app/src/main/java/app/lonecloud/prism/activities/SettingsViewModel.kt b/app/src/main/java/app/lonecloud/prism/activities/SettingsViewModel.kt index 82e9391..8b83836 100644 --- a/app/src/main/java/app/lonecloud/prism/activities/SettingsViewModel.kt +++ b/app/src/main/java/app/lonecloud/prism/activities/SettingsViewModel.kt @@ -1,18 +1,29 @@ package app.lonecloud.prism.activities import android.app.Application +import android.content.Intent import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.lonecloud.prism.AppStore +import app.lonecloud.prism.PrismServerClient import app.lonecloud.prism.activities.ui.SettingsState +import app.lonecloud.prism.receivers.PrismConfigReceiver import kotlinx.coroutines.launch +import org.unifiedpush.android.distributor.ipc.InternalMessenger +import org.unifiedpush.android.distributor.ipc.InternalOpcode +import org.unifiedpush.android.distributor.ipc.sendUiAction -class SettingsViewModel(state: SettingsState, val application: Application? = null) : ViewModel() { - constructor(application: Application) : this( +class SettingsViewModel( + state: SettingsState, + val messenger: InternalMessenger?, + val application: Application? = null +) : ViewModel() { + constructor(messenger: InternalMessenger?, application: Application) : this( SettingsState.from(application), + messenger, application ) @@ -22,7 +33,8 @@ class SettingsViewModel(state: SettingsState, val application: Application? = nu fun toggleShowToasts() { viewModelScope.launch { state = state.copy(showToasts = !state.showToasts) - publishAction(AppAction(AppAction.Action.ShowToasts(state.showToasts))) + application?.let { AppStore(it).showToasts = state.showToasts } + messenger?.sendIMessage(InternalOpcode.SHOW_TOASTS_SET, if (state.showToasts) 1 else 0) } } @@ -33,11 +45,17 @@ class SettingsViewModel(state: SettingsState, val application: Application? = nu application?.let { AppStore(it).prismServerUrl = trimmedUrl.ifBlank { null } + val intent = Intent(PrismConfigReceiver.ACTION_SET_PRISM_SERVER_URL).apply { + putExtra(PrismConfigReceiver.EXTRA_URL, trimmedUrl) + setPackage(it.packageName) + } + it.sendBroadcast(intent) + if (trimmedUrl.isNotBlank() && state.prismApiKey.isNotBlank()) { - publishAction(AppAction(AppAction.Action.RegisterPrismServer)) + PrismServerClient.registerAllApps(it) } - UiAction.publish(UiAction.Action.UpdatePrismServerConfigured) + sendUiAction(it, "UpdatePrismServerConfigured") } } } @@ -49,16 +67,24 @@ class SettingsViewModel(state: SettingsState, val application: Application? = nu application?.let { AppStore(it).prismApiKey = trimmedKey.ifBlank { null } + val intent = Intent(PrismConfigReceiver.ACTION_SET_PRISM_API_KEY).apply { + putExtra(PrismConfigReceiver.EXTRA_API_KEY, trimmedKey) + setPackage(it.packageName) + } + it.sendBroadcast(intent) + if (state.prismServerUrl.isNotBlank() && trimmedKey.isNotBlank()) { - publishAction(AppAction(AppAction.Action.RegisterPrismServer)) + PrismServerClient.registerAllApps(it) } - UiAction.publish(UiAction.Action.UpdatePrismServerConfigured) + sendUiAction(it, "UpdatePrismServerConfigured") } } } fun restartService() { - publishAction(AppAction(AppAction.Action.RestartService)) + viewModelScope.launch { + messenger?.sendIMessage(InternalOpcode.WORKER_RESTART, 0) + } } } diff --git a/app/src/main/java/app/lonecloud/prism/activities/ThemeViewModel.kt b/app/src/main/java/app/lonecloud/prism/activities/ThemeViewModel.kt index b03b601..0664218 100644 --- a/app/src/main/java/app/lonecloud/prism/activities/ThemeViewModel.kt +++ b/app/src/main/java/app/lonecloud/prism/activities/ThemeViewModel.kt @@ -8,18 +8,17 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.lonecloud.prism.AppStore import kotlinx.coroutines.launch +import org.unifiedpush.android.distributor.ipc.InternalMessenger +import org.unifiedpush.android.distributor.ipc.InternalOpcode -class ThemeViewModel(val application: Application? = null) : ViewModel() { - var dynamicColors by mutableStateOf( - application?.let { AppStore(it).dynamicColors } ?: false - ) +class ThemeViewModel(val messenger: InternalMessenger?, val application: Application?) : ViewModel() { + var dynamicColors by mutableStateOf(application?.let { AppStore(it).dynamicColors } ?: false) fun toggleDynamicColors() { viewModelScope.launch { dynamicColors = !dynamicColors - application?.run { - AppStore(this).dynamicColors = dynamicColors - } + application?.let { AppStore(it).dynamicColors = dynamicColors } + messenger?.sendIMessage(InternalOpcode.THEME_DYN_SET, if (dynamicColors) 1 else 0) } } } diff --git a/app/src/main/java/app/lonecloud/prism/activities/UiAction.kt b/app/src/main/java/app/lonecloud/prism/activities/UiAction.kt deleted file mode 100644 index a182134..0000000 --- a/app/src/main/java/app/lonecloud/prism/activities/UiAction.kt +++ /dev/null @@ -1,25 +0,0 @@ -package app.lonecloud.prism.activities - -import app.lonecloud.prism.EventBus -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - -class UiAction(val action: Action) { - enum class Action { - RefreshRegistrations, - UpdatePrismServerConfigured - } - - fun handle(action: (Action) -> Unit) { - action(this.action) - } - - companion object { - fun publish(type: Action) { - CoroutineScope(Dispatchers.IO).launch { - EventBus.publish(UiAction(type)) - } - } - } -} diff --git a/app/src/main/java/app/lonecloud/prism/activities/ViewModelFactory.kt b/app/src/main/java/app/lonecloud/prism/activities/ViewModelFactory.kt index 178c447..ad5f6a4 100644 --- a/app/src/main/java/app/lonecloud/prism/activities/ViewModelFactory.kt +++ b/app/src/main/java/app/lonecloud/prism/activities/ViewModelFactory.kt @@ -2,24 +2,31 @@ package app.lonecloud.prism.activities import android.app.Application import android.content.Context +import android.os.PowerManager import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import app.lonecloud.prism.activities.ThemeViewModel +import app.lonecloud.prism.PrismConfig import app.lonecloud.prism.activities.ui.MainUiState import app.lonecloud.prism.activities.ui.SettingsState -import org.unifiedpush.android.distributor.ui.compose.BatteryOptimisationViewModel -import org.unifiedpush.android.distributor.ui.compose.previewRegistrationsViewModel -import org.unifiedpush.android.distributor.ui.compose.state.DistribMigrationState +import org.unifiedpush.android.distributor.ipc.InternalMessenger +import org.unifiedpush.android.distributor.ui.state.DistribMigrationState +import org.unifiedpush.android.distributor.ui.vm.DistribMigrationViewModel + +class ViewModelFactory(val application: Application, val messenger: InternalMessenger) : ViewModelProvider.Factory { + private val requireBatteryOptimization = + !(application.getSystemService(Context.POWER_SERVICE) as PowerManager) + .isIgnoringBatteryOptimizations(application.packageName) -class ViewModelFactory(val application: Application) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = when { - modelClass.isAssignableFrom(MainViewModel::class.java) -> MainViewModel(application) - modelClass.isAssignableFrom(SettingsViewModel::class.java) -> SettingsViewModel( - application + modelClass.isAssignableFrom(MainViewModel::class.java) -> MainViewModel(requireBatteryOptimization, messenger, application) + modelClass.isAssignableFrom(SettingsViewModel::class.java) -> SettingsViewModel(messenger, application) + modelClass.isAssignableFrom(ThemeViewModel::class.java) -> ThemeViewModel(messenger, application) + modelClass.isAssignableFrom(DistribMigrationViewModel::class.java) -> DistribMigrationViewModel( + DistribMigrationState(), + PrismConfig, + messenger ) - modelClass.isAssignableFrom(ThemeViewModel::class.java) -> ThemeViewModel(application) - modelClass.isAssignableFrom(DistribMigrationViewModel::class.java) -> DistribMigrationViewModel(application) else -> throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}") } as T } @@ -30,8 +37,15 @@ class PreviewFactory(val context: Context) : ViewModelProvider.Factory { modelClass.isAssignableFrom(MainViewModel::class.java) -> { MainViewModel( MainUiState(), - BatteryOptimisationViewModel(true), - previewRegistrationsViewModel(context) + org.unifiedpush.android.distributor.ui.vm.BatteryOptimisationViewModel(false, null), + org.unifiedpush.android.distributor.ui.vm.RegistrationsViewModel( + org.unifiedpush.android.distributor.ui.state.RegistrationListState( + emptyList() + ), + null + ), + null, + null ) } modelClass.isAssignableFrom(SettingsViewModel::class.java) -> { @@ -40,12 +54,18 @@ class PreviewFactory(val context: Context) : ViewModelProvider.Factory { showToasts = false, prismServerUrl = "", prismApiKey = "" - ) + ), + null, + null ) } - modelClass.isAssignableFrom(ThemeViewModel::class.java) -> ThemeViewModel() + modelClass.isAssignableFrom(ThemeViewModel::class.java) -> ThemeViewModel(null, null) modelClass.isAssignableFrom(DistribMigrationViewModel::class.java) -> { - DistribMigrationViewModel(DistribMigrationState()) + DistribMigrationViewModel( + DistribMigrationState(), + PrismConfig, + null + ) } else -> throw IllegalArgumentException("Unknown ViewModel class") } as T diff --git a/app/src/main/java/app/lonecloud/prism/activities/ui/AppScreen.kt b/app/src/main/java/app/lonecloud/prism/activities/ui/AppScreen.kt index f350d74..8b33cbd 100644 --- a/app/src/main/java/app/lonecloud/prism/activities/ui/AppScreen.kt +++ b/app/src/main/java/app/lonecloud/prism/activities/ui/AppScreen.kt @@ -33,17 +33,17 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import app.lonecloud.prism.R -import app.lonecloud.prism.activities.DistribMigrationViewModel import app.lonecloud.prism.activities.MainViewModel import app.lonecloud.prism.activities.PreviewFactory import app.lonecloud.prism.activities.SettingsViewModel import app.lonecloud.prism.activities.ThemeViewModel -import org.unifiedpush.android.distributor.ui.R as LibR +import org.unifiedpush.android.distributor.ipc.subscribeUiActions import org.unifiedpush.android.distributor.ui.compose.AppBar +import org.unifiedpush.android.distributor.ui.vm.DistribMigrationViewModel enum class AppScreen(@param:StringRes val title: Int) { Main(R.string.app_name), - Settings(LibR.string.settings) + Settings(R.string.settings) } @OptIn(ExperimentalMaterial3Api::class) @@ -84,11 +84,13 @@ fun App( themeViewModel: ThemeViewModel = viewModel(factory = factory), navController: NavHostController = rememberNavController() ) { + val context = LocalContext.current + val uiActionsFlow = subscribeUiActions(context) + val backStackEntry by navController.currentBackStackEntryAsState() val currentScreen = AppScreen.valueOf( backStackEntry?.destination?.route ?: AppScreen.Main.name ) - // shared with all views, no need to scope it val migrationViewModel = viewModel(factory = factory) val mainViewModel = viewModel(factory = factory) @@ -147,7 +149,8 @@ fun App( ) { MainScreen( mainViewModel, - migrationViewModel + migrationViewModel, + uiActionsFlow ) } composable( diff --git a/app/src/main/java/app/lonecloud/prism/activities/ui/MainScreen.kt b/app/src/main/java/app/lonecloud/prism/activities/ui/MainScreen.kt index 5d8efa6..32f678e 100644 --- a/app/src/main/java/app/lonecloud/prism/activities/ui/MainScreen.kt +++ b/app/src/main/java/app/lonecloud/prism/activities/ui/MainScreen.kt @@ -25,13 +25,9 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import app.lonecloud.prism.AppStore -import app.lonecloud.prism.EventBus import app.lonecloud.prism.R -import app.lonecloud.prism.activities.DistribMigrationViewModel import app.lonecloud.prism.activities.MainViewModel import app.lonecloud.prism.activities.PreviewFactory -import app.lonecloud.prism.activities.UiAction -import org.unifiedpush.android.distributor.ui.R as LibR import org.unifiedpush.android.distributor.ui.compose.AppBar import org.unifiedpush.android.distributor.ui.compose.CardDisableBatteryOptimisation import org.unifiedpush.android.distributor.ui.compose.CardDisabledForMigration @@ -40,6 +36,7 @@ import org.unifiedpush.android.distributor.ui.compose.PermissionsUi import org.unifiedpush.android.distributor.ui.compose.RegistrationList import org.unifiedpush.android.distributor.ui.compose.RegistrationListHeading import org.unifiedpush.android.distributor.ui.compose.UnregisterBarUi +import org.unifiedpush.android.distributor.ui.vm.DistribMigrationViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -69,7 +66,7 @@ fun MainAppBar(onGoToSettings: () -> Unit) { ) { Icon( imageVector = Icons.Default.Settings, - contentDescription = stringResource(LibR.string.settings) + contentDescription = stringResource(R.string.settings) ) } } @@ -77,21 +74,23 @@ fun MainAppBar(onGoToSettings: () -> Unit) { } @Composable -fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationViewModel) { +fun MainScreen( + viewModel: MainViewModel, + migrationViewModel: DistribMigrationViewModel, + uiActionsFlow: kotlinx.coroutines.flow.Flow? +) { val lifecycleOwner = LocalLifecycleOwner.current LaunchedEffect(Unit) { - EventBus.subscribe { - it.handle { type -> - when (type) { - UiAction.Action.RefreshRegistrations -> viewModel.refreshRegistrations() - UiAction.Action.UpdatePrismServerConfigured -> { - viewModel.application?.let { app -> - val store = AppStore(app) - viewModel.updatePrismServerConfigured( - !store.prismServerUrl.isNullOrBlank() && - !store.prismApiKey.isNullOrBlank() - ) - } + uiActionsFlow?.collect { action -> + when (action) { + "RefreshRegistrations" -> viewModel.refreshRegistrations() + "UpdatePrismServerConfigured" -> { + viewModel.application?.let { app -> + val store = AppStore(app) + viewModel.updatePrismServerConfigured( + !store.prismServerUrl.isNullOrBlank() && + !store.prismApiKey.isNullOrBlank() + ) } } } @@ -133,7 +132,7 @@ fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationVie ) } - RegistrationList(viewModel.registrationsViewModel) {} + RegistrationList(viewModel.registrationsViewModel) } if (viewModel.mainUiState.showPermissionDialog) { PermissionsUi { @@ -155,7 +154,7 @@ fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationVie } ) } - if (migrationViewModel.state.showMigrations) { + if (migrationViewModel.state.canMigrate) { DistribMigrationUi(migrationViewModel) } } @@ -166,5 +165,5 @@ fun MainPreview() { val factory = PreviewFactory(LocalContext.current) val mainVM = viewModel(factory = factory) val migrationVM = viewModel(factory = factory) - MainScreen(mainVM, migrationVM) + MainScreen(mainVM, migrationVM, null) } diff --git a/app/src/main/java/app/lonecloud/prism/activities/ui/PrismServerConfig.kt b/app/src/main/java/app/lonecloud/prism/activities/ui/PrismServerConfig.kt index 9488e76..ff95320 100644 --- a/app/src/main/java/app/lonecloud/prism/activities/ui/PrismServerConfig.kt +++ b/app/src/main/java/app/lonecloud/prism/activities/ui/PrismServerConfig.kt @@ -29,7 +29,11 @@ import androidx.compose.ui.unit.dp import app.lonecloud.prism.R @Composable -fun PrismServerConfigButton(currentUrl: String, onConfigure: (url: String, apiKey: String) -> Unit) { +fun PrismServerConfigButton( + currentUrl: String, + currentApiKey: String, + onConfigure: (url: String, apiKey: String) -> Unit +) { var showDialog by remember { mutableStateOf(false) } Surface( @@ -61,6 +65,7 @@ fun PrismServerConfigButton(currentUrl: String, onConfigure: (url: String, apiKe if (showDialog) { PrismServerConfigDialog( initialUrl = currentUrl, + initialApiKey = currentApiKey, onDismiss = { showDialog = false }, onSave = { url, apiKey -> onConfigure(url, apiKey) @@ -73,11 +78,12 @@ fun PrismServerConfigButton(currentUrl: String, onConfigure: (url: String, apiKe @Composable fun PrismServerConfigDialog( initialUrl: String, + initialApiKey: String, onDismiss: () -> Unit, onSave: (url: String, apiKey: String) -> Unit ) { var url by remember { mutableStateOf(initialUrl) } - var apiKey by remember { mutableStateOf("") } + var apiKey by remember { mutableStateOf(initialApiKey) } var isTesting by remember { mutableStateOf(false) } var testResult by remember { mutableStateOf(null) } var showServerChangeWarning by remember { mutableStateOf(false) } diff --git a/app/src/main/java/app/lonecloud/prism/activities/ui/SettingsScreen.kt b/app/src/main/java/app/lonecloud/prism/activities/ui/SettingsScreen.kt index bb58925..a1a3cb7 100644 --- a/app/src/main/java/app/lonecloud/prism/activities/ui/SettingsScreen.kt +++ b/app/src/main/java/app/lonecloud/prism/activities/ui/SettingsScreen.kt @@ -14,12 +14,11 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import app.lonecloud.prism.R -import app.lonecloud.prism.activities.DistribMigrationViewModel import app.lonecloud.prism.activities.PreviewFactory import app.lonecloud.prism.activities.SettingsViewModel import app.lonecloud.prism.activities.ThemeViewModel import org.unifiedpush.android.distributor.ui.compose.DistribMigrationUi -import org.unifiedpush.android.distributor.ui.compose.MigrationPreferences +import org.unifiedpush.android.distributor.ui.vm.DistribMigrationViewModel @Composable fun SettingsScreen( @@ -39,6 +38,7 @@ fun SettingsScreen( ) { PrismServerConfigButton( currentUrl = viewModel.state.prismServerUrl, + currentApiKey = viewModel.state.prismApiKey, onConfigure = { url, apiKey -> viewModel.updatePrismServerUrl(url) viewModel.updatePrismApiKey(apiKey) @@ -57,13 +57,11 @@ fun SettingsScreen( onCheckedChange = { themeViewModel.toggleDynamicColors() } ) - MigrationPreferences(migrationViewModel) - RestartServicesPreference { viewModel.restartService() } } - if (migrationViewModel.state.showMigrations) { + if (migrationViewModel.state.canMigrate) { DistribMigrationUi(migrationViewModel) } } diff --git a/app/src/main/java/app/lonecloud/prism/api/ApiUrlCandidate.kt b/app/src/main/java/app/lonecloud/prism/api/ApiUrlCandidate.kt index ad8f6f2..13d30d5 100644 --- a/app/src/main/java/app/lonecloud/prism/api/ApiUrlCandidate.kt +++ b/app/src/main/java/app/lonecloud/prism/api/ApiUrlCandidate.kt @@ -34,7 +34,6 @@ sealed class ApiUrlCandidate { instance.set(New(url)) DatabaseFactory.getDb(context).run { if (countApps() == 0) { - // registerApp update the counter which restart the service registerApp( context.packageName, FAKE_TOKEN, @@ -44,9 +43,7 @@ sealed class ApiUrlCandidate { null ) } else { - // Else SourceManager.setFailOnce() - // We restart in 1sec if it hasn't been replaced until then, as setFailOnce should call RestartWorker.run RestartWorker.run(context, delay = 1_000) } } diff --git a/app/src/main/java/app/lonecloud/prism/api/MessageSender.kt b/app/src/main/java/app/lonecloud/prism/api/MessageSender.kt index 49226e4..5233a8b 100644 --- a/app/src/main/java/app/lonecloud/prism/api/MessageSender.kt +++ b/app/src/main/java/app/lonecloud/prism/api/MessageSender.kt @@ -19,7 +19,6 @@ object MessageSender { fun send(context: Context, message: ClientMessage) { synchronized(this) { websocket?.let { - // Log.d(TAG, "Sending: ${message.serialize()}") Log.d(TAG, "Sending: ${message::class.java.simpleName}") message.send(it) } ?: run { diff --git a/app/src/main/java/app/lonecloud/prism/api/ServerConnection.kt b/app/src/main/java/app/lonecloud/prism/api/ServerConnection.kt index d773e38..d27f0d2 100644 --- a/app/src/main/java/app/lonecloud/prism/api/ServerConnection.kt +++ b/app/src/main/java/app/lonecloud/prism/api/ServerConnection.kt @@ -11,6 +11,7 @@ import app.lonecloud.prism.DatabaseFactory import app.lonecloud.prism.Distributor import app.lonecloud.prism.Distributor.sendMessage import app.lonecloud.prism.EncryptionKeyStore +import app.lonecloud.prism.R import app.lonecloud.prism.api.data.ClientMessage import app.lonecloud.prism.api.data.ServerMessage import app.lonecloud.prism.callback.NetworkCallbackFactory @@ -27,8 +28,7 @@ import okhttp3.Request import okhttp3.Response import okhttp3.WebSocket import okhttp3.WebSocketListener -import org.unifiedpush.android.distributor.ui.R as LibR -import org.unifiedpush.distributor.ChannelCreationStatus +import org.unifiedpush.android.distributor.ChannelCreationStatus class ServerConnection(private val context: Context, private val releaseLock: () -> Unit) : WebSocketListener() { @@ -90,7 +90,7 @@ class ServerConnection(private val context: Context, private val releaseLock: () Handler(Looper.getMainLooper()).post { Toast.makeText( context, - context.getString(LibR.string.toast_url_candidate_success, it), + context.getString(R.string.toast_url_candidate_success, it), Toast.LENGTH_SHORT ).show() } @@ -239,7 +239,7 @@ class ServerConnection(private val context: Context, private val releaseLock: () Handler(Looper.getMainLooper()).post { Toast.makeText( context, - context.getString(LibR.string.toast_url_candidate_fail, url), + context.getString(R.string.toast_url_candidate_fail, url), Toast.LENGTH_SHORT ).show() } diff --git a/app/src/main/java/app/lonecloud/prism/callback/BatteryCallbackFactory.kt b/app/src/main/java/app/lonecloud/prism/callback/BatteryCallbackFactory.kt index ea810f3..81f54b6 100644 --- a/app/src/main/java/app/lonecloud/prism/callback/BatteryCallbackFactory.kt +++ b/app/src/main/java/app/lonecloud/prism/callback/BatteryCallbackFactory.kt @@ -1,8 +1,8 @@ package app.lonecloud.prism.callback import android.content.Context -import org.unifiedpush.distributor.callback.BatteryCallback -import org.unifiedpush.distributor.callback.CallbackFactory +import org.unifiedpush.android.distributor.callback.BatteryCallback +import org.unifiedpush.android.distributor.callback.CallbackFactory /** * Battery callback - disabled since URGENCY feature is not supported by the Mozilla server diff --git a/app/src/main/java/app/lonecloud/prism/callback/NetworkCallbackFactory.kt b/app/src/main/java/app/lonecloud/prism/callback/NetworkCallbackFactory.kt index 4f5946f..479d41c 100644 --- a/app/src/main/java/app/lonecloud/prism/callback/NetworkCallbackFactory.kt +++ b/app/src/main/java/app/lonecloud/prism/callback/NetworkCallbackFactory.kt @@ -4,8 +4,8 @@ import android.content.Context import app.lonecloud.prism.services.MainRegistrationCounter import app.lonecloud.prism.services.RestartWorker import app.lonecloud.prism.services.SourceManager -import org.unifiedpush.distributor.callback.CallbackFactory -import org.unifiedpush.distributor.callback.NetworkCallback +import org.unifiedpush.android.distributor.callback.CallbackFactory +import org.unifiedpush.android.distributor.callback.NetworkCallback object NetworkCallbackFactory : CallbackFactory() { class MainNetworkCallback(val context: Context) : NetworkCallback() { diff --git a/app/src/main/java/app/lonecloud/prism/receivers/PrismConfigReceiver.kt b/app/src/main/java/app/lonecloud/prism/receivers/PrismConfigReceiver.kt new file mode 100644 index 0000000..640125b --- /dev/null +++ b/app/src/main/java/app/lonecloud/prism/receivers/PrismConfigReceiver.kt @@ -0,0 +1,31 @@ +package app.lonecloud.prism.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import app.lonecloud.prism.AppStore + +class PrismConfigReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + val store = AppStore(context) + + when (intent.action) { + ACTION_SET_PRISM_SERVER_URL -> { + val url = intent.getStringExtra(EXTRA_URL) ?: "" + store.prismServerUrl = url + } + ACTION_SET_PRISM_API_KEY -> { + val apiKey = intent.getStringExtra(EXTRA_API_KEY) ?: "" + store.prismApiKey = apiKey + } + } + } + + companion object { + const val ACTION_SET_PRISM_SERVER_URL = "app.lonecloud.prism.SET_PRISM_SERVER_URL" + const val ACTION_SET_PRISM_API_KEY = "app.lonecloud.prism.SET_PRISM_API_KEY" + const val EXTRA_URL = "url" + const val EXTRA_API_KEY = "api_key" + } +} diff --git a/app/src/main/java/app/lonecloud/prism/receivers/RegisterBroadcastReceiver.kt b/app/src/main/java/app/lonecloud/prism/receivers/RegisterBroadcastReceiver.kt index cae8ff2..2684188 100644 --- a/app/src/main/java/app/lonecloud/prism/receivers/RegisterBroadcastReceiver.kt +++ b/app/src/main/java/app/lonecloud/prism/receivers/RegisterBroadcastReceiver.kt @@ -6,19 +6,13 @@ import android.content.Context import app.lonecloud.prism.AppStore import app.lonecloud.prism.Distributor import app.lonecloud.prism.callback.NetworkCallbackFactory -import org.unifiedpush.distributor.receiver.DistributorReceiver +import org.unifiedpush.android.distributor.receiver.DistributorReceiver -/** - * THIS SERVICE IS USED BY OTHER APPS TO REGISTER - */ class RegisterBroadcastReceiver : DistributorReceiver() { override val distributor = Distributor - override fun isConnected(context: Context): Boolean { - // We don't have to care about login - return true - } + override fun isConnected(context: Context): Boolean = true override fun hasInternet(context: Context): Boolean = NetworkCallbackFactory.hasInternet() diff --git a/app/src/main/java/app/lonecloud/prism/services/FgService.kt b/app/src/main/java/app/lonecloud/prism/services/FgService.kt index 3043a91..d9db48f 100644 --- a/app/src/main/java/app/lonecloud/prism/services/FgService.kt +++ b/app/src/main/java/app/lonecloud/prism/services/FgService.kt @@ -10,8 +10,8 @@ import app.lonecloud.prism.utils.ForegroundNotification import app.lonecloud.prism.utils.NOTIFICATION_ID_FOREGROUND import app.lonecloud.prism.utils.TAG import java.util.concurrent.atomic.AtomicReference -import org.unifiedpush.distributor.service.ForegroundService -import org.unifiedpush.distributor.service.ForegroundServiceFactory +import org.unifiedpush.android.distributor.service.ForegroundService +import org.unifiedpush.android.distributor.service.ForegroundServiceFactory class FgService : ForegroundService() { diff --git a/app/src/main/java/app/lonecloud/prism/services/MainRegistrationCounter.kt b/app/src/main/java/app/lonecloud/prism/services/MainRegistrationCounter.kt index 904944c..e1b76bd 100644 --- a/app/src/main/java/app/lonecloud/prism/services/MainRegistrationCounter.kt +++ b/app/src/main/java/app/lonecloud/prism/services/MainRegistrationCounter.kt @@ -2,10 +2,10 @@ package app.lonecloud.prism.services import android.content.Context import app.lonecloud.prism.DatabaseFactory -import app.lonecloud.prism.activities.UiAction import app.lonecloud.prism.utils.ForegroundNotification -import org.unifiedpush.distributor.Database -import org.unifiedpush.distributor.RegistrationCounter +import org.unifiedpush.android.distributor.Database +import org.unifiedpush.android.distributor.RegistrationCounter +import org.unifiedpush.android.distributor.ipc.sendUiAction object MainRegistrationCounter : RegistrationCounter() { @@ -15,7 +15,7 @@ object MainRegistrationCounter : RegistrationCounter() { override fun onCountRefreshed(context: Context) { ForegroundNotification(context).update() - UiAction.publish(UiAction.Action.RefreshRegistrations) + sendUiAction(context, "RefreshRegistrations") } override fun getDb(context: Context): Database = DatabaseFactory.getDb(context) diff --git a/app/src/main/java/app/lonecloud/prism/services/MigrationManager.kt b/app/src/main/java/app/lonecloud/prism/services/MigrationManager.kt index 93f3e8b..199af35 100644 --- a/app/src/main/java/app/lonecloud/prism/services/MigrationManager.kt +++ b/app/src/main/java/app/lonecloud/prism/services/MigrationManager.kt @@ -3,7 +3,7 @@ package app.lonecloud.prism.services import android.content.Context import app.lonecloud.prism.AppStore import app.lonecloud.prism.Distributor -import org.unifiedpush.distributor.MigrationManager as MManager +import org.unifiedpush.android.distributor.MigrationManager as MManager class MigrationManager : MManager() { override val distrib = Distributor diff --git a/app/src/main/java/app/lonecloud/prism/services/PrismInternalService.kt b/app/src/main/java/app/lonecloud/prism/services/PrismInternalService.kt new file mode 100644 index 0000000..b494609 --- /dev/null +++ b/app/src/main/java/app/lonecloud/prism/services/PrismInternalService.kt @@ -0,0 +1,125 @@ +package app.lonecloud.prism.services + +import android.content.pm.PackageManager +import android.os.Bundle +import androidx.core.graphics.drawable.toBitmap +import app.lonecloud.prism.AppStore +import app.lonecloud.prism.DatabaseFactory +import app.lonecloud.prism.Distributor +import app.lonecloud.prism.PrismServerClient +import org.unifiedpush.android.distributor.Database +import org.unifiedpush.android.distributor.MigrationManager +import org.unifiedpush.android.distributor.SourceManager +import org.unifiedpush.android.distributor.UnifiedPushDistributor +import org.unifiedpush.android.distributor.WorkerCompanion +import org.unifiedpush.android.distributor.data.App +import org.unifiedpush.android.distributor.data.Description +import org.unifiedpush.android.distributor.ipc.handler.IAccount +import org.unifiedpush.android.distributor.ipc.handler.IApi +import org.unifiedpush.android.distributor.ipc.handler.IRegistrations +import org.unifiedpush.android.distributor.service.ForegroundServiceFactory +import org.unifiedpush.android.distributor.service.InternalService +import org.unifiedpush.android.distributor.utils.getApplicationIcon + +class PrismInternalService : InternalService() { + override val sourceManager: SourceManager<*> = SourceManager + override val restartWorker: WorkerCompanion = RestartWorker + override val startService: ForegroundServiceFactory = FgService + override val migrationManager: org.unifiedpush.android.distributor.MigrationManager = app.lonecloud.prism.services.MigrationManager() + override val distributor: UnifiedPushDistributor = Distributor + override val db: Database by lazy { DatabaseFactory.getDb(this) } + + private val appStore by lazy { AppStore(this) } + + override var themeDynamicColors: Boolean + get() = appStore.dynamicColors + set(value) { + appStore.dynamicColors = value + } + + override var showToasts: Boolean + get() = appStore.showToasts + set(value) { + appStore.showToasts = value + } + + override fun getDebugInfo(): String = "Prism Distributor" + + override fun runAppMigration() { + // No app migration needed for Prism currently + } + + override fun account(): IAccount = object : IAccount { + override fun get(): String? = null + override fun logout() {} + override fun login(data: Bundle) {} + } + + override fun api(): IApi = object : IApi { + override fun newPushServer(url: String?) { + // Prism uses fixed Mozilla server, but can be customized + } + override fun getUrl(): String = appStore.apiUrl + } + + override fun registrations() = object : IRegistrations { + override fun delete(registrations: List) { + registrations.forEach { token -> + // Prism's custom logic: delete from Prism server if it's a "target:" app + val dbApp = db.listApps().find { it.connectorToken == token } + if (dbApp?.description?.startsWith("target:") == true) { + val appName = dbApp.title ?: dbApp.packageName + PrismServerClient.deleteApp(context, appName) + } + + distributor.deleteApp(context, token) + } + } + + override fun list(): List = db + .listApps().map { + val pm = context.packageManager + + val isManualApp = it.description?.startsWith("target:") == true + val targetPackage = if (isManualApp) { + it.description?.substringAfter("target:")?.substringBefore("|")?.takeIf { pkg -> pkg.isNotBlank() } + } else { + null + } + + val packageToResolve = (targetPackage ?: it.packageName) ?: "" + val appName = try { + val appInfo = pm.getApplicationInfo(packageToResolve, PackageManager.GET_META_DATA) + pm.getApplicationLabel(appInfo).toString() + } catch (e: PackageManager.NameNotFoundException) { + packageToResolve + } + + val displayTitle = it.title ?: appName + + App( + connectorToken = it.connectorToken, + packageName = it.packageName, + endpoint = it.endpoint, + vapidKey = it.vapidKey, + title = displayTitle, + msgCount = it.msgCount, + description = if (it.packageName == context.packageName) { + Description.LocalChannel + } else { + Description.StringDescription(packageToResolve) + }, + icon = getApplicationIcon(packageToResolve)?.toBitmap(), + isLocal = it.packageName == context.packageName + ) + } + + override fun copyEndpoint(token: String?) { + super@PrismInternalService.registrations().copyEndpoint(token) + } + + override fun addLocal(title: String) { + super@PrismInternalService.registrations().addLocal(title) + } + } +} diff --git a/app/src/main/java/app/lonecloud/prism/services/RestartWorker.kt b/app/src/main/java/app/lonecloud/prism/services/RestartWorker.kt index 4569d4d..c2306f5 100644 --- a/app/src/main/java/app/lonecloud/prism/services/RestartWorker.kt +++ b/app/src/main/java/app/lonecloud/prism/services/RestartWorker.kt @@ -10,7 +10,7 @@ import app.lonecloud.prism.Distributor import app.lonecloud.prism.api.MessageSender import app.lonecloud.prism.callback.NetworkCallbackFactory import app.lonecloud.prism.utils.TAG -import org.unifiedpush.distributor.WorkerCompanion +import org.unifiedpush.android.distributor.WorkerCompanion class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { diff --git a/app/src/main/java/app/lonecloud/prism/services/SourceManager.kt b/app/src/main/java/app/lonecloud/prism/services/SourceManager.kt index 3ae5959..82b1a3f 100644 --- a/app/src/main/java/app/lonecloud/prism/services/SourceManager.kt +++ b/app/src/main/java/app/lonecloud/prism/services/SourceManager.kt @@ -5,8 +5,8 @@ import app.lonecloud.prism.utils.DisconnectedNotification import okhttp3.Request import okhttp3.WebSocket import okio.ByteString -import org.unifiedpush.distributor.AppNotification -import org.unifiedpush.distributor.SourceManager as SManager +import org.unifiedpush.android.distributor.AppNotification +import org.unifiedpush.android.distributor.SourceManager as SManager object SourceManager : SManager() { override val foregroundService = FgService.service diff --git a/app/src/main/java/app/lonecloud/prism/utils/Notifications.kt b/app/src/main/java/app/lonecloud/prism/utils/Notifications.kt index a33f242..f82af3a 100644 --- a/app/src/main/java/app/lonecloud/prism/utils/Notifications.kt +++ b/app/src/main/java/app/lonecloud/prism/utils/Notifications.kt @@ -4,11 +4,9 @@ import android.app.NotificationManager import android.content.Context import androidx.core.app.NotificationCompat import app.lonecloud.prism.R -import app.lonecloud.prism.activities.MainActivity import app.lonecloud.prism.services.MainRegistrationCounter import java.util.concurrent.atomic.AtomicBoolean -import org.unifiedpush.android.distributor.ui.R as LibR -import org.unifiedpush.distributor.AppNotification +import org.unifiedpush.android.distributor.AppNotification const val NOTIFICATION_ID_FOREGROUND = 51115 private const val NOTIFICATION_ID_WARNING = 51215 @@ -25,16 +23,15 @@ class MainNotificationData( text = text, ticker = ticker, priority = priority, - ongoing = ongoing, - activity = MainActivity::class.java + ongoing = ongoing ) private val Context.warningChannelData: AppNotification.ChannelData get() = AppNotification.ChannelData( "Warning", - this.getString(LibR.string.warning), + this.getString(R.string.warning), NotificationManager.IMPORTANCE_HIGH, - this.resources.getString(LibR.string.warning_notif_description).format(this.getString(R.string.app_name)) + this.resources.getString(R.string.warning_notif_description).format(this.getString(R.string.app_name)) ) class DisconnectedNotification(context: Context) : @@ -44,10 +41,10 @@ class DisconnectedNotification(context: Context) : NOTIFICATION_ID_WARNING, MainNotificationData( context.getString(R.string.app_name), - context.getString(LibR.string.warning_notif_content).format( + context.getString(R.string.warning_notif_content).format( context.getString(R.string.app_name) ), - context.getString(LibR.string.warning), + context.getString(R.string.warning), NotificationCompat.PRIORITY_HIGH, true ), @@ -63,20 +60,20 @@ class ForegroundNotification(context: Context) : context.getString(R.string.app_name), if (MainRegistrationCounter.oneOrMore(context)) { MainRegistrationCounter.getCount(context).let { - context.resources.getQuantityString(LibR.plurals.foreground_notif_content_with_reg, it, it) + context.resources.getQuantityString(R.plurals.foreground_notif_content_with_reg, it, it) } } else { - context.getString(LibR.string.foreground_notif_content_no_reg) + context.getString(R.string.foreground_notif_content_no_reg) }, - context.getString(LibR.string.foreground_service), + context.getString(R.string.foreground_service), NotificationCompat.PRIORITY_LOW, true ), ChannelData( "Foreground", - context.getString(LibR.string.foreground_service), + context.getString(R.string.foreground_service), NotificationManager.IMPORTANCE_LOW, - context.getString(LibR.string.foreground_notif_description) + context.getString(R.string.foreground_notif_description) ) ) diff --git a/app/src/main/res/drawable/baseline_content_copy_24.xml b/app/src/main/res/drawable/baseline_content_copy_24.xml new file mode 100644 index 0000000..942aeb9 --- /dev/null +++ b/app/src/main/res/drawable/baseline_content_copy_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_android_24dp.xml b/app/src/main/res/drawable/ic_android_24dp.xml new file mode 100644 index 0000000..20edfcf --- /dev/null +++ b/app/src/main/res/drawable/ic_android_24dp.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b74dcca..e5def83 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,33 +1,23 @@ - Prism Server URL - https://your-prism-server.com - Prism API Key - Your API key - Restart Push Service - Configure Server - Prism Server: %s - Not configured - Debug - Add Custom App - App Name - e.g., notifications - Add - Cancel - Select Target App - Search apps - App name or package - Testing connection… - Connection successful! - Connection failed: %s - Test & Save - Target App - Select an app - Notify about new registrations - Dynamic Colors - Add Manual App - - + + Distrib name + Distrib privacy policy\ + on the Internet. + + + Fail to use %1$s + Successfully using %1$s + Warning + %1$s is disconnected + Warn when %1$s is disconnected or an issue occurred. + Prism Warning + Foreground Service + Notification to run in the foreground + Waiting for registration to connect + Prism + %d selected + Are you sure to delete this registration? %d selected %d selected @@ -36,6 +26,38 @@ Are you sure to delete this registration? Are you sure to delete %d registrations? - Foreground Service - Warning - \ No newline at end of file + + Connected for %d registration + Connected for %d registrations + + + + Prism + Settings + Add Custom App + App Name + Enter app name + Target App (Optional) + Select an app + Add + Cancel + Select Target App + Search + Search for apps + Add manual app + Debug Information + Configure Prism Server + Prism server configured + Prism server not configured + Server URL + https://prism.example.com + API Key + Enter API key + Testing connection… + Connection successful + Connection failed + Test and Save + Restart Service + Notify when apps register + Dynamic Colors + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 136e249..56a1c6f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,8 +4,9 @@ androidx-activityCompose = "1.12.1" androidx-lifecycle = "2.10.0" androidx-work = "2.11.0" appcompat = "1.7.1" -unifiedpush_distributor = "0.5.6" -unifiedpush_distributor_ui = "0.5.5" +unifiedpush_distributor = "0.7.1" +unifiedpush_distributor_base = "0.7.0" +accompanist_permissions = "0.37.3" tink = "1.15.0" kotlin = "2.2.20" kotlinx_serializationJson = "1.9.0" @@ -29,7 +30,9 @@ androidx-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "u androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } unifiedpush-distributor = { module = "org.unifiedpush.android:distributor", version.ref = "unifiedpush_distributor" } -unifiedpush-distributor-ui = { module = "org.unifiedpush.android:distributor-ui", version.ref = "unifiedpush_distributor_ui" } +unifiedpush-distributor-base = { module = "org.unifiedpush.android:distributor-base", version.ref = "unifiedpush_distributor_base" } +unifiedpush-distributor-ui = { module = "org.unifiedpush.android:distributor-ui", version.ref = "unifiedpush_distributor" } +accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist_permissions" } tink-android = { module = "com.google.crypto.tink:tink-android", version.ref = "tink" } kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx_serializationJson" }