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
.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
;
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
.whispersystems
.signalservice
.internal
.util
.Util
.isEmpty
;
61 public class AccountHelper
{
63 private static final Logger logger
= LoggerFactory
.getLogger(AccountHelper
.class);
65 private final Context context
;
66 private final SignalAccount account
;
67 private final SignalDependencies dependencies
;
69 private Callable unregisteredListener
;
71 public AccountHelper(final Context context
) {
72 this.account
= context
.getAccount();
73 this.dependencies
= context
.getDependencies();
74 this.context
= context
;
77 public void setUnregisteredListener(final Callable unregisteredListener
) {
78 this.unregisteredListener
= unregisteredListener
;
81 public void checkAccountState() throws IOException
{
82 if (account
.getLastReceiveTimestamp() == 0) {
83 logger
.info("The Signal protocol expects that incoming messages are regularly received.");
85 var diffInMilliseconds
= System
.currentTimeMillis() - account
.getLastReceiveTimestamp();
86 long days
= TimeUnit
.DAYS
.convert(diffInMilliseconds
, TimeUnit
.MILLISECONDS
);
89 "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
94 updateAccountAttributes();
95 if (account
.getPreviousStorageVersion() < 9) {
96 context
.getPreKeyHelper().forceRefreshPreKeys();
98 context
.getPreKeyHelper().refreshPreKeysIfNecessary();
100 if (account
.getAci() == null || account
.getPni() == null) {
103 if (!account
.isPrimaryDevice() && account
.getPniIdentityKeyPair() == null) {
104 context
.getSyncHelper().requestSyncPniIdentity();
106 if (account
.getPreviousStorageVersion() < 4
107 && account
.isPrimaryDevice()
108 && account
.getRegistrationLockPin() != null) {
109 migrateRegistrationPin();
111 if (account
.getUsername() != null && account
.getUsernameLink() == null) {
113 tryToSetUsernameLink(new Username(account
.getUsername()));
114 } catch (BaseUsernameException e
) {
115 logger
.debug("Invalid local username");
118 } catch (DeprecatedVersionException e
) {
119 logger
.debug("Signal-Server returned deprecated version exception", e
);
121 } catch (AuthorizationFailedException e
) {
122 account
.setRegistered(false);
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())) {
136 updateSelfIdentifiers(number
, aci
, pni
);
139 private void updateSelfIdentifiers(final String number
, final ACI aci
, final PNI pni
) {
140 account
.setNumber(number
);
143 if (account
.isPrimaryDevice() && account
.getPniIdentityKeyPair() == null) {
144 account
.setPniIdentityKeyPair(KeyUtils
.generateIdentityKeyPair());
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());
155 final PNI updatedPni
,
156 final IdentityKeyPair pniIdentityKeyPair
,
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
);
167 public void startChangeNumber(
168 String newNumber
, boolean voiceVerification
, String captcha
169 ) throws IOException
, CaptchaRequiredException
, NonNormalizedPhoneNumberException
, RateLimitException
, VerificationMethodNotAvailableException
{
170 final var accountManager
= dependencies
.createUnauthenticatedAccountManager(newNumber
, account
.getPassword());
171 final var registrationApi
= accountManager
.getRegistrationApi();
172 String sessionId
= NumberVerificationUtils
.handleVerificationSession(registrationApi
,
173 account
.getSessionId(newNumber
),
174 id
-> account
.setSessionId(newNumber
, id
),
177 NumberVerificationUtils
.requestVerificationCode(registrationApi
, sessionId
, voiceVerification
);
180 public void finishChangeNumber(
181 String newNumber
, String verificationCode
, String pin
182 ) throws IncorrectPinException
, PinLockedException
, IOException
{
183 for (var attempts
= 0; attempts
< 5; attempts
++) {
185 finishChangeNumberInternal(newNumber
, verificationCode
, pin
);
187 } catch (MismatchedDevicesException e
) {
188 logger
.debug("Change number failed with mismatched devices, retrying.");
190 dependencies
.getMessageSender().handleChangeNumberMismatchDevices(e
.getMismatchedDevices());
191 } catch (UntrustedIdentityException ex
) {
192 throw new AssertionError(ex
);
198 private void finishChangeNumberInternal(
199 String newNumber
, String verificationCode
, String pin
200 ) throws IncorrectPinException
, PinLockedException
, IOException
{
201 final var pniIdentity
= KeyUtils
.generateIdentityKeyPair();
202 final var encryptedDeviceMessages
= new ArrayList
<OutgoingPushMessage
>();
203 final var devicePniSignedPreKeys
= new HashMap
<Integer
, SignedPreKeyEntity
>();
204 final var devicePniLastResortKyberPreKeys
= new HashMap
<Integer
, KyberPreKeyEntity
>();
205 final var pniRegistrationIds
= new HashMap
<Integer
, Integer
>();
207 final var selfDeviceId
= account
.getDeviceId();
208 SyncMessage
.PniChangeNumber selfChangeNumber
= null;
210 final var deviceIds
= new ArrayList
<Integer
>();
211 deviceIds
.add(SignalServiceAddress
.DEFAULT_DEVICE_ID
);
212 final var aci
= account
.getAci();
213 final var accountDataStore
= account
.getSignalServiceDataStore().aci();
214 final var subDeviceSessions
= accountDataStore
.getSubDeviceSessions(aci
.toString())
216 .filter(deviceId
-> accountDataStore
.containsSession(new SignalProtocolAddress(aci
.toString(),
219 deviceIds
.addAll(subDeviceSessions
);
221 final var messageSender
= dependencies
.getMessageSender();
222 for (final var deviceId
: deviceIds
) {
224 final SignedPreKeyRecord signedPreKeyRecord
;
226 signedPreKeyRecord
= KeyUtils
.generateSignedPreKeyRecord(KeyUtils
.getRandomInt(PREKEY_MAXIMUM_ID
),
227 pniIdentity
.getPrivateKey());
228 final var signedPreKeyEntity
= new SignedPreKeyEntity(signedPreKeyRecord
.getId(),
229 signedPreKeyRecord
.getKeyPair().getPublicKey(),
230 signedPreKeyRecord
.getSignature());
231 devicePniSignedPreKeys
.put(deviceId
, signedPreKeyEntity
);
232 } catch (InvalidKeyException e
) {
233 throw new AssertionError("unexpected invalid key", e
);
236 // Last-resort kyber prekey
237 final KyberPreKeyRecord lastResortKyberPreKeyRecord
;
239 lastResortKyberPreKeyRecord
= KeyUtils
.generateKyberPreKeyRecord(KeyUtils
.getRandomInt(PREKEY_MAXIMUM_ID
),
240 pniIdentity
.getPrivateKey());
241 final var kyberPreKeyEntity
= new KyberPreKeyEntity(lastResortKyberPreKeyRecord
.getId(),
242 lastResortKyberPreKeyRecord
.getKeyPair().getPublicKey(),
243 lastResortKyberPreKeyRecord
.getSignature());
244 devicePniLastResortKyberPreKeys
.put(deviceId
, kyberPreKeyEntity
);
245 } catch (InvalidKeyException e
) {
246 throw new AssertionError("unexpected invalid key", e
);
250 var pniRegistrationId
= -1;
251 while (pniRegistrationId
< 0 || pniRegistrationIds
.containsValue(pniRegistrationId
)) {
252 pniRegistrationId
= KeyHelper
.generateRegistrationId(false);
254 pniRegistrationIds
.put(deviceId
, pniRegistrationId
);
257 final var pniChangeNumber
= new SyncMessage
.PniChangeNumber
.Builder().identityKeyPair(ByteString
.of(
258 pniIdentity
.serialize()))
259 .signedPreKey(ByteString
.of(signedPreKeyRecord
.serialize()))
260 .lastResortKyberPreKey(ByteString
.of(lastResortKyberPreKeyRecord
.serialize()))
261 .registrationId(pniRegistrationId
)
265 if (deviceId
== selfDeviceId
) {
266 selfChangeNumber
= pniChangeNumber
;
269 final var message
= messageSender
.getEncryptedSyncPniInitializeDeviceMessage(deviceId
,
271 encryptedDeviceMessages
.add(message
);
272 } catch (UntrustedIdentityException
| IOException
| InvalidKeyException e
) {
273 throw new RuntimeException(e
);
278 final var sessionId
= account
.getSessionId(newNumber
);
279 final var result
= NumberVerificationUtils
.verifyNumber(sessionId
,
282 context
.getPinHelper(),
283 (sessionId1
, verificationCode1
, registrationLock
) -> {
284 final var registrationApi
= dependencies
.getRegistrationApi();
286 Utils
.handleResponseException(registrationApi
.verifyAccount(sessionId1
, verificationCode1
));
287 } catch (AlreadyVerifiedException e
) {
288 // Already verified so can continue changing number
290 return Utils
.handleResponseException(registrationApi
.changeNumber(new ChangePhoneNumberRequest(
295 pniIdentity
.getPublicKey(),
296 encryptedDeviceMessages
,
297 Utils
.mapKeys(devicePniSignedPreKeys
, Object
::toString
),
298 Utils
.mapKeys(devicePniLastResortKyberPreKeys
, Object
::toString
),
299 Utils
.mapKeys(pniRegistrationIds
, Object
::toString
))));
302 final var updatePni
= PNI
.parseOrThrow(result
.first().getPni());
303 if (updatePni
.equals(account
.getPni())) {
304 logger
.debug("PNI is unchanged after change number");
308 handlePniChangeNumberMessage(selfChangeNumber
, updatePni
);
311 public void handlePniChangeNumberMessage(
312 final SyncMessage
.PniChangeNumber pniChangeNumber
, final PNI updatedPni
314 if (pniChangeNumber
.identityKeyPair
!= null
315 && pniChangeNumber
.registrationId
!= null
316 && pniChangeNumber
.signedPreKey
!= null) {
317 logger
.debug("New PNI: {}", updatedPni
);
320 new IdentityKeyPair(pniChangeNumber
.identityKeyPair
.toByteArray()),
321 pniChangeNumber
.newE164
,
322 pniChangeNumber
.registrationId
,
323 new SignedPreKeyRecord(pniChangeNumber
.signedPreKey
.toByteArray()),
324 pniChangeNumber
.lastResortKyberPreKey
!= null
325 ?
new KyberPreKeyRecord(pniChangeNumber
.lastResortKyberPreKey
.toByteArray())
327 } catch (Exception e
) {
328 logger
.warn("Failed to handle change number message", e
);
333 public static final int USERNAME_MIN_LENGTH
= 3;
334 public static final int USERNAME_MAX_LENGTH
= 32;
336 public void reserveUsernameFromNickname(String nickname
) throws IOException
, BaseUsernameException
{
337 final var currentUsername
= account
.getUsername();
338 if (currentUsername
!= null) {
339 final var currentNickname
= currentUsername
.substring(0, currentUsername
.indexOf('.'));
340 if (currentNickname
.equals(nickname
)) {
342 refreshCurrentUsername();
344 } catch (IOException
| BaseUsernameException e
) {
345 logger
.warn("[reserveUsername] Failed to refresh current username, trying to claim new username");
350 final var candidates
= Username
.candidatesFrom(nickname
, USERNAME_MIN_LENGTH
, USERNAME_MAX_LENGTH
);
351 reserveUsername(candidates
);
354 public void reserveExactUsername(String username
) throws IOException
, BaseUsernameException
{
355 final var currentUsername
= account
.getUsername();
356 if (currentUsername
!= null) {
357 if (currentUsername
.equals(username
)) {
359 refreshCurrentUsername();
361 } catch (IOException
| BaseUsernameException e
) {
362 logger
.warn("[reserveUsername] Failed to refresh current username, trying to claim new username");
367 final var candidates
= List
.of(new Username(username
));
368 reserveUsername(candidates
);
371 private void reserveUsername(final List
<Username
> candidates
) throws IOException
{
372 final var candidateHashes
= new ArrayList
<String
>();
373 for (final var candidate
: candidates
) {
374 candidateHashes
.add(Base64
.encodeUrlSafeWithoutPadding(candidate
.getHash()));
377 final var response
= dependencies
.getAccountManager().reserveUsername(candidateHashes
);
378 final var hashIndex
= candidateHashes
.indexOf(response
.getUsernameHash());
379 if (hashIndex
== -1) {
380 logger
.warn("[reserveUsername] The response hash could not be found in our set of candidateHashes.");
381 throw new IOException("Unexpected username response");
384 logger
.debug("[reserveUsername] Successfully reserved username.");
385 final var username
= candidates
.get(hashIndex
);
387 final var linkComponents
= dependencies
.getAccountManager().confirmUsernameAndCreateNewLink(username
);
388 account
.setUsername(username
.getUsername());
389 account
.setUsernameLink(linkComponents
);
390 account
.getRecipientStore().resolveSelfRecipientTrusted(account
.getSelfRecipientAddress());
391 account
.getRecipientStore().rotateSelfStorageId();
392 logger
.debug("[confirmUsername] Successfully confirmed username.");
395 public void refreshCurrentUsername() throws IOException
, BaseUsernameException
{
396 final var localUsername
= account
.getUsername();
397 if (localUsername
== null) {
401 final var whoAmIResponse
= dependencies
.getAccountManager().getWhoAmI();
402 final var serverUsernameHash
= whoAmIResponse
.getUsernameHash();
403 final var hasServerUsername
= !isEmpty(serverUsernameHash
);
404 final var username
= new Username(localUsername
);
405 final var localUsernameHash
= Base64
.encodeUrlSafeWithoutPadding(username
.getHash());
407 if (!hasServerUsername
) {
408 logger
.debug("No remote username is set.");
411 if (!Objects
.equals(localUsernameHash
, serverUsernameHash
)) {
412 logger
.debug("Local username hash does not match server username hash.");
415 if (!hasServerUsername
|| !Objects
.equals(localUsernameHash
, serverUsernameHash
)) {
416 logger
.debug("Attempting to resynchronize username.");
418 tryReserveConfirmUsername(username
);
419 } catch (UsernameMalformedException
| UsernameTakenException
| UsernameIsNotReservedException e
) {
420 logger
.debug("[confirmUsername] Failed to reserve confirm username: {} ({})",
422 e
.getClass().getSimpleName());
423 account
.setUsername(null);
424 account
.setUsernameLink(null);
425 account
.getRecipientStore().rotateSelfStorageId();
429 logger
.debug("Username already set, not refreshing.");
433 private void tryReserveConfirmUsername(final Username username
) throws IOException
{
434 final var usernameLink
= account
.getUsernameLink();
436 if (usernameLink
== null) {
437 dependencies
.getAccountManager()
438 .reserveUsername(List
.of(Base64
.encodeUrlSafeWithoutPadding(username
.getHash())));
439 logger
.debug("[reserveUsername] Successfully reserved existing username.");
440 final var linkComponents
= dependencies
.getAccountManager().confirmUsernameAndCreateNewLink(username
);
441 account
.setUsernameLink(linkComponents
);
442 logger
.debug("[confirmUsername] Successfully confirmed existing username.");
444 final var linkComponents
= dependencies
.getAccountManager().reclaimUsernameAndLink(username
, usernameLink
);
445 account
.setUsernameLink(linkComponents
);
446 logger
.debug("[confirmUsername] Successfully reclaimed existing username and link.");
448 account
.getRecipientStore().rotateSelfStorageId();
451 private void tryToSetUsernameLink(Username username
) {
452 for (var i
= 1; i
< 4; i
++) {
454 final var linkComponents
= dependencies
.getAccountManager().createUsernameLink(username
);
455 account
.setUsernameLink(linkComponents
);
457 } catch (IOException e
) {
458 logger
.debug("[tryToSetUsernameLink] Failed with IOException on attempt {}/3", i
, e
);
463 public void deleteUsername() throws IOException
{
464 dependencies
.getAccountManager().deleteUsernameLink();
465 account
.setUsernameLink(null);
466 dependencies
.getAccountManager().deleteUsername();
467 account
.setUsername(null);
468 logger
.debug("[deleteUsername] Successfully deleted the username.");
471 public void setDeviceName(String deviceName
) {
472 final var privateKey
= account
.getAciIdentityKeyPair().getPrivateKey();
473 final var encryptedDeviceName
= DeviceNameUtil
.encryptDeviceName(deviceName
, privateKey
);
474 account
.setEncryptedDeviceName(encryptedDeviceName
);
477 public void updateAccountAttributes() throws IOException
{
478 dependencies
.getAccountManager().setAccountAttributes(account
.getAccountAttributes(null));
481 public void addDevice(DeviceLinkUrl deviceLinkInfo
) throws IOException
, InvalidDeviceLinkException
, org
.asamk
.signal
.manager
.api
.DeviceLimitExceededException
{
482 String verificationCode
;
484 verificationCode
= dependencies
.getAccountManager().getNewDeviceVerificationCode();
485 } catch (DeviceLimitExceededException e
) {
486 throw new org
.asamk
.signal
.manager
.api
.DeviceLimitExceededException("Too many linked devices", e
);
490 dependencies
.getAccountManager()
491 .addDevice(deviceLinkInfo
.deviceIdentifier(),
492 deviceLinkInfo
.deviceKey(),
493 account
.getAciIdentityKeyPair(),
494 account
.getPniIdentityKeyPair(),
495 account
.getProfileKey(),
496 account
.getOrCreatePinMasterKey(),
498 } catch (InvalidKeyException e
) {
499 throw new InvalidDeviceLinkException("Invalid device link", e
);
501 account
.setMultiDevice(true);
502 context
.getJobExecutor().enqueueJob(new SyncStorageJob());
505 public void removeLinkedDevices(int deviceId
) throws IOException
{
506 dependencies
.getAccountManager().removeDevice(deviceId
);
507 var devices
= dependencies
.getAccountManager().getDevices();
508 account
.setMultiDevice(devices
.size() > 1);
511 public void migrateRegistrationPin() throws IOException
{
512 var masterKey
= account
.getOrCreatePinMasterKey();
514 context
.getPinHelper().migrateRegistrationLockPin(account
.getRegistrationLockPin(), masterKey
);
515 dependencies
.getAccountManager().enableRegistrationLock(masterKey
);
518 public void setRegistrationPin(String pin
) throws IOException
{
519 var masterKey
= account
.getOrCreatePinMasterKey();
521 context
.getPinHelper().setRegistrationLockPin(pin
, masterKey
);
522 dependencies
.getAccountManager().enableRegistrationLock(masterKey
);
524 account
.setRegistrationLockPin(pin
);
525 updateAccountAttributes();
528 public void removeRegistrationPin() throws IOException
{
530 context
.getPinHelper().removeRegistrationLockPin();
531 dependencies
.getAccountManager().disableRegistrationLock();
533 account
.setRegistrationLockPin(null);
536 public void unregister() throws IOException
{
537 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
538 // If this is the primary device, other users can't send messages to this number anymore.
539 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
540 dependencies
.getAccountManager().setGcmId(Optional
.empty());
542 account
.setRegistered(false);
543 unregisteredListener
.call();
546 public void deleteAccount() throws IOException
{
548 context
.getPinHelper().removeRegistrationLockPin();
549 } catch (IOException e
) {
550 logger
.warn("Failed to remove registration lock pin");
552 account
.setRegistrationLockPin(null);
554 dependencies
.getAccountManager().deleteAccount();
556 account
.setRegistered(false);
557 unregisteredListener
.call();
560 public interface Callable
{