com.conferenceengineer.android.iosched.ui.SessionDetailFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.conferenceengineer.android.iosched.ui.SessionDetailFragment.java

Source

/*
 * Copyright 2012 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.conferenceengineer.android.iosched.ui;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.Pair;
import android.view.*;
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.conferenceengineer.android.iosched.conference686.R;
import com.conferenceengineer.android.iosched.provider.ScheduleContract;
import com.conferenceengineer.android.iosched.service.SessionAlarmService;
import com.conferenceengineer.android.iosched.ui.widget.ObservableScrollView;
import com.conferenceengineer.android.iosched.util.*;

import java.util.ArrayList;
import java.util.List;

import static com.conferenceengineer.android.iosched.util.LogUtils.LOGD;

/**
 * A fragment that shows detail information for a session, including session title, abstract,
 * time information, speaker photos and bios, etc.
 */
public class SessionDetailFragment extends Fragment
        implements LoaderManager.LoaderCallbacks<Cursor>, ObservableScrollView.Callbacks {

    // Set this boolean extra to true to show a variable height header
    public static final String EXTRA_VARIABLE_HEIGHT_HEADER = "com.google.android.iosched.extra.VARIABLE_HEIGHT_HEADER";

    private Handler mHandler = new Handler();

    private String mSessionId;
    private Uri mSessionUri;

    private long mSessionBlockStart;
    private long mSessionBlockEnd;
    private String mTitleString;
    private String mHashtags;
    private String mUrl;
    private String mRoomId;
    private String mType;

    private boolean mStarred;
    private boolean mInitStarred;
    private MenuItem mStarMenuItem;
    private MenuItem mSocialStreamMenuItem;
    private MenuItem mShareMenuItem;

    private ViewGroup mRootView;
    private TextView mTitle;
    private TextView mSubtitle;

    private ObservableScrollView mScrollView;
    private CheckableLinearLayout mAddScheduleButton;
    private View mAddSchedulePlaceholderView;

    private TextView mAbstract;
    private TextView mRequirements;

    private boolean mSessionCursor = false;
    private boolean mSpeakersCursor = false;
    private boolean mHasSummaryContent = false;
    private boolean mVariableHeightHeader = false;

    private ImageLoader mImageLoader;
    private List<Runnable> mDeferredUiOperations = new ArrayList<Runnable>();

    private StringBuilder mBuffer = new StringBuilder();
    private Rect mBufferRect = new Rect();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final Intent intent = BaseActivity.fragmentArgumentsToIntent(getArguments());
        mSessionUri = intent.getData();

        if (mSessionUri == null) {
            return;
        }

        mSessionId = ScheduleContract.Sessions.getSessionId(mSessionUri);

        mVariableHeightHeader = intent.getBooleanExtra(EXTRA_VARIABLE_HEIGHT_HEADER, false);

        LoaderManager manager = getLoaderManager();
        manager.restartLoader(SessionsQuery._TOKEN, null, this);
        manager.restartLoader(SpeakersQuery._TOKEN, null, this);

        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        mRootView = (ViewGroup) inflater.inflate(R.layout.fragment_session_detail, null);

        mTitle = (TextView) mRootView.findViewById(R.id.session_title);
        mSubtitle = (TextView) mRootView.findViewById(R.id.session_subtitle);

        // Larger target triggers plus one button
        View headerView = mRootView.findViewById(R.id.header_session);
        mAbstract = (TextView) mRootView.findViewById(R.id.session_abstract);
        mRequirements = (TextView) mRootView.findViewById(R.id.session_requirements);

        mAddScheduleButton = (CheckableLinearLayout) mRootView.findViewById(R.id.add_schedule_button);
        mAddScheduleButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                setSessionStarred(!mStarred, true);
            }
        });
        mAddScheduleButton.setVisibility(View.GONE);

        if (mVariableHeightHeader) {
            ViewGroup.LayoutParams layoutParams = headerView.getLayoutParams();
            layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
            headerView.setLayoutParams(layoutParams);
        }

        setupCustomScrolling(mRootView);

        return mRootView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity() instanceof ImageLoader.ImageLoaderProvider) {
            mImageLoader = ((ImageLoader.ImageLoaderProvider) getActivity()).getImageLoaderInstance();
        }
    }

    private void setupCustomScrolling(View rootView) {
        mAddSchedulePlaceholderView = rootView.findViewById(R.id.add_to_schedule_button_placeholder);
        if (mAddSchedulePlaceholderView == null) {
            if (mType != null && !ScheduleContract.Sessions.SESSION_TYPE_KEYNOTE.equals(mType)) {
                mAddScheduleButton.setVisibility(View.VISIBLE);
            }
            return;
        }

        mScrollView = (ObservableScrollView) rootView.findViewById(R.id.scroll_view);
        mScrollView.setCallbacks(this);
        ViewTreeObserver vto = mScrollView.getViewTreeObserver();
        if (vto.isAlive()) {
            vto.addOnGlobalLayoutListener(mGlobalLayoutListener);
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (mScrollView == null) {
            return;
        }

        ViewTreeObserver vto = mScrollView.getViewTreeObserver();
        if (vto.isAlive()) {
            vto.removeGlobalOnLayoutListener(mGlobalLayoutListener);
        }
    }

    private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            onScrollChanged();
            if (mType != null && !ScheduleContract.Sessions.SESSION_TYPE_KEYNOTE.equals(mType)) {
                mAddScheduleButton.setVisibility(View.VISIBLE);
            }
        }
    };

    @Override
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void onScrollChanged() {
        float newTop = Math.max(mAddSchedulePlaceholderView.getTop(), mScrollView.getScrollY());
        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mAddScheduleButton.getLayoutParams();
        if (UIUtils.hasICS()) {
            mAddScheduleButton.setTranslationY(newTop);
        } else {
            lp.gravity = Gravity.TOP | Gravity.START; // needed for earlier platform versions
            lp.topMargin = (int) newTop;
            mScrollView.requestLayout();
        }

        mScrollView.getGlobalVisibleRect(mBufferRect);
        int parentLeft = mBufferRect.left;
        int parentRight = mBufferRect.right;
        if (mAddSchedulePlaceholderView.getGlobalVisibleRect(mBufferRect)) {
            lp.leftMargin = mBufferRect.left - parentLeft;
            lp.rightMargin = parentRight - mBufferRect.right;
        }
        mAddScheduleButton.setLayoutParams(lp);
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mInitStarred != mStarred) {
            if (mStarred && UIUtils.getCurrentTime(getActivity()) < mSessionBlockStart) {
                setupNotification();
            }
        }
    }

    private void setupNotification() {
        // Schedule an alarm that fires a system notification when expires.
        final Context context = getActivity();
        Intent scheduleIntent = new Intent(SessionAlarmService.ACTION_SCHEDULE_STARRED_BLOCK, null, context,
                SessionAlarmService.class);
        scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_START, mSessionBlockStart);
        scheduleIntent.putExtra(SessionAlarmService.EXTRA_SESSION_END, mSessionBlockEnd);
        context.startService(scheduleIntent);
    }

    /**
     * Handle {@link SessionsQuery} {@link Cursor}.
     */
    private void onSessionQueryComplete(Cursor cursor) {
        mSessionCursor = true;
        if (!cursor.moveToFirst()) {
            if (isAdded()) {
                // TODO: Remove this in favor of a callbacks interface that the activity
                // can implement.
                getActivity().finish();
            }
            return;
        }

        mTitleString = cursor.getString(SessionsQuery.TITLE);

        // Format time block this session occupies
        mType = cursor.getString(SessionsQuery.TYPE);
        mSessionBlockStart = cursor.getLong(SessionsQuery.BLOCK_START);
        mSessionBlockEnd = cursor.getLong(SessionsQuery.BLOCK_END);
        String roomName = cursor.getString(SessionsQuery.ROOM_NAME);
        final String subtitle = UIUtils.formatSessionSubtitle(mTitleString, mSessionBlockStart, mSessionBlockEnd,
                roomName, mBuffer, getActivity());

        mTitle.setText(mTitleString);

        mUrl = cursor.getString(SessionsQuery.URL);
        if (TextUtils.isEmpty(mUrl)) {
            mUrl = "";
        }

        mHashtags = cursor.getString(SessionsQuery.HASHTAGS);
        if (!TextUtils.isEmpty(mHashtags)) {
            enableSocialStreamMenuItemDeferred();
        }

        mRoomId = cursor.getString(SessionsQuery.ROOM_ID);

        setupShareMenuItemDeferred();
        showStarredDeferred(mInitStarred = (cursor.getInt(SessionsQuery.STARRED) != 0), false);

        final String sessionAbstract = cursor.getString(SessionsQuery.ABSTRACT);
        if (!TextUtils.isEmpty(sessionAbstract)) {
            UIUtils.setTextMaybeHtml(mAbstract, sessionAbstract);
            mAbstract.setVisibility(View.VISIBLE);
            mHasSummaryContent = true;
        } else {
            mAbstract.setVisibility(View.GONE);
        }

        final View requirementsBlock = mRootView.findViewById(R.id.session_requirements_block);
        final String sessionRequirements = cursor.getString(SessionsQuery.REQUIREMENTS);
        if (!TextUtils.isEmpty(sessionRequirements)) {
            UIUtils.setTextMaybeHtml(mRequirements, sessionRequirements);
            requirementsBlock.setVisibility(View.VISIBLE);
            mHasSummaryContent = true;
        } else {
            requirementsBlock.setVisibility(View.GONE);
        }

        // Show empty message when all data is loaded, and nothing to show
        if (mSpeakersCursor && !mHasSummaryContent) {
            mRootView.findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
        }

        // Compile list of links (submit feedback, and normal links)
        ViewGroup linkContainer = (ViewGroup) mRootView.findViewById(R.id.links_container);
        linkContainer.removeAllViews();

        final Context context = mRootView.getContext();

        List<Pair<Integer, Intent>> links = new ArrayList<Pair<Integer, Intent>>();

        // Add session feedback link
        if (getResources().getBoolean(R.bool.has_session_feedback_enabled)) {
            links.add(new Pair<Integer, Intent>(R.string.session_feedback_submitlink,
                    new Intent(Intent.ACTION_VIEW, mSessionUri, getActivity(), SessionFeedbackActivity.class)));
        }

        for (int i = 0; i < SessionsQuery.LINKS_INDICES.length; i++) {
            final String linkUrl = cursor.getString(SessionsQuery.LINKS_INDICES[i]);
            if (TextUtils.isEmpty(linkUrl)) {
                continue;
            }

            links.add(new Pair<Integer, Intent>(SessionsQuery.LINKS_TITLES[i],
                    new Intent(Intent.ACTION_VIEW, Uri.parse(linkUrl))
                            .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)));
        }

        // Render links
        if (links.size() > 0) {
            LayoutInflater inflater = LayoutInflater.from(context);
            int columns = context.getResources().getInteger(R.integer.links_columns);

            LinearLayout currentLinkRowView = null;
            for (int i = 0; i < links.size(); i++) {
                final Pair<Integer, Intent> link = links.get(i);

                // Create link view
                TextView linkView = (TextView) inflater.inflate(R.layout.list_item_session_link, linkContainer,
                        false);
                linkView.setText(getString(link.first));
                linkView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        fireLinkEvent(link.first);
                        try {
                            startActivity(link.second);
                        } catch (ActivityNotFoundException ignored) {
                        }
                    }
                });

                // Place it inside a container
                if (columns == 1) {
                    linkContainer.addView(linkView);
                } else {
                    // create a new link row
                    if (i % columns == 0) {
                        currentLinkRowView = (LinearLayout) inflater.inflate(R.layout.include_link_row,
                                linkContainer, false);
                        currentLinkRowView.setWeightSum(columns);
                        linkContainer.addView(currentLinkRowView);
                    }

                    ((LinearLayout.LayoutParams) linkView.getLayoutParams()).width = 0;
                    ((LinearLayout.LayoutParams) linkView.getLayoutParams()).weight = 1;
                    currentLinkRowView.addView(linkView);
                }
            }

            mRootView.findViewById(R.id.session_links_header).setVisibility(View.VISIBLE);
            mRootView.findViewById(R.id.links_container).setVisibility(View.VISIBLE);

        } else {
            mRootView.findViewById(R.id.session_links_header).setVisibility(View.GONE);
            mRootView.findViewById(R.id.links_container).setVisibility(View.GONE);
        }

        // Show past/present/future and livestream status for this block.
        UIUtils.updateTimeBlockUI(context, mSessionBlockStart, mSessionBlockEnd, null, mSubtitle, subtitle);

        LOGD("Tracker", "Session: " + mTitleString);
    }

    private void enableSocialStreamMenuItemDeferred() {
        mDeferredUiOperations.add(new Runnable() {
            @Override
            public void run() {
                mSocialStreamMenuItem.setVisible(true);
            }
        });
        tryExecuteDeferredUiOperations();
    }

    private void showStarredDeferred(final boolean starred, final boolean allowAnimate) {
        mDeferredUiOperations.add(new Runnable() {
            @Override
            public void run() {
                showStarred(starred, allowAnimate);
            }
        });
        tryExecuteDeferredUiOperations();
    }

    private void showStarred(boolean starred, boolean allowAnimate) {
        mStarMenuItem.setTitle(starred ? R.string.description_remove_schedule : R.string.description_add_schedule);
        mStarMenuItem.setIcon(starred ? R.drawable.ic_action_remove_schedule : R.drawable.ic_action_add_schedule);
        mStarred = starred;

        mAddScheduleButton.setChecked(mStarred);
        ImageView iconView = (ImageView) mAddScheduleButton.findViewById(R.id.add_schedule_icon);
        setOrAnimateIconTo(iconView, starred ? R.drawable.add_schedule_button_icon_checked
                : R.drawable.add_schedule_button_icon_unchecked, allowAnimate && starred);
        TextView textView = (TextView) mAddScheduleButton.findViewById(R.id.add_schedule_text);
        textView.setText(starred ? R.string.remove_from_schedule : R.string.add_to_schedule);
        mAddScheduleButton.setContentDescription(
                getString(starred ? R.string.remove_from_schedule_desc : R.string.add_to_schedule_desc));
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private void setOrAnimateIconTo(final ImageView imageView, final int imageResId, boolean animate) {
        if (UIUtils.hasICS() && imageView.getTag() != null) {
            if (imageView.getTag() instanceof Animator) {
                Animator anim = (Animator) imageView.getTag();
                anim.end();
                imageView.setAlpha(1f);
            }
        }

        animate = animate && UIUtils.hasICS();
        if (animate) {
            int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);

            Animator outAnimator = ObjectAnimator.ofFloat(imageView, View.ALPHA, 0f);
            outAnimator.setDuration(duration / 2);
            outAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    imageView.setImageResource(imageResId);
                }
            });

            AnimatorSet inAnimator = new AnimatorSet();
            inAnimator.setDuration(duration);
            inAnimator.playTogether(ObjectAnimator.ofFloat(imageView, View.ALPHA, 1f),
                    ObjectAnimator.ofFloat(imageView, View.SCALE_X, 0f, 1f),
                    ObjectAnimator.ofFloat(imageView, View.SCALE_Y, 0f, 1f));

            AnimatorSet set = new AnimatorSet();
            set.playSequentially(outAnimator, inAnimator);
            set.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    imageView.setTag(null);
                }
            });
            imageView.setTag(set);
            set.start();
        } else {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    imageView.setImageResource(imageResId);
                }
            });
        }
    }

    private void setupShareMenuItemDeferred() {
        mDeferredUiOperations.add(new Runnable() {
            @Override
            public void run() {
                new SessionsHelper(getActivity()).tryConfigureShareMenuItem(mShareMenuItem, R.string.share_template,
                        mTitleString, mHashtags, mUrl);
            }
        });
        tryExecuteDeferredUiOperations();
    }

    private void tryExecuteDeferredUiOperations() {
        if (mStarMenuItem != null && mSocialStreamMenuItem != null) {
            for (Runnable r : mDeferredUiOperations) {
                r.run();
            }
            mDeferredUiOperations.clear();
        }
    }

    private void onSpeakersQueryComplete(Cursor cursor) {
        mSpeakersCursor = true;
        final ViewGroup speakersGroup = (ViewGroup) mRootView.findViewById(R.id.session_speakers_block);

        // Remove all existing speakers (everything but first child, which is the header)
        for (int i = speakersGroup.getChildCount() - 1; i >= 1; i--) {
            speakersGroup.removeViewAt(i);
        }

        final LayoutInflater inflater = getActivity().getLayoutInflater();

        boolean hasSpeakers = false;

        while (cursor.moveToNext()) {
            final String speakerName = cursor.getString(SpeakersQuery.SPEAKER_NAME);
            if (TextUtils.isEmpty(speakerName)) {
                continue;
            }

            final String speakerImageUrl = cursor.getString(SpeakersQuery.SPEAKER_IMAGE_URL);
            final String speakerCompany = cursor.getString(SpeakersQuery.SPEAKER_COMPANY);
            final String speakerUrl = cursor.getString(SpeakersQuery.SPEAKER_URL);
            final String speakerAbstract = cursor.getString(SpeakersQuery.SPEAKER_ABSTRACT);

            String speakerHeader = speakerName;
            if (!TextUtils.isEmpty(speakerCompany)) {
                speakerHeader += ", " + speakerCompany;
            }

            final View speakerView = inflater.inflate(R.layout.speaker_detail, speakersGroup, false);
            final TextView speakerHeaderView = (TextView) speakerView.findViewById(R.id.speaker_header);
            final ImageView speakerImageView = (ImageView) speakerView.findViewById(R.id.speaker_image);
            final TextView speakerAbstractView = (TextView) speakerView.findViewById(R.id.speaker_abstract);

            if (!TextUtils.isEmpty(speakerImageUrl) && mImageLoader != null) {
                mImageLoader.get(UIUtils.getConferenceImageUrl(speakerImageUrl), speakerImageView);
            }

            speakerHeaderView.setText(speakerHeader);
            speakerImageView.setContentDescription(getString(R.string.speaker_googleplus_profile, speakerHeader));
            UIUtils.setTextMaybeHtml(speakerAbstractView, speakerAbstract);

            if (!TextUtils.isEmpty(speakerUrl)) {
                speakerImageView.setEnabled(true);
                speakerImageView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Intent speakerProfileIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(speakerUrl));
                        speakerProfileIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
                        startActivity(speakerProfileIntent);
                    }
                });
            } else {
                speakerImageView.setEnabled(false);
                speakerImageView.setOnClickListener(null);
            }

            speakersGroup.addView(speakerView);
            hasSpeakers = true;
            mHasSummaryContent = true;
        }

        speakersGroup.setVisibility(hasSpeakers ? View.VISIBLE : View.GONE);

        // Show empty message when all data is loaded, and nothing to show
        if (mSessionCursor && !mHasSummaryContent) {
            mRootView.findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.session_detail, menu);
        mStarMenuItem = menu.findItem(R.id.menu_star);
        mStarMenuItem.setVisible(false); // functionality taken care of by button
        mSocialStreamMenuItem = menu.findItem(R.id.menu_social_stream);
        mShareMenuItem = menu.findItem(R.id.menu_share);
        tryExecuteDeferredUiOperations();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        SessionsHelper helper = new SessionsHelper(getActivity());
        switch (item.getItemId()) {
        case R.id.menu_star:
            setSessionStarred(!mStarred, true);
            return true;

        case R.id.menu_share:
            // On ICS+ devices, we normally won't reach this as ShareActionProvider will handle
            // sharing.
            helper.shareSession(getActivity(), R.string.share_template, mTitleString, mHashtags, mUrl);
            return true;

        case R.id.menu_social_stream:
            LOGD("Tracker", "Stream: " + mTitleString);

            helper.startSocialStream(mHashtags);
            return true;
        }
        return false;
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
    }

    @Override
    public void onDestroyOptionsMenu() {
    }

    /*
     * Event structure:
     * Category -> "Session Details"
     * Action -> Link Text
     * Label -> Session's Title
     * Value -> 0.
     */
    void fireLinkEvent(int actionId) {
        LOGD("Tracker", getString(actionId) + ": " + mTitleString);
    }

    void setSessionStarred(boolean star, boolean allowAnimate) {
        SessionsHelper helper = new SessionsHelper(getActivity());
        showStarred(star, allowAnimate);
        helper.setSessionStarred(mSessionUri, star, mTitleString);
        LOGD("Tracker", (star ? "Starred: " : "Unstarred: ") + mTitleString);
    }

    /**
     * {@link com.conferenceengineer.android.iosched.provider.ScheduleContract.Sessions} query parameters.
     */
    private interface SessionsQuery {
        int _TOKEN = 0x1;

        String[] PROJECTION = { ScheduleContract.Blocks.BLOCK_START, ScheduleContract.Blocks.BLOCK_END,
                ScheduleContract.Sessions.SESSION_LEVEL, ScheduleContract.Sessions.SESSION_TITLE,
                ScheduleContract.Sessions.SESSION_ABSTRACT, ScheduleContract.Sessions.SESSION_REQUIREMENTS,
                ScheduleContract.Sessions.SESSION_STARRED, ScheduleContract.Sessions.SESSION_HASHTAGS,
                ScheduleContract.Sessions.SESSION_URL, ScheduleContract.Sessions.SESSION_YOUTUBE_URL,
                ScheduleContract.Sessions.SESSION_PDF_URL, ScheduleContract.Sessions.SESSION_NOTES_URL,
                ScheduleContract.Sessions.SESSION_MODERATOR_URL, ScheduleContract.Sessions.ROOM_ID,
                ScheduleContract.Rooms.ROOM_NAME, ScheduleContract.Sessions.SESSION_TYPE, };

        int BLOCK_START = 0;
        int BLOCK_END = 1;
        int LEVEL = 2;
        int TITLE = 3;
        int ABSTRACT = 4;
        int REQUIREMENTS = 5;
        int STARRED = 6;
        int HASHTAGS = 7;
        int URL = 8;
        int YOUTUBE_URL = 9;
        int PDF_URL = 10;
        int NOTES_URL = 11;
        int MODERATOR_URL = 12;
        int ROOM_ID = 13;
        int ROOM_NAME = 14;
        int TYPE = 15;

        int[] LINKS_INDICES = { URL, YOUTUBE_URL, MODERATOR_URL, PDF_URL, NOTES_URL, };

        int[] LINKS_TITLES = { R.string.session_link_main, R.string.session_link_youtube,
                R.string.session_link_moderator, R.string.session_link_pdf, R.string.session_link_notes, };
    }

    private interface SpeakersQuery {
        int _TOKEN = 0x3;

        String[] PROJECTION = { ScheduleContract.Speakers.SPEAKER_NAME, ScheduleContract.Speakers.SPEAKER_IMAGE_URL,
                ScheduleContract.Speakers.SPEAKER_COMPANY, ScheduleContract.Speakers.SPEAKER_ABSTRACT,
                ScheduleContract.Speakers.SPEAKER_URL, };

        int SPEAKER_NAME = 0;
        int SPEAKER_IMAGE_URL = 1;
        int SPEAKER_COMPANY = 2;
        int SPEAKER_ABSTRACT = 3;
        int SPEAKER_URL = 4;
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle data) {
        CursorLoader loader = null;
        if (id == SessionsQuery._TOKEN) {
            loader = new CursorLoader(getActivity(), mSessionUri, SessionsQuery.PROJECTION, null, null, null);
        } else if (id == SpeakersQuery._TOKEN && mSessionUri != null) {
            Uri speakersUri = ScheduleContract.Sessions.buildSpeakersDirUri(mSessionId);
            loader = new CursorLoader(getActivity(), speakersUri, SpeakersQuery.PROJECTION, null, null, null);
        }
        return loader;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        if (!isAdded()) {
            return;
        }

        if (loader.getId() == SessionsQuery._TOKEN) {
            onSessionQueryComplete(cursor);
            if (mType != null && !ScheduleContract.Sessions.SESSION_TYPE_KEYNOTE.equals(mType)) {
                mAddScheduleButton.setVisibility(View.VISIBLE);
            }
        } else if (loader.getId() == SpeakersQuery._TOKEN) {
            onSpeakersQueryComplete(cursor);
        } else {
            cursor.close();
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }
}