Update androidtv prebuilts from GitHub

Upstream commits:
github.com/googlesamples/androidtv-sample-inputs 68f1fb51df10fd75879227a1207472d216234bee
github.com/googlesamples/androidtv-leanback 0fffc9b025de86edda34d5419891036ae8aff2e5

Change-Id: Iaecc986a2b31b35d7b6f74eb8a4df58291cb985b
diff --git a/prebuilts/androidtv/leanback/.gitignore b/prebuilts/androidtv/leanback/.gitignore
index fa29e9c..6c8c2cc 100644
--- a/prebuilts/androidtv/leanback/.gitignore
+++ b/prebuilts/androidtv/leanback/.gitignore
@@ -28,6 +28,8 @@
 
 # Android Studio
 .idea
+*.iml
+
 #.idea/workspace.xml - remove # and delete .idea if it better suit your needs.
 .gradle
 build/
diff --git a/prebuilts/androidtv/leanback/CONTRIBUTING.md b/prebuilts/androidtv/leanback/CONTRIBUTING.md
index 59e2c2f..14ac8cc 100644
--- a/prebuilts/androidtv/leanback/CONTRIBUTING.md
+++ b/prebuilts/androidtv/leanback/CONTRIBUTING.md
@@ -45,12 +45,12 @@
 
 1. Create your sample app in this repo.
     * Be sure to clone the README.md, CONTRIBUTING.md and LICENSE files from the
-      googlecast repo.
+      googlesamples repo.
     * Ensure that your code is clear and comprehensible.
     * Ensure that your code has an appropriate set of unit tests which all pass.
     * Instructional value is the top priority when evaluating new app proposals for
       this collection of repos.
-1. Submit a request to fork your repo in googlecast organization.
+1. Submit a request to fork your repo in googlesamples organization.
 1. The repo owner will review your request. If it is approved, the sample will
    be merged. If it needs additional work, the repo owner will respond with 
    useful comments.
