mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Remove plaintext device creation timestamp.
This commit is contained in:
committed by
Cody Henthorne
parent
9dcc704a9e
commit
eb7012b7ae
@@ -3,4 +3,4 @@ package org.thoughtcrime.securesms.linkdevice
|
||||
/**
|
||||
* Class that represents a linked device
|
||||
*/
|
||||
data class Device(val id: Int, val name: String?, val createdMillis: Long, val lastSeenMillis: Long)
|
||||
data class Device(val id: Int, val name: String?, val createdMillis: Long?, val lastSeenMillis: Long, val registrationId: Int)
|
||||
|
||||
@@ -149,7 +149,7 @@ private fun DeviceListScreenLinkingPreview() {
|
||||
Previews.Preview {
|
||||
EditNameScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
deviceToEdit = Device(1, "Laptop", 0, 0)
|
||||
deviceToEdit = Device(1, "Laptop", 0, 0, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -495,7 +495,7 @@ fun DeviceListScreen(
|
||||
@Composable
|
||||
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 linkedDate = DateUtils.getDayPrecisionTimeSpanString(LocalContext.current, Locale.getDefault(), device.createdMillis)
|
||||
val linkedDate = device.createdMillis?.let { DateUtils.getDayPrecisionTimeSpanString(LocalContext.current, Locale.getDefault(), device.createdMillis) }
|
||||
val lastActive = DateUtils.getDayPrecisionTimeSpanString(LocalContext.current, Locale.getDefault(), device.lastSeenMillis)
|
||||
val menuController = remember { DropdownMenus.MenuController() }
|
||||
Row(
|
||||
@@ -524,7 +524,9 @@ fun DeviceRow(device: Device, setDeviceToRemove: (Device) -> Unit, onEditDevice:
|
||||
.weight(1f)
|
||||
) {
|
||||
Text(text = titleString, style = MaterialTheme.typography.bodyLarge)
|
||||
Text(stringResource(R.string.DeviceListItem_linked_s, linkedDate), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
if (linkedDate != null) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -599,8 +601,8 @@ private fun DeviceListScreenPreview() {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
devices = listOf(
|
||||
Device(1, "Sam's Macbook Pro", 1715793982000, 1716053182000),
|
||||
Device(1, "Sam's iPad", 1715793182000, 1716053122000)
|
||||
Device(1, "Sam's Macbook Pro", 1715793982000, 1716053182000, 0),
|
||||
Device(1, "Sam's iPad", 1715793182000, 1716053122000, 0)
|
||||
),
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
@@ -653,7 +655,7 @@ private fun DeviceListScreenSyncingMessagesPreview() {
|
||||
Previews.Preview {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.SyncingMessages(1, 1),
|
||||
dialogState = DialogState.SyncingMessages(1),
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
@@ -681,7 +683,7 @@ private fun DeviceListScreenSyncingFailedPreview() {
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.SyncingFailed(
|
||||
deviceId = 1,
|
||||
deviceCreatedAt = 1,
|
||||
deviceRegistrationId = 1,
|
||||
syncFailType = LinkDeviceSettingsState.SyncFailType.NOT_RETRYABLE
|
||||
),
|
||||
seenQrEducationSheet = true
|
||||
@@ -724,7 +726,7 @@ private fun DeviceListScreenNotEnoughStoragePreview() {
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.SyncingFailed(
|
||||
deviceId = 1,
|
||||
deviceCreatedAt = 1,
|
||||
deviceRegistrationId = 1,
|
||||
syncFailType = LinkDeviceSettingsState.SyncFailType.NOT_ENOUGH_SPACE
|
||||
),
|
||||
seenQrEducationSheet = true
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.logging.logD
|
||||
import org.signal.core.util.logging.logI
|
||||
import org.signal.core.util.logging.logW
|
||||
import org.signal.core.util.toByteArray
|
||||
import org.signal.libsignal.protocol.InvalidKeyException
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey
|
||||
import org.thoughtcrime.securesms.backup.BackupFileIOError
|
||||
@@ -34,6 +35,7 @@ import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@@ -44,6 +46,7 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
object LinkDeviceRepository {
|
||||
|
||||
private val TAG = Log.tag(LinkDeviceRepository::class)
|
||||
private const val DECRYPTION_INFO = "deviceCreatedAt"
|
||||
|
||||
fun removeDevice(deviceId: Int): Boolean {
|
||||
return when (val result = AppDependencies.linkDeviceApi.removeDevice(deviceId)) {
|
||||
@@ -75,18 +78,20 @@ object LinkDeviceRepository {
|
||||
}
|
||||
}
|
||||
|
||||
fun WaitForLinkedDeviceResponse.getPlaintextDeviceName(): String {
|
||||
fun WaitForLinkedDeviceResponse.getPlaintextDevice(): Device {
|
||||
val response = this
|
||||
return DeviceInfo().apply {
|
||||
id = response.id
|
||||
name = response.name
|
||||
created = response.created
|
||||
lastSeen = response.lastSeen
|
||||
}.toDevice().name ?: ""
|
||||
registrationId = response.registrationId
|
||||
createdAtCiphertext = response.createdAtCiphertext
|
||||
}.toDevice()
|
||||
}
|
||||
|
||||
private fun DeviceInfo.toDevice(): Device {
|
||||
val defaultDevice = Device(getId(), getName(), getCreated(), getLastSeen())
|
||||
val createdAt = this.getPlaintextCreatedAt()
|
||||
val defaultDevice = Device(getId(), getName(), createdAt, getLastSeen(), getRegistrationId())
|
||||
try {
|
||||
if (getName().isNullOrEmpty() || getName().length < 4) {
|
||||
Log.w(TAG, "Invalid DeviceInfo name.")
|
||||
@@ -105,13 +110,28 @@ object LinkDeviceRepository {
|
||||
return defaultDevice
|
||||
}
|
||||
|
||||
return Device(getId(), String(plaintext), getCreated(), getLastSeen())
|
||||
return Device(getId(), String(plaintext), createdAt, getLastSeen(), getRegistrationId())
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed while reading the protobuf.", e)
|
||||
}
|
||||
return defaultDevice
|
||||
}
|
||||
|
||||
private fun DeviceInfo.getPlaintextCreatedAt(): Long? {
|
||||
return try {
|
||||
val associatedData = byteArrayOf(getId().toByte()) + getRegistrationId().toByteArray()
|
||||
val createdAtPlaintext = SignalStore.account.aciIdentityKey.privateKey.open(
|
||||
ciphertext = Base64.decode(getCreatedAtCiphertext().toByteArray()),
|
||||
info = DECRYPTION_INFO,
|
||||
associatedData = associatedData
|
||||
)
|
||||
ByteBuffer.wrap(createdAtPlaintext).getLong()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed while reading the protobuf.", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun isValidQr(uri: Uri): Boolean {
|
||||
if (!uri.isHierarchical) {
|
||||
return false
|
||||
@@ -254,7 +274,7 @@ object LinkDeviceRepository {
|
||||
/**
|
||||
* Performs the entire process of creating and uploading an archive for a newly-linked device.
|
||||
*/
|
||||
fun createAndUploadArchive(ephemeralMessageBackupKey: MessageBackupKey, deviceId: Int, deviceCreatedAt: Long, cancellationSignal: () -> Boolean): LinkUploadArchiveResult {
|
||||
fun createAndUploadArchive(ephemeralMessageBackupKey: MessageBackupKey, deviceId: Int, deviceRegistrationId: Int, cancellationSignal: () -> Boolean): LinkUploadArchiveResult {
|
||||
Log.d(TAG, "[createAndUploadArchive] Beginning process.")
|
||||
val stopwatch = Stopwatch("link-archive")
|
||||
val tempBackupFile = BlobProvider.getInstance().forNonAutoEncryptingSingleSessionOnDisk(AppDependencies.application)
|
||||
@@ -283,7 +303,7 @@ object LinkDeviceRepository {
|
||||
|
||||
if (cancellationSignal()) {
|
||||
Log.i(TAG, "[createAndUploadArchive] Backup was cancelled.")
|
||||
sendTransferArchiveError(deviceId, deviceCreatedAt, TransferArchiveError.RELINK_REQUESTED)
|
||||
sendTransferArchiveError(deviceId, deviceRegistrationId, TransferArchiveError.RELINK_REQUESTED)
|
||||
return LinkUploadArchiveResult.BackupCreationCancelled
|
||||
}
|
||||
|
||||
@@ -308,7 +328,7 @@ object LinkDeviceRepository {
|
||||
|
||||
if (cancellationSignal()) {
|
||||
Log.i(TAG, "[createAndUploadArchive] Backup was cancelled.")
|
||||
sendTransferArchiveError(deviceId, deviceCreatedAt, TransferArchiveError.RELINK_REQUESTED)
|
||||
sendTransferArchiveError(deviceId, deviceRegistrationId, TransferArchiveError.RELINK_REQUESTED)
|
||||
return LinkUploadArchiveResult.BackupCreationCancelled
|
||||
}
|
||||
|
||||
@@ -322,7 +342,7 @@ object LinkDeviceRepository {
|
||||
|
||||
if (cancellationSignal()) {
|
||||
Log.i(TAG, "[createAndUploadArchive] Backup was cancelled.")
|
||||
sendTransferArchiveError(deviceId, deviceCreatedAt, TransferArchiveError.RELINK_REQUESTED)
|
||||
sendTransferArchiveError(deviceId, deviceRegistrationId, TransferArchiveError.RELINK_REQUESTED)
|
||||
return LinkUploadArchiveResult.BackupCreationCancelled
|
||||
}
|
||||
|
||||
@@ -336,7 +356,7 @@ object LinkDeviceRepository {
|
||||
|
||||
if (cancellationSignal()) {
|
||||
Log.i(TAG, "[createAndUploadArchive] Backup was cancelled.")
|
||||
sendTransferArchiveError(deviceId, deviceCreatedAt, TransferArchiveError.RELINK_REQUESTED)
|
||||
sendTransferArchiveError(deviceId, deviceRegistrationId, TransferArchiveError.RELINK_REQUESTED)
|
||||
return LinkUploadArchiveResult.BackupCreationCancelled
|
||||
}
|
||||
|
||||
@@ -344,7 +364,7 @@ object LinkDeviceRepository {
|
||||
val transferSetResult = NetworkResult.withRetry {
|
||||
SignalNetwork.linkDevice.setTransferArchive(
|
||||
destinationDeviceId = deviceId,
|
||||
destinationDeviceCreated = deviceCreatedAt,
|
||||
destinationDeviceRegistrationId = deviceRegistrationId,
|
||||
cdn = uploadForm.cdn,
|
||||
cdnKey = uploadForm.key
|
||||
)
|
||||
@@ -402,10 +422,10 @@ object LinkDeviceRepository {
|
||||
/**
|
||||
* If [createAndUploadArchive] is cancelled or fails to upload an archive, alert the linked device of the failure and if the user will try again
|
||||
*/
|
||||
fun sendTransferArchiveError(deviceId: Int, deviceCreatedAt: Long, error: TransferArchiveError) {
|
||||
fun sendTransferArchiveError(deviceId: Int, deviceRegistrationId: Int, error: TransferArchiveError) {
|
||||
val archiveErrorResult = SignalNetwork.linkDevice.setTransferArchiveError(
|
||||
destinationDeviceId = deviceId,
|
||||
destinationDeviceCreated = deviceCreatedAt,
|
||||
destinationDeviceRegistrationId = deviceRegistrationId,
|
||||
error = error
|
||||
)
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ data class LinkDeviceSettingsState(
|
||||
data object None : DialogState
|
||||
data object Linking : DialogState
|
||||
data object Unlinking : DialogState
|
||||
data class SyncingMessages(val deviceId: Int, val deviceCreatedAt: Long) : DialogState
|
||||
data class SyncingMessages(val deviceId: Int) : DialogState
|
||||
data object SyncingTimedOut : DialogState
|
||||
data class SyncingFailed(val deviceId: Int, val deviceCreatedAt: Long, val syncFailType: SyncFailType) : DialogState
|
||||
data class SyncingFailed(val deviceId: Int, val deviceRegistrationId: Int, val syncFailType: SyncFailType) : DialogState
|
||||
data class DeviceUnlinked(val deviceCreatedAt: Long) : DialogState
|
||||
data object LoadingDebugLog : DialogState
|
||||
data object ContactSupport : DialogState
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.jobs.LinkedDeviceInactiveCheckJob
|
||||
import org.thoughtcrime.securesms.jobs.NewLinkedDeviceNotificationJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceRepository.LinkDeviceResult
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceRepository.getPlaintextDeviceName
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceRepository.getPlaintextDevice
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceSettingsState.DialogState
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceSettingsState.OneTimeEvent
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceSettingsState.QrCodeState
|
||||
@@ -300,12 +300,12 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
Log.d(TAG, "[addDeviceWithSync] Found a linked device! Creating notification job.")
|
||||
NewLinkedDeviceNotificationJob.enqueue(waitResult.id, waitResult.created)
|
||||
NewLinkedDeviceNotificationJob.enqueue(waitResult.id, waitResult.getPlaintextDevice().createdMillis ?: System.currentTimeMillis())
|
||||
|
||||
_state.update {
|
||||
it.copy(
|
||||
linkDeviceResult = result,
|
||||
dialogState = DialogState.SyncingMessages(waitResult.id, waitResult.created)
|
||||
dialogState = DialogState.SyncingMessages(waitResult.id)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
val uploadResult = LinkDeviceRepository.createAndUploadArchive(
|
||||
ephemeralMessageBackupKey = ephemeralMessageBackupKey,
|
||||
deviceId = waitResult.id,
|
||||
deviceCreatedAt = waitResult.created,
|
||||
deviceRegistrationId = waitResult.registrationId,
|
||||
cancellationSignal = { _state.value.shouldCancelArchiveUpload }
|
||||
)
|
||||
|
||||
@@ -323,7 +323,7 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
Log.i(TAG, "[addDeviceWithSync] Successfully uploaded archive.")
|
||||
_state.update {
|
||||
it.copy(
|
||||
oneTimeEvent = OneTimeEvent.ToastLinked(waitResult.getPlaintextDeviceName()),
|
||||
oneTimeEvent = OneTimeEvent.ToastLinked(waitResult.getPlaintextDevice().name ?: ""),
|
||||
dialogState = DialogState.None
|
||||
)
|
||||
}
|
||||
@@ -335,7 +335,7 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
it.copy(
|
||||
dialogState = DialogState.SyncingFailed(
|
||||
deviceId = waitResult.id,
|
||||
deviceCreatedAt = waitResult.created,
|
||||
deviceRegistrationId = waitResult.registrationId,
|
||||
syncFailType = LinkDeviceSettingsState.SyncFailType.NOT_ENOUGH_SPACE
|
||||
)
|
||||
)
|
||||
@@ -347,7 +347,7 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
it.copy(
|
||||
dialogState = DialogState.SyncingFailed(
|
||||
deviceId = waitResult.id,
|
||||
deviceCreatedAt = waitResult.created,
|
||||
deviceRegistrationId = waitResult.registrationId,
|
||||
syncFailType = LinkDeviceSettingsState.SyncFailType.NOT_RETRYABLE
|
||||
)
|
||||
)
|
||||
@@ -360,7 +360,7 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
it.copy(
|
||||
dialogState = DialogState.SyncingFailed(
|
||||
deviceId = waitResult.id,
|
||||
deviceCreatedAt = waitResult.created,
|
||||
deviceRegistrationId = waitResult.registrationId,
|
||||
syncFailType = LinkDeviceSettingsState.SyncFailType.RETRYABLE
|
||||
)
|
||||
)
|
||||
@@ -404,9 +404,10 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
Log.i(TAG, "No linked device found!")
|
||||
} else {
|
||||
Log.i(TAG, "Found a linked device! Creating notification job.")
|
||||
NewLinkedDeviceNotificationJob.enqueue(waitResult.id, waitResult.created)
|
||||
val device = waitResult.getPlaintextDevice()
|
||||
NewLinkedDeviceNotificationJob.enqueue(waitResult.id, device.createdMillis ?: System.currentTimeMillis())
|
||||
_state.update {
|
||||
it.copy(oneTimeEvent = OneTimeEvent.ToastLinked(waitResult.getPlaintextDeviceName()))
|
||||
it.copy(oneTimeEvent = OneTimeEvent.ToastLinked(device.name ?: ""))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +439,7 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
val dialogState = _state.value.dialogState
|
||||
if (dialogState is DialogState.SyncingFailed) {
|
||||
Log.i(TAG, "Alerting linked device of sync failure - will not retry")
|
||||
LinkDeviceRepository.sendTransferArchiveError(dialogState.deviceId, dialogState.deviceCreatedAt, TransferArchiveError.CONTINUE_WITHOUT_UPLOAD)
|
||||
LinkDeviceRepository.sendTransferArchiveError(dialogState.deviceId, dialogState.deviceRegistrationId, TransferArchiveError.CONTINUE_WITHOUT_UPLOAD)
|
||||
}
|
||||
loadDevices()
|
||||
|
||||
@@ -462,7 +463,7 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
val dialogState = _state.value.dialogState
|
||||
if (dialogState is DialogState.SyncingFailed) {
|
||||
Log.i(TAG, "Alerting linked device of sync failure - will retry")
|
||||
LinkDeviceRepository.sendTransferArchiveError(dialogState.deviceId, dialogState.deviceCreatedAt, TransferArchiveError.RELINK_REQUESTED)
|
||||
LinkDeviceRepository.sendTransferArchiveError(dialogState.deviceId, dialogState.deviceRegistrationId, TransferArchiveError.RELINK_REQUESTED)
|
||||
|
||||
Log.i(TAG, "Need to unlink device first...")
|
||||
val success = LinkDeviceRepository.removeDevice(dialogState.deviceId)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.core.util
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
/**
|
||||
* Converts the integer into [ByteArray].
|
||||
*/
|
||||
fun Int.toByteArray(): ByteArray {
|
||||
return ByteBuffer
|
||||
.allocate(Int.SIZE_BYTES)
|
||||
.putInt(this)
|
||||
.array()
|
||||
}
|
||||
@@ -166,10 +166,10 @@ class LinkDeviceApi(
|
||||
* - 422: Bad inputs.
|
||||
* - 429: Rate-limited.
|
||||
*/
|
||||
fun setTransferArchive(destinationDeviceId: Int, destinationDeviceCreated: Long, cdn: Int, cdnKey: String): NetworkResult<Unit> {
|
||||
fun setTransferArchive(destinationDeviceId: Int, destinationDeviceRegistrationId: Int, cdn: Int, cdnKey: String): NetworkResult<Unit> {
|
||||
val body = SetLinkedDeviceTransferArchiveRequest(
|
||||
destinationDeviceId = destinationDeviceId,
|
||||
destinationDeviceCreated = destinationDeviceCreated,
|
||||
destinationDeviceRegistrationId = destinationDeviceRegistrationId,
|
||||
transferArchive = SetLinkedDeviceTransferArchiveRequest.TransferArchive.CdnInfo(
|
||||
cdn = cdn,
|
||||
key = cdnKey
|
||||
@@ -189,10 +189,10 @@ class LinkDeviceApi(
|
||||
* - 422: Bad inputs.
|
||||
* - 429: Rate-limited.
|
||||
*/
|
||||
fun setTransferArchiveError(destinationDeviceId: Int, destinationDeviceCreated: Long, error: TransferArchiveError): NetworkResult<Unit> {
|
||||
fun setTransferArchiveError(destinationDeviceId: Int, destinationDeviceRegistrationId: Int, error: TransferArchiveError): NetworkResult<Unit> {
|
||||
val body = SetLinkedDeviceTransferArchiveRequest(
|
||||
destinationDeviceId = destinationDeviceId,
|
||||
destinationDeviceCreated = destinationDeviceCreated,
|
||||
destinationDeviceRegistrationId = destinationDeviceRegistrationId,
|
||||
transferArchive = SetLinkedDeviceTransferArchiveRequest.TransferArchive.Error(error)
|
||||
)
|
||||
val request = WebSocketRequestMessage.put("/v1/devices/transfer_archive", body)
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||
*/
|
||||
data class SetLinkedDeviceTransferArchiveRequest(
|
||||
@JsonProperty val destinationDeviceId: Int,
|
||||
@JsonProperty val destinationDeviceCreated: Long,
|
||||
@JsonProperty val destinationDeviceRegistrationId: Int,
|
||||
@JsonProperty val transferArchive: TransferArchive
|
||||
) {
|
||||
sealed class TransferArchive {
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||
data class WaitForLinkedDeviceResponse(
|
||||
@JsonProperty val id: Int,
|
||||
@JsonProperty val name: String,
|
||||
@JsonProperty val created: Long,
|
||||
@JsonProperty val lastSeen: Long
|
||||
@JsonProperty val lastSeen: Long,
|
||||
@JsonProperty val registrationId: Int,
|
||||
@JsonProperty val createdAtCiphertext: String?
|
||||
)
|
||||
|
||||
@@ -17,10 +17,13 @@ public class DeviceInfo {
|
||||
public String name;
|
||||
|
||||
@JsonProperty
|
||||
public long created;
|
||||
public long lastSeen;
|
||||
|
||||
@JsonProperty
|
||||
public long lastSeen;
|
||||
public int registrationId;
|
||||
|
||||
@JsonProperty
|
||||
public String createdAtCiphertext;
|
||||
|
||||
public DeviceInfo() {}
|
||||
|
||||
@@ -32,11 +35,15 @@ public class DeviceInfo {
|
||||
return name;
|
||||
}
|
||||
|
||||
public long getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public long getLastSeen() {
|
||||
return lastSeen;
|
||||
}
|
||||
|
||||
public int getRegistrationId() {
|
||||
return registrationId;
|
||||
}
|
||||
|
||||
public String getCreatedAtCiphertext() {
|
||||
return createdAtCiphertext;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user