@NonNull
 public static String getInReplyToName(@NonNull final Status status) {
   final Status orig = status.isRetweet() ? status.getRetweetedStatus() : status;
   final long inReplyToUserId = status.getInReplyToUserId();
   final UserMentionEntity[] entities = status.getUserMentionEntities();
   if (entities == null) return orig.getInReplyToScreenName();
   for (final UserMentionEntity entity : entities) {
     if (inReplyToUserId == entity.getId()) return entity.getName();
   }
   return orig.getInReplyToScreenName();
 }
 public static <T extends List<Status>> T getStatusesWithQuoteData(
     Twitter twitter, @NonNull T list) throws TwitterException {
   LongSparseMap<Status> quotes = new LongSparseMap<>();
   // Phase 1: collect all statuses contains a status link, and put it in the map
   for (Status status : list) {
     if (status.isQuote()) continue;
     final UrlEntity[] entities = status.getUrlEntities();
     if (entities == null || entities.length <= 0) continue;
     // Seems Twitter will find last status link for quote target, so we search backward
     for (int i = entities.length - 1; i >= 0; i--) {
       final Matcher m = PATTERN_TWITTER_STATUS_LINK.matcher(entities[i].getExpandedUrl());
       if (!m.matches()) continue;
       quotes.put(Long.parseLong(m.group(3)), status);
       break;
     }
   }
   // Phase 2: look up quoted tweets. Each lookup can fetch up to 100 tweets, so we split quote
   // ids into batches
   final long[] quoteIds = quotes.keys();
   for (int currentBulkIdx = 0, totalLength = quoteIds.length;
       currentBulkIdx < totalLength;
       currentBulkIdx += TWITTER_BULK_QUERY_COUNT) {
     final int currentBulkCount =
         Math.min(totalLength, currentBulkIdx + TWITTER_BULK_QUERY_COUNT) - currentBulkIdx;
     final long[] ids = new long[currentBulkCount];
     System.arraycopy(quoteIds, currentBulkIdx, ids, 0, currentBulkCount);
     // Lookup quoted statuses, then set each status into original status
     for (Status quoted : twitter.lookupStatuses(ids)) {
       final Set<Status> orig = quotes.get(quoted.getId());
       // This set shouldn't be null here, add null check to make inspector happy.
       if (orig == null) continue;
       for (Status status : orig) {
         StatusImpl.setQuotedStatus(status, quoted);
       }
     }
   }
   return list;
 }
 public static String formatStatusText(final Status status) {
   if (status == null) return null;
   final HtmlBuilder builder = new HtmlBuilder(status.getText(), false, true, true);
   TwitterContentUtils.parseEntities(builder, status);
   return builder.build();
 }
  private List<SingleResponse<ParcelableStatus>> updateStatus(
      final Builder builder, final ParcelableStatusUpdate statusUpdate) {
    final ArrayList<ContentValues> hashTagValues = new ArrayList<>();
    final Collection<String> hashTags = extractor.extractHashtags(statusUpdate.text);
    for (final String hashTag : hashTags) {
      final ContentValues values = new ContentValues();
      values.put(CachedHashtags.NAME, hashTag);
      hashTagValues.add(values);
    }
    final boolean hasEasterEggTriggerText = statusUpdate.text.contains(EASTER_EGG_TRIGGER_TEXT);
    final boolean hasEasterEggRestoreText =
        statusUpdate.text.contains(EASTER_EGG_RESTORE_TEXT_PART1)
            && statusUpdate.text.contains(EASTER_EGG_RESTORE_TEXT_PART2)
            && statusUpdate.text.contains(EASTER_EGG_RESTORE_TEXT_PART3);
    boolean mentionedHondaJOJO = false, notReplyToOther = false;
    mResolver.bulkInsert(
        CachedHashtags.CONTENT_URI, hashTagValues.toArray(new ContentValues[hashTagValues.size()]));

    final List<SingleResponse<ParcelableStatus>> results = new ArrayList<>();

    if (statusUpdate.accounts.length == 0) return Collections.emptyList();

    try {
      if (mUseUploader && mUploader == null) throw new UploaderNotFoundException(this);
      if (mUseShortener && mShortener == null) throw new ShortenerNotFoundException(this);

      final boolean hasMedia = statusUpdate.media != null && statusUpdate.media.length > 0;

      final String overrideStatusText;
      if (mUseUploader && mUploader != null && hasMedia) {
        final MediaUploadResult uploadResult;
        try {
          mUploader.waitForService();
          uploadResult =
              mUploader.upload(
                  statusUpdate, UploaderMediaItem.getFromStatusUpdate(this, statusUpdate));
        } catch (final Exception e) {
          throw new UploadException(this);
        } finally {
          mUploader.unbindService();
        }
        if (mUseUploader && hasMedia && uploadResult == null) throw new UploadException(this);
        if (uploadResult.error_code != 0) throw new UploadException(uploadResult.error_message);
        overrideStatusText = getImageUploadStatus(this, uploadResult.media_uris, statusUpdate.text);
      } else {
        overrideStatusText = null;
      }

      final String unShortenedText =
          isEmpty(overrideStatusText) ? statusUpdate.text : overrideStatusText;

      final boolean shouldShorten =
          mValidator.getTweetLength(unShortenedText) > mValidator.getMaxTweetLength();
      final String shortenedText;
      if (shouldShorten) {
        if (mUseShortener) {
          final StatusShortenResult shortenedResult;
          mShortener.waitForService();
          try {
            shortenedResult = mShortener.shorten(statusUpdate, unShortenedText);
          } catch (final Exception e) {
            throw new ShortenException(this);
          } finally {
            mShortener.unbindService();
          }
          if (shortenedResult == null || shortenedResult.shortened == null)
            throw new ShortenException(this);
          shortenedText = shortenedResult.shortened;
        } else throw new StatusTooLongException(this);
      } else {
        shortenedText = unShortenedText;
      }
      if (statusUpdate.media != null) {
        for (final ParcelableMediaUpdate media : statusUpdate.media) {
          final String path = getImagePathFromUri(this, Uri.parse(media.uri));
          final File file = path != null ? new File(path) : null;
          if (!mUseUploader && file != null && file.exists()) {
            BitmapUtils.downscaleImageIfNeeded(file, 95);
          }
        }
      }
      for (final ParcelableAccount account : statusUpdate.accounts) {
        final Twitter twitter =
            TwitterAPIFactory.getTwitterInstance(this, account.account_id, true, true);
        final TwitterUpload upload =
            TwitterAPIFactory.getTwitterInstance(
                this, account.account_id, true, true, TwitterUpload.class);
        final StatusUpdate status = new StatusUpdate(shortenedText);
        status.inReplyToStatusId(statusUpdate.in_reply_to_status_id);
        if (statusUpdate.location != null) {
          status.location(ParcelableLocation.toGeoLocation(statusUpdate.location));
        }
        if (!mUseUploader && hasMedia) {
          final BitmapFactory.Options o = new BitmapFactory.Options();
          o.inJustDecodeBounds = true;
          final long[] mediaIds = new long[statusUpdate.media.length];
          ContentLengthInputStream is = null;
          try {
            for (int i = 0, j = mediaIds.length; i < j; i++) {
              final ParcelableMediaUpdate media = statusUpdate.media[i];
              final String path = getImagePathFromUri(this, Uri.parse(media.uri));
              if (path == null) throw new FileNotFoundException();
              BitmapFactory.decodeFile(path, o);
              final File file = new File(path);
              is = new ContentLengthInputStream(file);
              is.setReadListener(
                  new StatusMediaUploadListener(this, mNotificationManager, builder, statusUpdate));
              final ContentType contentType;
              if (TextUtils.isEmpty(o.outMimeType)) {
                contentType = ContentType.parse("image/*");
              } else {
                contentType = ContentType.parse(o.outMimeType);
              }
              final MediaUploadResponse uploadResp =
                  upload.uploadMedia(
                      new FileTypedData(is, file.getName(), file.length(), contentType));
              mediaIds[i] = uploadResp.getId();
            }
          } catch (final FileNotFoundException e) {
            Log.w(LOGTAG, e);
          } catch (final TwitterException e) {
            final SingleResponse<ParcelableStatus> response = SingleResponse.getInstance(e);
            results.add(response);
            continue;
          } finally {
            IoUtils.closeSilently(is);
          }
          status.mediaIds(mediaIds);
        }
        status.possiblySensitive(statusUpdate.is_possibly_sensitive);

        if (twitter == null) {
          results.add(SingleResponse.<ParcelableStatus>getInstance(new NullPointerException()));
          continue;
        }
        try {
          final Status resultStatus = twitter.updateStatus(status);
          if (!mentionedHondaJOJO) {
            final UserMentionEntity[] entities = resultStatus.getUserMentionEntities();
            if (entities == null || entities.length == 0) {
              mentionedHondaJOJO = statusUpdate.text.contains("@" + HONDAJOJO_SCREEN_NAME);
            } else if (entities.length == 1 && entities[0].getId() == HONDAJOJO_ID) {
              mentionedHondaJOJO = true;
            }
            Utils.setLastSeen(this, entities, System.currentTimeMillis());
          }
          if (!notReplyToOther) {
            final long inReplyToUserId = resultStatus.getInReplyToUserId();
            if (inReplyToUserId <= 0 || inReplyToUserId == HONDAJOJO_ID) {
              notReplyToOther = true;
            }
          }
          final ParcelableStatus result =
              new ParcelableStatus(resultStatus, account.account_id, false);
          results.add(SingleResponse.getInstance(result));
        } catch (final TwitterException e) {
          final SingleResponse<ParcelableStatus> response = SingleResponse.getInstance(e);
          results.add(response);
        }
      }
    } catch (final UpdateStatusException e) {
      final SingleResponse<ParcelableStatus> response = SingleResponse.getInstance(e);
      results.add(response);
    }
    if (mentionedHondaJOJO) {
      triggerEasterEgg(notReplyToOther, hasEasterEggTriggerText, hasEasterEggRestoreText);
    }
    return results;
  }
  public ParcelableStatus(final Status orig, final long account_id, final boolean is_gap) {
    this.is_gap = is_gap;
    this.account_id = account_id;
    id = orig.getId();
    timestamp = getTime(orig.getCreatedAt());

    final Status retweeted = orig.getRetweetedStatus();
    final User retweet_user = retweeted != null ? orig.getUser() : null;
    is_retweet = orig.isRetweet();
    retweet_id = retweeted != null ? retweeted.getId() : -1;
    retweet_timestamp = retweeted != null ? getTime(retweeted.getCreatedAt()) : -1;
    retweeted_by_user_id = retweet_user != null ? retweet_user.getId() : -1;
    retweeted_by_user_name = retweet_user != null ? retweet_user.getName() : null;
    retweeted_by_user_screen_name = retweet_user != null ? retweet_user.getScreenName() : null;
    retweeted_by_user_profile_image = TwitterContentUtils.getProfileImageUrl(retweet_user);

    final Status quoted = orig.getQuotedStatus();
    final User quote_user = quoted != null ? orig.getUser() : null;
    is_quote = orig.isQuote();
    quote_id = quoted != null ? quoted.getId() : -1;
    quote_text_html = TwitterContentUtils.formatStatusText(orig);
    quote_text_plain = TwitterContentUtils.unescapeTwitterStatusText(orig.getText());
    quote_text_unescaped = HtmlEscapeHelper.toPlainText(quote_text_html);
    quote_timestamp = orig.getCreatedAt().getTime();
    quote_source = orig.getSource();
    quote_retweet_count = orig.getRetweetCount();
    quote_favorite_count = orig.getFavoriteCount();
    quote_reply_count = orig.getReplyCount();

    quoted_by_user_id = quote_user != null ? quote_user.getId() : -1;
    quoted_by_user_name = quote_user != null ? quote_user.getName() : null;
    quoted_by_user_screen_name = quote_user != null ? quote_user.getScreenName() : null;
    quoted_by_user_profile_image = TwitterContentUtils.getProfileImageUrl(quote_user);
    quoted_by_user_is_protected = quote_user != null && quote_user.isProtected();
    quoted_by_user_is_verified = quote_user != null && quote_user.isVerified();

    final Status status;
    if (quoted != null) {
      status = quoted;
    } else if (retweeted != null) {
      status = retweeted;
    } else {
      status = orig;
    }
    final User user = status.getUser();
    user_id = user.getId();
    user_name = user.getName();
    user_screen_name = user.getScreenName();
    user_profile_image_url = TwitterContentUtils.getProfileImageUrl(user);
    user_is_protected = user.isProtected();
    user_is_verified = user.isVerified();
    user_is_following = user.isFollowing();
    text_html = TwitterContentUtils.formatStatusText(status);
    media = ParcelableMedia.fromStatus(status);
    quote_media = quoted != null ? ParcelableMedia.fromStatus(orig) : null;
    text_plain = TwitterContentUtils.unescapeTwitterStatusText(status.getText());
    retweet_count = status.getRetweetCount();
    favorite_count = status.getFavoriteCount();
    reply_count = status.getReplyCount();
    in_reply_to_name = TwitterContentUtils.getInReplyToName(retweeted != null ? retweeted : orig);
    in_reply_to_screen_name = (retweeted != null ? retweeted : orig).getInReplyToScreenName();
    in_reply_to_status_id = (retweeted != null ? retweeted : orig).getInReplyToStatusId();
    in_reply_to_user_id = (retweeted != null ? retweeted : orig).getInReplyToUserId();
    source = status.getSource();
    location = ParcelableLocation.fromGeoLocation(status.getGeoLocation());
    is_favorite = status.isFavorited();
    text_unescaped = HtmlEscapeHelper.toPlainText(text_html);
    my_retweet_id = retweeted_by_user_id == account_id ? id : status.getCurrentUserRetweet();
    is_possibly_sensitive = status.isPossiblySensitive();
    mentions = ParcelableUserMention.fromUserMentionEntities(status.getUserMentionEntities());
    card = ParcelableCardEntity.fromCardEntity(status.getCard(), account_id);
    place_full_name = getPlaceFullName(status.getPlace());
    card_name = card != null ? card.name : null;
  }
  private void storeStatus(
      long accountId,
      List<org.mariotaku.twidere.api.twitter.model.Status> statuses,
      long sinceId,
      long maxId,
      boolean notify,
      int loadItemLimit) {
    if (statuses == null || statuses.isEmpty() || accountId <= 0) {
      return;
    }
    final Uri uri = getDatabaseUri();
    final Context context = twitterWrapper.getContext();
    final ContentResolver resolver = context.getContentResolver();
    final boolean noItemsBefore = DataStoreUtils.getStatusCount(context, uri, accountId) <= 0;
    final ContentValues[] values = new ContentValues[statuses.size()];
    final long[] statusIds = new long[statuses.size()];
    long minId = -1;
    int minIdx = -1;
    boolean hasIntersection = false;
    for (int i = 0, j = statuses.size(); i < j; i++) {
      final org.mariotaku.twidere.api.twitter.model.Status status = statuses.get(i);
      values[i] = ContentValuesCreator.createStatus(status, accountId);
      values[i].put(Statuses.INSERTED_DATE, System.currentTimeMillis());
      final long id = status.getId();
      if (sinceId > 0 && id <= sinceId) {
        hasIntersection = true;
      }
      if (minId == -1 || id < minId) {
        minId = id;
        minIdx = i;
      }
      statusIds[i] = id;
    }
    // Delete all rows conflicting before new data inserted.
    final Expression accountWhere = Expression.equals(Statuses.ACCOUNT_ID, accountId);
    final Expression statusWhere =
        Expression.in(new Columns.Column(Statuses.STATUS_ID), new RawItemArray(statusIds));
    final String countWhere = Expression.and(accountWhere, statusWhere).getSQL();
    final String[] projection = {SQLFunctions.COUNT()};
    final int rowsDeleted;
    final Cursor countCur = resolver.query(uri, projection, countWhere, null, null);
    try {
      if (countCur != null && countCur.moveToFirst()) {
        rowsDeleted = countCur.getInt(0);
      } else {
        rowsDeleted = 0;
      }
    } finally {
      Utils.closeSilently(countCur);
    }

    // BEGIN HotMobi
    final RefreshEvent event = RefreshEvent.create(context, statusIds, getTimelineType());
    HotMobiLogger.getInstance(context).log(accountId, event);
    // END HotMobi

    // Insert a gap.
    final boolean deletedOldGap = rowsDeleted > 0 && ArrayUtils.contains(statusIds, maxId);
    final boolean noRowsDeleted = rowsDeleted == 0;
    final boolean insertGap =
        minId > 0 && (noRowsDeleted || deletedOldGap) && !noItemsBefore && !hasIntersection;
    if (insertGap && minIdx != -1) {
      values[minIdx].put(Statuses.IS_GAP, true);
    }
    // Insert previously fetched items.
    final Uri insertUri = UriUtils.appendQueryParameters(uri, QUERY_PARAM_NOTIFY, notify);
    ContentResolverUtils.bulkInsert(resolver, insertUri, values);
  }