diff --git a/prebuilts/androidtv/leanback/app/build.gradle b/prebuilts/androidtv/leanback/app/build.gradle
index d3bcdb4..02b19dc 100644
--- a/prebuilts/androidtv/leanback/app/build.gradle
+++ b/prebuilts/androidtv/leanback/app/build.gradle
@@ -1,19 +1,18 @@
-apply plugin: 'android'
+apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 20
-    buildToolsVersion "20"
-
+    compileSdkVersion 22
+    buildToolsVersion '22.0.0'
     defaultConfig {
-        applicationId "com.example.android.leanback"
-        minSdkVersion 20
-        targetSdkVersion 20
+        applicationId "com.example.android.tvleanback"
+        minSdkVersion 21
+        targetSdkVersion 22
         versionCode 1
         versionName "1.0"
     }
     buildTypes {
         release {
-            runProguard false
+            minifyEnabled false
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }
@@ -21,8 +20,9 @@
 
 dependencies {
     compile fileTree(dir: 'libs', include: ['*.jar'])
-    compile "com.android.support:leanback-v17:21.0.+"
-    compile "com.android.support:recyclerview-v7:21.0.+"
-    compile 'com.android.support:support-v4:13.0.+'
-    compile 'com.squareup.picasso:picasso:2.3.3'
+    compile 'com.android.support:recyclerview-v7:22.0.0'
+    compile 'com.android.support:leanback-v17:22.0.0'
+    compile 'com.android.support:appcompat-v7:22.0.0'
+    compile 'com.github.bumptech.glide:glide:3.4.+'
+    compile 'com.android.support:support-v4:22.0.0'
 }
diff --git a/prebuilts/androidtv/leanback/app/src/main/AndroidManifest.xml b/prebuilts/androidtv/leanback/app/src/main/AndroidManifest.xml
index 65b02b5..19b18e2 100644
--- a/prebuilts/androidtv/leanback/app/src/main/AndroidManifest.xml
+++ b/prebuilts/androidtv/leanback/app/src/main/AndroidManifest.xml
@@ -16,17 +16,20 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.android.leanback"
+    package="com.example.android.tvleanback"
     android:versionCode="1"
-    android:versionName="1.0" >
+    android:versionName="1.1" >
 
     <uses-sdk
         android:minSdkVersion="19"
-        android:targetSdkVersion="19" />
+        android:targetSdkVersion="21" />
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+    <uses-feature
+        android:name="android.hardware.microphone"
+        android:required="false" />
 
     <uses-feature
         android:name="android.hardware.touchscreen"
@@ -40,9 +43,9 @@
         android:icon="@drawable/videos_by_google_banner"
         android:label="@string/app_name"
         android:logo="@drawable/videos_by_google_banner"
-        android:theme="@style/Theme.Leanback" >
+        android:theme="@style/Theme.Example.Leanback" >
         <activity
-            android:name="MainActivity"
+            android:name=".ui.MainActivity"
             android:icon="@drawable/videos_by_google_banner"
             android:label="@string/app_name"
             android:logo="@drawable/videos_by_google_banner"
@@ -52,24 +55,50 @@
 
                 <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
             </intent-filter>
+
         </activity>
+
         <activity
-            android:name="DetailsActivity"
+            android:name=".ui.MovieDetailsActivity"
+            android:exported="true">
+
+            <!-- Receives the search request. -->
+            <intent-filter>
+                <action android:name="android.intent.action.SEARCH" />
+                <!-- No category needed, because the Intent will specify this class component-->
+           </intent-filter>
+
+            <!-- Points to searchable meta data. -->
+            <meta-data android:name="android.app.searchable"
+                android:resource="@xml/searchable" />
+        </activity>
+
+        <activity android:name=".ui.PlaybackOverlayActivity"
             android:exported="true" />
+
         <activity
-            android:name="PlayerActivity"
+            android:name=".ui.VerticalGridActivity"
             android:exported="true"
-            android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />
+            android:parentActivityName=".ui.MainActivity" />
         <activity
-            android:name="VerticalGridActivity"
-            android:exported="true"
-            android:parentActivityName="MainActivity" />
-        <activity
-            android:name="SearchActivity"
+            android:name=".ui.SearchActivity"
+            android:exported="true" />
+
+        <activity android:name=".ui.BrowseErrorActivity"
+            android:exported="true" />
+
+        <!-- Provides search suggestions for keywords against video meta data. -->
+        <provider android:name=".data.VideoContentProvider"
+            android:authorities="com.example.android.tvleanback"
+            android:exported="true" />
+
+        <provider
+            android:name=".recommendation.RecommendationBuilder$RecommendationBackgroundContentProvider"
+            android:authorities="com.example.android.tvleanback.recommendation"
             android:exported="true" />
 
         <receiver
-            android:name=".BootupActivity"
+            android:name=".recommendation.BootupActivity"
             android:enabled="true"
             android:exported="false" >
             <intent-filter>
@@ -78,7 +107,7 @@
         </receiver>
 
         <service
-            android:name=".UpdateRecommendationsService"
+            android:name=".recommendation.UpdateRecommendationsService"
             android:enabled="true" />
     </application>
 
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/CardPresenter.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/CardPresenter.java
deleted file mode 100644
index 00afac5..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/CardPresenter.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.v17.leanback.widget.ImageCardView;
-import android.support.v17.leanback.widget.Presenter;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-import java.net.URI;
-
-/*
- * A CardPresenter is used to generate Views and bind Objects to them on demand. 
- * It contains an Image CardView
- */
-public class CardPresenter extends Presenter {
-    private static final String TAG = "CardPresenter";
-
-    private static Context mContext;
-    private static int CARD_WIDTH = 313;
-    private static int CARD_HEIGHT = 176;
-
-    static class ViewHolder extends Presenter.ViewHolder {
-        private Movie mMovie;
-        private ImageCardView mCardView;
-        private Drawable mDefaultCardImage;
-        private PicassoImageCardViewTarget mImageCardViewTarget;
-
-        public ViewHolder(View view) {
-            super(view);
-            mCardView = (ImageCardView) view;
-            mImageCardViewTarget = new PicassoImageCardViewTarget(mCardView);
-            mDefaultCardImage = mContext.getResources().getDrawable(R.drawable.movie);
-        }
-
-        public void setMovie(Movie m) {
-            mMovie = m;
-        }
-
-        public Movie getMovie() {
-            return mMovie;
-        }
-
-        public ImageCardView getCardView() {
-            return mCardView;
-        }
-
-        protected void updateCardViewImage(URI uri) {
-            Picasso.with(mContext)
-                    .load(uri.toString())
-                    .resize(Utils.dpToPx(CARD_WIDTH, mContext), Utils.dpToPx(CARD_HEIGHT, mContext))
-                    .error(mDefaultCardImage)
-                    .into(mImageCardViewTarget);
-        }
-    }
-
-    @Override
-    public ViewHolder onCreateViewHolder(ViewGroup parent) {
-        Log.d(TAG, "onCreateViewHolder");
-        mContext = parent.getContext();
-
-        ImageCardView cardView = new ImageCardView(mContext);
-        cardView.setFocusable(true);
-        cardView.setFocusableInTouchMode(true);
-        cardView.setBackgroundColor(mContext.getResources().getColor(R.color.fastlane_background));
-        return new ViewHolder(cardView);
-    }
-
-    @Override
-    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
-        Movie movie = (Movie) item;
-        ((ViewHolder) viewHolder).setMovie(movie);
-
-        Log.d(TAG, "onBindViewHolder");
-        if (movie.getCardImageUrl() != null) {
-            ((ViewHolder) viewHolder).mCardView.setTitleText(movie.getTitle());
-            ((ViewHolder) viewHolder).mCardView.setContentText(movie.getStudio());
-            ((ViewHolder) viewHolder).mCardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
-            ((ViewHolder) viewHolder).updateCardViewImage(movie.getCardImageURI());
-        }
-    }
-
-    @Override
-    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
-        Log.d(TAG, "onUnbindViewHolder");
-    }
-
-    @Override
-    public void onViewAttachedToWindow(Presenter.ViewHolder viewHolder) {
-        // TO DO
-    }
-
-    public static class PicassoImageCardViewTarget implements Target {
-        private ImageCardView mImageCardView;
-
-        public PicassoImageCardViewTarget(ImageCardView imageCardView) {
-            mImageCardView = imageCardView;
-        }
-
-        @Override
-        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
-            Drawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
-            mImageCardView.setMainImage(bitmapDrawable);
-        }
-
-        @Override
-        public void onBitmapFailed(Drawable drawable) {
-            mImageCardView.setMainImage(drawable);
-        }
-
-        @Override
-        public void onPrepareLoad(Drawable drawable) {
-            // Do nothing, default_background manager has its own transitions
-        }
-    }
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/DetailsActivity.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/DetailsActivity.java
deleted file mode 100644
index c35d150..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/DetailsActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-/*
- * A wrapper class for details activity
- */
-public class DetailsActivity extends Activity
-{
-
-    /** Called when the activity is first created. */
-    @Override
-    public void onCreate(Bundle savedInstanceState)
-    {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.details);
-
-    }
-
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/DetailsDescriptionPresenter.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/DetailsDescriptionPresenter.java
deleted file mode 100644
index 39a83b9..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/DetailsDescriptionPresenter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
-
-public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter {
-
-    @Override
-    protected void onBindDescription(ViewHolder viewHolder, Object item) {
-        Movie movie = (Movie) item;
-
-        if (movie != null) {
-            viewHolder.getTitle().setText(movie.getTitle());
-            viewHolder.getSubtitle().setText(movie.getStudio());
-            viewHolder.getBody().setText(movie.getDescription());
-        }
-    }
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/LeanbackDetailsFragment.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/LeanbackDetailsFragment.java
deleted file mode 100644
index 1bdc175..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/LeanbackDetailsFragment.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.support.v17.leanback.app.BackgroundManager;
-import android.support.v17.leanback.app.DetailsFragment;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ClassPresenterSelector;
-import android.support.v17.leanback.widget.DetailsOverviewRow;
-import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.OnItemClickedListener;
-import android.support.v17.leanback.widget.Row;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.widget.Toast;
-
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/*
- * LeanbackDetailsFragment extends DetailsFragment, a Wrapper fragment for leanback details screens.
- * It shows a detailed view of video and its meta plus related videos.
- */
-public class LeanbackDetailsFragment extends DetailsFragment {
-    private static final String TAG = "DetailsFragment";
-
-    private static final int ACTION_WATCH_TRAILER = 1;
-    private static final int ACTION_RENT = 2;
-    private static final int ACTION_BUY = 3;
-
-    private static final int DETAIL_THUMB_WIDTH = 274;
-    private static final int DETAIL_THUMB_HEIGHT = 274;
-
-    private Movie selectedMovie;
-
-    private Drawable mDefaultBackground;
-    private Target mBackgroundTarget;
-    private DisplayMetrics mMetrics;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        Log.i(TAG, "onCreate DetailsFragment");
-        super.onCreate(savedInstanceState);
-
-        BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
-        backgroundManager.attach(getActivity().getWindow());
-        mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
-
-        mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
-
-        mMetrics = new DisplayMetrics();
-        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
-
-        selectedMovie = (Movie) getActivity().getIntent().getSerializableExtra("Movie");
-        Log.d(TAG, "DetailsActivity movie: " + selectedMovie.toString());
-        new DetailRowBuilderTask().execute(selectedMovie);
-
-        setOnItemClickedListener(getDefaultItemClickedListener());
-        updateBackground(selectedMovie.getBackgroundImageURI());
-    }
-
-    private class DetailRowBuilderTask extends AsyncTask<Movie, Integer, DetailsOverviewRow> {
-        @Override
-        protected DetailsOverviewRow doInBackground(Movie... movies) {
-            selectedMovie = movies[0];
-
-            Log.d(TAG, "doInBackground: " + selectedMovie.toString());
-            DetailsOverviewRow row = new DetailsOverviewRow(selectedMovie);
-            try {
-                Bitmap poster = Picasso.with(getActivity())
-                        .load(selectedMovie.getCardImageUrl())
-                        .resize(Utils.dpToPx(DETAIL_THUMB_WIDTH, getActivity()
-                                .getApplicationContext()),
-                                Utils.dpToPx(DETAIL_THUMB_HEIGHT, getActivity()
-                                        .getApplicationContext()))
-                        .centerCrop()
-                        .get();
-                row.setImageBitmap(getActivity(), poster);
-            } catch (IOException e) {
-            }
-
-            row.addAction(new Action(ACTION_WATCH_TRAILER, getResources().getString(
-                    R.string.watch_trailer_1), getResources().getString(R.string.watch_trailer_2)));
-            row.addAction(new Action(ACTION_RENT, getResources().getString(R.string.rent_1),
-                    getResources().getString(R.string.rent_2)));
-            row.addAction(new Action(ACTION_BUY, getResources().getString(R.string.buy_1),
-                    getResources().getString(R.string.buy_2)));
-            return row;
-        }
-
-        @Override
-        protected void onPostExecute(DetailsOverviewRow detailRow) {
-            ClassPresenterSelector ps = new ClassPresenterSelector();
-            DetailsOverviewRowPresenter dorPresenter =
-                    new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
-            // set detail background and style
-            dorPresenter.setBackgroundColor(getResources().getColor(R.color.detail_background));
-            dorPresenter.setStyleLarge(true);
-            dorPresenter.setOnActionClickedListener(new OnActionClickedListener() {
-                @Override
-                public void onActionClicked(Action action) {
-                    if (action.getId() == ACTION_WATCH_TRAILER) {
-                        Intent intent = new Intent(getActivity(), PlayerActivity.class);
-                        intent.putExtra(getResources().getString(R.string.movie), selectedMovie);
-                        intent.putExtra(getResources().getString(R.string.should_start), true);
-                        startActivity(intent);
-                    }
-                    else {
-                        Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show();
-                    }
-                }
-            });
-
-            ps.addClassPresenter(DetailsOverviewRow.class, dorPresenter);
-            ps.addClassPresenter(ListRow.class,
-                    new ListRowPresenter());
-
-            ArrayObjectAdapter adapter = new ArrayObjectAdapter(ps);
-            adapter.add(detailRow);
-
-            String subcategories[] = {
-                    getString(R.string.related_movies)
-            };
-            HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
-
-            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
-            for (Map.Entry<String, List<Movie>> entry : movies.entrySet())
-            {
-                if (selectedMovie.getCategory().indexOf(entry.getKey()) >= 0) {
-                    List<Movie> list = entry.getValue();
-                    for (int j = 0; j < list.size(); j++) {
-                        listRowAdapter.add(list.get(j));
-                    }
-                }
-            }
-            HeaderItem header = new HeaderItem(0, subcategories[0], null);
-            adapter.add(new ListRow(header, listRowAdapter));
-
-            setAdapter(adapter);
-        }
-
-    }
-
-    protected OnItemClickedListener getDefaultItemClickedListener() {
-        return new OnItemClickedListener() {
-            @Override
-            public void onItemClicked(Object item, Row row) {
-                if (item instanceof Movie) {
-                    Movie movie = (Movie) item;
-                    Intent intent = new Intent(getActivity(), DetailsActivity.class);
-                    intent.putExtra(getResources().getString(R.string.movie), movie);
-                    startActivity(intent);
-                }
-            }
-        };
-    }
-
-    protected void updateBackground(URI uri) {
-        Picasso.with(getActivity())
-                .load(uri.toString())
-                .resize(mMetrics.widthPixels, mMetrics.heightPixels)
-                .error(mDefaultBackground)
-                .into(mBackgroundTarget);
-    }
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/MainFragment.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/MainFragment.java
deleted file mode 100644
index 7d6ff11..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/MainFragment.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import android.app.LoaderManager;
-import android.content.Intent;
-import android.content.Loader;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.app.BackgroundManager;
-import android.support.v17.leanback.app.BrowseFragment;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.OnItemClickedListener;
-import android.support.v17.leanback.widget.OnItemSelectedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-import java.net.URI;
-import java.util.*;
-
-/*
- * Main class to show BrowseFragment with header and rows of videos
- */
-public class MainFragment extends BrowseFragment implements
-        LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {
-    private static final String TAG = "MainFragment";
-
-    private static int BACKGROUND_UPDATE_DELAY = 300;
-    private static int GRID_ITEM_WIDTH = 200;
-    private static int GRID_ITEM_HEIGHT = 200;
-
-    private ArrayObjectAdapter mRowsAdapter;
-    private Drawable mDefaultBackground;
-    private Target mBackgroundTarget;
-    private DisplayMetrics mMetrics;
-    private Timer mBackgroundTimer;
-    private final Handler mHandler = new Handler();
-    private URI mBackgroundURI;
-    private static String mVideosUrl;
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        Log.i(TAG, "onCreate");
-        super.onActivityCreated(savedInstanceState);
-
-        loadVideoData();
-
-        prepareBackgroundManager();
-        setupUIElements();
-        setupEventListeners();
-    }
-
-    private void prepareBackgroundManager() {
-        BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
-        backgroundManager.attach(getActivity().getWindow());
-        mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
-        mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
-        mMetrics = new DisplayMetrics();
-        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
-    }
-
-    private void setupUIElements() {
-        // setBadgeDrawable(getActivity().getResources().getDrawable(R.drawable.videos_by_google_banner));
-        setTitle(getString(R.string.browse_title)); // Badge, when set, takes precedent over title
-        setHeadersState(HEADERS_ENABLED);
-        setHeadersTransitionOnBackEnabled(true);
-        // set fastLane (or headers) background color
-        setBrandColor(getResources().getColor(R.color.fastlane_background));
-        // set search icon color
-        setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
-    }
-
-    private void loadVideoData() {
-        VideoProvider.setContext(getActivity());
-        mVideosUrl = getActivity().getResources().getString(R.string.catalog_url);
-        getLoaderManager().initLoader(0, null, this);
-    }
-
-    private void setupEventListeners() {
-        setOnSearchClickedListener(new View.OnClickListener() {
-
-            @Override
-            public void onClick(View view) {
-                Intent intent = new Intent(getActivity(), SearchActivity.class);
-                startActivity(intent);
-            }
-        });
-
-        setOnItemSelectedListener(getDefaultItemSelectedListener());
-        setOnItemClickedListener(getDefaultItemClickedListener());
-    }
-
-    /*
-     * (non-Javadoc)
-     * @see android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader(int,
-     * android.os.Bundle)
-     */
-    @Override
-    public Loader<HashMap<String, List<Movie>>> onCreateLoader(int arg0, Bundle arg1) {
-        Log.d(TAG, "VideoItemLoader created ");
-        return new VideoItemLoader(getActivity(), mVideosUrl);
-    }
-
-    /*
-     * (non-Javadoc)
-     * @see android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished(android
-     * .support.v4.content.Loader, java.lang.Object)
-     */
-    @Override
-    public void onLoadFinished(Loader<HashMap<String, List<Movie>>> arg0,
-            HashMap<String, List<Movie>> data) {
-
-        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
-        CardPresenter cardPresenter = new CardPresenter();
-
-        int i = 0;
-
-        for (Map.Entry<String, List<Movie>> entry : data.entrySet())
-        {
-            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
-            List<Movie> list = entry.getValue();
-
-            for (int j = 0; j < list.size(); j++) {
-                listRowAdapter.add(list.get(j));
-            }
-            HeaderItem header = new HeaderItem(i, entry.getKey(), null);
-            i++;
-            mRowsAdapter.add(new ListRow(header, listRowAdapter));
-        }
-
-        HeaderItem gridHeader = new HeaderItem(i, getResources().getString(R.string.preferences),
-                null);
-
-        GridItemPresenter gridPresenter = new GridItemPresenter();
-        ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(gridPresenter);
-        gridRowAdapter.add(getResources().getString(R.string.grid_view));
-        gridRowAdapter.add(getResources().getString(R.string.send_feeback));
-        gridRowAdapter.add(getResources().getString(R.string.personal_settings));
-        mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));
-
-        setAdapter(mRowsAdapter);
-
-        updateRecommendations();
-    }
-
-    @Override
-    public void onLoaderReset(Loader<HashMap<String, List<Movie>>> arg0) {
-        mRowsAdapter.clear();
-    }
-
-    protected OnItemSelectedListener getDefaultItemSelectedListener() {
-        return new OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(Object item, Row row) {
-                if (item instanceof Movie) {
-                    mBackgroundURI = ((Movie) item).getBackgroundImageURI();
-                    startBackgroundTimer();
-                }
-            }
-        };
-    }
-
-    protected OnItemClickedListener getDefaultItemClickedListener() {
-        return new OnItemClickedListener() {
-            @Override
-            public void onItemClicked(Object item, Row row) {
-                if (item instanceof Movie) {
-                    Movie movie = (Movie) item;
-                    Log.d(TAG, "Item: " + item.toString());
-                    Intent intent = new Intent(getActivity(), DetailsActivity.class);
-                    intent.putExtra(getString(R.string.movie), movie);
-                    startActivity(intent);
-                }
-                else if (item instanceof String) {
-                    if (((String) item).indexOf(getResources().getString(R.string.grid_view)) >= 0) {
-                        Intent intent = new Intent(getActivity(), VerticalGridActivity.class);
-                        startActivity(intent);
-                    }
-                    else {
-                        Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT)
-                                .show();
-                    }
-                }
-
-            }
-        };
-    }
-
-    protected void setDefaultBackground(Drawable background) {
-        mDefaultBackground = background;
-    }
-
-    protected void setDefaultBackground(int resourceId) {
-        mDefaultBackground = getResources().getDrawable(resourceId);
-    }
-
-    protected void updateBackground(URI uri) {
-        Picasso.with(getActivity())
-                .load(uri.toString())
-                .resize(mMetrics.widthPixels, mMetrics.heightPixels)
-                .centerCrop()
-                .error(mDefaultBackground)
-                .into(mBackgroundTarget);
-    }
-
-    protected void updateBackground(Drawable drawable) {
-        BackgroundManager.getInstance(getActivity()).setDrawable(drawable);
-    }
-
-    protected void clearBackground() {
-        BackgroundManager.getInstance(getActivity()).setDrawable(mDefaultBackground);
-    }
-
-    private void startBackgroundTimer() {
-        if (null != mBackgroundTimer) {
-            mBackgroundTimer.cancel();
-        }
-        mBackgroundTimer = new Timer();
-        mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY);
-    }
-
-    private class UpdateBackgroundTask extends TimerTask {
-
-        @Override
-        public void run() {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    if (mBackgroundURI != null) {
-                        updateBackground(mBackgroundURI);
-                    }
-                }
-            });
-        }
-    }
-
-    private class GridItemPresenter extends Presenter {
-        @Override
-        public ViewHolder onCreateViewHolder(ViewGroup parent) {
-            TextView view = new TextView(parent.getContext());
-            view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
-            view.setFocusable(true);
-            view.setFocusableInTouchMode(true);
-            view.setBackgroundColor(getResources().getColor(R.color.default_background));
-            view.setTextColor(Color.WHITE);
-            view.setGravity(Gravity.CENTER);
-            return new ViewHolder(view);
-        }
-
-        @Override
-        public void onBindViewHolder(ViewHolder viewHolder, Object item) {
-            ((TextView) viewHolder.view).setText((String) item);
-        }
-
-        @Override
-        public void onUnbindViewHolder(ViewHolder viewHolder) {
-        }
-    }
-
-    private void updateRecommendations() {
-        Intent recommendationIntent = new Intent(getActivity(), UpdateRecommendationsService.class);
-        getActivity().startService(recommendationIntent);
-    }
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/Movie.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/Movie.java
deleted file mode 100644
index adc01a9..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/Movie.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import android.util.Log;
-
-import java.io.Serializable;
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/*
- * Movie class represents video entity with title, description, image thumbs and video url. 
- * 
- */
-public class Movie implements Serializable {
-    static final long serialVersionUID = 727566175075960653L;
-    private static long count = 0;
-    private long id;
-    private String title;
-    private String description;
-    private String bgImageUrl;
-    private String cardImageUrl;
-    private String videoUrl;
-    private String studio;
-    private String category;
-
-    public Movie() {
-    }
-
-    public static long getCount() {
-        return count;
-    }
-
-    public static void incCount() {
-        count++;
-    }
-
-    public long getId() {
-        return id;
-    }
-
-    public void setId(long id) {
-        this.id = id;
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    public String getStudio() {
-        return studio;
-    }
-
-    public void setStudio(String studio) {
-        this.studio = studio;
-    }
-
-    public String getVideoUrl() {
-        return videoUrl;
-    }
-
-    public void setVideoUrl(String videoUrl) {
-        this.videoUrl = videoUrl;
-    }
-
-    public String getBackgroundImageUrl() {
-        return bgImageUrl;
-    }
-
-    public void setBackgroundImageUrl(String bgImageUrl) {
-        this.bgImageUrl = bgImageUrl;
-    }
-
-    public String getCardImageUrl() {
-        return cardImageUrl;
-    }
-
-    public void setCardImageUrl(String cardImageUrl) {
-        this.cardImageUrl = cardImageUrl;
-    }
-
-    public String getCategory() {
-        return category;
-    }
-
-    public void setCategory(String category) {
-        this.category = category;
-    }
-
-    public URI getBackgroundImageURI() {
-        try {
-            Log.d("BACK MOVIE: ", bgImageUrl);
-            return new URI(getBackgroundImageUrl());
-        } catch (URISyntaxException e) {
-            Log.d("URI exception: ", bgImageUrl);
-            return null;
-        }
-    }
-
-    public URI getCardImageURI() {
-        try {
-            return new URI(getCardImageUrl());
-        } catch (URISyntaxException e) {
-            return null;
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "Movie{" +
-                "id=" + id +
-                ", title='" + title + '\'' +
-                ", videoUrl='" + videoUrl + '\'' +
-                ", backgroundImageUrl='" + bgImageUrl + '\'' +
-                ", backgroundImageURI='" + getBackgroundImageURI().toString() + '\'' +
-                ", cardImageUrl='" + cardImageUrl + '\'' +
-                '}';
-    }
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/PlayerActivity.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/PlayerActivity.java
deleted file mode 100644
index d2faf6f..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/PlayerActivity.java
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * Copyright (C) 2013 Google Inc. All Rights Reserved.
- *
- * 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.example.android.leanback;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.MediaPlayer.OnPreparedListener;
-import android.os.Bundle;
-import android.os.Handler;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.ProgressBar;
-import android.widget.RelativeLayout.LayoutParams;
-import android.widget.SeekBar;
-import android.widget.TextView;
-import android.widget.VideoView;
-
-import java.util.Locale;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.TimeUnit;
-
-/*
- * PlayerActivity handles video playback 
- */
-public class PlayerActivity extends Activity {
-
-    private static final String TAG = "PlayerActivity";
-
-    private static final int HIDE_CONTROLLER_TIME = 5000;
-    private static final int SEEKBAR_DELAY_TIME = 100;
-    private static final int SEEKBAR_INTERVAL_TIME = 1000;
-    private static final int MIN_SCRUB_TIME = 3000;
-    private static final int SCRUB_SEGMENT_DIVISOR = 30;
-    private static final double MEDIA_BAR_TOP_MARGIN = 0.8;
-    private static final double MEDIA_BAR_RIGHT_MARGIN = 0.2;
-    private static final double MEDIA_BAR_BOTTOM_MARGIN = 0.0;
-    private static final double MEDIA_BAR_LEFT_MARGIN = 0.2;
-    private static final double MEDIA_BAR_HEIGHT = 0.1;
-    private static final double MEDIA_BAR_WIDTH = 0.9;
-
-    private VideoView mVideoView;
-    private TextView mStartText;
-    private TextView mEndText;
-    private SeekBar mSeekbar;
-    private ImageView mPlayPause;
-    private ProgressBar mLoading;
-    private View mControllers;
-    private View mContainer;
-    private Timer mSeekbarTimer;
-    private Timer mControllersTimer;
-    private PlaybackState mPlaybackState;
-    private final Handler mHandler = new Handler();
-    private Movie mSelectedMovie;
-    private boolean mShouldStartPlayback;
-    private boolean mControllersVisible;
-    private int mDuration;
-    private DisplayMetrics mMetrics;
-
-    /*
-     * List of various states that we can be in
-     */
-    public static enum PlaybackState {
-        PLAYING, PAUSED, BUFFERING, IDLE;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.player_activity);
-
-        mMetrics = new DisplayMetrics();
-        getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
-
-        loadViews();
-        setupController();
-        setupControlsCallbacks();
-        startVideoPlayer();
-        updateMetadata(true);
-    }
-
-    private void startVideoPlayer() {
-        Bundle b = getIntent().getExtras();
-        mSelectedMovie = (Movie) getIntent().getSerializableExtra(
-                getResources().getString(R.string.movie));
-        if (null != b) {
-            mShouldStartPlayback = b.getBoolean(getResources().getString(R.string.should_start));
-            int startPosition = b.getInt(getResources().getString(R.string.start_position), 0);
-            mVideoView.setVideoPath(mSelectedMovie.getVideoUrl());
-            if (mShouldStartPlayback) {
-                mPlaybackState = PlaybackState.PLAYING;
-                updatePlayButton(mPlaybackState);
-                if (startPosition > 0) {
-                    mVideoView.seekTo(startPosition);
-                }
-                mVideoView.start();
-                mPlayPause.requestFocus();
-                startControllersTimer();
-            } else {
-                updatePlaybackLocation();
-                mPlaybackState = PlaybackState.PAUSED;
-                updatePlayButton(mPlaybackState);
-            }
-        }
-    }
-
-    private void updatePlaybackLocation() {
-        if (mPlaybackState == PlaybackState.PLAYING ||
-                mPlaybackState == PlaybackState.BUFFERING) {
-            startControllersTimer();
-        } else {
-            stopControllersTimer();
-        }
-    }
-
-    private void play(int position) {
-        startControllersTimer();
-        mVideoView.seekTo(position);
-        mVideoView.start();
-        restartSeekBarTimer();
-    }
-
-    private void stopSeekBarTimer() {
-        if (null != mSeekbarTimer) {
-            mSeekbarTimer.cancel();
-        }
-    }
-
-    private void restartSeekBarTimer() {
-        stopSeekBarTimer();
-        mSeekbarTimer = new Timer();
-        mSeekbarTimer.scheduleAtFixedRate(new UpdateSeekbarTask(), SEEKBAR_DELAY_TIME,
-                SEEKBAR_INTERVAL_TIME);
-    }
-
-    private void stopControllersTimer() {
-        if (null != mControllersTimer) {
-            mControllersTimer.cancel();
-        }
-    }
-
-    private void startControllersTimer() {
-        if (null != mControllersTimer) {
-            mControllersTimer.cancel();
-        }
-        mControllersTimer = new Timer();
-        mControllersTimer.schedule(new HideControllersTask(), HIDE_CONTROLLER_TIME);
-    }
-
-    private void updateControllersVisibility(boolean show) {
-        if (show) {
-            mControllers.setVisibility(View.VISIBLE);
-        } else {
-            mControllers.setVisibility(View.INVISIBLE);
-        }
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        Log.d(TAG, "onPause() was called");
-        if (null != mSeekbarTimer) {
-            mSeekbarTimer.cancel();
-            mSeekbarTimer = null;
-        }
-        if (null != mControllersTimer) {
-            mControllersTimer.cancel();
-        }
-        mVideoView.pause();
-        mPlaybackState = PlaybackState.PAUSED;
-        updatePlayButton(PlaybackState.PAUSED);
-    }
-
-    @Override
-    protected void onStop() {
-        Log.d(TAG, "onStop() was called");
-        super.onStop();
-    }
-
-    @Override
-    protected void onDestroy() {
-        Log.d(TAG, "onDestroy() is called");
-        stopControllersTimer();
-        stopSeekBarTimer();
-        super.onDestroy();
-    }
-
-    @Override
-    protected void onStart() {
-        Log.d(TAG, "onStart() was called");
-        super.onStart();
-    }
-
-    @Override
-    protected void onResume() {
-        Log.d(TAG, "onResume() was called");
-        super.onResume();
-    }
-
-    private class HideControllersTask extends TimerTask {
-        @Override
-        public void run() {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    updateControllersVisibility(false);
-                    mControllersVisible = false;
-                }
-            });
-
-        }
-    }
-
-    private class UpdateSeekbarTask extends TimerTask {
-
-        @Override
-        public void run() {
-            mHandler.post(new Runnable() {
-
-                @Override
-                public void run() {
-                    int currentPos = 0;
-                    currentPos = mVideoView.getCurrentPosition();
-                    updateSeekbar(currentPos, mDuration);
-                }
-            });
-        }
-    }
-
-    private class BackToDetailTask extends TimerTask {
-
-        @Override
-        public void run() {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    Intent intent = new Intent(PlayerActivity.this, DetailsActivity.class);
-                    intent.putExtra(getResources().getString(R.string.movie), mSelectedMovie);
-                    startActivity(intent);
-                }
-            });
-
-        }
-    }
-
-    private void setupController() {
-
-        int w = (int) (mMetrics.widthPixels * MEDIA_BAR_WIDTH);
-        int h = (int) (mMetrics.heightPixels * MEDIA_BAR_HEIGHT);
-        int marginLeft = (int) (mMetrics.widthPixels * MEDIA_BAR_LEFT_MARGIN);
-        int marginTop = (int) (mMetrics.heightPixels * MEDIA_BAR_TOP_MARGIN);
-        int marginRight = (int) (mMetrics.widthPixels * MEDIA_BAR_RIGHT_MARGIN);
-        int marginBottom = (int) (mMetrics.heightPixels * MEDIA_BAR_BOTTOM_MARGIN);
-        LayoutParams lp = new LayoutParams(w, h);
-        lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
-        mControllers.setLayoutParams(lp);
-        mStartText.setText(getResources().getString(R.string.init_text));
-        mEndText.setText(getResources().getString(R.string.init_text));
-    }
-
-    private void setupControlsCallbacks() {
-
-        mVideoView.setOnErrorListener(new OnErrorListener() {
-
-            @Override
-            public boolean onError(MediaPlayer mp, int what, int extra) {
-                String msg = "";
-                if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
-                    msg = getString(R.string.video_error_media_load_timeout);
-                } else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
-                    msg = getString(R.string.video_error_server_unaccessible);
-                } else {
-                    msg = getString(R.string.video_error_unknown_error);
-                }
-                Utils.showErrorDialog(PlayerActivity.this, msg);
-                mVideoView.stopPlayback();
-                mPlaybackState = PlaybackState.IDLE;
-                return false;
-            }
-        });
-
-        mVideoView.setOnPreparedListener(new OnPreparedListener() {
-
-            @Override
-            public void onPrepared(MediaPlayer mp) {
-                Log.d(TAG, "onPrepared is reached");
-                mDuration = mp.getDuration();
-                mEndText.setText(formatTimeSignature(mDuration));
-                mSeekbar.setMax(mDuration);
-                restartSeekBarTimer();
-            }
-        });
-
-        mVideoView.setOnCompletionListener(new OnCompletionListener() {
-
-            @Override
-            public void onCompletion(MediaPlayer mp) {
-                stopSeekBarTimer();
-                mPlaybackState = PlaybackState.IDLE;
-                updatePlayButton(PlaybackState.IDLE);
-                mControllersTimer = new Timer();
-                mControllersTimer.schedule(new BackToDetailTask(), HIDE_CONTROLLER_TIME);
-            }
-        });
-    }
-
-    /*
-     * @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return
-     * super.onKeyDown(keyCode, event); }
-     */
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        int currentPos = 0;
-        int delta = (int) (mDuration / SCRUB_SEGMENT_DIVISOR);
-        if (delta < MIN_SCRUB_TIME)
-            delta = MIN_SCRUB_TIME;
-
-        Log.v("keycode", "duration " + mDuration + " delta:" + delta);
-        if (!mControllersVisible) {
-            updateControllersVisibility(true);
-        }
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_DPAD_CENTER:
-                return true;
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-                return true;
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-                currentPos = mVideoView.getCurrentPosition();
-                currentPos -= delta;
-                if (currentPos > 0)
-                    play(currentPos);
-                return true;
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-                currentPos = mVideoView.getCurrentPosition();
-                currentPos += delta;
-                if (currentPos < mDuration)
-                    play(currentPos);
-                return true;
-            case KeyEvent.KEYCODE_DPAD_UP:
-                return true;
-        }
-
-        return super.onKeyDown(keyCode, event);
-    }
-
-    private void updateSeekbar(int position, int duration) {
-        mSeekbar.setProgress(position);
-        mSeekbar.setMax(duration);
-        mStartText.setText(formatTimeSignature(mDuration));
-    }
-
-    private void updatePlayButton(PlaybackState state) {
-        switch (state) {
-            case PLAYING:
-                mLoading.setVisibility(View.INVISIBLE);
-                mPlayPause.setVisibility(View.VISIBLE);
-                mPlayPause.setImageDrawable(
-                        getResources().getDrawable(R.drawable.ic_pause_playcontrol_normal));
-                break;
-            case PAUSED:
-            case IDLE:
-                mLoading.setVisibility(View.INVISIBLE);
-                mPlayPause.setVisibility(View.VISIBLE);
-                mPlayPause.setImageDrawable(
-                        getResources().getDrawable(R.drawable.ic_play_playcontrol_normal));
-                break;
-            case BUFFERING:
-                mPlayPause.setVisibility(View.INVISIBLE);
-                mLoading.setVisibility(View.VISIBLE);
-                break;
-            default:
-                break;
-        }
-    }
-
-    private void updateMetadata(boolean visible) {
-        mVideoView.invalidate();
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        return true;
-    }
-
-    private void loadViews() {
-        mVideoView = (VideoView) findViewById(R.id.videoView);
-        mStartText = (TextView) findViewById(R.id.startText);
-        mEndText = (TextView) findViewById(R.id.endText);
-        mSeekbar = (SeekBar) findViewById(R.id.seekBar);
-        mPlayPause = (ImageView) findViewById(R.id.playpause);
-        mLoading = (ProgressBar) findViewById(R.id.progressBar);
-        mControllers = findViewById(R.id.controllers);
-        mContainer = findViewById(R.id.container);
-
-        mVideoView.setOnClickListener(mPlayPauseHandler);
-    }
-
-    View.OnClickListener mPlayPauseHandler = new View.OnClickListener() {
-        public void onClick(View v) {
-            Log.d(TAG, "clicked play pause button");
-
-            if (!mControllersVisible) {
-                updateControllersVisibility(true);
-            }
-
-            if (mPlaybackState == PlaybackState.PAUSED) {
-                mPlaybackState = PlaybackState.PLAYING;
-                updatePlayButton(mPlaybackState);
-                mVideoView.start();
-                startControllersTimer();
-            } else {
-                mVideoView.pause();
-                mPlaybackState = PlaybackState.PAUSED;
-                updatePlayButton(PlaybackState.PAUSED);
-                stopControllersTimer();
-            }
-        }
-    };
-
-    private String formatTimeSignature(int timeSignature) {
-        return String.format(Locale.US,
-                "%02d:%02d",
-                TimeUnit.MILLISECONDS.toMinutes(timeSignature),
-                TimeUnit.MILLISECONDS.toSeconds(timeSignature)
-                        -
-                        TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS
-                                .toMinutes(timeSignature)));
-    }
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/RecommendationBuilder.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/RecommendationBuilder.java
deleted file mode 100644
index 7ef96bc..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/RecommendationBuilder.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.support.v4.app.NotificationCompat;
-import android.util.Log;
-
-import com.squareup.picasso.Picasso;
-
-import java.io.IOException;
-
-/*
- * This class builds recommendations as notifications with videos as inputs.
- */
-public class RecommendationBuilder {
-    private static final String TAG = "RecommendationBuilder";
-
-    private static int CARD_WIDTH = 313;
-    private static int CARD_HEIGHT = 176;
-
-    public static final String EXTRA_BACKGROUND_IMAGE_URL = "background_image_url";
-    private Context mContext;
-    private NotificationManager mNotificationManager;
-
-    private int mId;
-    private int mPriority;
-    private int mSmallIcon;
-    private String mTitle;
-    private String mDescription;
-    private String mImageUri;
-    private String mBackgroundUri;
-    private PendingIntent mIntent;
-
-    public RecommendationBuilder() {
-    }
-
-    public RecommendationBuilder setContext(Context context) {
-        mContext = context;
-        return this;
-    }
-
-    public RecommendationBuilder setId(int id) {
-        mId = id;
-        return this;
-    }
-
-    public RecommendationBuilder setPriority(int priority) {
-        mPriority = priority;
-        return this;
-    }
-
-    public RecommendationBuilder setTitle(String title) {
-        mTitle = title;
-        return this;
-    }
-
-    public RecommendationBuilder setDescription(String description) {
-        mDescription = description;
-        return this;
-    }
-
-    public RecommendationBuilder setImage(String uri) {
-        mImageUri = uri;
-        return this;
-    }
-
-    public RecommendationBuilder setBackground(String uri) {
-        mBackgroundUri = uri;
-        return this;
-    }
-
-    public RecommendationBuilder setIntent(PendingIntent intent) {
-        mIntent = intent;
-        return this;
-    }
-
-    public RecommendationBuilder setSmallIcon(int resourceId) {
-        mSmallIcon = resourceId;
-        return this;
-    }
-
-    public Notification build() throws IOException {
-
-        Log.d(TAG, "Building notification - " + this.toString());
-
-        if (mNotificationManager == null) {
-            mNotificationManager = (NotificationManager) mContext
-                    .getSystemService(Context.NOTIFICATION_SERVICE);
-        }
-
-        Bundle extras = new Bundle();
-        if (mBackgroundUri != null) {
-            extras.putString(EXTRA_BACKGROUND_IMAGE_URL, mBackgroundUri);
-        }
-
-        Bitmap image = Picasso.with(mContext)
-                .load(mImageUri)
-                .resize(Utils.dpToPx(CARD_WIDTH, mContext), Utils.dpToPx(CARD_HEIGHT, mContext))
-                .get();
-
-        Notification notification = new NotificationCompat.BigPictureStyle(
-                new NotificationCompat.Builder(mContext)
-                        .setContentTitle(mTitle)
-                        .setContentText(mDescription)
-                        .setPriority(mPriority)
-                        .setLocalOnly(true)
-                        .setOngoing(true)
-                        .setColor(mContext.getResources().getColor(R.color.fastlane_background))
-                        // .setCategory(Notification.CATEGORY_RECOMMENDATION)
-                        .setCategory("recommendation")
-                        .setLargeIcon(image)
-                        .setSmallIcon(mSmallIcon)
-                        .setContentIntent(mIntent)
-                        .setExtras(extras))
-                .build();
-
-        mNotificationManager.notify(mId, notification);
-        mNotificationManager = null;
-        return notification;
-    }
-
-    @Override
-    public String toString() {
-        return "RecommendationBuilder{" +
-                ", mId=" + mId +
-                ", mPriority=" + mPriority +
-                ", mSmallIcon=" + mSmallIcon +
-                ", mTitle='" + mTitle + '\'' +
-                ", mDescription='" + mDescription + '\'' +
-                ", mImageUri='" + mImageUri + '\'' +
-                ", mBackgroundUri='" + mBackgroundUri + '\'' +
-                ", mIntent=" + mIntent +
-                '}';
-    }
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/SearchActivity.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/SearchActivity.java
deleted file mode 100644
index 0ce0fb9..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/SearchActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-/*
- * This class is a wrapper activity for SearchFragment
- */
-public class SearchActivity extends Activity
-{
-    /** Called when the activity is first created. */
-    @Override
-    public void onCreate(Bundle savedInstanceState)
-    {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.search);
-    }
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/SearchFragment.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/SearchFragment.java
deleted file mode 100644
index 49f4689..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/SearchFragment.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-import android.annotation.SuppressLint;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.HeaderItem;
-import android.support.v17.leanback.widget.ListRow;
-import android.support.v17.leanback.widget.ListRowPresenter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemClickedListener;
-import android.support.v17.leanback.widget.Row;
-import android.text.TextUtils;
-import android.util.Log;
-
-/*
- * This class demonstrates how to do in-app search 
- */
-@SuppressLint("DefaultLocale")
-public class SearchFragment extends android.support.v17.leanback.app.SearchFragment
-        implements android.support.v17.leanback.app.SearchFragment.SearchResultProvider {
-    private static final String TAG = "SearchFragment";
-    private static final int SEARCH_DELAY_MS = 300;
-
-    private ArrayObjectAdapter mRowsAdapter;
-    private Handler mHandler = new Handler();
-    private SearchRunnable mDelayedLoad;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
-        setSearchResultProvider(this);
-        setOnItemClickedListener(getDefaultItemClickedListener());
-        mDelayedLoad = new SearchRunnable();
-    }
-
-    @Override
-    public ObjectAdapter getResultsAdapter() {
-        return mRowsAdapter;
-    }
-
-    private void queryByWords(String words) {
-        mRowsAdapter.clear();
-        if (!TextUtils.isEmpty(words)) {
-            mDelayedLoad.setSearchQuery(words);
-            mHandler.removeCallbacks(mDelayedLoad);
-            mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
-        }
-    }
-
-    @Override
-    public boolean onQueryTextChange(String newQuery) {
-        Log.i(TAG, String.format("Search Query Text Change %s", newQuery));
-        queryByWords(newQuery);
-        return true;
-    }
-
-    @Override
-    public boolean onQueryTextSubmit(String query) {
-        Log.i(TAG, String.format("Search Query Text Submit %s", query));
-        queryByWords(query);
-        return true;
-    }
-
-    private void loadRows(String query) {
-        HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
-        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
-        for (Map.Entry<String, List<Movie>> entry : movies.entrySet())
-        {
-            for (int i = 0; i < entry.getValue().size(); i++) {
-                Movie movie = entry.getValue().get(i);
-                if (movie.getTitle().toLowerCase(Locale.ENGLISH)
-                        .indexOf(query.toLowerCase(Locale.ENGLISH)) >= 0
-                        || movie.getDescription().toLowerCase(Locale.ENGLISH)
-                                .indexOf(query.toLowerCase(Locale.ENGLISH)) >= 0) {
-                    listRowAdapter.add(movie);
-                }
-            }
-        }
-        HeaderItem header = new HeaderItem(0, getResources().getString(R.string.search_results),
-                null);
-        mRowsAdapter.add(new ListRow(header, listRowAdapter));
-    }
-
-    protected OnItemClickedListener getDefaultItemClickedListener() {
-        return new OnItemClickedListener() {
-            @Override
-            public void onItemClicked(Object item, Row row) {
-                if (item instanceof Movie) {
-                    Movie movie = (Movie) item;
-                    Intent intent = new Intent(getActivity(), DetailsActivity.class);
-                    intent.putExtra(getResources().getString(R.string.movie), movie);
-                    startActivity(intent);
-                }
-            }
-        };
-    }
-
-    private class SearchRunnable implements Runnable {
-
-        private volatile String searchQuery;
-
-        public SearchRunnable() {
-        }
-
-        public void run() {
-            loadRows(searchQuery);
-        }
-
-        public void setSearchQuery(String value) {
-            this.searchQuery = value;
-        }
-    }
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/UpdateRecommendationsService.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/UpdateRecommendationsService.java
deleted file mode 100644
index d6fec30..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/UpdateRecommendationsService.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import android.app.IntentService;
-import android.app.PendingIntent;
-import android.app.TaskStackBuilder;
-import android.content.Intent;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/*
- * This class builds up to MAX_RECOMMMENDATIONS of recommendations and defines what happens 
- * when they're clicked from Recommendations seciton on Home screen
- */
-public class UpdateRecommendationsService extends IntentService {
-    private static final String TAG = "UpdateRecommendationsService";
-    private static final int MAX_RECOMMENDATIONS = 3;
-
-    public UpdateRecommendationsService() {
-        super("RecommendationService");
-    }
-
-    @Override
-    protected void onHandleIntent(Intent intent) {
-        Log.d(TAG, "Updating recommendation cards");
-        HashMap<String, List<Movie>> recommendations = VideoProvider.getMovieList();
-
-        int count = 0;
-
-        try {
-            RecommendationBuilder builder = new RecommendationBuilder()
-                    .setContext(getApplicationContext())
-                    .setSmallIcon(R.drawable.videos_by_google_icon);
-
-            for (Map.Entry<String, List<Movie>> entry : recommendations.entrySet())
-            {
-                for (int i = 0; i < entry.getValue().size(); i++) {
-                    Movie movie = entry.getValue().get(i);
-                    Log.d(TAG, "Recommendation - " + movie.getTitle());
-
-                    builder.setBackground(movie.getCardImageUrl())
-                            .setId(count + 1)
-                            .setPriority(MAX_RECOMMENDATIONS - count)
-                            .setTitle(movie.getTitle())
-                            .setDescription(getString(R.string.popular_header))
-                            .setImage(movie.getCardImageUrl())
-                            .setIntent(buildPendingIntent(movie))
-                            .build();
-
-                    if (++count >= MAX_RECOMMENDATIONS) {
-                        break;
-                    }
-                }
-                if (++count >= MAX_RECOMMENDATIONS) {
-                    break;
-                }
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to update recommendation", e);
-        }
-    }
-
-    private PendingIntent buildPendingIntent(Movie movie) {
-        Intent detailsIntent = new Intent(this, DetailsActivity.class);
-        detailsIntent.putExtra("Movie", movie);
-
-        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
-        stackBuilder.addParentStack(DetailsActivity.class);
-        stackBuilder.addNextIntent(detailsIntent);
-        // Ensure a unique PendingIntents, otherwise all recommendations end up with the same
-        // PendingIntent
-        detailsIntent.setAction(Long.toString(movie.getId()));
-
-        PendingIntent intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
-        return intent;
-    }
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VerticalGridFragment.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VerticalGridFragment.java
deleted file mode 100644
index 8d42f78..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VerticalGridFragment.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * 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.example.android.leanback;
-
-import java.util.*;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.OnItemClickedListener;
-import android.support.v17.leanback.widget.OnItemSelectedListener;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.VerticalGridPresenter;
-import android.util.Log;
-
-/*
- * VerticalGridFragment shows a grid of videos
- */
-public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment {
-    private static final String TAG = "VerticalGridFragment";
-
-    private static final int NUM_COLUMNS = 5;
-
-    private ArrayObjectAdapter mAdapter;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        Log.i(TAG, "onCreate");
-        super.onCreate(savedInstanceState);
-
-        setTitle(getString(R.string.vertical_grid_title));
-
-        setupFragment();
-    }
-
-    private void setupFragment() {
-        VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
-        gridPresenter.setNumberOfColumns(NUM_COLUMNS);
-        setGridPresenter(gridPresenter);
-
-        mAdapter = new ArrayObjectAdapter(new CardPresenter());
-
-        long seed = System.nanoTime();
-
-        HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
-
-        for (Map.Entry<String, List<Movie>> entry : movies.entrySet())
-        {
-            List<Movie> list = entry.getValue();
-            Collections.shuffle(list, new Random(seed));
-            for (int j = 0; j < list.size(); j++) {
-                mAdapter.add(list.get(j));
-            }
-        }
-
-        setAdapter(mAdapter);
-
-        setOnItemSelectedListener(new OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(Object item, Row row) {
-            }
-        });
-
-        setOnItemClickedListener(new OnItemClickedListener() {
-            @Override
-            public void onItemClicked(Object item, Row row) {
-                if (item instanceof Movie) {
-                    Movie movie = (Movie) item;
-                    Intent intent = new Intent(getActivity(), DetailsActivity.class);
-                    intent.putExtra(getString(R.string.movie), movie);
-                    startActivity(intent);
-                }
-            }
-        });
-
-    }
-
-}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/Utils.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/Utils.java
similarity index 63%
rename from prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/Utils.java
rename to prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/Utils.java
index 7f66018..bb881f1 100644
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/Utils.java
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/Utils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Google Inc. All Rights Reserved.
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package com.example.android.leanback;
+package com.example.android.tvleanback;
 
-import android.app.AlertDialog;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.graphics.Point;
 import android.view.Display;
 import android.view.WindowManager;
@@ -37,9 +35,6 @@
 
     /**
      * Returns the screen/display size
-     * 
-     * @param context
-     * @return
      */
     public static Point getDisplaySize(Context context) {
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
@@ -48,34 +43,11 @@
         display.getSize(size);
         int width = size.x;
         int height = size.y;
-        return new Point(width, height);
-    }
-
-    /**
-     * Shows an error dialog with a given text message.
-     * 
-     * @param context
-     * @param errorString
-     */
-
-    public static final void showErrorDialog(Context context, String errorString) {
-        new AlertDialog.Builder(context).setTitle(R.string.error)
-                .setMessage(errorString)
-                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int id) {
-                        dialog.cancel();
-                    }
-                })
-                .create()
-                .show();
+        return size;
     }
 
     /**
      * Shows a (long) toast
-     * 
-     * @param context
-     * @param msg
      */
     public static void showToast(Context context, String msg) {
         Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
@@ -83,15 +55,12 @@
 
     /**
      * Shows a (long) toast.
-     * 
-     * @param context
-     * @param resourceId
      */
     public static void showToast(Context context, int resourceId) {
         Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
     }
 
-    public static int dpToPx(int dp, Context ctx) {
+    public static int convertDpToPixel(Context ctx, int dp) {
         float density = ctx.getResources().getDisplayMetrics().density;
         return Math.round((float) dp * density);
     }
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/PaginatedCursor.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/PaginatedCursor.java
new file mode 100644
index 0000000..6ef9c08
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/PaginatedCursor.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.data;
+
+import android.database.AbstractCursor;
+import android.database.Cursor;
+
+/**
+ * A sample paginated cursor which will pre-fetch and cache rows.
+ */
+public class PaginatedCursor extends AbstractCursor {
+    /**
+     * The number of items that should be loaded each time.
+     */
+    private static final int PAGE_SIZE = 10;
+
+    /**
+     * The threshold of number of items left that a new page should be loaded.
+     */
+    private static final int PAGE_THRESHOLD = PAGE_SIZE / 2;
+
+    private final Cursor mCursor;
+    private final int mRowCount;
+    private final boolean[] mCachedRows;
+    private final String[] mColumnNames;
+    private final int mColumnCount;
+    private final int[] mColumnTypes;
+
+    private final byte[][][] mByteArrayDataCache;
+    private final float[][] mFloatDataCache;
+    private final int[][] mIntDataCache;
+    private final String[][] mStringDataCache;
+    /**
+     * Index mapping from column index into the data type specific cache index;
+     */
+    private final int[] mByteArrayCacheIndexMap;
+    private final int[] mFloatCacheIndexMap;
+    private final int[] mIntCacheIndexMap;
+    private final int[] mStringCacheIndexMap;
+    private int mByteArrayCacheColumnSize;
+    private int mFloatCacheColumnSize;
+    private int mIntCacheColumnSize;
+    private int mStringCacheColumnSize;
+    private int mLastCachePosition;
+
+    public PaginatedCursor(Cursor cursor) {
+        super();
+        mCursor = cursor;
+        mRowCount = mCursor.getCount();
+        mCachedRows = new boolean[mRowCount];
+        mColumnNames = mCursor.getColumnNames();
+        mColumnCount = mCursor.getColumnCount();
+        mColumnTypes = new int[mColumnCount];
+
+        mByteArrayCacheColumnSize = 0;
+        mFloatCacheColumnSize = 0;
+        mIntCacheColumnSize = 0;
+        mStringCacheColumnSize = 0;
+
+        mByteArrayCacheIndexMap = new int[mColumnCount];
+        mFloatCacheIndexMap = new int[mColumnCount];
+        mIntCacheIndexMap = new int[mColumnCount];
+        mStringCacheIndexMap = new int[mColumnCount];
+
+        mCursor.moveToFirst();
+        for (int i = 0; i < mColumnCount; i++) {
+            int type = mCursor.getType(i);
+            mColumnTypes[i] = type;
+            switch (type) {
+                case Cursor.FIELD_TYPE_BLOB:
+                    mByteArrayCacheIndexMap[i] = mByteArrayCacheColumnSize++;
+                    break;
+                case Cursor.FIELD_TYPE_FLOAT:
+                    mFloatCacheIndexMap[i] = mFloatCacheColumnSize++;
+                    break;
+                case Cursor.FIELD_TYPE_INTEGER:
+                    mIntCacheIndexMap[i] = mIntCacheColumnSize++;
+                    break;
+                case Cursor.FIELD_TYPE_STRING:
+                    mStringCacheIndexMap[i] = mStringCacheColumnSize++;
+                    break;
+            }
+        }
+
+        mByteArrayDataCache = mByteArrayCacheColumnSize > 0 ? new byte[mRowCount][][] : null;
+        mFloatDataCache = mFloatCacheColumnSize > 0 ? new float[mRowCount][] : null;
+        mIntDataCache = mIntCacheColumnSize > 0 ? new int[mRowCount][] : null;
+        mStringDataCache = mStringCacheColumnSize > 0 ? new String[mRowCount][] : null;
+
+        for (int i = 0; i < mRowCount; i++) {
+            mCachedRows[i] = false;
+            if (mByteArrayDataCache != null) {
+                mByteArrayDataCache[i] = new byte[mByteArrayCacheColumnSize][];
+            }
+            if (mFloatDataCache != null) {
+                mFloatDataCache[i] = new float[mFloatCacheColumnSize];
+            }
+            if (mIntDataCache != null) {
+                mIntDataCache[i] = new int[mIntCacheColumnSize];
+            }
+            if (mStringDataCache != null) {
+                mStringDataCache[i] = new String[mStringCacheColumnSize];
+            }
+        }
+
+        // Cache at the initialization stage.
+        loadCacheStartingFromPosition(0);
+    }
+
+    /**
+     * Try to load un-cached data with size {@link PAGE_SIZE} starting from given index.
+     */
+    private void loadCacheStartingFromPosition(int index) {
+        mCursor.moveToPosition(index);
+        for (int row = index; row < (index + PAGE_SIZE) && row < mRowCount; row++) {
+            if (!mCachedRows[row]) {
+                for (int col = 0; col < mColumnCount; col++) {
+                    switch (mCursor.getType(col)) {
+                        case Cursor.FIELD_TYPE_BLOB:
+                            mByteArrayDataCache[row][mByteArrayCacheIndexMap[col]] =
+                                    mCursor.getBlob(col);
+                            break;
+                        case Cursor.FIELD_TYPE_FLOAT:
+                            mFloatDataCache[row][mFloatCacheIndexMap[col]] = mCursor.getFloat(col);
+                            break;
+                        case Cursor.FIELD_TYPE_INTEGER:
+                            mIntDataCache[row][mIntCacheIndexMap[col]] = mCursor.getInt(col);
+                            break;
+                        case Cursor.FIELD_TYPE_STRING:
+                            mStringDataCache[row][mStringCacheIndexMap[col]] =
+                                    mCursor.getString(col);
+                            break;
+                    }
+                }
+                mCachedRows[row] = true;
+            }
+            mCursor.moveToNext();
+        }
+        mLastCachePosition = Math.min(index + PAGE_SIZE, mRowCount) - 1;
+    }
+
+    @Override
+    public boolean onMove(int oldPosition, int newPosition) {
+        // If it's a consecutive move and haven't exceeds the threshold, do nothing.
+        if ((newPosition - oldPosition) != 1 ||
+                (newPosition + PAGE_THRESHOLD) <= mLastCachePosition) {
+            loadCacheStartingFromPosition(newPosition);
+        }
+        return true;
+    }
+
+    @Override
+    public int getType(int column) {
+        return mColumnTypes[column];
+    }
+
+    @Override
+    public int getCount() {
+        return mRowCount;
+    }
+
+    @Override
+    public String[] getColumnNames() {
+        return mColumnNames;
+    }
+
+    @Override
+    public String getString(int column) {
+        return mStringDataCache[mPos][mStringCacheIndexMap[column]];
+    }
+
+    @Override
+    public short getShort(int column) {
+        return (short) mIntDataCache[mPos][mIntCacheIndexMap[column]];
+    }
+
+    @Override
+    public int getInt(int column) {
+        return mIntDataCache[mPos][mIntCacheIndexMap[column]];
+    }
+
+    @Override
+    public long getLong(int column) {
+        return mIntDataCache[mPos][mIntCacheIndexMap[column]];
+    }
+
+    @Override
+    public float getFloat(int column) {
+        return mFloatDataCache[mPos][mFloatCacheIndexMap[column]];
+    }
+
+    @Override
+    public double getDouble(int column) {
+        return mFloatDataCache[mPos][mFloatCacheIndexMap[column]];
+    }
+
+    @Override
+    public byte[] getBlob(int column) {
+        return mByteArrayDataCache[mPos][mByteArrayCacheIndexMap[column]];
+    }
+
+    @Override
+    public boolean isNull(int column) {
+        return mColumnTypes[column] == Cursor.FIELD_TYPE_NULL;
+    }
+
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoContentProvider.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoContentProvider.java
new file mode 100644
index 0000000..c010d03
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoContentProvider.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.data;
+
+import android.app.SearchManager;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+/**
+ * Provides access to the video database.
+ */
+public class VideoContentProvider extends ContentProvider {
+    private static String TAG = "VideoContentProvider";
+    public static String AUTHORITY = "com.example.android.tvleanback";
+
+    // UriMatcher stuff
+    private static final int SEARCH_SUGGEST = 0;
+    private static final int REFRESH_SHORTCUT = 1;
+    private static final UriMatcher URI_MATCHER = buildUriMatcher();
+
+    private VideoDatabase mVideoDatabase;
+
+    /**
+     * Builds up a UriMatcher for search suggestion and shortcut refresh queries.
+     */
+    private static UriMatcher buildUriMatcher() {
+        UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
+        // to get suggestions...
+        Log.d(TAG, "suggest_uri_path_query: " + SearchManager.SUGGEST_URI_PATH_QUERY);
+        matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
+        matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
+        return matcher;
+    }
+
+    @Override
+    public boolean onCreate() {
+        Log.d(TAG, "onCreate");
+        mVideoDatabase = new VideoDatabase(getContext());
+        return true;
+    }
+
+    /**
+     * Handles all the video searches and suggestion queries from the Search Manager.
+     * When requesting a specific word, the uri alone is required.
+     * When searching all of the video for matches, the selectionArgs argument must carry
+     * the search query as the first element.
+     * All other arguments are ignored.
+     */
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                        String sortOrder) {
+        // Use the UriMatcher to see what kind of query we have and format the db query accordingly
+        switch (URI_MATCHER.match(uri)) {
+            case SEARCH_SUGGEST:
+                Log.d(TAG, "search suggest: " + selectionArgs[0] + " URI: " + uri);
+                if (selectionArgs == null) {
+                    throw new IllegalArgumentException(
+                            "selectionArgs must be provided for the Uri: " + uri);
+                }
+                return getSuggestions(selectionArgs[0]);
+            default:
+                throw new IllegalArgumentException("Unknown Uri: " + uri);
+        }
+    }
+
+    private Cursor getSuggestions(String query) {
+        query = query.toLowerCase();
+        String[] columns = new String[]{
+                BaseColumns._ID,
+                VideoDatabase.KEY_NAME,
+                VideoDatabase.KEY_DESCRIPTION,
+                VideoDatabase.KEY_ICON,
+                VideoDatabase.KEY_DATA_TYPE,
+                VideoDatabase.KEY_IS_LIVE,
+                VideoDatabase.KEY_VIDEO_WIDTH,
+                VideoDatabase.KEY_VIDEO_HEIGHT,
+                VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG,
+                VideoDatabase.KEY_PURCHASE_PRICE,
+                VideoDatabase.KEY_RENTAL_PRICE,
+                VideoDatabase.KEY_RATING_STYLE,
+                VideoDatabase.KEY_RATING_SCORE,
+                VideoDatabase.KEY_PRODUCTION_YEAR,
+                VideoDatabase.KEY_COLUMN_DURATION,
+                VideoDatabase.KEY_ACTION,
+                SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID
+        };
+        return mVideoDatabase.getWordMatch(query, columns);
+    }
+
+    /**
+     * This method is required in order to query the supported types.
+     * It's also useful in our own query() method to determine the type of Uri received.
+     */
+    @Override
+    public String getType(Uri uri) {
+        switch (URI_MATCHER.match(uri)) {
+            case SEARCH_SUGGEST:
+                return SearchManager.SUGGEST_MIME_TYPE;
+            case REFRESH_SHORTCUT:
+                return SearchManager.SHORTCUT_MIME_TYPE;
+            default:
+                throw new IllegalArgumentException("Unknown URL " + uri);
+        }
+    }
+
+    // Other required implementations...
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoDatabase.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoDatabase.java
new file mode 100644
index 0000000..c547f42
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoDatabase.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.data;
+
+import android.app.SearchManager;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.media.Rating;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.model.Movie;
+
+import org.json.JSONException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Contains logic to return specific words from the video database, and
+ * load the video database table when it needs to be created.
+ */
+public class VideoDatabase {
+    //The columns we'll include in the video database table
+    public static final String KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1;
+    public static final String KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2;
+    public static final String KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE;
+    public static final String KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE;
+    public static final String KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE;
+    public static final String KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH;
+    public static final String KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT;
+    public static final String KEY_AUDIO_CHANNEL_CONFIG =
+            SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG;
+    public static final String KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE;
+    public static final String KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE;
+    public static final String KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE;
+    public static final String KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE;
+    public static final String KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR;
+    public static final String KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION;
+    public static final String KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION;
+    private static final String TAG = "VideoDatabase";
+    private static final String DATABASE_NAME = "video_database_leanback";
+    private static final String FTS_VIRTUAL_TABLE = "Leanback_table";
+    private static final int DATABASE_VERSION = 2;
+    private static final HashMap<String, String> COLUMN_MAP = buildColumnMap();
+    private static int CARD_WIDTH = 313;
+    private static int CARD_HEIGHT = 176;
+    private final VideoDatabaseOpenHelper mDatabaseOpenHelper;
+
+    /**
+     * Constructor
+     *
+     * @param context The Context within which to work, used to create the DB
+     */
+    public VideoDatabase(Context context) {
+        mDatabaseOpenHelper = new VideoDatabaseOpenHelper(context);
+    }
+
+    /**
+     * Builds a map for all columns that may be requested, which will be given to the
+     * SQLiteQueryBuilder. This is a good way to define aliases for column names, but must include
+     * all columns, even if the value is the key. This allows the ContentProvider to request
+     * columns w/o the need to know real column names and create the alias itself.
+     */
+    private static HashMap<String, String> buildColumnMap() {
+        HashMap<String, String> map = new HashMap<String, String>();
+        map.put(KEY_NAME, KEY_NAME);
+        map.put(KEY_DESCRIPTION, KEY_DESCRIPTION);
+        map.put(KEY_ICON, KEY_ICON);
+        map.put(KEY_DATA_TYPE, KEY_DATA_TYPE);
+        map.put(KEY_IS_LIVE, KEY_IS_LIVE);
+        map.put(KEY_VIDEO_WIDTH, KEY_VIDEO_WIDTH);
+        map.put(KEY_VIDEO_HEIGHT, KEY_VIDEO_HEIGHT);
+        map.put(KEY_AUDIO_CHANNEL_CONFIG, KEY_AUDIO_CHANNEL_CONFIG);
+        map.put(KEY_PURCHASE_PRICE, KEY_PURCHASE_PRICE);
+        map.put(KEY_RENTAL_PRICE, KEY_RENTAL_PRICE);
+        map.put(KEY_RATING_STYLE, KEY_RATING_STYLE);
+        map.put(KEY_RATING_SCORE, KEY_RATING_SCORE);
+        map.put(KEY_PRODUCTION_YEAR, KEY_PRODUCTION_YEAR);
+        map.put(KEY_COLUMN_DURATION, KEY_COLUMN_DURATION);
+        map.put(KEY_ACTION, KEY_ACTION);
+        map.put(BaseColumns._ID, "rowid AS " +
+                BaseColumns._ID);
+        map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " +
+                SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
+        map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " +
+                SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
+        return map;
+    }
+
+    /**
+     * Returns a Cursor positioned at the word specified by rowId
+     *
+     * @param rowId   id of word to retrieve
+     * @param columns The columns to include, if null then all are included
+     * @return Cursor positioned to matching word, or null if not found.
+     */
+    public Cursor getWord(String rowId, String[] columns) {
+        /* This builds a query that looks like:
+         *     SELECT <columns> FROM <table> WHERE rowid = <rowId>
+         */
+        String selection = "rowid = ?";
+        String[] selectionArgs = new String[]{rowId};
+
+        return query(selection, selectionArgs, columns);
+    }
+
+    /**
+     * Returns a Cursor over all words that match the first letter of the given query
+     *
+     * @param query   The string to search for
+     * @param columns The columns to include, if null then all are included
+     * @return Cursor over all words that match, or null if none found.
+     */
+    public Cursor getWordMatch(String query, String[] columns) {
+        /* This builds a query that looks like:
+         *     SELECT <columns> FROM <table> WHERE <KEY_WORD> MATCH 'query*'
+         * which is an FTS3 search for the query text (plus a wildcard) inside the word column.
+         *
+         * - "rowid" is the unique id for all rows but we need this value for the "_id" column in
+         *    order for the Adapters to work, so the columns need to make "_id" an alias for "rowid"
+         * - "rowid" also needs to be used by the SUGGEST_COLUMN_INTENT_DATA alias in order
+         *   for suggestions to carry the proper intent data.SearchManager
+         *   These aliases are defined in the VideoProvider when queries are made.
+         * - This can be revised to also search the definition text with FTS3 by changing
+         *   the selection clause to use FTS_VIRTUAL_TABLE instead of KEY_WORD (to search across
+         *   the entire table, but sorting the relevance could be difficult.
+         */
+        String selection = KEY_NAME + " MATCH ?";
+        String[] selectionArgs = new String[]{query + "*"};
+
+        return query(selection, selectionArgs, columns);
+    }
+
+    /**
+     * Performs a database query.
+     *
+     * @param selection     The selection clause
+     * @param selectionArgs Selection arguments for "?" components in the selection
+     * @param columns       The columns to return
+     * @return A Cursor over all rows matching the query
+     */
+    private Cursor query(String selection, String[] selectionArgs, String[] columns) {
+        /* The SQLiteBuilder provides a map for all possible columns requested to
+         * actual columns in the database, creating a simple column alias mechanism
+         * by which the ContentProvider does not need to know the real column names
+         */
+        SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
+        builder.setTables(FTS_VIRTUAL_TABLE);
+        builder.setProjectionMap(COLUMN_MAP);
+
+        Cursor cursor = new PaginatedCursor(builder.query(mDatabaseOpenHelper.getReadableDatabase(),
+                columns, selection, selectionArgs, null, null, null));
+
+        if (cursor == null) {
+            return null;
+        } else if (!cursor.moveToFirst()) {
+            cursor.close();
+            return null;
+        }
+        return cursor;
+    }
+
+    /**
+     * This creates/opens the database.
+     */
+    private static class VideoDatabaseOpenHelper extends SQLiteOpenHelper {
+
+        private final Context mHelperContext;
+        private SQLiteDatabase mDatabase;
+
+        VideoDatabaseOpenHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+            mHelperContext = context;
+        }
+
+        /* Note that FTS3 does not support column constraints and thus, you cannot
+         * declare a primary key. However, "rowid" is automatically used as a unique
+         * identifier, so when making requests, we will use "_id" as an alias for "rowid"
+         */
+        private static final String FTS_TABLE_CREATE =
+                "CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE +
+                        " USING fts3 (" +
+                        KEY_NAME + ", " +
+                        KEY_DESCRIPTION + "," +
+                        KEY_ICON + "," +
+                        KEY_DATA_TYPE + "," +
+                        KEY_IS_LIVE + "," +
+                        KEY_VIDEO_WIDTH + "," +
+                        KEY_VIDEO_HEIGHT + "," +
+                        KEY_AUDIO_CHANNEL_CONFIG + "," +
+                        KEY_PURCHASE_PRICE + "," +
+                        KEY_RENTAL_PRICE + "," +
+                        KEY_RATING_STYLE + "," +
+                        KEY_RATING_SCORE + "," +
+                        KEY_PRODUCTION_YEAR + "," +
+                        KEY_COLUMN_DURATION + "," +
+                        KEY_ACTION + ");";
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            mDatabase = db;
+            mDatabase.execSQL(FTS_TABLE_CREATE);
+            loadDatabase();
+        }
+
+        /**
+         * Starts a thread to load the database table with words
+         */
+        private void loadDatabase() {
+            new Thread(new Runnable() {
+                public void run() {
+                    try {
+                        loadMovies();
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }).start();
+        }
+
+        private void loadMovies() throws IOException {
+            Log.d(TAG, "Loading movies...");
+
+            HashMap<String, List<Movie>> movies = null;
+            try {
+                VideoProvider.setContext(mHelperContext);
+                movies = VideoProvider.buildMedia(mHelperContext,
+                        mHelperContext.getResources().getString(R.string.catalog_url));
+            } catch (JSONException e) {
+                Log.e(TAG, "JSon Exception when loading movie", e);
+            }
+
+            for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
+                List<Movie> list = entry.getValue();
+                for (Movie movie : list) {
+                    long id = addMovie(movie);
+                    if (id < 0) {
+                        Log.e(TAG, "unable to add movie: " + movie.toString());
+                    }
+                }
+            }
+            // add dummy movies to illustrate action deep link in search detail
+            // Android TV Search requires that the media’s title, MIME type, production year,
+            // and duration all match exactly to those found from Google’s servers.
+            addMovieForDeepLink(mHelperContext.getString(R.string.noah_title),
+                    mHelperContext.getString(R.string.noah_description),
+                    R.drawable.noah,
+                    8280000,
+                    "2014");
+            addMovieForDeepLink(mHelperContext.getString(R.string.dragon2_title),
+                    mHelperContext.getString(R.string.dragon2_description),
+                    R.drawable.dragon2,
+                    6300000,
+                    "2014");
+            addMovieForDeepLink(mHelperContext.getString(R.string.maleficent_title),
+                    mHelperContext.getString(R.string.maleficent_description),
+                    R.drawable.maleficent,
+                    5820000,
+                    "2014");
+        }
+
+        /**
+         * Add a movie to the database.
+         *
+         * @return rowId or -1 if failed
+         */
+        public long addMovie(Movie movie) {
+            ContentValues initialValues = new ContentValues();
+            initialValues.put(KEY_NAME, movie.getTitle());
+            initialValues.put(KEY_DESCRIPTION, movie.getDescription());
+            initialValues.put(KEY_ICON, movie.getCardImageUrl());
+            initialValues.put(KEY_DATA_TYPE, "video/mp4");
+            initialValues.put(KEY_IS_LIVE, false);
+            initialValues.put(KEY_VIDEO_WIDTH, CARD_WIDTH);
+            initialValues.put(KEY_VIDEO_HEIGHT, CARD_HEIGHT);
+            initialValues.put(KEY_AUDIO_CHANNEL_CONFIG, "2.0");
+            initialValues.put(KEY_PURCHASE_PRICE, mHelperContext.getString(R.string.buy_2));
+            initialValues.put(KEY_RENTAL_PRICE, mHelperContext.getString(R.string.rent_2));
+            initialValues.put(KEY_RATING_STYLE, Rating.RATING_5_STARS);
+            initialValues.put(KEY_RATING_SCORE, 3.5f);
+            initialValues.put(KEY_PRODUCTION_YEAR, 2014);
+            initialValues.put(KEY_COLUMN_DURATION, 0);
+            initialValues.put(KEY_ACTION, mHelperContext.getString(R.string.global_search));
+            return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues);
+        }
+
+        /**
+         * Add an entry to the database for dummy deep link.
+         *
+         * @return rowId or -1 if failed
+         */
+        public long addMovieForDeepLink(String title, String description, int icon, long duration, String production_year) {
+            ContentValues initialValues = new ContentValues();
+            initialValues.put(KEY_NAME, title);
+            initialValues.put(KEY_DESCRIPTION, description);
+            initialValues.put(KEY_ICON, icon);
+            initialValues.put(KEY_DATA_TYPE, "video/mp4");
+            initialValues.put(KEY_IS_LIVE, false);
+            initialValues.put(KEY_VIDEO_WIDTH, 1280);
+            initialValues.put(KEY_VIDEO_HEIGHT, 720);
+            initialValues.put(KEY_AUDIO_CHANNEL_CONFIG, "2.0");
+            initialValues.put(KEY_PURCHASE_PRICE, "Free");
+            initialValues.put(KEY_RENTAL_PRICE, "Free");
+            initialValues.put(KEY_RATING_STYLE, Rating.RATING_5_STARS);
+            initialValues.put(KEY_RATING_SCORE, 3.5f);
+            initialValues.put(KEY_PRODUCTION_YEAR, production_year);
+            initialValues.put(KEY_COLUMN_DURATION, duration);
+            return mDatabase.insert(FTS_VIRTUAL_TABLE, null, initialValues);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+                    + newVersion + ", which will destroy all old data");
+            db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE);
+            onCreate(db);
+        }
+
+
+    }
+
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VideoItemLoader.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoItemLoader.java
similarity index 88%
rename from prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VideoItemLoader.java
rename to prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoItemLoader.java
index 108d179..cc7fb47 100644
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VideoItemLoader.java
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoItemLoader.java
@@ -1,28 +1,30 @@
 /*
- * Copyright (C) 2013 Google Inc. All Rights Reserved. 
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * 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 
+ * 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 
+ * 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 
+ * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-package com.example.android.leanback;
-
-import java.util.HashMap;
-import java.util.List;
+package com.example.android.tvleanback.data;
 
 import android.content.AsyncTaskLoader;
 import android.content.Context;
 import android.util.Log;
 
+import com.example.android.tvleanback.model.Movie;
+
+import java.util.HashMap;
+import java.util.List;
+
 /*
  * This class asynchronously loads videos from a backend
  */
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VideoProvider.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoProvider.java
similarity index 82%
rename from prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VideoProvider.java
rename to prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoProvider.java
index cd7ebfd..0be2197 100644
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VideoProvider.java
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/data/VideoProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 Google Inc. All Rights Reserved.
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-package com.example.android.leanback;
+package com.example.android.tvleanback.data;
 
 import android.content.Context;
 import android.util.Log;
 
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.model.Movie;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -49,58 +52,25 @@
     private static String TAG_BACKGROUND = "background";
     private static String TAG_TITLE = "title";
 
