com.gdgdevfest.android.apps.devfestbcn.ui.SessionLivestreamActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.gdgdevfest.android.apps.devfestbcn.ui.SessionLivestreamActivity.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.gdgdevfest.android.apps.devfestbcn.ui;

import static com.gdgdevfest.android.apps.devfestbcn.util.LogUtils.LOGD;
import static com.gdgdevfest.android.apps.devfestbcn.util.LogUtils.LOGE;
import static com.gdgdevfest.android.apps.devfestbcn.util.LogUtils.makeLogTag;

import java.util.ArrayList;
import java.util.HashMap;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Presentation;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Color;
import android.media.MediaRouter;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.BaseColumns;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.app.ActionBar;
import android.text.TextUtils;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TabHost;
import android.widget.TabWidget;
import android.widget.TextView;
import android.widget.Toast;

import com.gdgdevfest.android.apps.devfestbcn.Config;
import com.gdgdevfest.android.apps.devfestbcn.R;
import com.gdgdevfest.android.apps.devfestbcn.provider.ScheduleContract;
import com.gdgdevfest.android.apps.devfestbcn.provider.ScheduleContract.Sessions;
import com.gdgdevfest.android.apps.devfestbcn.provider.ScheduleContract.Tracks;
import com.gdgdevfest.android.apps.devfestbcn.util.SessionsHelper;
import com.gdgdevfest.android.apps.devfestbcn.util.UIUtils;
import com.google.analytics.tracking.android.EasyTracker;
import com.google.android.youtube.player.YouTubeInitializationResult;
import com.google.android.youtube.player.YouTubePlayer;
import com.google.android.youtube.player.YouTubePlayerSupportFragment;

