diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index dbb897e11c..84592c85ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -468,16 +468,19 @@ private void initializeNetworkSettings() { } } + // MOLLY: this initialize FCM, websocket and UnifiedPush public void initializeFcmCheck() { if (!SignalStore.account().isRegistered()) { return; } PlayServicesUtil.PlayServicesStatus fcmStatus = PlayServicesUtil.getPlayServicesStatus(this); + boolean unifiedPushAvailable = UnifiedPushHelper.isUnifiedPushAvailable(); + boolean forceWebSocket = SignalStore.internalValues().isWebsocketModeForced(); - if (UnifiedPushHelper.isUnifiedPushAvailable() + if (unifiedPushAvailable || forceWebSocket || fcmStatus == PlayServicesUtil.PlayServicesStatus.DISABLED) { - if (!SignalStore.unifiedpush().getAirGaped()) { + if (unifiedPushAvailable && !SignalStore.unifiedpush().getAirGaped()) { ApplicationDependencies.getJobManager().add(new UnifiedPushRefreshJob()); } ApplicationDependencies.getJobManager().cancel(new FcmRefreshJob().getId()); @@ -495,12 +498,10 @@ public void initializeFcmCheck() { SignalStore.account().getFcmTokenLastSetTime() < 0) { Log.i(TAG, "Play Services are newly-available. Updating to use FCM."); SignalStore.account().setFcmEnabled(true); - ApplicationDependencies.getJobManager().cancel(new UnifiedPushRefreshJob().getId()); ApplicationDependencies.getJobManager().startChain(new FcmRefreshJob()) .then(new RefreshAttributesJob()) .enqueue(); } else { - ApplicationDependencies.getJobManager().cancel(new UnifiedPushRefreshJob().getId()); long nextSetTime = SignalStore.account().getFcmTokenLastSetTime() + TimeUnit.HOURS.toMillis(6); if (SignalStore.account().getFcmToken() == null || nextSetTime <= System.currentTimeMillis()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt index 0673b37fae..5a3ea86361 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsViewModel.kt @@ -6,6 +6,7 @@ import android.os.Build import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import im.molly.unifiedpush.jobs.UnifiedPushRefreshJob import im.molly.unifiedpush.util.UnifiedPushHelper import org.signal.core.util.concurrent.SignalExecutors @@ -111,23 +112,24 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer fun setNotificationDeliveryMethod(method: NotificationDeliveryMethod) { SignalStore.settings().notificationDeliveryMethod = method - SignalStore.unifiedpush().enabled = method == NotificationDeliveryMethod.UNIFIEDPUSH SignalStore.internalValues().isWebsocketModeForced = method == NotificationDeliveryMethod.WEBSOCKET val context = ApplicationContext.getInstance() if (method == NotificationDeliveryMethod.UNIFIEDPUSH) { + SignalStore.unifiedpush().pending = true UnifiedPush.getDistributors(context).getOrNull(0)?.let { refresh() EXECUTOR.enqueue { UnifiedPush.saveDistributor(context, it) UnifiedPush.registerApp(context) UnifiedPushHelper.initializeMollySocketLinkedDevice(context) + ApplicationDependencies.getJobManager().add(UnifiedPushRefreshJob()) } // Do not enable if there is no distributor } ?: return } else { UnifiedPush.unregisterApp(context) SignalStore.unifiedpush().airGaped = false - SignalStore.unifiedpush().mollySocketUrl = null + ApplicationDependencies.getJobManager().add(UnifiedPushRefreshJob()) } refresh() } diff --git a/app/src/unifiedpush/java/im/molly/unifiedpush/components/settings/app/notifications/UnifiedPushSettingsViewModel.kt b/app/src/unifiedpush/java/im/molly/unifiedpush/components/settings/app/notifications/UnifiedPushSettingsViewModel.kt index 7ddc3ed25e..6cc13eb225 100644 --- a/app/src/unifiedpush/java/im/molly/unifiedpush/components/settings/app/notifications/UnifiedPushSettingsViewModel.kt +++ b/app/src/unifiedpush/java/im/molly/unifiedpush/components/settings/app/notifications/UnifiedPushSettingsViewModel.kt @@ -7,13 +7,14 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import im.molly.unifiedpush.events.UnifiedPushRegistrationEvent +import im.molly.unifiedpush.jobs.UnifiedPushRefreshJob import im.molly.unifiedpush.model.UnifiedPushStatus -import im.molly.unifiedpush.model.saveStatus import im.molly.unifiedpush.util.MollySocketRequest import org.greenrobot.eventbus.Subscribe import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor import org.thoughtcrime.securesms.util.livedata.Store @@ -29,8 +30,10 @@ class UnifiedPushSettingsViewModel(private val application: Application) : ViewM val state: LiveData = store.stateLiveData @Subscribe - fun onNewEndpoint(e: UnifiedPushRegistrationEvent) { - processNewStatus() + fun onStatusRefreshed(e: UnifiedPushRegistrationEvent) { + Log.d(TAG, "Received event to refresh.") + status = SignalStore.unifiedpush().status + store.update { getState() } } private fun getState(): UnifiedPushSettingsState { @@ -104,40 +107,21 @@ class UnifiedPushSettingsViewModel(private val application: Application) : ViewM } else { url } - processNewStatus() + EXECUTOR.enqueue { + SignalStore.unifiedpush().mollySocketFound = try { + MollySocketRequest.discoverMollySocketServer() + } catch (e: Exception) { + SignalStore.unifiedpush().mollySocketInternalError = true + false + } + processNewStatus() + } } private fun processNewStatus() { - status = SignalStore.unifiedpush().status - if (SignalStore.unifiedpush().status in listOf( - UnifiedPushStatus.OK, - UnifiedPushStatus.FORBIDDEN_UUID, - UnifiedPushStatus.INTERNAL_ERROR, - UnifiedPushStatus.SERVER_NOT_FOUND_AT_URL, - ) - ) { - Log.d(TAG, "Trying to register to MollySocket") - status = UnifiedPushStatus.PENDING - EXECUTOR.enqueue { - try { - if (MollySocketRequest.discoverMollySocketServer()) { - SignalStore.unifiedpush().mollySocketFound = true - MollySocketRequest.registerToMollySocketServer().saveStatus() - status = SignalStore.unifiedpush().status - } else { - SignalStore.unifiedpush().mollySocketFound = false - status = SignalStore.unifiedpush().status - } - } catch (e: Exception) { - SignalStore.unifiedpush().mollySocketFound = false - status = UnifiedPushStatus.INTERNAL_ERROR - } - store.update { getState() } - } - } else { - status = SignalStore.unifiedpush().status - } + SignalStore.unifiedpush().pending = true store.update { getState() } + ApplicationDependencies.getJobManager().add(UnifiedPushRefreshJob()) } class Factory(private val application: Application) : ViewModelProvider.Factory { diff --git a/app/src/unifiedpush/java/im/molly/unifiedpush/jobs/UnifiedPushRefreshJob.kt b/app/src/unifiedpush/java/im/molly/unifiedpush/jobs/UnifiedPushRefreshJob.kt index f69a08c06b..44c58d7979 100644 --- a/app/src/unifiedpush/java/im/molly/unifiedpush/jobs/UnifiedPushRefreshJob.kt +++ b/app/src/unifiedpush/java/im/molly/unifiedpush/jobs/UnifiedPushRefreshJob.kt @@ -1,20 +1,32 @@ package im.molly.unifiedpush.jobs +import im.molly.unifiedpush.events.UnifiedPushRegistrationEvent import im.molly.unifiedpush.model.RegistrationStatus +import im.molly.unifiedpush.model.UnifiedPushStatus import im.molly.unifiedpush.model.saveStatus import im.molly.unifiedpush.util.MollySocketRequest import im.molly.unifiedpush.util.UnifiedPushHelper import im.molly.unifiedpush.util.UnifiedPushNotificationBuilder +import org.greenrobot.eventbus.EventBus import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.jobs.BaseJob +import org.thoughtcrime.securesms.keyvalue.SettingsValues import org.thoughtcrime.securesms.keyvalue.SignalStore import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException import java.util.concurrent.TimeUnit +/* +This job is called when : +- The delivery Method is called +- Delivery method == UnifiedPush and : + - The app starts + - one component related to UnifiedPush is changed + */ + class UnifiedPushRefreshJob private constructor(parameters: Parameters) : BaseJob(parameters) { constructor() : this( Parameters.Builder() @@ -35,20 +47,75 @@ class UnifiedPushRefreshJob private constructor(parameters: Parameters) : BaseJo @Throws(Exception::class) public override fun onRun() { Log.d(TAG, "Running the refresh job") + + // If this job is called while changing the notification method + val currentMethod = SignalStore.settings().notificationDeliveryMethod + if (currentMethod == SettingsValues.NotificationDeliveryMethod.FCM + || currentMethod == SettingsValues.NotificationDeliveryMethod.WEBSOCKET) { + Log.d(TAG, "New method: $currentMethod, reinitializing notification services.") + reInitializeNotificationServices() + return + } + + // Else : we try to use UnifiedPush UnifiedPushHelper.checkDistributorPresence(context) - if (UnifiedPushHelper.isUnifiedPushAvailable() && !SignalStore.unifiedpush().airGaped) { - Log.i(TAG, "Reregistering to MollySocket...") - when (val status = MollySocketRequest.registerToMollySocketServer()) { - RegistrationStatus.INTERNAL_ERROR -> Log.d(TAG, "An error occurred while trying to re-register with MollySocket. It may be a bad connection: ignore it.") - RegistrationStatus.OK -> Log.d(TAG, "Successfully re-registered to MollySocket") - else -> { - Log.w(TAG, "The registration status has changed!") - status.saveStatus() - ApplicationContext.getInstance().initializeFcmCheck() - UnifiedPushNotificationBuilder(context).setNotificationMollySocketRegistrationChanged() + when (val status = SignalStore.unifiedpush().status) { + // Should not occur + UnifiedPushStatus.DISABLED, + UnifiedPushStatus.UNKNOWN -> Log.e(TAG, "UnifiedPush setup should not be in this state here : $status.") + // It will fallback on FCM/Websocket + UnifiedPushStatus.MISSING_ENDPOINT, + UnifiedPushStatus.NO_DISTRIBUTOR, + UnifiedPushStatus.LINK_DEVICE_ERROR, + UnifiedPushStatus.SERVER_NOT_FOUND_AT_URL-> { + Log.i(TAG, "UnifiedPush enabled, but this is currently unavailable. Status=$status.") + reInitializeNotificationServices() + } + // Considered as successful setup + UnifiedPushStatus.AIR_GAPED -> { + Log.i(TAG, "UnifiedPush available in AirGaped mode. No MollySocket to register to.") + reInitializeNotificationServices() + } + // We try to register to MollySocket server, + // Then re-init the services + UnifiedPushStatus.PENDING, + UnifiedPushStatus.FORBIDDEN_UUID, + UnifiedPushStatus.FORBIDDEN_ENDPOINT, + UnifiedPushStatus.INTERNAL_ERROR -> { + Log.i(TAG, "Registering to MollySocket...") + SignalStore.unifiedpush().pending = false + val msStatus = MollySocketRequest.registerToMollySocketServer() + msStatus.saveStatus() + when (msStatus) { + RegistrationStatus.INTERNAL_ERROR -> Log.d(TAG, "An error occurred while trying to re-register with MollySocket.") + RegistrationStatus.OK -> { + Log.d(TAG, "Successfully re-registered to MollySocket") + reInitializeNotificationServices() + } + else -> Log.d(TAG, "Still not able to register to MollySocket: $msStatus.") + } + } + UnifiedPushStatus.OK -> { + Log.i(TAG, "Registering again to MollySocket...") + when (val msStatus = MollySocketRequest.registerToMollySocketServer()) { + RegistrationStatus.INTERNAL_ERROR -> Log.d(TAG, "An error occurred while trying to re-register with MollySocket. It may be a bad connection: ignore it.") + RegistrationStatus.OK -> Log.d(TAG, "Successfully re-registered to MollySocket") + else -> { + Log.w(TAG, "The registration status has changed!") + msStatus.saveStatus() + reInitializeNotificationServices() + UnifiedPushNotificationBuilder(context).setNotificationMollySocketRegistrationChanged() + } } } } + EventBus.getDefault().post(UnifiedPushRegistrationEvent) + } + + private fun reInitializeNotificationServices() { + ApplicationContext.getInstance().finalizeMessageRetrieval() + ApplicationContext.getInstance().initializeFcmCheck() + ApplicationContext.getInstance().initializeMessageRetrieval() } override fun onFailure() { diff --git a/app/src/unifiedpush/java/im/molly/unifiedpush/receiver/UnifiedPushReceiver.kt b/app/src/unifiedpush/java/im/molly/unifiedpush/receiver/UnifiedPushReceiver.kt index d89d8f0121..92c23e9f97 100644 --- a/app/src/unifiedpush/java/im/molly/unifiedpush/receiver/UnifiedPushReceiver.kt +++ b/app/src/unifiedpush/java/im/molly/unifiedpush/receiver/UnifiedPushReceiver.kt @@ -3,15 +3,12 @@ package im.molly.unifiedpush.receiver import android.content.Context import androidx.core.os.bundleOf import com.google.firebase.messaging.RemoteMessage -import im.molly.unifiedpush.events.UnifiedPushRegistrationEvent -import im.molly.unifiedpush.model.UnifiedPushStatus -import im.molly.unifiedpush.model.saveStatus -import im.molly.unifiedpush.util.MollySocketRequest +import im.molly.unifiedpush.jobs.UnifiedPushRefreshJob import im.molly.unifiedpush.util.UnifiedPushHelper import im.molly.unifiedpush.util.UnifiedPushNotificationBuilder -import org.greenrobot.eventbus.EventBus import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.gcm.FcmReceiveService import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor @@ -25,32 +22,7 @@ class UnifiedPushReceiver : MessagingReceiver() { Log.d(TAG, "New endpoint: $endpoint") if (SignalStore.unifiedpush().endpoint != endpoint) { SignalStore.unifiedpush().endpoint = endpoint - when (SignalStore.unifiedpush().status) { - UnifiedPushStatus.AIR_GAPED -> { - EventBus.getDefault().post(UnifiedPushRegistrationEvent) - UnifiedPushNotificationBuilder(context).setNotificationEndpointChangedAirGaped() - } - UnifiedPushStatus.OK -> { - EXECUTOR.enqueue { - MollySocketRequest.registerToMollySocketServer().saveStatus() - EventBus.getDefault().post(UnifiedPushRegistrationEvent) - if (SignalStore.unifiedpush().status != UnifiedPushStatus.OK) - UnifiedPushNotificationBuilder(context).setNotificationEndpointChangedError() - } - } - in listOf( - UnifiedPushStatus.INTERNAL_ERROR, - UnifiedPushStatus.MISSING_ENDPOINT, - ) -> { - EXECUTOR.enqueue { - MollySocketRequest.registerToMollySocketServer().saveStatus() - EventBus.getDefault().post(UnifiedPushRegistrationEvent) - } - } - else -> { - EventBus.getDefault().post(UnifiedPushRegistrationEvent) - } - } + ApplicationDependencies.getJobManager().add(UnifiedPushRefreshJob()) } } @@ -63,7 +35,7 @@ class UnifiedPushReceiver : MessagingReceiver() { // called when this application is unregistered from receiving push messages // isPushAvailable becomes false => The websocket starts SignalStore.unifiedpush().endpoint = null - EventBus.getDefault().post(UnifiedPushRegistrationEvent) + ApplicationDependencies.getJobManager().add(UnifiedPushRefreshJob()) } override fun onMessage(context: Context, message: ByteArray, instance: String) { diff --git a/app/src/unifiedpush/java/org/thoughtcrime/securesms/keyvalue/UnifiedPushValues.kt b/app/src/unifiedpush/java/org/thoughtcrime/securesms/keyvalue/UnifiedPushValues.kt index f6cd98edbf..4098ebcb8f 100644 --- a/app/src/unifiedpush/java/org/thoughtcrime/securesms/keyvalue/UnifiedPushValues.kt +++ b/app/src/unifiedpush/java/org/thoughtcrime/securesms/keyvalue/UnifiedPushValues.kt @@ -16,6 +16,7 @@ internal class UnifiedPushValues(store: KeyValueStore) : SignalStoreValues(store private const val MOLLYSOCKET_INTERNAL_ERROR = "unifiedpush.mollysocket.internal_error" private const val UNIFIEDPUSH_ENDPOINT = "unifiedpush.endpoint" private const val UNIFIEDPUSH_ENABLED = "unifiedpush.enabled" + private const val UNIFIEDPUSH_PENDING = "unifiedpush.pending" private const val UNIFIEDPUSH_AIR_GAPED = "unifiedpush.air_gaped" } @@ -45,7 +46,7 @@ internal class UnifiedPushValues(store: KeyValueStore) : SignalStoreValues(store var endpoint: String? by stringValue(UNIFIEDPUSH_ENDPOINT, null) - var enabled: Boolean by booleanValue(UNIFIEDPUSH_ENABLED, false) + var pending: Boolean by booleanValue(UNIFIEDPUSH_PENDING, false) var airGaped: Boolean by booleanValue(UNIFIEDPUSH_AIR_GAPED, false) @@ -61,12 +62,14 @@ internal class UnifiedPushValues(store: KeyValueStore) : SignalStoreValues(store val status: UnifiedPushStatus get() = when { - !SignalStore.unifiedpush().enabled -> UnifiedPushStatus.DISABLED + SignalStore.settings().notificationDeliveryMethod != SettingsValues.NotificationDeliveryMethod.UNIFIEDPUSH -> UnifiedPushStatus.DISABLED + SignalStore.unifiedpush().pending -> UnifiedPushStatus.PENDING SignalStore.unifiedpush().device == null -> UnifiedPushStatus.LINK_DEVICE_ERROR SignalStore.unifiedpush().endpoint == null -> UnifiedPushStatus.MISSING_ENDPOINT SignalStore.unifiedpush().airGaped -> UnifiedPushStatus.AIR_GAPED SignalStore.unifiedpush().mollySocketUrl.isNullOrBlank() || !SignalStore.unifiedpush().mollySocketFound -> UnifiedPushStatus.SERVER_NOT_FOUND_AT_URL + SignalStore.unifiedpush().mollySocketInternalError -> UnifiedPushStatus.INTERNAL_ERROR SignalStore.unifiedpush().forbiddenUuid -> UnifiedPushStatus.FORBIDDEN_UUID SignalStore.unifiedpush().forbiddenEndpoint -> UnifiedPushStatus.FORBIDDEN_ENDPOINT else -> UnifiedPushStatus.OK