private Pair<Long, Long> insertSyncMediaMessage(
      MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message)
      throws MmsException {
    MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
    TextSecureSyncContext syncContext = message.getSyncContext().get();
    Recipients recipients = getSyncMessageDestination(message);
    OutgoingMediaMessage mediaMessage =
        new OutgoingMediaMessage(
            context,
            masterSecret,
            recipients,
            message.getAttachments().get(),
            message.getBody().orNull());

    mediaMessage = new OutgoingSecureMediaMessage(mediaMessage);

    long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
    long messageId =
        database.insertMessageOutbox(
            masterSecret, mediaMessage, threadId, false, syncContext.getTimestamp());

    database.markAsSent(messageId, "push".getBytes(), 0);
    database.markAsPush(messageId);

    return new Pair<>(messageId, threadId);
  }
  private Pair<Long, Long> insertSyncTextMessage(
      MasterSecret masterSecret,
      TextSecureEnvelope envelope,
      TextSecureMessage message,
      Optional<Long> smsMessageId) {
    EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
    Recipients recipients = getSyncMessageDestination(message);
    String body = message.getBody().or("");
    OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipients, body);

    long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
    long messageId =
        database.insertMessageOutbox(
            masterSecret,
            threadId,
            outgoingTextMessage,
            false,
            message.getSyncContext().get().getTimestamp());

    database.markAsSent(messageId);
    database.markAsPush(messageId);
    database.markAsSecure(messageId);

    if (smsMessageId.isPresent()) {
      database.deleteMessage(smsMessageId.get());
    }

    return new Pair<>(messageId, threadId);
  }
  private void handleUntrustedIdentityMessage(
      MasterSecret masterSecret, TextSecureEnvelope envelope, Optional<Long> smsMessageId) {
    try {
      EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
      Recipients recipients =
          RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
      long recipientId = recipients.getPrimaryRecipient().getRecipientId();
      PreKeyWhisperMessage whisperMessage = new PreKeyWhisperMessage(envelope.getMessage());
      IdentityKey identityKey = whisperMessage.getIdentityKey();
      String encoded = Base64.encodeBytes(envelope.getMessage());
      IncomingTextMessage textMessage =
          new IncomingTextMessage(
              envelope.getSource(),
              envelope.getSourceDevice(),
              envelope.getTimestamp(),
              encoded,
              Optional.<TextSecureGroup>absent());

      if (!smsMessageId.isPresent()) {
        IncomingPreKeyBundleMessage bundleMessage =
            new IncomingPreKeyBundleMessage(textMessage, encoded);
        Pair<Long, Long> messageAndThreadId =
            database.insertMessageInbox(masterSecret, bundleMessage);

        database.setMismatchedIdentity(messageAndThreadId.first, recipientId, identityKey);
        MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
      } else {
        database.updateMessageBody(masterSecret, smsMessageId.get(), encoded);
        database.markAsPreKeyBundle(smsMessageId.get());
        database.setMismatchedIdentity(smsMessageId.get(), recipientId, identityKey);
      }
    } catch (InvalidMessageException | InvalidVersionException e) {
      throw new AssertionError(e);
    }
  }
  private void handleEndSessionMessage(
      MasterSecret masterSecret,
      TextSecureEnvelope envelope,
      TextSecureMessage message,
      Optional<Long> smsMessageId) {
    EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
    IncomingTextMessage incomingTextMessage =
        new IncomingTextMessage(
            envelope.getSource(),
            envelope.getSourceDevice(),
            message.getTimestamp(),
            "",
            Optional.<TextSecureGroup>absent());

    long threadId;

    if (!smsMessageId.isPresent()) {
      IncomingEndSessionMessage incomingEndSessionMessage =
          new IncomingEndSessionMessage(incomingTextMessage);
      Pair<Long, Long> messageAndThreadId =
          smsDatabase.insertMessageInbox(masterSecret, incomingEndSessionMessage);
      threadId = messageAndThreadId.second;
    } else {
      smsDatabase.markAsEndSession(smsMessageId.get());
      threadId = smsDatabase.getThreadIdForMessage(smsMessageId.get());
    }

    SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
    sessionStore.deleteAllSessions(envelope.getSource());

    SecurityEvent.broadcastSecurityUpdateEvent(context, threadId);
    MessageNotifier.updateNotification(context, masterSecret, threadId);
  }
 private void markFailed(long messageId, PduPart part, PartDatabase.PartId partId) {
   try {
     PartDatabase database = DatabaseFactory.getPartDatabase(context);
     database.updateFailedDownloadedPart(messageId, partId, part);
   } catch (MmsException e) {
     Log.w(TAG, e);
   }
 }
  @Override
  public void onCanceled() {
    PartDatabase database = DatabaseFactory.getPartDatabase(context);
    List<PduPart> parts = database.getParts(messageId);

    for (PduPart part : parts) {
      markFailed(messageId, part, part.getPartId());
    }
  }
  @Override
  public void onRun(MasterSecret masterSecret) throws NoSuchMessageException {
    PushDatabase database = DatabaseFactory.getPushDatabase(context);
    TextSecureEnvelope envelope = database.get(messageId);
    Optional<Long> optionalSmsMessageId =
        smsMessageId > 0 ? Optional.of(smsMessageId) : Optional.<Long>absent();

    handleMessage(masterSecret, envelope, optionalSmsMessageId);
    database.delete(messageId);
  }
  private void handleGroupMessage(
      MasterSecret masterSecret,
      TextSecureEnvelope envelope,
      TextSecureMessage message,
      Optional<Long> smsMessageId) {
    GroupMessageProcessor.process(context, masterSecret, envelope, message);

    if (smsMessageId.isPresent()) {
      DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
    }
  }
  private void handleLegacyMessage(
      MasterSecret masterSecret, TextSecureEnvelope envelope, Optional<Long> smsMessageId) {
    EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);

    if (!smsMessageId.isPresent()) {
      Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
      smsDatabase.markAsLegacyVersion(messageAndThreadId.first);
      MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
    } else {
      smsDatabase.markAsLegacyVersion(smsMessageId.get());
    }
  }
  @Override
  public void onRun(MasterSecret masterSecret) throws IOException {
    PartDatabase database = DatabaseFactory.getPartDatabase(context);

    Log.w(TAG, "Downloading push parts for: " + messageId);

    List<PduPart> parts = database.getParts(messageId);

    for (PduPart part : parts) {
      retrievePart(masterSecret, part, messageId);
      Log.w(TAG, "Got part: " + part.getPartId());
    }
  }
  private Pair<Long, Recipients> handleCreatePushGroup(
      String groupName, byte[] avatar, Set<Recipient> members)
      throws InvalidNumberException, MmsException {
    GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
    byte[] groupId = groupDatabase.allocateGroupId();
    Set<String> memberE164Numbers = getE164Numbers(members);

    memberE164Numbers.add(TextSecurePreferences.getLocalNumber(this));

    groupDatabase.create(groupId, groupName, new LinkedList<String>(memberE164Numbers), null, null);
    groupDatabase.updateAvatar(groupId, avatar);

    return handlePushOperation(groupId, groupName, avatar, memberE164Numbers);
  }
  private Pair<Long, Recipients> handleUpdatePushGroup(
      byte[] groupId, String groupName, byte[] avatar, Set<Recipient> members)
      throws InvalidNumberException, MmsException {
    GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(this);
    Set<String> memberE164Numbers = getE164Numbers(members);
    memberE164Numbers.add(TextSecurePreferences.getLocalNumber(this));

    for (String number : memberE164Numbers) Log.w(TAG, "Updating: " + number);

    groupDatabase.updateMembers(groupId, new LinkedList<String>(memberE164Numbers));
    groupDatabase.updateTitle(groupId, groupName);
    groupDatabase.updateAvatar(groupId, avatar);

    return handlePushOperation(groupId, groupName, avatar, memberE164Numbers);
  }
  private Pair<Long, Long> insertPlaceholder(
      MasterSecret masterSecret, TextSecureEnvelope envelope) {
    EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);

    IncomingTextMessage textMessage =
        new IncomingTextMessage(
            envelope.getSource(),
            envelope.getSourceDevice(),
            envelope.getTimestamp(),
            "",
            Optional.<TextSecureGroup>absent());

    textMessage = new IncomingEncryptedMessage(textMessage, "");

    return database.insertMessageInbox(masterSecret, textMessage);
  }
  // TODO: build correct method logic here
  private Pair<Long, Long> insertSyncSMPMessage(
      MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureSMPMessage message) {

    EncryptingSMPDatabase database = DatabaseFactory.getEncryptingSMPDatabase(context);
    String body = message.getBody().isPresent() ? message.getBody().get() : "";

    IncomingSMPMessage smpMessage =
        new IncomingSMPMessage(
            envelope.getSource(),
            envelope.getSourceDevice(),
            message.getTimestamp(),
            body,
            message.getGroupInfo());

    smpMessage = new IncomingEncryptedSMPSyncMessage(smpMessage, body);

    return database.insertMessageInbox(masterSecret, smpMessage);
  }
  private Pair<Long, Long> insertStandardMediaMessage(
      MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureMessage message)
      throws MmsException {
    MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
    String localNumber = TextSecurePreferences.getLocalNumber(context);
    IncomingMediaMessage mediaMessage =
        new IncomingMediaMessage(
            masterSecret,
            envelope.getSource(),
            localNumber,
            message.getTimestamp(),
            Optional.fromNullable(envelope.getRelay()),
            message.getBody(),
            message.getGroupInfo(),
            message.getAttachments());

    return database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1);
  }
 @Override
 protected Void doInBackground(Void... voids) {
   final GroupDatabase db = DatabaseFactory.getGroupDatabase(GroupCreateActivity.this);
   final Recipients recipients = db.getGroupMembers(groupId, false);
   if (recipients != null) {
     final List<Recipient> recipientList = recipients.getRecipientsList();
     if (recipientList != null) {
       if (existingContacts == null) existingContacts = new HashSet<>(recipientList.size());
       existingContacts.addAll(recipientList);
     }
   }
   GroupDatabase.GroupRecord group = db.getGroup(groupId);
   if (group != null) {
     existingTitle = group.getTitle();
     final byte[] existingAvatar = group.getAvatar();
     if (existingAvatar != null) {
       existingAvatarBmp =
           BitmapFactory.decodeByteArray(existingAvatar, 0, existingAvatar.length);
     }
   }
   return null;
 }
  private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId)
      throws IOException {
    PartDatabase database = DatabaseFactory.getPartDatabase(context);
    File attachmentFile = null;
    PartDatabase.PartId partId = part.getPartId();

    try {
      attachmentFile = createTempFile();

      TextSecureAttachmentPointer pointer = createAttachmentPointer(masterSecret, part);
      InputStream attachment = messageReceiver.retrieveAttachment(pointer, attachmentFile);

      database.updateDownloadedPart(masterSecret, messageId, partId, part, attachment);
    } catch (InvalidPartException
        | NonSuccessfulResponseCodeException
        | InvalidMessageException
        | MmsException e) {
      Log.w(TAG, e);
      markFailed(messageId, part, partId);
    } finally {
      if (attachmentFile != null) attachmentFile.delete();
    }
  }
  private void handleMediaMessage(
      MasterSecret masterSecret,
      TextSecureEnvelope envelope,
      TextSecureMessage message,
      Optional<Long> smsMessageId)
      throws MmsException {
    Pair<Long, Long> messageAndThreadId;

    if (message.getSyncContext().isPresent()) {
      messageAndThreadId = insertSyncMediaMessage(masterSecret, envelope, message);
    } else {
      messageAndThreadId = insertStandardMediaMessage(masterSecret, envelope, message);
    }

    ApplicationContext.getInstance(context)
        .getJobManager()
        .add(new AttachmentDownloadJob(context, messageAndThreadId.first));

    if (smsMessageId.isPresent()) {
      DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
    }

    MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
  }
  private Pair<Long, Long> insertStandardTextMessage(
      MasterSecret masterSecret,
      TextSecureEnvelope envelope,
      TextSecureMessage message,
      Optional<Long> smsMessageId) {
    EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
    String body = message.getBody().isPresent() ? message.getBody().get() : "";

    if (smsMessageId.isPresent()) {
      return database.updateBundleMessageBody(masterSecret, smsMessageId.get(), body);
    } else {
      IncomingTextMessage textMessage =
          new IncomingTextMessage(
              envelope.getSource(),
              envelope.getSourceDevice(),
              message.getTimestamp(),
              body,
              message.getGroupInfo());

      textMessage = new IncomingEncryptedMessage(textMessage, body);

      return database.insertMessageInbox(masterSecret, textMessage);
    }
  }
 private long handleCreateMmsGroup(Set<Recipient> members) {
   Recipients recipients =
       RecipientFactory.getRecipientsFor(this, new LinkedList<>(members), false);
   return DatabaseFactory.getThreadDatabase(this)
       .getThreadIdFor(recipients, ThreadDatabase.DistributionTypes.CONVERSATION);
 }