Allow renaming of linked device.
This commit is contained in:
parent
ce69c5f7da
commit
3e699a132b
16 changed files with 500 additions and 12 deletions
|
@ -0,0 +1,77 @@
|
||||||
|
package org.thoughtcrime.securesms.jobs
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Job
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||||
|
import org.thoughtcrime.securesms.jobs.protos.DeviceNameChangeJobData
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException
|
||||||
|
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage
|
||||||
|
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a sync message that a linked device has changed its name
|
||||||
|
*/
|
||||||
|
class DeviceNameChangeJob private constructor(
|
||||||
|
private val data: DeviceNameChangeJobData,
|
||||||
|
parameters: Parameters
|
||||||
|
) : Job(parameters) {
|
||||||
|
companion object {
|
||||||
|
const val KEY: String = "DeviceNameChangeJob"
|
||||||
|
private val TAG = Log.tag(DeviceNameChangeJob::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
deviceId: Int
|
||||||
|
) : this(
|
||||||
|
DeviceNameChangeJobData(deviceId),
|
||||||
|
Parameters.Builder()
|
||||||
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
|
.setQueue("DeviceNameChangeJob")
|
||||||
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||||
|
.setMaxAttempts(Parameters.UNLIMITED)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun serialize(): ByteArray {
|
||||||
|
return data.encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFactoryKey(): String = KEY
|
||||||
|
|
||||||
|
override fun run(): Result {
|
||||||
|
if (!Recipient.self().isRegistered) {
|
||||||
|
Log.w(TAG, "Not registered")
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val result = AppDependencies.signalServiceMessageSender.sendSyncMessage(
|
||||||
|
SignalServiceSyncMessage.forDeviceNameChange(SyncMessage.DeviceNameChange(data.deviceId))
|
||||||
|
)
|
||||||
|
if (result.isSuccess) {
|
||||||
|
Result.success()
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Unable to send device name sync - trying later")
|
||||||
|
Result.retry(defaultBackoff())
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.w(TAG, "Unable to send device name sync - trying later", e)
|
||||||
|
Result.retry(defaultBackoff())
|
||||||
|
} catch (e: UntrustedIdentityException) {
|
||||||
|
Log.w(TAG, "Unable to send device name sync", e)
|
||||||
|
Result.failure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure() = Unit
|
||||||
|
|
||||||
|
class Factory : Job.Factory<DeviceNameChangeJob?> {
|
||||||
|
override fun create(parameters: Parameters, serializedData: ByteArray?): DeviceNameChangeJob {
|
||||||
|
return DeviceNameChangeJob(DeviceNameChangeJobData.ADAPTER.decode(serializedData!!), parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -142,6 +142,7 @@ public final class JobManagerFactories {
|
||||||
put(CopyAttachmentToArchiveJob.KEY, new CopyAttachmentToArchiveJob.Factory());
|
put(CopyAttachmentToArchiveJob.KEY, new CopyAttachmentToArchiveJob.Factory());
|
||||||
put(CreateReleaseChannelJob.KEY, new CreateReleaseChannelJob.Factory());
|
put(CreateReleaseChannelJob.KEY, new CreateReleaseChannelJob.Factory());
|
||||||
put(DeleteAbandonedAttachmentsJob.KEY, new DeleteAbandonedAttachmentsJob.Factory());
|
put(DeleteAbandonedAttachmentsJob.KEY, new DeleteAbandonedAttachmentsJob.Factory());
|
||||||
|
put(DeviceNameChangeJob.KEY, new DeviceNameChangeJob.Factory());
|
||||||
put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory());
|
put(DirectoryRefreshJob.KEY, new DirectoryRefreshJob.Factory());
|
||||||
put(DownloadLatestEmojiDataJob.KEY, new DownloadLatestEmojiDataJob.Factory());
|
put(DownloadLatestEmojiDataJob.KEY, new DownloadLatestEmojiDataJob.Factory());
|
||||||
put(EmojiSearchIndexDownloadJob.KEY, new EmojiSearchIndexDownloadJob.Factory());
|
put(EmojiSearchIndexDownloadJob.KEY, new EmojiSearchIndexDownloadJob.Factory());
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
package org.thoughtcrime.securesms.linkdevice
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.TextRange
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import org.signal.core.ui.Buttons
|
||||||
|
import org.signal.core.ui.Previews
|
||||||
|
import org.signal.core.ui.Scaffolds
|
||||||
|
import org.signal.core.ui.SignalPreview
|
||||||
|
import org.signal.core.util.isNotNullOrBlank
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for changing the name of a linked device
|
||||||
|
*/
|
||||||
|
class EditDeviceNameFragment : ComposeFragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = Log.tag(EditDeviceNameFragment::class)
|
||||||
|
const val MAX_LENGTH = 50
|
||||||
|
}
|
||||||
|
|
||||||
|
private val viewModel: LinkDeviceViewModel by activityViewModels()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun FragmentContent() {
|
||||||
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
val navController: NavController by remember { mutableStateOf(findNavController()) }
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(state.oneTimeEvent) {
|
||||||
|
when (state.oneTimeEvent) {
|
||||||
|
LinkDeviceSettingsState.OneTimeEvent.SnackbarNameChangeSuccess -> {
|
||||||
|
Snackbar.make(requireView(), context.getString(R.string.EditDeviceNameFragment__device_name_updated), Snackbar.LENGTH_LONG).show()
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
LinkDeviceSettingsState.OneTimeEvent.SnackbarNameChangeFailure -> {
|
||||||
|
Snackbar.make(requireView(), context.getString(R.string.EditDeviceNameFragment__unable_to_change), Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
LinkDeviceSettingsState.OneTimeEvent.HideFinishedSheet -> Unit
|
||||||
|
LinkDeviceSettingsState.OneTimeEvent.LaunchQrCodeScanner -> Unit
|
||||||
|
LinkDeviceSettingsState.OneTimeEvent.None -> Unit
|
||||||
|
LinkDeviceSettingsState.OneTimeEvent.ShowFinishedSheet -> Unit
|
||||||
|
is LinkDeviceSettingsState.OneTimeEvent.ToastLinked -> Unit
|
||||||
|
LinkDeviceSettingsState.OneTimeEvent.ToastNetworkFailed -> Unit
|
||||||
|
is LinkDeviceSettingsState.OneTimeEvent.ToastUnlinked -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffolds.Settings(
|
||||||
|
title = stringResource(id = R.string.EditDeviceNameFragment__edit),
|
||||||
|
onNavigationClick = { navController.popBackStack() },
|
||||||
|
navigationIconPainter = painterResource(id = R.drawable.ic_arrow_left_24),
|
||||||
|
navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close)
|
||||||
|
) { contentPadding: PaddingValues ->
|
||||||
|
EditNameScreen(
|
||||||
|
state = state,
|
||||||
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
onSave = { viewModel.saveName(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun EditNameScreen(
|
||||||
|
state: LinkDeviceSettingsState,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onSave: (String) -> Unit = {}
|
||||||
|
) {
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
val name = state.deviceToEdit!!.name ?: ""
|
||||||
|
var deviceName by remember { mutableStateOf(TextFieldValue(name, TextRange(name.length))) }
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier.fillMaxHeight()
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
value = deviceName,
|
||||||
|
label = { Text(text = stringResource(id = R.string.EditDeviceNameFragment__device_name)) },
|
||||||
|
onValueChange = {
|
||||||
|
deviceName = it.copy(
|
||||||
|
text = it.text.substring(0, minOf(it.text.length, EditDeviceNameFragment.MAX_LENGTH))
|
||||||
|
)
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
|
||||||
|
singleLine = true,
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.padding(top = 16.dp, bottom = 12.dp, start = 20.dp, end = 28.dp)
|
||||||
|
)
|
||||||
|
Buttons.MediumTonal(
|
||||||
|
enabled = deviceName.text.isNotNullOrBlank() && (deviceName.text != name),
|
||||||
|
onClick = { onSave(deviceName.text) },
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.padding(end = 24.dp, bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.EditDeviceNameFragment__save))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SignalPreview
|
||||||
|
@Composable
|
||||||
|
private fun DeviceListScreenLinkingPreview() {
|
||||||
|
Previews.Preview {
|
||||||
|
EditNameScreen(
|
||||||
|
state = LinkDeviceSettingsState(
|
||||||
|
deviceToEdit = Device(1, "Laptop", 0, 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import androidx.biometric.BiometricPrompt
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
@ -59,6 +60,7 @@ import androidx.navigation.fragment.findNavController
|
||||||
import org.signal.core.ui.Buttons
|
import org.signal.core.ui.Buttons
|
||||||
import org.signal.core.ui.Dialogs
|
import org.signal.core.ui.Dialogs
|
||||||
import org.signal.core.ui.Dividers
|
import org.signal.core.ui.Dividers
|
||||||
|
import org.signal.core.ui.DropdownMenus
|
||||||
import org.signal.core.ui.Previews
|
import org.signal.core.ui.Previews
|
||||||
import org.signal.core.ui.Scaffolds
|
import org.signal.core.ui.Scaffolds
|
||||||
import org.signal.core.ui.SignalPreview
|
import org.signal.core.ui.SignalPreview
|
||||||
|
@ -146,6 +148,8 @@ class LinkDeviceFragment : ComposeFragment() {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LinkDeviceSettingsState.OneTimeEvent.SnackbarNameChangeFailure -> Unit
|
||||||
|
LinkDeviceSettingsState.OneTimeEvent.SnackbarNameChangeSuccess -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.oneTimeEvent != LinkDeviceSettingsState.OneTimeEvent.None) {
|
if (state.oneTimeEvent != LinkDeviceSettingsState.OneTimeEvent.None) {
|
||||||
|
@ -176,7 +180,11 @@ class LinkDeviceFragment : ComposeFragment() {
|
||||||
onDeviceSelectedForRemoval = { device -> viewModel.setDeviceToRemove(device) },
|
onDeviceSelectedForRemoval = { device -> viewModel.setDeviceToRemove(device) },
|
||||||
onDeviceRemovalConfirmed = { device -> viewModel.removeDevice(device) },
|
onDeviceRemovalConfirmed = { device -> viewModel.removeDevice(device) },
|
||||||
onSyncFailureRetryRequested = { deviceId -> viewModel.onSyncErrorRetryRequested(deviceId) },
|
onSyncFailureRetryRequested = { deviceId -> viewModel.onSyncErrorRetryRequested(deviceId) },
|
||||||
onSyncFailureIgnored = { viewModel.onSyncErrorIgnored() }
|
onSyncFailureIgnored = { viewModel.onSyncErrorIgnored() },
|
||||||
|
onEditDevice = { device ->
|
||||||
|
viewModel.setDeviceToEdit(device)
|
||||||
|
navController.safeNavigate(R.id.action_linkDeviceFragment_to_editDeviceNameFragment)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +229,8 @@ fun DeviceListScreen(
|
||||||
onDeviceSelectedForRemoval: (Device?) -> Unit = {},
|
onDeviceSelectedForRemoval: (Device?) -> Unit = {},
|
||||||
onDeviceRemovalConfirmed: (Device) -> Unit = {},
|
onDeviceRemovalConfirmed: (Device) -> Unit = {},
|
||||||
onSyncFailureRetryRequested: (Int?) -> Unit = {},
|
onSyncFailureRetryRequested: (Int?) -> Unit = {},
|
||||||
onSyncFailureIgnored: () -> Unit = {}
|
onSyncFailureIgnored: () -> Unit = {},
|
||||||
|
onEditDevice: (Device) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
// If a bottom sheet is showing, we don't want the spinner underneath
|
// If a bottom sheet is showing, we don't want the spinner underneath
|
||||||
if (!state.bottomSheetVisible) {
|
if (!state.bottomSheetVisible) {
|
||||||
|
@ -328,7 +337,7 @@ fun DeviceListScreen(
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
state.devices.forEach { device ->
|
state.devices.forEach { device ->
|
||||||
DeviceRow(device, onDeviceSelectedForRemoval)
|
DeviceRow(device, onDeviceSelectedForRemoval, onEditDevice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -372,16 +381,14 @@ fun DeviceListScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeviceRow(device: Device, setDeviceToRemove: (Device) -> Unit) {
|
fun DeviceRow(device: Device, setDeviceToRemove: (Device) -> Unit, onEditDevice: (Device) -> Unit) {
|
||||||
val titleString = if (device.name.isNullOrEmpty()) stringResource(R.string.DeviceListItem_unnamed_device) else device.name
|
val titleString = if (device.name.isNullOrEmpty()) stringResource(R.string.DeviceListItem_unnamed_device) else device.name
|
||||||
val linkedDate = DateUtils.getDayPrecisionTimeSpanString(LocalContext.current, Locale.getDefault(), device.createdMillis)
|
val linkedDate = DateUtils.getDayPrecisionTimeSpanString(LocalContext.current, Locale.getDefault(), device.createdMillis)
|
||||||
val lastActive = DateUtils.getDayPrecisionTimeSpanString(LocalContext.current, Locale.getDefault(), device.lastSeenMillis)
|
val lastActive = DateUtils.getDayPrecisionTimeSpanString(LocalContext.current, Locale.getDefault(), device.lastSeenMillis)
|
||||||
|
val menuController = remember { DropdownMenus.MenuController() }
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { setDeviceToRemove(device) },
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.drawable.symbol_devices_24),
|
painter = painterResource(id = R.drawable.symbol_devices_24),
|
||||||
|
@ -395,13 +402,76 @@ fun DeviceRow(device: Device, setDeviceToRemove: (Device) -> Unit) {
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
shape = CircleShape
|
shape = CircleShape
|
||||||
)
|
)
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.size(16.dp))
|
|
||||||
Column {
|
Column(
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically).padding(start = 16.dp).weight(1f)
|
||||||
|
) {
|
||||||
Text(text = titleString, style = MaterialTheme.typography.bodyLarge)
|
Text(text = titleString, style = MaterialTheme.typography.bodyLarge)
|
||||||
Text(stringResource(R.string.DeviceListItem_linked_s, linkedDate), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
Text(stringResource(R.string.DeviceListItem_linked_s, linkedDate), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
Text(stringResource(R.string.DeviceListItem_last_active_s, lastActive), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
Text(stringResource(R.string.DeviceListItem_last_active_s, lastActive), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box {
|
||||||
|
Icon(
|
||||||
|
painterResource(id = R.drawable.symbol_more_vertical),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.padding(top = 16.dp, end = 16.dp).clickable { menuController.show() }
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenus.Menu(controller = menuController, offsetX = 16.dp, offsetY = 4.dp) { controller ->
|
||||||
|
DropdownMenus.Item(
|
||||||
|
contentPadding = PaddingValues(0.dp),
|
||||||
|
text = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.symbol_link_slash_16),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.LinkDeviceFragment__unlink),
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
setDeviceToRemove(device)
|
||||||
|
controller.hide()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenus.Item(
|
||||||
|
contentPadding = PaddingValues(0.dp),
|
||||||
|
text = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.symbol_edit_24),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.LinkDeviceFragment__edit_name),
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
onEditDevice(device)
|
||||||
|
controller.hide()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.signal.core.util.Base64
|
||||||
import org.signal.core.util.Stopwatch
|
import org.signal.core.util.Stopwatch
|
||||||
import org.signal.core.util.isNotNullOrBlank
|
import org.signal.core.util.isNotNullOrBlank
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.signal.core.util.logging.logI
|
||||||
import org.signal.core.util.logging.logW
|
import org.signal.core.util.logging.logW
|
||||||
import org.signal.libsignal.protocol.InvalidKeyException
|
import org.signal.libsignal.protocol.InvalidKeyException
|
||||||
import org.signal.libsignal.protocol.ecc.Curve
|
import org.signal.libsignal.protocol.ecc.Curve
|
||||||
|
@ -13,6 +14,7 @@ import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||||
import org.thoughtcrime.securesms.devicelist.protos.DeviceName
|
import org.thoughtcrime.securesms.devicelist.protos.DeviceName
|
||||||
|
import org.thoughtcrime.securesms.jobs.DeviceNameChangeJob
|
||||||
import org.thoughtcrime.securesms.jobs.LinkedDeviceInactiveCheckJob
|
import org.thoughtcrime.securesms.jobs.LinkedDeviceInactiveCheckJob
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||||
|
@ -29,6 +31,7 @@ import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
@ -334,6 +337,28 @@ object LinkDeviceRepository {
|
||||||
return NetworkResult.NetworkError(IOException("Hit max retries!"))
|
return NetworkResult.NetworkError(IOException("Hit max retries!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the name of a linked device and sends a sync message if successful
|
||||||
|
*/
|
||||||
|
fun changeDeviceName(deviceName: String, deviceId: Int): DeviceNameChangeResult {
|
||||||
|
val encryptedDeviceName = Base64.encodeWithoutPadding(DeviceNameCipher.encryptDeviceName(deviceName.toByteArray(StandardCharsets.UTF_8), SignalStore.account.aciIdentityKey))
|
||||||
|
return when (val result = SignalNetwork.linkDevice.setDeviceName(encryptedDeviceName, deviceId)) {
|
||||||
|
is NetworkResult.Success -> {
|
||||||
|
AppDependencies.jobManager.add(DeviceNameChangeJob(deviceId))
|
||||||
|
DeviceNameChangeResult.Success.logI(TAG, "Successfully changed device name")
|
||||||
|
}
|
||||||
|
is NetworkResult.NetworkError -> {
|
||||||
|
DeviceNameChangeResult.NetworkError(result.exception).logW(TAG, "Could not change name due to network error.", result.exception)
|
||||||
|
}
|
||||||
|
is NetworkResult.StatusCodeError -> {
|
||||||
|
DeviceNameChangeResult.NetworkError(result.exception).logW(TAG, "Could not change name due to status code error ${result.code}")
|
||||||
|
}
|
||||||
|
is NetworkResult.ApplicationError -> {
|
||||||
|
throw result.throwable.logW(TAG, "Could not change name due to application error.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed interface LinkDeviceResult {
|
sealed interface LinkDeviceResult {
|
||||||
data object None : LinkDeviceResult
|
data object None : LinkDeviceResult
|
||||||
data class Success(val token: String) : LinkDeviceResult
|
data class Success(val token: String) : LinkDeviceResult
|
||||||
|
@ -350,4 +375,9 @@ object LinkDeviceRepository {
|
||||||
data class BadRequest(val exception: IOException) : LinkUploadArchiveResult
|
data class BadRequest(val exception: IOException) : LinkUploadArchiveResult
|
||||||
data class NetworkError(val exception: IOException) : LinkUploadArchiveResult
|
data class NetworkError(val exception: IOException) : LinkUploadArchiveResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed interface DeviceNameChangeResult {
|
||||||
|
data object Success : DeviceNameChangeResult
|
||||||
|
data class NetworkError(val exception: IOException) : DeviceNameChangeResult
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,8 @@ data class LinkDeviceSettingsState(
|
||||||
val linkDeviceResult: LinkDeviceResult = LinkDeviceResult.None,
|
val linkDeviceResult: LinkDeviceResult = LinkDeviceResult.None,
|
||||||
val seenIntroSheet: Boolean = false,
|
val seenIntroSheet: Boolean = false,
|
||||||
val seenEducationSheet: Boolean = false,
|
val seenEducationSheet: Boolean = false,
|
||||||
val bottomSheetVisible: Boolean = false
|
val bottomSheetVisible: Boolean = false,
|
||||||
|
val deviceToEdit: Device? = null
|
||||||
) {
|
) {
|
||||||
sealed interface DialogState {
|
sealed interface DialogState {
|
||||||
data object None : DialogState
|
data object None : DialogState
|
||||||
|
@ -34,6 +35,8 @@ data class LinkDeviceSettingsState(
|
||||||
data object ToastNetworkFailed : OneTimeEvent
|
data object ToastNetworkFailed : OneTimeEvent
|
||||||
data class ToastUnlinked(val name: String) : OneTimeEvent
|
data class ToastUnlinked(val name: String) : OneTimeEvent
|
||||||
data class ToastLinked(val name: String) : OneTimeEvent
|
data class ToastLinked(val name: String) : OneTimeEvent
|
||||||
|
data object SnackbarNameChangeSuccess : OneTimeEvent
|
||||||
|
data object SnackbarNameChangeFailure : OneTimeEvent
|
||||||
data object ShowFinishedSheet : OneTimeEvent
|
data object ShowFinishedSheet : OneTimeEvent
|
||||||
data object HideFinishedSheet : OneTimeEvent
|
data object HideFinishedSheet : OneTimeEvent
|
||||||
data object LaunchQrCodeScanner : OneTimeEvent
|
data object LaunchQrCodeScanner : OneTimeEvent
|
||||||
|
|
|
@ -323,4 +323,29 @@ class LinkDeviceViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setDeviceToEdit(device: Device) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
deviceToEdit = device
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveName(name: String) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val device = _state.value.deviceToEdit!!
|
||||||
|
val result = LinkDeviceRepository.changeDeviceName(name, device.id)
|
||||||
|
val event = when (result) {
|
||||||
|
LinkDeviceRepository.DeviceNameChangeResult.Success -> OneTimeEvent.SnackbarNameChangeSuccess
|
||||||
|
is LinkDeviceRepository.DeviceNameChangeResult.NetworkError -> OneTimeEvent.SnackbarNameChangeFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
oneTimeEvent = event
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,3 +141,7 @@ message UploadAttachmentToArchiveJobData {
|
||||||
message BackupMediaSnapshotSyncJobData {
|
message BackupMediaSnapshotSyncJobData {
|
||||||
uint64 syncTime = 1;
|
uint64 syncTime = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message DeviceNameChangeJobData {
|
||||||
|
uint32 deviceId = 1;
|
||||||
|
}
|
||||||
|
|
|
@ -244,6 +244,13 @@
|
||||||
app:exitAnim="@anim/fragment_open_exit"
|
app:exitAnim="@anim/fragment_open_exit"
|
||||||
app:popEnterAnim="@anim/fragment_close_enter"
|
app:popEnterAnim="@anim/fragment_close_enter"
|
||||||
app:popExitAnim="@anim/fragment_close_exit" />
|
app:popExitAnim="@anim/fragment_close_exit" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_linkDeviceFragment_to_editDeviceNameFragment"
|
||||||
|
app:destination="@id/editDeviceNameFragment"
|
||||||
|
app:enterAnim="@anim/fragment_open_enter"
|
||||||
|
app:exitAnim="@anim/fragment_open_exit"
|
||||||
|
app:popEnterAnim="@anim/fragment_close_enter"
|
||||||
|
app:popExitAnim="@anim/fragment_close_exit" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_linkDeviceFragment_to_linkDeviceFinishedSheet"
|
android:id="@+id/action_linkDeviceFragment_to_linkDeviceFinishedSheet"
|
||||||
app:destination="@id/linkDeviceFinishedSheet" />
|
app:destination="@id/linkDeviceFinishedSheet" />
|
||||||
|
@ -264,6 +271,10 @@
|
||||||
android:id="@+id/linkDeviceEducationSheet"
|
android:id="@+id/linkDeviceEducationSheet"
|
||||||
android:name="org.thoughtcrime.securesms.linkdevice.LinkDeviceEducationSheet" />
|
android:name="org.thoughtcrime.securesms.linkdevice.LinkDeviceEducationSheet" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/editDeviceNameFragment"
|
||||||
|
android:name="org.thoughtcrime.securesms.linkdevice.EditDeviceNameFragment" />
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/addLinkDeviceFragment"
|
android:id="@+id/addLinkDeviceFragment"
|
||||||
android:name="org.thoughtcrime.securesms.linkdevice.AddLinkDeviceFragment"
|
android:name="org.thoughtcrime.securesms.linkdevice.AddLinkDeviceFragment"
|
||||||
|
|
|
@ -1012,6 +1012,20 @@
|
||||||
<string name="LinkDeviceFragment__sync_failure_retry_button">Try linking again</string>
|
<string name="LinkDeviceFragment__sync_failure_retry_button">Try linking again</string>
|
||||||
<!-- Text button of a button in a dialog that, when pressed, will ignore syncing errors and link a new device without syncing message content -->
|
<!-- Text button of a button in a dialog that, when pressed, will ignore syncing errors and link a new device without syncing message content -->
|
||||||
<string name="LinkDeviceFragment__sync_failure_dismiss_button">Continue without transferring</string>
|
<string name="LinkDeviceFragment__sync_failure_dismiss_button">Continue without transferring</string>
|
||||||
|
<!-- Option in context menu to edit the name of a linked device -->
|
||||||
|
<string name="LinkDeviceFragment__edit_name">Edit name</string>
|
||||||
|
|
||||||
|
<!-- EditDeviceNameFragment -->
|
||||||
|
<!-- App bar title when editing the name of a device -->
|
||||||
|
<string name="EditDeviceNameFragment__edit">Edit device name</string>
|
||||||
|
<!-- Text hint shown when entering in a new device name -->
|
||||||
|
<string name="EditDeviceNameFragment__device_name">Device name</string>
|
||||||
|
<!-- Button to save name change -->
|
||||||
|
<string name="EditDeviceNameFragment__save">Save</string>
|
||||||
|
<!-- Toast message shown when a device name was successfully changed -->
|
||||||
|
<string name="EditDeviceNameFragment__device_name_updated">Device name updated</string>
|
||||||
|
<!-- Toast message shown when a device name could not be changed and to try again later -->
|
||||||
|
<string name="EditDeviceNameFragment__unable_to_change">Unable to change device name. Try again later.</string>
|
||||||
|
|
||||||
<!-- AddLinkDeviceFragment -->
|
<!-- AddLinkDeviceFragment -->
|
||||||
<!-- Description text shown on the QR code scanner when linking a device -->
|
<!-- Description text shown on the QR code scanner when linking a device -->
|
||||||
|
|
|
@ -757,6 +757,8 @@ public class SignalServiceMessageSender {
|
||||||
content = createCallLinkUpdateContent(message.getCallLinkUpdate().get());
|
content = createCallLinkUpdateContent(message.getCallLinkUpdate().get());
|
||||||
} else if (message.getCallLogEvent().isPresent()) {
|
} else if (message.getCallLogEvent().isPresent()) {
|
||||||
content = createCallLogEventContent(message.getCallLogEvent().get());
|
content = createCallLogEventContent(message.getCallLogEvent().get());
|
||||||
|
} else if (message.getDeviceNameChange().isPresent()) {
|
||||||
|
content = createDeviceNameChangeContent(message.getDeviceNameChange().get());
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("Unsupported sync message!");
|
throw new IOException("Unsupported sync message!");
|
||||||
}
|
}
|
||||||
|
@ -1729,6 +1731,13 @@ public class SignalServiceMessageSender {
|
||||||
return container.syncMessage(builder.build()).build();
|
return container.syncMessage(builder.build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Content createDeviceNameChangeContent(SyncMessage.DeviceNameChange proto) {
|
||||||
|
Content.Builder container = new Content.Builder();
|
||||||
|
SyncMessage.Builder builder = createSyncMessageBuilder().deviceNameChange(proto);
|
||||||
|
|
||||||
|
return container.syncMessage(builder.build()).build();
|
||||||
|
}
|
||||||
|
|
||||||
private SyncMessage.Builder createSyncMessageBuilder() {
|
private SyncMessage.Builder createSyncMessageBuilder() {
|
||||||
byte[] padding = Util.getRandomLengthSecretBytes(512);
|
byte[] padding = Util.getRandomLengthSecretBytes(512);
|
||||||
|
|
||||||
|
|
|
@ -131,4 +131,19 @@ class LinkDeviceApi(private val pushServiceSocket: PushServiceSocket) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name for a linked device
|
||||||
|
*
|
||||||
|
* PUT /v1/accounts/name
|
||||||
|
*
|
||||||
|
* - 204: Success.
|
||||||
|
* - 403: Not authorized to change the name of the device with the given ID
|
||||||
|
* - 404: No device found with the given ID
|
||||||
|
*/
|
||||||
|
fun setDeviceName(encryptedDeviceName: String, deviceId: Int): NetworkResult<Unit> {
|
||||||
|
return NetworkResult.fromFetch {
|
||||||
|
pushServiceSocket.setDeviceName(deviceId, SetDeviceNameRequest(encryptedDeviceName))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.whispersystems.signalservice.api.link
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request body for setting the name of a linked device.
|
||||||
|
*/
|
||||||
|
data class SetDeviceNameRequest(
|
||||||
|
@JsonProperty val deviceName: String
|
||||||
|
)
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
package org.whispersystems.signalservice.api.messages.multidevice;
|
package org.whispersystems.signalservice.api.messages.multidevice;
|
||||||
|
|
||||||
|
import org.whispersystems.signalservice.internal.push.SyncMessage.DeviceNameChange;
|
||||||
import org.whispersystems.signalservice.internal.push.SyncMessage.CallEvent;
|
import org.whispersystems.signalservice.internal.push.SyncMessage.CallEvent;
|
||||||
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLinkUpdate;
|
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLinkUpdate;
|
||||||
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLogEvent;
|
import org.whispersystems.signalservice.internal.push.SyncMessage.CallLogEvent;
|
||||||
|
@ -35,6 +36,7 @@ public class SignalServiceSyncMessage {
|
||||||
private final Optional<CallEvent> callEvent;
|
private final Optional<CallEvent> callEvent;
|
||||||
private final Optional<CallLinkUpdate> callLinkUpdate;
|
private final Optional<CallLinkUpdate> callLinkUpdate;
|
||||||
private final Optional<CallLogEvent> callLogEvent;
|
private final Optional<CallLogEvent> callLogEvent;
|
||||||
|
private final Optional<DeviceNameChange> deviceNameChange;
|
||||||
|
|
||||||
private SignalServiceSyncMessage(Optional<SentTranscriptMessage> sent,
|
private SignalServiceSyncMessage(Optional<SentTranscriptMessage> sent,
|
||||||
Optional<ContactsMessage> contacts,
|
Optional<ContactsMessage> contacts,
|
||||||
|
@ -52,7 +54,8 @@ public class SignalServiceSyncMessage {
|
||||||
Optional<List<ViewedMessage>> views,
|
Optional<List<ViewedMessage>> views,
|
||||||
Optional<CallEvent> callEvent,
|
Optional<CallEvent> callEvent,
|
||||||
Optional<CallLinkUpdate> callLinkUpdate,
|
Optional<CallLinkUpdate> callLinkUpdate,
|
||||||
Optional<CallLogEvent> callLogEvent)
|
Optional<CallLogEvent> callLogEvent,
|
||||||
|
Optional<DeviceNameChange> deviceNameChange)
|
||||||
{
|
{
|
||||||
this.sent = sent;
|
this.sent = sent;
|
||||||
this.contacts = contacts;
|
this.contacts = contacts;
|
||||||
|
@ -71,6 +74,7 @@ public class SignalServiceSyncMessage {
|
||||||
this.callEvent = callEvent;
|
this.callEvent = callEvent;
|
||||||
this.callLinkUpdate = callLinkUpdate;
|
this.callLinkUpdate = callLinkUpdate;
|
||||||
this.callLogEvent = callLogEvent;
|
this.callLogEvent = callLogEvent;
|
||||||
|
this.deviceNameChange = deviceNameChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SignalServiceSyncMessage forSentTranscript(SentTranscriptMessage sent) {
|
public static SignalServiceSyncMessage forSentTranscript(SentTranscriptMessage sent) {
|
||||||
|
@ -90,6 +94,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +115,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +136,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,6 +157,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +178,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.of(views),
|
Optional.of(views),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,6 +199,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +223,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,6 +244,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +265,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,6 +286,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,6 +307,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,6 +328,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,6 +349,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,6 +370,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,6 +391,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,6 +412,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.of(callEvent),
|
Optional.of(callEvent),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,6 +433,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.of(callLinkUpdate),
|
Optional.of(callLinkUpdate),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,7 +454,29 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.of(callLogEvent));
|
Optional.of(callLogEvent),
|
||||||
|
Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignalServiceSyncMessage forDeviceNameChange(@Nonnull DeviceNameChange deviceNameChange) {
|
||||||
|
return new SignalServiceSyncMessage(Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
|
Optional.of(deviceNameChange));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SignalServiceSyncMessage empty() {
|
public static SignalServiceSyncMessage empty() {
|
||||||
|
@ -453,6 +496,7 @@ public class SignalServiceSyncMessage {
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
Optional.empty(),
|
Optional.empty(),
|
||||||
|
Optional.empty(),
|
||||||
Optional.empty());
|
Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -524,6 +568,10 @@ public class SignalServiceSyncMessage {
|
||||||
return callLogEvent;
|
return callLogEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<DeviceNameChange> getDeviceNameChange() {
|
||||||
|
return deviceNameChange;
|
||||||
|
}
|
||||||
|
|
||||||
public enum FetchType {
|
public enum FetchType {
|
||||||
LOCAL_PROFILE,
|
LOCAL_PROFILE,
|
||||||
STORAGE_MANIFEST,
|
STORAGE_MANIFEST,
|
||||||
|
|
|
@ -66,6 +66,7 @@ import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.CredentialResponse;
|
import org.whispersystems.signalservice.api.groupsv2.CredentialResponse;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
||||||
import org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse;
|
import org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse;
|
||||||
|
import org.whispersystems.signalservice.api.link.SetDeviceNameRequest;
|
||||||
import org.whispersystems.signalservice.api.link.SetLinkedDeviceTransferArchiveRequest;
|
import org.whispersystems.signalservice.api.link.SetLinkedDeviceTransferArchiveRequest;
|
||||||
import org.whispersystems.signalservice.api.link.WaitForLinkedDeviceResponse;
|
import org.whispersystems.signalservice.api.link.WaitForLinkedDeviceResponse;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
|
||||||
|
@ -239,6 +240,7 @@ public class PushServiceSocket {
|
||||||
private static final String USERNAME_LINK_PATH = "/v1/accounts/username_link";
|
private static final String USERNAME_LINK_PATH = "/v1/accounts/username_link";
|
||||||
private static final String USERNAME_FROM_LINK_PATH = "/v1/accounts/username_link/%s";
|
private static final String USERNAME_FROM_LINK_PATH = "/v1/accounts/username_link/%s";
|
||||||
private static final String DELETE_ACCOUNT_PATH = "/v1/accounts/me";
|
private static final String DELETE_ACCOUNT_PATH = "/v1/accounts/me";
|
||||||
|
private static final String SET_DEVICE_NAME_PATH = "/v1/accounts/name?deviceId=%s";
|
||||||
private static final String CHANGE_NUMBER_PATH = "/v2/accounts/number";
|
private static final String CHANGE_NUMBER_PATH = "/v2/accounts/number";
|
||||||
private static final String IDENTIFIER_REGISTERED_PATH = "/v1/accounts/account/%s";
|
private static final String IDENTIFIER_REGISTERED_PATH = "/v1/accounts/account/%s";
|
||||||
private static final String REQUEST_ACCOUNT_DATA_PATH = "/v2/accounts/data_report";
|
private static final String REQUEST_ACCOUNT_DATA_PATH = "/v2/accounts/data_report";
|
||||||
|
@ -1380,6 +1382,11 @@ public class PushServiceSocket {
|
||||||
makeServiceRequest(DELETE_ACCOUNT_PATH, "DELETE", null);
|
makeServiceRequest(DELETE_ACCOUNT_PATH, "DELETE", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDeviceName(int deviceId, @Nonnull SetDeviceNameRequest request) throws IOException {
|
||||||
|
String body = JsonUtil.toJson(request);
|
||||||
|
makeServiceRequest(String.format(Locale.US, SET_DEVICE_NAME_PATH, deviceId), "PUT", body);
|
||||||
|
}
|
||||||
|
|
||||||
public void requestRateLimitPushChallenge() throws IOException {
|
public void requestRateLimitPushChallenge() throws IOException {
|
||||||
makeServiceRequest(REQUEST_RATE_LIMIT_PUSH_CHALLENGE, "POST", "");
|
makeServiceRequest(REQUEST_RATE_LIMIT_PUSH_CHALLENGE, "POST", "");
|
||||||
}
|
}
|
||||||
|
|
|
@ -707,6 +707,11 @@ message SyncMessage {
|
||||||
repeated AttachmentDelete attachmentDeletes = 4;
|
repeated AttachmentDelete attachmentDeletes = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message DeviceNameChange {
|
||||||
|
reserved /*name*/ 1;
|
||||||
|
optional uint32 deviceId = 2;
|
||||||
|
}
|
||||||
|
|
||||||
optional Sent sent = 1;
|
optional Sent sent = 1;
|
||||||
optional Contacts contacts = 2;
|
optional Contacts contacts = 2;
|
||||||
reserved /*groups*/ 3;
|
reserved /*groups*/ 3;
|
||||||
|
@ -729,6 +734,7 @@ message SyncMessage {
|
||||||
optional CallLinkUpdate callLinkUpdate = 20;
|
optional CallLinkUpdate callLinkUpdate = 20;
|
||||||
optional CallLogEvent callLogEvent = 21;
|
optional CallLogEvent callLogEvent = 21;
|
||||||
optional DeleteForMe deleteForMe = 22;
|
optional DeleteForMe deleteForMe = 22;
|
||||||
|
optional DeviceNameChange deviceNameChange = 23;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AttachmentPointer {
|
message AttachmentPointer {
|
||||||
|
|
Loading…
Add table
Reference in a new issue