com.google.android.apps.iosched.ui.gtv.GoogleTVSessionLivestreamActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.google.android.apps.iosched.ui.gtv.GoogleTVSessionLivestreamActivity.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.google.android.apps.iosched.ui.gtv;

import com.google.analytics.tracking.android.EasyTracker;
import com.google.android.apps.iosched.Config;
import com.google.android.apps.iosched.R;
import com.google.android.apps.iosched.provider.ScheduleContract;
import com.google.android.apps.iosched.provider.ScheduleContract.Sessions;
import com.google.android.apps.iosched.sync.SyncHelper;
import com.google.android.apps.iosched.ui.BaseActivity;
import com.google.android.apps.iosched.ui.SessionLivestreamActivity.SessionSummaryFragment;
import com.google.android.apps.iosched.util.UIUtils;
import com.google.android.youtube.api.YouTube;
import com.google.android.youtube.api.YouTubePlayer;

import android.annotation.TargetApi;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.BaseColumns;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.method.LinkMovementMethod;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;

import static com.google.android.apps.iosched.util.LogUtils.LOGD;
import static com.google.android.apps.iosched.util.LogUtils.LOGE;
import static com.google.android.apps.iosched.util.LogUtils.makeLogTag;

/**
 * A Google TV home activity for Google I/O 2012 that displays session live streams pulled in
 * from YouTube.
 */
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class GoogleTVSessionLivestreamActivity extends BaseActivity implements LoaderCallbacks<Cursor>,
        YouTubePlayer.OnPlaybackEventsListener, YouTubePlayer.OnFullscreenListener, OnItemClickListener {

    private static final String TAG = makeLogTag(GoogleTVSessionLivestreamActivity.class);

    private static final String TAG_SESSION_SUMMARY = "session_summary";
    private static final int UPCOMING_SESSIONS_QUERY_ID = 3;

    private static final String PROMO_VIDEO_URL = "http://www.youtube.com/watch?v=gTA-5HM8Zhs";

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

    private YouTubePlayer mYouTubePlayer;
    private FrameLayout mPlayerContainer;
    private String mYouTubeVideoId;
    private LinearLayout mMainLayout;
    private LinearLayout mVideoLayout;
    private LinearLayout mExtraLayout;
    private FrameLayout mSummaryLayout;
    private ListView mLiveListView;

    private SyncHelper mGTVSyncHelper;
    private LivestreamAdapter mLivestreamAdapter;

    private String mSessionId;

    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        YouTube.initialize(this, Config.YOUTUBE_API_KEY);

        setContentView(R.layout.activity_session_livestream);

        // Set up YouTube player
        mYouTubePlayer = (YouTubePlayer) getSupportFragmentManager().findFragmentById(R.id.livestream_player);
        mYouTubePlayer.setOnPlaybackEventsListener(this);
        mYouTubePlayer.enableCustomFullscreen(this);
        mYouTubePlayer.setFullscreenControlFlags(YouTubePlayer.FULLSCREEN_FLAG_CONTROL_SYSTEM_UI);

        // Views that are common over all layouts
        mMainLayout = (LinearLayout) findViewById(R.id.livestream_mainlayout);
        mPlayerContainer = (FrameLayout) findViewById(R.id.livestream_player_container);

        // 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.googletv_livesextra_layout);
        mSummaryLayout = (FrameLayout) findViewById(R.id.livestream_summary);

        // Reload all other data in this activity
        reloadFromUri(getIntent().getData());

        // Start sessions query to populate action bar navigation spinner
        getSupportLoaderManager().initLoader(SessionsQuery._TOKEN, null, this);

        // Set up left side listview
        mLivestreamAdapter = new LivestreamAdapter(this);
        mLiveListView = (ListView) findViewById(R.id.live_session_list);
        mLiveListView.setAdapter(mLivestreamAdapter);
        mLiveListView.setOnItemClickListener(this);
        mLiveListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        mLiveListView.setSelector(android.R.color.transparent);

        getSupportActionBar().hide();

        // Sync data from Conference API
        new SyncOperationTask().execute((Void) null);
    }

    /**
     * Reloads all data in the activity and fragments from a given uri
     */
    private void reloadFromUri(Uri newUri) {
        if (newUri != null && newUri.getPathSegments().size() >= 2) {
            mSessionId = Sessions.getSessionId(newUri);
            getSupportLoaderManager().restartLoader(SessionSummaryQuery._TOKEN, null, this);
        }
    }

    @Override
    public void onBackPressed() {
        if (mIsFullscreen) {
            // Exit full screen mode on back key
            mYouTubePlayer.setFullscreen(false);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int itemPosition, long itemId) {
        final Cursor cursor = (Cursor) mLivestreamAdapter.getItem(itemPosition);
        final String sessionId = cursor.getString(SessionsQuery.SESSION_ID);
        if (sessionId != null) {
            reloadFromUri(Sessions.buildSessionUri(sessionId));
        }
    }

    @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:
            final long currentTime = UIUtils.getCurrentTime(this);
            String selection = Sessions.LIVESTREAM_SELECTION + " and " + Sessions.AT_TIME_SELECTION;
            String[] selectionArgs = Sessions.buildAtTimeSelectionArgs(currentTime);
            return new CursorLoader(this, Sessions.buildWithTracksUri(), SessionsQuery.PROJECTION, selection,
                    selectionArgs, null);

        case UPCOMING_SESSIONS_QUERY_ID:
            final long newCurrentTime = UIUtils.getCurrentTime(this);
            String sessionsSelection = Sessions.LIVESTREAM_SELECTION + " and " + Sessions.UPCOMING_SELECTION;
            String[] sessionsSelectionArgs = Sessions.buildUpcomingSelectionArgs(newCurrentTime);
            return new CursorLoader(this, Sessions.buildWithTracksUri(), SessionsQuery.PROJECTION,
                    sessionsSelection, sessionsSelectionArgs, null);
        }
        return null;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mHandler.removeCallbacks(mNextSessionStartsInCountdownRunnable);

        switch (loader.getId()) {
        case SessionSummaryQuery._TOKEN:
            loadSession(data);
            break;

        case SessionsQuery._TOKEN:
            mLivestreamAdapter.swapCursor(data);
            final int selected = locateSelectedItem(data);
            if (data.getCount() == 0) {
                handleNoLiveSessionsAvailable();
            } else {
                mLiveListView.setSelection(selected);
                mLiveListView.requestFocus(selected);
                final Cursor cursor = (Cursor) mLivestreamAdapter.getItem(selected);
                final String sessionId = cursor.getString(SessionsQuery.SESSION_ID);
                if (sessionId != null) {
                    reloadFromUri(Sessions.buildSessionUri(sessionId));
                }
            }
            break;

        case UPCOMING_SESSIONS_QUERY_ID:
            if (data != null && data.getCount() > 0) {
                data.moveToFirst();
                handleUpdateNextUpcomingSession(data);
            }
            break;
        }
    }

    public void handleNoLiveSessionsAvailable() {
        getSupportLoaderManager().initLoader(UPCOMING_SESSIONS_QUERY_ID, null, this);
        updateSessionViews(PROMO_VIDEO_URL, getString(R.string.missed_io_title),
                getString(R.string.missed_io_subtitle), UIUtils.CONFERENCE_HASHTAG);

        //Make link in abstract view clickable
        TextView abstractLinkTextView = (TextView) findViewById(R.id.session_abstract);
        abstractLinkTextView.setMovementMethod(new LinkMovementMethod());
    }

    private String mNextSessionTitle;
    private long mNextSessionStartTime;

    private final Runnable mNextSessionStartsInCountdownRunnable = new Runnable() {
        public void run() {
            int remainingSec = (int) Math.max(0,
                    (mNextSessionStartTime - UIUtils.getCurrentTime(GoogleTVSessionLivestreamActivity.this))
                            / 1000);

            final int secs = remainingSec % 86400;
            final int days = remainingSec / 86400;
            final String str;
            if (days == 0) {
                str = getResources().getString(R.string.starts_in_template_0_days,
                        DateUtils.formatElapsedTime(secs));
            } else {
                str = getResources().getQuantityString(R.plurals.starts_in_template, days, days,
                        DateUtils.formatElapsedTime(secs));
            }

            updateSessionSummaryFragment(mNextSessionTitle, str);

            if (remainingSec == 0) {
                // Next session starting now!
                mHandler.postDelayed(mRefreshSessionsRunnable, 1000);
                return;
            }

            // Repost ourselves to keep updating countdown
            mHandler.postDelayed(mNextSessionStartsInCountdownRunnable, 1000);
        }
    };

    private final Runnable mRefreshSessionsRunnable = new Runnable() {
        @Override
        public void run() {
            getSupportLoaderManager().restartLoader(SessionsQuery._TOKEN, null,
                    GoogleTVSessionLivestreamActivity.this);
        }
    };

    public void handleUpdateNextUpcomingSession(Cursor data) {
        mNextSessionTitle = getString(R.string.next_live_stream_session_template,
                data.getString(SessionsQuery.TITLE));
        mNextSessionStartTime = data.getLong(SessionsQuery.BLOCK_START);
        updateSessionViews(PROMO_VIDEO_URL, mNextSessionTitle, "", UIUtils.CONFERENCE_HASHTAG);

        // Begin countdown til next session
        mHandler.post(mNextSessionStartsInCountdownRunnable);
    }

    /**
     * Locates which item should be selected in the action bar drop-down spinner based on the
     * current active session uri
     */
    private int locateSelectedItem(Cursor data) {
        int selected = 0;
        if (data != null && mSessionId != null) {
            while (data.moveToNext()) {
                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 onLoaded(String s) {
    }

    @Override
    public void onPlaying() {
    }

    @Override
    public void onPaused() {
    }

    @Override
    public void onBuffering(boolean b) {
    }

    @Override
    public void onEnded() {
        mHandler.post(mRefreshSessionsRunnable);
    }

    @Override
    public void onError() {
        Toast.makeText(this, R.string.session_livestream_error, Toast.LENGTH_LONG).show();
        LOGE(TAG, getString(R.string.session_livestream_error));
    }

    private void loadSession(Cursor data) {
        if (data != null && data.moveToFirst()) {
            // Schedule a data refresh after the session ends.
            // NOTE: using postDelayed instead of postAtTime helps during debugging, using
            // mock times.
            mHandler.postDelayed(mRefreshSessionsRunnable,
                    data.getLong(SessionSummaryQuery.BLOCK_END) + 1000 - UIUtils.getCurrentTime(this));

            updateSessionViews(data.getString(SessionSummaryQuery.LIVESTREAM_URL),
                    data.getString(SessionSummaryQuery.TITLE), data.getString(SessionSummaryQuery.ABSTRACT),
                    data.getString(SessionSummaryQuery.HASHTAGS));
        }
    }

    /**
     * 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) {

        if (youtubeUrl == null) {
            // Get out, nothing to do here
            Toast.makeText(this, R.string.error_tv_no_url, Toast.LENGTH_SHORT).show();
            LOGE(TAG, getString(R.string.error_tv_no_url));
            return;
        }

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

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

        updateSessionSummaryFragment(title, sessionAbstract);

        mLiveListView.requestFocus();
    }

    private void updateSessionSummaryFragment(String title, String sessionAbstract) {
        SessionSummaryFragment sessionSummaryFragment = (SessionSummaryFragment) getSupportFragmentManager()
                .findFragmentByTag(TAG_SESSION_SUMMARY);
        if (sessionSummaryFragment != null) {
            sessionSummaryFragment.setSessionSummaryInfo(title, sessionAbstract);
        }
    }

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

    private void layoutFullscreenVideo(boolean fullscreen) {
        if (mIsFullscreen != fullscreen) {
            mIsFullscreen = fullscreen;
            mYouTubePlayer.setFullscreen(fullscreen);
            layoutGoogleTVFullScreen(fullscreen);

            // Full screen layout changes for all form factors
            final LayoutParams params = (LayoutParams) mPlayerContainer.getLayoutParams();
            if (fullscreen) {
                params.height = LayoutParams.MATCH_PARENT;
                mMainLayout.setPadding(0, 0, 0, 0);
            } else {
                params.height = LayoutParams.WRAP_CONTENT;
            }
            mPlayerContainer.setLayoutParams(params);
        }
    }

    /**
     * Adjusts tablet layouts for full screen video.
     * 
     * @param fullscreen True to layout in full screen, false to switch to regular layout
     */
    private void layoutGoogleTVFullScreen(boolean fullscreen) {
        if (fullscreen) {
            mExtraLayout.setVisibility(View.GONE);
            mSummaryLayout.setVisibility(View.GONE);
            mMainLayout.setPadding(0, 0, 0, 0);
            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);
            mMainLayout.setPadding(padding, padding, padding, padding);
            mVideoLayout.setBackgroundResource(R.drawable.grey_frame_on_white);
            final LayoutParams videoLayoutParams = (LayoutParams) mVideoLayout.getLayoutParams();
            videoLayoutParams.setMargins(padding, padding, padding, padding);
            mVideoLayout.setLayoutParams(videoLayoutParams);
        }
    }

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

        public LivestreamAdapter(Context context) {
            super(context, null, false);
            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 side list view
            return mLayoutInflater.inflate(R.layout.list_item_live_session, parent, false);
        }

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

        @Override
        public void bindView(View view, Context context, Cursor cursor) {

            final TextView titleView = (TextView) view.findViewById(R.id.live_session_title);
            final TextView subTitleView = (TextView) view.findViewById(R.id.live_session_subtitle);

            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);
            }

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

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

    // Enabling media keys for Google TV Devices
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        switch (keyCode) {
        case KeyEvent.KEYCODE_MEDIA_PAUSE:
            mYouTubePlayer.pause();
            return true;

        case KeyEvent.KEYCODE_MEDIA_PLAY:
            mYouTubePlayer.play();
            return true;

        case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
            mYouTubePlayer.seekRelativeMillis(20000);
            return true;

        case KeyEvent.KEYCODE_MEDIA_REWIND:
            mYouTubePlayer.seekRelativeMillis(-20000);
            return true;

        default:
            return super.onKeyDown(keyCode, event);
        }
    }

    // Need to sync with the conference API to get the livestream URLs
    private class SyncOperationTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... params) {
            // Perform a sync using SyncHelper
            if (mGTVSyncHelper == null) {
                mGTVSyncHelper = new SyncHelper(getApplicationContext());
            }
            try {
                mGTVSyncHelper.performSync(null, SyncHelper.FLAG_SYNC_LOCAL | SyncHelper.FLAG_SYNC_REMOTE);
            } catch (IOException e) {
                LOGE(TAG, "Error loading data for Google I/O 2012.", e);
            }
            return null;
        }
    }

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

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

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

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

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

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