docs: private OmemoMessage.Sent encrypt(OmemoManager.LoggedInOmemoManager managerGuard,
                                        Set<OmemoDevice> contactsDevices,
                                        byte[] messageKey,
                                        byte[] iv,
                                        String message)
              throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
              UndecidedOmemoIdentityException, CryptoFailedException {
  
          OmemoManager manager = managerGuard.get();
          OmemoDevice userDevice = manager.getOwnDevice();
  
          // Do not encrypt for our own device.
          removeOurDevice(userDevice, contactsDevices);
  
          buildMissingSessionsWithDevices(manager.getConnection(), userDevice, contactsDevices);
  
          Set<OmemoDevice> undecidedDevices = getUndecidedDevices(userDevice, manager.getTrustCallback(), contactsDevices);
          if (!undecidedDevices.isEmpty()) {
              throw new UndecidedOmemoIdentityException(undecidedDevices);
          }
  
          // Keep track of skipped devices
          HashMap<OmemoDevice, Throwable> skippedRecipients = new HashMap<>();
  
          OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> builder;
          try {
              builder = new OmemoMessageBuilder<>(
                      userDevice, manager.getTrustCallback(), getOmemoRatchet(managerGuard.get()), messageKey, iv, message);
          } catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException |
                  NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) {
              throw new CryptoFailedException(e);
          }
  
          for (OmemoDevice contactsDevice : contactsDevices) {
              // Build missing sessions
              if (!hasSession(userDevice, contactsDevice)) {
                  try {
                      buildFreshSessionWithDevice(manager.getConnection(), userDevice, contactsDevice);
                  } catch (CorruptedOmemoKeyException | CannotEstablishOmemoSessionException e) {
                      LOGGER.log(Level.WARNING, "Could not build session with " + contactsDevice + ".", e);
                      skippedRecipients.put(contactsDevice, e);
                      continue;
                  }
              }
  
              int messageCounter = omemoStore.loadOmemoMessageCounter(userDevice, contactsDevice);
  
              // Ignore read-only devices
              if (OmemoConfiguration.getIgnoreReadOnlyDevices()) {
  
                  boolean readOnly = messageCounter >= OmemoConfiguration.getMaxReadOnlyMessageCount();
  
                  if (readOnly) {
                      LOGGER.log(Level.FINE, "Device " + contactsDevice + " seems to be read-only (We sent "
                              + messageCounter + " messages without getting a reply back (max allowed is " +
                              OmemoConfiguration.getMaxReadOnlyMessageCount() + "). Ignoring the device.");
                      skippedRecipients.put(contactsDevice, new ReadOnlyDeviceException(contactsDevice));
  
                      // Skip this device and handle next device
                      continue;
                  }
              }
  
              // Add recipients
              try {
                  builder.addRecipient(contactsDevice);
              }
              catch (NoIdentityKeyException | CorruptedOmemoKeyException e) {
                  LOGGER.log(Level.WARNING, "Encryption failed for device " + contactsDevice + ".", e);
                  skippedRecipients.put(contactsDevice, e);
              }
              catch (UndecidedOmemoIdentityException e) {
                  throw new AssertionError("Recipients device seems to be undecided, even though we should have thrown" +
                          " an exception earlier in that case. " + e);
              }
              catch (UntrustedOmemoIdentityException e) {
                  LOGGER.log(Level.WARNING, "Device " + contactsDevice + " is untrusted. Message is not encrypted for it.");
                  skippedRecipients.put(contactsDevice, e);
              }
  
              // Increment the message counter of the device
              omemoStore.storeOmemoMessageCounter(userDevice, contactsDevice,
                      messageCounter + 1);
          }
  
          OmemoElement element = builder.finish();
  
          return new OmemoMessage.Sent(element, messageKey, iv, contactsDevices, skippedRecipients);
      }