-    private static HashMap<String, List<Movie>> mMovieList;
-    private static Context mContext;
-    private static String mPrefixUrl;
+    private static HashMap<String, List<Movie>> sMovieList;
+    private static Context sContext;
+    private static String sPrefixUrl;
 
     public static void setContext(Context context) {
-        if (mContext == null)
-            mContext = context;
-    }
-
-    protected JSONObject parseUrl(String urlString) {
-        Log.d(TAG, "Parse URL: " + urlString);
-        InputStream is = null;
-
-        mPrefixUrl = mContext.getResources().getString(R.string.prefix_url);
-
-        try {
-            java.net.URL url = new java.net.URL(urlString);
-            URLConnection urlConnection = url.openConnection();
-            is = new BufferedInputStream(urlConnection.getInputStream());
-            BufferedReader reader = new BufferedReader(new InputStreamReader(
-                    urlConnection.getInputStream(), "iso-8859-1"), 8);
-            StringBuilder sb = new StringBuilder();
-            String line = null;
-            while ((line = reader.readLine()) != null) {
-                sb.append(line);
-            }
-            String json = sb.toString();
-            return new JSONObject(json);
-        } catch (Exception e) {
-            Log.d(TAG, "Failed to parse the json for media list", e);
-            return null;
-        } finally {
-            if (null != is) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                    Log.d(TAG, "JSON feed closed", e);
-                }
-            }
-        }
+        if (sContext == null)
+            sContext = context;
     }
 
     public static HashMap<String, List<Movie>> getMovieList() {
-        return mMovieList;
+        return sMovieList;
     }
 
     public static HashMap<String, List<Movie>> buildMedia(Context ctx, String url)
             throws JSONException {
-        if (null != mMovieList) {
-            return mMovieList;
+        if (null != sMovieList) {
+            return sMovieList;
         }
-        mMovieList = new HashMap<String, List<Movie>>();
+        sMovieList = new HashMap<String, List<Movie>>();
 
         JSONObject jsonObj = new VideoProvider().parseUrl(url);
         JSONArray categories = jsonObj.getJSONArray(TAG_GOOGLE_VIDEOS);
@@ -138,19 +108,23 @@
                                 videoUrl, cardImageUrl,
                                 bgImageUrl));
                     }
-                    mMovieList.put(category_name, categoryList);
+                    sMovieList.put(category_name, categoryList);
                 }
             }
         }
-        return mMovieList;
+        return sMovieList;
     }
 
