]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java
Reformat files
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / AccountHelper.java
1 package org.asamk.signal.manager.helper;
2
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.InvalidDeviceLinkException;
7 import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
8 import org.asamk.signal.manager.api.PinLockedException;
9 import org.asamk.signal.manager.api.RateLimitException;
10 import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
11 import org.asamk.signal.manager.internal.SignalDependencies;
12 import org.asamk.signal.manager.jobs.SyncStorageJob;
13 import org.asamk.signal.manager.storage.SignalAccount;
14 import org.asamk.signal.manager.util.KeyUtils;
15 import org.asamk.signal.manager.util.NumberVerificationUtils;
16 import org.asamk.signal.manager.util.Utils;
17 import org.signal.core.util.Base64;
18 import org.signal.libsignal.protocol.IdentityKeyPair;
19 import org.signal.libsignal.protocol.InvalidKeyException;
20 import org.signal.libsignal.protocol.SignalProtocolAddress;
21 import org.signal.libsignal.protocol.state.KyberPreKeyRecord;
22 import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
23 import org.signal.libsignal.protocol.util.KeyHelper;
24 import org.signal.libsignal.usernames.BaseUsernameException;
25 import org.signal.libsignal.usernames.Username;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28 import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
29 import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
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;
47
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;
55
56 import okio.ByteString;
57
58 import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_MAXIMUM_ID;
59 import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
60
61 public class AccountHelper {
62
63 private static final Logger logger = LoggerFactory.getLogger(AccountHelper.class);
64
65 private final Context context;
66 private final SignalAccount account;
67 private final SignalDependencies dependencies;
68
69 private Callable unregisteredListener;
70
71 public AccountHelper(final Context context) {
72 this.account = context.getAccount();
73 this.dependencies = context.getDependencies();
74 this.context = context;
75 }
76
77 public void setUnregisteredListener(final Callable unregisteredListener) {
78 this.unregisteredListener = unregisteredListener;
79 }
80
81 public void checkAccountState() throws IOException {
82 if (account.getLastReceiveTimestamp() == 0) {
83 logger.info("The Signal protocol expects that incoming messages are regularly received.");
84 } else {
85 var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
86 long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
87 if (days > 7) {
88 logger.warn(
89 "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
90 days);
91 }
92 }
93 try {
94 updateAccountAttributes();
95 if (account.getPreviousStorageVersion() < 9) {
96 context.getPreKeyHelper().forceRefreshPreKeys();
97 } else {
98 context.getPreKeyHelper().refreshPreKeysIfNecessary();
99 }
100 if (account.getAci() == null || account.getPni() == null) {
101 checkWhoAmiI();
102 }
103 if (!account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
104 context.getSyncHelper().requestSyncPniIdentity();
105 }
106 if (account.getPreviousStorageVersion() < 4
107 && account.isPrimaryDevice()
108 && account.getRegistrationLockPin() != null) {
109 migrateRegistrationPin();
110 }
111 if (account.getUsername() != null && account.getUsernameLink() == null) {
112 try {
113 tryToSetUsernameLink(new Username(account.getUsername()));
114 } catch (BaseUsernameException e) {
115 logger.debug("Invalid local username");
116 }
117 }
118 } catch (DeprecatedVersionException e) {
119 logger.debug("Signal-Server returned deprecated version exception", e);
120 throw e;
121 } catch (AuthorizationFailedException e) {
122 account.setRegistered(false);
123 throw e;
124 }
125 }
126
127 public void checkWhoAmiI() throws IOException {
128 final var whoAmI = dependencies.getAccountManager().getWhoAmI();
129 final var number = whoAmI.getNumber();
130 final var aci = ACI.parseOrThrow(whoAmI.getAci());
131 final var pni = PNI.parseOrThrow(whoAmI.getPni());
132 if (number.equals(account.getNumber()) && aci.equals(account.getAci()) && pni.equals(account.getPni())) {
133 return;
134 }
135
136 updateSelfIdentifiers(number, aci, pni);
137 }
138
139 private void updateSelfIdentifiers(final String number, final ACI aci, final PNI pni) {
140 account.setNumber(number);
141 account.setAci(aci);
142 account.setPni(pni);
143 if (account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
144 account.setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
145 }
146 account.getRecipientTrustedResolver().resolveSelfRecipientTrusted(account.getSelfRecipientAddress());
147 context.getUnidentifiedAccessHelper().rotateSenderCertificates();
148 dependencies.resetAfterAddressChange();
149 context.getGroupV2Helper().clearAuthCredentialCache();
150 context.getAccountFileUpdater().updateAccountIdentifiers(account.getNumber(), account.getAci());
151 context.getJobExecutor().enqueueJob(new SyncStorageJob());
152 }
153
154 public void setPni(
155 final PNI updatedPni,
156 final IdentityKeyPair pniIdentityKeyPair,
157 final String number,
158 final int localPniRegistrationId,
159 final SignedPreKeyRecord pniSignedPreKey,
160 final KyberPreKeyRecord lastResortKyberPreKey
161 ) throws IOException {
162 updateSelfIdentifiers(number != null ? number : account.getNumber(), account.getAci(), updatedPni);
163 account.setNewPniIdentity(pniIdentityKeyPair, pniSignedPreKey, lastResortKyberPreKey, localPniRegistrationId);
164 context.getPreKeyHelper().refreshPreKeysIfNecessary(ServiceIdType.PNI);
165 }
166
167 public void startChangeNumber(
168 String newNumber,
169 boolean voiceVerification,
170 String captcha
171 ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException {
172 final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword());
173 final var registrationApi = accountManager.getRegistrationApi();
174 String sessionId = NumberVerificationUtils.handleVerificationSession(registrationApi,
175 account.getSessionId(newNumber),
176 id -> account.setSessionId(newNumber, id),
177 voiceVerification,
178 captcha);
179 NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification);
180 }
181
182 public void finishChangeNumber(
183 String newNumber,
184 String verificationCode,
185 String pin
186 ) throws IncorrectPinException, PinLockedException, IOException {
187 for (var attempts = 0; attempts < 5; attempts++) {
188 try {
189 finishChangeNumberInternal(newNumber, verificationCode, pin);
190 break;
191 } catch (MismatchedDevicesException e) {
192 logger.debug("Change number failed with mismatched devices, retrying.");
193 try {
194 dependencies.getMessageSender().handleChangeNumberMismatchDevices(e.getMismatchedDevices());
195 } catch (UntrustedIdentityException ex) {
196 throw new AssertionError(ex);
197 }
198 }
199 }
200 }
201
202 private void finishChangeNumberInternal(
203 String newNumber,
204 String verificationCode,
205 String pin
206 ) throws IncorrectPinException, PinLockedException, IOException {
207 final var pniIdentity = KeyUtils.generateIdentityKeyPair();
208 final var encryptedDeviceMessages = new ArrayList<OutgoingPushMessage>();
209 final var devicePniSignedPreKeys = new HashMap<Integer, SignedPreKeyEntity>();
210 final var devicePniLastResortKyberPreKeys = new HashMap<Integer, KyberPreKeyEntity>();
211 final var pniRegistrationIds = new HashMap<Integer, Integer>();
212
213 final var selfDeviceId = account.getDeviceId();
214 SyncMessage.PniChangeNumber selfChangeNumber = null;
215
216 final var deviceIds = new ArrayList<Integer>();
217 deviceIds.add(SignalServiceAddress.DEFAULT_DEVICE_ID);
218 final var aci = account.getAci();
219 final var accountDataStore = account.getSignalServiceDataStore().aci();
220 final var subDeviceSessions = accountDataStore.getSubDeviceSessions(aci.toString())
221 .stream()
222 .filter(deviceId -> accountDataStore.containsSession(new SignalProtocolAddress(aci.toString(),
223 deviceId)))
224 .toList();
225 deviceIds.addAll(subDeviceSessions);
226
227 final var messageSender = dependencies.getMessageSender();
228 for (final var deviceId : deviceIds) {
229 // Signed Prekey
230 final SignedPreKeyRecord signedPreKeyRecord;
231 try {
232 signedPreKeyRecord = KeyUtils.generateSignedPreKeyRecord(KeyUtils.getRandomInt(PREKEY_MAXIMUM_ID),
233 pniIdentity.getPrivateKey());
234 final var signedPreKeyEntity = new SignedPreKeyEntity(signedPreKeyRecord.getId(),
235 signedPreKeyRecord.getKeyPair().getPublicKey(),
236 signedPreKeyRecord.getSignature());
237 devicePniSignedPreKeys.put(deviceId, signedPreKeyEntity);
238 } catch (InvalidKeyException e) {
239 throw new AssertionError("unexpected invalid key", e);
240 }
241
242 // Last-resort kyber prekey
243 final KyberPreKeyRecord lastResortKyberPreKeyRecord;
244 try {
245 lastResortKyberPreKeyRecord = KeyUtils.generateKyberPreKeyRecord(KeyUtils.getRandomInt(PREKEY_MAXIMUM_ID),
246 pniIdentity.getPrivateKey());
247 final var kyberPreKeyEntity = new KyberPreKeyEntity(lastResortKyberPreKeyRecord.getId(),
248 lastResortKyberPreKeyRecord.getKeyPair().getPublicKey(),
249 lastResortKyberPreKeyRecord.getSignature());
250 devicePniLastResortKyberPreKeys.put(deviceId, kyberPreKeyEntity);
251 } catch (InvalidKeyException e) {
252 throw new AssertionError("unexpected invalid key", e);
253 }
254
255 // Registration Id
256 var pniRegistrationId = -1;
257 while (pniRegistrationId < 0 || pniRegistrationIds.containsValue(pniRegistrationId)) {
258 pniRegistrationId = KeyHelper.generateRegistrationId(false);
259 }
260 pniRegistrationIds.put(deviceId, pniRegistrationId);
261
262 // Device Message
263 final var pniChangeNumber = new SyncMessage.PniChangeNumber.Builder().identityKeyPair(ByteString.of(
264 pniIdentity.serialize()))
265 .signedPreKey(ByteString.of(signedPreKeyRecord.serialize()))
266 .lastResortKyberPreKey(ByteString.of(lastResortKyberPreKeyRecord.serialize()))
267 .registrationId(pniRegistrationId)
268 .newE164(newNumber)
269 .build();
270
271 if (deviceId == selfDeviceId) {
272 selfChangeNumber = pniChangeNumber;
273 } else {
274 try {
275 final var message = messageSender.getEncryptedSyncPniInitializeDeviceMessage(deviceId,
276 pniChangeNumber);
277 encryptedDeviceMessages.add(message);
278 } catch (UntrustedIdentityException | IOException | InvalidKeyException e) {
279 throw new RuntimeException(e);
280 }
281 }
282 }
283
284 final var sessionId = account.getSessionId(newNumber);
285 final var result = NumberVerificationUtils.verifyNumber(sessionId,
286 verificationCode,
287 pin,
288 context.getPinHelper(),
289 (sessionId1, verificationCode1, registrationLock) -> {
290 final var registrationApi = dependencies.getRegistrationApi();
291 try {
292 Utils.handleResponseException(registrationApi.verifyAccount(sessionId1, verificationCode1));
293 } catch (AlreadyVerifiedException e) {
294 // Already verified so can continue changing number
295 }
296 return Utils.handleResponseException(registrationApi.changeNumber(new ChangePhoneNumberRequest(
297 sessionId1,
298 null,
299 newNumber,
300 registrationLock,
301 pniIdentity.getPublicKey(),
302 encryptedDeviceMessages,
303 Utils.mapKeys(devicePniSignedPreKeys, Object::toString),
304 Utils.mapKeys(devicePniLastResortKyberPreKeys, Object::toString),
305 Utils.mapKeys(pniRegistrationIds, Object::toString))));
306 });
307
308 final var updatePni = PNI.parseOrThrow(result.first().getPni());
309 if (updatePni.equals(account.getPni())) {
310 logger.debug("PNI is unchanged after change number");
311 return;
312 }
313
314 handlePniChangeNumberMessage(selfChangeNumber, updatePni);
315 }
316
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);
322 try {
323 setPni(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())
330 : null);
331 } catch (Exception e) {
332 logger.warn("Failed to handle change number message", e);
333 }
334 }
335 }
336
337 public static final int USERNAME_MIN_LENGTH = 3;
338 public static final int USERNAME_MAX_LENGTH = 32;
339
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)) {
345 try {
346 refreshCurrentUsername();
347 return;
348 } catch (IOException | BaseUsernameException e) {
349 logger.warn("[reserveUsername] Failed to refresh current username, trying to claim new username");
350 }
351 }
352 }
353
354 final var candidates = Username.candidatesFrom(nickname, USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH);
355 reserveUsername(candidates);
356 }
357
358 public void reserveExactUsername(String username) throws IOException, BaseUsernameException {
359 final var currentUsername = account.getUsername();
360 if (currentUsername != null) {
361 if (currentUsername.equals(username)) {
362 try {
363 refreshCurrentUsername();
364 return;
365 } catch (IOException | BaseUsernameException e) {
366 logger.warn("[reserveUsername] Failed to refresh current username, trying to claim new username");
367 }
368 }
369 }
370
371 final var candidates = List.of(new Username(username));
372 reserveUsername(candidates);
373 }
374
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()));
379 }
380
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");
386 }
387
388 logger.debug("[reserveUsername] Successfully reserved username.");
389 final var username = candidates.get(hashIndex);
390
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.");
397 }
398
399 public void refreshCurrentUsername() throws IOException, BaseUsernameException {
400 final var localUsername = account.getUsername();
401 if (localUsername == null) {
402 return;
403 }
404
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());
410
411 if (!hasServerUsername) {
412 logger.debug("No remote username is set.");
413 }
414
415 if (!Objects.equals(localUsernameHash, serverUsernameHash)) {
416 logger.debug("Local username hash does not match server username hash.");
417 }
418
419 if (!hasServerUsername || !Objects.equals(localUsernameHash, serverUsernameHash)) {
420 logger.debug("Attempting to resynchronize username.");
421 try {
422 tryReserveConfirmUsername(username);
423 } catch (UsernameMalformedException | UsernameTakenException | UsernameIsNotReservedException e) {
424 logger.debug("[confirmUsername] Failed to reserve confirm username: {} ({})",
425 e.getMessage(),
426 e.getClass().getSimpleName());
427 account.setUsername(null);
428 account.setUsernameLink(null);
429 account.getRecipientStore().rotateSelfStorageId();
430 throw e;
431 }
432 } else {
433 logger.debug("Username already set, not refreshing.");
434 }
435 }
436
437 private void tryReserveConfirmUsername(final Username username) throws IOException {
438 final var usernameLink = account.getUsernameLink();
439
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.");
447 } else {
448 final var linkComponents = dependencies.getAccountManager().reclaimUsernameAndLink(username, usernameLink);
449 account.setUsernameLink(linkComponents);
450 logger.debug("[confirmUsername] Successfully reclaimed existing username and link.");
451 }
452 account.getRecipientStore().rotateSelfStorageId();
453 }
454
455 private void tryToSetUsernameLink(Username username) {
456 for (var i = 1; i < 4; i++) {
457 try {
458 final var linkComponents = dependencies.getAccountManager().createUsernameLink(username);
459 account.setUsernameLink(linkComponents);
460 break;
461 } catch (IOException e) {
462 logger.debug("[tryToSetUsernameLink] Failed with IOException on attempt {}/3", i, e);
463 }
464 }
465 }
466
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.");
473 }
474
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);
479 }
480
481 public void updateAccountAttributes() throws IOException {
482 dependencies.getAccountManager().setAccountAttributes(account.getAccountAttributes(null));
483 }
484
485 public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, InvalidDeviceLinkException, org.asamk.signal.manager.api.DeviceLimitExceededException {
486 String verificationCode;
487 try {
488 verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode();
489 } catch (DeviceLimitExceededException e) {
490 throw new org.asamk.signal.manager.api.DeviceLimitExceededException("Too many linked devices", e);
491 }
492
493 try {
494 dependencies.getAccountManager()
495 .addDevice(deviceLinkInfo.deviceIdentifier(),
496 deviceLinkInfo.deviceKey(),
497 account.getAciIdentityKeyPair(),
498 account.getPniIdentityKeyPair(),
499 account.getProfileKey(),
500 account.getOrCreatePinMasterKey(),
501 verificationCode);
502 } catch (InvalidKeyException e) {
503 throw new InvalidDeviceLinkException("Invalid device link", e);
504 }
505 account.setMultiDevice(true);
506 context.getJobExecutor().enqueueJob(new SyncStorageJob());
507 }
508
509 public void removeLinkedDevices(int deviceId) throws IOException {
510 dependencies.getAccountManager().removeDevice(deviceId);
511 var devices = dependencies.getAccountManager().getDevices();
512 account.setMultiDevice(devices.size() > 1);
513 }
514
515 public void migrateRegistrationPin() throws IOException {
516 var masterKey = account.getOrCreatePinMasterKey();
517
518 context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey);
519 dependencies.getAccountManager().enableRegistrationLock(masterKey);
520 }
521
522 public void setRegistrationPin(String pin) throws IOException {
523 var masterKey = account.getOrCreatePinMasterKey();
524
525 context.getPinHelper().setRegistrationLockPin(pin, masterKey);
526 dependencies.getAccountManager().enableRegistrationLock(masterKey);
527
528 account.setRegistrationLockPin(pin);
529 updateAccountAttributes();
530 }
531
532 public void removeRegistrationPin() throws IOException {
533 // Remove KBS Pin
534 context.getPinHelper().removeRegistrationLockPin();
535 dependencies.getAccountManager().disableRegistrationLock();
536
537 account.setRegistrationLockPin(null);
538 }
539
540 public void unregister() throws IOException {
541 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
542 // If this is the primary device, other users can't send messages to this number anymore.
543 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
544 dependencies.getAccountManager().setGcmId(Optional.empty());
545
546 account.setRegistered(false);
547 unregisteredListener.call();
548 }
549
550 public void deleteAccount() throws IOException {
551 try {
552 context.getPinHelper().removeRegistrationLockPin();
553 } catch (IOException e) {
554 logger.warn("Failed to remove registration lock pin");
555 }
556 account.setRegistrationLockPin(null);
557
558 dependencies.getAccountManager().deleteAccount();
559
560 account.setRegistered(false);
561 unregisteredListener.call();
562 }
563
564 public interface Callable {
565
566 void call();
567 }
568 }