org.mariotaku.twidere.fragment.StatusFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.mariotaku.twidere.fragment.StatusFragment.java

Source

/*
 *            Twidere - Twitter client for Android
 * 
 * Copyright (C) 2012 Mariotaku Lee <mariotaku.lee@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.mariotaku.twidere.fragment;

import static android.text.TextUtils.isEmpty;
import static org.mariotaku.twidere.util.Utils.cancelRetweet;
import static org.mariotaku.twidere.util.Utils.clearUserColor;
import static org.mariotaku.twidere.util.Utils.findStatus;
import static org.mariotaku.twidere.util.Utils.formatToLongTimeString;
import static org.mariotaku.twidere.util.Utils.getAccountColor;
import static org.mariotaku.twidere.util.Utils.getBiggerTwitterProfileImage;
import static org.mariotaku.twidere.util.Utils.getImagesInStatus;
import static org.mariotaku.twidere.util.Utils.getQuoteStatus;
import static org.mariotaku.twidere.util.Utils.getTwitterInstance;
import static org.mariotaku.twidere.util.Utils.getUserColor;
import static org.mariotaku.twidere.util.Utils.getUserTypeIconRes;
import static org.mariotaku.twidere.util.Utils.isMyActivatedAccount;
import static org.mariotaku.twidere.util.Utils.isMyRetweet;
import static org.mariotaku.twidere.util.Utils.openImage;
import static org.mariotaku.twidere.util.Utils.openUserProfile;
import static org.mariotaku.twidere.util.Utils.setMenuForStatus;
import static org.mariotaku.twidere.util.Utils.setUserColor;
import static org.mariotaku.twidere.util.Utils.showErrorToast;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.mariotaku.menubar.MenuBar;
import org.mariotaku.menubar.MenuBar.OnMenuItemClickListener;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.SetColorActivity;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.loader.DummyParcelableStatusesLoader;
import org.mariotaku.twidere.model.ImageSpec;
import org.mariotaku.twidere.model.Panes;
import org.mariotaku.twidere.model.ParcelableLocation;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.provider.TweetStore.Accounts;
import org.mariotaku.twidere.provider.TweetStore.Filters;
import org.mariotaku.twidere.util.AsyncTask;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ClipboardUtils;
import org.mariotaku.twidere.util.HtmlEscapeHelper;
import org.mariotaku.twidere.util.LazyImageLoader;
import org.mariotaku.twidere.util.OnLinkClickHandler;
import org.mariotaku.twidere.util.SynchronizedStateSavedList;
import org.mariotaku.twidere.util.TwidereLinkify;
import org.mariotaku.twidere.view.ColorLabelRelativeLayout;
import org.mariotaku.twidere.view.ExtendedFrameLayout;
import org.mariotaku.twidere.view.StatusImagePreviewLayout;
import org.mariotaku.twidere.view.StatusImagePreviewLayout.OnImageClickListener;

import twitter4j.Relationship;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.location.Address;
import android.location.Geocoder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
import com.twitter.Extractor;

import edu.ucdavis.earlybird.ProfilingUtil;

public class StatusFragment extends ParcelableStatusesListFragment
        implements OnClickListener, Panes.Right, OnImageClickListener {

    private static final int LOADER_ID_STATUS = 1;
    private static final int LOADER_ID_FOLLOW = 2;
    private static final int LOADER_ID_LOCATION = 3;

    private long mAccountId, mStatusId;
    private ParcelableStatus mStatus;

    private boolean mLoadMoreAutomatically;
    private boolean mFollowInfoDisplayed, mLocationInfoDisplayed;
    private boolean mStatusLoaderInitialized, mLocationLoaderInitialized;
    private boolean mFollowInfoLoaderInitialized;;
    private boolean mShouldScroll;

    private SharedPreferences mPreferences;
    private AsyncTwitterWrapper mTwitterWrapper;
    private LazyImageLoader mProfileImageLoader;

    private TextView mNameView, mScreenNameView, mTextView, mTimeAndSourceView, mInReplyToView, mLocationView,
            mRetweetedStatusView;
    private ImageView mProfileImageView;
    private Button mFollowButton;
    private View mMainContent, mFollowIndicator, mImagePreviewContainer;
    private ColorLabelRelativeLayout mProfileView;
    private MenuBar mMenuBar;
    private ProgressBar mStatusLoadProgress, mFollowInfoProgress;
    private StatusImagePreviewLayout mImagePreviewView;
    private View mStatusView;
    private View mLoadImagesIndicator;
    private ExtendedFrameLayout mStatusContainer;
    private ListView mListView;

    private LoadConversationTask mConversationTask;

    private final BroadcastReceiver mStatusReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(final Context context, final Intent intent) {
            final String action = intent.getAction();
            if (BROADCAST_FRIENDSHIP_CHANGED.equals(action)) {
                if (mStatus != null && mStatus.user_id == intent.getLongExtra(INTENT_KEY_USER_ID, -1)
                        && intent.getBooleanExtra(INTENT_KEY_SUCCEED, false)) {
                    showFollowInfo(true);
                }
            } else if (BROADCAST_FAVORITE_CHANGED.equals(action)) {
                final long status_id = intent.getLongExtra(INTENT_KEY_STATUS_ID, -1);
                if (status_id > 0 && status_id == mStatusId) {
                    getStatus(true);
                }
            } else if (BROADCAST_RETWEET_CHANGED.equals(action)) {
                final long status_id = intent.getLongExtra(INTENT_KEY_STATUS_ID, -1);
                if (status_id > 0 && status_id == mStatusId) {
                    getStatus(true);
                }
            }
        }
    };

    private final LoaderCallbacks<Response<ParcelableStatus>> mStatusLoaderCallbacks = new LoaderCallbacks<Response<ParcelableStatus>>() {

        @Override
        public Loader<Response<ParcelableStatus>> onCreateLoader(final int id, final Bundle args) {
            mStatusLoadProgress.setVisibility(View.VISIBLE);
            mMainContent.setVisibility(View.INVISIBLE);
            mMainContent.setEnabled(false);
            setProgressBarIndeterminateVisibility(true);
            final boolean omit_intent_extra = args != null ? args.getBoolean(INTENT_KEY_OMIT_INTENT_EXTRA, true)
                    : true;
            return new StatusLoader(getActivity(), omit_intent_extra, getArguments(), mAccountId, mStatusId);
        }

        @Override
        public void onLoaderReset(final Loader<Response<ParcelableStatus>> loader) {

        }

        @Override
        public void onLoadFinished(final Loader<Response<ParcelableStatus>> loader,
                final Response<ParcelableStatus> data) {
            if (data.value == null) {
                showErrorToast(getActivity(), getString(R.string.getting_status), data.exception, true);
            } else {
                displayStatus(data.value);
                mStatusLoadProgress.setVisibility(View.GONE);
                mMainContent.setVisibility(View.VISIBLE);
                mMainContent.setEnabled(true);
            }
            setProgressBarIndeterminateVisibility(false);
        }

    };

    private final LoaderCallbacks<String> mLocationLoaderCallbacks = new LoaderCallbacks<String>() {

        @Override
        public Loader<String> onCreateLoader(final int id, final Bundle args) {
            return new LocationInfoLoader(getActivity(), mStatus != null ? mStatus.location : null);
        }

        @Override
        public void onLoaderReset(final Loader<String> loader) {

        }

        @Override
        public void onLoadFinished(final Loader<String> loader, final String data) {
            if (data != null) {
                mLocationView.setText(data);
                mLocationInfoDisplayed = true;
            } else {
                mLocationView.setText(R.string.view_map);
                mLocationInfoDisplayed = false;
            }
        }

    };

    private final LoaderCallbacks<Response<Boolean>> mFollowInfoLoaderCallbacks = new LoaderCallbacks<Response<Boolean>>() {

        @Override
        public Loader<Response<Boolean>> onCreateLoader(final int id, final Bundle args) {
            mFollowIndicator.setVisibility(View.VISIBLE);
            mFollowButton.setVisibility(View.GONE);
            mFollowInfoProgress.setVisibility(View.VISIBLE);
            return new FollowInfoLoader(getActivity(), mStatus);
        }

        @Override
        public void onLoaderReset(final Loader<Response<Boolean>> loader) {

        }

        @Override
        public void onLoadFinished(final Loader<Response<Boolean>> loader, final Response<Boolean> data) {
            if (data.exception == null) {
                mFollowIndicator.setVisibility(data.value == null || data.value ? View.GONE : View.VISIBLE);
                if (data.value != null) {
                    mFollowButton.setVisibility(data.value ? View.GONE : View.VISIBLE);
                    mFollowInfoDisplayed = true;
                }
            }
            mFollowInfoProgress.setVisibility(View.GONE);
        }

    };

    private final OnMenuItemClickListener mMenuItemClickListener = new OnMenuItemClickListener() {

        @Override
        public boolean onMenuItemClick(final MenuItem item) {
            if (mStatus == null)
                return false;
            final String text_plain = mStatus.text_plain;
            final String screen_name = mStatus.screen_name;
            final String name = mStatus.name;
            switch (item.getItemId()) {
            case MENU_SHARE: {
                final Intent intent = new Intent(Intent.ACTION_SEND);
                intent.setType("text/plain");
                intent.putExtra(Intent.EXTRA_TEXT, "@" + mStatus.screen_name + ": " + text_plain);
                startActivity(Intent.createChooser(intent, getString(R.string.share)));
                break;
            }
            case MENU_COPY: {
                final CharSequence text = Html.fromHtml(mStatus.text_html);
                if (ClipboardUtils.setText(getActivity(), text)) {
                    Toast.makeText(getActivity(), R.string.text_copied, Toast.LENGTH_SHORT).show();
                }
                break;
            }
            case MENU_RETWEET: {
                if (isMyRetweet(mStatus)) {
                    cancelRetweet(mTwitterWrapper, mStatus);
                } else {
                    final long id_to_retweet = mStatus.is_retweet && mStatus.retweet_id > 0 ? mStatus.retweet_id
                            : mStatus.status_id;
                    mTwitterWrapper.retweetStatus(mStatus.account_id, id_to_retweet);
                }
                break;
            }
            case MENU_QUOTE: {
                final Intent intent = new Intent(INTENT_ACTION_COMPOSE);
                final Bundle bundle = new Bundle();
                bundle.putLong(INTENT_KEY_ACCOUNT_ID, mAccountId);
                bundle.putLong(INTENT_KEY_IN_REPLY_TO_ID, mStatusId);
                bundle.putString(INTENT_KEY_IN_REPLY_TO_SCREEN_NAME, screen_name);
                bundle.putString(INTENT_KEY_IN_REPLY_TO_NAME, name);
                bundle.putBoolean(INTENT_KEY_IS_QUOTE, true);
                bundle.putString(INTENT_KEY_TEXT, getQuoteStatus(getActivity(), screen_name, text_plain));
                intent.putExtras(bundle);
                startActivity(intent);
                break;
            }
            case MENU_REPLY: {
                final Intent intent = new Intent(INTENT_ACTION_COMPOSE);
                final Bundle bundle = new Bundle();
                final List<String> mentions = new Extractor().extractMentionedScreennames(mStatus.text_plain);
                mentions.remove(mStatus.screen_name);
                mentions.add(0, mStatus.screen_name);
                bundle.putStringArray(INTENT_KEY_MENTIONS, mentions.toArray(new String[mentions.size()]));
                bundle.putLong(INTENT_KEY_ACCOUNT_ID, mStatus.account_id);
                bundle.putLong(INTENT_KEY_IN_REPLY_TO_ID, mStatus.status_id);
                bundle.putString(INTENT_KEY_IN_REPLY_TO_SCREEN_NAME, mStatus.screen_name);
                bundle.putString(INTENT_KEY_IN_REPLY_TO_NAME, mStatus.name);
                intent.putExtras(bundle);
                startActivity(intent);
                break;
            }
            case MENU_FAVORITE: {
                if (mStatus.is_favorite) {
                    mTwitterWrapper.destroyFavorite(mAccountId, mStatusId);
                } else {
                    mTwitterWrapper.createFavoriteAsync(mAccountId, mStatusId);
                }
                break;
            }
            case MENU_DELETE: {
                mTwitterWrapper.destroyStatus(mAccountId, mStatusId);
                break;
            }
            case MENU_MUTE_SOURCE: {
                final String source = HtmlEscapeHelper.toPlainText(mStatus.source);
                if (source == null)
                    return false;
                final Uri uri = Filters.Sources.CONTENT_URI;
                final ContentValues values = new ContentValues();
                final ContentResolver resolver = getContentResolver();
                values.put(Filters.TEXT, source);
                resolver.delete(uri, Filters.TEXT + " = '" + source + "'", null);
                resolver.insert(uri, values);
                Toast.makeText(getActivity(), getString(R.string.source_muted, source), Toast.LENGTH_SHORT).show();
                break;
            }
            case MENU_SET_COLOR: {
                final Intent intent = new Intent(getActivity(), SetColorActivity.class);
                startActivityForResult(intent, REQUEST_SET_COLOR);
                break;
            }
            case MENU_CLEAR_COLOR: {
                clearUserColor(getActivity(), mStatus.user_id);
                updateUserColor();
                break;
            }
            default: {
                if (item.getIntent() != null) {
                    try {
                        startActivity(item.getIntent());
                    } catch (final ActivityNotFoundException e) {
                        Log.w(LOGTAG, e);
                        return false;
                    }
                }
                break;
            }
            }
            return true;
        }
    };

    private final ExtendedFrameLayout.OnSizeChangedListener mOnSizeChangedListener = new ExtendedFrameLayout.OnSizeChangedListener() {

        @Override
        public void onSizeChanged(final View view, final int w, final int h, final int oldw, final int oldh) {
            if (getActivity() == null)
                return;
            final float density = getResources().getDisplayMetrics().density;
            mStatusView.setMinimumHeight(h - (int) (density * 2));
        }

    };

    public void displayStatus(final ParcelableStatus status) {
        onRefreshComplete();
        updatePullRefresh();
        final boolean status_changed = mStatus != null && status != null && !status.equals(mStatus);
        if (status_changed) {
            mAdapter.clear();
            // UCD
            ProfilingUtil.profile(getActivity(), mStatus.account_id, "End, " + mStatus.status_id);
        } else {
            mListView.setSelection(0);
        }
        mStatusId = -1;
        mAccountId = -1;
        mStatus = status;
        if (mStatus != null) {
            // UCD
            ProfilingUtil.profile(getActivity(), mStatus.account_id, "Start, " + mStatus.status_id);
            mAccountId = mStatus.account_id;
            mStatusId = mStatus.status_id;
        }
        clearPreviewImages();
        if (status == null || getActivity() == null)
            return;
        final Bundle args = getArguments();
        args.putLong(INTENT_KEY_ACCOUNT_ID, mAccountId);
        args.putLong(INTENT_KEY_STATUS_ID, mStatusId);
        args.putParcelable(INTENT_KEY_STATUS, status);
        mMenuBar.inflate(R.menu.menu_status);
        setMenuForStatus(getActivity(), mMenuBar.getMenu(), status);
        mMenuBar.show();

        updateUserColor();
        mProfileView.drawRight(getAccountColor(getActivity(), status.account_id));

        mNameView.setText(status.name);
        mNameView.setCompoundDrawablesWithIntrinsicBounds(0, 0,
                getUserTypeIconRes(status.is_verified, status.is_protected), 0);
        mScreenNameView.setText("@" + status.screen_name);
        mTextView.setText(Html.fromHtml(status.text_html));
        final TwidereLinkify linkify = new TwidereLinkify(mTextView);
        linkify.setOnLinkClickListener(
                new OnLinkClickHandler(getActivity(), mAccountId, status.is_possibly_sensitive));
        linkify.addAllLinks();
        final boolean is_reply = status.in_reply_to_status_id > 0;
        final String time = formatToLongTimeString(getActivity(), status.status_timestamp);
        final String source_html = status.source;
        setPullToRefreshEnabled(!mLoadMoreAutomatically && is_reply);
        if (!isEmpty(time) && !isEmpty(source_html)) {
            mTimeAndSourceView.setText(Html.fromHtml(getString(R.string.time_source, time, source_html)));
        } else if (isEmpty(time) && !isEmpty(source_html)) {
            mTimeAndSourceView.setText(Html.fromHtml(getString(R.string.source, source_html)));
        } else if (!isEmpty(time) && isEmpty(source_html)) {
            mTimeAndSourceView.setText(time);
        }
        mTimeAndSourceView.setMovementMethod(LinkMovementMethod.getInstance());
        final boolean display_screen_name = NAME_DISPLAY_OPTION_SCREEN_NAME
                .equals(mPreferences.getString(PREFERENCE_KEY_NAME_DISPLAY_OPTION, NAME_DISPLAY_OPTION_BOTH));
        mInReplyToView.setVisibility(is_reply ? View.VISIBLE : View.GONE);
        if (is_reply) {
            if (mLoadMoreAutomatically) {
                mInReplyToView.setText(getString(R.string.in_reply_to, "@" + status.in_reply_to_screen_name));
            } else {
                mInReplyToView.setText(getString(R.string.in_reply_to_pull_to_load_conversation,
                        "@" + status.in_reply_to_screen_name));
            }
        }

        if (mPreferences.getBoolean(PREFERENCE_KEY_DISPLAY_PROFILE_IMAGE, true)) {
            final boolean hires_profile_image = getResources().getBoolean(R.bool.hires_profile_image);
            mProfileImageLoader.displayImage(mProfileImageView,
                    hires_profile_image ? getBiggerTwitterProfileImage(status.profile_image_url_string)
                            : status.profile_image_url_string);
        } else {
            mProfileImageView.setImageResource(R.drawable.ic_profile_image_default);
        }
        final List<ImageSpec> images = getImagesInStatus(status.text_html);
        mImagePreviewContainer.setVisibility(images.size() > 0 ? View.VISIBLE : View.GONE);
        loadPreviewImages(images);
        if (mPreferences.getBoolean(PREFERENCE_KEY_LOAD_MORE_AUTOMATICALLY, false)) {
            showPreviewImages();
        }
        mRetweetedStatusView.setVisibility(status.retweet_id > 0 ? View.VISIBLE : View.GONE);
        if (status.retweet_id > 0) {
            if (display_screen_name) {
                mRetweetedStatusView.setText(status.retweet_count > 1
                        ? getString(R.string.retweeted_by_with_count, status.retweeted_by_screen_name,
                                status.retweet_count - 1)
                        : getString(R.string.retweeted_by, status.retweeted_by_screen_name));
            } else {
                mRetweetedStatusView.setText(status.retweet_count > 1
                        ? getString(R.string.retweeted_by_with_count, status.retweeted_by_name,
                                status.retweet_count - 1)
                        : getString(R.string.retweeted_by, status.retweeted_by_name));
            }
        }
        mLocationView.setVisibility(ParcelableLocation.isValidLocation(status.location) ? View.VISIBLE : View.GONE);
        if (mLoadMoreAutomatically) {
            showFollowInfo(true);
            showLocationInfo(true);
            showConversation();
        } else {
            mFollowIndicator.setVisibility(View.GONE);
        }

    }

    @Override
    public Loader<SynchronizedStateSavedList<ParcelableStatus, Long>> newLoaderInstance(final Bundle args) {
        final long account_id = args != null ? args.getLong(INTENT_KEY_ACCOUNT_ID, -1) : -1;
        return new DummyParcelableStatusesLoader(getActivity(), account_id, getData());
    }

    @Override
    public void onActivityCreated(final Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setListShownNoAnimation(true);
        mListView = getListView();
        mListView.setStackFromBottom(true);
        mAdapter.setGapDisallowed(true);
        final TwidereApplication application = getApplication();
        mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
        mProfileImageLoader = application.getProfileImageLoader();
        mTwitterWrapper = getTwitterWrapper();
        mLoadMoreAutomatically = mPreferences.getBoolean(PREFERENCE_KEY_LOAD_MORE_AUTOMATICALLY, false);
        if (mLoadMoreAutomatically) {
            setPullToRefreshEnabled(false);
        } else {
            setMode(Mode.PULL_DOWN_TO_REFRESH);
            setPullLabel(getString(R.string.pull_to_load_conversation_label), Mode.BOTH);
            setReleaseLabel(getString(R.string.pull_to_load_conversation_release_label), Mode.BOTH);
        }
        final Bundle bundle = getArguments();
        if (bundle != null) {
            mAccountId = bundle.getLong(INTENT_KEY_ACCOUNT_ID);
            mStatusId = bundle.getLong(INTENT_KEY_STATUS_ID);
        }
        mLoadImagesIndicator.setOnClickListener(this);
        mInReplyToView.setOnClickListener(this);
        mFollowButton.setOnClickListener(this);
        mProfileView.setOnClickListener(this);
        mLocationView.setOnClickListener(this);
        mRetweetedStatusView.setOnClickListener(this);
        mMenuBar.setOnMenuItemClickListener(mMenuItemClickListener);
        getStatus(false);
        mImagePreviewView.setOnImageClickListener(this);
    }

    @Override
    public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
        if (intent == null || mStatus == null)
            return;
        switch (requestCode) {
        case REQUEST_SET_COLOR: {
            if (resultCode == Activity.RESULT_OK)
                if (intent != null && intent.getExtras() != null) {
                    final int color = intent.getIntExtra(Accounts.USER_COLOR, Color.TRANSPARENT);
                    setUserColor(getActivity(), mStatus.user_id, color);
                    updateUserColor();
                }
            break;
        }
        }

    }

    @Override
    public void onClick(final View view) {
        if (mStatus == null)
            return;
        switch (view.getId()) {
        case R.id.profile: {
            openUserProfile(getActivity(), mStatus.account_id, mStatus.user_id, null);
            break;
        }
        case R.id.follow: {
            mTwitterWrapper.createFriendship(mAccountId, mStatus.user_id);
            break;
        }
        case R.id.in_reply_to: {
            showConversation();
            break;
        }
        case R.id.location_view: {
            if (mStatus.location == null)
                return;
            final ParcelableLocation location = mStatus.location;
            if (location == null || !location.isValid())
                return;
            final Uri.Builder builder = new Uri.Builder();
            builder.scheme(SCHEME_TWIDERE);
            builder.authority(AUTHORITY_MAP);
            builder.appendQueryParameter(QUERY_PARAM_LAT, String.valueOf(location.latitude));
            builder.appendQueryParameter(QUERY_PARAM_LNG, String.valueOf(location.longitude));
            startActivity(new Intent(Intent.ACTION_VIEW, builder.build()));
            break;
        }
        case R.id.load_images: {
            showPreviewImages();
            // UCD
            ProfilingUtil.profile(getActivity(), mAccountId, "Thumbnail click, " + mStatusId);
            break;
        }
        }

    }

    @Override
    public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
            final Bundle savedInstanceState) {
        final View view = inflater.inflate(R.layout.status, null, false);
        mMainContent = view.findViewById(R.id.content);
        mStatusLoadProgress = (ProgressBar) view.findViewById(R.id.status_load_progress);
        mMenuBar = (MenuBar) view.findViewById(R.id.menu_bar);
        mStatusContainer = (ExtendedFrameLayout) view.findViewById(R.id.status_container);
        mStatusContainer.addView(super.onCreateView(inflater, container, savedInstanceState));
        mStatusContainer.setOnSizeChangedListener(mOnSizeChangedListener);
        mStatusView = inflater.inflate(R.layout.status_content, null, false);
        mImagePreviewContainer = mStatusView.findViewById(R.id.image_preview);
        mLocationView = (TextView) mStatusView.findViewById(R.id.location_view);
        mRetweetedStatusView = (TextView) mStatusView.findViewById(R.id.retweet_view);
        mNameView = (TextView) mStatusView.findViewById(R.id.name);
        mScreenNameView = (TextView) mStatusView.findViewById(R.id.screen_name);
        mTextView = (TextView) mStatusView.findViewById(R.id.text);
        mProfileImageView = (ImageView) mStatusView.findViewById(R.id.profile_image);
        mTimeAndSourceView = (TextView) mStatusView.findViewById(R.id.time_source);
        mInReplyToView = (TextView) mStatusView.findViewById(R.id.in_reply_to);
        mFollowButton = (Button) mStatusView.findViewById(R.id.follow);
        mFollowIndicator = mStatusView.findViewById(R.id.follow_indicator);
        mFollowInfoProgress = (ProgressBar) mStatusView.findViewById(R.id.follow_info_progress);
        mProfileView = (ColorLabelRelativeLayout) mStatusView.findViewById(R.id.profile);
        mImagePreviewView = (StatusImagePreviewLayout) mStatusView.findViewById(R.id.preview_gallery);
        mLoadImagesIndicator = mStatusView.findViewById(R.id.load_images);
        return view;
    }

    @Override
    public void onDestroyView() {
        // UCD
        if (mStatus != null) {
            ProfilingUtil.profile(getActivity(), mAccountId, "End, " + mStatus.status_id);
        }
        mStatus = null;
        mAccountId = -1;
        mStatusId = -1;
        final LoaderManager lm = getLoaderManager();
        lm.destroyLoader(LOADER_ID_STATUS);
        lm.destroyLoader(LOADER_ID_LOCATION);
        lm.destroyLoader(LOADER_ID_FOLLOW);
        if (mConversationTask != null) {
            mConversationTask.cancel(true);
        }
        super.onDestroyView();
    }

    @Override
    public void onImageClick(final ImageSpec spec) {
        if (mStatus == null || spec == null)
            return;
        // UCD
        ProfilingUtil.profile(getActivity(), mAccountId, "Large image click, " + mStatusId + ", " + spec);
        openImage(getActivity(), Uri.parse(spec.full_image_link), mStatus.is_possibly_sensitive);
    }

    @Override
    public void onPullDownToRefresh() {
        onRefreshComplete();
        showConversation();
    }

    @Override
    public void onScrollStateChanged(final AbsListView view, final int scrollState) {
        super.onScrollStateChanged(view, scrollState);
        mShouldScroll = false;
    }

    @Override
    public void onStart() {
        super.onStart();
        final IntentFilter filter = new IntentFilter();
        filter.addAction(BROADCAST_FRIENDSHIP_CHANGED);
        filter.addAction(BROADCAST_FAVORITE_CHANGED);
        filter.addAction(BROADCAST_RETWEET_CHANGED);
        registerReceiver(mStatusReceiver, filter);
        updateUserColor();
        final int text_size = mPreferences.getInt(PREFERENCE_KEY_TEXT_SIZE, PREFERENCE_DEFAULT_TEXT_SIZE);
        mNameView.setTextSize(text_size * 1.25f);
        mTextView.setTextSize(text_size * 1.25f);
        mScreenNameView.setTextSize(text_size * 0.85f);
        mTimeAndSourceView.setTextSize(text_size * 0.85f);
        mInReplyToView.setTextSize(text_size * 0.85f);
        mLocationView.setTextSize(text_size * 0.85f);
        mRetweetedStatusView.setTextSize(text_size * 0.85f);
    }

    @Override
    public void onStop() {
        unregisterReceiver(mStatusReceiver);
        super.onStop();
    }

    private void clearPreviewImages() {
        mImagePreviewView.clear();
        mLoadImagesIndicator.setVisibility(View.VISIBLE);
        mImagePreviewView.setVisibility(View.GONE);
    }

    private void getStatus(final boolean omit_intent_extra) {
        final LoaderManager lm = getLoaderManager();
        lm.destroyLoader(LOADER_ID_STATUS);
        final Bundle args = new Bundle();
        args.putBoolean(INTENT_KEY_OMIT_INTENT_EXTRA, omit_intent_extra);
        if (!mStatusLoaderInitialized) {
            lm.initLoader(LOADER_ID_STATUS, args, mStatusLoaderCallbacks);
            mStatusLoaderInitialized = true;
        } else {
            lm.restartLoader(LOADER_ID_STATUS, args, mStatusLoaderCallbacks);
        }
    }

    private boolean loadPreviewImages(final Collection<? extends ImageSpec> images) {
        mImagePreviewView.clear();
        return mImagePreviewView.addAll(images);
    }

    private void showConversation() {
        if (mConversationTask != null && mConversationTask.getStatus() == AsyncTask.Status.RUNNING)
            return;
        final int count = mAdapter.getCount();
        final ParcelableStatus status;
        if (count == 0) {
            mShouldScroll = !mLoadMoreAutomatically;
            status = mStatus;
        } else {
            status = mAdapter.getItem(0);
        }
        if (status == null || status.in_reply_to_status_id <= 0)
            return;
        mConversationTask = new LoadConversationTask(this);
        mConversationTask.execute(status);
    }

    private void showFollowInfo(final boolean force) {
        if (mFollowInfoDisplayed && !force)
            return;
        final LoaderManager lm = getLoaderManager();
        lm.destroyLoader(LOADER_ID_FOLLOW);
        if (!mFollowInfoLoaderInitialized) {
            lm.initLoader(LOADER_ID_FOLLOW, null, mFollowInfoLoaderCallbacks);
            mFollowInfoLoaderInitialized = true;
        } else {
            lm.restartLoader(LOADER_ID_FOLLOW, null, mFollowInfoLoaderCallbacks);
        }
    }

    private void showLocationInfo(final boolean force) {
        if (mLocationInfoDisplayed && !force)
            return;
        final LoaderManager lm = getLoaderManager();
        lm.destroyLoader(LOADER_ID_LOCATION);
        if (!mLocationLoaderInitialized) {
            lm.initLoader(LOADER_ID_LOCATION, null, mLocationLoaderCallbacks);
            mLocationLoaderInitialized = true;
        } else {
            lm.restartLoader(LOADER_ID_LOCATION, null, mLocationLoaderCallbacks);
        }
    }

    private void showPreviewImages() {
        if (mStatus == null)
            return;
        mLoadImagesIndicator.setVisibility(View.GONE);
        mImagePreviewView.setVisibility(View.VISIBLE);
        mImagePreviewView.show(mStatus.is_possibly_sensitive);
    }

    private void updatePullRefresh() {
        final boolean has_converstion = mStatus != null && mStatus.in_reply_to_status_id > 0;
        final boolean load_not_finished = mAdapter.getCount() > 0 && mAdapter.getItem(0).in_reply_to_status_id > 0;
        final boolean should_enable = has_converstion && load_not_finished;
        mListView.setTranscriptMode(
                should_enable ? ListView.TRANSCRIPT_MODE_NORMAL : ListView.TRANSCRIPT_MODE_DISABLED);
        mInReplyToView.setClickable(should_enable);
        setPullToRefreshEnabled(should_enable);
    }

    private void updateUserColor() {
        if (mStatus == null)
            return;
        mProfileView.drawLeft(getUserColor(getActivity(), mStatus.user_id));
    }

    @Override
    void setListHeaderFooters(final ListView list) {
        list.addFooterView(mStatusView);
    }

    public static class LoadConversationTask extends AsyncTask<ParcelableStatus, Void, Response<Boolean>> {

        final Handler handler;
        final Context context;
        final StatusFragment fragment;

        LoadConversationTask(final StatusFragment fragment) {
            context = fragment.getActivity();
            this.fragment = fragment;
            handler = new Handler();
        }

        @Override
        protected Response<Boolean> doInBackground(final ParcelableStatus... params) {
            if (params == null || params.length != 1)
                return new Response<Boolean>(false, null);
            try {
                final long account_id = params[0].account_id;
                ParcelableStatus status = params[0];
                while (status != null && status.in_reply_to_status_id > 0 && !isCancelled()) {
                    status = findStatus(context, account_id, status.in_reply_to_status_id);
                    if (status == null) {
                        break;
                    }
                    handler.post(new AddStatusRunnable(status));
                }
            } catch (final TwitterException e) {
                return new Response<Boolean>(false, e);
            }
            return new Response<Boolean>(true, null);
        }

        @Override
        protected void onCancelled() {
            fragment.setProgressBarIndeterminateVisibility(false);
            fragment.updatePullRefresh();
        }

        @Override
        protected void onPostExecute(final Response<Boolean> data) {
            fragment.setProgressBarIndeterminateVisibility(false);
            fragment.updatePullRefresh();
            if (data == null || data.value == null || !data.value) {
                showErrorToast(context, context.getString(R.string.getting_status), data.exception, true);
            }
        }

        @Override
        protected void onPreExecute() {
            fragment.setProgressBarIndeterminateVisibility(true);
            fragment.updatePullRefresh();
        }

        class AddStatusRunnable implements Runnable {

            final ParcelableStatus status;

            AddStatusRunnable(final ParcelableStatus status) {
                this.status = status;
            }

            @Override
            public void run() {
                fragment.mAdapter.add(status);
                fragment.mAdapter.sort(ParcelableStatus.REVERSE_ID_COMPARATOR);
                if (!fragment.mLoadMoreAutomatically && fragment.mShouldScroll) {
                    fragment.mListView.setSelection(0 + fragment.mListView.getHeaderViewsCount());
                }
            }
        }
    }

    public static class LocationInfoLoader extends AsyncTaskLoader<String> {

        private final Context context;
        private final ParcelableLocation location;

        public LocationInfoLoader(final Context context, final ParcelableLocation location) {
            super(context);
            this.context = context;
            this.location = location;
        }

        @Override
        public String loadInBackground() {
            if (location == null)
                return null;
            try {
                final Geocoder coder = new Geocoder(context);
                final List<Address> addresses = coder.getFromLocation(location.latitude, location.longitude, 1);
                if (addresses.size() == 1) {
                    final Address address = addresses.get(0);
                    final StringBuilder builder = new StringBuilder();
                    final int max_idx = address.getMaxAddressLineIndex();
                    for (int i = 0; i < max_idx; i++) {
                        builder.append(address.getAddressLine(i));
                        if (i != max_idx - 1) {
                            builder.append(", ");
                        }
                    }
                    return builder.toString();

                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onStartLoading() {
            forceLoad();
        }

    }

    public static class Response<T> {
        public final T value;
        public final TwitterException exception;

        public Response(final T value, final TwitterException exception) {
            this.value = value;
            this.exception = exception;
        }
    }

    public static class StatusLoader extends AsyncTaskLoader<Response<ParcelableStatus>> {

        private final Context context;
        private final boolean omit_intent_extra;
        private final Bundle extras;
        private final long account_id, status_id;

        public StatusLoader(final Context context, final boolean omit_intent_extra, final Bundle extras,
                final long account_id, final long status_id) {
            super(context);
            this.context = context;
            this.omit_intent_extra = omit_intent_extra;
            this.extras = extras;
            this.account_id = account_id;
            this.status_id = status_id;
        }

        @Override
        public Response<ParcelableStatus> loadInBackground() {
            if (!omit_intent_extra && extras != null) {
                final ParcelableStatus status = extras.getParcelable(INTENT_KEY_STATUS);
                if (status != null)
                    return new Response<ParcelableStatus>(status, null);
            }
            try {
                return new Response<ParcelableStatus>(findStatus(context, account_id, status_id), null);
            } catch (final TwitterException e) {
                return new Response<ParcelableStatus>(null, e);
            }
        }

        @Override
        protected void onStartLoading() {
            forceLoad();
        }

    }

    static class FollowInfoLoader extends AsyncTaskLoader<Response<Boolean>> {

        private final ParcelableStatus status;
        private final Context context;

        public FollowInfoLoader(final Context context, final ParcelableStatus status) {
            super(context);
            this.context = context;
            this.status = status;
        }

        @Override
        public Response<Boolean> loadInBackground() {
            return isAllFollowing();
        }

        @Override
        protected void onStartLoading() {
            forceLoad();
        }

        private Response<Boolean> isAllFollowing() {
            if (status == null)
                return new Response<Boolean>(null, null);
            if (isMyActivatedAccount(context, status.user_id))
                return new Response<Boolean>(true, null);
            final Twitter twitter = getTwitterInstance(context, status.account_id, false);
            if (twitter == null)
                return new Response<Boolean>(null, null);
            try {
                final Relationship result = twitter.showFriendship(status.account_id, status.user_id);
                if (!result.isSourceFollowingTarget())
                    return new Response<Boolean>(false, null);
            } catch (final TwitterException e) {
                return new Response<Boolean>(null, e);
            }
            return new Response<Boolean>(null, null);
        }
    }

    static class ImagesAdapter extends BaseAdapter {

        private final List<ImageSpec> mImages = new ArrayList<ImageSpec>();
        private final LazyImageLoader mImageLoader;
        private final LayoutInflater mInflater;

        public ImagesAdapter(final Context context) {
            mImageLoader = TwidereApplication.getInstance(context).getPreviewImageLoader();
            mInflater = LayoutInflater.from(context);
        }

        public boolean addAll(final Collection<? extends ImageSpec> images) {
            final boolean ret = images != null && mImages.addAll(images);
            notifyDataSetChanged();
            return ret;
        }

        public void clear() {
            mImages.clear();
            notifyDataSetChanged();
        }

        @Override
        public int getCount() {
            return mImages.size();
        }

        @Override
        public ImageSpec getItem(final int position) {
            return mImages.get(position);
        }

        @Override
        public long getItemId(final int position) {
            final ImageSpec spec = getItem(position);
            return spec != null ? spec.hashCode() : 0;
        }

        @Override
        public View getView(final int position, final View convertView, final ViewGroup parent) {
            final View view = convertView != null ? convertView
                    : mInflater.inflate(R.layout.image_preview_item, null);
            final ImageView image = (ImageView) view.findViewById(R.id.image);
            final ImageSpec spec = getItem(position);
            mImageLoader.displayImage(image, spec != null ? spec.preview_image_link : null);
            return view;
        }

    }

}