-    private static Movie buildMovieInfo(String category, String title,
-            String description, String studio, String videoUrl, String cardImageUrl,
-            String bgImageUrl) {
+    private static Movie buildMovieInfo(String category,
+                                        String title,
+                                        String description,
+                                        String studio,
+                                        String videoUrl,
+                                        String cardImageUrl,
+                                        String bgImageUrl) {
         Movie movie = new Movie();
         movie.setId(Movie.getCount());
-        Movie.incCount();
+        Movie.incrementCount();
         movie.setTitle(title);
         movie.setDescription(description);
         movie.setStudio(studio);
@@ -164,7 +138,7 @@
 
     private static String getVideoPrefix(String category, String videoUrl) {
         String ret = "";
-        ret = mPrefixUrl + category.replace(" ", "%20") + '/' +
+        ret = sPrefixUrl + category.replace(" ", "%20") + '/' +
                 videoUrl.replace(" ", "%20");
         return ret;
     }
@@ -172,9 +146,42 @@
     private static String getThumbPrefix(String category, String title, String imageUrl) {
         String ret = "";
 
-        ret = mPrefixUrl + category.replace(" ", "%20") + '/' +
+        ret = sPrefixUrl + category.replace(" ", "%20") + '/' +
                 title.replace(" ", "%20") + '/' +
                 imageUrl.replace(" ", "%20");
         return ret;
     }
+
+    protected JSONObject parseUrl(String urlString) {
+        Log.d(TAG, "Parse URL: " + urlString);
+        InputStream is = null;
+
+        sPrefixUrl = sContext.getResources().getString(R.string.prefix_url);
+
+        try {
+            java.net.URL url = new java.net.URL(urlString);
+            URLConnection urlConnection = url.openConnection();
+            is = new BufferedInputStream(urlConnection.getInputStream());
+            BufferedReader reader = new BufferedReader(new InputStreamReader(
+                    urlConnection.getInputStream(), "iso-8859-1"), 8);
+            StringBuilder sb = new StringBuilder();
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                sb.append(line);
+            }
+            String json = sb.toString();
+            return new JSONObject(json);
+        } catch (Exception e) {
+            Log.d(TAG, "Failed to parse the json for media list", e);
+            return null;
+        } finally {
+            if (null != is) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    Log.d(TAG, "JSON feed closed", e);
+                }
+            }
+        }
+    }
 }
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/model/Movie.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/model/Movie.java
new file mode 100644
index 0000000..f4e540f
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/model/Movie.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.model;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/*
+ * Movie class represents video entity with title, description, image thumbs and video url.
+ */
+public class Movie implements Parcelable {
+    private static final String TAG = "Movie";
+    static final long serialVersionUID = 727566175075960653L;
+    private static int sCount = 0;
+    private String mId;
+    private String mTitle;
+    private String mDescription;
+    private String mBgImageUrl;
+    private String mCardImageUrl;
+    private String mVideoUrl;
+    private String mStudio;
+    private String mCategory;
+
+    public Movie() {
+
+    }
+
+    public Movie(Parcel in){
+        String[] data = new String[8];
+
+        in.readStringArray(data);
+        mId = data[0];
+        mTitle = data[1];
+        mDescription = data[2];
+        mBgImageUrl = data[3];
+        mCardImageUrl = data[4];
+        mVideoUrl = data[5];
+        mStudio = data[6];
+        mCategory = data[7];
+    }
+
+    public static String getCount() {
+        return Integer.toString(sCount);
+    }
+
+    public static void incrementCount() {
+        sCount++;
+    }
+
+    public String getId() {
+        return mId;
+    }
+
+    public void setId(String id) {
+        mId = id;
+    }
+
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public void setTitle(String title) {
+        mTitle = title;
+    }
+
+    public String getDescription() {
+        return mDescription;
+    }
+
+    public void setDescription(String description) {
+        mDescription = description;
+    }
+
+    public String getStudio() {
+        return mStudio;
+    }
+
+    public void setStudio(String studio) {
+        mStudio = studio;
+    }
+
+    public String getVideoUrl() {
+        return mVideoUrl;
+    }
+
+    public void setVideoUrl(String videoUrl) {
+        mVideoUrl = videoUrl;
+    }
+
+    public String getBackgroundImageUrl() {
+        return mBgImageUrl;
+    }
+
+    public void setBackgroundImageUrl(String bgImageUrl) {
+        mBgImageUrl = bgImageUrl;
+    }
+
+    public String getCardImageUrl() {
+        return mCardImageUrl;
+    }
+
+    public void setCardImageUrl(String cardImageUrl) {
+        mCardImageUrl = cardImageUrl;
+    }
+
+    public String getCategory() {
+        return mCategory;
+    }
+
+    public void setCategory(String category) {
+        mCategory = category;
+    }
+
+    public URI getBackgroundImageURI() {
+        try {
+            return new URI(getBackgroundImageUrl());
+        } catch (URISyntaxException e) {
+            return null;
+        }
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStringArray(new String[] {mId,
+                mTitle,
+                mDescription,
+                mBgImageUrl,
+                mCardImageUrl,
+                mVideoUrl,
+                mStudio,
+                mCategory});
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(200);
+        sb.append("Movie{");
+        sb.append("mId=" + mId);
+        sb.append(", mTitle='" + mTitle + '\'');
+        sb.append(", mVideoUrl='" + mVideoUrl + '\'');
+        sb.append(", backgroundImageUrl='" + mBgImageUrl + '\'');
+        sb.append(", backgroundImageURI='" + getBackgroundImageURI().toString() + '\'');
+        sb.append(", mCardImageUrl='" + mCardImageUrl + '\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public Movie createFromParcel(Parcel in) {
+            return new Movie(in);
+        }
+
+        public Movie[] newArray(int size) {
+            return new Movie[size];
+        }
+    };
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/presenter/CardPresenter.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/presenter/CardPresenter.java
new file mode 100644
index 0000000..9e4c2b5
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/presenter/CardPresenter.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.presenter;
+
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.Presenter;
+import android.util.Log;
+import android.view.ViewGroup;
+
+import com.bumptech.glide.Glide;
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.model.Movie;
+
+/*
+ * A CardPresenter is used to generate Views and bind Objects to them on demand. 
+ * It contains an Image CardView
+ */
+public class CardPresenter extends Presenter {
+    private static final String TAG = "CardPresenter";
+
+    private static int CARD_WIDTH = 313;
+    private static int CARD_HEIGHT = 176;
+    private static int sSelectedBackgroundColor;
+    private static int sDefaultBackgroundColor;
+    private Drawable mDefaultCardImage;
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent) {
+        Log.d(TAG, "onCreateViewHolder");
+
+        sDefaultBackgroundColor = parent.getResources().getColor(R.color.default_background);
+        sSelectedBackgroundColor = parent.getResources().getColor(R.color.selected_background);
+        mDefaultCardImage = parent.getResources().getDrawable(R.drawable.movie);
+
+        ImageCardView cardView = new ImageCardView(parent.getContext()) {
+            @Override
+            public void setSelected(boolean selected) {
+                updateCardBackgroundColor(this, selected);
+                super.setSelected(selected);
+            }
+        };
+
+        cardView.setFocusable(true);
+        cardView.setFocusableInTouchMode(true);
+        updateCardBackgroundColor(cardView, false);
+        return new ViewHolder(cardView);
+    }
+
+    private static void updateCardBackgroundColor(ImageCardView view, boolean selected) {
+        int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
+        // Both background colors should be set because the view's background is temporarily visible
+        // during animations.
+        view.setBackgroundColor(color);
+        view.findViewById(R.id.info_field).setBackgroundColor(color);
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        Movie movie = (Movie) item;
+        ImageCardView cardView = (ImageCardView) viewHolder.view;
+
+        Log.d(TAG, "onBindViewHolder");
+        if (movie.getCardImageUrl() != null) {
+            cardView.setTitleText(movie.getTitle());
+            cardView.setContentText(movie.getStudio());
+            cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
+            Glide.with(viewHolder.view.getContext())
+                    .load(movie.getCardImageUrl())
+                    .centerCrop()
+                    .error(mDefaultCardImage)
+                    .into(cardView.getMainImageView());
+        }
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+        Log.d(TAG, "onUnbindViewHolder");
+        ImageCardView cardView = (ImageCardView) viewHolder.view;
+        // Remove references to images so that the garbage collector can free up memory
+        cardView.setBadgeImage(null);
+        cardView.setMainImage(null);
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/presenter/DetailsDescriptionPresenter.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/presenter/DetailsDescriptionPresenter.java
new file mode 100644
index 0000000..fc6c6c9
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/presenter/DetailsDescriptionPresenter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.presenter;
+
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+
+import com.example.android.tvleanback.model.Movie;
+
+public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter {
+
+    @Override
+    protected void onBindDescription(ViewHolder viewHolder, Object item) {
+        Movie movie = (Movie) item;
+
+        if (movie != null) {
+            viewHolder.getTitle().setText(movie.getTitle());
+            viewHolder.getSubtitle().setText(movie.getStudio());
+            viewHolder.getBody().setText(movie.getDescription());
+        }
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/presenter/GridItemPresenter.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/presenter/GridItemPresenter.java
new file mode 100644
index 0000000..bfec364
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/presenter/GridItemPresenter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.presenter;
+
+import android.graphics.Color;
+import android.support.v17.leanback.widget.Presenter;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.ui.MainFragment;
+
+public class GridItemPresenter extends Presenter {
+    private static int GRID_ITEM_WIDTH = 200;
+    private static int GRID_ITEM_HEIGHT = 200;
+
+    private MainFragment mainFragment;
+
+    public GridItemPresenter(MainFragment mainFragment) {
+        this.mainFragment = mainFragment;
+    }
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent) {
+        TextView view = new TextView(parent.getContext());
+        view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
+        view.setFocusable(true);
+        view.setFocusableInTouchMode(true);
+        view.setBackgroundColor(mainFragment.getResources().getColor(R.color.default_background));
+        view.setTextColor(Color.WHITE);
+        view.setGravity(Gravity.CENTER);
+        return new ViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+        ((TextView) viewHolder.view).setText((String) item);
+    }
+
+    @Override
+    public void onUnbindViewHolder(ViewHolder viewHolder) {
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/BootupActivity.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/recommendation/BootupActivity.java
similarity index 73%
rename from prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/BootupActivity.java
rename to prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/recommendation/BootupActivity.java
index 4e35e3d..5a1b7d3 100644
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/BootupActivity.java
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/recommendation/BootupActivity.java
@@ -1,18 +1,20 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
- * 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
+ * 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
+ *      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.
+ * 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.example.android.leanback;
+package com.example.android.tvleanback.recommendation;
 
 import android.app.AlarmManager;
 import android.app.PendingIntent;
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/recommendation/RecommendationBuilder.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/recommendation/RecommendationBuilder.java
new file mode 100644
index 0000000..ce6b6b3
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/recommendation/RecommendationBuilder.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.recommendation;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+
+import com.example.android.tvleanback.R;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/*
+ * This class builds recommendations as notifications with videos as inputs.
+ */
+public class RecommendationBuilder {
+    private static final String TAG = "RecommendationBuilder";
+    private static final String
+            BACKGROUND_URI_PREFIX = "content://com.example.android.tvleanback.recommendation/";
+
+    private Context mContext;
+
+    private int mId;
+    private int mPriority;
+    private int mSmallIcon;
+    private String mTitle;
+    private String mDescription;
+    private Bitmap mBitmap;
+    private String mBackgroundUri;
+    private String mGroupKey;
+    private String mSort;
+    private PendingIntent mIntent;
+
+    public RecommendationBuilder() {
+    }
+
+    public RecommendationBuilder setContext(Context context) {
+        mContext = context;
+        return this;
+    }
+
+    public RecommendationBuilder setId(int id) {
+        mId = id;
+        return this;
+    }
+
+    public RecommendationBuilder setPriority(int priority) {
+        mPriority = priority;
+        return this;
+    }
+
+    public RecommendationBuilder setTitle(String title) {
+        mTitle = title;
+        return this;
+    }
+
+    public RecommendationBuilder setDescription(String description) {
+        mDescription = description;
+        return this;
+    }
+
+    public RecommendationBuilder setBitmap(Bitmap bitmap) {
+        mBitmap = bitmap;
+        return this;
+    }
+
+    public RecommendationBuilder setBackground(String uri) {
+        mBackgroundUri = uri;
+        return this;
+    }
+
+    public RecommendationBuilder setIntent(PendingIntent intent) {
+        mIntent = intent;
+        return this;
+    }
+
+    public RecommendationBuilder setSmallIcon(int resourceId) {
+        mSmallIcon = resourceId;
+        return this;
+    }
+
+    public Notification build() {
+
+        Bundle extras = new Bundle();
+        File bitmapFile = getNotificationBackground(mContext, mId);
+
+        if (mBackgroundUri != null) {
+            extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
+                    Uri.parse(BACKGROUND_URI_PREFIX + Integer.toString(mId)).toString());
+        }
+
+        // the following simulates group assignment into "Top", "Middle", "Bottom"
+        // by checking mId and similarly sort order
+        mGroupKey = (mId < 3) ? "Top" : (mId < 5) ? "Middle" : "Bottom";
+        mSort = (mId < 3) ? "1.0" : (mId < 5) ? "0.7" : "0.3";
+
+        // save bitmap into files for content provider to serve later
+        try {
+            bitmapFile.createNewFile();
+            FileOutputStream fOut = new FileOutputStream(bitmapFile);
+            mBitmap.compress(Bitmap.CompressFormat.PNG, 85, fOut);
+            fOut.flush();
+            fOut.close();
+        } catch (IOException ioe) {
+            Log.d(TAG, "Exception caught writing bitmap to file!", ioe);
+        }
+
+        Notification notification = new NotificationCompat.BigPictureStyle(
+                new NotificationCompat.Builder(mContext)
+                        .setAutoCancel(true)
+                        .setContentTitle(mTitle)
+                        .setContentText(mDescription)
+                        .setPriority(mPriority)
+                        .setLocalOnly(true)
+                        .setOngoing(true)
+                        /*
+                        groupKey (optional): Can be used to group together recommendations, so
+                        they are ranked by the launcher as a separate group. Can be useful if the
+                        application has different sources for recommendations, like "trending",
+                        "subscriptions", and "new music" categories for YouTube, where the user can
+                        be more interested in recommendations from one group than another.
+                         */
+                        .setGroup(mGroupKey)
+                        /*
+                        sortKey (optional): A float number between 0.0 and 1.0, used to indicate
+                        the relative importance (and sort order) of a single recommendation within
+                        its specified group. The recommendations will be ordered in decreasing
+                        order of importance within a given group.
+                         */
+                        .setSortKey(mSort)
+                        .setColor(mContext.getResources().getColor(R.color.fastlane_background))
+                        .setCategory(Notification.CATEGORY_RECOMMENDATION)
+                        .setLargeIcon(mBitmap)
+                        .setSmallIcon(mSmallIcon)
+                        .setContentIntent(mIntent)
+                        .setExtras(extras))
+                .build();
+
+        Log.d(TAG, "Building notification - " + this.toString());
+
+        return notification;
+    }
+
+    @Override
+    public String toString() {
+        return "RecommendationBuilder{" +
+                ", mId=" + mId +
+                ", mPriority=" + mPriority +
+                ", mSmallIcon=" + mSmallIcon +
+                ", mTitle='" + mTitle + '\'' +
+                ", mDescription='" + mDescription + '\'' +
+                ", mBitmap='" + mBitmap + '\'' +
+                ", mBackgroundUri='" + mBackgroundUri + '\'' +
+                ", mIntent=" + mIntent +
+                '}';
+    }
+
+    public static class RecommendationBackgroundContentProvider extends ContentProvider {
+
+        @Override
+        public boolean onCreate() {
+            return true;
+        }
+
+        @Override
+        public int delete(Uri uri, String selection, String[] selectionArgs) {
+            return 0;
+        }
+
+        @Override
+        public String getType(Uri uri) {
+            return null;
+        }
+
+        @Override
+        public Uri insert(Uri uri, ContentValues values) {
+            return null;
+        }
+
+        @Override
+        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+                            String sortOrder) {
+            return null;
+        }
+
+        @Override
+        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+            return 0;
+        }
+
+        @Override
+        /*
+         * content provider serving files that are saved locally when recommendations are built
+         */
+        public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+            int backgroundId = Integer.parseInt(uri.getLastPathSegment());
+            File bitmapFile = getNotificationBackground(getContext(), backgroundId);
+            return ParcelFileDescriptor.open(bitmapFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        }
+    }
+
+    private static File getNotificationBackground(Context context, int notificationId) {
+        return new File(context.getCacheDir(), "tmp" + Integer.toString(notificationId) + ".png");
+    }
+
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/recommendation/UpdateRecommendationsService.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/recommendation/UpdateRecommendationsService.java
new file mode 100644
index 0000000..f89ae98
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/recommendation/UpdateRecommendationsService.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.recommendation;
+
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import com.bumptech.glide.Glide;
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.data.VideoProvider;
+import com.example.android.tvleanback.model.Movie;
+import com.example.android.tvleanback.ui.MovieDetailsActivity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+/*
+ * This class builds up to MAX_RECOMMMENDATIONS of recommendations and defines what happens
+ * when they're clicked from Recommendations section on Home screen
+ */
+public class UpdateRecommendationsService extends IntentService {
+    private static final String TAG = "RecommendationsService";
+    private static final int MAX_RECOMMENDATIONS = 3;
+
+    private static final int CARD_WIDTH = 313;
+    private static final int CARD_HEIGHT = 176;
+
+    private NotificationManager mNotificationManager;
+
+    public UpdateRecommendationsService() {
+        super(TAG);
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        Log.d(TAG, "Updating recommendation cards");
+        HashMap<String, List<Movie>> recommendations = VideoProvider.getMovieList();
+        if (recommendations == null) {
+            return;
+        }
+
+        if (mNotificationManager == null) {
+            mNotificationManager = (NotificationManager) getApplicationContext()
+                    .getSystemService(Context.NOTIFICATION_SERVICE);
+        }
+
+        RecommendationBuilder builder = new RecommendationBuilder()
+                .setContext(getApplicationContext())
+                .setSmallIcon(R.drawable.videos_by_google_icon);
+
+        // flatten to list
+        List flattenedRecommendations = new ArrayList();
+        for (Map.Entry<String, List<Movie>> entry : recommendations.entrySet()) {
+            for (Movie movie : entry.getValue()) {
+                Log.d(TAG, "Recommendation - " + movie.getTitle());
+                flattenedRecommendations.add(movie);
+            }
+        }
+
+        Collections.shuffle(flattenedRecommendations);
+        Movie movie;
+        for (int i = 0; i < flattenedRecommendations.size() && i < MAX_RECOMMENDATIONS; i++) {
+            movie = (Movie) flattenedRecommendations.get(i);
+            final RecommendationBuilder notificationBuilder = builder
+                    .setBackground(movie.getCardImageUrl())
+                    .setId(i+1)
+                    .setPriority(MAX_RECOMMENDATIONS - i - 1)
+                    .setTitle(movie.getTitle())
+                    .setDescription(getString(R.string.popular_header))
+                    .setIntent(buildPendingIntent(movie, i + 1));
+
+            try {
+                Bitmap bitmap = Glide.with(getApplicationContext())
+                        .load(movie.getCardImageUrl())
+                        .asBitmap()
+                        .into(CARD_WIDTH, CARD_HEIGHT) // Only use for synchronous .get()
+                        .get();
+                notificationBuilder.setBitmap(bitmap);
+                Notification notification = notificationBuilder.build();
+                mNotificationManager.notify(i + 1, notification);
+            } catch (InterruptedException | ExecutionException e) {
+                Log.e(TAG, "Could not create recommendation: " + e);
+            }
+        }
+    }
+
+    private PendingIntent buildPendingIntent(Movie movie, int id) {
+        Intent detailsIntent = new Intent(this, MovieDetailsActivity.class);
+        detailsIntent.putExtra(MovieDetailsActivity.MOVIE, movie);
+        detailsIntent.putExtra(MovieDetailsActivity.NOTIFICATION_ID, id);
+
+        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+        stackBuilder.addParentStack(MovieDetailsActivity.class);
+        stackBuilder.addNextIntent(detailsIntent);
+        // Ensure a unique PendingIntents, otherwise all recommendations end up with the same
+        // PendingIntent
+        detailsIntent.setAction(movie.getId());
+
+        return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/BrowseErrorActivity.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/BrowseErrorActivity.java
new file mode 100644
index 0000000..1a992e9
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/BrowseErrorActivity.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.ui;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+
+import com.example.android.tvleanback.R;
+
+/*
+ * BrowseErrorActivity shows how to use ErrorFragment
+ */
+public class BrowseErrorActivity extends Activity {
+    private static int TIMER_DELAY = 3000;
+    private static int SPINNER_WIDTH = 100;
+    private static int SPINNER_HEIGHT = 100;
+
+    private ErrorFragment mErrorFragment;
+    private SpinnerFragment mSpinnerFragment;
+
+    /**
+     * Called when the activity is first created.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        testError();
+    }
+
+    private void testError() {
+        mErrorFragment = new ErrorFragment();
+        getFragmentManager().beginTransaction().add(R.id.main_frame, mErrorFragment).commit();
+
+        mSpinnerFragment = new SpinnerFragment();
+        getFragmentManager().beginTransaction().add(R.id.main_frame, mSpinnerFragment).commit();
+
+        final Handler handler = new Handler();
+        handler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                getFragmentManager().beginTransaction().remove(mSpinnerFragment).commit();
+                mErrorFragment.setErrorContent();
+            }
+        }, TIMER_DELAY);
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        startActivity(new Intent(this, SearchActivity.class));
+        return true;
+    }
+
+    static public class SpinnerFragment extends Fragment {
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                                 Bundle savedInstanceState) {
+            ProgressBar progressBar = new ProgressBar(container.getContext());
+            if (container instanceof FrameLayout) {
+                FrameLayout.LayoutParams layoutParams =
+                        new FrameLayout.LayoutParams(SPINNER_WIDTH, SPINNER_HEIGHT, Gravity.CENTER);
+                progressBar.setLayoutParams(layoutParams);
+            }
+            return progressBar;
+        }
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/ErrorFragment.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/ErrorFragment.java
new file mode 100644
index 0000000..36055e9
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/ErrorFragment.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.ui;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import com.example.android.tvleanback.R;
+
+/*
+ * This class demonstrates how to extend ErrorFragment
+ */
+public class ErrorFragment extends android.support.v17.leanback.app.ErrorFragment {
+    private static final String TAG = "ErrorFragment";
+    private static final boolean TRANSLUCENT = true;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.d(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+        setTitle(getResources().getString(R.string.app_name));
+    }
+
+    void setErrorContent() {
+        setImageDrawable(getResources().getDrawable(R.drawable.lb_ic_sad_cloud));
+        setMessage(getResources().getString(R.string.error_fragment_message));
+        setDefaultBackground(TRANSLUCENT);
+
+        setButtonText(getResources().getString(R.string.dismiss_error));
+        setButtonClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                getFragmentManager().beginTransaction().remove(ErrorFragment.this).commit();
+            }
+        });
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/MainActivity.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MainActivity.java
similarity index 68%
rename from prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/MainActivity.java
rename to prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MainActivity.java
index a63a3c9..facbdd3 100644
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/MainActivity.java
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MainActivity.java
@@ -12,22 +12,31 @@
  * the License.
  */
 
-package com.example.android.leanback;
+package com.example.android.tvleanback.ui;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 
+import com.example.android.tvleanback.R;
+
 /*
- * A wrapper class for main view of the app
+ * MainActivity class that loads MainFragment
  */
 public class MainActivity extends Activity {
-    /** Called when the activity is first created. */
+    /**
+     * Called when the activity is first created.
+     */
 
     @Override
-    public void onCreate(Bundle savedInstanceState)
-    {
+    public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
     }
+
+    @Override
+    public boolean onSearchRequested() {
+        startActivity(new Intent(this, SearchActivity.class));
+        return true;
+    }
 }
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MainFragment.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MainFragment.java
new file mode 100644
index 0000000..9114d7e
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MainFragment.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.ui;
+
+import android.app.LoaderManager;
+import android.content.Intent;
+import android.content.Loader;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.app.BackgroundManager;
+import android.support.v17.leanback.app.BrowseFragment;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.data.VideoItemLoader;
+import com.example.android.tvleanback.data.VideoProvider;
+import com.example.android.tvleanback.model.Movie;
+import com.example.android.tvleanback.presenter.CardPresenter;
+import com.example.android.tvleanback.presenter.GridItemPresenter;
+import com.example.android.tvleanback.recommendation.UpdateRecommendationsService;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/*
+ * Main class to show BrowseFragment with header and rows of videos
+ */
+public class MainFragment extends BrowseFragment implements
+        LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {
+    private static final String TAG = "MainFragment";
+
+    private static int BACKGROUND_UPDATE_DELAY = 300;
+    private static String mVideosUrl;
+    private final Handler mHandler = new Handler();
+    private ArrayObjectAdapter mRowsAdapter;
+    private Drawable mDefaultBackground;
+    private DisplayMetrics mMetrics;
+    private Timer mBackgroundTimer;
+    private URI mBackgroundURI;
+    private BackgroundManager mBackgroundManager;
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        Log.d(TAG, "onCreate");
+        super.onActivityCreated(savedInstanceState);
+
+        loadVideoData();
+
+        prepareBackgroundManager();
+        setupUIElements();
+        setupEventListeners();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (null != mBackgroundTimer) {
+            Log.d(TAG, "onDestroy: " + mBackgroundTimer.toString());
+            mBackgroundTimer.cancel();
+        }
+    }
+
+    private void prepareBackgroundManager() {
+        mBackgroundManager = BackgroundManager.getInstance(getActivity());
+        mBackgroundManager.attach(getActivity().getWindow());
+        mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
+        mMetrics = new DisplayMetrics();
+        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
+    }
+
+    private void setupUIElements() {
+        setBadgeDrawable(getActivity().getResources().getDrawable(R.drawable.videos_by_google_banner));
+        setTitle(getString(R.string.browse_title)); // Badge, when set, takes precedent over title
+        setHeadersState(HEADERS_ENABLED);
+        setHeadersTransitionOnBackEnabled(true);
+        // set fastLane (or headers) background color
+        setBrandColor(getResources().getColor(R.color.fastlane_background));
+        // set search icon color
+        setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
+    }
+
+    private void loadVideoData() {
+        VideoProvider.setContext(getActivity());
+        mVideosUrl = getActivity().getResources().getString(R.string.catalog_url);
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    private void setupEventListeners() {
+        setOnSearchClickedListener(new View.OnClickListener() {
+
+            @Override
+            public void onClick(View view) {
+                Intent intent = new Intent(getActivity(), SearchActivity.class);
+                startActivity(intent);
+            }
+        });
+
+        setOnItemViewClickedListener(new ItemViewClickedListener());
+        setOnItemViewSelectedListener(new ItemViewSelectedListener());
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader(int,
+     * android.os.Bundle)
+     */
+    @Override
+    public Loader<HashMap<String, List<Movie>>> onCreateLoader(int arg0, Bundle arg1) {
+        Log.d(TAG, "VideoItemLoader created ");
+        return new VideoItemLoader(getActivity(), mVideosUrl);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished(android
+     * .support.v4.content.Loader, java.lang.Object)
+     */
+    @Override
+    public void onLoadFinished(Loader<HashMap<String, List<Movie>>> arg0,
+                               HashMap<String, List<Movie>> data) {
+
+        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+        CardPresenter cardPresenter = new CardPresenter();
+
+        int i = 0;
+
+        for (Map.Entry<String, List<Movie>> entry : data.entrySet()) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
+            List<Movie> list = entry.getValue();
+
+            for (int j = 0; j < list.size(); j++) {
+                listRowAdapter.add(list.get(j));
+            }
+            HeaderItem header = new HeaderItem(i, entry.getKey());
+            i++;
+            mRowsAdapter.add(new ListRow(header, listRowAdapter));
+        }
+
+        HeaderItem gridHeader = new HeaderItem(i, getString(R.string.more_samples));
+
+        GridItemPresenter gridPresenter = new GridItemPresenter(this);
+        ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(gridPresenter);
+        gridRowAdapter.add(getString(R.string.grid_view));
+        gridRowAdapter.add(getString(R.string.error_fragment));
+        gridRowAdapter.add(getString(R.string.personal_settings));
+        mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));
+
+        setAdapter(mRowsAdapter);
+
+        updateRecommendations();
+    }
+
+    @Override
+    public void onLoaderReset(Loader<HashMap<String, List<Movie>>> arg0) {
+        mRowsAdapter.clear();
+    }
+
+    protected void setDefaultBackground(Drawable background) {
+        mDefaultBackground = background;
+    }
+
+    protected void setDefaultBackground(int resourceId) {
+        mDefaultBackground = getResources().getDrawable(resourceId);
+    }
+
+    protected void updateBackground(String uri) {
+        int width = mMetrics.widthPixels;
+        int height = mMetrics.heightPixels;
+        Glide.with(getActivity())
+                .load(uri)
+                .centerCrop()
+                .error(mDefaultBackground)
+                .into(new SimpleTarget<GlideDrawable>(width, height) {
+                    @Override
+                    public void onResourceReady(GlideDrawable resource,
+                                                GlideAnimation<? super GlideDrawable>
+                                                        glideAnimation) {
+                        mBackgroundManager.setDrawable(resource);
+                    }
+                });
+        mBackgroundTimer.cancel();
+    }
+
+    protected void updateBackground(Drawable drawable) {
+        BackgroundManager.getInstance(getActivity()).setDrawable(drawable);
+    }
+
+    protected void clearBackground() {
+        BackgroundManager.getInstance(getActivity()).setDrawable(mDefaultBackground);
+    }
+
+    private void startBackgroundTimer() {
+        if (null != mBackgroundTimer) {
+            mBackgroundTimer.cancel();
+        }
+        mBackgroundTimer = new Timer();
+        mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY);
+    }
+
+    private void updateRecommendations() {
+        Intent recommendationIntent = new Intent(getActivity(), UpdateRecommendationsService.class);
+        getActivity().startService(recommendationIntent);
+    }
+
+    private class UpdateBackgroundTask extends TimerTask {
+
+        @Override
+        public void run() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mBackgroundURI != null) {
+                        updateBackground(mBackgroundURI.toString());
+                    }
+                }
+            });
+        }
+    }
+
+    private final class ItemViewClickedListener implements OnItemViewClickedListener {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+
+            if (item instanceof Movie) {
+                Movie movie = (Movie) item;
+                Log.d(TAG, "Movie: " + movie.toString());
+                Intent intent = new Intent(getActivity(), MovieDetailsActivity.class);
+                intent.putExtra(MovieDetailsActivity.MOVIE, movie);
+
+                Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
+                        getActivity(),
+                        ((ImageCardView) itemViewHolder.view).getMainImageView(),
+                        MovieDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
+                getActivity().startActivity(intent, bundle);
+            } else if (item instanceof String) {
+                if (((String) item).indexOf(getString(R.string.grid_view)) >= 0) {
+                    Intent intent = new Intent(getActivity(), VerticalGridActivity.class);
+                    startActivity(intent);
+                } else if (((String) item).indexOf(getString(R.string.error_fragment)) >= 0) {
+                    Intent intent = new Intent(getActivity(), BrowseErrorActivity.class);
+                    startActivity(intent);
+                } else {
+                    Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT)
+                            .show();
+                }
+            }
+        }
+    }
+
+
+    private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                                   RowPresenter.ViewHolder rowViewHolder, Row row) {
+            if (item instanceof Movie) {
+                mBackgroundURI = ((Movie) item).getBackgroundImageURI();
+                startBackgroundTimer();
+            }
+
+        }
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MovieDetailsActivity.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MovieDetailsActivity.java
new file mode 100644
index 0000000..6169a49
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MovieDetailsActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.example.android.tvleanback.R;
+
+/*
+ * Details activity class that loads LeanbackDetailsFragment class
+ */
+public class MovieDetailsActivity extends Activity {
+    public static final String SHARED_ELEMENT_NAME = "hero";
+    public static final String MOVIE = "Movie";
+    public static final String NOTIFICATION_ID = "NotificationId";
+
+    /**
+     * Called when the activity is first created.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.details);
+
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        startActivity(new Intent(this, SearchActivity.class));
+        return true;
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MovieDetailsFragment.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MovieDetailsFragment.java
new file mode 100644
index 0000000..6298789
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/MovieDetailsFragment.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.ui;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v17.leanback.app.BackgroundManager;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.DetailsOverviewRow;
+import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.widget.Toast;
+
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.Utils;
+import com.example.android.tvleanback.data.VideoProvider;
+import com.example.android.tvleanback.model.Movie;
+import com.example.android.tvleanback.presenter.CardPresenter;
+import com.example.android.tvleanback.presenter.DetailsDescriptionPresenter;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/*
+ * LeanbackDetailsFragment extends DetailsFragment, a Wrapper fragment for leanback details screens.
+ * It shows a detailed view of video and its meta plus related videos.
+ */
+public class MovieDetailsFragment extends android.support.v17.leanback.app.DetailsFragment {
+    private static final String TAG = "DetailsFragment";
+
+    private static final int ACTION_WATCH_TRAILER = 1;
+    private static final int ACTION_RENT = 2;
+    private static final int ACTION_BUY = 3;
+
+    private static final int DETAIL_THUMB_WIDTH = 274;
+    private static final int DETAIL_THUMB_HEIGHT = 274;
+
+    private static final int NO_NOTIFICATION = -1;
+
+    private Movie mSelectedMovie;
+
+    private ArrayObjectAdapter mAdapter;
+    private ClassPresenterSelector mPresenterSelector;
+
+    private BackgroundManager mBackgroundManager;
+    private Drawable mDefaultBackground;
+    private DisplayMetrics mMetrics;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.d(TAG, "onCreate DetailsFragment");
+        super.onCreate(savedInstanceState);
+
+        prepareBackgroundManager();
+
+        mSelectedMovie = (Movie) getActivity().getIntent()
+                .getParcelableExtra(MovieDetailsActivity.MOVIE);
+        if (mSelectedMovie != null || checkGlobalSearchIntent()) {
+            removeNotification(getActivity().getIntent()
+                    .getIntExtra(MovieDetailsActivity.NOTIFICATION_ID, NO_NOTIFICATION));
+            setupAdapter();
+            setupDetailsOverviewRow();
+            setupDetailsOverviewRowPresenter();
+            setupMovieListRow();
+            setupMovieListRowPresenter();
+            updateBackground(mSelectedMovie.getBackgroundImageUrl());
+            setOnItemViewClickedListener(new ItemViewClickedListener());
+        } else {
+            Intent intent = new Intent(getActivity(), MainActivity.class);
+            startActivity(intent);
+        }
+    }
+
+    private void removeNotification(int notificationId) {
+        if (notificationId != NO_NOTIFICATION) {
+            NotificationManager notificationManager = (NotificationManager) getActivity()
+                    .getSystemService(Context.NOTIFICATION_SERVICE);
+            notificationManager.cancel(notificationId);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+    }
+
+    /*
+     * Check if there is a global search intent
+     */
+    private boolean checkGlobalSearchIntent() {
+        Intent intent = getActivity().getIntent();
+        String intentAction = intent.getAction();
+        String globalSearch = getString(R.string.global_search);
+        if (globalSearch.equalsIgnoreCase(intentAction)) {
+            Uri intentData = intent.getData();
+            Log.d(TAG, "action: " + intentAction + " intentData:" + intentData);
+            int selectedIndex = Integer.parseInt(intentData.getLastPathSegment());
+            HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
+            int movieTally = 0;
+            if (movies == null) {
+                return false;
+            }
+            for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
+                List<Movie> list = entry.getValue();
+                for (Movie movie : list) {
+                    movieTally++;
+                    if (selectedIndex == movieTally) {
+                        mSelectedMovie = movie;
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private void prepareBackgroundManager() {
+        mBackgroundManager = BackgroundManager.getInstance(getActivity());
+        mBackgroundManager.attach(getActivity().getWindow());
+        mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
+        mMetrics = new DisplayMetrics();
+        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
+    }
+
+    protected void updateBackground(String uri) {
+        Glide.with(getActivity())
+                .load(uri)
+                .centerCrop()
+                .error(mDefaultBackground)
+                .into(new SimpleTarget<GlideDrawable>(mMetrics.widthPixels, mMetrics.heightPixels) {
+                    @Override
+                    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
+                        mBackgroundManager.setDrawable(resource);
+                    }
+                });
+    }
+
+    private void setupAdapter() {
+        mPresenterSelector = new ClassPresenterSelector();
+        mAdapter = new ArrayObjectAdapter(mPresenterSelector);
+        setAdapter(mAdapter);
+    }
+
+    private void setupDetailsOverviewRow() {
+        Log.d(TAG, "doInBackground: " + mSelectedMovie.toString());
+        final DetailsOverviewRow row = new DetailsOverviewRow(mSelectedMovie);
+        row.setImageDrawable(getResources().getDrawable(R.drawable.default_background));
+        int width = Utils.convertDpToPixel(getActivity()
+                .getApplicationContext(), DETAIL_THUMB_WIDTH);
+        int height = Utils.convertDpToPixel(getActivity()
+                .getApplicationContext(), DETAIL_THUMB_HEIGHT);
+        Glide.with(getActivity())
+                .load(mSelectedMovie.getCardImageUrl())
+                .centerCrop()
+                .error(R.drawable.default_background)
+                .into(new SimpleTarget<GlideDrawable>(width, height) {
+                    @Override
+                    public void onResourceReady(GlideDrawable resource,
+                                                GlideAnimation<? super GlideDrawable>
+                                                        glideAnimation) {
+                        Log.d(TAG, "details overview card image url ready: " + resource);
+                        row.setImageDrawable(resource);
+                        mAdapter.notifyArrayItemRangeChanged(0, mAdapter.size());
+                    }
+                });
+
+        row.addAction(new Action(ACTION_WATCH_TRAILER, getResources().getString(
+                R.string.watch_trailer_1), getResources().getString(R.string.watch_trailer_2)));
+        row.addAction(new Action(ACTION_RENT, getResources().getString(R.string.rent_1),
+                getResources().getString(R.string.rent_2)));
+        row.addAction(new Action(ACTION_BUY, getResources().getString(R.string.buy_1),
+                getResources().getString(R.string.buy_2)));
+
+        mAdapter.add(row);
+    }
+
+    private void setupDetailsOverviewRowPresenter() {
+        // Set detail background and style.
+        DetailsOverviewRowPresenter detailsPresenter =
+                new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
+        detailsPresenter.setBackgroundColor(getResources().getColor(R.color.selected_background));
+        detailsPresenter.setStyleLarge(true);
+
+        // Hook up transition element.
+        detailsPresenter.setSharedElementEnterTransition(getActivity(),
+                MovieDetailsActivity.SHARED_ELEMENT_NAME);
+
+        detailsPresenter.setOnActionClickedListener(new OnActionClickedListener() {
+            @Override
+            public void onActionClicked(Action action) {
+                if (action.getId() == ACTION_WATCH_TRAILER) {
+                    Intent intent = new Intent(getActivity(), PlaybackOverlayActivity.class);
+                    intent.putExtra(MovieDetailsActivity.MOVIE, mSelectedMovie);
+                    startActivity(intent);
+                } else {
+                    Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show();
+                }
+            }
+        });
+        mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, detailsPresenter);
+    }
+
+    private void setupMovieListRow() {
+        String subcategories[] = {getString(R.string.related_movies)};
+        HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
+
+        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
+        for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
+            if (mSelectedMovie.getCategory().indexOf(entry.getKey()) >= 0) {
+                List<Movie> list = entry.getValue();
+                for (int j = 0; j < list.size(); j++) {
+                    listRowAdapter.add(list.get(j));
+                }
+            }
+        }
+        HeaderItem header = new HeaderItem(0, subcategories[0]);
+        mAdapter.add(new ListRow(header, listRowAdapter));
+    }
+
+    private void setupMovieListRowPresenter() {
+        mPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
+    }
+
+    private final class ItemViewClickedListener implements OnItemViewClickedListener {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+
+            if (item instanceof Movie) {
+                Movie movie = (Movie) item;
+                Log.d(TAG, "Item: " + item.toString());
+                Intent intent = new Intent(getActivity(), MovieDetailsActivity.class);
+                intent.putExtra(MovieDetailsActivity.MOVIE, movie);
+
+                Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
+                        getActivity(),
+                        ((ImageCardView) itemViewHolder.view).getMainImageView(),
+                        MovieDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
+                getActivity().startActivity(intent, bundle);
+            }
+        }
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/PlaybackOverlayActivity.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/PlaybackOverlayActivity.java
new file mode 100644
index 0000000..416b594
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/PlaybackOverlayActivity.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.media.MediaMetadata;
+import android.media.MediaPlayer;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.widget.FrameLayout;
+import android.widget.VideoView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.model.Movie;
+
+/**
+ * PlaybackOverlayActivity for video playback that loads PlaybackOverlayFragment
+ */
+public class PlaybackOverlayActivity extends Activity implements
+        PlaybackOverlayFragment.OnPlayPauseClickedListener {
+    private static final String TAG = "PlaybackOverlayActivity";
+
+    private static final double MEDIA_HEIGHT = 0.95;
+    private static final double MEDIA_WIDTH = 0.95;
+    private static final double MEDIA_TOP_MARGIN = 0.025;
+    private static final double MEDIA_RIGHT_MARGIN = 0.025;
+    private static final double MEDIA_BOTTOM_MARGIN = 0.025;
+    private static final double MEDIA_LEFT_MARGIN = 0.025;
+    private VideoView mVideoView;
+    private LeanbackPlaybackState mPlaybackState = LeanbackPlaybackState.IDLE;
+    private MediaSession mSession;
+
+    /**
+     * Called when the activity is first created.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.playback_controls);
+        loadViews();
+        //Example for handling resizing view for overscan
+        //overScan();
+
+        mSession = new MediaSession (this, "LeanbackSampleApp");
+        mSession.setCallback(new MediaSessionCallback());
+        mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
+                MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+
+        mSession.setActive(true);
+
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mVideoView.suspend();
+    }
+
+    /**
+     * Implementation of OnPlayPauseClickedListener
+     */
+    public void onFragmentPlayPause(Movie movie, int position, Boolean playPause) {
+        mVideoView.setVideoPath(movie.getVideoUrl());
+
+        if (position == 0 || mPlaybackState == LeanbackPlaybackState.IDLE) {
+            setupCallbacks();
+            mPlaybackState = LeanbackPlaybackState.IDLE;
+        }
+
+        if (playPause && mPlaybackState != LeanbackPlaybackState.PLAYING) {
+            mPlaybackState = LeanbackPlaybackState.PLAYING;
+            if (position > 0) {
+                mVideoView.seekTo(position);
+                mVideoView.start();
+            }
+        } else {
+            mPlaybackState = LeanbackPlaybackState.PAUSED;
+            mVideoView.pause();
+        }
+        updatePlaybackState(position);
+        updateMetadata(movie);
+    }
+
+    /**
+     * Implementation of OnPlayPauseClickedListener
+     */
+    public void onFragmentFfwRwd(Movie movie, int position) {
+        mVideoView.setVideoPath(movie.getVideoUrl());
+
+        Log.d(TAG, "seek current time: " + position);
+        if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
+            if (position > 0) {
+                mVideoView.seekTo(position);
+                mVideoView.start();
+            }
+        }
+    }
+
+    private void updatePlaybackState(int position) {
+        PlaybackState.Builder stateBuilder = new PlaybackState.Builder()
+                .setActions(getAvailableActions());
+        int state = PlaybackState.STATE_PLAYING;
+        if (mPlaybackState == LeanbackPlaybackState.PAUSED) {
+            state = PlaybackState.STATE_PAUSED;
+        }
+        stateBuilder.setState(state, position, 1.0f);
+        mSession.setPlaybackState(stateBuilder.build());
+    }
+
+    private long getAvailableActions() {
+        long actions = PlaybackState.ACTION_PLAY |
+                PlaybackState.ACTION_PLAY_FROM_MEDIA_ID |
+                PlaybackState.ACTION_PLAY_FROM_SEARCH;
+
+        if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
+            actions |= PlaybackState.ACTION_PAUSE;
+        }
+
+        return actions;
+    }
+
+    private void updateMetadata(final Movie movie) {
+        final MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
+
+        String title = movie.getTitle().replace("_", " -");
+
+        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title);
+        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
+                movie.getDescription());
+        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
+                movie.getCardImageUrl());
+
+        // And at minimum the title and artist for legacy support
+        metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, title);
+        metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, movie.getStudio());
+
+        Glide.with(this)
+            .load(Uri.parse(movie.getCardImageUrl()))
+            .asBitmap()
+            .into(new SimpleTarget<Bitmap>(500, 500) {
+                @Override
+                public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
+                    metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, bitmap);
+                    mSession.setMetadata(metadataBuilder.build());
+                }
+            });
+    }
+
+    private void loadViews() {
+        mVideoView = (VideoView) findViewById(R.id.videoView);
+    }
+
+    /**
+     * Example for handling resizing content for overscan.  Typically you won't need to resize which
+     * is why overScan(); is commented out.
+     */
+    private void overScan() {
+        DisplayMetrics metrics = new DisplayMetrics();
+        getWindowManager().getDefaultDisplay().getMetrics(metrics);
+        int w = (int) (metrics.widthPixels * MEDIA_WIDTH);
+        int h = (int) (metrics.heightPixels * MEDIA_HEIGHT);
+        int marginLeft = (int) (metrics.widthPixels * MEDIA_LEFT_MARGIN);
+        int marginTop = (int) (metrics.heightPixels * MEDIA_TOP_MARGIN);
+        int marginRight = (int) (metrics.widthPixels * MEDIA_RIGHT_MARGIN);
+        int marginBottom = (int) (metrics.heightPixels * MEDIA_BOTTOM_MARGIN);
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h);
+        lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
+        mVideoView.setLayoutParams(lp);
+    }
+
+    private void setupCallbacks() {
+
+        mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                String msg = "";
+                if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) {
+                    msg = getString(R.string.video_error_media_load_timeout);
+                } else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
+                    msg = getString(R.string.video_error_server_inaccessible);
+                } else {
+                    msg = getString(R.string.video_error_unknown_error);
+                }
+                mVideoView.stopPlayback();
+                mPlaybackState = LeanbackPlaybackState.IDLE;
+                return false;
+            }
+        });
+
+
+        mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                if (mPlaybackState == LeanbackPlaybackState.PLAYING) {
+                    mVideoView.start();
+                }
+            }
+        });
+
+
+        mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+            @Override
+            public void onCompletion(MediaPlayer mp) {
+                mPlaybackState = LeanbackPlaybackState.IDLE;
+            }
+        });
+
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        if (mVideoView.isPlaying()) {
+            if (!requestVisibleBehind(true)) {
+                // Try to play behind launcher, but if it fails, stop playback.
+                stopPlayback();
+            }
+        } else {
+            requestVisibleBehind(false);
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mSession.release();
+    }
+
+    @Override
+    public void onVisibleBehindCanceled() {
+        super.onVisibleBehindCanceled();
+        stopPlayback();
+    }
+
+    private void stopPlayback() {
+        if (mVideoView != null) {
+            mVideoView.stopPlayback();
+        }
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        startActivity(new Intent(this, SearchActivity.class));
+        return true;
+    }
+
+    /*
+     * List of various states that we can be in
+     */
+    public static enum LeanbackPlaybackState {
+        PLAYING, PAUSED, BUFFERING, IDLE;
+    }
+
+    private class MediaSessionCallback extends MediaSession.Callback {
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/PlaybackOverlayFragment.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/PlaybackOverlayFragment.java
new file mode 100644
index 0000000..35dc5c5
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/PlaybackOverlayFragment.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaMetadataRetriever;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.PlayPauseAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.RepeatAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.ShuffleAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.SkipNextAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.SkipPreviousAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsDownAction;
+import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsUpAction;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.util.Log;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.data.VideoProvider;
+import com.example.android.tvleanback.model.Movie;
+import com.example.android.tvleanback.presenter.CardPresenter;
+
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/*
+ * Class for video playback with media control
+ */
+public class PlaybackOverlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment {
+    private static final String TAG = "PlaybackOverlayFragment";
+    private static final boolean SHOW_DETAIL = true;
+    private static final boolean HIDE_MORE_ACTIONS = false;
+    private static final int PRIMARY_CONTROLS = 5;
+    private static final boolean SHOW_IMAGE = PRIMARY_CONTROLS <= 5;
+    private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_LIGHT;
+    private static final int CARD_WIDTH = 150;
+    private static final int CARD_HEIGHT = 240;
+    private static final int DEFAULT_UPDATE_PERIOD = 1000;
+    private static final int UPDATE_PERIOD = 16;
+    private static final int SIMULATED_BUFFERED_TIME = 10000;
+    private static final int CLICK_TRACKING_DELAY = 1000;
+    private static final int INITIAL_SPEED = 10000;
+
+    private static Context sContext;
+    private final Handler mClickTrackingHandler = new Handler();
+    OnPlayPauseClickedListener mCallback;
+    private ArrayObjectAdapter mRowsAdapter;
+    private ArrayObjectAdapter mPrimaryActionsAdapter;
+    private ArrayObjectAdapter mSecondaryActionsAdapter;
+    private PlayPauseAction mPlayPauseAction;
+    private RepeatAction mRepeatAction;
+    private ThumbsUpAction mThumbsUpAction;
+    private ThumbsDownAction mThumbsDownAction;
+    private ShuffleAction mShuffleAction;
+    private FastForwardAction mFastForwardAction;
+    private RewindAction mRewindAction;
+    private SkipNextAction mSkipNextAction;
+    private SkipPreviousAction mSkipPreviousAction;
+    private PlaybackControlsRow mPlaybackControlsRow;
+    private ArrayList<Movie> mItems = new ArrayList<Movie>();
+    private int mCurrentItem;
+    private long mDuration;
+    private Handler mHandler;
+    private Runnable mRunnable;
+    private Movie mSelectedMovie;
+    private int mFfwRwdSpeed = INITIAL_SPEED;
+    private Timer mClickTrackingTimer;
+    private int mClickCount;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+        sContext = getActivity();
+
+        mItems = new ArrayList<Movie>();
+        mSelectedMovie = (Movie) getActivity()
+                .getIntent().getParcelableExtra(MovieDetailsActivity.MOVIE);
+
+        HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
+
+        if(movies != null) {
+            for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
+                if (mSelectedMovie.getCategory().contains(entry.getKey())) {
+                    List<Movie> list = entry.getValue();
+                    if(list != null && !list.isEmpty()) {
+                        for (int j = 0; j < list.size(); j++) {
+                            mItems.add(list.get(j));
+                            if (mSelectedMovie.getTitle().contentEquals(list.get(j).getTitle())) {
+                                mCurrentItem = j;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        mHandler = new Handler();
+
+        setBackgroundType(BACKGROUND_TYPE);
+        setFadingEnabled(false);
+
+        setupRows();
+
+        setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+            @Override
+            public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                                       RowPresenter.ViewHolder rowViewHolder, Row row) {
+                Log.i(TAG, "onItemSelected: " + item + " row " + row);
+            }
+        });
+        setOnItemViewClickedListener(new ItemViewClickedListener());
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        // This makes sure that the container activity has implemented
+        // the callback interface. If not, it throws an exception
+        try {
+            mCallback = (OnPlayPauseClickedListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement OnPlayPauseClickedListener");
+        }
+    }
+
+    @Override
+    public void onResume(){
+        super.onResume();
+    }
+
+    private void setupRows() {
+
+        ClassPresenterSelector ps = new ClassPresenterSelector();
+
+        PlaybackControlsRowPresenter playbackControlsRowPresenter;
+        if (SHOW_DETAIL) {
+            playbackControlsRowPresenter = new PlaybackControlsRowPresenter(
+                    new DescriptionPresenter());
+        } else {
+            playbackControlsRowPresenter = new PlaybackControlsRowPresenter();
+        }
+        playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() {
+            public void onActionClicked(Action action) {
+                if (action.getId() == mPlayPauseAction.getId()) {
+                    if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
+                        startProgressAutomation();
+                        setFadingEnabled(true);
+                        mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),
+                                mPlaybackControlsRow.getCurrentTime(), true);
+                    } else {
+                        stopProgressAutomation();
+                        setFadingEnabled(false);
+                        mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),
+                                mPlaybackControlsRow.getCurrentTime(), false);
+                    }
+                } else if (action.getId() == mSkipNextAction.getId()) {
+                    next();
+                } else if (action.getId() == mSkipPreviousAction.getId()) {
+                    prev();
+                } else if (action.getId() == mFastForwardAction.getId()) {
+                    fastForward();
+                } else if (action.getId() == mRewindAction.getId()) {
+                    fastRewind();
+                }
+                if (action instanceof PlaybackControlsRow.MultiAction) {
+                    ((PlaybackControlsRow.MultiAction) action).nextIndex();
+                    notifyChanged(action);
+                }
+            }
+        });
+        playbackControlsRowPresenter.setSecondaryActionsHidden(HIDE_MORE_ACTIONS);
+
+        ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter);
+        ps.addClassPresenter(ListRow.class, new ListRowPresenter());
+        mRowsAdapter = new ArrayObjectAdapter(ps);
+
+        addPlaybackControlsRow();
+        addOtherRows();
+
+        setAdapter(mRowsAdapter);
+    }
+
+    private int getDuration() {
+        Movie movie = mItems.get(mCurrentItem);
+        MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            mmr.setDataSource(movie.getVideoUrl(), new HashMap<String, String>());
+        } else {
+            mmr.setDataSource(movie.getVideoUrl());
+        }
+        String time = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+        mDuration = Long.parseLong(time);
+        return (int) mDuration;
+    }
+
+    private void addPlaybackControlsRow() {
+        if (SHOW_DETAIL) {
+            mPlaybackControlsRow = new PlaybackControlsRow(mSelectedMovie);
+        } else {
+            mPlaybackControlsRow = new PlaybackControlsRow();
+        }
+        mRowsAdapter.add(mPlaybackControlsRow);
+
+        updatePlaybackRow(mCurrentItem);
+
+        ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector();
+        mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
+        mSecondaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
+        mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
+        mPlaybackControlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter);
+
+        mPlayPauseAction = new PlayPauseAction(sContext);
+        mRepeatAction = new RepeatAction(sContext);
+        mThumbsUpAction = new ThumbsUpAction(sContext);
+        mThumbsDownAction = new ThumbsDownAction(sContext);
+        mShuffleAction = new ShuffleAction(sContext);
+        mSkipNextAction = new PlaybackControlsRow.SkipNextAction(sContext);
+        mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(sContext);
+        mFastForwardAction = new PlaybackControlsRow.FastForwardAction(sContext);
+        mRewindAction = new PlaybackControlsRow.RewindAction(sContext);
+
+        if (PRIMARY_CONTROLS > 5) {
+            mPrimaryActionsAdapter.add(mThumbsUpAction);
+        } else {
+            mSecondaryActionsAdapter.add(mThumbsUpAction);
+        }
+        mPrimaryActionsAdapter.add(mSkipPreviousAction);
+        if (PRIMARY_CONTROLS > 3) {
+            mPrimaryActionsAdapter.add(new PlaybackControlsRow.RewindAction(sContext));
+        }
+        mPrimaryActionsAdapter.add(mPlayPauseAction);
+        if (PRIMARY_CONTROLS > 3) {
+            mPrimaryActionsAdapter.add(new PlaybackControlsRow.FastForwardAction(sContext));
+        }
+        mPrimaryActionsAdapter.add(mSkipNextAction);
+
+        mSecondaryActionsAdapter.add(mRepeatAction);
+        mSecondaryActionsAdapter.add(mShuffleAction);
+        if (PRIMARY_CONTROLS > 5) {
+            mPrimaryActionsAdapter.add(mThumbsDownAction);
+        } else {
+            mSecondaryActionsAdapter.add(mThumbsDownAction);
+        }
+        mSecondaryActionsAdapter.add(new PlaybackControlsRow.HighQualityAction(sContext));
+        mSecondaryActionsAdapter.add(new PlaybackControlsRow.ClosedCaptioningAction(sContext));
+    }
+
+    private void notifyChanged(Action action) {
+        ArrayObjectAdapter adapter = mPrimaryActionsAdapter;
+        if (adapter.indexOf(action) >= 0) {
+            adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
+            return;
+        }
+        adapter = mSecondaryActionsAdapter;
+        if (adapter.indexOf(action) >= 0) {
+            adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
+            return;
+        }
+    }
+
+    private void updatePlaybackRow(int index) {
+        if (mPlaybackControlsRow.getItem() != null) {
+            Movie item = (Movie) mPlaybackControlsRow.getItem();
+            item.setTitle(mItems.get(mCurrentItem).getTitle());
+            item.setStudio(mItems.get(mCurrentItem).getStudio());
+        }
+        if (SHOW_IMAGE) {
+            updateVideoImage(mItems.get(mCurrentItem).getCardImageUrl());
+        }
+        mRowsAdapter.notifyArrayItemRangeChanged(0, 1);
+        mPlaybackControlsRow.setTotalTime(getDuration());
+        mPlaybackControlsRow.setCurrentTime(0);
+        mPlaybackControlsRow.setBufferedProgress(0);
+    }
+
+    private void addOtherRows() {
+        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
+        for (Movie movie : mItems) {
+            listRowAdapter.add(movie);
+        }
+        HeaderItem header = new HeaderItem(0, getString(R.string.related_movies));
+        mRowsAdapter.add(new ListRow(header, listRowAdapter));
+
+    }
+
+    private int getUpdatePeriod() {
+        if (getView() == null || mPlaybackControlsRow.getTotalTime() <= 0) {
+            return DEFAULT_UPDATE_PERIOD;
+        }
+        return Math.max(UPDATE_PERIOD, mPlaybackControlsRow.getTotalTime() / getView().getWidth());
+    }
+
+    private void startProgressAutomation() {
+        mRunnable = new Runnable() {
+            @Override
+            public void run() {
+                int updatePeriod = getUpdatePeriod();
+                int currentTime = mPlaybackControlsRow.getCurrentTime() + updatePeriod;
+                int totalTime = mPlaybackControlsRow.getTotalTime();
+                mPlaybackControlsRow.setCurrentTime(currentTime);
+                mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
+
+                if (totalTime > 0 && totalTime <= currentTime) {
+                    next();
+                }
+                mHandler.postDelayed(this, updatePeriod);
+            }
+        };
+        mHandler.postDelayed(mRunnable, getUpdatePeriod());
+    }
+
+    private void next() {
+        if (++mCurrentItem >= mItems.size()) {
+            mCurrentItem = 0;
+        }
+        if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
+            mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, false);
+        } else {
+            mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, true);
+        }
+        mFfwRwdSpeed = INITIAL_SPEED;
+        updatePlaybackRow(mCurrentItem);
+    }
+
+    private void prev() {
+        if (--mCurrentItem < 0) {
+            mCurrentItem = mItems.size() - 1;
+        }
+        if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
+            mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, false);
+        } else {
+            mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, true);
+        }
+        mFfwRwdSpeed = INITIAL_SPEED;
+        updatePlaybackRow(mCurrentItem);
+    }
+
+    private void fastForward() {
+        Log.d(TAG, "current time: " + mPlaybackControlsRow.getCurrentTime());
+        startClickTrackingTimer();
+        int currentTime = mPlaybackControlsRow.getCurrentTime() + mFfwRwdSpeed;
+        if (currentTime > (int) mDuration) {
+            currentTime = (int) mDuration;
+        }
+        fastFR(currentTime);
+    }
+
+    private void fastRewind() {
+        startClickTrackingTimer();
+        int currentTime = mPlaybackControlsRow.getCurrentTime() - mFfwRwdSpeed;
+        if (currentTime < 0 || currentTime > (int) mDuration) {
+            currentTime = 0;
+        }
+        fastFR(currentTime);
+    }
+
+    private void fastFR(int currentTime) {
+        mCallback.onFragmentFfwRwd(mItems.get(mCurrentItem), currentTime);
+        mPlaybackControlsRow.setCurrentTime(currentTime);
+        mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME);
+    }
+
+    private void stopProgressAutomation() {
+        if (mHandler != null && mRunnable != null) {
+            mHandler.removeCallbacks(mRunnable);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        stopProgressAutomation();
+        super.onStop();
+    }
+
+    protected void updateVideoImage(String uri) {
+        Glide.with(sContext)
+                .load(uri)
+                .centerCrop()
+                .into(new SimpleTarget<GlideDrawable>(CARD_WIDTH, CARD_HEIGHT) {
+                    @Override
+                    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
+                        mPlaybackControlsRow.setImageDrawable(resource);
+                        mRowsAdapter.notifyArrayItemRangeChanged(0, mRowsAdapter.size());
+                    }
+                });
+    }
+
+    private void startClickTrackingTimer() {
+        if (null != mClickTrackingTimer) {
+            mClickCount++;
+            mClickTrackingTimer.cancel();
+        } else {
+            mClickCount = 0;
+            mFfwRwdSpeed = INITIAL_SPEED;
+        }
+        mClickTrackingTimer = new Timer();
+        mClickTrackingTimer.schedule(new UpdateFfwRwdSpeedTask(), CLICK_TRACKING_DELAY);
+    }
+
+    // Container Activity must implement this interface
+    public interface OnPlayPauseClickedListener {
+        public void onFragmentPlayPause(Movie movie, int position, Boolean playPause);
+
+        public void onFragmentFfwRwd(Movie movie, int position);
+    }
+
+    static class DescriptionPresenter extends AbstractDetailsDescriptionPresenter {
+        @Override
+        protected void onBindDescription(ViewHolder viewHolder, Object item) {
+            viewHolder.getTitle().setText(((Movie) item).getTitle());
+            viewHolder.getSubtitle().setText(((Movie) item).getStudio());
+        }
+    }
+
+    private class UpdateFfwRwdSpeedTask extends TimerTask {
+
+        @Override
+        public void run() {
+            mClickTrackingHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mClickCount == 0) {
+                        mFfwRwdSpeed = INITIAL_SPEED;
+                    } else if (mClickCount == 1) {
+                        mFfwRwdSpeed *= 2;
+                    } else if (mClickCount >= 2) {
+                        mFfwRwdSpeed *= 4;
+                    }
+                    mClickCount = 0;
+                    mClickTrackingTimer = null;
+                }
+            });
+        }
+    }
+
+    private final class ItemViewClickedListener implements OnItemViewClickedListener {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+
+            if (item instanceof Movie) {
+                Movie movie = (Movie) item;
+                Log.d(TAG, "Item: " + item.toString());
+                Intent intent = new Intent(getActivity(), PlaybackOverlayActivity.class);
+                intent.putExtra(MovieDetailsActivity.MOVIE, movie);
+
+                Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
+                        getActivity(),
+                        ((ImageCardView) itemViewHolder.view).getMainImageView(),
+                        MovieDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
+                getActivity().startActivity(intent, bundle);
+            }
+        }
+    }
+
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/SearchActivity.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/SearchActivity.java
new file mode 100644
index 0000000..3c34d8c
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/SearchActivity.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.SpeechRecognitionCallback;
+import android.util.Log;
+
+import com.example.android.tvleanback.R;
+
+/*
+ * SearchActivity for SearchFragment
+ */
+public class SearchActivity extends Activity {
+    /**
+     * Called when the activity is first created.
+     */
+
+    private static final String TAG = "SearchActivity";
+    private static boolean DEBUG = true;
+    /**
+     * SpeechRecognitionCallback is not required and if not provided recognition will be handled
+     * using internal speech recognizer, in which case you must have RECORD_AUDIO permission
+     */
+    private static final int REQUEST_SPEECH = 1;
+    private SearchFragment mFragment;
+    private SpeechRecognitionCallback mSpeechRecognitionCallback;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.search);
+
+        mFragment = (SearchFragment) getFragmentManager().findFragmentById(R.id.search_fragment);
+
+        mSpeechRecognitionCallback = new SpeechRecognitionCallback() {
+            @Override
+            public void recognizeSpeech() {
+                if (DEBUG) Log.v(TAG, "recognizeSpeech");
+                startActivityForResult(mFragment.getRecognizerIntent(), REQUEST_SPEECH);
+            }
+        };
+        mFragment.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (DEBUG) Log.v(TAG, "onActivityResult requestCode=" + requestCode +
+                " resultCode=" + resultCode +
+                " data=" + data);
+        if (requestCode == REQUEST_SPEECH && resultCode == RESULT_OK) {
+            mFragment.setSearchQuery(data, true);
+        }
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        startActivity(new Intent(this, SearchActivity.class));
+        return true;
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/SearchFragment.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/SearchFragment.java
new file mode 100644
index 0000000..7984e8b
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/SearchFragment.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.data.VideoProvider;
+import com.example.android.tvleanback.model.Movie;
+import com.example.android.tvleanback.presenter.CardPresenter;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/*
+ * This class demonstrates how to do in-app search
+ */
+public class SearchFragment extends android.support.v17.leanback.app.SearchFragment
+        implements android.support.v17.leanback.app.SearchFragment.SearchResultProvider {
+    private static final String TAG = "SearchFragment";
+    private static final int SEARCH_DELAY_MS = 1000;
+
+    private ArrayObjectAdapter mRowsAdapter;
+    private Handler mHandler = new Handler();
+    private SearchRunnable mDelayedLoad;
+    private String mQuery;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+        setSearchResultProvider(this);
+        setOnItemViewClickedListener(new ItemViewClickedListener());
+        mDelayedLoad = new SearchRunnable();
+    }
+
+    @Override
+    public ObjectAdapter getResultsAdapter() {
+        return mRowsAdapter;
+    }
+
+    @Override
+    public boolean onQueryTextChange(String newQuery) {
+        Log.i(TAG, String.format("Search Query Text Change %s", newQuery));
+        loadQuery(newQuery);
+        return true;
+    }
+
+    @Override
+    public boolean onQueryTextSubmit(String query) {
+        Log.i(TAG, String.format("Search Query Text Submit %s", query));
+        loadQuery(query);
+        return true;
+    }
+
+    private void loadQuery(String query) {
+        mQuery = query;
+        mRowsAdapter.clear();
+        mHandler.removeCallbacks(mDelayedLoad);
+        if (!TextUtils.isEmpty(query) && !query.equals("nil")) {
+            mDelayedLoad.setSearchQuery(query);
+            mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
+        }
+    }
+
+    private void loadRows(String query) {
+        HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
+        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
+        for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
+            for (Movie movie : entry.getValue()) {
+                if (movie.getTitle().toLowerCase(Locale.ENGLISH)
+                        .contains(query.toLowerCase(Locale.ENGLISH))
+                        || movie.getDescription().toLowerCase(Locale.ENGLISH)
+                        .contains(query.toLowerCase(Locale.ENGLISH))) {
+                    listRowAdapter.add(movie);
+                }
+            }
+        }
+        HeaderItem header = new HeaderItem(0, getResources().getString(R.string.search_results)
+                + " '" + mQuery + "'");
+        mRowsAdapter.add(new ListRow(header, listRowAdapter));
+    }
+
+    private class SearchRunnable implements Runnable {
+
+        private volatile String searchQuery;
+
+        public SearchRunnable() {
+        }
+
+        public void run() {
+            loadRows(searchQuery);
+        }
+
+        public void setSearchQuery(String value) {
+            this.searchQuery = value;
+        }
+    }
+
+    private final class ItemViewClickedListener implements OnItemViewClickedListener {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+
+            if (item instanceof Movie) {
+                Movie movie = (Movie) item;
+                Log.d(TAG, "Movie: " + movie.toString());
+                Intent intent = new Intent(getActivity(), MovieDetailsActivity.class);
+                intent.putExtra(MovieDetailsActivity.MOVIE, movie);
+
+                Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
+                        getActivity(),
+                        ((ImageCardView) itemViewHolder.view).getMainImageView(),
+                        MovieDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
+                getActivity().startActivity(intent, bundle);
+            } else {
+                Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT)
+                        .show();
+            }
+        }
+    }
+
+
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VerticalGridActivity.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/VerticalGridActivity.java
similarity index 63%
rename from prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VerticalGridActivity.java
rename to prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/VerticalGridActivity.java
index 05e490d..0285df6 100644
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/VerticalGridActivity.java
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/VerticalGridActivity.java
@@ -12,22 +12,31 @@
  * the License.
  */
 
