diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt index e8ba1cca2b..68a67d90e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsFragment.kt @@ -25,7 +25,6 @@ import org.signal.core.ui.BottomSheetUtil import org.signal.core.ui.permissions.PermissionDeniedBottomSheet import org.signal.core.ui.permissions.RationaleDialog import org.signal.core.util.AppUtil -import org.signal.core.util.ThreadUtil import org.signal.core.util.Util import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.concurrent.SimpleTask @@ -1055,19 +1054,17 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter .setTitle("Unregister?") .setMessage("Are you sure? You'll have to re-register to use Signal again -- no promises that the process will go smoothly.") .setPositiveButton(android.R.string.ok) { _, _ -> - AdvancedPrivacySettingsRepository(requireContext()).disablePushMessages { - ThreadUtil.runOnMain { - when (it) { - AdvancedPrivacySettingsRepository.DisablePushMessagesResult.SUCCESS -> { - SignalStore.account.setRegistered(false) - SignalStore.registration.clearRegistrationComplete() - SignalStore.registration.hasUploadedProfile = false - Toast.makeText(context, "Unregistered!", Toast.LENGTH_SHORT).show() - } + lifecycleScope.launch { + when (AdvancedPrivacySettingsRepository(requireContext()).disablePushMessages()) { + AdvancedPrivacySettingsRepository.DisablePushMessagesResult.SUCCESS -> { + SignalStore.account.setRegistered(false) + SignalStore.registration.clearRegistrationComplete() + SignalStore.registration.hasUploadedProfile = false + Toast.makeText(context, "Unregistered!", Toast.LENGTH_SHORT).show() + } - AdvancedPrivacySettingsRepository.DisablePushMessagesResult.NETWORK_ERROR -> { - Toast.makeText(context, "Network error!", Toast.LENGTH_SHORT).show() - } + AdvancedPrivacySettingsRepository.DisablePushMessagesResult.NETWORK_ERROR -> { + Toast.makeText(context, "Network error!", Toast.LENGTH_SHORT).show() } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsRepository.kt index 75d58ada59..bf7a37dc7a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/advanced/AdvancedPrivacySettingsRepository.kt @@ -3,8 +3,12 @@ package org.thoughtcrime.securesms.components.settings.app.privacy.advanced import android.content.Context import com.google.android.gms.tasks.Tasks import com.google.firebase.installations.FirebaseInstallations +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.logging.Log +import org.signal.libsignal.net.DeviceDeregisteredException +import org.signal.libsignal.net.RequestResult import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob @@ -13,8 +17,6 @@ import org.thoughtcrime.securesms.net.SignalNetwork import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.TextSecurePreferences -import org.whispersystems.signalservice.api.NetworkResultUtil -import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException import java.io.IOException import java.util.concurrent.ExecutionException @@ -22,30 +24,34 @@ private val TAG = Log.tag(AdvancedPrivacySettingsRepository::class.java) class AdvancedPrivacySettingsRepository(private val context: Context) { - fun disablePushMessages(consumer: (DisablePushMessagesResult) -> Unit) { - SignalExecutors.BOUNDED.execute { - val result = try { - try { - NetworkResultUtil.toBasicLegacy(SignalNetwork.account.clearFcmToken()) - } catch (e: AuthorizationFailedException) { - Log.w(TAG, e) - } - if (SignalStore.account.fcmEnabled) { - Tasks.await(FirebaseInstallations.getInstance().delete()) - } - DisablePushMessagesResult.SUCCESS - } catch (ioe: IOException) { - Log.w(TAG, ioe) - DisablePushMessagesResult.NETWORK_ERROR - } catch (e: InterruptedException) { - Log.w(TAG, "Interrupted while deleting", e) - DisablePushMessagesResult.NETWORK_ERROR - } catch (e: ExecutionException) { - Log.w(TAG, "Error deleting", e.cause) - DisablePushMessagesResult.NETWORK_ERROR - } + suspend fun disablePushMessages(): DisablePushMessagesResult = withContext(Dispatchers.IO) { + val clearTokenError: Throwable? = when (val result = SignalNetwork.account.clearFcmToken()) { + is RequestResult.Success, is RequestResult.NonSuccess -> null + is RequestResult.RetryableNetworkError -> result.networkError + is RequestResult.ApplicationError -> result.cause + } - consumer(result) + if (clearTokenError != null) { + Log.w(TAG, clearTokenError) + if (clearTokenError !is DeviceDeregisteredException) { + return@withContext DisablePushMessagesResult.NETWORK_ERROR + } + } + + try { + if (SignalStore.account.fcmEnabled) { + Tasks.await(FirebaseInstallations.getInstance().delete()) + } + DisablePushMessagesResult.SUCCESS + } catch (ioe: IOException) { + Log.w(TAG, ioe) + DisablePushMessagesResult.NETWORK_ERROR + } catch (e: InterruptedException) { + Log.w(TAG, "Interrupted while deleting", e) + DisablePushMessagesResult.NETWORK_ERROR + } catch (e: ExecutionException) { + Log.w(TAG, "Error deleting", e.cause) + DisablePushMessagesResult.NETWORK_ERROR } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java index f9596f45f8..a70d6335e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/FcmRefreshJob.java @@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.net.SignalNetwork; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.signal.core.util.PlayServicesUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.signalservice.api.NetworkResultUtil; +import org.whispersystems.signalservice.api.RequestResultUtil; import org.signal.network.exceptions.NonSuccessfulResponseCodeException; import java.io.IOException; @@ -101,7 +101,7 @@ public class FcmRefreshJob extends BaseJob { Log.i(TAG, "Token didn't change."); } - NetworkResultUtil.toBasicLegacy(SignalNetwork.account().setFcmToken(token.get())); + RequestResultUtil.successOrThrowNoError(SignalNetwork.account().setFcmToken(token.get())); SignalStore.account().setFcmToken(token.get()); if (!SignalStore.account().isFcmEnabled()) { diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/RequestResultExtensions.kt b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/RequestResultExtensions.kt index 27178c6c94..bbe3e7b6d8 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/RequestResultExtensions.kt +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/RequestResultExtensions.kt @@ -70,6 +70,25 @@ fun RequestResult.successOrThrow(): T { } } +/** + * [successOrThrow] variant for an API that defines no business-logic errors, so there is no + * [RequestResult.NonSuccess] case to handle. Exists as a separate overload because Java cannot + * bind a [Nothing] error argument to the [RestStatusCodeError] receiver above. + */ +@JvmName("successOrThrowNoError") +@Throws(IOException::class) +fun RequestResult.successOrThrow(): T { + return when (this) { + is RequestResult.Success -> result + is RequestResult.RetryableNetworkError -> throw networkError + is RequestResult.NonSuccess -> error("Branch is unreachable") + is RequestResult.ApplicationError -> throw when (val error = cause) { + is IOException, is RuntimeException -> error + else -> RuntimeException(error) + } + } +} + private fun WebsocketResponse.toRequestResult(clazz: KClass): RequestResult { return if (status < 200 || status > 299) { RequestResult.NonSuccess(RestStatusCodeError(status, headers, body?.toByteArray())) diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/account/AccountApi.kt b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/account/AccountApi.kt index f26e374341..d8e1a35687 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/account/AccountApi.kt +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/account/AccountApi.kt @@ -5,8 +5,10 @@ package org.whispersystems.signalservice.api.account +import kotlinx.coroutines.runBlocking import org.signal.core.util.Base64 import org.signal.core.util.Base64.encodeUrlSafeWithoutPadding +import org.signal.libsignal.net.AuthDevicesService import org.signal.libsignal.net.RequestResult import org.signal.libsignal.usernames.BaseUsernameException import org.signal.libsignal.usernames.Username @@ -21,7 +23,6 @@ import org.whispersystems.signalservice.api.push.UsernameLinkComponents import org.whispersystems.signalservice.api.websocket.SignalWebSocket import org.whispersystems.signalservice.internal.push.ConfirmUsernameRequest import org.whispersystems.signalservice.internal.push.ConfirmUsernameResponse -import org.whispersystems.signalservice.internal.push.GcmRegistrationId import org.whispersystems.signalservice.internal.push.PhoneNumberDiscoverabilityRequest import org.whispersystems.signalservice.internal.push.PushServiceSocket import org.whispersystems.signalservice.internal.push.ReserveUsernameRequest @@ -52,21 +53,24 @@ class AccountApi(private val authWebSocket: SignalWebSocket.AuthenticatedWebSock } /** - * PUT /v1/accounts/gcm - * - 200: Success + * Sets the FCM push token the server should use to notify this device of new messages. */ - fun setFcmToken(fcmToken: String): NetworkResult { - val request = WebSocketRequestMessage.put("/v1/accounts/gcm", GcmRegistrationId(fcmToken, true)) - return NetworkResult.fromWebSocketRequest(authWebSocket, request) + fun setFcmToken(fcmToken: String): RequestResult { + return runBlocking { + authWebSocket.runCatchingWithChatConnection { connection -> + AuthDevicesService(connection).setPushToken(fcmToken) + } + } } /** - * DELETE /v1/account/gcm - * - 204: Success + * Removes any push tokens associated with this device. Afterwards, the server will assume this device + * polls for new messages over an open websocket. */ - fun clearFcmToken(): NetworkResult { - val request = WebSocketRequestMessage.delete("/v1/accounts/gcm") - return NetworkResult.fromWebSocketRequest(authWebSocket, request) + suspend fun clearFcmToken(): RequestResult { + return authWebSocket.runCatchingWithChatConnection { connection -> + AuthDevicesService(connection).clearPushToken() + } } /**