1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.api
.CaptchaRequiredException
;
4 import org
.asamk
.signal
.manager
.api
.DeviceLinkUrl
;
5 import org
.asamk
.signal
.manager
.api
.IncorrectPinException
;
6 import org
.asamk
.signal
.manager
.api
.NonNormalizedPhoneNumberException
;
7 import org
.asamk
.signal
.manager
.api
.PinLockedException
;
8 import org
.asamk
.signal
.manager
.api
.RateLimitException
;
9 import org
.asamk
.signal
.manager
.api
.VerificationMethodNotAvailableException
;
10 import org
.asamk
.signal
.manager
.internal
.SignalDependencies
;
11 import org
.asamk
.signal
.manager
.jobs
.SyncStorageJob
;
12 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
13 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
14 import org
.asamk
.signal
.manager
.util
.NumberVerificationUtils
;
15 import org
.asamk
.signal
.manager
.util
.Utils
;
16 import org
.signal
.core
.util
.Base64
;
17 import org
.signal
.libsignal
.protocol
.IdentityKeyPair
;
18 import org
.signal
.libsignal
.protocol
.InvalidKeyException
;
19 import org
.signal
.libsignal
.protocol
.SignalProtocolAddress
;
20 import org
.signal
.libsignal
.protocol
.state
.KyberPreKeyRecord
;
21 import org
.signal
.libsignal
.protocol
.state
.SignedPreKeyRecord
;
22 import org
.signal
.libsignal
.protocol
.util
.KeyHelper
;
23 import org
.signal
.libsignal
.usernames
.BaseUsernameException
;
24 import org
.signal
.libsignal
.usernames
.Username
;
25 import org
.slf4j
.Logger
;
26 import org
.slf4j
.LoggerFactory
;
27 import org
.whispersystems
.signalservice
.api
.account
.ChangePhoneNumberRequest
;
28 import org
.whispersystems
.signalservice
.api
.crypto
.UntrustedIdentityException
;
29 import org
.whispersystems
.signalservice
.api
.link
.LinkedDeviceVerificationCodeResponse
;
30 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
.ACI
;
31 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
.PNI
;
32 import org
.whispersystems
.signalservice
.api
.push
.ServiceIdType
;
33 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
34 import org
.whispersystems
.signalservice
.api
.push
.SignedPreKeyEntity
;
35 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AlreadyVerifiedException
;
36 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
37 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.DeprecatedVersionException
;
38 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UsernameIsNotReservedException
;
39 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UsernameMalformedException
;
40 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UsernameTakenException
;
41 import org
.whispersystems
.signalservice
.api
.util
.DeviceNameUtil
;
42 import org
.whispersystems
.signalservice
.internal
.push
.DeviceLimitExceededException
;
43 import org
.whispersystems
.signalservice
.internal
.push
.KyberPreKeyEntity
;
44 import org
.whispersystems
.signalservice
.internal
.push
.OutgoingPushMessage
;
45 import org
.whispersystems
.signalservice
.internal
.push
.SyncMessage
;
46 import org
.whispersystems
.signalservice
.internal
.push
.exceptions
.MismatchedDevicesException
;
48 import java
.io
.IOException
;
49 import java
.util
.ArrayList
;
50 import java
.util
.HashMap
;
51 import java
.util
.List
;
52 import java
.util
.Objects
;
53 import java
.util
.Optional
;
54 import java
.util
.concurrent
.TimeUnit
;
56 import okio
.ByteString
;
58 import static org
.asamk
.signal
.manager
.config
.ServiceConfig
.PREKEY_MAXIMUM_ID
;
59 import static org
.asamk
.signal
.manager
.util
.Utils
.handleResponseException
;
60 import static org
.whispersystems
.signalservice
.internal
.util
.Util
.isEmpty
;
62 public class AccountHelper
{
64 private static final Logger logger
= LoggerFactory
.getLogger(AccountHelper
.class);
66 private final Context context
;
67 private final SignalAccount account
;
68 private final SignalDependencies dependencies
;
70 private Callable unregisteredListener
;
72 public AccountHelper(final Context context
) {
73 this.account
= context
.getAccount();
74 this.dependencies
= context
.getDependencies();
75 this.context
= context
;
78 public void setUnregisteredListener(final Callable unregisteredListener
) {
79 this.unregisteredListener
= unregisteredListener
;
82 public void checkAccountState() throws IOException
{
83 if (account
.getLastReceiveTimestamp() == 0) {
84 logger
.info("The Signal protocol expects that incoming messages are regularly received.");
86 var diffInMilliseconds
= System
.currentTimeMillis() - account
.getLastReceiveTimestamp();
87 long days
= TimeUnit
.DAYS
.convert(diffInMilliseconds
, TimeUnit
.MILLISECONDS
);
90 "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
95 updateAccountAttributes();
96 if (account
.getPreviousStorageVersion() < 9) {
97 context
.getPreKeyHelper().forceRefreshPreKeys();
99 context
.getPreKeyHelper().refreshPreKeysIfNecessary();
101 if (account
.getAci() == null || account
.getPni() == null) {
104 if (!account
.isPrimaryDevice() && account
.getPniIdentityKeyPair() == null) {
105 throw new IOException("Missing PNI identity key, relinking required");
107 if (account
.getPreviousStorageVersion() < 4
108 && account
.isPrimaryDevice()
109 && account
.getRegistrationLockPin() != null) {
110 migrateRegistrationPin();
112 if (account
.getUsername() != null && account
.getUsernameLink() == null) {
114 tryToSetUsernameLink(new Username(account
.getUsername()));
115 } catch (BaseUsernameException e
) {
116 logger
.debug("Invalid local username");
119 } catch (DeprecatedVersionException e
) {
120 logger
.debug("Signal-Server returned deprecated version exception", e
);
122 } catch (AuthorizationFailedException e
) {
123 account
.setRegistered(false);
128 public void checkWhoAmiI() throws IOException
{
129 final var whoAmI
= dependencies
.getAccountManager().getWhoAmI();
130 final var number
= whoAmI
.getNumber();
131 final var aci
= ACI
.parseOrThrow(whoAmI
.getAci());
132 final var pni
= PNI
.parseOrThrow(whoAmI
.getPni());
133 if (number
.equals(account
.getNumber()) && aci
.equals(account
.getAci()) && pni
.equals(account
.getPni())) {
137 updateSelfIdentifiers(number
, aci
, pni
);
140 private void updateSelfIdentifiers(final String number
, final ACI aci
, final PNI pni
) {
141 account
.setNumber(number
);
144 if (account
.isPrimaryDevice() && account
.getPniIdentityKeyPair() == null) {
145 account
.setPniIdentityKeyPair(KeyUtils
.generateIdentityKeyPair());
147 account
.getRecipientTrustedResolver().resolveSelfRecipientTrusted(account
.getSelfRecipientAddress());
148 context
.getUnidentifiedAccessHelper().rotateSenderCertificates();
149 dependencies
.resetAfterAddressChange();
150 context
.getGroupV2Helper().clearAuthCredentialCache();
151 context
.getAccountFileUpdater().updateAccountIdentifiers(account
.getNumber(), account
.getAci());
152 context
.getJobExecutor().enqueueJob(new SyncStorageJob());
156 final PNI updatedPni
,
157 final IdentityKeyPair pniIdentityKeyPair
,
159 final int localPniRegistrationId
,
160 final SignedPreKeyRecord pniSignedPreKey
,
161 final KyberPreKeyRecord lastResortKyberPreKey
162 ) throws IOException
{
163 updateSelfIdentifiers(number
!= null ? number
: account
.getNumber(), account
.getAci(), updatedPni
);
164 account
.setNewPniIdentity(pniIdentityKeyPair
, pniSignedPreKey
, lastResortKyberPreKey
, localPniRegistrationId
);
165 context
.getPreKeyHelper().refreshPreKeysIfNecessary(ServiceIdType
.PNI
);
168 public void startChangeNumber(
170 boolean voiceVerification
,
172 ) throws IOException
, CaptchaRequiredException
, NonNormalizedPhoneNumberException
, RateLimitException
, VerificationMethodNotAvailableException
{
173 final var accountManager
= dependencies
.createUnauthenticatedAccountManager(newNumber
, account
.getPassword());
174 final var registrationApi
= accountManager
.getRegistrationApi();
175 String sessionId
= NumberVerificationUtils
.handleVerificationSession(registrationApi
,
176 account
.getSessionId(newNumber
),
177 id
-> account
.setSessionId(newNumber
, id
),
180 NumberVerificationUtils
.requestVerificationCode(registrationApi
, sessionId
, voiceVerification
);
183 public void finishChangeNumber(
185 String verificationCode
,
187 ) throws IncorrectPinException
, PinLockedException
, IOException
{
188 for (var attempts
= 0; attempts
< 5; attempts
++) {
190 finishChangeNumberInternal(newNumber
, verificationCode
, pin
);
192 } catch (MismatchedDevicesException e
) {
193 logger
.debug("Change number failed with mismatched devices, retrying.");
195 dependencies
.getMessageSender().handleChangeNumberMismatchDevices(e
.getMismatchedDevices());
196 } catch (UntrustedIdentityException ex
) {
197 throw new AssertionError(ex
);
203 private void finishChangeNumberInternal(
205 String verificationCode
,
207 ) throws IncorrectPinException
, PinLockedException
, IOException
{
208 final var pniIdentity
= KeyUtils
.generateIdentityKeyPair();
209 final var encryptedDeviceMessages
= new ArrayList
<OutgoingPushMessage
>();
210 final var devicePniSignedPreKeys
= new HashMap
<Integer
, SignedPreKeyEntity
>();
211 final var devicePniLastResortKyberPreKeys
= new HashMap
<Integer
, KyberPreKeyEntity
>();
212 final var pniRegistrationIds
= new HashMap
<Integer
, Integer
>();
214 final var selfDeviceId
= account
.getDeviceId();
215 SyncMessage
.PniChangeNumber selfChangeNumber
= null;
217 final var deviceIds
= new ArrayList
<Integer
>();
218 deviceIds
.add(SignalServiceAddress
.DEFAULT_DEVICE_ID
);
219 final var aci
= account
.getAci();
220 final var accountDataStore
= account
.getSignalServiceDataStore().aci();
221 final var subDeviceSessions
= accountDataStore
.getSubDeviceSessions(aci
.toString())
223 .filter(deviceId
-> accountDataStore
.containsSession(new SignalProtocolAddress(aci
.toString(),
226 deviceIds
.addAll(subDeviceSessions
);
228 final var messageSender
= dependencies
.getMessageSender();
229 for (final var deviceId
: deviceIds
) {
231 final SignedPreKeyRecord signedPreKeyRecord
;
233 signedPreKeyRecord
= KeyUtils
.generateSignedPreKeyRecord(KeyUtils
.getRandomInt(PREKEY_MAXIMUM_ID
),
234 pniIdentity
.getPrivateKey());
235 final var signedPreKeyEntity
= new SignedPreKeyEntity(signedPreKeyRecord
.getId(),
236 signedPreKeyRecord
.getKeyPair().getPublicKey(),
237 signedPreKeyRecord
.getSignature());
238 devicePniSignedPreKeys
.put(deviceId
, signedPreKeyEntity
);
239 } catch (InvalidKeyException e
) {
240 throw new AssertionError("unexpected invalid key", e
);
243 // Last-resort kyber prekey
244 final KyberPreKeyRecord lastResortKyberPreKeyRecord
;
246 lastResortKyberPreKeyRecord
= KeyUtils
.generateKyberPreKeyRecord(KeyUtils
.getRandomInt(PREKEY_MAXIMUM_ID
),
247 pniIdentity
.getPrivateKey());
248 final var kyberPreKeyEntity
= new KyberPreKeyEntity(lastResortKyberPreKeyRecord
.getId(),
249 lastResortKyberPreKeyRecord
.getKeyPair().getPublicKey(),
250 lastResortKyberPreKeyRecord
.getSignature());
251 devicePniLastResortKyberPreKeys
.put(deviceId
, kyberPreKeyEntity
);
252 } catch (InvalidKeyException e
) {
253 throw new AssertionError("unexpected invalid key", e
);
257 var pniRegistrationId
= -1;
258 while (pniRegistrationId
< 0 || pniRegistrationIds
.containsValue(pniRegistrationId
)) {
259 pniRegistrationId
= KeyHelper
.generateRegistrationId(false);
261 pniRegistrationIds
.put(deviceId
, pniRegistrationId
);
264 final var pniChangeNumber
= new SyncMessage
.PniChangeNumber
.Builder().identityKeyPair(ByteString
.of(
265 pniIdentity
.serialize()))
266 .signedPreKey(ByteString
.of(signedPreKeyRecord
.serialize()))
267 .lastResortKyberPreKey(ByteString
.of(lastResortKyberPreKeyRecord
.serialize()))
268 .registrationId(pniRegistrationId
)
272 if (deviceId
== selfDeviceId
) {
273 selfChangeNumber
= pniChangeNumber
;
276 final var message
= messageSender
.getEncryptedSyncPniInitializeDeviceMessage(deviceId
,
278 encryptedDeviceMessages
.add(message
);
279 } catch (UntrustedIdentityException
| IOException
| InvalidKeyException e
) {
280 throw new RuntimeException(e
);
285 final var sessionId
= account
.getSessionId(newNumber
);
286 final var result
= NumberVerificationUtils
.verifyNumber(sessionId
,
289 context
.getPinHelper(),
290 (sessionId1
, verificationCode1
, registrationLock
) -> {
291 final var registrationApi
= dependencies
.getRegistrationApi();
293 handleResponseException(registrationApi
.verifyAccount(sessionId1
, verificationCode1
));
294 } catch (AlreadyVerifiedException e
) {
295 // Already verified so can continue changing number
297 return handleResponseException(registrationApi
.changeNumber(new ChangePhoneNumberRequest(sessionId1
,
301 pniIdentity
.getPublicKey(),
302 encryptedDeviceMessages
,
303 Utils
.mapKeys(devicePniSignedPreKeys
, Object
::toString
),
304 Utils
.mapKeys(devicePniLastResortKyberPreKeys
, Object
::toString
),
305 Utils
.mapKeys(pniRegistrationIds
, Object
::toString
))));
308 final var updatePni
= PNI
.parseOrThrow(result
.first().getPni());
309 if (updatePni
.equals(account
.getPni())) {
310 logger
.debug("PNI is unchanged after change number");
314 handlePniChangeNumberMessage(selfChangeNumber
, updatePni
);
317 public void handlePniChangeNumberMessage(final SyncMessage
.PniChangeNumber pniChangeNumber
, final PNI updatedPni
) {
318 if (pniChangeNumber
.identityKeyPair
!= null
319 && pniChangeNumber
.registrationId
!= null
320 && pniChangeNumber
.signedPreKey
!= null) {
321 logger
.debug("New PNI: {}", updatedPni
);
324 new IdentityKeyPair(pniChangeNumber
.identityKeyPair
.toByteArray()),
325 pniChangeNumber
.newE164
,
326 pniChangeNumber
.registrationId
,
327 new SignedPreKeyRecord(pniChangeNumber
.signedPreKey
.toByteArray()),
328 pniChangeNumber
.lastResortKyberPreKey
!= null
329 ?
new KyberPreKeyRecord(pniChangeNumber
.lastResortKyberPreKey
.toByteArray())
331 } catch (Exception e
) {
332 logger
.warn("Failed to handle change number message", e
);
337 public static final int USERNAME_MIN_LENGTH
= 3;
338 public static final int USERNAME_MAX_LENGTH
= 32;
340 public void reserveUsernameFromNickname(String nickname
) throws IOException
, BaseUsernameException
{
341 final var currentUsername
= account
.getUsername();
342 if (currentUsername
!= null) {
343 final var currentNickname
= currentUsername
.substring(0, currentUsername
.indexOf('.'));
344 if (currentNickname
.equals(nickname
)) {
346 refreshCurrentUsername();
348 } catch (IOException
| BaseUsernameException e
) {
349 logger
.warn("[reserveUsername] Failed to refresh current username, trying to claim new username");
354 final var candidates
= Username
.candidatesFrom(nickname
, USERNAME_MIN_LENGTH
, USERNAME_MAX_LENGTH
);
355 reserveUsername(candidates
);
358 public void reserveExactUsername(String username
) throws IOException
, BaseUsernameException
{
359 final var currentUsername
= account
.getUsername();
360 if (currentUsername
!= null) {
361 if (currentUsername
.equals(username
)) {
363 refreshCurrentUsername();
365 } catch (IOException
| BaseUsernameException e
) {
366 logger
.warn("[reserveUsername] Failed to refresh current username, trying to claim new username");
371 final var candidates
= List
.of(new Username(username
));
372 reserveUsername(candidates
);
375 private void reserveUsername(final List
<Username
> candidates
) throws IOException
{
376 final var candidateHashes
= new ArrayList
<String
>();
377 for (final var candidate
: candidates
) {
378 candidateHashes
.add(Base64
.encodeUrlSafeWithoutPadding(candidate
.getHash()));
381 final var response
= dependencies
.getAccountManager().reserveUsername(candidateHashes
);
382 final var hashIndex
= candidateHashes
.indexOf(response
.getUsernameHash());
383 if (hashIndex
== -1) {
384 logger
.warn("[reserveUsername] The response hash could not be found in our set of candidateHashes.");
385 throw new IOException("Unexpected username response");
388 logger
.debug("[reserveUsername] Successfully reserved username.");
389 final var username
= candidates
.get(hashIndex
);
391 final var linkComponents
= dependencies
.getAccountManager().confirmUsernameAndCreateNewLink(username
);
392 account
.setUsername(username
.getUsername());
393 account
.setUsernameLink(linkComponents
);
394 account
.getRecipientStore().resolveSelfRecipientTrusted(account
.getSelfRecipientAddress());
395 account
.getRecipientStore().rotateSelfStorageId();
396 logger
.debug("[confirmUsername] Successfully confirmed username.");
399 public void refreshCurrentUsername() throws IOException
, BaseUsernameException
{
400 final var localUsername
= account
.getUsername();
401 if (localUsername
== null) {
405 final var whoAmIResponse
= dependencies
.getAccountManager().getWhoAmI();
406 final var serverUsernameHash
= whoAmIResponse
.getUsernameHash();
407 final var hasServerUsername
= !isEmpty(serverUsernameHash
);
408 final var username
= new Username(localUsername
);
409 final var localUsernameHash
= Base64
.encodeUrlSafeWithoutPadding(username
.getHash());
411 if (!hasServerUsername
) {
412 logger
.debug("No remote username is set.");
415 if (!Objects
.equals(localUsernameHash
, serverUsernameHash
)) {
416 logger
.debug("Local username hash does not match server username hash.");
419 if (!hasServerUsername
|| !Objects
.equals(localUsernameHash
, serverUsernameHash
)) {
420 logger
.debug("Attempting to resynchronize username.");
422 tryReserveConfirmUsername(username
);
423 } catch (UsernameMalformedException
| UsernameTakenException
| UsernameIsNotReservedException e
) {
424 logger
.debug("[confirmUsername] Failed to reserve confirm username: {} ({})",
426 e
.getClass().getSimpleName());
427 account
.setUsername(null);
428 account
.setUsernameLink(null);
429 account
.getRecipientStore().rotateSelfStorageId();
433 logger
.debug("Username already set, not refreshing.");
437 private void tryReserveConfirmUsername(final Username username
) throws IOException
{
438 final var usernameLink
= account
.getUsernameLink();
440 if (usernameLink
== null) {
441 dependencies
.getAccountManager()
442 .reserveUsername(List
.of(Base64
.encodeUrlSafeWithoutPadding(username
.getHash())));
443 logger
.debug("[reserveUsername] Successfully reserved existing username.");
444 final var linkComponents
= dependencies
.getAccountManager().confirmUsernameAndCreateNewLink(username
);
445 account
.setUsernameLink(linkComponents
);
446 logger
.debug("[confirmUsername] Successfully confirmed existing username.");
448 final var linkComponents
= dependencies
.getAccountManager().reclaimUsernameAndLink(username
, usernameLink
);
449 account
.setUsernameLink(linkComponents
);
450 logger
.debug("[confirmUsername] Successfully reclaimed existing username and link.");
452 account
.getRecipientStore().rotateSelfStorageId();
455 private void tryToSetUsernameLink(Username username
) {
456 for (var i
= 1; i
< 4; i
++) {
458 final var linkComponents
= dependencies
.getAccountManager().createUsernameLink(username
);
459 account
.setUsernameLink(linkComponents
);
461 } catch (IOException e
) {
462 logger
.debug("[tryToSetUsernameLink] Failed with IOException on attempt {}/3", i
, e
);
467 public void deleteUsername() throws IOException
{
468 dependencies
.getAccountManager().deleteUsernameLink();
469 account
.setUsernameLink(null);
470 dependencies
.getAccountManager().deleteUsername();
471 account
.setUsername(null);
472 logger
.debug("[deleteUsername] Successfully deleted the username.");
475 public void setDeviceName(String deviceName
) {
476 final var privateKey
= account
.getAciIdentityKeyPair().getPrivateKey();
477 final var encryptedDeviceName
= DeviceNameUtil
.encryptDeviceName(deviceName
, privateKey
);
478 account
.setEncryptedDeviceName(encryptedDeviceName
);
481 public void updateAccountAttributes() throws IOException
{
482 dependencies
.getAccountManager().setAccountAttributes(account
.getAccountAttributes(null));
485 public void addDevice(DeviceLinkUrl deviceLinkInfo
) throws IOException
, org
.asamk
.signal
.manager
.api
.DeviceLimitExceededException
{
486 final var linkDeviceApi
= dependencies
.getLinkDeviceApi();
487 final LinkedDeviceVerificationCodeResponse verificationCode
;
489 verificationCode
= handleResponseException(linkDeviceApi
.getDeviceVerificationCode());
490 } catch (DeviceLimitExceededException e
) {
491 throw new org
.asamk
.signal
.manager
.api
.DeviceLimitExceededException("Too many linked devices", e
);
494 handleResponseException(dependencies
.getLinkDeviceApi()
495 .linkDevice(account
.getNumber(),
498 deviceLinkInfo
.deviceIdentifier(),
499 deviceLinkInfo
.deviceKey(),
500 account
.getAciIdentityKeyPair(),
501 account
.getPniIdentityKeyPair(),
502 account
.getProfileKey(),
503 account
.getOrCreatePinMasterKey(),
504 account
.getOrCreateMediaRootBackupKey(),
505 account
.getOrCreateAccountEntropyPool(),
506 verificationCode
.getVerificationCode(),
508 account
.setMultiDevice(true);
509 context
.getJobExecutor().enqueueJob(new SyncStorageJob());
512 public void removeLinkedDevices(int deviceId
) throws IOException
{
513 dependencies
.getAccountManager().removeDevice(deviceId
);
514 var devices
= dependencies
.getAccountManager().getDevices();
515 account
.setMultiDevice(devices
.size() > 1);
518 public void migrateRegistrationPin() throws IOException
{
519 var masterKey
= account
.getOrCreatePinMasterKey();
521 context
.getPinHelper().migrateRegistrationLockPin(account
.getRegistrationLockPin(), masterKey
);
522 dependencies
.getAccountManager().enableRegistrationLock(masterKey
);
525 public void setRegistrationPin(String pin
) throws IOException
{
526 var masterKey
= account
.getOrCreatePinMasterKey();
528 context
.getPinHelper().setRegistrationLockPin(pin
, masterKey
);
529 dependencies
.getAccountManager().enableRegistrationLock(masterKey
);
531 account
.setRegistrationLockPin(pin
);
532 updateAccountAttributes();
535 public void removeRegistrationPin() throws IOException
{
537 context
.getPinHelper().removeRegistrationLockPin();
538 dependencies
.getAccountManager().disableRegistrationLock();
540 account
.setRegistrationLockPin(null);
543 public void unregister() throws IOException
{
544 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
545 // If this is the primary device, other users can't send messages to this number anymore.
546 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
547 dependencies
.getAccountManager().setGcmId(Optional
.empty());
549 account
.setRegistered(false);
550 unregisteredListener
.call();
553 public void deleteAccount() throws IOException
{
555 context
.getPinHelper().removeRegistrationLockPin();
556 } catch (IOException e
) {
557 logger
.warn("Failed to remove registration lock pin");
559 account
.setRegistrationLockPin(null);
561 dependencies
.getAccountManager().deleteAccount();
563 account
.setRegistered(false);
564 unregisteredListener
.call();
567 public interface Callable
{