]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java
209ca711719e69ac25e6377f4d65734e43588e8b
[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.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;
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.asamk.signal.manager.util.Utils.handleResponseException;
60 import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
61
62 public class AccountHelper {
63
64 private static final Logger logger = LoggerFactory.getLogger(AccountHelper.class);
65
66 private final Context context;
67 private final SignalAccount account;
68 private final SignalDependencies dependencies;
69
70 private Callable unregisteredListener;
71
72 public AccountHelper(final Context context) {
73 this.account = context.getAccount();
74 this.dependencies = context.getDependencies();
75 this.context = context;
76 }
77
78 public void setUnregisteredListener(final Callable unregisteredListener) {
79 this.unregisteredListener = unregisteredListener;
80 }
81
82 public void checkAccountState() throws IOException {
83 if (account.getLastReceiveTimestamp() == 0) {
84 logger.info("The Signal protocol expects that incoming messages are regularly received.");
85 } else {
86 var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
87 long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
88 if (days > 7) {
89 logger.warn(
90 "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
91 days);
92 }
93 }
94 try {
95 updateAccountAttributes();
96 if (account.getPreviousStorageVersion() < 9) {
97 context.getPreKeyHelper().forceRefreshPreKeys();
98 } else {
99 context.getPreKeyHelper().refreshPreKeysIfNecessary();
100 }
101 if (account.getAci() == null || account.getPni() == null) {
102 checkWhoAmiI();
103 }
104 if (!account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
105 context.getSyncHelper().requestSyncPniIdentity();
106 }
107 if (account.getPreviousStorageVersion() < 4
108 && account.isPrimaryDevice()
109 && account.getRegistrationLockPin() != null) {
110 migrateRegistrationPin();
111 }
112 if (account.getUsername() != null && account.getUsernameLink() == null) {
113 try {
114 tryToSetUsernameLink(new Username(account.getUsername()));
115 } catch (BaseUsernameException e) {
116 logger.debug("Invalid local username");
117 }
118 }
119 } catch (DeprecatedVersionException e) {
120 logger.debug("Signal-Server returned deprecated version exception", e);
121 throw e;
122 } catch (AuthorizationFailedException e) {
123 account.setRegistered(false);
124 throw e;
125 }
126 }
127
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())) {
134 return;
135 }
136
137 updateSelfIdentifiers(number, aci, pni);
138 }
139
140 private void updateSelfIdentifiers(final String number, final ACI aci, final PNI pni) {
141 account.setNumber(number);
142 account.setAci(aci);
143 account.setPni(pni);
144 if (account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
145 account.setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
146 }
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());
153 }
154
155 public void setPni(
156 final PNI updatedPni,
157 final IdentityKeyPair pniIdentityKeyPair,
158 final String number,
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);
166 }
167
168 public void startChangeNumber(
169 String newNumber,
170 boolean voiceVerification,
171 String captcha
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),
178 voiceVerification,
179 captcha);
180 NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification);
181 }
182
183 public void finishChangeNumber(
184 String newNumber,
185 String verificationCode,
186 String pin
187 ) throws IncorrectPinException, PinLockedException, IOException {
188 for (var attempts = 0; attempts < 5; attempts++) {
189 try {
190 finishChangeNumberInternal(newNumber, verificationCode, pin);
191 break;
192 } catch (MismatchedDevicesException e) {
193 logger.debug("Change number failed with mismatched devices, retrying.");
194 try {
195 dependencies.getMessageSender().handleChangeNumberMismatchDevices(e.getMismatchedDevices());
196 } catch (UntrustedIdentityException ex) {
197 throw new AssertionError(ex);
198 }
199 }
200 }
201 }
202
203 private void finishChangeNumberInternal(
204 String newNumber,
205 String verificationCode,
206 String pin
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>();
213
214 final var selfDeviceId = account.getDeviceId();
215 SyncMessage.PniChangeNumber selfChangeNumber = null;
216
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())
222 .stream()
223 .filter(deviceId -> accountDataStore.containsSession(new SignalProtocolAddress(aci.toString(),
224 deviceId)))
225 .toList();
226 deviceIds.addAll(subDeviceSessions);
227
228 final var messageSender = dependencies.getMessageSender();
229 for (final var deviceId : deviceIds) {
230 // Signed Prekey
231 final SignedPreKeyRecord signedPreKeyRecord;
232 try {
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);
241 }
242
243 // Last-resort kyber prekey
244 final KyberPreKeyRecord lastResortKyberPreKeyRecord;
245 try {
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);
254 }
255
256 // Registration Id
257 var pniRegistrationId = -1;
258 while (pniRegistrationId < 0 || pniRegistrationIds.containsValue(pniRegistrationId)) {
259 pniRegistrationId = KeyHelper.generateRegistrationId(false);
260 }
261 pniRegistrationIds.put(deviceId, pniRegistrationId);
262
263 // Device Message
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)
269 .newE164(newNumber)
270 .build();
271
272 if (deviceId == selfDeviceId) {
273 selfChangeNumber = pniChangeNumber;
274 } else {
275 try {
276 final var message = messageSender.getEncryptedSyncPniInitializeDeviceMessage(deviceId,
277 pniChangeNumber);
278 encryptedDeviceMessages.add(message);
279 } catch (UntrustedIdentityException | IOException | InvalidKeyException e) {
280 throw new RuntimeException(e);
281 }
282 }
283 }
284
285 final var sessionId = account.getSessionId(newNumber);
286 final var result = NumberVerificationUtils.verifyNumber(sessionId,
287 verificationCode,
288 pin,
289 context.getPinHelper(),
290 (sessionId1, verificationCode1, registrationLock) -> {
291 final var registrationApi = dependencies.getRegistrationApi();
292 try {
293 handleResponseException(registrationApi.verifyAccount(sessionId1, verificationCode1));
294 } catch (AlreadyVerifiedException e) {
295 // Already verified so can continue changing number
296 }
297 return handleResponseException(registrationApi.changeNumber(new ChangePhoneNumberRequest(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, org.asamk.signal.manager.api.DeviceLimitExceededException {
486 final var linkDeviceApi = dependencies.getLinkDeviceApi();
487 final LinkedDeviceVerificationCodeResponse verificationCode;
488 try {
489 verificationCode = handleResponseException(linkDeviceApi.getDeviceVerificationCode());
490 } catch (DeviceLimitExceededException e) {
491 throw new org.asamk.signal.manager.api.DeviceLimitExceededException("Too many linked devices", e);
492 }
493
494 handleResponseException(dependencies.getLinkDeviceApi()
495 .linkDevice(account.getNumber(),
496 account.getAci(),
497 account.getPni(),
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(),
507 null));
508 account.setMultiDevice(true);
509 context.getJobExecutor().enqueueJob(new SyncStorageJob());
510 }
511
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);
516 }
517
518 public void migrateRegistrationPin() throws IOException {
519 var masterKey = account.getOrCreatePinMasterKey();
520
521 context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey);
522 dependencies.getAccountManager().enableRegistrationLock(masterKey);
523 }
524
525 public void setRegistrationPin(String pin) throws IOException {
526 var masterKey = account.getOrCreatePinMasterKey();
527
528 context.getPinHelper().setRegistrationLockPin(pin, masterKey);
529 dependencies.getAccountManager().enableRegistrationLock(masterKey);
530
531 account.setRegistrationLockPin(pin);
532 updateAccountAttributes();
533 }
534
535 public void removeRegistrationPin() throws IOException {
536 // Remove KBS Pin
537 context.getPinHelper().removeRegistrationLockPin();
538 dependencies.getAccountManager().disableRegistrationLock();
539
540 account.setRegistrationLockPin(null);
541 }
542
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());
548
549 account.setRegistered(false);
550 unregisteredListener.call();
551 }
552
553 public void deleteAccount() throws IOException {
554 try {
555 context.getPinHelper().removeRegistrationLockPin();
556 } catch (IOException e) {
557 logger.warn("Failed to remove registration lock pin");
558 }
559 account.setRegistrationLockPin(null);
560
561 dependencies.getAccountManager().deleteAccount();
562
563 account.setRegistered(false);
564 unregisteredListener.call();
565 }
566
567 public interface Callable {
568
569 void call();
570 }
571 }