-package com.example.android.leanback;
+package com.example.android.tvleanback.ui;
 
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 
+import com.example.android.tvleanback.R;
+
 /*
- * Wrapper class for VerticalGridFragment
+ * VerticalGridActivity that loads VerticalGridFragment
  */
-public class VerticalGridActivity extends Activity
-{
-    /** Called when the activity is first created. */
+public class VerticalGridActivity extends Activity {
+    /**
+     * Called when the activity is first created.
+     */
     @Override
-    public void onCreate(Bundle savedInstanceState)
-    {
+    public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.vertical_grid);
         getWindow().setBackgroundDrawableResource(R.drawable.grid_bg);
     }
+
+    @Override
+    public boolean onSearchRequested() {
+        startActivity(new Intent(this, SearchActivity.class));
+        return true;
+    }
 }
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/VerticalGridFragment.java b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/VerticalGridFragment.java
new file mode 100644
index 0000000..989c651
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/tvleanback/ui/VerticalGridFragment.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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.example.android.tvleanback.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridPresenter;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.util.Log;
+import android.view.View;
+
+import com.example.android.tvleanback.R;
+import com.example.android.tvleanback.data.VideoProvider;
+import com.example.android.tvleanback.model.Movie;
+import com.example.android.tvleanback.presenter.CardPresenter;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/*
+ * VerticalGridFragment shows a grid of videos
+ */
+public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment {
+    private static final String TAG = "VerticalGridFragment";
+
+    private static final int NUM_COLUMNS = 5;
+
+    private ArrayObjectAdapter mAdapter;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.d(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        setTitle(getString(R.string.vertical_grid_title));
+
+        setupFragment();
+    }
+
+    private void setupFragment() {
+        VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
+        gridPresenter.setNumberOfColumns(NUM_COLUMNS);
+        setGridPresenter(gridPresenter);
+
+        mAdapter = new ArrayObjectAdapter(new CardPresenter());
+
+        long seed = System.nanoTime();
+
+        HashMap<String, List<Movie>> movies = VideoProvider.getMovieList();
+
+        for (Map.Entry<String, List<Movie>> entry : movies.entrySet()) {
+            List<Movie> list = entry.getValue();
+            Collections.shuffle(list, new Random(seed));
+            for (Movie movie : list) {
+                mAdapter.add(movie);
+            }
+        }
+
+        setAdapter(mAdapter);
+
+        setOnSearchClickedListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Intent intent = new Intent(getActivity(), SearchActivity.class);
+                startActivity(intent);
+            }
+        });
+
+        setOnItemViewClickedListener(new ItemViewClickedListener());
+        setOnItemViewSelectedListener(new ItemViewSelectedListener());
+    }
+
+    private final class ItemViewClickedListener implements OnItemViewClickedListener {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+
+            if (item instanceof Movie) {
+                Movie movie = (Movie) item;
+                Log.d(TAG, "Item: " + item.toString());
+                Intent intent = new Intent(getActivity(), MovieDetailsActivity.class);
+                intent.putExtra(MovieDetailsActivity.MOVIE, movie);
+
+                Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
+                        getActivity(),
+                        ((ImageCardView) itemViewHolder.view).getMainImageView(),
+                        MovieDetailsActivity.SHARED_ELEMENT_NAME).toBundle();
+                getActivity().startActivity(intent, bundle);
+            }
+        }
+    }
+
+
+    private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                                   RowPresenter.ViewHolder rowViewHolder, Row row) {
+        }
+    }
+
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/bg.png b/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/bg.png
new file mode 100644
index 0000000..476c698
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/bg.png
Binary files differ
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/dragon2.jpg b/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/dragon2.jpg
new file mode 100644
index 0000000..dcf708d
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/dragon2.jpg
Binary files differ
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/maleficent.jpg b/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/maleficent.jpg
new file mode 100644
index 0000000..94cb637
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/maleficent.jpg
Binary files differ
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/noah.jpg b/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/noah.jpg
new file mode 100644
index 0000000..aada53f
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/res/drawable-xhdpi/noah.jpg
Binary files differ
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/drawable/text_bg.xml b/prebuilts/androidtv/leanback/app/src/main/res/drawable/text_bg.xml
new file mode 100644
index 0000000..a26937d
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/res/drawable/text_bg.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:startColor="#FFFF0000"
+        android:endColor="#FFFF00FF"
+        android:angle="45" />
+    <padding
+        android:left="7dp"
+        android:top="7dp"
+        android:right="7dp"
+        android:bottom="7dp" />
+    <size
+        android:height="160dp"
+        android:width="100dp" />
+</shape>
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/layout/details.xml b/prebuilts/androidtv/leanback/app/src/main/res/layout/details.xml
index e7f675d..3924218 100644
--- a/prebuilts/androidtv/leanback/app/src/main/res/layout/details.xml
+++ b/prebuilts/androidtv/leanback/app/src/main/res/layout/details.xml
@@ -16,7 +16,7 @@
 -->
 
 <fragment xmlns:android="http://schemas.android.com/apk/res/android"
-    android:name="com.example.android.leanback.LeanbackDetailsFragment"
+    android:name="com.example.android.tvleanback.ui.MovieDetailsFragment"
     android:id="@+id/details_fragment"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/layout/main.xml b/prebuilts/androidtv/leanback/app/src/main/res/layout/main.xml
index 46eaad9..1a6d184 100644
--- a/prebuilts/androidtv/leanback/app/src/main/res/layout/main.xml
+++ b/prebuilts/androidtv/leanback/app/src/main/res/layout/main.xml
@@ -15,9 +15,16 @@
      limitations under the License.
 -->
 
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
-    android:name="com.example.android.leanback.MainFragment"
-    android:id="@+id/main_browse_fragment"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/main_frame"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-/>
+    android:layout_height="match_parent">
+
+    <fragment
+        android:name="com.example.android.tvleanback.ui.MainFragment"
+        android:id="@+id/main_browse_fragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</FrameLayout>
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/layout/playback_controls.xml b/prebuilts/androidtv/leanback/app/src/main/res/layout/playback_controls.xml
new file mode 100644
index 0000000..1039d52
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/res/layout/playback_controls.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <VideoView android:id="@+id/videoView"
+        android:layout_width="match_parent"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentBottom="true"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:layout_centerInParent="true">
+    </VideoView>
+
+    <fragment
+        android:id="@+id/playback_controls_fragment"
+        android:name="com.example.android.tvleanback.ui.PlaybackOverlayFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</FrameLayout>
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/layout/player_activity.xml b/prebuilts/androidtv/leanback/app/src/main/res/layout/player_activity.xml
deleted file mode 100644
index f976f7f..0000000
--- a/prebuilts/androidtv/leanback/app/src/main/res/layout/player_activity.xml
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/container"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <VideoView android:id="@+id/videoView"
-             android:layout_width="fill_parent"
-             android:layout_alignParentRight="true"
-             android:layout_alignParentLeft="true"
-             android:layout_alignParentTop="true"
-             android:layout_alignParentBottom="true"
-             android:layout_height="fill_parent"
-             android:layout_gravity="center"
-             android:layout_centerInParent="true">
-    </VideoView>
-    
-    <RelativeLayout
-        android:id="@+id/controllers"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignBottom="@+id/videoView"
-        android:layout_alignLeft="@+id/videoView"
-        android:layout_alignRight="@+id/videoView"
-        android:layout_alignTop="@+id/videoView"
-        android:layout_centerInParent="true"
-        android:background="@drawable/player_bg_gradient_dark" >
-
-        <ProgressBar
-            android:id="@+id/progressBar"
-            style="@android:style/Widget.ProgressBar.Horizontal"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            android:visibility="gone" />
-
-        <RelativeLayout
-            android:layout_width="fill_parent"
-            android:layout_height="45dp"
-            android:layout_alignParentBottom="true" >
-
-            <ImageView
-                android:id="@+id/playpause"
-                android:contentDescription="@+id/play_pause_description"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentLeft="true"
-                android:src="@drawable/ic_play_playcontrol_normal" />
-
-            <TextView
-                android:id="@+id/startText"
-                android:layout_width="wrap_content"
-                android:layout_height="fill_parent"
-                android:layout_marginLeft="5dp"
-                android:layout_toRightOf="@+id/playpause"
-                android:gravity="center_vertical"
-                android:maxLines="1"
-                android:text="@+id/init_text"
-                android:textColor="@color/white" />
-
-            <TextView
-                android:id="@+id/endText"
-                android:layout_width="wrap_content"
-                android:layout_height="fill_parent"
-                android:layout_alignParentRight="true"
-                android:layout_marginRight="16dp"
-                android:gravity="center_vertical"
-                android:maxLines="1"
-                android:text="@+id/init_text"
-                android:textColor="@color/white" />
-
-            <SeekBar
-                android:id="@+id/seekBar"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:layout_centerVertical="true"
-                android:layout_gravity="center"
-                android:layout_marginLeft="5dp"
-                android:layout_marginRight="5dp"
-                android:layout_toLeftOf="@+id/endText"
-                android:layout_toRightOf="@+id/startText" />
-        </RelativeLayout>
-    </RelativeLayout>
-
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/layout/search.xml b/prebuilts/androidtv/leanback/app/src/main/res/layout/search.xml
index b65600c..09c7816 100644
--- a/prebuilts/androidtv/leanback/app/src/main/res/layout/search.xml
+++ b/prebuilts/androidtv/leanback/app/src/main/res/layout/search.xml
@@ -16,8 +16,8 @@
 -->
 
 <fragment xmlns:android="http://schemas.android.com/apk/res/android"
-          android:name="com.example.android.leanback.SearchFragment"
+          android:name="com.example.android.tvleanback.ui.SearchFragment"
           android:id="@+id/search_fragment"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
-        />
\ No newline at end of file
+        />
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/layout/vertical_grid.xml b/prebuilts/androidtv/leanback/app/src/main/res/layout/vertical_grid.xml
index 1007042..44c8d0e 100644
--- a/prebuilts/androidtv/leanback/app/src/main/res/layout/vertical_grid.xml
+++ b/prebuilts/androidtv/leanback/app/src/main/res/layout/vertical_grid.xml
@@ -16,7 +16,7 @@
 -->
 
 <fragment xmlns:android="http://schemas.android.com/apk/res/android"
-    android:name="com.example.android.leanback.VerticalGridFragment"
+    android:name="com.example.android.tvleanback.ui.VerticalGridFragment"
     android:id="@+id/vertical_grid_fragment"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/values/colors.xml b/prebuilts/androidtv/leanback/app/src/main/res/values/colors.xml
index 1583f32..ba5c7ad 100644
--- a/prebuilts/androidtv/leanback/app/src/main/res/values/colors.xml
+++ b/prebuilts/androidtv/leanback/app/src/main/res/values/colors.xml
@@ -4,7 +4,7 @@
     <color name="background_gradient_end">#DDDDDD</color>
     <color name="fastlane_background">#0096a6</color>
     <color name="search_opaque">#ffaa3f</color>
-    <color name="detail_background">#0096a6</color>
+    <color name="selected_background">#ffaa3f</color>
     <color name="soft_opaque">#30000000</color>
     <color name="img_soft_opaque">#30FF0000</color>
     <color name="img_full_opaque">#00000000</color>
@@ -14,5 +14,5 @@
     <color name="orange_transparent">#AAFADCA7</color>
     <color name="orange">#FADCA7</color>
     <color name="yellow">#EEFF41</color>
-    <color name="default_background">#3d3d3d</color>
+    <color name="default_background">#0096a6</color>
 </resources>
\ No newline at end of file
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/values/strings.xml b/prebuilts/androidtv/leanback/app/src/main/res/values/strings.xml
index 3e47d39..e9ce0ee 100644
--- a/prebuilts/androidtv/leanback/app/src/main/res/values/strings.xml
+++ b/prebuilts/androidtv/leanback/app/src/main/res/values/strings.xml
@@ -16,7 +16,7 @@
 -->
 
 <resources>
-    <string name="app_name">Leanback API Demo</string>
+    <string name="app_name"><![CDATA[Videos by Google]]></string>
     <string name="browse_title"><![CDATA[Videos by Google]]></string>
     <string name="related_movies">Related Movies</string>
     <string name="vertical_grid_title"><![CDATA[Vertical Video Grid]]></string>
@@ -31,9 +31,9 @@
     <string name="no_video_found">No video was found</string>
     <string name="version">Version: %1$s</string>
     <string name="popular_header">Popular Videos</string>
-    <string name="preferences">PREFERENCES</string>
+    <string name="more_samples">More Samples</string>
     <string name="grid_view">Grid View</string>
-    <string name="send_feeback">Send Feedback</string>
+    <string name="error_fragment">Error Fragment</string>
     <string name="personal_settings">Personal Settings</string>
     <string name="watch_trailer_1">Watch trailer</string>
     <string name="watch_trailer_2">FREE</string>
@@ -41,13 +41,13 @@
     <string name="rent_2">From $1.99</string>
     <string name="buy_1">Buy and Own</string>
     <string name="buy_2">AT $9.99</string>
-    <string name="movie">Movie</string>
+    <string name="movie_index">MovieIndex</string>
     <string name="should_start">shouldStart</string>
     <string name="start_position">startPosition</string>
     <string name="search_results">Search Results</string>
     <string name="catalog_url">http://commondatastorage.googleapis.com/android-tv/android_tv_videos.json</string>
     <string name="prefix_url">http://commondatastorage.googleapis.com/android-tv/Sample%20videos/</string>
-    
+
     <!-- Error messages -->
     <string name="failed_to_launch_app">Failed to launch application</string>
     <string name="failed_to_find_app">The application you are trying to launch is not available</string>
@@ -57,8 +57,10 @@
     <string name="failed_to_connect">Could not connect to the device</string>
     <string name="failed_to_seek">Failed to seek to the specified position on the remote device</string>
     <string name="video_error_media_load_timeout">Media loading timed out</string>
-    <string name="video_error_server_unaccessible">Media server was not reachable</string>
+    <string name="video_error_server_inaccessible">Media server was not reachable</string>
     <string name="video_error_unknown_error">Failed to load video</string>
+    <string name="error_fragment_message">An error occurred</string>
+    <string name="dismiss_error">Dismiss</string>
     <string name="oops">Oops</string>
 
     <!-- Preferences -->
@@ -71,4 +73,46 @@
     <string name="prefs_volume_dialog_title">Volume Assignment</string>
     <string name="prefs_volume_default">device</string>
     <string name="title_activity_test">TestActivity</string>