/**
 * An activity that displays the session live stream video which is pulled in from YouTube. The
 * UI adapts for both phone and tablet. As we want to prevent the YouTube player from restarting
 * and buffering again on orientation change, we handle configuration changes manually.
 */
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class SessionLivestreamActivity extends BaseActivity implements LoaderCallbacks<Cursor>,
        YouTubePlayer.OnInitializedListener, YouTubePlayer.PlayerStateChangeListener,
        YouTubePlayer.OnFullscreenListener, ActionBar.OnNavigationListener {

    private static final String TAG = makeLogTag(SessionLivestreamActivity.class);
    private static final String EXTRA_PREFIX = "com.gdgdevfest.android.apps.devfestbcn.extra.";
    private static final int YOUTUBE_RECOVERY_RESULT = 1;
    private static final String LOADER_SESSIONS_ARG = "futureSessions";

    public static final String EXTRA_YOUTUBE_URL = EXTRA_PREFIX + "youtube_url";
    public static final String EXTRA_TRACK = EXTRA_PREFIX + "track";
    public static final String EXTRA_TITLE = EXTRA_PREFIX + "title";
    public static final String EXTRA_ABSTRACT = EXTRA_PREFIX + "abstract";
    public static final String KEYNOTE_TRACK_NAME = "Keynote";

    private static final String TAG_SESSION_SUMMARY = "session_summary";
    private static final String TAG_CAPTIONS = "captions";
    private static final int TABNUM_SESSION_SUMMARY = 0;
    private static final int TABNUM_SOCIAL_STREAM = 1;
    private static final int TABNUM_LIVE_CAPTIONS = 2;
    private static final String EXTRA_TAB_STATE = "tag";

    private static final int STREAM_REFRESH_TIME = 5 * 60 * 1000; // 5 minutes

    private boolean mIsTablet;
    private boolean mIsFullscreen = false;
    private boolean mLoadFromExtras = false;
    private boolean mTrackPlay = true;

    private TabHost mTabHost;
    private TabsAdapter mTabsAdapter;
    private YouTubePlayer mYouTubePlayer;
    private YouTubePlayerSupportFragment mYouTubeFragment;
    private LinearLayout mPlayerContainer;
    private String mYouTubeVideoId;
    private LinearLayout mMainLayout;
    private LinearLayout mVideoLayout;
    private LinearLayout mExtraLayout;
    private FrameLayout mPresentationControls;
    private FrameLayout mSummaryLayout;
    private FrameLayout mFullscreenCaptions;
    private MenuItem mCaptionsMenuItem;
    private MenuItem mShareMenuItem;
    private MenuItem mPresentationMenuItem;
    private Runnable mShareMenuDeferredSetup;
    private Runnable mSessionSummaryDeferredSetup;
    private SessionShareData mSessionShareData;
    private boolean mSessionsFound;
    private boolean isKeynote = false;
    private Handler mHandler = new Handler();
    private int mYouTubeFullscreenFlags;

    private LivestreamAdapter mLivestreamAdapter;

    private Uri mSessionUri;
    private String mSessionId;
    private String mTrackName;

    private MediaRouter mMediaRouter;
    private YouTubePresentation mPresentation;
    private MediaRouter.SimpleCallback mMediaRouterCallback;

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (UIUtils.hasICS()) {
            // We can't use this mode on HC as compatible ActionBar doesn't work well with the YT
            // player in full screen mode (no overlays allowed).
            requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
        }
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_session_livestream);
        mIsTablet = UIUtils.isHoneycombTablet(this);

        // Set up YouTube player
        mYouTubeFragment = (YouTubePlayerSupportFragment) getSupportFragmentManager()
                .findFragmentById(R.id.livestream_player);
        mYouTubeFragment.initialize(Config.YOUTUBE_API_KEY, this);

        // Views that are common over all layouts
        mMainLayout = (LinearLayout) findViewById(R.id.livestream_mainlayout);
        mPresentationControls = (FrameLayout) findViewById(R.id.presentation_controls);
        adjustMainLayoutForActionBar();
        mPlayerContainer = (LinearLayout) findViewById(R.id.livestream_player_container);
        mFullscreenCaptions = (FrameLayout) findViewById(R.id.fullscreen_captions);
        final LayoutParams params = (LayoutParams) mFullscreenCaptions.getLayoutParams();
        params.setMargins(0, getActionBarHeightPx(), 0, getActionBarHeightPx());
        mFullscreenCaptions.setLayoutParams(params);

        ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
        viewPager.setOffscreenPageLimit(2);
        if (!mIsTablet) {
            viewPager.setPageMarginDrawable(R.drawable.grey_border_inset_lr);
        }
        viewPager.setPageMargin(getResources().getDimensionPixelSize(R.dimen.page_margin_width));

        // Set up tabs w/ViewPager
        mTabHost = (TabHost) findViewById(android.R.id.tabhost);
        mTabHost.setup();
        mTabsAdapter = new TabsAdapter(this, mTabHost, viewPager);

        if (mIsTablet) {
            // Tablet UI specific views
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.livestream_summary, new SessionSummaryFragment(), TAG_SESSION_SUMMARY).commit();
            mVideoLayout = (LinearLayout) findViewById(R.id.livestream_videolayout);
            mExtraLayout = (LinearLayout) findViewById(R.id.livestream_extralayout);
            mSummaryLayout = (FrameLayout) findViewById(R.id.livestream_summary);
        } else {
            // Handset UI specific views
            mTabsAdapter.addTab(getString(R.string.session_livestream_info), new SessionSummaryFragment(),
                    TABNUM_SESSION_SUMMARY);
        }

        mTabsAdapter.addTab(getString(R.string.title_stream), new SocialStreamFragment(), TABNUM_SOCIAL_STREAM);
        mTabsAdapter.addTab(getString(R.string.session_livestream_captions), new SessionLiveCaptionsFragment(),
                TABNUM_LIVE_CAPTIONS);

        if (savedInstanceState != null) {
            mTabHost.setCurrentTabByTag(savedInstanceState.getString(EXTRA_TAB_STATE));
        }

        // Reload all other data in this activity
        reloadFromIntent(getIntent());

        // Update layout based on current configuration
        updateLayout(getResources().getConfiguration());

        // Set up action bar
        if (!mLoadFromExtras) {
            // Start sessions query to populate action bar navigation spinner
            getSupportLoaderManager().initLoader(SessionsQuery._TOKEN, null, this);

            // Set up action bar
            mLivestreamAdapter = new LivestreamAdapter(getSupportActionBar().getThemedContext());
            final ActionBar actionBar = getSupportActionBar();
            actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
            actionBar.setListNavigationCallbacks(mLivestreamAdapter, this);
            actionBar.setDisplayShowTitleEnabled(false);
        }

        // Media Router and Presentation set up
        if (UIUtils.hasJellyBeanMR1()) {
            mMediaRouterCallback = new MediaRouter.SimpleCallback() {
                @Override
                public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) {
                    LOGD(TAG, "onRouteSelected: type=" + type + ", info=" + info);
                    updatePresentation();
                }

                @Override
                public void onRouteUnselected(MediaRouter router, int type, MediaRouter.RouteInfo info) {
                    LOGD(TAG, "onRouteUnselected: type=" + type + ", info=" + info);
                    updatePresentation();
                }

                @Override
                public void onRoutePresentationDisplayChanged(MediaRouter router, MediaRouter.RouteInfo info) {
                    LOGD(TAG, "onRoutePresentationDisplayChanged: info=" + info);
                    updatePresentation();
                }
            };

            mMediaRouter = (MediaRouter) getSystemService(Context.MEDIA_ROUTER_SERVICE);
            final ImageButton playPauseButton = (ImageButton) findViewById(R.id.play_pause_button);
            playPauseButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (mYouTubePlayer != null) {
                        if (mYouTubePlayer.isPlaying()) {
                            mYouTubePlayer.pause();
                            playPauseButton.setImageResource(R.drawable.ic_livestream_play);
                        } else {
                            mYouTubePlayer.play();
                            playPauseButton.setImageResource(R.drawable.ic_livestream_pause);
                        }
                    }
                }
            });
            updatePresentation();
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    @Override
    protected void onResume() {
        super.onResume();
        if (mSessionSummaryDeferredSetup != null) {
            mSessionSummaryDeferredSetup.run();
            mSessionSummaryDeferredSetup = null;
        }
        mHandler.postDelayed(mStreamRefreshRunnable, STREAM_REFRESH_TIME);
        if (UIUtils.hasJellyBeanMR1()) {
            mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mMediaRouterCallback);
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    @Override
    protected void onPause() {
        super.onPause();
        mHandler.removeCallbacks(mStreamRefreshRunnable);
        if (UIUtils.hasJellyBeanMR1()) {
            mMediaRouter.removeCallback(mMediaRouterCallback);
        }
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    @Override
    public void onStop() {
        super.onStop();

        // Dismiss the presentation when the activity is not visible
        if (mPresentation != null) {
            LOGD(TAG, "Dismissing presentation because the activity is no longer visible.");
            mPresentation.dismiss();
            mPresentation = null;
        }
    }

    /**
     * Reloads all data in the activity and fragments from a given intent
     * @param intent The intent to load from
     */
    private void reloadFromIntent(Intent intent) {
        final String youtubeUrl = intent.getStringExtra(EXTRA_YOUTUBE_URL);
        // Check if youtube url is set as an extra first
        if (youtubeUrl != null) {
            mLoadFromExtras = true;
            String trackName = intent.getStringExtra(EXTRA_TRACK);
            String actionBarTitle;
            if (trackName == null) {
                actionBarTitle = getString(R.string.session_livestream_title);
            } else {
                isKeynote = KEYNOTE_TRACK_NAME.equals(trackName);
                actionBarTitle = trackName + " - " + getString(R.string.session_livestream_title);
            }
            getSupportActionBar().setTitle(actionBarTitle);
            updateSessionViews(youtubeUrl, intent.getStringExtra(EXTRA_TITLE),
                    intent.getStringExtra(EXTRA_ABSTRACT), UIUtils.CONFERENCE_HASHTAG, trackName);
        } else {
            // Otherwise load from session uri
            reloadFromUri(intent.getData());
        }
    }

    /**
     * Reloads all data in the activity and fragments from a given uri
     * @param newUri The session uri to load from
     */
    private void reloadFromUri(Uri newUri) {
        mSessionUri = newUri;
        if (mSessionUri != null && mSessionUri.getPathSegments().size() >= 2) {
            mSessionId = Sessions.getSessionId(mSessionUri);
            getSupportLoaderManager().restartLoader(SessionSummaryQuery._TOKEN, null, this);
        } else {
            // No session uri, get out
            mSessionUri = null;
            finish();
        }
    }

    /**
     * Helper method to start this activity using only extras (rather than session uri).
     * @param context The package context
     * @param youtubeUrl The youtube url or video id to load
     * @param track The track title (appears as part of action bar title), can be null
     * @param title The title to show in the session info fragment, can be null
     * @param sessionAbstract The session abstract to show in the session info fragment, can be null
     */
    public static void startFromExtras(Context context, String youtubeUrl, String track, String title,
            String sessionAbstract) {
        if (youtubeUrl == null) {
            return;
        }
        final Intent i = new Intent();
        i.setClass(context, SessionLivestreamActivity.class);
        i.putExtra(EXTRA_YOUTUBE_URL, youtubeUrl);
        i.putExtra(EXTRA_TRACK, track);
        i.putExtra(EXTRA_TITLE, title);
        i.putExtra(EXTRA_ABSTRACT, sessionAbstract);
        context.startActivity(i);
    }

    /**
     * Start a keynote live stream from extras. This sets the track name correctly so captions
     * will be correctly displayed.
     *
     * This will play the video given in youtubeUrl, however if a number ranging from 1-99 is
     * passed in this param instead it will be parsed and used to lookup a youtube ID from a
     * google spreadsheet using the param as the spreadsheet row number.
     */
    public static void startKeynoteFromExtras(Context context, String youtubeUrl, String title,
            String sessionAbstract) {
        startFromExtras(context, youtubeUrl, KEYNOTE_TRACK_NAME, title, sessionAbstract);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mTabHost != null) {
            outState.putString(EXTRA_TAB_STATE, mTabHost.getCurrentTabTag());
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.session_livestream, menu);
        mCaptionsMenuItem = menu.findItem(R.id.menu_captions);
        mPresentationMenuItem = menu.findItem(R.id.menu_presentation);
        mPresentationMenuItem.setVisible(mPresentation != null);
        updatePresentationMenuItem(mPresentation != null);
        mShareMenuItem = menu.findItem(R.id.menu_share);
        if (mShareMenuDeferredSetup != null) {
            mShareMenuDeferredSetup.run();
        }
        if (!mIsTablet && Configuration.ORIENTATION_LANDSCAPE == getResources().getConfiguration().orientation) {
            mCaptionsMenuItem.setVisible(true);
        }
        return true;
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_captions:
            if (mIsFullscreen) {
                if (mFullscreenCaptions.getVisibility() == View.GONE) {
                    mFullscreenCaptions.setVisibility(View.VISIBLE);
                    SessionLiveCaptionsFragment captionsFragment;
                    captionsFragment = (SessionLiveCaptionsFragment) getSupportFragmentManager()
                            .findFragmentByTag(TAG_CAPTIONS);
                    if (captionsFragment == null) {
                        captionsFragment = new SessionLiveCaptionsFragment();
                        captionsFragment.setDarkTheme(true);
                        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                        ft.add(R.id.fullscreen_captions, captionsFragment, TAG_CAPTIONS);
                        ft.commit();
                    }
                    captionsFragment.setTrackName(mTrackName);
                    return true;
                }
            }
            mFullscreenCaptions.setVisibility(View.GONE);
            break;
        case R.id.menu_share:
            if (mSessionShareData != null) {
                new SessionsHelper(this).shareSession(this, R.string.share_livestream_template,
                        mSessionShareData.title, mSessionShareData.hashtag, mSessionShareData.sessionUrl);
                return true;
            }
            break;
        case R.id.menu_presentation:
            if (mPresentation != null) {
                mPresentation.dismiss();
            } else {
                updatePresentation();
            }
            break;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        // Need to handle configuration changes so as not to interrupt YT player and require
        // buffering again
        updateLayout(newConfig);
        super.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onNavigationItemSelected(int itemPosition, long itemId) {
        final Cursor cursor = (Cursor) mLivestreamAdapter.getItem(itemPosition);
        String trackName = cursor.getString(SessionsQuery.TRACK_NAME);
        int trackColor = cursor.getInt(SessionsQuery.TRACK_COLOR);
        setActionBarTrackIcon(trackName, trackColor);

        final String sessionId = cursor.getString(SessionsQuery.SESSION_ID);
        if (sessionId != null) {
            reloadFromUri(Sessions.buildSessionUri(sessionId));
            return true;
        }
        return false;
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        switch (id) {
        case SessionSummaryQuery._TOKEN:
            return new CursorLoader(this, Sessions.buildWithTracksUri(mSessionId), SessionSummaryQuery.PROJECTION,
                    null, null, null);
        case SessionsQuery._TOKEN:
            boolean futureSessions = false;
            if (args != null) {
                futureSessions = args.getBoolean(LOADER_SESSIONS_ARG, false);
            }
            final long currentTime = UIUtils.getCurrentTime(this);
            String selection = Sessions.LIVESTREAM_SELECTION + " and ";
            String[] selectionArgs;
            if (!futureSessions) {
                selection += Sessions.AT_TIME_SELECTION;
                selectionArgs = Sessions.buildAtTimeSelectionArgs(currentTime);
            } else {
                selection += Sessions.UPCOMING_SELECTION;
                selectionArgs = Sessions.buildUpcomingSelectionArgs(currentTime);
            }
            return new CursorLoader(this, Sessions.buildWithTracksUri(), SessionsQuery.PROJECTION, selection,
                    selectionArgs, null);
        }
        return null;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        switch (loader.getId()) {
        case SessionSummaryQuery._TOKEN:
            loadSession(data);
            break;

        case SessionsQuery._TOKEN:
            mLivestreamAdapter.swapCursor(data);
            if (data != null && data.getCount() > 0) {
                mSessionsFound = true;
                final int selected = locateSelectedItem(data);
                getSupportActionBar().setSelectedNavigationItem(selected);
            } else if (mSessionsFound) {
                mSessionsFound = false;
                final Bundle bundle = new Bundle();
                bundle.putBoolean(LOADER_SESSIONS_ARG, true);
                getSupportLoaderManager().restartLoader(SessionsQuery._TOKEN, bundle, this);
            } else {
                finish();
            }
            break;
        }
    }

    /**
     * Locates which item should be selected in the action bar drop-down spinner based on the
     * current active session uri
     * @param data The data
     * @return The row num of the item that should be selected or 0 if not found
     */
    private int locateSelectedItem(Cursor data) {
        int selected = 0;
        if (data != null && (mSessionId != null || mTrackName != null)) {
            final boolean findNextSessionByTrack = mTrackName != null;
            while (data.moveToNext()) {
                if (findNextSessionByTrack) {
                    if (mTrackName.equals(data.getString(SessionsQuery.TRACK_NAME))) {
                        selected = data.getPosition();
                        mTrackName = null;
                        break;
                    }
                } else {
                    if (mSessionId.equals(data.getString(SessionsQuery.SESSION_ID))) {
                        selected = data.getPosition();
                    }
                }
            }
        }
        return selected;
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        switch (loader.getId()) {
        case SessionsQuery._TOKEN:
            mLivestreamAdapter.swapCursor(null);
            break;
        }
    }

    @Override
    public void onFullscreen(boolean fullscreen) {
        layoutFullscreenVideo(fullscreen);
    }

    @Override
    public void onLoading() {
    }

    @Override
    public void onLoaded(String s) {
    }

    @Override
    public void onAdStarted() {
    }

    @Override
    public void onVideoStarted() {
    }

    @Override
    public void onVideoEnded() {
    }

    @Override
    public void onError(YouTubePlayer.ErrorReason errorReason) {
        Toast.makeText(this, R.string.session_livestream_error_playback, Toast.LENGTH_LONG).show();
        LOGE(TAG, errorReason.toString());
    }

    private void loadSession(Cursor data) {
        if (data != null && data.moveToFirst()) {
            mTrackName = data.getString(SessionSummaryQuery.TRACK_NAME);
            if (TextUtils.isEmpty(mTrackName)) {
                mTrackName = KEYNOTE_TRACK_NAME;
                isKeynote = true;
            }
            final long currentTime = UIUtils.getCurrentTime(this);
            if (currentTime > data.getLong(SessionSummaryQuery.BLOCK_END)) {
                getSupportLoaderManager().restartLoader(SessionsQuery._TOKEN, null, this);
                return;
            }
            updateTagStreamFragment(data.getString(SessionSummaryQuery.HASHTAGS));
            updateSessionViews(data.getString(SessionSummaryQuery.LIVESTREAM_URL),
                    data.getString(SessionSummaryQuery.TITLE), data.getString(SessionSummaryQuery.ABSTRACT),
                    data.getString(SessionSummaryQuery.HASHTAGS), mTrackName);
        }
    }

    /**
     * Updates views that rely on session data from explicit strings.
     */
    private void updateSessionViews(final String youtubeUrl, final String title, final String sessionAbstract,
            final String hashTag, final String trackName) {

        if (youtubeUrl == null) {
            // Get out, nothing to do here
            finish();
            return;
        }

        mTrackName = trackName;
        String youtubeVideoId = youtubeUrl;
        if (youtubeUrl.startsWith("http")) {
            final Uri youTubeUri = Uri.parse(youtubeUrl);
            youtubeVideoId = youTubeUri.getQueryParameter("v");
        }

        // Play the video
        playVideo(youtubeVideoId);

        if (mTrackPlay) {
            EasyTracker.getTracker().sendView("Live Streaming: " + title);
            LOGD("Tracker", "Live Streaming: " + title);
        }

        final String newYoutubeUrl = Config.YOUTUBE_SHARE_URL_PREFIX + youtubeVideoId;
        mSessionShareData = new SessionShareData(title, hashTag, newYoutubeUrl);
        mShareMenuDeferredSetup = new Runnable() {
            @Override
            public void run() {
                new SessionsHelper(SessionLivestreamActivity.this).tryConfigureShareMenuItem(mShareMenuItem,
                        R.string.share_livestream_template, title, hashTag, newYoutubeUrl);
            }
        };

        if (mShareMenuItem != null) {
            mShareMenuDeferredSetup.run();
            mShareMenuDeferredSetup = null;
        }

        mSessionSummaryDeferredSetup = new Runnable() {
            @Override
            public void run() {
                updateSessionSummaryFragment(title, sessionAbstract);
                updateSessionLiveCaptionsFragment(trackName);
            }
        };

        if (!mLoadFromExtras) {
            mSessionSummaryDeferredSetup.run();
            mSessionSummaryDeferredSetup = null;
        }
    }

    private void updateSessionSummaryFragment(String title, String sessionAbstract) {
        SessionSummaryFragment sessionSummaryFragment = null;
        if (mIsTablet) {
            sessionSummaryFragment = (SessionSummaryFragment) getSupportFragmentManager()
                    .findFragmentByTag(TAG_SESSION_SUMMARY);
        } else {
            if (mTabsAdapter.mFragments.containsKey(TABNUM_SESSION_SUMMARY)) {
                sessionSummaryFragment = (SessionSummaryFragment) mTabsAdapter.mFragments
                        .get(TABNUM_SESSION_SUMMARY);
            }
        }
        if (sessionSummaryFragment != null) {
            sessionSummaryFragment.setSessionSummaryInfo(
                    isKeynote ? getString(R.string.session_livestream_keynote_title, title) : title,
                    (isKeynote && TextUtils.isEmpty(sessionAbstract))
                            ? getString(R.string.session_livestream_keynote_desc)
                            : sessionAbstract);
        }
    }

    private Runnable mStreamRefreshRunnable = new Runnable() {
        @Override
        public void run() {
            if (mTabsAdapter != null && mTabsAdapter.mFragments != null
                    && mTabsAdapter.mFragments.containsKey(TABNUM_SOCIAL_STREAM)) {
                final SocialStreamFragment socialStreamFragment = (SocialStreamFragment) mTabsAdapter.mFragments
                        .get(TABNUM_SOCIAL_STREAM);
                socialStreamFragment.refresh();
            }
            mHandler.postDelayed(mStreamRefreshRunnable, STREAM_REFRESH_TIME);
        }
    };

    private void updateTagStreamFragment(String trackHashTag) {
        String hashTags = UIUtils.CONFERENCE_HASHTAG;
        if (!TextUtils.isEmpty(trackHashTag)) {
            hashTags += " " + trackHashTag;
        }

        if (mTabsAdapter.mFragments.containsKey(TABNUM_SOCIAL_STREAM)) {
            final SocialStreamFragment socialStreamFragment = (SocialStreamFragment) mTabsAdapter.mFragments
                    .get(TABNUM_SOCIAL_STREAM);
            socialStreamFragment.refresh(hashTags);
        }
    }

    private void updateSessionLiveCaptionsFragment(String trackName) {
        if (mTabsAdapter.mFragments.containsKey(TABNUM_LIVE_CAPTIONS)) {
            final SessionLiveCaptionsFragment captionsFragment = (SessionLiveCaptionsFragment) mTabsAdapter.mFragments
                    .get(TABNUM_LIVE_CAPTIONS);
            captionsFragment.setTrackName(trackName);
        }
    }

    private void playVideo(String videoId) {
        if ((TextUtils.isEmpty(mYouTubeVideoId) || !mYouTubeVideoId.equals(videoId))
                && !TextUtils.isEmpty(videoId)) {
            mYouTubeVideoId = videoId;
            if (mYouTubePlayer != null) {
                mYouTubePlayer.loadVideo(mYouTubeVideoId);
            }
            mTrackPlay = true;

            if (mSessionShareData != null) {
                mSessionShareData.sessionUrl = Config.YOUTUBE_SHARE_URL_PREFIX + mYouTubeVideoId;
            }
        }
    }

    @Override
    public Intent getParentActivityIntent() {
        if (mLoadFromExtras || isKeynote || mSessionUri == null) {
            return new Intent(this, HomeActivity.class);
        } else {
            return new Intent(Intent.ACTION_VIEW, mSessionUri);
        }
    }

    @Override
    public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer youTubePlayer,
            boolean wasRestored) {

        // Set up YouTube player
        mYouTubePlayer = youTubePlayer;
        mYouTubePlayer.setPlayerStateChangeListener(this);
        mYouTubePlayer.setFullscreen(mIsFullscreen);
        mYouTubePlayer.setOnFullscreenListener(this);

        // YouTube player flags: use a custom full screen layout; let the YouTube player control
        // the system UI (hiding navigation controls, ActionBar etc); and let the YouTube player
        // handle the orientation state of the activity.
        mYouTubeFullscreenFlags = YouTubePlayer.FULLSCREEN_FLAG_CUSTOM_LAYOUT
                | YouTubePlayer.FULLSCREEN_FLAG_CONTROL_SYSTEM_UI
                | YouTubePlayer.FULLSCREEN_FLAG_CONTROL_ORIENTATION;

        // On smaller screen devices always switch to full screen in landscape mode
        if (!mIsTablet) {
            mYouTubeFullscreenFlags |= YouTubePlayer.FULLSCREEN_FLAG_ALWAYS_FULLSCREEN_IN_LANDSCAPE;
        }

        // Apply full screen flags
        setFullscreenFlags();

        // If a presentation display is available, set YouTube player style to minimal
        if (mPresentation != null) {
            mYouTubePlayer.setPlayerStyle(YouTubePlayer.PlayerStyle.MINIMAL);
        }

        // Load the requested video
        if (!TextUtils.isEmpty(mYouTubeVideoId)) {
            mYouTubePlayer.loadVideo(mYouTubeVideoId);
        }

        updatePresentation();
    }

    @Override
    public void onInitializationFailure(YouTubePlayer.Provider provider, YouTubeInitializationResult result) {
        LOGE(TAG, result.toString());
        if (result.isUserRecoverableError()) {
            result.getErrorDialog(this, YOUTUBE_RECOVERY_RESULT).show();
        } else {
            String errorMessage = getString(R.string.session_livestream_error_init, result.toString());
            Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == YOUTUBE_RECOVERY_RESULT) {
            if (mYouTubeFragment != null) {
                mYouTubeFragment.initialize(Config.YOUTUBE_API_KEY, this);
            }
        }
    }

    /**
     * Updates the layout based on the provided configuration
     */
    private void updateLayout(Configuration config) {
        if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            if (mIsTablet) {
                layoutTabletForLandscape();
            } else {
                layoutPhoneForLandscape();
            }
        } else {
            if (mIsTablet) {
                layoutTabletForPortrait();
            } else {
                layoutPhoneForPortrait();
            }
        }
    }

    private void layoutPhoneForLandscape() {
        layoutFullscreenVideo(true);
    }

    private void layoutPhoneForPortrait() {
        layoutFullscreenVideo(false);
        if (mTabsAdapter.mFragments.containsKey(TABNUM_SESSION_SUMMARY)) {
            ((SessionSummaryFragment) mTabsAdapter.mFragments.get(TABNUM_SESSION_SUMMARY)).updateViews();
        }
    }

    private void layoutTabletForLandscape() {
        mMainLayout.setOrientation(LinearLayout.HORIZONTAL);

        final LayoutParams videoLayoutParams = (LayoutParams) mVideoLayout.getLayoutParams();
        videoLayoutParams.height = LayoutParams.MATCH_PARENT;
        videoLayoutParams.width = 0;
        videoLayoutParams.weight = 1;
        mVideoLayout.setLayoutParams(videoLayoutParams);

        final LayoutParams extraLayoutParams = (LayoutParams) mExtraLayout.getLayoutParams();
        extraLayoutParams.height = LayoutParams.MATCH_PARENT;
        extraLayoutParams.width = 0;
        extraLayoutParams.weight = 1;
        mExtraLayout.setLayoutParams(extraLayoutParams);
    }

    private void layoutTabletForPortrait() {
        mMainLayout.setOrientation(LinearLayout.VERTICAL);

        final LayoutParams videoLayoutParams = (LayoutParams) mVideoLayout.getLayoutParams();
        videoLayoutParams.width = LayoutParams.MATCH_PARENT;
        if (mLoadFromExtras) {
            // Loading from extras, let the top fragment wrap_content
            videoLayoutParams.height = LayoutParams.WRAP_CONTENT;
            videoLayoutParams.weight = 0;
        } else {
            // Otherwise the session description will be longer, give it some space
            videoLayoutParams.height = 0;
            videoLayoutParams.weight = 7;
        }
        mVideoLayout.setLayoutParams(videoLayoutParams);

        final LayoutParams extraLayoutParams = (LayoutParams) mExtraLayout.getLayoutParams();
        extraLayoutParams.width = LayoutParams.MATCH_PARENT;
        extraLayoutParams.height = 0;
        extraLayoutParams.weight = 5;
        mExtraLayout.setLayoutParams(extraLayoutParams);
    }

    private void layoutFullscreenVideo(boolean fullscreen) {
        if (mIsFullscreen != fullscreen) {
            mIsFullscreen = fullscreen;
            if (mIsTablet) {
                // Tablet specific full screen layout
                layoutTabletFullscreen(fullscreen);
            } else {
                // Phone specific full screen layout
                layoutPhoneFullscreen(fullscreen);
            }

            // Full screen layout changes for all form factors
            final LayoutParams params = (LayoutParams) mPlayerContainer.getLayoutParams();
            if (fullscreen) {
                if (mCaptionsMenuItem != null) {
                    mCaptionsMenuItem.setVisible(true);
                }
                params.height = LayoutParams.MATCH_PARENT;
                mMainLayout.setPadding(0, 0, 0, 0);
            } else {
                getSupportActionBar().show();
                if (mCaptionsMenuItem != null) {
                    mCaptionsMenuItem.setVisible(false);
                }
                mFullscreenCaptions.setVisibility(View.GONE);
                params.height = LayoutParams.WRAP_CONTENT;
                adjustMainLayoutForActionBar();
            }
            View youTubePlayerView = mYouTubeFragment.getView();
            if (youTubePlayerView != null) {
                ViewGroup.LayoutParams playerParams = mYouTubeFragment.getView().getLayoutParams();
                playerParams.height = fullscreen ? LayoutParams.MATCH_PARENT : LayoutParams.WRAP_CONTENT;
                youTubePlayerView.setLayoutParams(playerParams);
            }
            mPlayerContainer.setLayoutParams(params);
        }
    }

    private void adjustMainLayoutForActionBar() {
        if (UIUtils.hasICS()) {
            // On ICS+ we use FEATURE_ACTION_BAR_OVERLAY so full screen mode doesn't need to
            // re-adjust layouts when hiding action bar. To account for this we add action bar
            // height pack to the padding when not in full screen mode.
            mMainLayout.setPadding(0, getActionBarHeightPx(), 0, 0);
        }
    }

    /**
     * Adjusts tablet layouts for full screen video.
     *
     * @param fullscreen True to layout in full screen, false to switch to regular layout
     */
    private void layoutTabletFullscreen(boolean fullscreen) {
        if (fullscreen) {
            mExtraLayout.setVisibility(View.GONE);
            mSummaryLayout.setVisibility(View.GONE);
            mVideoLayout.setPadding(0, 0, 0, 0);
            final LayoutParams videoLayoutParams = (LayoutParams) mVideoLayout.getLayoutParams();
            videoLayoutParams.setMargins(0, 0, 0, 0);
            mVideoLayout.setLayoutParams(videoLayoutParams);
        } else {
            final int padding = getResources().getDimensionPixelSize(R.dimen.multipane_half_padding);
            mExtraLayout.setVisibility(View.VISIBLE);
            mSummaryLayout.setVisibility(View.VISIBLE);
            mVideoLayout.setBackgroundResource(R.drawable.grey_frame_on_white);
            final LayoutParams videoLayoutParams = (LayoutParams) mVideoLayout.getLayoutParams();
            videoLayoutParams.setMargins(padding, padding, padding, padding);
            mVideoLayout.setLayoutParams(videoLayoutParams);
        }
    }

    /**
     * Adjusts phone layouts for full screen video.
     */
    private void layoutPhoneFullscreen(boolean fullscreen) {
        if (fullscreen) {
            mTabHost.setVisibility(View.GONE);
            if (mYouTubePlayer != null) {
                mYouTubePlayer.setFullscreen(true);
            }
        } else {
            mTabHost.setVisibility(View.VISIBLE);
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
    }

    private int getActionBarHeightPx() {
        int[] attrs = new int[] { android.R.attr.actionBarSize };
        return (int) getTheme().obtainStyledAttributes(attrs).getDimension(0, 0f);
    }

    /**
     * Adapter that backs the action bar drop-down spinner.
     */
    private class LivestreamAdapter extends CursorAdapter {
        LayoutInflater mLayoutInflater;

        public LivestreamAdapter(Context context) {
            super(context, null, 0);
            if (UIUtils.hasICS()) {
                mLayoutInflater = (LayoutInflater) getSupportActionBar().getThemedContext()
                        .getSystemService(LAYOUT_INFLATER_SERVICE);
            } else {
                mLayoutInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
            }
        }

        @Override
        public Object getItem(int position) {
            mCursor.moveToPosition(position);
            return mCursor;
        }

        @Override
        public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
            // Inflate view that appears in the drop-down spinner views
            return mLayoutInflater.inflate(R.layout.spinner_item_session_livestream, parent, false);
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            // Inflate view that appears in the selected spinner
            return mLayoutInflater.inflate(android.R.layout.simple_spinner_item, parent, false);
        }

        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            // Bind view that appears in the selected spinner or in the drop-down
            final TextView titleView = (TextView) view.findViewById(android.R.id.text1);
            final TextView subTitleView = (TextView) view.findViewById(android.R.id.text2);

            String trackName = cursor.getString(SessionsQuery.TRACK_NAME);
            if (TextUtils.isEmpty(trackName)) {
                trackName = getString(R.string.app_name);
            } else {
                trackName = getString(R.string.session_livestream_track_title, trackName);
            }

            String sessionTitle = cursor.getString(SessionsQuery.TITLE);

            if (subTitleView != null) { // Drop-down view
                titleView.setText(trackName);
                subTitleView.setText(sessionTitle);
            } else { // Selected view
                titleView.setText(getString(R.string.session_livestream_title) + ": " + trackName);
            }
        }
    }

    /**
     * Adapter that backs the ViewPager tabs on the phone UI.
     */
    private static class TabsAdapter extends FragmentPagerAdapter
            implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
        private final FragmentActivity mContext;
        private final TabHost mTabHost;
        private final ViewPager mViewPager;
        public final HashMap<Integer, Fragment> mFragments;
        private final ArrayList<Integer> mTabNums;
        private int tabCount = 0;

        static class DummyTabFactory implements TabHost.TabContentFactory {
            private final Context mContext;

            public DummyTabFactory(Context context) {
                mContext = context;
            }

            @Override
            public View createTabContent(String tag) {
                View v = new View(mContext);
                v.setMinimumWidth(0);
                v.setMinimumHeight(0);
                return v;
            }
        }

        @SuppressLint("UseSparseArrays")
        public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
            super(activity.getSupportFragmentManager());
            mFragments = new HashMap<Integer, Fragment>(3);
            mContext = activity;
            mTabHost = tabHost;
            mViewPager = pager;
            mTabHost.setOnTabChangedListener(this);
            mViewPager.setAdapter(this);
            mViewPager.setOnPageChangeListener(this);
            mTabNums = new ArrayList<Integer>(3);
        }

        public void addTab(String tabTitle, Fragment newFragment, int tabId) {
            ViewGroup tabWidget = (ViewGroup) mTabHost.findViewById(android.R.id.tabs);
            TextView tabIndicatorView = (TextView) mContext.getLayoutInflater()
                    .inflate(R.layout.tab_indicator_color, tabWidget, false);
            tabIndicatorView.setText(tabTitle);

            final TabHost.TabSpec tabSpec = mTabHost.newTabSpec(String.valueOf(tabCount++));
            tabSpec.setIndicator(tabIndicatorView);
            tabSpec.setContent(new DummyTabFactory(mContext));
            mTabHost.addTab(tabSpec);
            mFragments.put(tabId, newFragment);
            mTabNums.add(tabId);
            notifyDataSetChanged();
        }

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

        @Override
        public Fragment getItem(int position) {
            return mFragments.get(mTabNums.get(position));
        }

        @Override
        public void onTabChanged(String tabId) {
            int position = mTabHost.getCurrentTab();
            mViewPager.setCurrentItem(position);
        }

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        }

        @Override
        public void onPageSelected(int position) {
            // Unfortunately when TabHost changes the current tab, it kindly also takes care of
            // putting focus on it when not in touch mode. The jerk. This hack tries to prevent
            // this from pulling focus out of our ViewPager.
            TabWidget widget = mTabHost.getTabWidget();
            int oldFocusability = widget.getDescendantFocusability();
            widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
            mTabHost.setCurrentTab(position);
            widget.setDescendantFocusability(oldFocusability);
        }

        @Override
        public void onPageScrollStateChanged(int state) {
        }
    }

    /**
     * Simple fragment that inflates a session summary layout that displays session title and
     * abstract.
     */
    public static class SessionSummaryFragment extends Fragment {
        private TextView mTitleView;
        private TextView mAbstractView;
        private String mTitle;
        private String mAbstract;

        public SessionSummaryFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_session_summary, null);
            mTitleView = (TextView) view.findViewById(R.id.session_title);
            mAbstractView = (TextView) view.findViewById(R.id.session_abstract);
            updateViews();
            return view;
        }

        public void setSessionSummaryInfo(String sessionTitle, String sessionAbstract) {
            mTitle = sessionTitle;
            mAbstract = sessionAbstract;
            updateViews();
        }

        public void updateViews() {
            if (mTitleView != null && mAbstractView != null) {
                mTitleView.setText(mTitle);
                if (!TextUtils.isEmpty(mAbstract)) {
                    mAbstractView.setVisibility(View.VISIBLE);
                    mAbstractView.setText(mAbstract);
                } else {
                    mAbstractView.setVisibility(View.GONE);
                }
            }
        }
    }

    /**
     * Simple fragment that shows the live captions.
     */
    public static class SessionLiveCaptionsFragment extends Fragment {
        private static final String CAPTIONS_DARK_THEME_URL_PARAM = "&theme=dark";

        private FrameLayout mContainer;
        private WebView mWebView;
        private TextView mNoCaptionsTextView;
        private boolean mDarkTheme = false;
        private String mSessionTrack;

        public SessionLiveCaptionsFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_session_captions, null);
            mContainer = (FrameLayout) view.findViewById(R.id.session_caption_container);
            if (!UIUtils.isTablet(getActivity())) {
                mContainer.setBackgroundColor(Color.WHITE);
            }
            mNoCaptionsTextView = (TextView) view.findViewById(android.R.id.empty);
            mWebView = (WebView) view.findViewById(R.id.session_caption_area);
            mWebView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    // Disable text selection in WebView (doesn't work well with the YT player
                    // triggering full screen chrome after a timeout)
                    return true;
                }
            });
            mWebView.setWebViewClient(new WebViewClient() {
                @Override
                public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                    showNoCaptionsAvailable();
                }
            });
            updateViews();
            return view;
        }

        public void setTrackName(String sessionTrack) {
            mSessionTrack = sessionTrack;
            updateViews();
        }

        public void setDarkTheme(boolean dark) {
            mDarkTheme = dark;
        }

        @SuppressLint("SetJavaScriptEnabled")
        public void updateViews() {
            if (mWebView != null && !TextUtils.isEmpty(mSessionTrack)) {
                if (mDarkTheme) {
                    mWebView.setBackgroundColor(Color.BLACK);
                    mContainer.setBackgroundColor(Color.BLACK);
                    mNoCaptionsTextView.setTextColor(Color.WHITE);
                } else {
                    mWebView.setBackgroundColor(Color.WHITE);
                    mContainer.setBackgroundResource(R.drawable.grey_frame_on_white);
                }

                String captionsUrl;
                final String trackLowerCase = mSessionTrack.toLowerCase();
                if (mSessionTrack.equals(KEYNOTE_TRACK_NAME)
                        || trackLowerCase.equals(Config.PRIMARY_LIVESTREAM_TRACK)) {
                    // if keynote or primary track, use primary captions url
                    captionsUrl = Config.PRIMARY_LIVESTREAM_CAPTIONS_URL;
                } else if (trackLowerCase.equals(Config.SECONDARY_LIVESTREAM_TRACK)) {
                    // else if secondary track use secondary captions url
                    captionsUrl = Config.SECONDARY_LIVESTREAM_CAPTIONS_URL;
                } else {
                    // otherwise we don't have captions
                    captionsUrl = null;
                }
                if (captionsUrl != null) {
                    if (mDarkTheme) {
                        captionsUrl += CAPTIONS_DARK_THEME_URL_PARAM;
                    }
                    mWebView.getSettings().setJavaScriptEnabled(true);
                    mWebView.loadUrl(captionsUrl);
                    mNoCaptionsTextView.setVisibility(View.GONE);
                    mWebView.setVisibility(View.VISIBLE);
                } else {
                    showNoCaptionsAvailable();
                }
            }
        }

        private void showNoCaptionsAvailable() {
            mWebView.setVisibility(View.GONE);
            mNoCaptionsTextView.setVisibility(View.VISIBLE);
        }
    }

    /*
     * Presentation and Media Router methods and classes. This allows use of an externally
     * connected display to show video content full screen while still showing the regular views
     * on the primary device display.
     */

    /**
     * Updates the presentation display. If a suitable external display is attached. The video
     * will be displayed on that screen instead. If not, the video will be displayed normally.
     */
    private void updatePresentation() {
        updatePresentation(false);
    }

    /**
     * Updates the presentation display. If a suitable external display is attached. The video
     * will be displayed on that screen instead. If not, the video will be displayed normally.
     * @param forceDismiss If true, the external display will be dismissed even if it is still
     *                     available. This is useful for if the user wants to explicitly toggle
     *                     the external display off even if it's still connected. Setting this to
     *                     false will adjust the display based on if a suitable external display
     *                     is available or not.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    private void updatePresentation(boolean forceDismiss) {
        if (!UIUtils.hasJellyBeanMR1()) {
            return;
        }

        // Get the current route and its presentation display
        MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO);
        Display presentationDisplay = route != null ? route.getPresentationDisplay() : null;

        // Dismiss the current presentation if the display has changed
        if (mPresentation != null && (mPresentation.getDisplay() != presentationDisplay || forceDismiss)) {
            hidePresentation();
        }

        // Show a new presentation if needed.
        if (mPresentation == null && presentationDisplay != null && !forceDismiss) {
            showPresentation(presentationDisplay);
        }

        // Show/hide toggle presentation action item
        if (mPresentationMenuItem != null) {
            mPresentationMenuItem.setVisible(presentationDisplay != null);
        }
    }

    /**
     * Shows the Presentation on the designated Display. Some UI components are also updated:
     * a play/pause button is displayed where the video would normally be; the YouTube player style
     * is updated to MINIMAL (more suitable for an external display); and an action item is updated
     * to indicate the external display is being used.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    private void showPresentation(Display presentationDisplay) {
        LOGD(TAG, "Showing video presentation on display: " + presentationDisplay);
        if (mIsFullscreen) {
            if (!mIsTablet) {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            }
            if (mYouTubePlayer != null) {
                mYouTubePlayer.setFullscreen(false);
            }
        }
        View presentationView = mYouTubeFragment.getView();
        ((ViewGroup) presentationView.getParent()).removeView(presentationView);
        mPresentation = new YouTubePresentation(this, presentationDisplay, presentationView);
        mPresentation.setOnDismissListener(mOnDismissListener);
        try {
            mPresentation.show();
            mPresentationControls.setVisibility(View.VISIBLE);
            updatePresentationMenuItem(true);
            if (mYouTubePlayer != null) {
                mYouTubePlayer.setPlayerStyle(YouTubePlayer.PlayerStyle.MINIMAL);
                mYouTubePlayer.play();
                setFullscreenFlags(true);
            }
        } catch (WindowManager.InvalidDisplayException e) {
            LOGE(TAG, "Couldn't show presentation! Display was removed in the meantime.", e);
            hidePresentation();
        }
    }

    /**
     * Hides the Presentation. Some UI components are also updated:
     * the video view is returned to its normal place on the primary display; the YouTube player
     * style is updated to the default; and an action item is updated to indicate the external
     * display is no longer in use.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    private void hidePresentation() {
        LOGD(TAG, "Hiding video presentation");
        View presentationView = mPresentation.getYouTubeFragmentView();
        ((ViewGroup) presentationView.getParent()).removeView(presentationView);
        mPlayerContainer.addView(presentationView);
        mPresentationControls.setVisibility(View.GONE);
        updatePresentationMenuItem(false);
        mPresentation.dismiss();
        mPresentation = null;
        if (mYouTubePlayer != null) {
            mYouTubePlayer.setPlayerStyle(YouTubePlayer.PlayerStyle.DEFAULT);
            mYouTubePlayer.play();
            setFullscreenFlags(false);
        }
    }

    /**
     * Updates the presentation action item. Toggles the icon between two drawables to indicate
     * if the external display is currently being used or not.
     *
     * @param enabled If the external display is enabled or not.
     */
    private void updatePresentationMenuItem(boolean enabled) {
        if (mPresentationMenuItem != null) {
            mPresentationMenuItem.setIcon(
                    enabled ? R.drawable.ic_media_route_on_holo_light : R.drawable.ic_media_route_off_holo_light);
        }
    }

    /**
     * Applies YouTube player full screen flags. Calls through to
     * {@link #setFullscreenFlags(boolean)}.
     */
    private void setFullscreenFlags() {
        setFullscreenFlags(true);
    }

    /**
     * Applies YouTube player full screen flags plus forces portrait orientation on small screens
     * when presentation (secondary display) is enabled (as the full screen layout won't have
     * anything to display when an external display is connected).
     *
     * @param usePresentationMode Whether or not to use presentation mode. This is used when an
     *                            external display is connected and the user toggles between
     *                            presentation mode.
     */
    private void setFullscreenFlags(boolean usePresentationMode) {
        if (mYouTubePlayer != null) {
            if (usePresentationMode && mPresentation != null && !mIsTablet) {
                mYouTubePlayer.setFullscreenControlFlags(0);
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            } else {
                mYouTubePlayer.setFullscreenControlFlags(mYouTubeFullscreenFlags);
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
            }
        }
    }

    /**
     * Listens for when the Presentation (which is a type of Dialog) is dismissed.
     */
    private final DialogInterface.OnDismissListener mOnDismissListener = new DialogInterface.OnDismissListener() {
        @Override
        public void onDismiss(DialogInterface dialog) {
            if (dialog == mPresentation) {
                LOGD(TAG, "Presentation was dismissed.");
                updatePresentation(true);
            }
        }
    };

    /**
     * The main subclass of Presentation that is used to show content on an externally connected
     * display. There is no way to add a {@link android.app.Fragment}, and therefore a
     * YouTubePlayerFragment to a {@link android.app.Presentation} (which is just a subclass of
     * {@link android.app.Dialog}) so instead we pass in a View directly which is the extracted
     * YouTubePlayerView from the {@link SessionLivestreamActivity} YouTubePlayerFragment. Not
     * ideal and fairly hacky but there aren't any better alternatives unfortunately.
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    private final static class YouTubePresentation extends Presentation {
        private View mYouTubeFragmentView;

        public YouTubePresentation(Context context, Display display, View presentationView) {
            super(context, display);
            mYouTubeFragmentView = presentationView;
        }

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(mYouTubeFragmentView);
        }

        public View getYouTubeFragmentView() {
            return mYouTubeFragmentView;
        }
    }

    private static class SessionShareData {
        String title;
        String hashtag;
        String sessionUrl;

        public SessionShareData(String title, String hashTag, String url) {
            this.title = title;
            hashtag = hashTag;
            sessionUrl = url;
        }
    }

    /**
     * Single session query
     */
    public interface SessionSummaryQuery {
        final static int _TOKEN = 0;

        final static String[] PROJECTION = { ScheduleContract.Sessions.SESSION_ID,
                ScheduleContract.Sessions.SESSION_TITLE, ScheduleContract.Sessions.SESSION_ABSTRACT,
                ScheduleContract.Sessions.SESSION_HASHTAGS, ScheduleContract.Sessions.SESSION_LIVESTREAM_URL,
                Tracks.TRACK_NAME, ScheduleContract.Blocks.BLOCK_START, ScheduleContract.Blocks.BLOCK_END, };

        final static int ID = 0;
        final static int TITLE = 1;
        final static int ABSTRACT = 2;
        final static int HASHTAGS = 3;
        final static int LIVESTREAM_URL = 4;
        final static int TRACK_NAME = 5;
        final static int BLOCK_START = 6;
        final static int BLOCK_END = 7;
    }

    /**
     * List of sessions query
     */
    public interface SessionsQuery {
        final static int _TOKEN = 1;

        final static String[] PROJECTION = { BaseColumns._ID, Sessions.SESSION_ID, Sessions.SESSION_TITLE,
                Tracks.TRACK_NAME, Tracks.TRACK_COLOR, ScheduleContract.Blocks.BLOCK_START,
                ScheduleContract.Blocks.BLOCK_END, };

        final static int _ID = 0;
        final static int SESSION_ID = 1;
        final static int TITLE = 2;
        final static int TRACK_NAME = 3;
        final static int TRACK_COLOR = 4;
        final static int BLOCK_START = 5;
        final static int BLOCK_END = 6;
    }
}