+
+    <!-- Global Search -->
+    <!-- The action label for global search -->
+    <string name="global_search">GLOBALSEARCH</string>
+
+    <!-- The label for use as a searchable item -->
+    <string name="search_label">Leanback API Demo</string>
+
+    <!-- The hint text that appears in the search box. -->
+    <string name="search_hint">Search for videos</string>
+
+    <!-- The description that will show up in the search settings for this source.  -->
+    <string name="settings_description">Leanback API Demo Videos</string>
+
+    <!-- General instructions in the main activity. -->
+    <string name="search_instructions">Use the search box to search for videos</string>
+
+    <!-- Shown above search results when we receive a search request. -->
+    <plurals name="search_results">
+        <item quantity="one">%1$d result for \"%2$s\": </item>
+        <item quantity="other">%1$d results for \"%2$s\": </item>
+    </plurals>
+
+    <!-- Search failure message. -->
+    <string name="no_results">No results found for \"%s\"</string>
+    <string name="noah_description">to build an Ark to save creation from the coming flood.</string>
+    <string name="noah_title">Noah</string>
+    <string name="dragon2_title">How to Train Your Dragon 2</string>
+    <string name="dragon2_description">Set in the mythical world of burly Vikings and wild dragons,
+        and based on the book by Cressida Cowell, the action comedy tells the story of Hiccup, a
+        Viking teenager who doesn\'t exactly fit in with his tribe\'s longstanding tradition of
+        heroic dragon slayers. Hiccup\'s world is turned upside down when he encounters a dragon
+        that challenges he and his fellow Vikings to see the world from an entirely different point
+        of view.
+    </string>
+    <string name="maleficent_title">Maleficent</string>
+    <string name="maleficent_description">The untold story of Disney\'s most iconic villain from the
+        1959 classic "Sleeping Beauty." A beautiful, pure-hearted young woman with stunning black
+        wings, Maleficent has an idyllic life growing up in a peaceable forest kingdom, until one
+        day when an invading army of humans threatens the harmony of the land.
+    </string>
+
 </resources>
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/values/themes.xml b/prebuilts/androidtv/leanback/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..9ab74c6
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/res/values/themes.xml
@@ -0,0 +1,13 @@
+<resources>
+    <style name="Theme.Example.Leanback" parent="Theme.Leanback">
+        <item name="android:colorPrimary">@color/search_opaque</item>
+        <item name="android:windowEnterTransition">@android:transition/fade</item>
+        <item name="android:windowExitTransition">@android:transition/fade</item>
+        <item name="android:windowSharedElementExitTransition">@android:transition/move</item>
+        <item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
+        <!-- Set to display colorPrimary when apps launches -->
+        <item name="android:windowAllowReturnTransitionOverlap">true</item>
+        <item name="android:windowAllowEnterTransitionOverlap">false</item>
+        <item name="android:windowContentTransitions">true</item>
+    </style>
+</resources>
diff --git a/prebuilts/androidtv/leanback/app/src/main/res/xml/searchable.xml b/prebuilts/androidtv/leanback/app/src/main/res/xml/searchable.xml
new file mode 100644
index 0000000..4eb74ef
--- /dev/null
+++ b/prebuilts/androidtv/leanback/app/src/main/res/xml/searchable.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** 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.
+*/
+-->
+
+<!-- The attributes below configure the Android search box appearance
+     and the search suggestions settings.
+     See the Developer Guide for more information
+     http://developer.android.com/guide/topics/search/
+ -->
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+        android:label="@string/search_label"
+        android:hint="@string/search_hint"
+        android:searchSettingsDescription="@string/settings_description"
+        android:searchSuggestAuthority="com.example.android.tvleanback"
+        android:searchSuggestIntentAction="android.intent.action.VIEW"
+        android:searchSuggestIntentData="content://com.example.android.tvleanback/video_database_leanback"
+        android:searchSuggestSelection=" ?"
+        android:searchSuggestThreshold="1"
+        android:includeInGlobalSearch="true"
+        >
+ </searchable>
diff --git a/prebuilts/androidtv/leanback/build.gradle b/prebuilts/androidtv/leanback/build.gradle
index a1d991f..70088bd 100644
--- a/prebuilts/androidtv/leanback/build.gradle
+++ b/prebuilts/androidtv/leanback/build.gradle
@@ -5,7 +5,7 @@
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:0.12.+'
+        classpath 'com.android.tools.build:gradle:1.1.0'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
diff --git a/prebuilts/androidtv/leanback/gradle/wrapper/gradle-wrapper.properties b/prebuilts/androidtv/leanback/gradle/wrapper/gradle-wrapper.properties
index 897ca3c..0b1b820 100644
--- a/prebuilts/androidtv/leanback/gradle/wrapper/gradle-wrapper.properties
+++ b/prebuilts/androidtv/leanback/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Fri Jul 11 18:48:36 PDT 2014
+#Tue Jan 06 11:11:20 PST 2015
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/prebuilts/androidtv/sample-inputs/.gitignore b/prebuilts/androidtv/sample-inputs/.gitignore
new file mode 100644
index 0000000..53c4a9d
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/.gitignore
@@ -0,0 +1,7 @@
+.gradle/
+.idea/
+app/app.iml
+app/build/
+build/
+local.properties
+sample-inputs.iml
diff --git a/prebuilts/androidtv/sample-inputs/CONTRIBUTING.md b/prebuilts/androidtv/sample-inputs/CONTRIBUTING.md
new file mode 100644
index 0000000..51c5b7f
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/CONTRIBUTING.md
@@ -0,0 +1,56 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement
+(CLA).
+
+  * If you are an individual writing original source code and you're sure you
+    own the intellectual property, then you'll need to sign an [individual CLA]
+    (http://code.google.com/legal/individual-cla-v1.0.html).
+  * If you work for a company that wants to allow you to contribute your work,
+    then you'll need to sign a [corporate CLA]
+    (http://code.google.com/legal/corporate-cla-v1.0.html).
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing a Patch
+
+1. Sign a Contributor License Agreement, if you have not yet done so (see
+   details above).
+1. Create your change to the repo in question.
+    * Fork the desired repo, develop and test your code changes.
+    * Ensure that your code is clear and comprehensible.
+    * Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
+1. The repo owner will review your request. If it is approved, the change will
+   be merged. If it needs additional work, the repo owner will respond with
+   useful comments.
+
+## Contributing a New Sample App
+
+1. Sign a Contributor License Agreement, if you have not yet done so (see
+   details above).
+1. Create your own repo for your app following this naming convention:
+    * mirror-{app-name}-{language or plaform}
+    * apps: quickstart, photohunt-server, photohunt-client
+    * example:  mirror-quickstart-android
+    * For multi-language apps, concatenate the primary languages like this:
+      mirror-photohunt-server-java-python.
+
+1. Create your sample app in this repo.
+    * Be sure to clone the README.md, CONTRIBUTING.md and LICENSE files from the
+      googlesamples repo.
+    * Ensure that your code is clear and comprehensible.
+    * Ensure that your code has an appropriate set of unit tests which all pass.
+    * Instructional value is the top priority when evaluating new app proposals for
+      this collection of repos.
+1. Submit a request to fork your repo in googlesamples organization.
+1. The repo owner will review your request. If it is approved, the sample will
+   be merged. If it needs additional work, the repo owner will respond with
+   useful comments.
diff --git a/prebuilts/androidtv/sample-inputs/LICENSE b/prebuilts/androidtv/sample-inputs/LICENSE
new file mode 100644
index 0000000..8405e89
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
\ No newline at end of file
diff --git a/prebuilts/androidtv/sample-inputs/README.md b/prebuilts/androidtv/sample-inputs/README.md
new file mode 100644
index 0000000..d635765
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/README.md
@@ -0,0 +1,51 @@
+# Sample for live TV Input Framework (TIF) on Android TV
+
+This app is designed to show how to build live TV apps for Android TV.  The sample is a service that once installed, it's recognized and run by the default TV app.
+
+It consists of:
+
+* Simple TV input: 2 channels from local video files
+* Rich TV input: 4 channels served from Google Cloud Storage consisting of MP4 videos, HLS stream and MPEG-DASH stream
+
+Users can set up these TV inputs using the Live Channels app.
+
+## Dependencies
+* ExoPlayer: http://developer.android.com/guide/topics/media/exoplayer.html
+
+## Setup Instructions
+* Compile the project and install the app to your Android TV device.
+* Start the pre-installed system app Live Channels (which does not show up in Apps on Home screen until there is at least one TV input service or a physical input like HDMI1).
+* To set up the Rich TV input:
+    - Either click Search upon starting Live Channels app to search for channels
+    - Or when running Live Channels app, 
+        + Click ENTER to bring out Recent Channels
+        + Click DOWN to enter TV options
+        + Click RIGHT to Channel sources and select it
+        + Click DOWN to select Rich Input and hit ENTER
+        + Click ADD CHANNELS NOW to add 3 channels of MP4 videos, HLS stream and MPEG-DASH stream
+* To watch sample channels, simply toggle UP and DOWN to switch channels
+* Visit Channel Sources -> Rich Input -> Settings to see mock options for input settings.
+
+## References and How to report bugs
+* [Android TV Developer Documentation: TV Input Framework](http://developer.android.com/training/tv/tif/index.html)
+
+## How to make contributions?
+Please read and follow the steps in the CONTRIBUTING.md
+
+## License
+See LICENSE
+
+## Google+
+Android TV Community Page on Google+ [https://g.co/androidtvdev](https://g.co/androidtvdev)
+
+## Change List
+Version 1.0
+
+## Notice
+
+Images/videos used in this sample are courtesy of the Blender Foundation, shared under copyright or Creative Commons license.
+
+* Elephant's Dream: (c) copyright 2006, Blender Foundation / Netherlands Media Art Institute / www.elephantsdream.org
+* Sintel: (c) copyright Blender Foundation | www.sintel.org
+* Tears of Steel: (CC) Blender Foundation | mango.blender.org
+* Big Buck Bunny: (c) copyright 2008, Blender Foundation / www.bigbuckbunny.org
diff --git a/prebuilts/androidtv/sample-inputs/app/build.gradle b/prebuilts/androidtv/sample-inputs/app/build.gradle
new file mode 100644
index 0000000..fe70a57
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 21
+    buildToolsVersion "21.0.0"
+
+    defaultConfig {
+        applicationId "com.example.android.sampletvinput"
+        minSdkVersion 21
+        targetSdkVersion 21
+        versionCode 1
+        versionName "1.0"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    compile 'com.android.support:recyclerview-v7:21.0.0'
+    compile 'com.android.support:leanback-v17:21.0.0'
+    compile 'com.android.support:appcompat-v7:21.0.0'
+    compile 'com.squareup.picasso:picasso:2.3.2'
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/libs/exoplayer_dev-hls_20150123.jar b/prebuilts/androidtv/sample-inputs/app/libs/exoplayer_dev-hls_20150123.jar
new file mode 100644
index 0000000..771f7b7
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/libs/exoplayer_dev-hls_20150123.jar
Binary files differ
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/AndroidManifest.xml b/prebuilts/androidtv/sample-inputs/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..14ad57e
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/AndroidManifest.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.sampletvinput">
+
+    <!-- Required to play internet-based streaming contents. -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <!-- Required to register a SyncStatusObserver. -->
+    <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
+    <!-- Required to enable our SyncAdapter after it's created. -->
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
+    <!-- Required because we're manually creating a new account. -->
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
+    <!-- Required to update or read existing channel and program information in TvProvider. -->
+    <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+    <!-- Required to update channel and program information in TvProvider. -->
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+
+    <application android:label="@string/sample_tv_input"
+            android:icon="@drawable/android_48dp"
+            android:theme="@style/Theme.Leanback" >
+        <!-- Launched by the TV app before it uses SimpleTvInputService to set up channels for this
+        input. -->
+        <activity android:name=".simple.SimpleTvInputSetupActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <!-- TV input sample which includes minimal implementation for seeing a video on the TV app.
+        Requires "android.permission.BIND_TV_INPUT" to ensure that only system services can bind.
+        Lots of features including EPG and parental controls are left out in favor of keeping this
+        sample simple. For a fully-featured example, see RichTvInputService. -->
+        <service android:name=".simple.SimpleTvInputService"
+            android:permission="android.permission.BIND_TV_INPUT"
+            android:label="@string/simple_input_label">
+            <!-- Required filter used by the system to launch our account service. -->
+            <intent-filter>
+                <action android:name="android.media.tv.TvInputService" />
+            </intent-filter>
+            <!-- An XML file which describes this input. This provides a pointer to the
+            SimpleTvInputSetupActivity to the system/TV app. -->
+            <meta-data android:name="android.media.tv.input"
+                android:resource="@xml/simpletvinputservice" />
+        </service>
+
+        <!-- Launched by the TV app before it uses RichTvInputService. This registers channels and
+        sets up SyncAdapter to provide program information in the background. -->
+        <activity android:name=".rich.RichTvInputSetupActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <!-- Launched by the TV app when user wants to change the settings for this input. The
+        settings activity is expected to be used for the existing configuration. E.g. subscription
+        change, fine tuning of the channels, etc. -->
+        <activity android:name=".rich.RichTvInputSettingsActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+        <!-- TV input which provides channels based on the streaming contents.
+        Requires "android.permission.BIND_TV_INPUT" to ensure that only system services can bind.
+        This provides the full implementation of TvInputService including EPG, subtitles,
+        multi-audio, parental controls, and overlay view.
+        -->
+        <service android:name=".rich.RichTvInputService"
+            android:permission="android.permission.BIND_TV_INPUT"
+            android:label="@string/rich_input_label">
+            <!-- Required filter used by the system to launch our account service. -->
+            <intent-filter>
+                <action android:name="android.media.tv.TvInputService" />
+            </intent-filter>
+            <!-- An XML file which describes this input. This provides pointers to the
+            RichTvInputSetupActivity and RichTvInputSettingsActivity to the system/TV app. -->
+            <meta-data android:name="android.media.tv.input"
+                android:resource="@xml/richtvinputservice" />
+        </service>
+
+        <!-- This service implements the SyncAdapter for updating program information regularly in
+        the background. It needs to be exported, so that the sync framework can access it. -->
+        <service android:name=".syncadapter.SyncService"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.content.SyncAdapter" />
+            </intent-filter>
+            <meta-data android:name="android.content.SyncAdapter"
+                android:resource="@xml/syncadapter" />
+        </service>
+
+        <!-- Since the channel/program feed here does not require any authentication, we use a dummy
+        account used for SyncAdapter. -->
+        <service android:name=".syncadapter.DummyAccountService">
+            <intent-filter>
+                <action android:name="android.accounts.AccountAuthenticator" />
+            </intent-filter>
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                android:resource="@xml/authenticator" />
+        </service>
+    </application>
+
+    <uses-feature
+        android:name="android.hardware.touchscreen"
+        android:required="false" />
+    <uses-feature
+        android:name="android.software.leanback"
+        android:required="true" />
+    <!-- Required to expose this app in the store only when the device has TV input framework
+    with the TV app. -->
+    <uses-feature
+        android:name="android.software.live_tv"
+        android:required="true" />
+</manifest>
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/TvContractUtils.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/TvContractUtils.java
new file mode 100644
index 0000000..14a9184
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/TvContractUtils.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvContract;
+import android.media.tv.TvContract.Channels;
+import android.media.tv.TvContract.Programs;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.example.android.sampletvinput.rich.RichTvInputService.ChannelInfo;
+import com.example.android.sampletvinput.rich.RichTvInputService.PlaybackInfo;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Static helper methods for working with {@link android.media.tv.TvContract}.
+ */
+public class TvContractUtils {
+    private static final String TAG = "TvContractUtils";
+    private static final boolean DEBUG = true;
+
+    private static final SparseArray<String> VIDEO_HEIGHT_TO_FORMAT_MAP =
+            new SparseArray<String>();
+
+    static {
+        VIDEO_HEIGHT_TO_FORMAT_MAP.put(480, TvContract.Channels.VIDEO_FORMAT_480P);
+        VIDEO_HEIGHT_TO_FORMAT_MAP.put(576, TvContract.Channels.VIDEO_FORMAT_576P);
+        VIDEO_HEIGHT_TO_FORMAT_MAP.put(720, TvContract.Channels.VIDEO_FORMAT_720P);
+        VIDEO_HEIGHT_TO_FORMAT_MAP.put(1080, TvContract.Channels.VIDEO_FORMAT_1080P);
+        VIDEO_HEIGHT_TO_FORMAT_MAP.put(2160, TvContract.Channels.VIDEO_FORMAT_2160P);
+        VIDEO_HEIGHT_TO_FORMAT_MAP.put(4320, TvContract.Channels.VIDEO_FORMAT_4320P);
+    }
+
+    public static void updateChannels(
+            Context context, String inputId, List<ChannelInfo> channels) {
+        // Create a map from original network ID to channel row ID for existing channels.
+        SparseArray<Long> mExistingChannelsMap = new SparseArray<Long>();
+        Uri channelsUri = TvContract.buildChannelsUriForInput(inputId);
+        String[] projection = {Channels._ID, Channels.COLUMN_ORIGINAL_NETWORK_ID};
+        Cursor cursor = null;
+        ContentResolver resolver = context.getContentResolver();
+        try {
+            cursor = resolver.query(channelsUri, projection, null, null, null);
+            while (cursor != null && cursor.moveToNext()) {
+                long rowId = cursor.getLong(0);
+                int originalNetworkId = cursor.getInt(1);
+                mExistingChannelsMap.put(originalNetworkId, rowId);
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        // If a channel exists, update it. If not, insert a new one.
+        ContentValues values = new ContentValues();
+        values.put(Channels.COLUMN_INPUT_ID, inputId);
+        Map<Uri, String> logos = new HashMap<Uri, String>();
+        for (ChannelInfo channel : channels) {
+            values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.number);
+            values.put(Channels.COLUMN_DISPLAY_NAME, channel.name);
+            values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.originalNetworkId);
+            values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, channel.transportStreamId);
+            values.put(Channels.COLUMN_SERVICE_ID, channel.serviceId);
+            String videoFormat = getVideoFormat(channel.videoHeight);
+            if (videoFormat != null) {
+                values.put(Channels.COLUMN_VIDEO_FORMAT, videoFormat);
+            } else {
+                values.putNull(Channels.COLUMN_VIDEO_FORMAT);
+            }
+            Long rowId = mExistingChannelsMap.get(channel.originalNetworkId);
+            Uri uri;
+            if (rowId == null) {
+                uri = resolver.insert(TvContract.Channels.CONTENT_URI, values);
+            } else {
+                uri = TvContract.buildChannelUri(rowId);
+                resolver.update(uri, values, null, null);
+                mExistingChannelsMap.remove(channel.originalNetworkId);
+            }
+            if (!TextUtils.isEmpty(channel.logoUrl)) {
+                logos.put(TvContract.buildChannelLogoUri(uri), channel.logoUrl);
+            }
+        }
+        if (!logos.isEmpty()) {
+            new InsertLogosTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, logos);
+        }
+
+        // Deletes channels which don't exist in the new feed.
+        int size = mExistingChannelsMap.size();
+        for(int i = 0; i < size; ++i) {
+            Long rowId = mExistingChannelsMap.valueAt(i);
+            resolver.delete(TvContract.buildChannelUri(rowId), null, null);
+        }
+    }
+
+    public static int getChannelCount(ContentResolver resolver, String inputId) {
+        Uri uri = TvContract.buildChannelsUriForInput(inputId);
+        String[] projection = { TvContract.Channels._ID };
+
+        Cursor cursor = null;
+        try {
+            cursor = resolver.query(uri, projection, null, null, null);
+            if (cursor != null) {
+                return cursor.getCount();
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return 0;
+    }
+
+    private static String getVideoFormat(int videoHeight) {
+        return VIDEO_HEIGHT_TO_FORMAT_MAP.get(videoHeight);
+    }
+
+    public static LongSparseArray<ChannelInfo> buildChannelMap(ContentResolver resolver,
+            String inputId, List<ChannelInfo> channels) {
+        Uri uri = TvContract.buildChannelsUriForInput(inputId);
+        String[] projection = {
+                TvContract.Channels._ID,
+                TvContract.Channels.COLUMN_DISPLAY_NUMBER
+        };
+
+        LongSparseArray<ChannelInfo> channelMap = new LongSparseArray<>();
+        Cursor cursor = null;
+        try {
+            cursor = resolver.query(uri, projection, null, null, null);
+            if (cursor == null || cursor.getCount() == 0) {
+                return null;
+            }
+
+            while (cursor.moveToNext()) {
+                long channelId = cursor.getLong(0);
+                String channelNumber = cursor.getString(1);
+                channelMap.put(channelId, getChannelByNumber(channelNumber, channels));
+            }
+        } catch (Exception e) {
+            Log.d(TAG, "Content provider query: " + e.getStackTrace());
+            return null;
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return channelMap;
+    }
+
+    public static long getLastProgramEndTimeMillis(ContentResolver resolver, Uri channelUri) {
+        Uri uri = TvContract.buildProgramsUriForChannel(channelUri);
+        String[] projection = {Programs.COLUMN_END_TIME_UTC_MILLIS};
+        Cursor cursor = null;
+        try {
+            // TvProvider returns programs chronological order by default.
+            cursor = resolver.query(uri, projection, null, null, null);
+            if (cursor == null || cursor.getCount() == 0) {
+                return 0;
+            }
+            cursor.moveToLast();
+            return cursor.getLong(0);
+        } catch (Exception e) {
+            Log.w(TAG, "Unable to get last program end time for " + channelUri, e);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return 0;
+    }
+
+    public static List<PlaybackInfo> getProgramPlaybackInfo(
+            ContentResolver resolver, Uri channelUri, long startTimeMs, long endTimeMs,
+            int maxProgramInReturn) {
+        Uri uri = TvContract.buildProgramsUriForChannel(channelUri, startTimeMs,
+                endTimeMs);
+        String[] projection = { Programs.COLUMN_START_TIME_UTC_MILLIS,
+                Programs.COLUMN_END_TIME_UTC_MILLIS,
+                Programs.COLUMN_CONTENT_RATING,
+                Programs.COLUMN_INTERNAL_PROVIDER_DATA };
+        Cursor cursor = null;
+        List<PlaybackInfo> list = new ArrayList<>();
+        try {
+            cursor = resolver.query(uri, projection, null, null, null);
+            while (cursor.moveToNext()) {
+                long startMs = cursor.getLong(0);
+                long endMs = cursor.getLong(1);
+                TvContentRating[] ratings = stringToContentRatings(cursor.getString(2));
+                Pair<Integer, String> values = parseInternalProviderData(cursor.getString(3));
+                int videoType = values.first;
+                String videoUrl = values.second;
+                list.add(new PlaybackInfo(startMs, endMs, videoUrl, videoType,
+                        ratings));
+                if (list.size() > maxProgramInReturn) {
+                    break;
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to get program playback info from TvProvider.", e);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return list;
+    }
+
+    public static String convertVideoInfoToInternalProviderData(int videotype, String videoUrl) {
+        return videotype + "," + videoUrl;
+    }
+
+    public static Pair<Integer, String> parseInternalProviderData(String internalData) {
+        String[] values = internalData.split(",", 2);
+        if (values.length != 2) {
+            throw new IllegalArgumentException(internalData);
+        }
+        return new Pair<>(Integer.parseInt(values[0]), values[1]);
+    }
+
+    public static void insertUrl(Context context, Uri contentUri, URL sourceUrl) {
+        if (DEBUG) {
+            Log.d(TAG, "Inserting " + sourceUrl + " to " + contentUri);
+        }
+        InputStream is = null;
+        OutputStream os = null;
+        try {
+            is = sourceUrl.openStream();
+            os = context.getContentResolver().openOutputStream(contentUri);
+            copy(is, os);
+        } catch (IOException ioe) {
+            Log.e(TAG, "Failed to write " + sourceUrl + "  to " + contentUri, ioe);
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    // Ignore exception.
+                }
+            }
+            if (os != null) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                    // Ignore exception.
+                }
+            }
+        }
+    }
+
+    public static void copy(InputStream is, OutputStream os) throws IOException {
+        byte[] buffer = new byte[1024];
+        int len;
+        while ((len = is.read(buffer)) != -1) {
+            os.write(buffer, 0, len);
+        }
+    }
+
+    public static String getServiceNameFromInputId(Context context, String inputId) {
+        TvInputManager tim = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
+        for (TvInputInfo info : tim.getTvInputList()) {
+            if (info.getId().equals(inputId)) {
+                return info.getServiceInfo().name;
+            }
+        }
+        return null;
+    }
+
+    public static TvContentRating[] stringToContentRatings(String commaSeparatedRatings) {
+        if (TextUtils.isEmpty(commaSeparatedRatings)) {
+            return null;
+        }
+        String[] ratings = commaSeparatedRatings.split("\\s*,\\s*");
+        TvContentRating[] contentRatings = new TvContentRating[ratings.length];
+        for (int i = 0; i < contentRatings.length; ++i) {
+            contentRatings[i] = TvContentRating.unflattenFromString(ratings[i]);
+        }
+        return contentRatings;
+    }
+
+    public static String contentRatingsToString(TvContentRating[] contentRatings) {
+        if (contentRatings == null || contentRatings.length == 0) {
+            return null;
+        }
+        final String DELIMITER = ",";
+        StringBuilder ratings = new StringBuilder(contentRatings[0].flattenToString());
+        for (int i = 1; i < contentRatings.length; ++i) {
+            ratings.append(DELIMITER);
+            ratings.append(contentRatings[i].flattenToString());
+        }
+        return ratings.toString();
+    }
+
+    private static ChannelInfo getChannelByNumber(String channelNumber,
+            List<ChannelInfo> channels) {
+        for (ChannelInfo info : channels) {
+            if (info.number.equals(channelNumber)) {
+                return info;
+            }
+        }
+        throw new IllegalArgumentException("Unknown channel: " + channelNumber);
+    }
+
+    private TvContractUtils() {}
+
+    public static class InsertLogosTask extends AsyncTask<Map<Uri, String>, Void, Void> {
+        private final Context mContext;
+
+        InsertLogosTask(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public Void doInBackground(Map<Uri, String>... logosList) {
+            for (Map<Uri, String> logos : logosList) {
+                for (Uri uri : logos.keySet()) {
+                    try {
+                        insertUrl(mContext, uri, new URL(logos.get(uri)));
+                    } catch (MalformedURLException e) {
+                        Log.e(TAG, "Can't load " + logos.get(uri), e);
+                    }
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/player/TvInputPlayer.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/player/TvInputPlayer.java
new file mode 100644
index 0000000..30ee412
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/player/TvInputPlayer.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.player;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.media.MediaCodec;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.view.Surface;
+
+import com.google.android.exoplayer.DefaultLoadControl;
+import com.google.android.exoplayer.DummyTrackRenderer;
+import com.google.android.exoplayer.ExoPlaybackException;
+import com.google.android.exoplayer.ExoPlayer;
+import com.google.android.exoplayer.ExoPlayerLibraryInfo;
+import com.google.android.exoplayer.FrameworkSampleSource;
+import com.google.android.exoplayer.LoadControl;
+import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
+import com.google.android.exoplayer.MediaCodecTrackRenderer;
+import com.google.android.exoplayer.MediaCodecUtil;
+import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
+import com.google.android.exoplayer.SampleSource;
+import com.google.android.exoplayer.TrackRenderer;
+import com.google.android.exoplayer.chunk.ChunkSampleSource;
+import com.google.android.exoplayer.chunk.ChunkSource;
+import com.google.android.exoplayer.chunk.Format;
+import com.google.android.exoplayer.chunk.FormatEvaluator;
+import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
+import com.google.android.exoplayer.dash.DashChunkSource;
+import com.google.android.exoplayer.dash.mpd.AdaptationSet;
+import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
+import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
+import com.google.android.exoplayer.dash.mpd.Period;
+import com.google.android.exoplayer.dash.mpd.Representation;
+import com.google.android.exoplayer.hls.HlsChunkSource;
+import com.google.android.exoplayer.hls.HlsPlaylist;
+import com.google.android.exoplayer.hls.HlsPlaylistParser;
+import com.google.android.exoplayer.hls.HlsSampleSource;
+import com.google.android.exoplayer.text.TextRenderer;
+import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer;
+import com.google.android.exoplayer.upstream.BufferPool;
+import com.google.android.exoplayer.upstream.DataSource;
+import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
+import com.google.android.exoplayer.upstream.UriDataSource;
+import com.google.android.exoplayer.util.ManifestFetcher;
+import com.google.android.exoplayer.util.MimeTypes;
+import com.google.android.exoplayer.util.Util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A wrapper around {@link ExoPlayer} that provides a higher level interface. Designed for
+ * integration with {@link android.media.tv.TvInputService}.
+ */
+public class TvInputPlayer implements TextRenderer {
+    private static final String TAG = "TvInputPlayer";
+
+    public static final int SOURCE_TYPE_HTTP_PROGRESSIVE = 0;
+    public static final int SOURCE_TYPE_HLS = 1;
+    public static final int SOURCE_TYPE_MPEG_DASH = 2;
+
+    public static final int STATE_IDLE = ExoPlayer.STATE_IDLE;
+    public static final int STATE_PREPARING = ExoPlayer.STATE_PREPARING;
+    public static final int STATE_BUFFERING = ExoPlayer.STATE_BUFFERING;
+    public static final int STATE_READY = ExoPlayer.STATE_READY;
+    public static final int STATE_ENDED = ExoPlayer.STATE_ENDED;
+
+    private static final int RENDERER_COUNT = 3;
+    private static final int MIN_BUFFER_MS = 1000;
+    private static final int MIN_REBUFFER_MS = 5000;
+
+    private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
+    private static final int VIDEO_BUFFER_SEGMENTS = 200;
+    private static final int AUDIO_BUFFER_SEGMENTS = 60;
+    private static final int LIVE_EDGE_LATENCY_MS = 30000;
+
+    private static final int NO_TRACK_SELECTED = -1;
+
+    private final Handler mHandler;
+    private final ExoPlayer mPlayer;
+    private TrackRenderer mVideoRenderer;
+    private TrackRenderer mAudioRenderer;
+    private TrackRenderer mTextRenderer;
+    private final CopyOnWriteArrayList<Callback> mCallbacks;
+    private float mVolume;
+    private Surface mSurface;
+    private TvTrackInfo[][] mTvTracks = new TvTrackInfo[RENDERER_COUNT][];
+    private int[] mSelectedTvTracks = new int[RENDERER_COUNT];
+    private MultiTrackChunkSource[] mMultiTrackSources = new MultiTrackChunkSource[RENDERER_COUNT];
+
+    private final MediaCodecVideoTrackRenderer.EventListener mVideoRendererEventListener =
+            new MediaCodecVideoTrackRenderer.EventListener() {
+        @Override
+        public void onDroppedFrames(int count, long elapsed) {
+            // Do nothing.
+        }
+
+        @Override
+        public void onVideoSizeChanged(int width, int height, float pixelWidthHeightRatio) {
+            // Do nothing.
+        }
+
+        @Override
+        public void onDrawnToSurface(Surface surface) {
+            for(Callback callback : mCallbacks) {
+                callback.onDrawnToSurface(surface);
+            }
+        }
+
+        @Override
+        public void onDecoderInitializationError(
+                MediaCodecTrackRenderer.DecoderInitializationException e) {
+            for(Callback callback : mCallbacks) {
+                callback.onPlayerError(new ExoPlaybackException(e));
+            }
+        }
+
+        @Override
+        public void onCryptoError(MediaCodec.CryptoException e) {
+            for(Callback callback : mCallbacks) {
+                callback.onPlayerError(new ExoPlaybackException(e));
+            }
+        }
+    };
+
+    public TvInputPlayer() {
+        mHandler = new Handler();
+        for (int i = 0; i < RENDERER_COUNT; ++i) {
+            mTvTracks[i] = new TvTrackInfo[0];
+            mSelectedTvTracks[i] = NO_TRACK_SELECTED;
+        }
+        mCallbacks = new CopyOnWriteArrayList<>();
+        mPlayer = ExoPlayer.Factory.newInstance(RENDERER_COUNT, MIN_BUFFER_MS, MIN_REBUFFER_MS);
+        mPlayer.addListener(new ExoPlayer.Listener() {
+            @Override
+            public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+                for(Callback callback : mCallbacks) {
+                    callback.onPlayerStateChanged(playWhenReady, playbackState);
+                }
+            }
+
+            @Override
+            public void onPlayWhenReadyCommitted() {
+                for(Callback callback : mCallbacks) {
+                    callback.onPlayWhenReadyCommitted();
+                }
+            }
+
+            @Override
+            public void onPlayerError(ExoPlaybackException e) {
+                for(Callback callback : mCallbacks) {
+                    callback.onPlayerError(e);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onText(String text) {
+        for (Callback callback : mCallbacks) {
+            callback.onText(text);
+        }
+    }
+
+    public void prepare(Context context, final Uri uri, int sourceType) {
+        if (sourceType == SOURCE_TYPE_HTTP_PROGRESSIVE) {
+            FrameworkSampleSource sampleSource = new FrameworkSampleSource(context, uri, null, 2);
+            mAudioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
+            mVideoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
+                    MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mHandler,
+                    mVideoRendererEventListener, 50);
+            mTextRenderer = new DummyTrackRenderer();
+            prepareInternal();
+        } else if (sourceType == SOURCE_TYPE_HLS) {
+            final String userAgent = getUserAgent(context);
+            HlsPlaylistParser parser = new HlsPlaylistParser();
+            ManifestFetcher<HlsPlaylist> playlistFetcher =
+                    new ManifestFetcher<>(parser, uri.toString(), uri.toString(), userAgent);
+            playlistFetcher.singleLoad(mHandler.getLooper(),
+                    new ManifestFetcher.ManifestCallback<HlsPlaylist>() {
+                        @Override
+                        public void onManifest(String contentId, HlsPlaylist manifest) {
+                            DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
+                            DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
+                            HlsChunkSource chunkSource = new HlsChunkSource(dataSource,
+                                    uri.toString(), manifest, bandwidthMeter, null,
+                                    HlsChunkSource.ADAPTIVE_MODE_SPLICE);
+                            HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true,
+                                    2);
+                            mAudioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
+                            mVideoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
+                                    MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mHandler,
+                                    mVideoRendererEventListener, 50);
+                            mTextRenderer = new Eia608TrackRenderer(sampleSource,
+                                    TvInputPlayer.this, mHandler.getLooper());
+                            // TODO: Implement custom HLS source to get the internal track metadata.
+                            mTvTracks[TvTrackInfo.TYPE_SUBTITLE] = new TvTrackInfo[1];
+                            mTvTracks[TvTrackInfo.TYPE_SUBTITLE][0] =
+                                    new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "1")
+                                        .build();
+                            prepareInternal();
+                        }
+
+                        @Override
+                        public void onManifestError(String contentId, IOException e) {
+                            for (Callback callback : mCallbacks) {
+                                callback.onPlayerError(new ExoPlaybackException(e));
+                            }
+                        }
+                    });
+        } else if (sourceType == SOURCE_TYPE_MPEG_DASH) {
+            final String userAgent = getUserAgent(context);
+            MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
+            final ManifestFetcher<MediaPresentationDescription> manifestFetcher =
+                    new ManifestFetcher<>(parser, uri.toString(), uri.toString(), userAgent);
+            manifestFetcher.singleLoad(mHandler.getLooper(),
+                    new ManifestFetcher.ManifestCallback<MediaPresentationDescription>() {
+                @Override
+                public void onManifest(String contentId, MediaPresentationDescription manifest) {
+                    Period period = manifest.periods.get(0);
+                    LoadControl loadControl = new DefaultLoadControl(new BufferPool(
+                            BUFFER_SEGMENT_SIZE));
+                    DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
+
+                    // Determine which video representations we should use for playback.
+                    int maxDecodableFrameSize;
+                    try {
+                        maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
+                    } catch (MediaCodecUtil.DecoderQueryException e) {
+                        for (Callback callback : mCallbacks) {
+                            callback.onPlayerError(new ExoPlaybackException(e));
+                        }
+                        return;
+                    }
+
+                    int videoAdaptationSetIndex = period.getAdaptationSetIndex(
+                            AdaptationSet.TYPE_VIDEO);
+                    List<Representation> videoRepresentations =
+                            period.adaptationSets.get(videoAdaptationSetIndex).representations;
+                    ArrayList<Integer> videoRepresentationIndexList = new ArrayList<Integer>();
+                    for (int i = 0; i < videoRepresentations.size(); i++) {
+                        Format format = videoRepresentations.get(i).format;
+                        if (format.width * format.height > maxDecodableFrameSize) {
+                            // Filtering stream that device cannot play
+                        } else if (!format.mimeType.equals(MimeTypes.VIDEO_MP4)
+                                && !format.mimeType.equals(MimeTypes.VIDEO_WEBM)) {
+                            // Filtering unsupported mime type
+                        } else {
+                            videoRepresentationIndexList.add(i);
+                        }
+                    }
+
+                    // Build the video renderer.
+                    if (videoRepresentationIndexList.isEmpty()) {
+                        mVideoRenderer = new DummyTrackRenderer();
+                    } else {
+                        int[] videoRepresentationIndices = Util.toArray(
+                                videoRepresentationIndexList);
+                        DataSource videoDataSource = new UriDataSource(userAgent, bandwidthMeter);
+                        ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher,
+                                videoAdaptationSetIndex, videoRepresentationIndices,
+                                videoDataSource,
+                                new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter),
+                                LIVE_EDGE_LATENCY_MS);
+                        ChunkSampleSource videoSampleSource = new ChunkSampleSource(
+                                videoChunkSource, loadControl,
+                                VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
+                        mVideoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
+                                MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mHandler,
+                                mVideoRendererEventListener, 50);
+                    }
+
+                    // Build the audio chunk sources.
+                    int audioAdaptationSetIndex = period.getAdaptationSetIndex(
+                            AdaptationSet.TYPE_AUDIO);
+                    AdaptationSet audioAdaptationSet = period.adaptationSets.get(
+                            audioAdaptationSetIndex);
+                    List<ChunkSource> audioChunkSourceList = new ArrayList<ChunkSource>();
+                    List<TvTrackInfo> audioTrackList = new ArrayList<>();
+                    if (audioAdaptationSet != null) {
+                        DataSource audioDataSource = new UriDataSource(userAgent, bandwidthMeter);
+                        FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator();
+                        List<Representation> audioRepresentations =
+                                audioAdaptationSet.representations;
+                        for (int i = 0; i < audioRepresentations.size(); i++) {
+                            Format format = audioRepresentations.get(i).format;
+                            audioTrackList.add(new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO,
+                                    Integer.toString(i))
+                                    .setAudioChannelCount(format.numChannels)
+                                    .setAudioSampleRate(format.audioSamplingRate)
+                                    .setLanguage(format.language)
+                                    .build());
+                            audioChunkSourceList.add(new DashChunkSource(manifestFetcher,
+                                    audioAdaptationSetIndex, new int[] {i}, audioDataSource,
+                                    audioEvaluator, LIVE_EDGE_LATENCY_MS));
+                        }
+                    }
+
+                    // Build the audio renderer.
+                    final MultiTrackChunkSource audioChunkSource;
+                    if (audioChunkSourceList.isEmpty()) {
+                        mAudioRenderer = new DummyTrackRenderer();
+                    } else {
+                        audioChunkSource = new MultiTrackChunkSource(audioChunkSourceList);
+                        SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource,
+                                loadControl, AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
+                        mAudioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource);
+                        TvTrackInfo[] tracks = new TvTrackInfo[audioTrackList.size()];
+                        audioTrackList.toArray(tracks);
+                        mTvTracks[TvTrackInfo.TYPE_AUDIO] = tracks;
+                        mSelectedTvTracks[TvTrackInfo.TYPE_AUDIO] = 0;
+                        mMultiTrackSources[TvTrackInfo.TYPE_AUDIO] = audioChunkSource;
+                    }
+
+                    // Build the text renderer.
+                    mTextRenderer = new DummyTrackRenderer();
+
+                    prepareInternal();
+                }
+
+                @Override
+                public void onManifestError(String contentId, IOException e) {
+                    for (Callback callback : mCallbacks) {
+                        callback.onPlayerError(new ExoPlaybackException(e));
+                    }
+                }
+            });
+        } else {
+            throw new IllegalArgumentException("Unknown source type: " + sourceType);
+        }
+    }
+
+    public TvTrackInfo[] getTracks(int trackType) {
+        if (trackType < 0 || trackType >= mTvTracks.length) {
+            throw new IllegalArgumentException("Illegal track type: " + trackType);
+        }
+        return mTvTracks[trackType];
+    }
+
+    public String getSelectedTrack(int trackType) {
+        if (trackType < 0 || trackType >= mTvTracks.length) {
+            throw new IllegalArgumentException("Illegal track type: " + trackType);
+        }
+        if (mSelectedTvTracks[trackType] == NO_TRACK_SELECTED) {
+            return null;
+        }
+        return mTvTracks[trackType][mSelectedTvTracks[trackType]].getId();
+    }
+
+    public boolean selectTrack(int trackType, String trackId) {
+        if (trackType < 0 || trackType >= mTvTracks.length) {
+            return false;
+        }
+        if (trackId == null) {
+            mPlayer.setRendererEnabled(trackType, false);
+        } else {
+            int trackIndex = Integer.parseInt(trackId);
+            if (mMultiTrackSources[trackType] == null) {
+                mPlayer.setRendererEnabled(trackType, true);
+            } else {
+                boolean playWhenReady = mPlayer.getPlayWhenReady();
+                mPlayer.setPlayWhenReady(false);
+                mPlayer.setRendererEnabled(trackType, false);
+                mPlayer.sendMessage(mMultiTrackSources[trackType],
+                        MultiTrackChunkSource.MSG_SELECT_TRACK, trackIndex);
+                mPlayer.setRendererEnabled(trackType, true);
+                mPlayer.setPlayWhenReady(playWhenReady);
+            }
+        }
+        return true;
+    }
+
+    public void setPlayWhenReady(boolean playWhenReady) {
+        mPlayer.setPlayWhenReady(playWhenReady);
+    }
+
+    public void setVolume(float volume) {
+        mVolume = volume;
+        if (mPlayer != null && mAudioRenderer != null) {
+            mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
+                    volume);
+        }
+    }
+
+    public void setSurface(Surface surface) {
+        mSurface = surface;
+        if (mPlayer != null && mVideoRenderer != null) {
+            mPlayer.sendMessage(mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
+                    surface);
+        }
+    }
+
+    public void seekTo(long position) {
+        mPlayer.seekTo(position);
+    }
+
+    public void stop() {
+        mPlayer.stop();
+    }
+
+    public void release() {
+        mPlayer.release();
+    }
+
+    public void addCallback(Callback callback) {
+        mCallbacks.add(callback);
+    }
+
+    public void removeCallback(Callback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    private void prepareInternal() {
+        mPlayer.prepare(mAudioRenderer, mVideoRenderer, mTextRenderer);
+        mPlayer.sendMessage(mAudioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
+                mVolume);
+        mPlayer.sendMessage(mVideoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
+                mSurface);
+        // Disable text track by default.
+        mPlayer.setRendererEnabled(TvTrackInfo.TYPE_SUBTITLE, false);
+        for (Callback callback : mCallbacks) {
+            callback.onPrepared();
+        }
+    }
+
+    public static String getUserAgent(Context context) {
+        String versionName;
+        try {
+            String packageName = context.getPackageName();
+            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+            versionName = info.versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            versionName = "?";
+        }
+        return "SampleTvInput/" + versionName + " (Linux;Android " + Build.VERSION.RELEASE +
+                ") " + "ExoPlayerLib/" + ExoPlayerLibraryInfo.VERSION;
+    }
+
+    public interface Callback {
+        void onPrepared();
+        void onPlayerStateChanged(boolean playWhenReady, int state);
+        void onPlayWhenReadyCommitted();
+        void onPlayerError(ExoPlaybackException e);
+        void onDrawnToSurface(Surface surface);
+        void onText(String text);
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/ChannelXMLParser.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/ChannelXMLParser.java
new file mode 100644
index 0000000..b2561cf
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/ChannelXMLParser.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.rich;
+
+import com.example.android.sampletvinput.rich.RichTvInputService.ChannelInfo;
+import com.example.android.sampletvinput.rich.RichTvInputService.ProgramInfo;
+import com.example.android.sampletvinput.rich.RichTvInputService.TvInput;
+import com.example.android.sampletvinput.TvContractUtils;
+import com.example.android.sampletvinput.player.TvInputPlayer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A parser for the channels and programs feed used in {@link RichTvInputService}.
+ * <p>
+ * NOTE: The feed format here is just an example. Developers may invent any new formats and parse
+ * them in their own way.
+ * </p>
+ */
+public class ChannelXMLParser {
+    private static String TAG = "ChannelXmlParser";
+
+    private static final String TAG_TVINPUTS = "TvInputs";
+    private static final String TAG_CHANNELS = "Channels";
+    private static final String TAG_CHANNEL = "Channel";
+    private static final String TAG_PROGRAM = "Program";
+
+    private static final String ATTR_TVINPUT_DISPLAY_NAME = "display_name";
+    private static final String ATTR_TVINPUT_NAME = "name";
+    private static final String ATTR_TVINPUT_DESCRIPTION = "description";
+    private static final String ATTR_LOGO_THUMB_URL = "logo_thumb_url";
+    private static final String ATTR_LOGO_BACKGROUND_URL = "logo_background_url";
+
+    private static final String ATTR_DISPLAY_NUMBER = "display_number";
+    private static final String ATTR_DISPLAY_NAME = "display_name";
+    private static final String ATTR_VIDEO_WIDTH = "video_width";
+    private static final String ATTR_VIDEO_HEIGHT = "video_height";
+    private static final String ATTR_LOGO_URL = "logo_url";
+
+    private static final String ATTR_TITLE = "title";
+    private static final String ATTR_POSTER_ART_URL = "poster_art_url";
+    private static final String ATTR_DURATION_SEC = "duration_sec";
+    private static final String ATTR_VIDEO_URL = "video_url";
+    private static final String ATTR_VIDEO_TYPE = "video_type";
+    private static final String ATTR_DESCRIPTION = "description";
+    private static final String ATTR_CONTENT_RATING = "content_rating";
+
+    private static final String VALUE_VIDEO_TYPE_HTTP_PROGRESSIVE = "HTTP_PROGRESSIVE";
+    private static final String VALUE_VIDEO_TYPE_HLS = "HLS";
+    private static final String VALUE_VIDEO_TYPE_MPEG_DASH = "MPEG_DASH";
+
+    public static TvInput parseTvInput(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        parser.nextTag();
+        parser.require(XmlPullParser.START_TAG, null, TAG_TVINPUTS);
+        return parseTvInputDetail(parser);
+    }
+
+    private static TvInput parseTvInputDetail(XmlPullParser parser) {
+        String displayName = null;
+        String name = null;
+        String description = null;
+        String logoThumbUrl = null;
+        String logoBackgroundUrl = null;
+        for (int i = 0; i < parser.getAttributeCount(); ++i) {
+            String attr = parser.getAttributeName(i);
+            String value = parser.getAttributeValue(i);
+            if (ATTR_TVINPUT_DISPLAY_NAME.equals(attr)) {
+                displayName = value;
+            } else if (ATTR_TVINPUT_NAME.equals(attr)) {
+                name = value;
+            } else if (ATTR_TVINPUT_DESCRIPTION.equals(attr)) {
+                description = value;
+            } else if (ATTR_LOGO_THUMB_URL.equals(attr)) {
+                logoThumbUrl = value;
+            } else if (ATTR_LOGO_BACKGROUND_URL.equals(attr)) {
+                logoBackgroundUrl = value;
+            }
+        }
+        return new TvInput(displayName, name, description, logoThumbUrl, logoBackgroundUrl);
+    }
+
+    public static List<ChannelInfo> parseChannelXML(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        List<ChannelInfo> list = new ArrayList<ChannelInfo>();
+        parser.nextTag();
+
+        parser.require(XmlPullParser.START_TAG, null, TAG_CHANNELS);
+        while (parser.next() != XmlPullParser.END_DOCUMENT) {
+            if (parser.getEventType() == XmlPullParser.START_TAG
+                    && TAG_CHANNEL.equals(parser.getName())) {
+                list.add(parseChannel(parser));
+            }
+        }
+        return list;
+    }
+
+    private static ChannelInfo parseChannel(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String displayNumber = null;
+        String displayName = null;
+        int videoWidth = 0;
+        int videoHeight = 0;
+        String logoUrl = null;
+        StringBuilder hashString = new StringBuilder();
+        for (int i = 0; i < parser.getAttributeCount(); ++i) {
+            String attr = parser.getAttributeName(i);
+            String value = parser.getAttributeValue(i);
+            // Here we assume that other metadata except number and name may change when the feed
+            // is updated.
+            if (ATTR_DISPLAY_NUMBER.equals(attr)) {
+                displayNumber = value;
+                hashString.append(value).append(";");
+            } else if (ATTR_DISPLAY_NAME.equals(attr)) {
+                displayName = value;
+                hashString.append(value).append(";");
+            } else if (ATTR_VIDEO_WIDTH.equals(attr)) {
+                videoWidth = Integer.parseInt(value);
+            } else if (ATTR_VIDEO_HEIGHT.equals(attr)) {
+                videoHeight = Integer.parseInt(value);
+            } else if (ATTR_LOGO_URL.equals(attr)) {
+                logoUrl = value;
+            }
+        }
+        List<ProgramInfo> programs = new ArrayList<>();
+        while (parser.next() != XmlPullParser.END_DOCUMENT) {
+            if (parser.getEventType() == XmlPullParser.START_TAG) {
+                if (TAG_PROGRAM.equals(parser.getName())) {
+                    programs.add(parseProgram(parser));
+                }
+            } else if (TAG_CHANNEL.equals(parser.getName())
+                    && parser.getEventType() == XmlPullParser.END_TAG) {
+                break;
+            }
+        }
+        // Developers should assign original network ID in the right way not using the fake ID.
+        int fakeOriginalNetworkId = hashString.toString().hashCode();
+        return new ChannelInfo(displayNumber, displayName, logoUrl, fakeOriginalNetworkId, 0, 0,
+                videoWidth, videoHeight, programs);
+    }
+
+    private static ProgramInfo parseProgram(XmlPullParser parser) {
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+        String title = null;
+        long durationSec = 0;
+        String videoUrl = null;
+        int videoType = TvInputPlayer.SOURCE_TYPE_HTTP_PROGRESSIVE;
+        String description = null;
+        String posterArtUri = null;
+        String contentRatings = null;
+        for (int i = 0; i < parser.getAttributeCount(); ++i) {
+            String attr = parser.getAttributeName(i);
+            String value = parser.getAttributeValue(i);
+            if (ATTR_TITLE.equals(attr)) {
+                title = value;
+            } else if (ATTR_POSTER_ART_URL.equals(attr)) {
+                posterArtUri = value;
+            } else if (ATTR_DURATION_SEC.equals(attr)) {
+                durationSec = Integer.parseInt(value);
+            } else if (ATTR_VIDEO_URL.equals(attr)) {
+                videoUrl = value;
+            } else if (ATTR_VIDEO_TYPE.equals(attr)) {
+                if (VALUE_VIDEO_TYPE_HTTP_PROGRESSIVE.equals(value)) {
+                    videoType = TvInputPlayer.SOURCE_TYPE_HTTP_PROGRESSIVE;
+                } else if (VALUE_VIDEO_TYPE_HLS.equals(value)) {
+                    videoType = TvInputPlayer.SOURCE_TYPE_HLS;
+                } else if (VALUE_VIDEO_TYPE_MPEG_DASH.equals(value)) {
+                    videoType = TvInputPlayer.SOURCE_TYPE_MPEG_DASH;
+                }
+            } else if (ATTR_DESCRIPTION.equals(attr)) {
+                description = value;
+            } else if (ATTR_CONTENT_RATING.equals(attr)) {
+                contentRatings = value;
+            }
+        }
+        return new ProgramInfo(title, posterArtUri, description, durationSec,
+                TvContractUtils.stringToContentRatings(contentRatings), videoUrl, videoType, 0);
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/DetailsDescriptionPresenter.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/DetailsDescriptionPresenter.java
new file mode 100644
index 0000000..4ad5827
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/DetailsDescriptionPresenter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.rich;
+
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import com.example.android.sampletvinput.rich.RichTvInputService.TvInput;
+
+public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter {
+
+    @Override
+    protected void onBindDescription(ViewHolder viewHolder, Object item) {
+        TvInput tvInput = (TvInput) item;
+
+        if (tvInput != null) {
+            viewHolder.getTitle().setText(tvInput.displayName);
+            viewHolder.getSubtitle().setText("By " + tvInput.name);
+        }
+    }
+}
diff --git a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/PicassoBackgroundManagerTarget.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/PicassoBackgroundManagerTarget.java
similarity index 74%
rename from prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/PicassoBackgroundManagerTarget.java
rename to prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/PicassoBackgroundManagerTarget.java
index b8fa117..18eb79a 100644
--- a/prebuilts/androidtv/leanback/app/src/main/java/com/example/android/leanback/PicassoBackgroundManagerTarget.java
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/PicassoBackgroundManagerTarget.java
@@ -1,22 +1,25 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright 2015 The Android Open Source Project
  *
- * 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
+ * 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
+ *      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.
+ * 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.example.android.leanback;
+package com.example.android.sampletvinput.rich;
 
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.support.v17.leanback.app.BackgroundManager;
+
 import com.squareup.picasso.Picasso;
 import com.squareup.picasso.Target;
 
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichFeedUtil.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichFeedUtil.java
new file mode 100644
index 0000000..3d30a84
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichFeedUtil.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.rich;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+import android.util.Xml;
+
+import com.example.android.sampletvinput.R;
+import com.example.android.sampletvinput.rich.RichTvInputService.ChannelInfo;
+import com.example.android.sampletvinput.rich.RichTvInputService.TvInput;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.List;
+
+/**
+ * Static helper methods for fetching the channel feed.
+ */
+public class RichFeedUtil {
+    private static final String TAG = "RichFeedUtil";
+    private static List<ChannelInfo> sSampleChannels;
+    private static TvInput sTvInput;
+
+    private static final boolean USE_LOCAL_XML_FEED = false;
+
+    private RichFeedUtil() {
+    }
+
+    /**
+     * Returns the channel metadata for {@link RichTvInputService}. Note that this will block until
+     * the channel feed has been retrieved.
+     */
+    public static List<ChannelInfo> getRichChannels(Context context) {
+        Uri catalogUri =
+                USE_LOCAL_XML_FEED ?
+                        Uri.parse("android.resource://" + context.getPackageName() + "/"
+                                + R.raw.rich_tv_inputs_tif)
+                        : Uri.parse(context.getResources().getString(R.string.rich_input_feed_url));
+        if (sSampleChannels != null) {
+            return sSampleChannels;
+        }
+
+        InputStream inputStream = null;
+        try {
+            if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(catalogUri.getScheme())) {
+                inputStream = context.getContentResolver().openInputStream(catalogUri);
+            } else {
+                URLConnection urlConnection = new URL(catalogUri.toString()).openConnection();
+                inputStream = urlConnection.getInputStream();
+            }
+
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
+            parser.setInput(inputStream, null);
+            sTvInput = ChannelXMLParser.parseTvInput(parser);
+            sSampleChannels = ChannelXMLParser.parseChannelXML(parser);
+        } catch (IOException e) {
+            Log.e(TAG, "Error in fetching " + catalogUri, e);
+        } catch (XmlPullParserException e) {
+            Log.e(TAG, "Error in parsing " + catalogUri, e);
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    // Do nothing.
+                }
+            }
+        }
+        return sSampleChannels;
+    }
+
+    public static TvInput getTvInput(Context context) {
+        if (sTvInput == null) {
+            getRichChannels(context);
+        }
+        return sTvInput;
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichSettingsFragment.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichSettingsFragment.java
new file mode 100644
index 0000000..2d9ff66
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichSettingsFragment.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.rich;
+
+import android.graphics.Color;
+import android.media.tv.TvInputInfo;
+import android.os.Bundle;
+import android.support.v17.leanback.app.BrowseFragment;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.android.sampletvinput.R;
+import com.example.android.sampletvinput.TvContractUtils;
+
+/**
+ * Fragment which shows a sample UI for configuring {@link RichTvInputService}.
+ */
+public class RichSettingsFragment extends BrowseFragment {
+    private static final String TAG = "SettingsFragment";
+
+    private static final int GRID_ITEM_WIDTH = 500;
+    private static final int GRID_ITEM_HEIGHT = 200;
+
+    private ArrayObjectAdapter mRowsAdapter;
+
+    // Container Activity must implement this interface
+    public interface SettingsClickedListener {
+        public void onSettingsClicked();
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        setupUIElements();
+
+        loadRows();
+
+        setupEventListeners();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    private void loadRows() {
+
+        String inputId = getActivity().getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
+        String serviceName = TvContractUtils.getServiceNameFromInputId(getActivity(), inputId);
+
+        if (!TextUtils.isEmpty(serviceName)) {
+            String[] a = serviceName.split("\\.");
+            serviceName = a[a.length - 1];
+        }
+
+        GridItemPresenter mGridPresenter = new GridItemPresenter();
+
+        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+        HeaderItem gridHeader = new HeaderItem(0, "SETTINGS", null);
+
+        ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
+        gridRowAdapter.add(serviceName + " Settings");
+        if (RichTvInputService.class.getName().contains(serviceName) ) {
+            gridRowAdapter.add("Server URLs");
+            gridRowAdapter.add("Update Frequency");
+        }
+        mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));
+
+        setAdapter(mRowsAdapter);
+    }
+
+    private void setupUIElements() {
+        setTitle(getString(R.string.rich_settings_browse_title));
+        setHeadersState(HEADERS_DISABLED);
+        setHeadersTransitionOnBackEnabled(true);
+
+        // set fastLane (or headers) background color
+        setBrandColor(getResources().getColor(R.color.fastlane_background));
+        // set search icon color
+        setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
+    }
+
+    private void setupEventListeners() {
+        setOnItemViewClickedListener(new ItemViewClickedListener());
+        setOnItemViewSelectedListener(new ItemViewSelectedListener());
+    }
+
+    private final class ItemViewClickedListener implements OnItemViewClickedListener {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+
+            if (item instanceof String) {
+                Toast.makeText(getActivity(), ((String) item), Toast.LENGTH_SHORT).show();
+            }
+        }
+    }
+
+
+    private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                                   RowPresenter.ViewHolder rowViewHolder, Row row) {
+        }
+    }
+
+    private class GridItemPresenter extends Presenter {
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            TextView view = new TextView(parent.getContext());
+            view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
+            view.setFocusable(true);
+            view.setFocusableInTouchMode(true);
+            view.setBackgroundColor(getResources().getColor(R.color.default_background));
+            view.setTextColor(Color.WHITE);
+            view.setGravity(Gravity.CENTER);
+            return new ViewHolder(view);
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+            ((TextView) viewHolder.view).setText((String) item);
+        }
+
+        @Override
+        public void onUnbindViewHolder(ViewHolder viewHolder) {
+        }
+    }
+
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichSetupFragment.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichSetupFragment.java
new file mode 100644
index 0000000..a306e5e
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichSetupFragment.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.rich;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncStatusObserver;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v17.leanback.app.BackgroundManager;
+import android.support.v17.leanback.app.DetailsFragment;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.DetailsOverviewRow;
+import android.support.v17.leanback.widget.DetailsOverviewRowPresenter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.example.android.sampletvinput.R;
+import com.example.android.sampletvinput.TvContractUtils;
+import com.example.android.sampletvinput.rich.RichTvInputService.ChannelInfo;
+import com.example.android.sampletvinput.rich.RichTvInputService.TvInput;
+import com.example.android.sampletvinput.syncadapter.DummyAccountService;
+import com.example.android.sampletvinput.syncadapter.SyncUtils;
+import com.squareup.picasso.Picasso;
+import com.squareup.picasso.Target;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Fragment which shows a sample UI for registering channels and setting up SyncAdapter to
+ * provide program information in the background.
+ */
+public class RichSetupFragment extends DetailsFragment {
+    private static final String TAG = "SetupFragment";
+
+    private static final int ACTION_ADD_CHANNELS = 1;
+    private static final int ACTION_CANCEL = 2;
+    private static final int ACTION_IN_PROGRESS = 3;
+
+    private static final int DETAIL_THUMB_WIDTH = 274;
+    private static final int DETAIL_THUMB_HEIGHT = 274;
+
+    private Drawable mDefaultBackground;
+    private Target mBackgroundTarget;
+    private DisplayMetrics mMetrics;
+    private DetailsOverviewRowPresenter mDorPresenter;
+
+    private List<ChannelInfo> mChannels = null;
+    private TvInput mTvInput = null;
+    private String mInputId = null;
+
+    private Action mAddChannelAction;
+    private Action mCancelAction;
+    private Action mInProgressAction;
+    private ArrayObjectAdapter mAdapter;
+    private Object mSyncObserverHandle;
+    private boolean mSyncRequested;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.d(TAG, "onCreate SetupFragment");
+        super.onCreate(savedInstanceState);
+
+        mInputId = getActivity().getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
+        new SetupRowTask().execute();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mSyncObserverHandle != null) {
+            ContentResolver.removeStatusChangeListener(mSyncObserverHandle);
+            mSyncObserverHandle = null;
+        }
+    }
+
+    protected void updateBackground(String uri) {
+        BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
+        backgroundManager.attach(getActivity().getWindow());
+        mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
+
+        mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
+
+        mMetrics = new DisplayMetrics();
+        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
+
+        Picasso.with(getActivity())
+                .load(uri)
+                .resize(mMetrics.widthPixels, mMetrics.heightPixels)
+                .error(mDefaultBackground)
+                .into(mBackgroundTarget);
+    }
+
+    private class SetupRowTask extends AsyncTask<Uri, String, Bitmap> {
+
+        @Override
+        protected Bitmap doInBackground(Uri... params) {
+            mChannels = RichFeedUtil.getRichChannels(getActivity());
+            mTvInput = RichFeedUtil.getTvInput(getActivity());
+            if (mTvInput != null) {
+                return getPoster();
+            } else {
+                publishProgress(getResources().getString(R.string.feed_error_message));
+                return null;
+            }
+        }
+
+        @Override
+        protected void onPostExecute(Bitmap poster) {
+            if (poster != null) {
+                addSetupChannelDetailedRow(poster);
+                updateBackground(mTvInput.logoBackgroundUrl);
+            }
+        }
+
+        protected void onProgressUpdate(String... progress) {
+            Toast.makeText(getActivity(), progress[0], Toast.LENGTH_SHORT).show();
+        }
+
+        private void addSetupChannelDetailedRow(Bitmap poster) {
+            mDorPresenter = new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
+            mDorPresenter.setSharedElementEnterTransition(getActivity(), "SetUpFragment");
+
+            mAddChannelAction = new Action(ACTION_ADD_CHANNELS, getResources().getString(
+                    R.string.rich_setup_add_channel));
+            mCancelAction = new Action(ACTION_CANCEL, getResources().getString(
+                    R.string.rich_setup_cancel));
+            mInProgressAction = new Action(ACTION_IN_PROGRESS, getResources().getString(
+                    R.string.rich_setup_in_progress));
+
+            DetailsOverviewRow row = new DetailsOverviewRow(mTvInput);
+            row.setImageBitmap(getActivity(), poster);
+
+            row.addAction(mAddChannelAction);
+            row.addAction(mCancelAction);
+
+            ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
+            // set detail background and style
+            mDorPresenter.setBackgroundColor(getResources().getColor(R.color.detail_background));
+            mDorPresenter.setStyleLarge(true);
+
+            mDorPresenter.setOnActionClickedListener(new OnActionClickedListener() {
+                @Override
+                public void onActionClicked(Action action) {
+                    if (action.getId() == ACTION_ADD_CHANNELS) {
+                        setupChannels(mInputId);
+                    } else if (action.getId() == ACTION_CANCEL) {
+                        getActivity().finish();
+                    }
+                }
+            });
+
+            presenterSelector.addClassPresenter(DetailsOverviewRow.class, mDorPresenter);
+            presenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
+            mAdapter = new ArrayObjectAdapter(presenterSelector);
+            mAdapter.add(row);
+
+            setAdapter(mAdapter);
+        }
+
+        private Bitmap getPoster() {
+            try {
+                Bitmap poster = Picasso.with(getActivity())
+                        .load(mTvInput.logoBackgroundUrl)
+                        .resize(convertDpToPixel(getActivity()
+                                        .getApplicationContext(), DETAIL_THUMB_WIDTH),
+                                convertDpToPixel(getActivity()
+                                        .getApplicationContext(), DETAIL_THUMB_HEIGHT))
+                        .centerCrop()
+                        .get();
+                return poster;
+            } catch (IOException e) {
+                Log.e(TAG, e.toString());
+                return null;
+            }
+        }
+    }
+
+    private void setupChannels(String inputId) {
+        if (mChannels == null) {
+            return;
+        }
+        TvContractUtils.updateChannels(getActivity(), inputId, mChannels);
+        SyncUtils.setUpPeriodicSync(getActivity(), inputId);
+        SyncUtils.requestSync(inputId);
+        mSyncRequested = true;
+        // Watch for sync state changes
+        if (mSyncObserverHandle == null) {
+            final int mask = ContentResolver.SYNC_OBSERVER_TYPE_PENDING |
+                    ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
+            mSyncObserverHandle = ContentResolver.addStatusChangeListener(mask,
+                    mSyncStatusObserver);
+        }
+    }
+
+    private int convertDpToPixel(Context ctx, int dp) {
+        float density = ctx.getResources().getDisplayMetrics().density;
+        return Math.round((float) dp * density);
+    }
+
+    private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() {
+        private boolean mSyncServiceStarted;
+        private boolean mFinished;
+
+        @Override
+        public void onStatusChanged(int which) {
+            getActivity().runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (mFinished) {
+                        return;
+                    }
+                    Account account = DummyAccountService.getAccount(SyncUtils.ACCOUNT_TYPE);
+                    boolean syncActive = ContentResolver.isSyncActive(account,
+                            TvContract.AUTHORITY);
+                    boolean syncPending = ContentResolver.isSyncPending(account,
+                            TvContract.AUTHORITY);
+                    boolean syncServiceInProgress = syncActive || syncPending;
+                    if (mSyncRequested && mSyncServiceStarted && !syncServiceInProgress) {
+                        getActivity().setResult(Activity.RESULT_OK);
+                        getActivity().finish();
+                        mFinished = true;
+                    }
+                    if (!mSyncServiceStarted && syncServiceInProgress) {
+                        mSyncServiceStarted = syncServiceInProgress;
+                        DetailsOverviewRow detailRow = (DetailsOverviewRow) mAdapter.get(0);
+                        detailRow.removeAction(mAddChannelAction);
+                        detailRow.addAction(0, mInProgressAction);
+                        mAdapter.notifyArrayItemRangeChanged(0, 1);
+                    }
+                }
+            });
+        }
+    };
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichTvInputService.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichTvInputService.java
new file mode 100644
index 0000000..5fbcf94
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichTvInputService.java
@@ -0,0 +1,510 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.rich;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Point;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputService;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.CaptioningManager;
+
+import com.example.android.sampletvinput.R;
+import com.example.android.sampletvinput.TvContractUtils;
+import com.example.android.sampletvinput.player.TvInputPlayer;
+import com.example.android.sampletvinput.syncadapter.SyncUtils;
+import com.google.android.exoplayer.ExoPlaybackException;
+import com.google.android.exoplayer.ExoPlayer;
+import com.google.android.exoplayer.text.CaptionStyleCompat;
+import com.google.android.exoplayer.text.SubtitleView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * TvInputService which provides a full implementation of EPG, subtitles, multi-audio,
+ * parental controls, and overlay view.
+ */
+public class RichTvInputService extends TvInputService {
+    private static final String TAG = "RichTvInputService";
+
+    private HandlerThread mHandlerThread;
+    private Handler mDbHandler;
+
+    private List<RichTvInputSessionImpl> mSessions;
+    private CaptioningManager mCaptioningManager;
+
+    private final BroadcastReceiver mParentalControlsBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (mSessions != null) {
+                for (RichTvInputSessionImpl session : mSessions) {
+                    session.checkContentBlockNeeded();
+                }
+            }
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mHandlerThread = new HandlerThread(getClass().getSimpleName());
+        mHandlerThread.start();
+        mDbHandler = new Handler(mHandlerThread.getLooper());
+        mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
+
+        setTheme(android.R.style.Theme_Holo_Light_NoActionBar);
+
+        mSessions = new ArrayList<RichTvInputSessionImpl>();
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED);
+        intentFilter.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
+        registerReceiver(mParentalControlsBroadcastReceiver, intentFilter);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mParentalControlsBroadcastReceiver);
+        mHandlerThread.quit();
+        mHandlerThread = null;
+        mDbHandler = null;
+    }
+
+    @Override
+    public final Session onCreateSession(String inputId) {
+        RichTvInputSessionImpl session = new RichTvInputSessionImpl(this, inputId);
+        session.setOverlayViewEnabled(true);
+        mSessions.add(session);
+        return session;
+    }
+
+    class RichTvInputSessionImpl extends TvInputService.Session implements Handler.Callback {
+        private static final int MSG_PLAY_PROGRAM = 1000;
+        private static final float CAPTION_LINE_HEIGHT_RATIO = 0.0533f;
+
+        private final Context mContext;
+        private final String mInputId;
+        private TvInputManager mTvInputManager;
+        protected TvInputPlayer mPlayer;
+        private Surface mSurface;
+        private float mVolume;
+        private boolean mCaptionEnabled;
+        private PlaybackInfo mCurrentPlaybackInfo;
+        private TvContentRating mLastBlockedRating;
+        private TvContentRating mCurrentContentRating;
+        private String mSelectedSubtitleTrackId;
+        private SubtitleView mSubtitleView;
+        private boolean mEpgSyncRequested;
+        private final Set<TvContentRating> mUnblockedRatingSet = new HashSet<>();
+        private Handler mHandler;
+
+        private final TvInputPlayer.Callback mPlayerCallback = new TvInputPlayer.Callback() {
+            private boolean mFirstFrameDrawn;
+            @Override
+            public void onPrepared() {
+                mFirstFrameDrawn = false;
+                List<TvTrackInfo> tracks = new ArrayList<>();
+                Collections.addAll(tracks, mPlayer.getTracks(TvTrackInfo.TYPE_AUDIO));
+                Collections.addAll(tracks, mPlayer.getTracks(TvTrackInfo.TYPE_VIDEO));
+                Collections.addAll(tracks, mPlayer.getTracks(TvTrackInfo.TYPE_SUBTITLE));
+
+                notifyTracksChanged(tracks);
+                notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, mPlayer.getSelectedTrack(
+                        TvTrackInfo.TYPE_AUDIO));
+                notifyTrackSelected(TvTrackInfo.TYPE_VIDEO, mPlayer.getSelectedTrack(
+                        TvTrackInfo.TYPE_VIDEO));
+                notifyTrackSelected(TvTrackInfo.TYPE_SUBTITLE, mPlayer.getSelectedTrack(
+                        TvTrackInfo.TYPE_SUBTITLE));
+            }
+
+            @Override
+            public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
+                if (playWhenReady == true && playbackState == ExoPlayer.STATE_BUFFERING) {
+                    if (mFirstFrameDrawn) {
+                        notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING);
+                    }
+                } else if (playWhenReady == true && playbackState == ExoPlayer.STATE_READY) {
+                    notifyVideoAvailable();
+                }
+            }
+
+            @Override
+            public void onPlayWhenReadyCommitted() {
+                // Do nothing.
+            }
+
+            @Override
+            public void onPlayerError(ExoPlaybackException e) {
+                // Do nothing.
+            }
+
+            @Override
+            public void onDrawnToSurface(Surface surface) {
+                mFirstFrameDrawn = true;
+                notifyVideoAvailable();
+            }
+
+            @Override
+            public void onText(String text) {
+                if (mSubtitleView != null) {
+                    if (TextUtils.isEmpty(text)) {
+                        mSubtitleView.setVisibility(View.INVISIBLE);
+                    } else {
+                        mSubtitleView.setVisibility(View.VISIBLE);
+                        mSubtitleView.setText(text);
+                    }
+                }
+            }
+        };
+
+        private PlayCurrentProgramRunnable mPlayCurrentProgramRunnable;
+
+        protected RichTvInputSessionImpl(Context context, String inputId) {
+            super(context);
+
+            mContext = context;
+            mInputId = inputId;
+            mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE);
+            mLastBlockedRating = null;
+            mCaptionEnabled = mCaptioningManager.isEnabled();
+            mHandler = new Handler(this);
+        }
+
+        @Override
+        public boolean handleMessage(Message msg) {
+            if (msg.what == MSG_PLAY_PROGRAM) {
+                playProgram((PlaybackInfo) msg.obj);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void onRelease() {
+            if (mDbHandler != null) {
+                mDbHandler.removeCallbacks(mPlayCurrentProgramRunnable);
+            }
+            releasePlayer();
+            mSessions.remove(this);
+        }
+
+        @Override
+        public View onCreateOverlayView() {
+            LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
+            View view = inflater.inflate(R.layout.overlayview, null);
+            mSubtitleView = (SubtitleView) view.findViewById(R.id.subtitles);
+
+            // Configure the subtitle view.
+            CaptionStyleCompat captionStyle;
+            float captionTextSize = getCaptionFontSize();
+            captionStyle = CaptionStyleCompat.createFromCaptionStyle(
+                    mCaptioningManager.getUserStyle());
+            captionTextSize *= mCaptioningManager.getFontScale();
+            mSubtitleView.setStyle(captionStyle);
+            mSubtitleView.setTextSize(captionTextSize);
+            return view;
+        }
+
+        @Override
+        public boolean onSetSurface(Surface surface) {
+            if (mPlayer != null) {
+                mPlayer.setSurface(surface);
+            }
+            mSurface = surface;
+            return true;
+        }
+
+        @Override
+        public void onSetStreamVolume(float volume) {
+            if (mPlayer != null) {
+                mPlayer.setVolume(volume);
+            }
+            mVolume = volume;
+        }
+
+        private boolean playProgram(PlaybackInfo info) {
+            releasePlayer();
+
+            mCurrentPlaybackInfo = info;
+            mCurrentContentRating = info.contentRatings.length > 0 ?
+                    info.contentRatings[0] : null;
+            mPlayer = new TvInputPlayer();
+            mPlayer.addCallback(mPlayerCallback);
+            mPlayer.prepare(RichTvInputService.this, Uri.parse(info.videoUrl), info.videoType);
+            mPlayer.setSurface(mSurface);
+            mPlayer.setVolume(mVolume);
+
+            long nowMs = System.currentTimeMillis();
+            if (info.videoType != TvInputPlayer.SOURCE_TYPE_HTTP_PROGRESSIVE) {
+                // If source type is HTTTP progressive, just play from the beginning.
+                // TODO: Seeking on http progressive source takes too long.
+                //       Enhance ExoPlayer/MediaExtractor and remove the condition above.
+                int seekPosMs = (int) (nowMs - info.startTimeMs);
+                if (seekPosMs > 0) {
+                    mPlayer.seekTo(seekPosMs);
+                }
+            }
+            mPlayer.setPlayWhenReady(true);
+
+            checkContentBlockNeeded();
+            mDbHandler.postDelayed(mPlayCurrentProgramRunnable, info.endTimeMs - nowMs + 1000);
+            return true;
+        }
+
+        @Override
+        public boolean onTune(Uri channelUri) {
+            if (mSubtitleView != null) {
+                mSubtitleView.setVisibility(View.INVISIBLE);
+            }
+            notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
+            mUnblockedRatingSet.clear();
+
+            mDbHandler.removeCallbacks(mPlayCurrentProgramRunnable);
+            mPlayCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri);
+            mDbHandler.post(mPlayCurrentProgramRunnable);
+            return true;
+        }
+
+        @Override
+        public void onSetCaptionEnabled(boolean enabled) {
+            mCaptionEnabled = enabled;
+            if (mPlayer != null) {
+                if (enabled) {
+                    if (mSelectedSubtitleTrackId != null && mPlayer != null) {
+                        mPlayer.selectTrack(TvTrackInfo.TYPE_SUBTITLE, mSelectedSubtitleTrackId);
+                    }
+                } else {
+                    mPlayer.selectTrack(TvTrackInfo.TYPE_SUBTITLE, null);
+                }
+            }
+        }
+
+        @Override
+        public boolean onSelectTrack(int type, String trackId) {
+            if (mPlayer != null) {
+                if (type == TvTrackInfo.TYPE_SUBTITLE) {
+                    if (!mCaptionEnabled && trackId != null) {
+                        return false;
+                    }
+                    mSelectedSubtitleTrackId = trackId;
+                    if (trackId == null) {
+                        mSubtitleView.setVisibility(View.INVISIBLE);
+                    }
+                }
+                if (mPlayer.selectTrack(type, trackId)) {
+                    notifyTrackSelected(type, trackId);
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void onUnblockContent(TvContentRating rating) {
+            if (rating != null) {
+                unblockContent(rating);
+            }
+        }
+
+        private void releasePlayer() {
+            if (mPlayer != null) {
+                mPlayer.removeCallback(mPlayerCallback);
+                mPlayer.setSurface(null);
+                mPlayer.stop();
+                mPlayer.release();
+                mPlayer = null;
+            }
+        }
+
+        private void checkContentBlockNeeded() {
+            if (mCurrentContentRating == null || !mTvInputManager.isParentalControlsEnabled()
+                    || !mTvInputManager.isRatingBlocked(mCurrentContentRating)
+                    || mUnblockedRatingSet.contains(mCurrentContentRating)) {
+                // Content rating is changed so we don't need to block anymore.
+                // Unblock content here explicitly to resume playback.
+                unblockContent(null);
+                return;
+            }
+
+            mLastBlockedRating = mCurrentContentRating;
+            if (mPlayer != null) {
+                // Children restricted content might be blocked by TV app as well,
+                // but TIS should do its best not to show any single frame of blocked content.
+                releasePlayer();
+            }
+
+            notifyContentBlocked(mCurrentContentRating);
+        }
+
+        private void unblockContent(TvContentRating rating) {
+            // TIS should unblock content only if unblock request is legitimate.
+            if (rating == null || mLastBlockedRating == null
+                    || (mLastBlockedRating != null && rating.equals(mLastBlockedRating))) {
+                mLastBlockedRating = null;
+                if (rating != null) {
+                    mUnblockedRatingSet.add(rating);
+                }
+                if (mPlayer == null && mCurrentPlaybackInfo != null) {
+                    playProgram(mCurrentPlaybackInfo);
+                }
+                notifyContentAllowed();
+            }
+        }
+
+        private float getCaptionFontSize() {
+            Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
+                    .getDefaultDisplay();
+            Point displaySize = new Point();
+            display.getSize(displaySize);
+            return Math.max(getResources().getDimension(R.dimen.subtitle_minimum_font_size),
+                    CAPTION_LINE_HEIGHT_RATIO * Math.min(displaySize.x, displaySize.y));
+        }
+
+        private class PlayCurrentProgramRunnable implements Runnable {
+            private static final int RETRY_DELAY_MS = 2000;
+            private final Uri mChannelUri;
+
+            public PlayCurrentProgramRunnable(Uri channelUri) {
+                mChannelUri = channelUri;
+            }
+
+            @Override
+            public void run() {
+                long nowMs = System.currentTimeMillis();
+                List<PlaybackInfo> programs = TvContractUtils.getProgramPlaybackInfo(
+                        mContext.getContentResolver(), mChannelUri, nowMs, nowMs + 1, 1);
+                if (!programs.isEmpty()) {
+                    mHandler.removeMessages(MSG_PLAY_PROGRAM);
+                    mHandler.obtainMessage(MSG_PLAY_PROGRAM, programs.get(0)).sendToTarget();
+                } else {
+                    Log.w(TAG, "Failed to get program info for " + mChannelUri + ". Retry in " +
+                            RETRY_DELAY_MS + "ms.");
+                    mDbHandler.postDelayed(mPlayCurrentProgramRunnable, RETRY_DELAY_MS);
+                    if (!mEpgSyncRequested) {
+                        SyncUtils.requestSync(mInputId);
+                        mEpgSyncRequested = true;
+                    }
+                }
+            }
+        }
+    }
+
+    public static final class ChannelInfo {
+        public final String number;
+        public final String name;
+        public final String logoUrl;
+        public final int originalNetworkId;
+        public final int transportStreamId;
+        public final int serviceId;
+        public final int videoWidth;
+        public final int videoHeight;
+        public final List<ProgramInfo> programs;
+
+        public ChannelInfo(String number, String name, String logoUrl, int originalNetworkId,
+                           int transportStreamId, int serviceId, int videoWidth, int videoHeight,
+                           List<ProgramInfo> programs) {
+            this.number = number;
+            this.name = name;
+            this.logoUrl = logoUrl;
+            this.originalNetworkId = originalNetworkId;
+            this.transportStreamId = transportStreamId;
+            this.serviceId = serviceId;
+            this.videoWidth = videoWidth;
+            this.videoHeight = videoHeight;
+            this.programs = programs;
+        }
+    }
+
+    public static final class ProgramInfo {
+        public final String title;
+        public final String posterArtUri;
+        public final String description;
+        public final long durationSec;
+        public final String videoUrl;
+        public final int videoType;
+        public final int resourceId;
+        public final TvContentRating[] contentRatings;
+
+        public ProgramInfo(String title, String posterArtUri, String description, long durationSec,
+                           TvContentRating[] contentRatings, String videoUrl, int videoType, int resourceId) {
+            this.title = title;
+            this.posterArtUri = posterArtUri;
+            this.description = description;
+            this.durationSec = durationSec;
+            this.contentRatings = contentRatings;
+            this.videoUrl = videoUrl;
+            this.videoType = videoType;
+            this.resourceId = resourceId;
+        }
+    }
+
+    public static final class PlaybackInfo {
+        public final long startTimeMs;
+        public final long endTimeMs;
+        public final String videoUrl;
+        public final int videoType;
+        public final TvContentRating[] contentRatings;
+
+        public PlaybackInfo(long startTimeMs, long endTimeMs, String videoUrl, int videoType,
+                            TvContentRating[] contentRatings) {
+            this.startTimeMs = startTimeMs;
+            this.endTimeMs = endTimeMs;
+            this.contentRatings = contentRatings;
+            this.videoUrl = videoUrl;
+            this.videoType = videoType;
+        }
+    }
+
+    public static final class TvInput {
+        public final String displayName;
+        public final String name;
+        public final String description;
+        public final String logoThumbUrl;
+        public final String logoBackgroundUrl;
+
+        public TvInput(String displayName,
+                       String name,
+                       String description,
+                       String logoThumbUrl,
+                       String logoBackgroundUrl) {
+            this.displayName = displayName;
+            this.name = name;
+            this.description = description;
+            this.logoThumbUrl = logoThumbUrl;
+            this.logoBackgroundUrl = logoBackgroundUrl;
+        }
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichTvInputSettingsActivity.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichTvInputSettingsActivity.java
new file mode 100644
index 0000000..6b576e7
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichTvInputSettingsActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.rich;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.example.android.sampletvinput.R;
+
+/**
+ * The settings activity for demonstrating {@link RichTvInputService}.
+ */
+public class RichTvInputSettingsActivity extends Activity implements
+        RichSettingsFragment.SettingsClickedListener {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.rich_settings);
+    }
+
+    /**
+     * Implementation of OnSettingsClickedListener
+     */
+    public void onSettingsClicked() {
+        setResult(Activity.RESULT_OK);
+    }
+
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichTvInputSetupActivity.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichTvInputSetupActivity.java
new file mode 100644
index 0000000..d5c56f1
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/rich/RichTvInputSetupActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.rich;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.example.android.sampletvinput.R;
+
+/**
+ * The setup activity for demonstrating {@link RichTvInputService}.
+ */
+public class RichTvInputSetupActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.rich_setup);
+    }
+
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/simple/SimpleTvInputService.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/simple/SimpleTvInputService.java
new file mode 100644
index 0000000..a442481
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/simple/SimpleTvInputService.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.simple;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.media.MediaPlayer;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputService;
+import android.net.Uri;
+import android.view.Surface;
+
+import com.example.android.sampletvinput.R;
+
+import java.io.IOException;
+
+/**
+ * Simple TV input service which provides two sample channels.
+ * <p>
+ * NOTE: The purpose of this sample is to provide a really simple TV input sample to the developers
+ * so that they can understand the core APIs and when/how/where they should use them with ease.
+ * This means lots of features including EPG, subtitles, multi-audio, parental controls, and overlay
+ * view are missing here. So, to check the example codes for them, see {@link RichTvInputService}.
+ * </p>
+ */
+public class SimpleTvInputService extends TvInputService {
+    @Override
+    public Session onCreateSession(String inputId) {
+        return new SimpleSessionImpl(this);
+    }
+
+    /**
+     * Simple session implementation which plays local videos on the application's tune request.
+     */
+    private class SimpleSessionImpl extends TvInputService.Session {
+        private static final int RESOURCE_1 =
+                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz;
+        private static final int RESOURCE_2 =
+                R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
+
+        private MediaPlayer mPlayer;
+        private float mVolume;
+        private Surface mSurface;
+
+        SimpleSessionImpl(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onRelease() {
+            mPlayer.release();
+        }
+
+        @Override
+        public boolean onSetSurface(Surface surface) {
+            if (mPlayer != null) {
+                mPlayer.setSurface(surface);
+            }
+            mSurface = surface;
+            return true;
+        }
+
+        @Override
+        public void onSetStreamVolume(float volume) {
+            if (mPlayer != null) {
+                mPlayer.setVolume(volume, volume);
+            }
+            mVolume = volume;
+        }
+
+        @Override
+        public boolean onTune(Uri channelUri) {
+            String[] projection = {TvContract.Channels.COLUMN_SERVICE_ID};
+            int resource = RESOURCE_1;
+            Cursor cursor = null;
+            try {
+                cursor = getContentResolver().query(channelUri, projection, null, null, null);
+                if (cursor == null || cursor.getCount() == 0) {
+                    return false;
+                }
+                cursor.moveToNext();
+                resource = (cursor.getInt(0) == SimpleTvInputSetupActivity.CHANNEL_1_SERVICE_ID ?
+                        RESOURCE_1 : RESOURCE_2);
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+            return startPlayback(resource);
+            // NOTE: To display the program information (e.g. title) properly in the channel banner,
+            // The implementation needs to register the program metadata on TvProvider.
+            // For the example implementation, please see {@link RichTvInputService}.
+        }
+
+        @Override
+        public void onSetCaptionEnabled(boolean enabled) {
+            // The sample content does not have caption. Nothing to do in this sample input.
+            // NOTE: If the channel has caption, the implementation should turn on/off the caption
+            // based on {@code enabled}.
+            // For the example implementation for the case, please see {@link RichTvInputService}.
+        }
+
+        private boolean startPlayback(int resource) {
+            if (mPlayer == null) {
+                mPlayer = new MediaPlayer();
+                mPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
+                    @Override
+                    public boolean onInfo(MediaPlayer player, int what, int arg) {
+                        // NOTE: TV input should notify the video playback state by using
+                        // {@code notifyVideoAvailable()} and {@code notifyVideoUnavailable() so
+                        // that the application can display back screen or spinner properly.
+                        if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
+                            notifyVideoUnavailable(
+                                    TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING);
+                            return true;
+                        } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END
+                                || what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
+                            notifyVideoAvailable();
+                            return true;
+                        }
+                        return false;
+                    }
+                });
+                mPlayer.setSurface(mSurface);
+                mPlayer.setVolume(mVolume, mVolume);
+            } else {
+                mPlayer.reset();
+            }
+            mPlayer.setLooping(true);
+            AssetFileDescriptor afd = getResources().openRawResourceFd(resource);
+            if (afd == null) {
+                return false;
+            }
+            try {
+                mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
+                        afd.getDeclaredLength());
+                mPlayer.prepare();
+                mPlayer.start();
+            } catch (IOException e) {
+                return false;
+            } finally {
+                try {
+                    afd.close();
+                } catch (IOException e) {
+                    // Do nothing.
+                }
+            }
+            // The sample content does not have rating information. Just allow the content here.
+            // NOTE: If the content might include problematic scenes, it should not be allowed.
+            // Also, if the content has rating information, the implementation should allow the
+            // content based on the current rating settings by using
+            // {@link android.media.tv.TvInputManager#isRatingBlocked()}.
+            // For the example implementation for the case, please see {@link RichTvInputService}.
+            notifyContentAllowed();
+            return true;
+        }
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/simple/SimpleTvInputSetupActivity.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/simple/SimpleTvInputSetupActivity.java
new file mode 100644
index 0000000..348991b
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/simple/SimpleTvInputSetupActivity.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.simple;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ContentValues;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.example.android.sampletvinput.R;
+
+/**
+ * The setup activity for {@link SimpleTvInputService}.
+ */
+public class SimpleTvInputSetupActivity extends Activity {
+    private static final String CHANNEL_1_NUMBER = "1-1";
+    private static final String CHANNEL_1_NAME = "Bunny - Low Resolution";
+    private static final int CHANNEL_1_ORIG_NETWORK_ID = 0;
+    private static final int CHANNEL_1_TRANSPORT_STREAM_ID = 0;
+    public static final int CHANNEL_1_SERVICE_ID = 1;
+
+    private static final String CHANNEL_2_NUMBER = "1-2";
+    private static final String CHANNEL_2_NAME = "Bunny - High Resolution";
+    private static final int CHANNEL_2_ORIG_NETWORK_ID = 0;
+    private static final int CHANNEL_2_TRANSPORT_STREAM_ID = 0;
+    public static final int CHANNEL_2_SERVICE_ID = 2;
+
+    private String mInputId;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mInputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
+
+        DialogFragment newFragment = new MyAlertDialogFragment();
+        newFragment.show(getFragmentManager(), "dialog");
+    }
+
+    private void registerChannels() {
+        // Check if we already registered channels.
+        Uri uri = TvContract.buildChannelsUriForInput(mInputId);
+        Cursor cursor = null;
+        try {
+            cursor = getContentResolver().query(uri, null, null, null, null);
+            if (cursor != null && cursor.getCount() > 0) {
+                return;
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        ContentValues values = new ContentValues();
+        values.put(TvContract.Channels.COLUMN_INPUT_ID, mInputId);
+
+        // Register channel 1-1.
+        values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, CHANNEL_1_NUMBER);
+        values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, CHANNEL_1_NAME);
+        values.put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, CHANNEL_1_ORIG_NETWORK_ID);
+        values.put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, CHANNEL_1_TRANSPORT_STREAM_ID);
+        values.put(TvContract.Channels.COLUMN_SERVICE_ID, CHANNEL_1_SERVICE_ID);
+        getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);
+
+        // Register channel 1-2.
+        values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, CHANNEL_2_NUMBER);
+        values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, CHANNEL_2_NAME);
+        values.put(TvContract.Channels.COLUMN_ORIGINAL_NETWORK_ID, CHANNEL_2_ORIG_NETWORK_ID);
+        values.put(TvContract.Channels.COLUMN_TRANSPORT_STREAM_ID, CHANNEL_2_TRANSPORT_STREAM_ID);
+        values.put(TvContract.Channels.COLUMN_SERVICE_ID, CHANNEL_2_SERVICE_ID);
+        getContentResolver().insert(TvContract.Channels.CONTENT_URI, values);
+    }
+
+    public static class MyAlertDialogFragment extends DialogFragment {
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            return new AlertDialog.Builder(getActivity())
+                    .setTitle(R.string.simple_setup_title)
+                    .setMessage(R.string.simple_setup_message)
+                    .setPositiveButton(android.R.string.ok,
+                            new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface dialog, int whichButton) {
+                                    ((SimpleTvInputSetupActivity) getActivity()).registerChannels();
+                                    // Sets the results so that the application can process the
+                                    // registered channels properly.
+                                    getActivity().setResult(Activity.RESULT_OK);
+                                    getActivity().finish();
+                                }
+                            }
+                    )
+                    .setNegativeButton(android.R.string.cancel,
+                            new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface dialog, int whichButton) {
+                                    getActivity().finish();
+                                }
+                            }
+                    )
+                    .create();
+        }
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/DummyAccountService.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/DummyAccountService.java
new file mode 100644
index 0000000..8f7c6f0
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/DummyAccountService.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.syncadapter;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.NetworkErrorException;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+
+/**
+ * Dummy account service for SyncAdapter. Note that this does nothing because this input uses a feed
+ * which does not require any authentication.
+ */
+public class DummyAccountService extends Service {
+    private static final String TAG = "DummyAccountService";
+    private DummyAuthenticator mAuthenticator;
+    public static final String ACCOUNT_NAME = "DummyAccount";
+
+    public static Account getAccount(String accountType) {
+        return new Account(ACCOUNT_NAME, accountType);
+    }
+
+    @Override
+    public void onCreate() {
+        mAuthenticator = new DummyAuthenticator(this);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mAuthenticator.getIBinder();
+    }
+
+    /**
+     * Dummy Authenticator used in {@link SyncAdapter}. This does nothing for all the operations
+     * since channel/program feed does not require any authentication.
+     */
+    public class DummyAuthenticator extends AbstractAccountAuthenticator {
+        public DummyAuthenticator(Context context) {
+            super(context);
+        }
+
+        @Override
+        public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse,
+                String s) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse,
+                String s, String s2, String[] strings, Bundle bundle) throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
+                Account account, Bundle bundle) throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse,
+                Account account, String s, Bundle bundle) throws NetworkErrorException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String getAuthTokenLabel(String s) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
+                Account account, String s, Bundle bundle) throws NetworkErrorException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse,
+                Account account, String[] strings) throws NetworkErrorException {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+}
+
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncAdapter.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncAdapter.java
new file mode 100644
index 0000000..0cd281d
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncAdapter.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.syncadapter;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.ContentProviderOperation;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.content.SyncResult;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import com.example.android.sampletvinput.rich.RichFeedUtil;
+import com.example.android.sampletvinput.rich.RichTvInputService.ChannelInfo;
+import com.example.android.sampletvinput.rich.RichTvInputService.ProgramInfo;
+import com.example.android.sampletvinput.TvContractUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A SyncAdapter implementation which updates program info periodically.
+ */
+class SyncAdapter extends AbstractThreadedSyncAdapter {
+    public static final String TAG = "SyncAdapter";
+
+    public static final String BUNDLE_KEY_INPUT_ID = "bundle_key_input_id";
+    public static final long SYNC_FREQUENCY_SEC = 60 * 60 * 6;  // 6 hours
+    private static final int SYNC_WINDOW_SEC = 60 * 60 * 12;  // 12 hours
+    private static final int BATCH_OPERATION_COUNT = 100;
+
+    private final Context mContext;
+
+    public SyncAdapter(Context context, boolean autoInitialize) {
+        super(context, autoInitialize);
+        mContext = context;
+    }
+
+    public SyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) {
+        super(context, autoInitialize, allowParallelSyncs);
+        mContext = context;
+    }
+
+    /**
+     * Called periodically by the system in every {@code SYNC_FREQUENCY_SEC}.
+     */
+    @Override
+    public void onPerformSync(Account account, Bundle extras, String authority,
+            ContentProviderClient provider, SyncResult syncResult) {
+        Log.d(TAG, "onPerformSync(" + account + ", " + authority + ", " + extras + ")");
+        String inputId = extras.getString(SyncAdapter.BUNDLE_KEY_INPUT_ID);
+        if (inputId == null) {
+            return;
+        }
+        List<ChannelInfo> channels = RichFeedUtil.getRichChannels(mContext);
+        LongSparseArray<ChannelInfo> channelMap = TvContractUtils.buildChannelMap(
+                mContext.getContentResolver(), inputId, channels);
+        for (int i = 0; i < channelMap.size(); ++i) {
+            Uri channelUri = TvContract.buildChannelUri(channelMap.keyAt(i));
+            insertPrograms(channelUri, channelMap.valueAt(i));
+        }
+    }
+
+    /**
+     * Inserts programs from now to {@link SyncAdapter#SYNC_WINDOW_SEC}.
+     *
+     * @param channelUri The channel where the program info will be added.
+     * @param channelInfo {@link ChannelInfo} instance which includes program information.
+     */
+    private void insertPrograms(Uri channelUri, ChannelInfo channelInfo) {
+        long durationSumSec = 0;
+        List<ContentValues> programs = new ArrayList<>();
+        for (ProgramInfo program : channelInfo.programs) {
+            durationSumSec += program.durationSec;
+
+            ContentValues values = new ContentValues();
+            values.put(TvContract.Programs.COLUMN_CHANNEL_ID, ContentUris.parseId(channelUri));
+            values.put(TvContract.Programs.COLUMN_TITLE, program.title);
+            values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, program.description);
+            values.put(TvContract.Programs.COLUMN_CONTENT_RATING,
+                    TvContractUtils.contentRatingsToString(program.contentRatings));
+            if (!TextUtils.isEmpty(program.posterArtUri)) {
+                values.put(TvContract.Programs.COLUMN_POSTER_ART_URI, program.posterArtUri);
+            }
+            // NOTE: {@code COLUMN_INTERNAL_PROVIDER_DATA} is a private field where TvInputService
+            // can store anything it wants. Here, we store video type and video URL so that
+            // TvInputService can play the video later with this field.
+            values.put(TvContract.Programs.COLUMN_INTERNAL_PROVIDER_DATA,
+                    TvContractUtils.convertVideoInfoToInternalProviderData(program.videoType,
+                            program.videoUrl));
+            programs.add(values);
+        }
+
+        long nowSec = System.currentTimeMillis() / 1000;
+        long insertionEndSec = nowSec + SYNC_WINDOW_SEC;
+        long lastProgramEndTimeSec = TvContractUtils.getLastProgramEndTimeMillis(
+                mContext.getContentResolver(), channelUri) / 1000;
+        if (nowSec < lastProgramEndTimeSec) {
+            nowSec = lastProgramEndTimeSec;
+        }
+        long insertionStartTimeSec = nowSec - nowSec % durationSumSec;
+        long nextPos = insertionStartTimeSec;
+        for (int i = 0; nextPos < insertionEndSec; ++i) {
+            long programStartSec = nextPos;
+            ArrayList<ContentProviderOperation> ops = new ArrayList<>();
+            int programsCount = channelInfo.programs.size();
+            for (int j = 0; j < programsCount; ++j) {
+                ProgramInfo program = channelInfo.programs.get(j);
+                ops.add(ContentProviderOperation.newInsert(
+                        TvContract.Programs.CONTENT_URI)
+                        .withValues(programs.get(j))
+                        .withValue(TvContract.Programs.COLUMN_START_TIME_UTC_MILLIS,
+                                programStartSec * 1000)
+                        .withValue(TvContract.Programs.COLUMN_END_TIME_UTC_MILLIS,
+                                (programStartSec + program.durationSec) * 1000)
+                        .build());
+                programStartSec = programStartSec + program.durationSec;
+
+                // Throttle the batch operation not to face TransactionTooLargeException.
+                if (j % BATCH_OPERATION_COUNT == BATCH_OPERATION_COUNT - 1
+                        || j == programsCount - 1) {
+                    try {
+                        mContext.getContentResolver().applyBatch(TvContract.AUTHORITY, ops);
+                    } catch (RemoteException | OperationApplicationException e) {
+                        Log.e(TAG, "Failed to insert programs.", e);
+                        return;
+                    }
+                    ops.clear();
+                }
+            }
+            nextPos = insertionStartTimeSec + i * durationSumSec;
+        }
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncService.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncService.java
new file mode 100644
index 0000000..89f23a8
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncService.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.syncadapter;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+/**
+ * Service which provides the SyncAdapter implementation to the framework on request.
+ */
+public class SyncService extends Service {
+    private static final Object sSyncAdapterLock = new Object();
+    private static SyncAdapter sSyncAdapter = null;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        synchronized (sSyncAdapterLock) {
+            if (sSyncAdapter == null) {
+                sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
+            }
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return sSyncAdapter.getSyncAdapterBinder();
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncUtils.java b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncUtils.java
new file mode 100644
index 0000000..0cd33b8
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.example.android.sampletvinput.syncadapter;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.tv.TvContract;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Static helper methods for working with the SyncAdapter framework.
+ */
+public class SyncUtils {
+    private static final String TAG = "SyncUtils";
+    private static final String CONTENT_AUTHORITY = TvContract.AUTHORITY;
+    public static final String ACCOUNT_TYPE = "com.example.android.sampletvinput.account";
+
+    public static void setUpPeriodicSync(Context context, String inputId) {
+        Account account = DummyAccountService.getAccount(ACCOUNT_TYPE);
+        AccountManager accountManager =
+                (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
+        if (!accountManager.addAccountExplicitly(account, null, null)) {
+            Log.e(TAG, "Account already exists.");
+        }
+        ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
+        ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
+        Bundle bundle = new Bundle();
+        bundle.putString(SyncAdapter.BUNDLE_KEY_INPUT_ID, inputId);
+        ContentResolver.addPeriodicSync(account, CONTENT_AUTHORITY, bundle,
+                SyncAdapter.SYNC_FREQUENCY_SEC);
+    }
+
+    public static void requestSync(String inputId) {
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+        bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+        bundle.putString(SyncAdapter.BUNDLE_KEY_INPUT_ID, inputId);
+        ContentResolver.requestSync(DummyAccountService.getAccount(ACCOUNT_TYPE), CONTENT_AUTHORITY,
+                bundle);
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable-xhdpi/android_48dp.png b/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable-xhdpi/android_48dp.png
new file mode 100644
index 0000000..9ea1cd1
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable-xhdpi/android_48dp.png
Binary files differ
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable-xhdpi/default_background.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable-xhdpi/default_background.xml
new file mode 100644
index 0000000..07b0589
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable-xhdpi/default_background.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <gradient
+            android:startColor="@color/background_gradient_start"
+            android:endColor="@color/background_gradient_end"
+            android:angle="-270" />
+</shape>
\ No newline at end of file
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable-xhdpi/icon.png b/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable-xhdpi/icon.png
new file mode 100644
index 0000000..8497c28
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable-xhdpi/icon.png
Binary files differ
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable/layout_border.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable/layout_border.xml
new file mode 100644
index 0000000..ddf0d5a
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/drawable/layout_border.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 Google Inc. All rights reserved.
+
+     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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <stroke android:width="10dip" android:color="#CCB1BCBE" />
+    <corners android:radius="30dip"/>
+    <padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
+</shape>
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/layout/overlayview.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/layout/overlayview.xml
new file mode 100644
index 0000000..25bd47e
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/layout/overlayview.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:keepScreenOn="true">
+
+    <com.google.android.exoplayer.text.SubtitleView
+        android:id="@+id/subtitles"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|center_horizontal"
+        android:layout_marginLeft="16dp"
+        android:layout_marginRight="16dp"
+        android:layout_marginBottom="32dp"
+        android:visibility="invisible"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/layout/rich_settings.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/layout/rich_settings.xml
new file mode 100644
index 0000000..0cc4622
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/layout/rich_settings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:name="com.example.android.sampletvinput.rich.RichSettingsFragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/layout/rich_setup.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/layout/rich_setup.xml
new file mode 100644
index 0000000..0ac07fb
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/layout/rich_setup.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:name="com.example.android.sampletvinput.rich.RichSetupFragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/raw/rich_tv_inputs_tif.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/raw/rich_tv_inputs_tif.xml
new file mode 100644
index 0000000..9fcaa6a
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/raw/rich_tv_inputs_tif.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<!--
+Example feed for RichTvInputService. This feed includes 3 channels and each channel may include
+several programs. The programs are expected to be played sequentially in a loop for a given channel
+so as to simulate a live TV channel.
+NOTE: By default, rich TV input fetches the feed from cloud. To make it used this local feed, modify
+the value of {@link com.example.android.sampletvinput.rich.RichFeedUtil#USE_LOCAL_XML_FEED}.
+NOTE: This is just an example feed. Developers are welcome to use your own formats in e.g xml or
+JSON, in which case you may need to write your own parser.
+-->
+<TvInputs display_name="Rich TV Input"
+    name="Your Company"
+    description="This is a sample TV input for demonstrating advanced features of TV Input Framework."
+    logo_thumb_url="http://commondatastorage.googleapis.com/android-tv/YourCompany.jpg"
+    logo_background_url="http://commondatastorage.googleapis.com/android-tv/YourCompany.jpg">
+    <Channels>
+        <Channel display_number="2-1" display_name="Google" video_width="1280" video_height="720"
+            logo_url="http://storage.googleapis.com/android-tv/images/google.png">
+            <Program title="Introducing Gmail Blue"
+                duration_sec="107"
+                video_url="http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Gmail%20Blue.mp4"
+                video_type="HTTP_PROGRESSIVE"
+                poster_art_url="http://storage.googleapis.com/android-tv/images/gmail.png"
+                description="Introducing Gmail Blue Introducing Gmail Blue Introducing Gmail Blue Introducing Gmail Blue."
+                content_rating="com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_L" />
+            <Program title="GoogleIO 2014 Casting To The Future"
+                duration_sec="2595"
+                video_url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/GoogleIO-2014-CastingToTheFuture.mp4"
+                video_type="HTTP_PROGRESSIVE"
+                poster_art_url="http://storage.googleapis.com/gtv-videos-bucket/sample/images_480x270/ToTheFuture2-480x270.jpg"
+                description="GoogleIO 2014 Casting To The Future"
+                content_rating="com.android.tv/US_TV/US_TV_PG/US_TV_D" />
+            <Program title="GoogleIO 2014 Making Google Cast Ready Apps Discoverable"
+                duration_sec="840"
+                video_url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/GoogleIO-2014-MakingGoogleCastReadyAppsDiscoverable.mp4"
+                video_type="HTTP_PROGRESSIVE"
+                poster_art_url="http://storage.googleapis.com/gtv-videos-bucket/sample/images_480x270/MakingGoogleCastReadyAppsDiscoverable-480-270.jpg"
+                description="GoogleIO 2014 Making Google Cast Ready Apps Discoverable."
+                content_rating="com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_L" />
+            <Program title="Introducing Google Fiber to the Pole"
+                duration_sec="131"
+                video_url="http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Google%20Fiber%20to%20the%20Pole.mp4"
+                video_type="HTTP_PROGRESSIVE"
+                poster_art_url="http://storage.googleapis.com/android-tv/images/fiber.png"
+                description="Introducing Google Fiber to the Pole."
+                content_rating="com.android.tv/US_TV/US_TV_PG/US_TV_D" />
+            <Program title="Introducing Google Nose"
+                duration_sec="122"
+                video_url="http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Google%20Nose.mp4"
+                video_type="HTTP_PROGRESSIVE"
+                poster_art_url="http://storage.googleapis.com/android-tv/images/nose.png"
+                description="Introducing Google Nose Introducing Google Nose Introducing Google Nose Introducing Google Nose Introducing Google Nose"
+                content_rating="com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_L" />
+        </Channel>
+        <Channel display_number="2-2" display_name="Creative Commons" video_width="1280" video_height="720"
+            logo_url="http://storage.googleapis.com/android-tv/images/cc.png">
+            <Program title="Elephants Dream"
+                duration_sec="653"
+                video_url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"
+                video_type="HTTP_PROGRESSIVE"
+                poster_art_url="http://storage.googleapis.com/gtv-videos-bucket/sample/images_480x270/ElephantsDream.jpg"
+                description="Elephants Dream Elephants Dream Elephants Dream."
+                content_rating="com.android.tv/US_TV/US_TV_14/US_TV_D/US_TV_L" />
+            <Program title="Sintel"
+                duration_sec="887"
+                video_url="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"
+                video_type="HTTP_PROGRESSIVE"
+                poster_art_url="http://storage.googleapis.com/gtv-videos-bucket/sample/images_480x270/Sintel.jpg"
+                description="Sintel a movie clip."
+                content_rating="com.android.tv/US_TV/US_TV_PG/US_TV_D" />
+        </Channel>
+        <Channel display_number="2-3" display_name="HLS" video_width="1920" video_height="1080"
+            logo_url="http://storage.googleapis.com/android-tv/images/hls.png">
+            <Program title="Bip-Bop"
+                duration_sec="1800"
+                video_url="https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
+                video_type="HLS"
+                poster_art_url="http://storage.googleapis.com/android-tv/images/bipbop.png"
+                description="Bip-Bop sample video with captions"
+                content_rating="com.android.tv/US_TV/US_TV_G" />
+        </Channel>
+        <Channel display_number="2-4" display_name="MPEG_DASH" video_width="1920" video_height="1080"
+            logo_url="http://storage.googleapis.com/android-tv/images/mpeg_dash.png">
+            <Program title="Google Glass"
+                duration_sec="135"
+                video_url="http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&amp;sparams=ip,ipbits,expire,as&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D.2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&amp;key=ik0"
+                video_type="MPEG_DASH"
+                poster_art_url="http://storage.googleapis.com/android-tv/images/glass.png"
+                description="Introduction to Google Glass projects with multiple audio tracks."
+                content_rating="com.android.tv/US_TV/US_TV_G" />
+        </Channel>
+    </Channels>
+</TvInputs>
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp b/prebuilts/androidtv/sample-inputs/app/src/main/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp
new file mode 100644
index 0000000..c0bef56
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/raw/video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp
Binary files differ
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4 b/prebuilts/androidtv/sample-inputs/app/src/main/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4
new file mode 100644
index 0000000..63e25b8
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4
Binary files differ
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/values/colors.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..fe60549
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/values/colors.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<resources>
+    <color name="background_gradient_start">#000000</color>
+    <color name="background_gradient_end">#DDDDDD</color>
+    <color name="fastlane_background">#0096a6</color>
+    <color name="search_opaque">#ffaa3f</color>
+    <color name="detail_background">#0096a6</color>
+    <color name="soft_opaque">#30000000</color>
+    <color name="img_soft_opaque">#30FF0000</color>
+    <color name="img_full_opaque">#00000000</color>
+    <color name="black_opaque">#AA000000</color>
+    <color name="black">#59000000</color>
+    <color name="white">#FFFFFF</color>
+    <color name="orange_transparent">#AAFADCA7</color>
+    <color name="orange">#FADCA7</color>
+    <color name="yellow">#EEFF41</color>
+    <color name="default_background">#3d3d3d</color>
+</resources>
\ No newline at end of file
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/values/constants.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/values/constants.xml
new file mode 100644
index 0000000..eee0f18
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/values/constants.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+  <!-- The minimum subtitle font size. -->
+  <dimen name="subtitle_minimum_font_size">13sp</dimen>
+
+</resources>
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/values/strings.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..031252e
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/values/strings.xml
@@ -0,0 +1,31 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<resources>
+    <string name="app_name">Sample TV Inputs</string>
+    <string name="about_app">About TV Sample Input</string>
+    <string name="version">Version: %1$s</string>
+    <string name="sample_tv_input" translatable="false">SampleTvInput</string>
+    <string name="simple_input_label" translatable="false">Simple Input</string>
+    <string name="simple_setup_title">Setup Simple Input</string>
+    <string name="simple_setup_message">Do you want to register detected channels?</string>
+    <string name="rich_input_label" translatable="false">Rich Input</string>
+    <string name="rich_input_feed_url">http://commondatastorage.googleapis.com/android-tv/rich_tv_inputs_tif.xml</string>
+    <string name="rich_setup_add_channel">Add Channels</string>
+    <string name="rich_setup_update_channel">Update Channels</string>
+    <string name="rich_setup_cancel">Cancel Setup</string>
+    <string name="rich_setup_in_progress">In Progress...</string>
+    <string name="rich_settings_browse_title">TV Inputs by Your Company</string>
+    <string name="feed_error_message">No feed. Check your connection!</string>
+</resources>
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/authenticator.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/authenticator.xml
new file mode 100644
index 0000000..6bb3be2
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/authenticator.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:accountType="com.example.android.sampletvinput.account"
+    android:icon="@drawable/icon"
+    android:smallIcon="@drawable/icon"
+    android:label="@string/app_name" />
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/richtvinputservice.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/richtvinputservice.xml
new file mode 100644
index 0000000..3538264
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/richtvinputservice.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+    android:setupActivity="com.example.android.sampletvinput.rich.RichTvInputSetupActivity"
+    android:settingsActivity="com.example.android.sampletvinput.rich.RichTvInputSettingsActivity" />
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/simpletvinputservice.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/simpletvinputservice.xml
new file mode 100644
index 0000000..b895152
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/simpletvinputservice.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+    android:setupActivity="com.example.android.sampletvinput.simple.SimpleTvInputSetupActivity" />
diff --git a/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/syncadapter.xml b/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/syncadapter.xml
new file mode 100644
index 0000000..ee4dd8b
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/app/src/main/res/xml/syncadapter.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     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.
+-->
+<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+    android:contentAuthority="android.media.tv"
+    android:accountType="com.example.android.sampletvinput.account"
+    android:userVisible="false"
+    android:supportsUploading="false"
+    android:allowParallelSyncs="false"
+    android:isAlwaysSyncable="true" />
diff --git a/prebuilts/androidtv/sample-inputs/build.gradle b/prebuilts/androidtv/sample-inputs/build.gradle
new file mode 100644
index 0000000..e26cdee
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/build.gradle
@@ -0,0 +1,15 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.0.0'
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+    }
+}
diff --git a/prebuilts/androidtv/sample-inputs/gradle/wrapper/gradle-wrapper.jar b/prebuilts/androidtv/sample-inputs/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..3c7abdf
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/prebuilts/androidtv/sample-inputs/gradle/wrapper/gradle-wrapper.properties b/prebuilts/androidtv/sample-inputs/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/prebuilts/androidtv/sample-inputs/gradlew b/prebuilts/androidtv/sample-inputs/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/prebuilts/androidtv/sample-inputs/gradlew.bat b/prebuilts/androidtv/sample-inputs/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windowz variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+if "%@eval[2+2]" == "4" goto 4NT_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+goto execute

+

+:4NT_args

+@rem Get arguments from the 4NT Shell from JP Software

+set CMD_LINE_ARGS=%$

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/prebuilts/androidtv/sample-inputs/settings.gradle b/prebuilts/androidtv/sample-inputs/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/prebuilts/androidtv/sample-inputs/settings.gradle
@@ -0,0 +1 @@
+include ':app'