Merge "Add ABOUT.txt for Displaying Bitmaps sample." into klp-docs
diff --git a/media/MediaRouter/MediaRouterSample/.gitignore b/media/MediaRouter/MediaRouterSample/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/.gitignore
@@ -0,0 +1,16 @@
+# Copyright 2013 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.
+src/template/
+src/common/
+build.gradle
diff --git a/media/MediaRouter/MediaRouterSample/proguard-project.txt b/media/MediaRouter/MediaRouterSample/proguard-project.txt
new file mode 100644
index 0000000..0d8f171
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/proguard-project.txt
@@ -0,0 +1,20 @@
+ To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/AndroidManifest.xml b/media/MediaRouter/MediaRouterSample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0b5bec4
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/AndroidManifest.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<!-- Declare the contents of this Android application.  The namespace
+     attribute brings in the Android platform namespace, and the package
+     supplies a unique name for the application.  When writing your
+     own application, the package name must be changed from "com.example.*"
+     to come from a domain that you own or have control over. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.example.android.mediarouter">
+    <!-- Permission for INTERNET is required for streaming video content
+         from the web, it's not required otherwise. -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <!-- Permission for SYSTEM_ALERT_WINDOW is only required for emulating
+         remote display using system alert window. -->
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <uses-sdk android:targetSdkVersion="19"
+        android:minSdkVersion="7"/>
+
+    <!-- The smallest screen this app works on is a phone.  The app will
+         scale its UI to larger screens but doesn't make good use of them
+         so allow the compatibility mode button to be shown (mostly because
+         this is just convenient for testing). -->
+    <supports-screens android:requiresSmallestWidthDp="320"
+                      android:compatibleWidthLimitDp="480" />
+
+    <application android:label="@string/app_name"
+                 android:icon="@drawable/ic_launcher"
+                 android:hardwareAccelerated="true">
+
+        <receiver android:name=".player.SampleMediaButtonReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_BUTTON" />
+            </intent-filter>
+        </receiver>
+        <!-- MediaRouter Support Samples -->
+
+        <activity android:name=".player.MainActivity"
+                  android:label="@string/app_name"
+                  android:theme="@style/Theme.AppCompat.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv7.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <service android:name=".provider.SampleMediaRouteProviderService"
+                 android:label="@string/sample_media_route_provider_service"
+                 android:process=":mrp">
+            <intent-filter>
+                <action android:name="android.media.MediaRouteProviderService" />
+            </intent-filter>
+        </service>
+
+    </application>
+</manifest>
diff --git a/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/LocalPlayer.java b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/LocalPlayer.java
new file mode 100644
index 0000000..86f5100
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/LocalPlayer.java
@@ -0,0 +1,630 @@
+/*
+ * Copyright (C) 2013 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.mediarouter.player;
+
+import android.app.Activity;
+import android.app.Presentation;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaRouter.RouteInfo;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.example.android.mediarouter.R;
+
+import java.io.IOException;
+
+/**
+ * Handles playback of a single media item using MediaPlayer.
+ */
+public abstract class LocalPlayer extends Player implements
+        MediaPlayer.OnPreparedListener,
+        MediaPlayer.OnCompletionListener,
+        MediaPlayer.OnErrorListener,
+        MediaPlayer.OnSeekCompleteListener {
+    private static final String TAG = "LocalPlayer";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final int STATE_IDLE = 0;
+    private static final int STATE_PLAY_PENDING = 1;
+    private static final int STATE_READY = 2;
+    private static final int STATE_PLAYING = 3;
+    private static final int STATE_PAUSED = 4;
+
+    private final Context mContext;
+    private final Handler mHandler = new Handler();
+    private MediaPlayer mMediaPlayer;
+    private int mState = STATE_IDLE;
+    private int mSeekToPos;
+    private int mVideoWidth;
+    private int mVideoHeight;
+    private Surface mSurface;
+    private SurfaceHolder mSurfaceHolder;
+
+    public LocalPlayer(Context context) {
+        mContext = context;
+
+        // reset media player
+        reset();
+    }
+
+    @Override
+    public boolean isRemotePlayback() {
+        return false;
+    }
+
+    @Override
+    public boolean isQueuingSupported() {
+        return false;
+    }
+
+    @Override
+    public void connect(RouteInfo route) {
+        if (DEBUG) {
+            Log.d(TAG, "connecting to: " + route);
+        }
+    }
+
+    @Override
+    public void release() {
+        if (DEBUG) {
+            Log.d(TAG, "releasing");
+        }
+        // release media player
+        if (mMediaPlayer != null) {
+            mMediaPlayer.stop();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+    }
+
+    // Player
+    @Override
+    public void play(final PlaylistItem item) {
+        if (DEBUG) {
+            Log.d(TAG, "play: item=" + item);
+        }
+        reset();
+        mSeekToPos = (int)item.getPosition();
+        try {
+            mMediaPlayer.setDataSource(mContext, item.getUri());
+            mMediaPlayer.prepareAsync();
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "MediaPlayer throws IllegalStateException, uri=" + item.getUri());
+        } catch (IOException e) {
+            Log.e(TAG, "MediaPlayer throws IOException, uri=" + item.getUri());
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "MediaPlayer throws IllegalArgumentException, uri=" + item.getUri());
+        } catch (SecurityException e) {
+            Log.e(TAG, "MediaPlayer throws SecurityException, uri=" + item.getUri());
+        }
+        if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
+            resume();
+        } else {
+            pause();
+        }
+    }
+
+    @Override
+    public void seek(final PlaylistItem item) {
+        if (DEBUG) {
+            Log.d(TAG, "seek: item=" + item);
+        }
+        int pos = (int)item.getPosition();
+        if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
+            mMediaPlayer.seekTo(pos);
+            mSeekToPos = pos;
+        } else if (mState == STATE_IDLE || mState == STATE_PLAY_PENDING) {
+            // Seek before onPrepared() arrives,
+            // need to performed delayed seek in onPrepared()
+            mSeekToPos = pos;
+        }
+    }
+
+    @Override
+    public void getStatus(final PlaylistItem item, final boolean update) {
+        if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
+            // use mSeekToPos if we're currently seeking (mSeekToPos is reset
+            // when seeking is completed)
+            item.setDuration(mMediaPlayer.getDuration());
+            item.setPosition(mSeekToPos > 0 ?
+                    mSeekToPos : mMediaPlayer.getCurrentPosition());
+            item.setTimestamp(SystemClock.elapsedRealtime());
+        }
+        if (update && mCallback != null) {
+            mCallback.onPlaylistReady();
+        }
+    }
+
+    @Override
+    public void pause() {
+        if (DEBUG) {
+            Log.d(TAG, "pause");
+        }
+        if (mState == STATE_PLAYING) {
+            mMediaPlayer.pause();
+            mState = STATE_PAUSED;
+        }
+    }
+
+    @Override
+    public void resume() {
+        if (DEBUG) {
+            Log.d(TAG, "resume");
+        }
+        if (mState == STATE_READY || mState == STATE_PAUSED) {
+            mMediaPlayer.start();
+            mState = STATE_PLAYING;
+        } else if (mState == STATE_IDLE){
+            mState = STATE_PLAY_PENDING;
+        }
+    }
+
+    @Override
+    public void stop() {
+        if (DEBUG) {
+            Log.d(TAG, "stop");
+        }
+        if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
+            mMediaPlayer.stop();
+            mState = STATE_IDLE;
+        }
+    }
+
+    @Override
+    public void enqueue(final PlaylistItem item) {
+        throw new UnsupportedOperationException("LocalPlayer doesn't support enqueue!");
+    }
+
+    @Override
+    public PlaylistItem remove(String iid) {
+        throw new UnsupportedOperationException("LocalPlayer doesn't support remove!");
+    }
+
+    //MediaPlayer Listeners
+    @Override
+    public void onPrepared(MediaPlayer mp) {
+        if (DEBUG) {
+            Log.d(TAG, "onPrepared");
+        }
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mState == STATE_IDLE) {
+                    mState = STATE_READY;
+                    updateVideoRect();
+                } else if (mState == STATE_PLAY_PENDING) {
+                    mState = STATE_PLAYING;
+                    updateVideoRect();
+                    if (mSeekToPos > 0) {
+                        if (DEBUG) {
+                            Log.d(TAG, "seek to initial pos: " + mSeekToPos);
+                        }
+                        mMediaPlayer.seekTo(mSeekToPos);
+                    }
+                    mMediaPlayer.start();
+                }
+                if (mCallback != null) {
+                    mCallback.onPlaylistChanged();
+                }
+            }
+        });
+    }
+
+    @Override
+    public void onCompletion(MediaPlayer mp) {
+        if (DEBUG) {
+            Log.d(TAG, "onCompletion");
+        }
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mCallback != null) {
+                    mCallback.onCompletion();
+                }
+            }
+        });
+    }
+
+    @Override
+    public boolean onError(MediaPlayer mp, int what, int extra) {
+        if (DEBUG) {
+            Log.d(TAG, "onError");
+        }
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mCallback != null) {
+                    mCallback.onError();
+                }
+            }
+        });
+        // return true so that onCompletion is not called
+        return true;
+    }
+
+    @Override
+    public void onSeekComplete(MediaPlayer mp) {
+        if (DEBUG) {
+            Log.d(TAG, "onSeekComplete");
+        }
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mSeekToPos = 0;
+                if (mCallback != null) {
+                    mCallback.onPlaylistChanged();
+                }
+            }
+        });
+    }
+
+    protected Context getContext() { return mContext; }
+    protected MediaPlayer getMediaPlayer() { return mMediaPlayer; }
+    protected int getVideoWidth() { return mVideoWidth; }
+    protected int getVideoHeight() { return mVideoHeight; }
+    protected void setSurface(Surface surface) {
+        mSurface = surface;
+        mSurfaceHolder = null;
+        updateSurface();
+    }
+
+    protected void setSurface(SurfaceHolder surfaceHolder) {
+        mSurface = null;
+        mSurfaceHolder = surfaceHolder;
+        updateSurface();
+    }
+
+    protected void removeSurface(SurfaceHolder surfaceHolder) {
+        if (surfaceHolder == mSurfaceHolder) {
+            setSurface((SurfaceHolder)null);
+        }
+    }
+
+    protected void updateSurface() {
+        if (mMediaPlayer == null) {
+            // just return if media player is already gone
+            return;
+        }
+        if (mSurface != null) {
+            // The setSurface API does not exist until V14+.
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+                ICSMediaPlayer.setSurface(mMediaPlayer, mSurface);
+            } else {
+                throw new UnsupportedOperationException("MediaPlayer does not support "
+                        + "setSurface() on this version of the platform.");
+            }
+        } else if (mSurfaceHolder != null) {
+            mMediaPlayer.setDisplay(mSurfaceHolder);
+        } else {
+            mMediaPlayer.setDisplay(null);
+        }
+    }
+
+    protected abstract void updateSize();
+
+    private void reset() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.stop();
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+        mMediaPlayer = new MediaPlayer();
+        mMediaPlayer.setOnPreparedListener(this);
+        mMediaPlayer.setOnCompletionListener(this);
+        mMediaPlayer.setOnErrorListener(this);
+        mMediaPlayer.setOnSeekCompleteListener(this);
+        updateSurface();
+        mState = STATE_IDLE;
+        mSeekToPos = 0;
+    }
+
+    private void updateVideoRect() {
+        if (mState != STATE_IDLE && mState != STATE_PLAY_PENDING) {
+            int width = mMediaPlayer.getVideoWidth();
+            int height = mMediaPlayer.getVideoHeight();
+            if (width > 0 && height > 0) {
+                mVideoWidth = width;
+                mVideoHeight = height;
+                updateSize();
+            } else {
+                Log.e(TAG, "video rect is 0x0!");
+                mVideoWidth = mVideoHeight = 0;
+            }
+        }
+    }
+
+    private static final class ICSMediaPlayer {
+        public static final void setSurface(MediaPlayer player, Surface surface) {
+            player.setSurface(surface);
+        }
+    }
+
+    /**
+     * Handles playback of a single media item using MediaPlayer in SurfaceView
+     */
+    public static class SurfaceViewPlayer extends LocalPlayer implements
+            SurfaceHolder.Callback {
+        private static final String TAG = "SurfaceViewPlayer";
+        private RouteInfo mRoute;
+        private final SurfaceView mSurfaceView;
+        private final FrameLayout mLayout;
+        private DemoPresentation mPresentation;
+
+        public SurfaceViewPlayer(Context context) {
+            super(context);
+
+            mLayout = (FrameLayout)((Activity)context).findViewById(R.id.player);
+            mSurfaceView = (SurfaceView)((Activity)context).findViewById(R.id.surface_view);
+
+            // add surface holder callback
+            SurfaceHolder holder = mSurfaceView.getHolder();
+            holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+            holder.addCallback(this);
+        }
+
+        @Override
+        public void connect(RouteInfo route) {
+            super.connect(route);
+            mRoute = route;
+        }
+
+        @Override
+        public void release() {
+            super.release();
+
+            // dismiss presentation display
+            if (mPresentation != null) {
+                Log.i(TAG, "Dismissing presentation because the activity is no longer visible.");
+                mPresentation.dismiss();
+                mPresentation = null;
+            }
+
+            // remove surface holder callback
+            SurfaceHolder holder = mSurfaceView.getHolder();
+            holder.removeCallback(this);
+
+            // hide the surface view when SurfaceViewPlayer is destroyed
+            mSurfaceView.setVisibility(View.GONE);
+            mLayout.setVisibility(View.GONE);
+        }
+
+        @Override
+        public void updatePresentation() {
+            // Get the current route and its presentation display.
+            Display presentationDisplay = mRoute != null ? mRoute.getPresentationDisplay() : null;
+
+            // Dismiss the current presentation if the display has changed.
+            if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) {
+                Log.i(TAG, "Dismissing presentation because the current route no longer "
+                        + "has a presentation display.");
+                mPresentation.dismiss();
+                mPresentation = null;
+            }
+
+            // Show a new presentation if needed.
+            if (mPresentation == null && presentationDisplay != null) {
+                Log.i(TAG, "Showing presentation on display: " + presentationDisplay);
+                mPresentation = new DemoPresentation(getContext(), presentationDisplay);
+                mPresentation.setOnDismissListener(mOnDismissListener);
+                try {
+                    mPresentation.show();
+                } catch (WindowManager.InvalidDisplayException ex) {
+                    Log.w(TAG, "Couldn't show presentation!  Display was removed in "
+                              + "the meantime.", ex);
+                    mPresentation = null;
+                }
+            }
+
+            updateContents();
+        }
+
+        // SurfaceHolder.Callback
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format,
+                int width, int height) {
+            if (DEBUG) {
+                Log.d(TAG, "surfaceChanged: " + width + "x" + height);
+            }
+            setSurface(holder);
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            if (DEBUG) {
+                Log.d(TAG, "surfaceCreated");
+            }
+            setSurface(holder);
+            updateSize();
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            if (DEBUG) {
+                Log.d(TAG, "surfaceDestroyed");
+            }
+            removeSurface(holder);
+        }
+
+        @Override
+        protected void updateSize() {
+            int width = getVideoWidth();
+            int height = getVideoHeight();
+            if (width > 0 && height > 0) {
+                if (mPresentation == null) {
+                    int surfaceWidth = mLayout.getWidth();
+                    int surfaceHeight = mLayout.getHeight();
+
+                    // Calculate the new size of mSurfaceView, so that video is centered
+                    // inside the framelayout with proper letterboxing/pillarboxing
+                    ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams();
+                    if (surfaceWidth * height < surfaceHeight * width) {
+                        // Black bars on top&bottom, mSurfaceView has full layout width,
+                        // while height is derived from video's aspect ratio
+                        lp.width = surfaceWidth;
+                        lp.height = surfaceWidth * height / width;
+                    } else {
+                        // Black bars on left&right, mSurfaceView has full layout height,
+                        // while width is derived from video's aspect ratio
+                        lp.width = surfaceHeight * width / height;
+                        lp.height = surfaceHeight;
+                    }
+                    Log.i(TAG, "video rect is " + lp.width + "x" + lp.height);
+                    mSurfaceView.setLayoutParams(lp);
+                } else {
+                    mPresentation.updateSize(width, height);
+                }
+            }
+        }
+
+        private void updateContents() {
+            // Show either the content in the main activity or the content in the presentation
+            if (mPresentation != null) {
+                mLayout.setVisibility(View.GONE);
+                mSurfaceView.setVisibility(View.GONE);
+            } else {
+                mLayout.setVisibility(View.VISIBLE);
+                mSurfaceView.setVisibility(View.VISIBLE);
+            }
+        }
+
+        // Listens for when presentations are dismissed.
+        private final DialogInterface.OnDismissListener mOnDismissListener =
+                new DialogInterface.OnDismissListener() {
+            @Override
+            public void onDismiss(DialogInterface dialog) {
+                if (dialog == mPresentation) {
+                    Log.i(TAG, "Presentation dismissed.");
+                    mPresentation = null;
+                    updateContents();
+                }
+            }
+        };
+
+        // Presentation
+        private final class DemoPresentation extends Presentation {
+            private SurfaceView mPresentationSurfaceView;
+
+            public DemoPresentation(Context context, Display display) {
+                super(context, display);
+            }
+
+            @Override
+            protected void onCreate(Bundle savedInstanceState) {
+                // Be sure to call the super class.
+                super.onCreate(savedInstanceState);
+
+                // Inflate the layout.
+                setContentView(R.layout.sample_media_router_presentation);
+
+                // Set up the surface view.
+                mPresentationSurfaceView = (SurfaceView)findViewById(R.id.surface_view);
+                SurfaceHolder holder = mPresentationSurfaceView.getHolder();
+                holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+                holder.addCallback(SurfaceViewPlayer.this);
+                Log.i(TAG, "Presentation created");
+            }
+
+            public void updateSize(int width, int height) {
+                int surfaceHeight = getWindow().getDecorView().getHeight();
+                int surfaceWidth = getWindow().getDecorView().getWidth();
+                ViewGroup.LayoutParams lp = mPresentationSurfaceView.getLayoutParams();
+                if (surfaceWidth * height < surfaceHeight * width) {
+                    lp.width = surfaceWidth;
+                    lp.height = surfaceWidth * height / width;
+                } else {
+                    lp.width = surfaceHeight * width / height;
+                    lp.height = surfaceHeight;
+                }
+                Log.i(TAG, "Presentation video rect is " + lp.width + "x" + lp.height);
+                mPresentationSurfaceView.setLayoutParams(lp);
+            }
+        }
+    }
+
+    /**
+     * Handles playback of a single media item using MediaPlayer in
+     * OverlayDisplayWindow.
+     */
+    public static class OverlayPlayer extends LocalPlayer implements
+            OverlayDisplayWindow.OverlayWindowListener {
+        private static final String TAG = "OverlayPlayer";
+        private final OverlayDisplayWindow mOverlay;
+
+        public OverlayPlayer(Context context) {
+            super(context);
+
+            mOverlay = OverlayDisplayWindow.create(getContext(),
+                    getContext().getResources().getString(
+                            R.string.sample_media_route_provider_remote),
+                    1024, 768, Gravity.CENTER);
+
+            mOverlay.setOverlayWindowListener(this);
+        }
+
+        @Override
+        public void connect(RouteInfo route) {
+            super.connect(route);
+            mOverlay.show();
+        }
+
+        @Override
+        public void release() {
+            super.release();
+            mOverlay.dismiss();
+        }
+
+        @Override
+        protected void updateSize() {
+            int width = getVideoWidth();
+            int height = getVideoHeight();
+            if (width > 0 && height > 0) {
+                mOverlay.updateAspectRatio(width, height);
+            }
+        }
+
+        // OverlayDisplayWindow.OverlayWindowListener
+        @Override
+        public void onWindowCreated(Surface surface) {
+            setSurface(surface);
+        }
+
+        @Override
+        public void onWindowCreated(SurfaceHolder surfaceHolder) {
+            setSurface(surfaceHolder);
+        }
+
+        @Override
+        public void onWindowDestroyed() {
+            setSurface((SurfaceHolder)null);
+        }
+    }
+}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/MainActivity.java b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/MainActivity.java
new file mode 100644
index 0000000..ad283dd
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/MainActivity.java
@@ -0,0 +1,724 @@
+/*
+ * Copyright (C) 2013 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.mediarouter.player;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.RemoteControlClient;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.app.MediaRouteActionProvider;
+import android.support.v7.app.MediaRouteDiscoveryFragment;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaRouteSelector;
+import android.support.v7.media.MediaRouter;
+import android.support.v7.media.MediaRouter.Callback;
+import android.support.v7.media.MediaRouter.ProviderInfo;
+import android.support.v7.media.MediaRouter.RouteInfo;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ImageButton;
+import android.widget.ListView;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TabHost;
+import android.widget.TabHost.OnTabChangeListener;
+import android.widget.TabHost.TabSpec;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.example.android.mediarouter.R;
+import com.example.android.mediarouter.provider.SampleMediaRouteProvider;
+
+import java.io.File;
+
+/**
+ * <h3>Media Router Support Activity</h3>
+ * <p/>
+ * <p>
+ * This demonstrates how to use the {@link MediaRouter} API to build an
+ * application that allows the user to send content to various rendering
+ * targets.
+ * </p>
+ */
+public class MainActivity extends ActionBarActivity {
+    private static final String TAG = "MainActivity";
+    private static final String DISCOVERY_FRAGMENT_TAG = "DiscoveryFragment";
+
+    private MediaRouter mMediaRouter;
+    private MediaRouteSelector mSelector;
+    private LibraryAdapter mLibraryItems;
+    private PlaylistAdapter mPlayListItems;
+    private TextView mInfoTextView;
+    private ListView mLibraryView;
+    private ListView mPlayListView;
+    private ImageButton mPauseResumeButton;
+    private ImageButton mStopButton;
+    private SeekBar mSeekBar;
+    private boolean mPaused;
+    private boolean mNeedResume;
+    private boolean mSeeking;
+
+    private RemoteControlClient mRemoteControlClient;
+    private ComponentName mEventReceiver;
+    private AudioManager mAudioManager;
+    private PendingIntent mMediaPendingIntent;
+
+    private final Handler mHandler = new Handler();
+    private final Runnable mUpdateSeekRunnable = new Runnable() {
+        @Override
+        public void run() {
+            updateProgress();
+            // update UI every 1 second
+            mHandler.postDelayed(this, 1000);
+        }
+    };
+
+    private final SessionManager mSessionManager = new SessionManager("app");
+    private Player mPlayer;
+
+    private final MediaRouter.Callback mMediaRouterCB = new MediaRouter.Callback() {
+        // Return a custom callback that will simply log all of the route events
+        // for demonstration purposes.
+        @Override
+        public void onRouteAdded(MediaRouter router, RouteInfo route) {
+            Log.d(TAG, "onRouteAdded: route=" + route);
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, RouteInfo route) {
+            Log.d(TAG, "onRouteChanged: route=" + route);
+        }
+
+        @Override
+        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
+            Log.d(TAG, "onRouteRemoved: route=" + route);
+        }
+
+        @Override
+        public void onRouteSelected(MediaRouter router, RouteInfo route) {
+            Log.d(TAG, "onRouteSelected: route=" + route);
+
+            mPlayer = Player.create(MainActivity.this, route);
+            mPlayer.updatePresentation();
+            mSessionManager.setPlayer(mPlayer);
+            mSessionManager.unsuspend();
+
+            registerRemoteControlClient();
+            updateUi();
+        }
+
+        @Override
+        public void onRouteUnselected(MediaRouter router, RouteInfo route) {
+            Log.d(TAG, "onRouteUnselected: route=" + route);
+            unregisterRemoteControlClient();
+
+            PlaylistItem item = getCheckedPlaylistItem();
+            if (item != null) {
+                long pos = item.getPosition() +
+                        (mPaused ? 0 : (SystemClock.elapsedRealtime() - item.getTimestamp()));
+                mSessionManager.suspend(pos);
+            }
+            mPlayer.updatePresentation();
+            mPlayer.release();
+        }
+
+        @Override
+        public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
+            Log.d(TAG, "onRouteVolumeChanged: route=" + route);
+        }
+
+        @Override
+        public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
+            Log.d(TAG, "onRoutePresentationDisplayChanged: route=" + route);
+            mPlayer.updatePresentation();
+        }
+
+        @Override
+        public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
+            Log.d(TAG, "onRouteProviderAdded: provider=" + provider);
+        }
+
+        @Override
+        public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
+            Log.d(TAG, "onRouteProviderRemoved: provider=" + provider);
+        }
+
+        @Override
+        public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
+            Log.d(TAG, "onRouteProviderChanged: provider=" + provider);
+        }
+    };
+
+    private final OnAudioFocusChangeListener mAfChangeListener = new OnAudioFocusChangeListener() {
+        @Override
+        public void onAudioFocusChange(int focusChange) {
+            if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
+                Log.d(TAG, "onAudioFocusChange: LOSS_TRANSIENT");
+            } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+                Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_GAIN");
+            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+                Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS");
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        // Be sure to call the super class.
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            mPlayer = (Player) savedInstanceState.getSerializable("mPlayer");
+        }
+
+        // Get the media router service.
+        mMediaRouter = MediaRouter.getInstance(this);
+
+        // Create a route selector for the type of routes that we care about.
+        mSelector =
+                new MediaRouteSelector.Builder().addControlCategory(MediaControlIntent
+                        .CATEGORY_LIVE_AUDIO).addControlCategory(MediaControlIntent
+                        .CATEGORY_LIVE_VIDEO).addControlCategory(MediaControlIntent
+                        .CATEGORY_REMOTE_PLAYBACK).addControlCategory(SampleMediaRouteProvider
+                        .CATEGORY_SAMPLE_ROUTE).build();
+
+        // Add a fragment to take care of media route discovery.
+        // This fragment automatically adds or removes a callback whenever the activity
+        // is started or stopped.
+        FragmentManager fm = getSupportFragmentManager();
+        DiscoveryFragment fragment =
+                (DiscoveryFragment) fm.findFragmentByTag(DISCOVERY_FRAGMENT_TAG);
+        if (fragment == null) {
+            fragment = new DiscoveryFragment(mMediaRouterCB);
+            fragment.setRouteSelector(mSelector);
+            fm.beginTransaction().add(fragment, DISCOVERY_FRAGMENT_TAG).commit();
+        } else {
+            fragment.setCallback(mMediaRouterCB);
+            fragment.setRouteSelector(mSelector);
+        }
+
+        // Populate an array adapter with streaming media items.
+        String[] mediaNames = getResources().getStringArray(R.array.media_names);
+        String[] mediaUris = getResources().getStringArray(R.array.media_uris);
+        mLibraryItems = new LibraryAdapter();
+        for (int i = 0; i < mediaNames.length; i++) {
+            mLibraryItems.add(new MediaItem(
+                    "[streaming] " + mediaNames[i], Uri.parse(mediaUris[i]), "video/mp4"));
+        }
+
+        // Scan local external storage directory for media files.
+        File externalDir = Environment.getExternalStorageDirectory();
+        if (externalDir != null) {
+            File list[] = externalDir.listFiles();
+            if (list != null) {
+                for (int i = 0; i < list.length; i++) {
+                    String filename = list[i].getName();
+                    if (filename.matches(".*\\.(m4v|mp4)")) {
+                        mLibraryItems.add(new MediaItem(
+                                "[local] " + filename, Uri.fromFile(list[i]), "video/mp4"));
+                    }
+                }
+            }
+        }
+
+        mPlayListItems = new PlaylistAdapter();
+
+        // Initialize the layout.
+        setContentView(R.layout.sample_media_router);
+
+        TabHost tabHost = (TabHost) findViewById(R.id.tabHost);
+        tabHost.setup();
+        String tabName = getResources().getString(R.string.library_tab_text);
+        TabSpec spec1 = tabHost.newTabSpec(tabName);
+        spec1.setContent(R.id.tab1);
+        spec1.setIndicator(tabName);
+
+        tabName = getResources().getString(R.string.playlist_tab_text);
+        TabSpec spec2 = tabHost.newTabSpec(tabName);
+        spec2.setIndicator(tabName);
+        spec2.setContent(R.id.tab2);
+
+        tabName = getResources().getString(R.string.statistics_tab_text);
+        TabSpec spec3 = tabHost.newTabSpec(tabName);
+        spec3.setIndicator(tabName);
+        spec3.setContent(R.id.tab3);
+
+        tabHost.addTab(spec1);
+        tabHost.addTab(spec2);
+        tabHost.addTab(spec3);
+        tabHost.setOnTabChangedListener(new OnTabChangeListener() {
+            @Override
+            public void onTabChanged(String arg0) {
+                updateUi();
+            }
+        });
+
+        mLibraryView = (ListView) findViewById(R.id.media);
+        mLibraryView.setAdapter(mLibraryItems);
+        mLibraryView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+        mLibraryView.setOnItemClickListener(new OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                updateButtons();
+            }
+        });
+
+        mPlayListView = (ListView) findViewById(R.id.playlist);
+        mPlayListView.setAdapter(mPlayListItems);
+        mPlayListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+        mPlayListView.setOnItemClickListener(new OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                updateButtons();
+            }
+        });
+
+        mInfoTextView = (TextView) findViewById(R.id.info);
+
+        mPauseResumeButton = (ImageButton) findViewById(R.id.pause_resume_button);
+        mPauseResumeButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mPaused = !mPaused;
+                if (mPaused) {
+                    mSessionManager.pause();
+                } else {
+                    mSessionManager.resume();
+                }
+            }
+        });
+
+        mStopButton = (ImageButton) findViewById(R.id.stop_button);
+        mStopButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mPaused = false;
+                mSessionManager.stop();
+            }
+        });
+
+        mSeekBar = (SeekBar) findViewById(R.id.seekbar);
+        mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                PlaylistItem item = getCheckedPlaylistItem();
+                if (fromUser && item != null && item.getDuration() > 0) {
+                    long pos = progress * item.getDuration() / 100;
+                    mSessionManager.seek(item.getItemId(), pos);
+                    item.setPosition(pos);
+                    item.setTimestamp(SystemClock.elapsedRealtime());
+                }
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+                mSeeking = true;
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+                mSeeking = false;
+                updateUi();
+            }
+        });
+
+        // Schedule Ui update
+        mHandler.postDelayed(mUpdateSeekRunnable, 1000);
+
+        // Build the PendingIntent for the remote control client
+        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        mEventReceiver =
+                new ComponentName(getPackageName(), SampleMediaButtonReceiver.class.getName());
+        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        mediaButtonIntent.setComponent(mEventReceiver);
+        mMediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
+
+        // Create and register the remote control client
+        registerRemoteControlClient();
+
+        // Set up playback manager and player
+        mPlayer = Player.create(MainActivity.this, mMediaRouter.getSelectedRoute());
+        mSessionManager.setPlayer(mPlayer);
+        mSessionManager.setCallback(new SessionManager.Callback() {
+            @Override
+            public void onStatusChanged() {
+                updateUi();
+            }
+
+            @Override
+            public void onItemChanged(PlaylistItem item) {
+            }
+        });
+
+        updateUi();
+    }
+
+    private void registerRemoteControlClient() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            // Create the RCC and register with AudioManager and MediaRouter
+            mAudioManager.requestAudioFocus(mAfChangeListener, AudioManager.STREAM_MUSIC,
+                    AudioManager.AUDIOFOCUS_GAIN);
+            mAudioManager.registerMediaButtonEventReceiver(mEventReceiver);
+            mRemoteControlClient = new RemoteControlClient(mMediaPendingIntent);
+            mAudioManager.registerRemoteControlClient(mRemoteControlClient);
+            mMediaRouter.addRemoteControlClient(mRemoteControlClient);
+            SampleMediaButtonReceiver.setActivity(MainActivity.this);
+            mRemoteControlClient.setTransportControlFlags(RemoteControlClient
+                    .FLAG_KEY_MEDIA_PLAY_PAUSE);
+            mRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
+        }
+    }
+
+    private void unregisterRemoteControlClient() {
+        // Unregister the RCC with AudioManager and MediaRouter
+        if (mRemoteControlClient != null) {
+            mRemoteControlClient.setTransportControlFlags(0);
+            mAudioManager.abandonAudioFocus(mAfChangeListener);
+            mAudioManager.unregisterMediaButtonEventReceiver(mEventReceiver);
+            mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
+            mMediaRouter.removeRemoteControlClient(mRemoteControlClient);
+            SampleMediaButtonReceiver.setActivity(null);
+            mRemoteControlClient = null;
+        }
+    }
+
+    public boolean handleMediaKey(KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+            switch (event.getKeyCode()) {
+                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: {
+                    Log.d(TAG, "Received Play/Pause event from RemoteControlClient");
+                    mPaused = !mPaused;
+                    if (mPaused) {
+                        mSessionManager.pause();
+                    } else {
+                        mSessionManager.resume();
+                    }
+                    return true;
+                }
+                case KeyEvent.KEYCODE_MEDIA_PLAY: {
+                    Log.d(TAG, "Received Play event from RemoteControlClient");
+                    if (mPaused) {
+                        mPaused = false;
+                        mSessionManager.resume();
+                    }
+                    return true;
+                }
+                case KeyEvent.KEYCODE_MEDIA_PAUSE: {
+                    Log.d(TAG, "Received Pause event from RemoteControlClient");
+                    if (!mPaused) {
+                        mPaused = true;
+                        mSessionManager.pause();
+                    }
+                    return true;
+                }
+                case KeyEvent.KEYCODE_MEDIA_STOP: {
+                    Log.d(TAG, "Received Stop event from RemoteControlClient");
+                    mPaused = false;
+                    mSessionManager.stop();
+                    return true;
+                }
+                default:
+                    break;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return handleMediaKey(event) || super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return handleMediaKey(event) || super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public void onStart() {
+        // Be sure to call the super class.
+        super.onStart();
+    }
+
+    @Override
+    public void onPause() {
+        // pause media player for local playback case only
+        if (!mPlayer.isRemotePlayback() && !mPaused) {
+            mNeedResume = true;
+            mSessionManager.pause();
+        }
+        super.onPause();
+    }
+
+    @Override
+    public void onResume() {
+        // resume media player for local playback case only
+        if (!mPlayer.isRemotePlayback() && mNeedResume) {
+            mSessionManager.resume();
+            mNeedResume = false;
+        }
+        super.onResume();
+    }
+
+    @Override
+    public void onDestroy() {
+        // Unregister the remote control client
+        unregisterRemoteControlClient();
+
+        mPaused = false;
+        mSessionManager.stop();
+        mPlayer.release();
+        super.onDestroy();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Be sure to call the super class.
+        super.onCreateOptionsMenu(menu);
+
+        // Inflate the menu and configure the media router action provider.
+        getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);
+
+        MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
+        MediaRouteActionProvider mediaRouteActionProvider =
+                (MediaRouteActionProvider) MenuItemCompat.getActionProvider(mediaRouteMenuItem);
+        mediaRouteActionProvider.setRouteSelector(mSelector);
+
+        // Return true to show the menu.
+        return true;
+    }
+
+    private void updateProgress() {
+        // Estimate content position from last status time and elapsed time.
+        // (Note this might be slightly out of sync with remote side, however
+        // it avoids frequent polling the MRP.)
+        int progress = 0;
+        PlaylistItem item = getCheckedPlaylistItem();
+        if (item != null) {
+            int state = item.getState();
+            long duration = item.getDuration();
+            if (duration <= 0) {
+                if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING ||
+                        state == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+                    mSessionManager.updateStatus();
+                }
+            } else {
+                long position = item.getPosition();
+                long timeDelta =
+                        mPaused ? 0 : (SystemClock.elapsedRealtime() - item.getTimestamp());
+                progress = (int) (100.0 * (position + timeDelta) / duration);
+            }
+        }
+        mSeekBar.setProgress(progress);
+    }
+
+    private void updateUi() {
+        updatePlaylist();
+        updateRouteDescription();
+        updateButtons();
+    }
+
+    private void updatePlaylist() {
+        mPlayListItems.clear();
+        for (PlaylistItem item : mSessionManager.getPlaylist()) {
+            mPlayListItems.add(item);
+        }
+        mPlayListView.invalidate();
+    }
+
+
+    private void updateRouteDescription() {
+        RouteInfo route = mMediaRouter.getSelectedRoute();
+        mInfoTextView.setText(
+                "Currently selected route:" + "\nName: " + route.getName() + "\nProvider: " +
+                        route.getProvider().getPackageName() + "\nDescription: " +
+                        route.getDescription() + "\nStatistics: " +
+                        mSessionManager.getStatistics());
+    }
+
+    private void updateButtons() {
+        MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
+        // show pause or resume icon depending on current state
+        mPauseResumeButton.setImageResource(
+                mPaused ? R.drawable.ic_action_play : R.drawable.ic_action_pause);
+        // disable pause/resume/stop if no session
+        mPauseResumeButton.setEnabled(mSessionManager.hasSession());
+        mStopButton.setEnabled(mSessionManager.hasSession());
+        // only enable seek bar when duration is known
+        PlaylistItem item = getCheckedPlaylistItem();
+        mSeekBar.setEnabled(item != null && item.getDuration() > 0);
+        if (mRemoteControlClient != null) {
+            mRemoteControlClient.setPlaybackState(mPaused ? RemoteControlClient.PLAYSTATE_PAUSED :
+                    RemoteControlClient.PLAYSTATE_PLAYING);
+        }
+    }
+
+    private PlaylistItem getCheckedPlaylistItem() {
+        int count = mPlayListView.getCount();
+        int index = mPlayListView.getCheckedItemPosition();
+        if (count > 0) {
+            if (index < 0 || index >= count) {
+                index = 0;
+                mPlayListView.setItemChecked(0, true);
+            }
+            return mPlayListItems.getItem(index);
+        }
+        return null;
+    }
+
+    public static final class DiscoveryFragment extends MediaRouteDiscoveryFragment {
+        private static final String TAG = "DiscoveryFragment";
+        private Callback mCallback;
+
+        public DiscoveryFragment() {
+            mCallback = null;
+        }
+
+        public DiscoveryFragment(Callback cb) {
+            mCallback = cb;
+        }
+
+        public void setCallback(Callback cb) {
+            mCallback = cb;
+        }
+
+        @Override
+        public Callback onCreateCallback() {
+            return mCallback;
+        }
+
+        @Override
+        public int onPrepareCallbackFlags() {
+            // Add the CALLBACK_FLAG_UNFILTERED_EVENTS flag to ensure that we will
+            // observe and log all route events including those that are for routes
+            // that do not match our selector.  This is only for demonstration purposes
+            // and should not be needed by most applications.
+            return super.onPrepareCallbackFlags() | MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS;
+        }
+    }
+
+    private static final class MediaItem {
+        public final String mName;
+        public final Uri mUri;
+        public final String mMime;
+
+        public MediaItem(String name, Uri uri, String mime) {
+            mName = name;
+            mUri = uri;
+            mMime = mime;
+        }
+
+        @Override
+        public String toString() {
+            return mName;
+        }
+    }
+
+    private final class LibraryAdapter extends ArrayAdapter<MediaItem> {
+        public LibraryAdapter() {
+            super(MainActivity.this, R.layout.media_item);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final View v;
+            if (convertView == null) {
+                v = getLayoutInflater().inflate(R.layout.media_item, null);
+            } else {
+                v = convertView;
+            }
+
+            final MediaItem item = getItem(position);
+
+            TextView tv = (TextView) v.findViewById(R.id.item_text);
+            tv.setText(item.mName);
+
+            ImageButton b = (ImageButton) v.findViewById(R.id.item_action);
+            b.setImageResource(R.drawable.ic_suggestions_add);
+            b.setTag(item);
+            b.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (item != null) {
+                        mSessionManager.add(item.mUri, item.mMime);
+                        Toast.makeText(MainActivity.this, R.string.playlist_item_added_text,
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+            });
+
+            return v;
+        }
+    }
+
+    private final class PlaylistAdapter extends ArrayAdapter<PlaylistItem> {
+        public PlaylistAdapter() {
+            super(MainActivity.this, R.layout.media_item);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final View v;
+            if (convertView == null) {
+                v = getLayoutInflater().inflate(R.layout.media_item, null);
+            } else {
+                v = convertView;
+            }
+
+            final PlaylistItem item = getItem(position);
+
+            TextView tv = (TextView) v.findViewById(R.id.item_text);
+            tv.setText(item.toString());
+
+            ImageButton b = (ImageButton) v.findViewById(R.id.item_action);
+            b.setImageResource(R.drawable.ic_suggestions_delete);
+            b.setTag(item);
+            b.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (item != null) {
+                        mSessionManager.remove(item.getItemId());
+                        Toast.makeText(MainActivity.this, R.string.playlist_item_removed_text,
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+            });
+
+            return v;
+        }
+    }
+}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/OverlayDisplayWindow.java b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/OverlayDisplayWindow.java
new file mode 100644
index 0000000..5834830
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/OverlayDisplayWindow.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2012 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.mediarouter.player;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.example.android.mediarouter.R;
+
+/**
+ * Manages an overlay display window, used for simulating remote playback.
+ */
+public abstract class OverlayDisplayWindow {
+    private static final String TAG = "OverlayDisplayWindow";
+    private static final boolean DEBUG = false;
+
+    private static final float WINDOW_ALPHA = 0.8f;
+    private static final float INITIAL_SCALE = 0.5f;
+    private static final float MIN_SCALE = 0.3f;
+    private static final float MAX_SCALE = 1.0f;
+
+    protected final Context mContext;
+    protected final String mName;
+    protected final int mWidth;
+    protected final int mHeight;
+    protected final int mGravity;
+    protected OverlayWindowListener mListener;
+
+    protected OverlayDisplayWindow(Context context, String name,
+            int width, int height, int gravity) {
+        mContext = context;
+        mName = name;
+        mWidth = width;
+        mHeight = height;
+        mGravity = gravity;
+    }
+
+    public static OverlayDisplayWindow create(Context context, String name,
+            int width, int height, int gravity) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return new JellybeanMr1Impl(context, name, width, height, gravity);
+        } else {
+            return new LegacyImpl(context, name, width, height, gravity);
+        }
+    }
+
+    public void setOverlayWindowListener(OverlayWindowListener listener) {
+        mListener = listener;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public abstract void show();
+
+    public abstract void dismiss();
+
+    public abstract void updateAspectRatio(int width, int height);
+
+    // Watches for significant changes in the overlay display window lifecycle.
+    public interface OverlayWindowListener {
+        public void onWindowCreated(Surface surface);
+        public void onWindowCreated(SurfaceHolder surfaceHolder);
+        public void onWindowDestroyed();
+    }
+
+    /**
+     * Implementation for older versions.
+     */
+    private static final class LegacyImpl extends OverlayDisplayWindow {
+        private final WindowManager mWindowManager;
+
+        private boolean mWindowVisible;
+        private SurfaceView mSurfaceView;
+
+        public LegacyImpl(Context context, String name,
+                int width, int height, int gravity) {
+            super(context, name, width, height, gravity);
+
+            mWindowManager = (WindowManager)context.getSystemService(
+                    Context.WINDOW_SERVICE);
+        }
+
+        @Override
+        public void show() {
+            if (!mWindowVisible) {
+                mSurfaceView = new SurfaceView(mContext);
+
+                Display display = mWindowManager.getDefaultDisplay();
+
+                WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                        WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+                params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+                params.alpha = WINDOW_ALPHA;
+                params.gravity = Gravity.LEFT | Gravity.BOTTOM;
+                params.setTitle(mName);
+
+                int width = (int)(display.getWidth() * INITIAL_SCALE);
+                int height = (int)(display.getHeight() * INITIAL_SCALE);
+                if (mWidth > mHeight) {
+                    height = mHeight * width / mWidth;
+                } else {
+                    width = mWidth * height / mHeight;
+                }
+                params.width = width;
+                params.height = height;
+
+                mWindowManager.addView(mSurfaceView, params);
+                mWindowVisible = true;
+
+                SurfaceHolder holder = mSurfaceView.getHolder();
+                holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+                mListener.onWindowCreated(holder);
+            }
+        }
+
+        @Override
+        public void dismiss() {
+            if (mWindowVisible) {
+                mListener.onWindowDestroyed();
+
+                mWindowManager.removeView(mSurfaceView);
+                mWindowVisible = false;
+            }
+        }
+
+        @Override
+        public void updateAspectRatio(int width, int height) {
+        }
+    }
+
+    /**
+     * Implementation for API version 17+.
+     */
+    private static final class JellybeanMr1Impl extends OverlayDisplayWindow {
+        // When true, disables support for moving and resizing the overlay.
+        // The window is made non-touchable, which makes it possible to
+        // directly interact with the content underneath.
+        private static final boolean DISABLE_MOVE_AND_RESIZE = false;
+
+        private final DisplayManager mDisplayManager;
+        private final WindowManager mWindowManager;
+
+        private final Display mDefaultDisplay;
+        private final DisplayMetrics mDefaultDisplayMetrics = new DisplayMetrics();
+
+        private View mWindowContent;
+        private WindowManager.LayoutParams mWindowParams;
+        private TextureView mTextureView;
+        private TextView mNameTextView;
+
+        private GestureDetector mGestureDetector;
+        private ScaleGestureDetector mScaleGestureDetector;
+
+        private boolean mWindowVisible;
+        private int mWindowX;
+        private int mWindowY;
+        private float mWindowScale;
+
+        private float mLiveTranslationX;
+        private float mLiveTranslationY;
+        private float mLiveScale = 1.0f;
+
+        public JellybeanMr1Impl(Context context, String name,
+                int width, int height, int gravity) {
+            super(context, name, width, height, gravity);
+
+            mDisplayManager = (DisplayManager)context.getSystemService(
+                    Context.DISPLAY_SERVICE);
+            mWindowManager = (WindowManager)context.getSystemService(
+                    Context.WINDOW_SERVICE);
+
+            mDefaultDisplay = mWindowManager.getDefaultDisplay();
+            updateDefaultDisplayInfo();
+
+            createWindow();
+        }
+
+        @Override
+        public void show() {
+            if (!mWindowVisible) {
+                mDisplayManager.registerDisplayListener(mDisplayListener, null);
+                if (!updateDefaultDisplayInfo()) {
+                    mDisplayManager.unregisterDisplayListener(mDisplayListener);
+                    return;
+                }
+
+                clearLiveState();
+                updateWindowParams();
+                mWindowManager.addView(mWindowContent, mWindowParams);
+                mWindowVisible = true;
+            }
+        }
+
+        @Override
+        public void dismiss() {
+            if (mWindowVisible) {
+                mDisplayManager.unregisterDisplayListener(mDisplayListener);
+                mWindowManager.removeView(mWindowContent);
+                mWindowVisible = false;
+            }
+        }
+
+        @Override
+        public void updateAspectRatio(int width, int height) {
+            if (mWidth * height < mHeight * width) {
+                mTextureView.getLayoutParams().width = mWidth;
+                mTextureView.getLayoutParams().height = mWidth * height / width;
+            } else {
+                mTextureView.getLayoutParams().width = mHeight * width / height;
+                mTextureView.getLayoutParams().height = mHeight;
+            }
+            relayout();
+        }
+
+        private void relayout() {
+            if (mWindowVisible) {
+                updateWindowParams();
+                mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
+            }
+        }
+
+        private boolean updateDefaultDisplayInfo() {
+            mDefaultDisplay.getMetrics(mDefaultDisplayMetrics);
+            return true;
+        }
+
+        private void createWindow() {
+            LayoutInflater inflater = LayoutInflater.from(mContext);
+
+            mWindowContent = inflater.inflate(
+                    R.layout.overlay_display_window, null);
+            mWindowContent.setOnTouchListener(mOnTouchListener);
+
+            mTextureView = (TextureView)mWindowContent.findViewById(
+                    R.id.overlay_display_window_texture);
+            mTextureView.setPivotX(0);
+            mTextureView.setPivotY(0);
+            mTextureView.getLayoutParams().width = mWidth;
+            mTextureView.getLayoutParams().height = mHeight;
+            mTextureView.setOpaque(false);
+            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
+
+            mNameTextView = (TextView)mWindowContent.findViewById(
+                    R.id.overlay_display_window_title);
+            mNameTextView.setText(mName);
+
+            mWindowParams = new WindowManager.LayoutParams(
+                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+            mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+            if (DISABLE_MOVE_AND_RESIZE) {
+                mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+            }
+            mWindowParams.alpha = WINDOW_ALPHA;
+            mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
+            mWindowParams.setTitle(mName);
+
+            mGestureDetector = new GestureDetector(mContext, mOnGestureListener);
+            mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
+
+            // Set the initial position and scale.
+            // The position and scale will be clamped when the display is first shown.
+            mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ?
+                    0 : mDefaultDisplayMetrics.widthPixels;
+            mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ?
+                    0 : mDefaultDisplayMetrics.heightPixels;
+            Log.d(TAG, mDefaultDisplayMetrics.toString());
+            mWindowScale = INITIAL_SCALE;
+
+            // calculate and save initial settings
+            updateWindowParams();
+            saveWindowParams();
+        }
+
+        private void updateWindowParams() {
+            float scale = mWindowScale * mLiveScale;
+            scale = Math.min(scale, (float)mDefaultDisplayMetrics.widthPixels / mWidth);
+            scale = Math.min(scale, (float)mDefaultDisplayMetrics.heightPixels / mHeight);
+            scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
+
+            float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
+            int width = (int)(mWidth * scale);
+            int height = (int)(mHeight * scale);
+            int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale);
+            int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale);
+            x = Math.max(0, Math.min(x, mDefaultDisplayMetrics.widthPixels - width));
+            y = Math.max(0, Math.min(y, mDefaultDisplayMetrics.heightPixels - height));
+
+            if (DEBUG) {
+                Log.d(TAG, "updateWindowParams: scale=" + scale
+                        + ", offsetScale=" + offsetScale
+                        + ", x=" + x + ", y=" + y
+                        + ", width=" + width + ", height=" + height);
+            }
+
+            mTextureView.setScaleX(scale);
+            mTextureView.setScaleY(scale);
+
+            mTextureView.setTranslationX(
+                    (mWidth - mTextureView.getLayoutParams().width) * scale / 2);
+            mTextureView.setTranslationY(
+                    (mHeight - mTextureView.getLayoutParams().height) * scale / 2);
+
+            mWindowParams.x = x;
+            mWindowParams.y = y;
+            mWindowParams.width = width;
+            mWindowParams.height = height;
+        }
+
+        private void saveWindowParams() {
+            mWindowX = mWindowParams.x;
+            mWindowY = mWindowParams.y;
+            mWindowScale = mTextureView.getScaleX();
+            clearLiveState();
+        }
+
+        private void clearLiveState() {
+            mLiveTranslationX = 0f;
+            mLiveTranslationY = 0f;
+            mLiveScale = 1.0f;
+        }
+
+        private final DisplayManager.DisplayListener mDisplayListener =
+                new DisplayManager.DisplayListener() {
+            @Override
+            public void onDisplayAdded(int displayId) {
+            }
+
+            @Override
+            public void onDisplayChanged(int displayId) {
+                if (displayId == mDefaultDisplay.getDisplayId()) {
+                    if (updateDefaultDisplayInfo()) {
+                        relayout();
+                    } else {
+                        dismiss();
+                    }
+                }
+            }
+
+            @Override
+            public void onDisplayRemoved(int displayId) {
+                if (displayId == mDefaultDisplay.getDisplayId()) {
+                    dismiss();
+                }
+            }
+        };
+
+        private final SurfaceTextureListener mSurfaceTextureListener =
+                new SurfaceTextureListener() {
+            @Override
+            public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
+                    int width, int height) {
+                if (mListener != null) {
+                    mListener.onWindowCreated(new Surface(surfaceTexture));
+                }
+            }
+
+            @Override
+            public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+                if (mListener != null) {
+                    mListener.onWindowDestroyed();
+                }
+                return true;
+            }
+
+            @Override
+            public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
+                    int width, int height) {
+            }
+
+            @Override
+            public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
+            }
+        };
+
+        private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View view, MotionEvent event) {
+                // Work in screen coordinates.
+                final float oldX = event.getX();
+                final float oldY = event.getY();
+                event.setLocation(event.getRawX(), event.getRawY());
+
+                mGestureDetector.onTouchEvent(event);
+                mScaleGestureDetector.onTouchEvent(event);
+
+                switch (event.getActionMasked()) {
+                    case MotionEvent.ACTION_UP:
+                    case MotionEvent.ACTION_CANCEL:
+                        saveWindowParams();
+                        break;
+                }
+
+                // Revert to window coordinates.
+                event.setLocation(oldX, oldY);
+                return true;
+            }
+        };
+
+        private final GestureDetector.OnGestureListener mOnGestureListener =
+                new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onScroll(MotionEvent e1, MotionEvent e2,
+                    float distanceX, float distanceY) {
+                mLiveTranslationX -= distanceX;
+                mLiveTranslationY -= distanceY;
+                relayout();
+                return true;
+            }
+        };
+
+        private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
+                new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+            @Override
+            public boolean onScale(ScaleGestureDetector detector) {
+                mLiveScale *= detector.getScaleFactor();
+                relayout();
+                return true;
+            }
+        };
+    }
+}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/Player.java b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/Player.java
new file mode 100644
index 0000000..f842cf6
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/Player.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 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.mediarouter.player;
+
+import android.content.Context;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouter.RouteInfo;
+
+/**
+ * Abstraction of common playback operations of media items, such as play,
+ * seek, etc. Used by PlaybackManager as a backend to handle actual playback
+ * of media items.
+ */
+public abstract class Player {
+    protected Callback mCallback;
+
+    public abstract boolean isRemotePlayback();
+    public abstract boolean isQueuingSupported();
+
+    public abstract void connect(RouteInfo route);
+    public abstract void release();
+
+    // basic operations that are always supported
+    public abstract void play(final PlaylistItem item);
+    public abstract void seek(final PlaylistItem item);
+    public abstract void getStatus(final PlaylistItem item, final boolean update);
+    public abstract void pause();
+    public abstract void resume();
+    public abstract void stop();
+
+    // advanced queuing (enqueue & remove) are only supported
+    // if isQueuingSupported() returns true
+    public abstract void enqueue(final PlaylistItem item);
+    public abstract PlaylistItem remove(String iid);
+
+    // route statistics
+    public void updateStatistics() {}
+    public String getStatistics() { return ""; }
+
+    // presentation display
+    public void updatePresentation() {}
+
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    public static Player create(Context context, RouteInfo route) {
+        Player player;
+        if (route != null && route.supportsControlCategory(
+                MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
+            player = new RemotePlayer(context);
+        } else if (route != null) {
+            player = new LocalPlayer.SurfaceViewPlayer(context);
+        } else {
+            player = new LocalPlayer.OverlayPlayer(context);
+        }
+        player.connect(route);
+        return player;
+    }
+
+    public interface Callback {
+        void onError();
+        void onCompletion();
+        void onPlaylistChanged();
+        void onPlaylistReady();
+    }
+}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/PlaylistItem.java b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/PlaylistItem.java
new file mode 100644
index 0000000..a560538
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/PlaylistItem.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2013 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.mediarouter.player;
+
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.support.v7.media.MediaItemStatus;
+
+/**
+ * PlaylistItem helps keep track of the current status of an media item.
+ */
+public final class PlaylistItem {
+    // immutables
+    private final String mSessionId;
+    private final String mItemId;
+    private final Uri mUri;
+    private final String mMime;
+    private final PendingIntent mUpdateReceiver;
+    // changeable states
+    private int mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
+    private long mContentPosition;
+    private long mContentDuration;
+    private long mTimestamp;
+    private String mRemoteItemId;
+
+    public PlaylistItem(String qid, String iid, Uri uri, String mime, PendingIntent pi) {
+        mSessionId = qid;
+        mItemId = iid;
+        mUri = uri;
+        mMime = mime;
+        mUpdateReceiver = pi;
+        setTimestamp(SystemClock.elapsedRealtime());
+    }
+
+    public void setRemoteItemId(String riid) {
+        mRemoteItemId = riid;
+    }
+
+    public void setState(int state) {
+        mPlaybackState = state;
+    }
+
+    public void setPosition(long pos) {
+        mContentPosition = pos;
+    }
+
+    public void setTimestamp(long ts) {
+        mTimestamp = ts;
+    }
+
+    public void setDuration(long duration) {
+        mContentDuration = duration;
+    }
+
+    public String getSessionId() {
+        return mSessionId;
+    }
+
+    public String getItemId() {
+        return mItemId;
+    }
+
+    public String getRemoteItemId() {
+        return mRemoteItemId;
+    }
+
+    public Uri getUri() {
+        return mUri;
+    }
+
+    public PendingIntent getUpdateReceiver() {
+        return mUpdateReceiver;
+    }
+
+    public int getState() {
+        return mPlaybackState;
+    }
+
+    public long getPosition() {
+        return mContentPosition;
+    }
+
+    public long getDuration() {
+        return mContentDuration;
+    }
+
+    public long getTimestamp() {
+        return mTimestamp;
+    }
+
+    public MediaItemStatus getStatus() {
+        return new MediaItemStatus.Builder(mPlaybackState)
+            .setContentPosition(mContentPosition)
+            .setContentDuration(mContentDuration)
+            .setTimestamp(mTimestamp)
+            .build();
+    }
+
+    @Override
+    public String toString() {
+        String state[] = {
+            "PENDING",
+            "PLAYING",
+            "PAUSED",
+            "BUFFERING",
+            "FINISHED",
+            "CANCELED",
+            "INVALIDATED",
+            "ERROR"
+        };
+        return "[" + mSessionId + "|" + mItemId + "|"
+            + (mRemoteItemId != null ? mRemoteItemId : "-") + "|"
+            + state[mPlaybackState] + "] " + mUri.toString();
+    }
+}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/RemotePlayer.java b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/RemotePlayer.java
new file mode 100644
index 0000000..6726718
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/RemotePlayer.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2013 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.mediarouter.player;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaRouter.ControlRequestCallback;
+import android.support.v7.media.MediaRouter.RouteInfo;
+import android.support.v7.media.MediaSessionStatus;
+import android.support.v7.media.RemotePlaybackClient;
+import android.support.v7.media.RemotePlaybackClient.ItemActionCallback;
+import android.support.v7.media.RemotePlaybackClient.SessionActionCallback;
+import android.support.v7.media.RemotePlaybackClient.StatusCallback;
+import android.util.Log;
+
+import com.example.android.mediarouter.player.Player;
+import com.example.android.mediarouter.player.PlaylistItem;
+import com.example.android.mediarouter.provider.SampleMediaRouteProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles playback of media items using a remote route.
+ *
+ * This class is used as a backend by PlaybackManager to feed media items to
+ * the remote route. When the remote route doesn't support queuing, media items
+ * are fed one-at-a-time; otherwise media items are enqueued to the remote side.
+ */
+public class RemotePlayer extends Player {
+    private static final String TAG = "RemotePlayer";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private Context mContext;
+    private RouteInfo mRoute;
+    private boolean mEnqueuePending;
+    private String mStatsInfo = "";
+    private List<PlaylistItem> mTempQueue = new ArrayList<PlaylistItem>();
+
+    private RemotePlaybackClient mClient;
+    private StatusCallback mStatusCallback = new StatusCallback() {
+        @Override
+        public void onItemStatusChanged(Bundle data,
+                String sessionId, MediaSessionStatus sessionStatus,
+                String itemId, MediaItemStatus itemStatus) {
+            logStatus("onItemStatusChanged", sessionId, sessionStatus, itemId, itemStatus);
+            if (mCallback != null) {
+                if (itemStatus.getPlaybackState() ==
+                        MediaItemStatus.PLAYBACK_STATE_FINISHED) {
+                    mCallback.onCompletion();
+                } else if (itemStatus.getPlaybackState() ==
+                        MediaItemStatus.PLAYBACK_STATE_ERROR) {
+                    mCallback.onError();
+                }
+            }
+        }
+
+        @Override
+        public void onSessionStatusChanged(Bundle data,
+                String sessionId, MediaSessionStatus sessionStatus) {
+            logStatus("onSessionStatusChanged", sessionId, sessionStatus, null, null);
+            if (mCallback != null) {
+                mCallback.onPlaylistChanged();
+            }
+        }
+
+        @Override
+        public void onSessionChanged(String sessionId) {
+            if (DEBUG) {
+                Log.d(TAG, "onSessionChanged: sessionId=" + sessionId);
+            }
+        }
+    };
+
+    public RemotePlayer(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public boolean isRemotePlayback() {
+        return true;
+    }
+
+    @Override
+    public boolean isQueuingSupported() {
+        return mClient.isQueuingSupported();
+    }
+
+    @Override
+    public void connect(RouteInfo route) {
+        mRoute = route;
+        mClient = new RemotePlaybackClient(mContext, route);
+        mClient.setStatusCallback(mStatusCallback);
+
+        if (DEBUG) {
+            Log.d(TAG, "connected to: " + route
+                    + ", isRemotePlaybackSupported: " + mClient.isRemotePlaybackSupported()
+                    + ", isQueuingSupported: "+ mClient.isQueuingSupported());
+        }
+    }
+
+    @Override
+    public void release() {
+        mClient.release();
+
+        if (DEBUG) {
+            Log.d(TAG, "released.");
+        }
+    }
+
+    // basic playback operations that are always supported
+    @Override
+    public void play(final PlaylistItem item) {
+        if (DEBUG) {
+            Log.d(TAG, "play: item=" + item);
+        }
+        mClient.play(item.getUri(), "video/mp4", null, 0, null, new ItemActionCallback() {
+            @Override
+            public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+                    String itemId, MediaItemStatus itemStatus) {
+                logStatus("play: succeeded", sessionId, sessionStatus, itemId, itemStatus);
+                item.setRemoteItemId(itemId);
+                if (item.getPosition() > 0) {
+                    seekInternal(item);
+                }
+                if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+                    pause();
+                }
+                if (mCallback != null) {
+                    mCallback.onPlaylistChanged();
+                }
+            }
+
+            @Override
+            public void onError(String error, int code, Bundle data) {
+                logError("play: failed", error, code);
+            }
+        });
+    }
+
+    @Override
+    public void seek(final PlaylistItem item) {
+        seekInternal(item);
+    }
+
+    @Override
+    public void getStatus(final PlaylistItem item, final boolean update) {
+        if (!mClient.hasSession() || item.getRemoteItemId() == null) {
+            // if session is not valid or item id not assigend yet.
+            // just return, it's not fatal
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "getStatus: item=" + item + ", update=" + update);
+        }
+        mClient.getStatus(item.getRemoteItemId(), null, new ItemActionCallback() {
+            @Override
+            public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+                    String itemId, MediaItemStatus itemStatus) {
+                logStatus("getStatus: succeeded", sessionId, sessionStatus, itemId, itemStatus);
+                int state = itemStatus.getPlaybackState();
+                if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING
+                        || state == MediaItemStatus.PLAYBACK_STATE_PAUSED
+                        || state == MediaItemStatus.PLAYBACK_STATE_PENDING) {
+                    item.setState(state);
+                    item.setPosition(itemStatus.getContentPosition());
+                    item.setDuration(itemStatus.getContentDuration());
+                    item.setTimestamp(itemStatus.getTimestamp());
+                }
+                if (update && mCallback != null) {
+                    mCallback.onPlaylistReady();
+                }
+            }
+
+            @Override
+            public void onError(String error, int code, Bundle data) {
+                logError("getStatus: failed", error, code);
+                if (update && mCallback != null) {
+                    mCallback.onPlaylistReady();
+                }
+            }
+        });
+    }
+
+    @Override
+    public void pause() {
+        if (!mClient.hasSession()) {
+            // ignore if no session
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "pause");
+        }
+        mClient.pause(null, new SessionActionCallback() {
+            @Override
+            public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+                logStatus("pause: succeeded", sessionId, sessionStatus, null, null);
+                if (mCallback != null) {
+                    mCallback.onPlaylistChanged();
+                }
+            }
+
+            @Override
+            public void onError(String error, int code, Bundle data) {
+                logError("pause: failed", error, code);
+            }
+        });
+    }
+
+    @Override
+    public void resume() {
+        if (!mClient.hasSession()) {
+            // ignore if no session
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "resume");
+        }
+        mClient.resume(null, new SessionActionCallback() {
+            @Override
+            public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+                logStatus("resume: succeeded", sessionId, sessionStatus, null, null);
+                if (mCallback != null) {
+                    mCallback.onPlaylistChanged();
+                }
+            }
+
+            @Override
+            public void onError(String error, int code, Bundle data) {
+                logError("resume: failed", error, code);
+            }
+        });
+    }
+
+    @Override
+    public void stop() {
+        if (!mClient.hasSession()) {
+            // ignore if no session
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "stop");
+        }
+        mClient.stop(null, new SessionActionCallback() {
+            @Override
+            public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+                logStatus("stop: succeeded", sessionId, sessionStatus, null, null);
+                if (mClient.isSessionManagementSupported()) {
+                    endSession();
+                }
+                if (mCallback != null) {
+                    mCallback.onPlaylistChanged();
+                }
+            }
+
+            @Override
+            public void onError(String error, int code, Bundle data) {
+                logError("stop: failed", error, code);
+            }
+        });
+    }
+
+    // enqueue & remove are only supported if isQueuingSupported() returns true
+    @Override
+    public void enqueue(final PlaylistItem item) {
+        throwIfQueuingUnsupported();
+
+        if (!mClient.hasSession() && !mEnqueuePending) {
+            mEnqueuePending = true;
+            if (mClient.isSessionManagementSupported()) {
+                startSession(item);
+            } else {
+                enqueueInternal(item);
+            }
+        } else if (mEnqueuePending){
+            mTempQueue.add(item);
+        } else {
+            enqueueInternal(item);
+        }
+    }
+
+    @Override
+    public PlaylistItem remove(String itemId) {
+        throwIfNoSession();
+        throwIfQueuingUnsupported();
+
+        if (DEBUG) {
+            Log.d(TAG, "remove: itemId=" + itemId);
+        }
+        mClient.remove(itemId, null, new ItemActionCallback() {
+            @Override
+            public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+                    String itemId, MediaItemStatus itemStatus) {
+                logStatus("remove: succeeded", sessionId, sessionStatus, itemId, itemStatus);
+            }
+
+            @Override
+            public void onError(String error, int code, Bundle data) {
+                logError("remove: failed", error, code);
+            }
+        });
+
+        return null;
+    }
+
+    @Override
+    public void updateStatistics() {
+        // clear stats info first
+        mStatsInfo = "";
+
+        Intent intent = new Intent(SampleMediaRouteProvider.ACTION_GET_STATISTICS);
+        intent.addCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE);
+
+        if (mRoute != null && mRoute.supportsControlRequest(intent)) {
+            ControlRequestCallback callback = new ControlRequestCallback() {
+                @Override
+                public void onResult(Bundle data) {
+                    if (DEBUG) {
+                        Log.d(TAG, "getStatistics: succeeded: data=" + data);
+                    }
+                    if (data != null) {
+                        int playbackCount = data.getInt(
+                                SampleMediaRouteProvider.DATA_PLAYBACK_COUNT, -1);
+                        mStatsInfo = "Total playback count: " + playbackCount;
+                    }
+                }
+
+                @Override
+                public void onError(String error, Bundle data) {
+                    Log.d(TAG, "getStatistics: failed: error=" + error + ", data=" + data);
+                }
+            };
+
+            mRoute.sendControlRequest(intent, callback);
+        }
+    }
+
+    @Override
+    public String getStatistics() {
+        return mStatsInfo;
+    }
+
+    private void enqueueInternal(final PlaylistItem item) {
+        throwIfQueuingUnsupported();
+
+        if (DEBUG) {
+            Log.d(TAG, "enqueue: item=" + item);
+        }
+        mClient.enqueue(item.getUri(), "video/mp4", null, 0, null, new ItemActionCallback() {
+            @Override
+            public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+                    String itemId, MediaItemStatus itemStatus) {
+                logStatus("enqueue: succeeded", sessionId, sessionStatus, itemId, itemStatus);
+                item.setRemoteItemId(itemId);
+                if (item.getPosition() > 0) {
+                    seekInternal(item);
+                }
+                if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+                    pause();
+                }
+                if (mEnqueuePending) {
+                    mEnqueuePending = false;
+                    for (PlaylistItem item : mTempQueue) {
+                        enqueueInternal(item);
+                    }
+                    mTempQueue.clear();
+                }
+                if (mCallback != null) {
+                    mCallback.onPlaylistChanged();
+                }
+            }
+
+            @Override
+            public void onError(String error, int code, Bundle data) {
+                logError("enqueue: failed", error, code);
+                if (mCallback != null) {
+                    mCallback.onPlaylistChanged();
+                }
+            }
+        });
+    }
+
+    private void seekInternal(final PlaylistItem item) {
+        throwIfNoSession();
+
+        if (DEBUG) {
+            Log.d(TAG, "seek: item=" + item);
+        }
+        mClient.seek(item.getRemoteItemId(), item.getPosition(), null, new ItemActionCallback() {
+           @Override
+           public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus,
+                   String itemId, MediaItemStatus itemStatus) {
+               logStatus("seek: succeeded", sessionId, sessionStatus, itemId, itemStatus);
+               if (mCallback != null) {
+                   mCallback.onPlaylistChanged();
+               }
+           }
+
+           @Override
+           public void onError(String error, int code, Bundle data) {
+               logError("seek: failed", error, code);
+           }
+        });
+    }
+
+    private void startSession(final PlaylistItem item) {
+        mClient.startSession(null, new SessionActionCallback() {
+            @Override
+            public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+                logStatus("startSession: succeeded", sessionId, sessionStatus, null, null);
+                enqueueInternal(item);
+            }
+
+            @Override
+            public void onError(String error, int code, Bundle data) {
+                logError("startSession: failed", error, code);
+            }
+        });
+    }
+
+    private void endSession() {
+        mClient.endSession(null, new SessionActionCallback() {
+            @Override
+            public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
+                logStatus("endSession: succeeded", sessionId, sessionStatus, null, null);
+            }
+
+            @Override
+            public void onError(String error, int code, Bundle data) {
+                logError("endSession: failed", error, code);
+            }
+        });
+    }
+
+    private void logStatus(String message,
+            String sessionId, MediaSessionStatus sessionStatus,
+            String itemId, MediaItemStatus itemStatus) {
+        if (DEBUG) {
+            String result = "";
+            if (sessionId != null && sessionStatus != null) {
+                result += "sessionId=" + sessionId + ", sessionStatus=" + sessionStatus;
+            }
+            if (itemId != null & itemStatus != null) {
+                result += (result.isEmpty() ? "" : ", ")
+                        + "itemId=" + itemId + ", itemStatus=" + itemStatus;
+            }
+            Log.d(TAG, message + ": " + result);
+        }
+    }
+
+    private void logError(String message, String error, int code) {
+        Log.d(TAG, message + ": error=" + error + ", code=" + code);
+    }
+
+    private void throwIfNoSession() {
+        if (!mClient.hasSession()) {
+            throw new IllegalStateException("Session is invalid");
+        }
+    }
+
+    private void throwIfQueuingUnsupported() {
+        if (!isQueuingSupported()) {
+            throw new UnsupportedOperationException("Queuing is unsupported");
+        }
+    }
+}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SampleMediaButtonReceiver.java b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SampleMediaButtonReceiver.java
new file mode 100644
index 0000000..855bc1e
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SampleMediaButtonReceiver.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 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.mediarouter.player;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+/**
+ * Broadcast receiver for handling ACTION_MEDIA_BUTTON.
+ *
+ * This is needed to create the RemoteControlClient for controlling
+ * remote route volume in lock screen. It routes media key events back
+ * to main app activity MainActivity.
+ */
+public class SampleMediaButtonReceiver extends BroadcastReceiver {
+    private static final String TAG = "SampleMediaButtonReceiver";
+    private static MainActivity mActivity;
+
+    public static void setActivity(MainActivity activity) {
+        mActivity = activity;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (mActivity != null && Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
+            mActivity.handleMediaKey(
+                    (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT));
+        }
+    }
+}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SessionManager.java b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SessionManager.java
new file mode 100644
index 0000000..b6c5a46
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/player/SessionManager.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2013 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.mediarouter.player;
+
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaSessionStatus;
+import android.util.Log;
+
+import com.example.android.mediarouter.player.Player.Callback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SessionManager manages a media session as a queue. It supports common
+ * queuing behaviors such as enqueue/remove of media items, pause/resume/stop,
+ * etc.
+ *
+ * Actual playback of a single media item is abstracted into a Player interface,
+ * and is handled outside this class.
+ */
+public class SessionManager implements Callback {
+    private static final String TAG = "SessionManager";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private String mName;
+    private int mSessionId;
+    private int mItemId;
+    private boolean mPaused;
+    private boolean mSessionValid;
+    private Player mPlayer;
+    private Callback mCallback;
+    private List<PlaylistItem> mPlaylist = new ArrayList<PlaylistItem>();
+
+    public SessionManager(String name) {
+        mName = name;
+    }
+
+    public boolean hasSession() {
+        return mSessionValid;
+    }
+
+    public String getSessionId() {
+        return mSessionValid ? Integer.toString(mSessionId) : null;
+    }
+
+    public PlaylistItem getCurrentItem() {
+        return mPlaylist.isEmpty() ? null : mPlaylist.get(0);
+    }
+
+    // Get the cached statistic info from the player (will not update it)
+    public String getStatistics() {
+        checkPlayer();
+        return mPlayer.getStatistics();
+    }
+
+    // Returns the cached playlist (note this is not responsible for updating it)
+    public List<PlaylistItem> getPlaylist() {
+        return mPlaylist;
+    }
+
+    // Updates the playlist asynchronously, calls onPlaylistReady() when finished.
+    public void updateStatus() {
+        if (DEBUG) {
+            log("updateStatus");
+        }
+        checkPlayer();
+        // update the statistics first, so that the stats string is valid when
+        // onPlaylistReady() gets called in the end
+        mPlayer.updateStatistics();
+
+        if (mPlaylist.isEmpty()) {
+            // If queue is empty, don't forget to call onPlaylistReady()!
+            onPlaylistReady();
+        } else if (mPlayer.isQueuingSupported()) {
+            // If player supports queuing, get status of each item. Player is
+            // responsible to call onPlaylistReady() after last getStatus().
+            // (update=1 requires player to callback onPlaylistReady())
+            for (int i = 0; i < mPlaylist.size(); i++) {
+                PlaylistItem item = mPlaylist.get(i);
+                mPlayer.getStatus(item, (i == mPlaylist.size() - 1) /* update */);
+            }
+        } else {
+            // Otherwise, only need to get status for current item. Player is
+            // responsible to call onPlaylistReady() when finished.
+            mPlayer.getStatus(getCurrentItem(), true /* update */);
+        }
+    }
+
+    public PlaylistItem add(Uri uri, String mime) {
+        return add(uri, mime, null);
+    }
+
+    public PlaylistItem add(Uri uri, String mime, PendingIntent receiver) {
+        if (DEBUG) {
+            log("add: uri=" + uri + ", receiver=" + receiver);
+        }
+        // create new session if needed
+        startSession();
+        checkPlayerAndSession();
+
+        // append new item with initial status PLAYBACK_STATE_PENDING
+        PlaylistItem item = new PlaylistItem(
+                Integer.toString(mSessionId), Integer.toString(mItemId), uri, mime, receiver);
+        mPlaylist.add(item);
+        mItemId++;
+
+        // if player supports queuing, enqueue the item now
+        if (mPlayer.isQueuingSupported()) {
+            mPlayer.enqueue(item);
+        }
+        updatePlaybackState();
+        return item;
+    }
+
+    public PlaylistItem remove(String iid) {
+        if (DEBUG) {
+            log("remove: iid=" + iid);
+        }
+        checkPlayerAndSession();
+        return removeItem(iid, MediaItemStatus.PLAYBACK_STATE_CANCELED);
+    }
+
+    public PlaylistItem seek(String iid, long pos) {
+        if (DEBUG) {
+            log("seek: iid=" + iid +", pos=" + pos);
+        }
+        checkPlayerAndSession();
+        // seeking on pending items are not yet supported
+        checkItemCurrent(iid);
+
+        PlaylistItem item = getCurrentItem();
+        if (pos != item.getPosition()) {
+            item.setPosition(pos);
+            if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
+                    || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+                mPlayer.seek(item);
+            }
+        }
+        return item;
+    }
+
+    public PlaylistItem getStatus(String iid) {
+        checkPlayerAndSession();
+
+        // This should only be called for local player. Remote player is
+        // asynchronous, need to use updateStatus() instead.
+        if (mPlayer.isRemotePlayback()) {
+            throw new IllegalStateException(
+                    "getStatus should not be called on remote player!");
+        }
+
+        for (PlaylistItem item : mPlaylist) {
+            if (item.getItemId().equals(iid)) {
+                if (item == getCurrentItem()) {
+                    mPlayer.getStatus(item, false);
+                }
+                return item;
+            }
+        }
+        return null;
+    }
+
+    public void pause() {
+        if (DEBUG) {
+            log("pause");
+        }
+        mPaused = true;
+        updatePlaybackState();
+    }
+
+    public void resume() {
+        if (DEBUG) {
+            log("resume");
+        }
+        mPaused = false;
+        updatePlaybackState();
+    }
+
+    public void stop() {
+        if (DEBUG) {
+            log("stop");
+        }
+        mPlayer.stop();
+        mPlaylist.clear();
+        mPaused = false;
+        updateStatus();
+    }
+
+    public String startSession() {
+        if (!mSessionValid) {
+            mSessionId++;
+            mItemId = 0;
+            mPaused = false;
+            mSessionValid = true;
+            return Integer.toString(mSessionId);
+        }
+        return null;
+    }
+
+    public boolean endSession() {
+        if (mSessionValid) {
+            mSessionValid = false;
+            return true;
+        }
+        return false;
+    }
+
+    public MediaSessionStatus getSessionStatus(String sid) {
+        int sessionState = (sid != null && sid.equals(mSessionId)) ?
+                MediaSessionStatus.SESSION_STATE_ACTIVE :
+                    MediaSessionStatus.SESSION_STATE_INVALIDATED;
+
+        return new MediaSessionStatus.Builder(sessionState)
+                .setQueuePaused(mPaused)
+                .build();
+    }
+
+    // Suspend the playback manager. Put the current item back into PENDING
+    // state, and remember the current playback position. Called when switching
+    // to a different player (route).
+    public void suspend(long pos) {
+        for (PlaylistItem item : mPlaylist) {
+            item.setRemoteItemId(null);
+            item.setDuration(0);
+        }
+        PlaylistItem item = getCurrentItem();
+        if (DEBUG) {
+            log("suspend: item=" + item + ", pos=" + pos);
+        }
+        if (item != null) {
+            if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
+                    || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+                item.setState(MediaItemStatus.PLAYBACK_STATE_PENDING);
+                item.setPosition(pos);
+            }
+        }
+    }
+
+    // Unsuspend the playback manager. Restart playback on new player (route).
+    // This will resume playback of current item. Furthermore, if the new player
+    // supports queuing, playlist will be re-established on the remote player.
+    public void unsuspend() {
+        if (DEBUG) {
+            log("unsuspend");
+        }
+        if (mPlayer.isQueuingSupported()) {
+            for (PlaylistItem item : mPlaylist) {
+                mPlayer.enqueue(item);
+            }
+        }
+        updatePlaybackState();
+    }
+
+    // Player.Callback
+    @Override
+    public void onError() {
+        finishItem(true);
+    }
+
+    @Override
+    public void onCompletion() {
+        finishItem(false);
+    }
+
+    @Override
+    public void onPlaylistChanged() {
+        // Playlist has changed, update the cached playlist
+        updateStatus();
+    }
+
+    @Override
+    public void onPlaylistReady() {
+        // Notify activity to update Ui
+        if (mCallback != null) {
+            mCallback.onStatusChanged();
+        }
+    }
+
+    private void log(String message) {
+        Log.d(TAG, mName + ": " + message);
+    }
+
+    private void checkPlayer() {
+        if (mPlayer == null) {
+            throw new IllegalStateException("Player not set!");
+        }
+    }
+
+    private void checkSession() {
+        if (!mSessionValid) {
+            throw new IllegalStateException("Session not set!");
+        }
+    }
+
+    private void checkPlayerAndSession() {
+        checkPlayer();
+        checkSession();
+    }
+
+    private void checkItemCurrent(String iid) {
+        PlaylistItem item = getCurrentItem();
+        if (item == null || !item.getItemId().equals(iid)) {
+            throw new IllegalArgumentException("Item is not current!");
+        }
+    }
+
+    private void updatePlaybackState() {
+        PlaylistItem item = getCurrentItem();
+        if (item != null) {
+            if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
+                item.setState(mPaused ? MediaItemStatus.PLAYBACK_STATE_PAUSED
+                        : MediaItemStatus.PLAYBACK_STATE_PLAYING);
+                if (!mPlayer.isQueuingSupported()) {
+                    mPlayer.play(item);
+                }
+            } else if (mPaused && item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
+                mPlayer.pause();
+                item.setState(MediaItemStatus.PLAYBACK_STATE_PAUSED);
+            } else if (!mPaused && item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+                mPlayer.resume();
+                item.setState(MediaItemStatus.PLAYBACK_STATE_PLAYING);
+            }
+            // notify client that item playback status has changed
+            if (mCallback != null) {
+                mCallback.onItemChanged(item);
+            }
+        }
+        updateStatus();
+    }
+
+    private PlaylistItem removeItem(String iid, int state) {
+        checkPlayerAndSession();
+        List<PlaylistItem> queue =
+                new ArrayList<PlaylistItem>(mPlaylist.size());
+        PlaylistItem found = null;
+        for (PlaylistItem item : mPlaylist) {
+            if (iid.equals(item.getItemId())) {
+                if (mPlayer.isQueuingSupported()) {
+                    mPlayer.remove(item.getRemoteItemId());
+                } else if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
+                        || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED){
+                    mPlayer.stop();
+                }
+                item.setState(state);
+                found = item;
+                // notify client that item is now removed
+                if (mCallback != null) {
+                    mCallback.onItemChanged(found);
+                }
+            } else {
+                queue.add(item);
+            }
+        }
+        if (found != null) {
+            mPlaylist = queue;
+            updatePlaybackState();
+        } else {
+            log("item not found");
+        }
+        return found;
+    }
+
+    private void finishItem(boolean error) {
+        PlaylistItem item = getCurrentItem();
+        if (item != null) {
+            removeItem(item.getItemId(), error ?
+                    MediaItemStatus.PLAYBACK_STATE_ERROR :
+                        MediaItemStatus.PLAYBACK_STATE_FINISHED);
+            updateStatus();
+        }
+    }
+
+    // set the Player that this playback manager will interact with
+    public void setPlayer(Player player) {
+        mPlayer = player;
+        checkPlayer();
+        mPlayer.setCallback(this);
+    }
+
+    // provide a callback interface to tell the UI when significant state changes occur
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    @Override
+    public String toString() {
+        String result = "Media Queue: ";
+        if (!mPlaylist.isEmpty()) {
+            for (PlaylistItem item : mPlaylist) {
+                result += "\n" + item.toString();
+            }
+        } else {
+            result += "<empty>";
+        }
+        return result;
+    }
+
+    public interface Callback {
+        void onStatusChanged();
+        void onItemChanged(PlaylistItem item);
+    }
+}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProvider.java b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProvider.java
new file mode 100644
index 0000000..739e3ba
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProvider.java
@@ -0,0 +1,602 @@
+/*
+ * Copyright (C) 2013 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.mediarouter.provider;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentFilter.MalformedMimeTypeException;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.MediaRouter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouteDescriptor;
+import android.support.v7.media.MediaRouteProvider;
+import android.support.v7.media.MediaRouteProviderDescriptor;
+import android.support.v7.media.MediaRouter.ControlRequestCallback;
+import android.support.v7.media.MediaSessionStatus;
+import android.util.Log;
+
+import com.example.android.mediarouter.player.Player;
+import com.example.android.mediarouter.player.PlaylistItem;
+import com.example.android.mediarouter.R;
+import com.example.android.mediarouter.player.SessionManager;
+
+import java.util.ArrayList;
+
+/**
+ * Demonstrates how to create a custom media route provider.
+ *
+ * @see SampleMediaRouteProviderService
+ */
+public final class SampleMediaRouteProvider extends MediaRouteProvider {
+    private static final String TAG = "SampleMediaRouteProvider";
+
+    private static final String FIXED_VOLUME_ROUTE_ID = "fixed";
+    private static final String VARIABLE_VOLUME_BASIC_ROUTE_ID = "variable_basic";
+    private static final String VARIABLE_VOLUME_QUEUING_ROUTE_ID = "variable_queuing";
+    private static final String VARIABLE_VOLUME_SESSION_ROUTE_ID = "variable_session";
+    private static final int VOLUME_MAX = 10;
+
+    /**
+     * A custom media control intent category for special requests that are
+     * supported by this provider's routes.
+     */
+    public static final String CATEGORY_SAMPLE_ROUTE =
+            "com.example.android.mediarouteprovider.CATEGORY_SAMPLE_ROUTE";
+
+    /**
+     * A custom media control intent action for special requests that are
+     * supported by this provider's routes.
+     * <p>
+     * This particular request is designed to return a bundle of not very
+     * interesting statistics for demonstration purposes.
+     * </p>
+     *
+     * @see #DATA_PLAYBACK_COUNT
+     */
+    public static final String ACTION_GET_STATISTICS =
+            "com.example.android.mediarouteprovider.ACTION_GET_STATISTICS";
+
+    /**
+     * {@link #ACTION_GET_STATISTICS} result data: Number of times the
+     * playback action was invoked.
+     */
+    public static final String DATA_PLAYBACK_COUNT =
+            "com.example.android.mediarouteprovider.EXTRA_PLAYBACK_COUNT";
+
+    private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
+    private static final ArrayList<IntentFilter> CONTROL_FILTERS_QUEUING;
+    private static final ArrayList<IntentFilter> CONTROL_FILTERS_SESSION;
+
+    static {
+        IntentFilter f1 = new IntentFilter();
+        f1.addCategory(CATEGORY_SAMPLE_ROUTE);
+        f1.addAction(ACTION_GET_STATISTICS);
+
+        IntentFilter f2 = new IntentFilter();
+        f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+        f2.addAction(MediaControlIntent.ACTION_PLAY);
+        f2.addDataScheme("http");
+        f2.addDataScheme("https");
+        f2.addDataScheme("rtsp");
+        f2.addDataScheme("file");
+        addDataTypeUnchecked(f2, "video/*");
+
+        IntentFilter f3 = new IntentFilter();
+        f3.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+        f3.addAction(MediaControlIntent.ACTION_SEEK);
+        f3.addAction(MediaControlIntent.ACTION_GET_STATUS);
+        f3.addAction(MediaControlIntent.ACTION_PAUSE);
+        f3.addAction(MediaControlIntent.ACTION_RESUME);
+        f3.addAction(MediaControlIntent.ACTION_STOP);
+
+        IntentFilter f4 = new IntentFilter();
+        f4.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+        f4.addAction(MediaControlIntent.ACTION_ENQUEUE);
+        f4.addDataScheme("http");
+        f4.addDataScheme("https");
+        f4.addDataScheme("rtsp");
+        f4.addDataScheme("file");
+        addDataTypeUnchecked(f4, "video/*");
+
+        IntentFilter f5 = new IntentFilter();
+        f5.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+        f5.addAction(MediaControlIntent.ACTION_REMOVE);
+
+        IntentFilter f6 = new IntentFilter();
+        f6.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+        f6.addAction(MediaControlIntent.ACTION_START_SESSION);
+        f6.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS);
+        f6.addAction(MediaControlIntent.ACTION_END_SESSION);
+
+        CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
+        CONTROL_FILTERS_BASIC.add(f1);
+        CONTROL_FILTERS_BASIC.add(f2);
+        CONTROL_FILTERS_BASIC.add(f3);
+
+        CONTROL_FILTERS_QUEUING =
+                new ArrayList<IntentFilter>(CONTROL_FILTERS_BASIC);
+        CONTROL_FILTERS_QUEUING.add(f4);
+        CONTROL_FILTERS_QUEUING.add(f5);
+
+        CONTROL_FILTERS_SESSION =
+                new ArrayList<IntentFilter>(CONTROL_FILTERS_QUEUING);
+        CONTROL_FILTERS_SESSION.add(f6);
+    }
+
+    private static void addDataTypeUnchecked(IntentFilter filter, String type) {
+        try {
+            filter.addDataType(type);
+        } catch (MalformedMimeTypeException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private int mVolume = 5;
+    private int mEnqueueCount;
+
+    public SampleMediaRouteProvider(Context context) {
+        super(context);
+
+        publishRoutes();
+    }
+
+    @Override
+    public RouteController onCreateRouteController(String routeId) {
+        return new SampleRouteController(routeId);
+    }
+
+    private void publishRoutes() {
+        Resources r = getContext().getResources();
+
+        MediaRouteDescriptor routeDescriptor1 = new MediaRouteDescriptor.Builder(
+                FIXED_VOLUME_ROUTE_ID,
+                r.getString(R.string.fixed_volume_route_name))
+                .setDescription(r.getString(R.string.sample_route_description))
+                .addControlFilters(CONTROL_FILTERS_BASIC)
+                .setPlaybackStream(AudioManager.STREAM_MUSIC)
+                .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+                .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED)
+                .setVolume(VOLUME_MAX)
+                .build();
+
+        MediaRouteDescriptor routeDescriptor2 = new MediaRouteDescriptor.Builder(
+                VARIABLE_VOLUME_BASIC_ROUTE_ID,
+                r.getString(R.string.variable_volume_basic_route_name))
+                .setDescription(r.getString(R.string.sample_route_description))
+                .addControlFilters(CONTROL_FILTERS_BASIC)
+                .setPlaybackStream(AudioManager.STREAM_MUSIC)
+                .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+                .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+                .setVolumeMax(VOLUME_MAX)
+                .setVolume(mVolume)
+                .build();
+
+        MediaRouteDescriptor routeDescriptor3 = new MediaRouteDescriptor.Builder(
+                VARIABLE_VOLUME_QUEUING_ROUTE_ID,
+                r.getString(R.string.variable_volume_queuing_route_name))
+                .setDescription(r.getString(R.string.sample_route_description))
+                .addControlFilters(CONTROL_FILTERS_QUEUING)
+                .setPlaybackStream(AudioManager.STREAM_MUSIC)
+                .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+                .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+                .setVolumeMax(VOLUME_MAX)
+                .setVolume(mVolume)
+                .build();
+
+        MediaRouteDescriptor routeDescriptor4 = new MediaRouteDescriptor.Builder(
+                VARIABLE_VOLUME_SESSION_ROUTE_ID,
+                r.getString(R.string.variable_volume_session_route_name))
+                .setDescription(r.getString(R.string.sample_route_description))
+                .addControlFilters(CONTROL_FILTERS_SESSION)
+                .setPlaybackStream(AudioManager.STREAM_MUSIC)
+                .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+                .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+                .setVolumeMax(VOLUME_MAX)
+                .setVolume(mVolume)
+                .build();
+
+        MediaRouteProviderDescriptor providerDescriptor =
+                new MediaRouteProviderDescriptor.Builder()
+                .addRoute(routeDescriptor1)
+                .addRoute(routeDescriptor2)
+                .addRoute(routeDescriptor3)
+                .addRoute(routeDescriptor4)
+                .build();
+        setDescriptor(providerDescriptor);
+    }
+
+    private final class SampleRouteController extends MediaRouteProvider.RouteController {
+        private final String mRouteId;
+        private final SessionManager mSessionManager = new SessionManager("mrp");
+        private final Player mPlayer;
+        private PendingIntent mSessionReceiver;
+
+        public SampleRouteController(String routeId) {
+            mRouteId = routeId;
+            mPlayer = Player.create(getContext(), null);
+            mSessionManager.setPlayer(mPlayer);
+            mSessionManager.setCallback(new SessionManager.Callback() {
+                @Override
+                public void onStatusChanged() {
+                }
+
+                @Override
+                public void onItemChanged(PlaylistItem item) {
+                    handleStatusChange(item);
+                }
+            });
+            Log.d(TAG, mRouteId + ": Controller created");
+        }
+
+        @Override
+        public void onRelease() {
+            Log.d(TAG, mRouteId + ": Controller released");
+            mPlayer.release();
+        }
+
+        @Override
+        public void onSelect() {
+            Log.d(TAG, mRouteId + ": Selected");
+            mPlayer.connect(null);
+        }
+
+        @Override
+        public void onUnselect() {
+            Log.d(TAG, mRouteId + ": Unselected");
+            mPlayer.release();
+        }
+
+        @Override
+        public void onSetVolume(int volume) {
+            Log.d(TAG, mRouteId + ": Set volume to " + volume);
+            if (!mRouteId.equals(FIXED_VOLUME_ROUTE_ID)) {
+                setVolumeInternal(volume);
+            }
+        }
+
+        @Override
+        public void onUpdateVolume(int delta) {
+            Log.d(TAG, mRouteId + ": Update volume by " + delta);
+            if (!mRouteId.equals(FIXED_VOLUME_ROUTE_ID)) {
+                setVolumeInternal(mVolume + delta);
+            }
+        }
+
+        @Override
+        public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
+            Log.d(TAG, mRouteId + ": Received control request " + intent);
+            String action = intent.getAction();
+            if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
+                boolean success = false;
+                if (action.equals(MediaControlIntent.ACTION_PLAY)) {
+                    success = handlePlay(intent, callback);
+                } else if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
+                    success = handleEnqueue(intent, callback);
+                } else if (action.equals(MediaControlIntent.ACTION_REMOVE)) {
+                    success = handleRemove(intent, callback);
+                } else if (action.equals(MediaControlIntent.ACTION_SEEK)) {
+                    success = handleSeek(intent, callback);
+                } else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) {
+                    success = handleGetStatus(intent, callback);
+                } else if (action.equals(MediaControlIntent.ACTION_PAUSE)) {
+                    success = handlePause(intent, callback);
+                } else if (action.equals(MediaControlIntent.ACTION_RESUME)) {
+                    success = handleResume(intent, callback);
+                } else if (action.equals(MediaControlIntent.ACTION_STOP)) {
+                    success = handleStop(intent, callback);
+                } else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) {
+                    success = handleStartSession(intent, callback);
+                } else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) {
+                    success = handleGetSessionStatus(intent, callback);
+                } else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) {
+                    success = handleEndSession(intent, callback);
+                }
+                Log.d(TAG, mSessionManager.toString());
+                return success;
+            }
+
+            if (action.equals(ACTION_GET_STATISTICS)
+                    && intent.hasCategory(CATEGORY_SAMPLE_ROUTE)) {
+                Bundle data = new Bundle();
+                data.putInt(DATA_PLAYBACK_COUNT, mEnqueueCount);
+                if (callback != null) {
+                    callback.onResult(data);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        private void setVolumeInternal(int volume) {
+            if (volume >= 0 && volume <= VOLUME_MAX) {
+                mVolume = volume;
+                Log.d(TAG, mRouteId + ": New volume is " + mVolume);
+                AudioManager audioManager =
+                        (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE);
+                audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+                publishRoutes();
+            }
+        }
+
+        private boolean handlePlay(Intent intent, ControlRequestCallback callback) {
+            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+            if (sid != null && !sid.equals(mSessionManager.getSessionId())) {
+                Log.d(TAG, "handlePlay fails because of bad sid="+sid);
+                return false;
+            }
+            if (mSessionManager.hasSession()) {
+                mSessionManager.stop();
+            }
+            return handleEnqueue(intent, callback);
+        }
+
+        private boolean handleEnqueue(Intent intent, ControlRequestCallback callback) {
+            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+            if (sid != null && !sid.equals(mSessionManager.getSessionId())) {
+                Log.d(TAG, "handleEnqueue fails because of bad sid="+sid);
+                return false;
+            }
+
+            Uri uri = intent.getData();
+            if (uri == null) {
+                Log.d(TAG, "handleEnqueue fails because of bad uri="+uri);
+                return false;
+            }
+
+            boolean enqueue = intent.getAction().equals(MediaControlIntent.ACTION_ENQUEUE);
+            String mime = intent.getType();
+            long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
+            Bundle metadata = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_METADATA);
+            Bundle headers = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_HTTP_HEADERS);
+            PendingIntent receiver = (PendingIntent)intent.getParcelableExtra(
+                    MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER);
+
+            Log.d(TAG, mRouteId + ": Received " + (enqueue?"enqueue":"play") + " request"
+                    + ", uri=" + uri
+                    + ", mime=" + mime
+                    + ", sid=" + sid
+                    + ", pos=" + pos
+                    + ", metadata=" + metadata
+                    + ", headers=" + headers
+                    + ", receiver=" + receiver);
+            PlaylistItem item = mSessionManager.add(uri, mime, receiver);
+            if (callback != null) {
+                if (item != null) {
+                    Bundle result = new Bundle();
+                    result.putString(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
+                    result.putString(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
+                    result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
+                            item.getStatus().asBundle());
+                    callback.onResult(result);
+                } else {
+                    callback.onError("Failed to open " + uri.toString(), null);
+                }
+            }
+            mEnqueueCount +=1;
+            return true;
+        }
+
+        private boolean handleRemove(Intent intent, ControlRequestCallback callback) {
+            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+            if (sid == null || !sid.equals(mSessionManager.getSessionId())) {
+                return false;
+            }
+
+            String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
+            PlaylistItem item = mSessionManager.remove(iid);
+            if (callback != null) {
+                if (item != null) {
+                    Bundle result = new Bundle();
+                    result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
+                            item.getStatus().asBundle());
+                    callback.onResult(result);
+                } else {
+                    callback.onError("Failed to remove" +
+                            ", sid=" + sid + ", iid=" + iid, null);
+                }
+            }
+            return (item != null);
+        }
+
+        private boolean handleSeek(Intent intent, ControlRequestCallback callback) {
+            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+            if (sid == null || !sid.equals(mSessionManager.getSessionId())) {
+                return false;
+            }
+
+            String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
+            long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
+            Log.d(TAG, mRouteId + ": Received seek request, pos=" + pos);
+            PlaylistItem item = mSessionManager.seek(iid, pos);
+            if (callback != null) {
+                if (item != null) {
+                    Bundle result = new Bundle();
+                    result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
+                            item.getStatus().asBundle());
+                    callback.onResult(result);
+                } else {
+                    callback.onError("Failed to seek" +
+                            ", sid=" + sid + ", iid=" + iid + ", pos=" + pos, null);
+                }
+            }
+            return (item != null);
+        }
+
+        private boolean handleGetStatus(Intent intent, ControlRequestCallback callback) {
+            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+            String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
+            Log.d(TAG, mRouteId + ": Received getStatus request, sid=" + sid + ", iid=" + iid);
+            PlaylistItem item = mSessionManager.getStatus(iid);
+            if (callback != null) {
+                if (item != null) {
+                    Bundle result = new Bundle();
+                    result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
+                            item.getStatus().asBundle());
+                    callback.onResult(result);
+                } else {
+                    callback.onError("Failed to get status" +
+                            ", sid=" + sid + ", iid=" + iid, null);
+                }
+            }
+            return (item != null);
+        }
+
+        private boolean handlePause(Intent intent, ControlRequestCallback callback) {
+            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+            boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
+            mSessionManager.pause();
+            if (callback != null) {
+                if (success) {
+                    callback.onResult(new Bundle());
+                    handleSessionStatusChange(sid);
+                } else {
+                    callback.onError("Failed to pause, sid=" + sid, null);
+                }
+            }
+            return success;
+        }
+
+        private boolean handleResume(Intent intent, ControlRequestCallback callback) {
+            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+            boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
+            mSessionManager.resume();
+            if (callback != null) {
+                if (success) {
+                    callback.onResult(new Bundle());
+                    handleSessionStatusChange(sid);
+                } else {
+                    callback.onError("Failed to resume, sid=" + sid, null);
+                }
+            }
+            return success;
+        }
+
+        private boolean handleStop(Intent intent, ControlRequestCallback callback) {
+            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+            boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
+            mSessionManager.stop();
+            if (callback != null) {
+                if (success) {
+                    callback.onResult(new Bundle());
+                    handleSessionStatusChange(sid);
+                } else {
+                    callback.onError("Failed to stop, sid=" + sid, null);
+                }
+            }
+            return success;
+        }
+
+        private boolean handleStartSession(Intent intent, ControlRequestCallback callback) {
+            String sid = mSessionManager.startSession();
+            Log.d(TAG, "StartSession returns sessionId "+sid);
+            if (callback != null) {
+                if (sid != null) {
+                    Bundle result = new Bundle();
+                    result.putString(MediaControlIntent.EXTRA_SESSION_ID, sid);
+                    result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
+                            mSessionManager.getSessionStatus(sid).asBundle());
+                    callback.onResult(result);
+                    mSessionReceiver = (PendingIntent)intent.getParcelableExtra(
+                            MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER);
+                    handleSessionStatusChange(sid);
+                } else {
+                    callback.onError("Failed to start session.", null);
+                }
+            }
+            return (sid != null);
+        }
+
+        private boolean handleGetSessionStatus(Intent intent, ControlRequestCallback callback) {
+            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+
+            MediaSessionStatus sessionStatus = mSessionManager.getSessionStatus(sid);
+            if (callback != null) {
+                if (sessionStatus != null) {
+                    Bundle result = new Bundle();
+                    result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
+                            mSessionManager.getSessionStatus(sid).asBundle());
+                    callback.onResult(result);
+                } else {
+                    callback.onError("Failed to get session status, sid=" + sid, null);
+                }
+            }
+            return (sessionStatus != null);
+        }
+
+        private boolean handleEndSession(Intent intent, ControlRequestCallback callback) {
+            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+            boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId())
+                    && mSessionManager.endSession();
+            if (callback != null) {
+                if (success) {
+                    Bundle result = new Bundle();
+                    MediaSessionStatus sessionStatus = new MediaSessionStatus.Builder(
+                            MediaSessionStatus.SESSION_STATE_ENDED).build();
+                    result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS, sessionStatus.asBundle());
+                    callback.onResult(result);
+                    handleSessionStatusChange(sid);
+                    mSessionReceiver = null;
+                } else {
+                    callback.onError("Failed to end session, sid=" + sid, null);
+                }
+            }
+            return success;
+        }
+
+        private void handleStatusChange(PlaylistItem item) {
+            if (item == null) {
+                item = mSessionManager.getCurrentItem();
+            }
+            if (item != null) {
+                PendingIntent receiver = item.getUpdateReceiver();
+                if (receiver != null) {
+                    Intent intent = new Intent();
+                    intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
+                    intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
+                    intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS,
+                            item.getStatus().asBundle());
+                    try {
+                        receiver.send(getContext(), 0, intent);
+                        Log.d(TAG, mRouteId + ": Sending status update from provider");
+                    } catch (PendingIntent.CanceledException e) {
+                        Log.d(TAG, mRouteId + ": Failed to send status update!");
+                    }
+                }
+            }
+        }
+
+        private void handleSessionStatusChange(String sid) {
+            if (mSessionReceiver != null) {
+                Intent intent = new Intent();
+                intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sid);
+                intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS,
+                        mSessionManager.getSessionStatus(sid).asBundle());
+                try {
+                    mSessionReceiver.send(getContext(), 0, intent);
+                    Log.d(TAG, mRouteId + ": Sending session status update from provider");
+                } catch (PendingIntent.CanceledException e) {
+                    Log.d(TAG, mRouteId + ": Failed to send session status update!");
+                }
+            }
+        }
+    }
+}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProviderService.java b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProviderService.java
new file mode 100644
index 0000000..41a6cbd
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/java/com/example/android/mediarouter/provider/SampleMediaRouteProviderService.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 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.mediarouter.provider;
+
+import android.support.v7.media.MediaRouteProvider;
+import android.support.v7.media.MediaRouteProviderService;
+
+import com.example.android.mediarouter.provider.SampleMediaRouteProvider;
+
+/**
+ * Demonstrates how to register a custom media route provider service
+ * using the support library.
+ *
+ * @see com.example.android.mediarouter.provider.SampleMediaRouteProvider
+ */
+public class SampleMediaRouteProviderService extends MediaRouteProviderService {
+    @Override
+    public MediaRouteProvider onCreateMediaRouteProvider() {
+        return new SampleMediaRouteProvider(this);
+    }
+}
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_pause.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_pause.png
new file mode 100644
index 0000000..d30ba3c
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_pause.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_play.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_play.png
new file mode 100644
index 0000000..869f001
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_play.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_stop.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_stop.png
new file mode 100644
index 0000000..2b6c0f4
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_action_stop.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_launcher.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 0000000..d340114
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_pause.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_pause.png
new file mode 100644
index 0000000..1d465a4
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_pause.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_play.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_play.png
new file mode 100644
index 0000000..2746d17
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_play.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_stop.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_stop.png
new file mode 100644
index 0000000..a0ff136
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_media_stop.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_add.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_add.png
new file mode 100644
index 0000000..444e8a5
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_add.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_delete.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_delete.png
new file mode 100644
index 0000000..24d8f6a
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-hdpi/ic_menu_delete.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_pause.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_pause.png
new file mode 100644
index 0000000..2c96c7b
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_pause.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_play.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_play.png
new file mode 100644
index 0000000..5f3bf86
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_play.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_stop.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_stop.png
new file mode 100644
index 0000000..ddaf37a
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_action_stop.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_launcher.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 0000000..6af9981
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_pause.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_pause.png
new file mode 100644
index 0000000..3e6b2a1
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_pause.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_play.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_play.png
new file mode 100644
index 0000000..7966bbc
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_play.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_stop.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_stop.png
new file mode 100644
index 0000000..8ea7efe
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_media_stop.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_add.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_add.png
new file mode 100644
index 0000000..361c7c4
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_add.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_delete.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_delete.png
new file mode 100644
index 0000000..e2c8700
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-mdpi/ic_menu_delete.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_pause.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_pause.png
new file mode 100644
index 0000000..504389a
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_pause.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_play.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_play.png
new file mode 100644
index 0000000..7f709bb
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_play.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_stop.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_stop.png
new file mode 100644
index 0000000..2b07de4
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_action_stop.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_launcher.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..16bd612
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_pause.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_pause.png
new file mode 100644
index 0000000..c3b376a
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_pause.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_play.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_play.png
new file mode 100644
index 0000000..df59947
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_play.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_stop.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_stop.png
new file mode 100644
index 0000000..f42d525
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_action_stop.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_launcher.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..4b1d920
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_add.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_add.png
new file mode 100644
index 0000000..b880d40
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_add.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_delete.png b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_delete.png
new file mode 100644
index 0000000..f9e2702
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable-xxhdpi/ic_suggestions_delete.png
Binary files differ
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/drawable/list_background.xml b/media/MediaRouter/MediaRouterSample/src/main/res/drawable/list_background.xml
new file mode 100644
index 0000000..be7240b
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/drawable/list_background.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true"
+          android:drawable="@color/list_highlight_color" />
+    <item
+          android:drawable="@android:color/transparent" />
+</selector>
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/layout-land/grid_layout_2.xml b/media/MediaRouter/MediaRouterSample/src/main/res/layout-land/grid_layout_2.xml
new file mode 100644
index 0000000..da16123
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/layout-land/grid_layout_2.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<android.support.v7.widget.GridLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:useDefaultMargins="true"
+    android:alignmentMode="alignBounds"
+    android:rowOrderPreserved="false"
+    android:columnCount="4"
+    >
+    <TextView
+        android:text="Email setup"
+        android:textSize="32dip"
+        android:layout_columnSpan="4"
+        android:layout_gravity="center_horizontal"
+    />
+    <TextView
+        android:text="You can configure email in a few simple steps:"
+        android:textSize="16dip"
+        android:layout_columnSpan="4"
+        android:layout_gravity="left"
+    />
+    <TextView
+        android:text="Email address:"
+        android:layout_gravity="right"
+    />
+    <EditText
+        android:ems="10"
+    />
+    <TextView
+        android:text="Password:"
+        android:layout_column="0"
+        android:layout_gravity="right"
+    />
+    <EditText
+        android:ems="8"
+    />
+    <Button
+        android:text="Manual setup"
+        android:layout_row="5"
+        android:layout_column="3"
+    />
+    <Button
+        android:text="Next"
+        android:layout_column="3"
+        android:layout_gravity="fill_horizontal"
+    />
+</android.support.v7.widget.GridLayout>
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/layout/media_item.xml b/media/MediaRouter/MediaRouterSample/src/main/res/layout/media_item.xml
new file mode 100644
index 0000000..3a14861
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/layout/media_item.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<!-- Layout for list item in Library or Playlist view. Displays ImageButton
+     instead of radio button to the right of the item. -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        android:clickable="true"
+        android:background="@drawable/list_background"
+        android:gravity="center_vertical">
+
+    <ImageButton android:id="@+id/item_action"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:minWidth="48dp"
+        android:minHeight="48dp"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
+        android:background="@null"/>
+
+    <TextView android:id="@+id/item_text"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_centerVertical="true"
+        android:layout_toLeftOf="@id/item_action"
+        android:layout_gravity="left"
+        android:gravity="left"/>
+</RelativeLayout>
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/layout/overlay_display_window.xml b/media/MediaRouter/MediaRouterSample/src/main/res/layout/overlay_display_window.xml
new file mode 100644
index 0000000..36b4a0d
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/layout/overlay_display_window.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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"
+      android:background="#000000">
+    <TextureView android:id="@+id/overlay_display_window_texture"
+               android:layout_width="0px"
+               android:layout_height="0px" />
+    <TextView android:id="@+id/overlay_display_window_title"
+               android:layout_width="wrap_content"
+               android:layout_height="wrap_content"
+               android:layout_gravity="top|center_horizontal" />
+</FrameLayout>
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router.xml b/media/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router.xml
new file mode 100644
index 0000000..1618418
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<!-- See corresponding Java code MainActivity.java. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="vertical">
+        <!-- Tabs for media library, playlist and statistics -->
+        <TabHost android:id="@+id/tabHost"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1">
+            <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent">
+                <TabWidget android:id="@android:id/tabs"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+                <FrameLayout android:id="@android:id/tabcontent"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content">
+                    <LinearLayout android:id="@+id/tab1"
+                        android:layout_width="fill_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="vertical">
+                        <ListView android:id="@+id/media"
+                                  android:listSelector="@drawable/list_background"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1" />
+                    </LinearLayout>
+
+                    <LinearLayout android:id="@+id/tab2"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:orientation="vertical">
+                        <ListView android:id="@+id/playlist"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"/>
+                    </LinearLayout>
+
+                    <LinearLayout android:id="@+id/tab3"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:orientation="vertical">
+                        <TextView android:id="@+id/info"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:textAppearance="?android:attr/textAppearanceMedium"/>
+                    </LinearLayout>
+                </FrameLayout>
+            </LinearLayout>
+        </TabHost>
+
+        <!-- Control buttons for the currently selected route. -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="0">
+
+            <SeekBar android:id="@+id/seekbar"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 style="?android:attr/progressBarStyleHorizontal"
+                 android:max="100"
+                 android:progress="0"
+                 android:layout_gravity="center"
+                 android:layout_weight="1"/>
+
+            <ImageButton android:id="@+id/pause_resume_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="0"
+                android:layout_gravity="right"
+                android:minWidth="48dp"
+                android:minHeight="48dp"
+                android:background="@null"
+                android:src="@drawable/ic_action_pause" />
+
+            <ImageButton android:id="@+id/stop_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="0"
+                android:layout_gravity="right"
+                android:minWidth="48dp"
+                android:minHeight="48dp"
+                android:background="@null"
+                android:src="@drawable/ic_action_stop" />
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <!-- Some content for visual interest in the case where no presentation is showing. -->
+    <FrameLayout android:id="@+id/player"
+        android:background="#ff000000"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center">
+            <SurfaceView android:id="@+id/surface_view"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"/>
+        </LinearLayout>
+        <TextView
+            android:textColor="#ffaaaaaa"
+            android:text="@string/sample_media_route_activity_local"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|center_horizontal" />
+    </FrameLayout>
+</LinearLayout>
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router_presentation.xml b/media/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router_presentation.xml
new file mode 100644
index 0000000..f029627
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/layout/sample_media_router_presentation.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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 content that we show on secondary displays.
+     See corresponding Java code PresentationWithMediaRouterActivity.java. -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#ff000000">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:gravity="center">
+        <SurfaceView android:id="@+id/surface_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </LinearLayout>
+    <TextView
+        android:textColor="#ffaaaaaa"
+        android:text="@string/sample_media_route_activity_presentation"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|center_horizontal" />
+</FrameLayout>
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/menu/sample_media_router_menu.xml b/media/MediaRouter/MediaRouterSample/src/main/res/menu/sample_media_router_menu.xml
new file mode 100644
index 0000000..8057fa8
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/menu/sample_media_router_menu.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 Google Inc.
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item android:id="@+id/media_route_menu_item"
+        android:title="@string/media_route_menu_title"
+        app:showAsAction="always"
+        app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"/>
+</menu>
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/values/arrays.xml b/media/MediaRouter/MediaRouterSample/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..8d658eb
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/values/arrays.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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-array name="media_names">
+        <item>Big Buck Bunny</item>
+        <item>Elephants Dream</item>
+        <item>Sintel</item>
+        <item>Tears of Steel</item>
+    </string-array>
+
+    <string-array name="media_uris">
+        <item>http://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4</item>
+        <item>http://archive.org/download/ElephantsDream_277/elephant_dreams_640_512kb.mp4</item>
+        <item>http://archive.org/download/Sintel/sintel-2048-stereo_512kb.mp4</item>
+        <item>http://archive.org/download/Tears-of-Steel/tears_of_steel_720p.mp4</item>
+    </string-array>
+</resources>
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/values/colors.xml b/media/MediaRouter/MediaRouterSample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..13cf3b5
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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>
+    <drawable name="blue">#770000ff</drawable>
+    <color name="list_highlight_color">#ccc</color>
+</resources>
diff --git a/media/MediaRouter/MediaRouterSample/src/main/res/values/strings.xml b/media/MediaRouter/MediaRouterSample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c750ce1
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/src/main/res/values/strings.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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="sample_media_router_text">This activity demonstrates how to
+            use MediaRouter from the support library.  Select a route from the action bar.</string>
+    <string name="media_route_menu_title">Play on...</string>
+
+    <string name="library_tab_text">Library</string>
+    <string name="playlist_tab_text">Playlist</string>
+    <string name="statistics_tab_text">Statistics</string>
+
+    <string name="sample_media_route_provider_service">Media Route Provider Service Support Library Sample</string>
+    <string name="fixed_volume_route_name">Fixed Volume Remote Playback Route</string>
+    <string name="variable_volume_basic_route_name">Variable Volume (Basic) Remote Playback Route</string>
+    <string name="variable_volume_queuing_route_name">Variable Volume (Queuing) Remote Playback Route</string>
+    <string name="variable_volume_session_route_name">Variable Volume (Session) Remote Playback Route</string>
+    <string name="sample_route_description">Sample route</string>
+
+    <string name="sample_media_route_provider_remote">Remote Playback (Simulated)</string>
+    <string name="sample_media_route_activity_local">Local Playback</string>
+    <string name="sample_media_route_activity_presentation">Local Playback on Presentation Display</string>
+    <string name="playlist_item_added_text">Added to playlist!</string>
+    <string name="playlist_item_removed_text">Removed from playlist</string>
+
+</resources>
diff --git a/media/MediaRouter/MediaRouterSample/tests/AndroidManifest.xml b/media/MediaRouter/MediaRouterSample/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f60a1d7
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/tests/AndroidManifest.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 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.
+-->
+
+
+
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2013 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 name must be unique so suffix with "tests" so package loader doesn't ignore us -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.example.android.mediarouter.tests"
+          android:versionCode="1"
+          android:versionName="1.0">
+
+    <uses-sdk
+            android:minSdkVersion="18"
+            android:targetSdkVersion="19" />
+
+    <!-- We add an application tag here just so that we can indicate that
+         this package needs to link against the android.test library,
+         which is needed when building test cases. -->
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!--
+    Specifies the instrumentation test runner used to run the tests.
+    -->
+    <instrumentation
+            android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="com.example.android.mediarouter"
+            android:label="Tests for com.example.android.mediarouter" />
+
+</manifest>
\ No newline at end of file
diff --git a/media/MediaRouter/MediaRouterSample/tests/src/com/example/android/mediarouter/tests/SampleTests.java b/media/MediaRouter/MediaRouterSample/tests/src/com/example/android/mediarouter/tests/SampleTests.java
new file mode 100644
index 0000000..e62998a
--- /dev/null
+++ b/media/MediaRouter/MediaRouterSample/tests/src/com/example/android/mediarouter/tests/SampleTests.java
@@ -0,0 +1,79 @@
+/*
+* Copyright 2013 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.
+*/
+
+
+
+/*
+* Copyright (C) 2013 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.mediarouter.tests;
+
+import com.example.android.mediarouter.*;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+* Tests for MediaRouter sample.
+*/
+public class SampleTests extends ActivityInstrumentationTestCase2<MainActivity> {
+
+    private MainActivity mTestActivity;
+    private MediaRouterFragment mTestFragment;
+
+    public SampleTests() {
+        super(MainActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // Starts the activity under test using the default Intent with:
+        // action = {@link Intent#ACTION_MAIN}
+        // flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK}
+        // All other fields are null or empty.
+        mTestActivity = getActivity();
+        mTestFragment = (MediaRouterFragment)
+            mTestActivity.getSupportFragmentManager().getFragments().get(1);
+    }
+
+    /**
+    * Test if the test fixture has been set up correctly.
+    */
+    public void testPreconditions() {
+        //Try to add a message to add context to your assertions. These messages will be shown if
+        //a tests fails and make it easy to understand why a test failed
+        assertNotNull("mTestActivity is null", mTestActivity);
+        assertNotNull("mTestFragment is null", mTestFragment);
+    }
+
+    /**
+    * Add more tests below.
+    */
+
+}
\ No newline at end of file
diff --git a/media/MediaRouter/build.gradle b/media/MediaRouter/build.gradle
new file mode 100644
index 0000000..f9f6f65
--- /dev/null
+++ b/media/MediaRouter/build.gradle
@@ -0,0 +1,14 @@
+
+
+
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+  pathToBuild "../../../../build"
+  pathToSamplesCommon "../../common"
+}
+apply from: "../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/media/MediaRouter/buildSrc/build.gradle b/media/MediaRouter/buildSrc/build.gradle
new file mode 100644
index 0000000..29282af
--- /dev/null
+++ b/media/MediaRouter/buildSrc/build.gradle
@@ -0,0 +1,18 @@
+
+
+
+repositories {
+    mavenCentral()
+}
+dependencies {
+    compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+    main {
+        groovy {
+            srcDir new File(rootDir, "../../../../../build/buildSrc/src/main/groovy")
+        }
+    }
+}
+
diff --git a/media/MediaRouter/gradle/wrapper/gradle-wrapper.jar b/media/MediaRouter/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/media/MediaRouter/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/media/MediaRouter/gradle/wrapper/gradle-wrapper.properties b/media/MediaRouter/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..5de946b
--- /dev/null
+++ b/media/MediaRouter/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=http\://services.gradle.org/distributions/gradle-1.10-all.zip
diff --git a/media/MediaRouter/gradlew b/media/MediaRouter/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/media/MediaRouter/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/media/MediaRouter/gradlew.bat b/media/MediaRouter/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/media/MediaRouter/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/media/MediaRouter/settings.gradle b/media/MediaRouter/settings.gradle
new file mode 100644
index 0000000..fff93ae
--- /dev/null
+++ b/media/MediaRouter/settings.gradle
@@ -0,0 +1,4 @@
+
+
+
+include 'MediaRouterSample'
diff --git a/media/MediaRouter/template-params.xml b/media/MediaRouter/template-params.xml
new file mode 100644
index 0000000..f43a126
--- /dev/null
+++ b/media/MediaRouter/template-params.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 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.
+-->
+
+
+
+<sample>
+    <name>MediaRouter</name>
+    <group>NoGroup</group>
+    <package>com.example.android.mediarouter</package>
+
+
+
+    <!-- change minSdk if needed-->
+    <minSdk>4</minSdk>
+
+    <dependency>com.android.support:appcompat-v7:+</dependency>
+    <dependency>com.android.support:mediarouter-v7:+</dependency>
+
+
+    <strings>
+        <intro>
+            <![CDATA[Demonstrates how to create a custom media route provider.]]>
+        </intro>
+    </strings>
+
+    <template src="base"/>
+    <common src="logger"/>
+
+</sample>
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/AndroidManifest.xml b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..01b414d
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.adaptertransition"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <uses-sdk
+        android:minSdkVersion="19"
+        android:targetSdkVersion="19"/>
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name="com.example.android.adaptertransition.MainActivity"
+            android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/AdapterTransitionFragment.java b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/AdapterTransitionFragment.java
new file mode 100644
index 0000000..525ed40
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/AdapterTransitionFragment.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 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.adaptertransition;
+
+import android.os.Bundle;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.Fragment;
+import android.transition.AutoTransition;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.FrameLayout;
+import android.widget.GridView;
+import android.widget.ListView;
+
+/**
+ * Main screen for AdapterTransition sample.
+ */
+public class AdapterTransitionFragment extends Fragment implements Transition.TransitionListener {
+
+    /**
+     * Since the transition framework requires all relevant views in a view hierarchy to be marked
+     * with IDs, we use this ID to mark the root view.
+     */
+    private static final int ROOT_ID = 1;
+
+    /**
+     * This is where we place our AdapterView (ListView / GridView).
+     */
+    private FrameLayout mContent;
+
+    /**
+     * This is where we carry out the transition.
+     */
+    private FrameLayout mCover;
+
+    /**
+     * This list shows our contents. It can be ListView or GridView, and we toggle between them
+     * using the transition framework.
+     */
+    private AbsListView mAbsListView;
+
+    /**
+     * This is our contents.
+     */
+    private MeatAdapter mAdapter;
+
+    public static AdapterTransitionFragment newInstance() {
+        return new AdapterTransitionFragment();
+    }
+
+    public AdapterTransitionFragment() {
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        // We use a ListView at first
+        mAbsListView = (AbsListView) inflater.inflate(R.layout.fragment_meat_list, container, false);
+        mAdapter = new MeatAdapter(inflater, R.layout.item_meat_list);
+        return inflater.inflate(R.layout.fragment_adapter_transition, container, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        // Retaining references for FrameLayouts that we use later.
+        mContent = (FrameLayout) view.findViewById(R.id.content);
+        mCover = (FrameLayout) view.findViewById(R.id.cover);
+        // We are attaching the list to the screen here.
+        mAbsListView.setAdapter(mAdapter);
+        mContent.addView(mAbsListView);
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        inflater.inflate(R.menu.fragment_adapter_transition, menu);
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        // We change the look of the icon every time the user toggles between list and grid.
+        MenuItem item = menu.findItem(R.id.action_toggle);
+        if (null != item) {
+            if (mAbsListView instanceof ListView) {
+                item.setIcon(R.drawable.ic_action_grid);
+            } else {
+                item.setIcon(R.drawable.ic_action_list);
+            }
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.action_toggle: {
+                toggle();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onTransitionStart(Transition transition) {
+    }
+
+    // BEGIN_INCLUDE(on_transition_end)
+    @Override
+    public void onTransitionEnd(Transition transition) {
+        // When the transition ends, we remove all the views from the overlay and hide it.
+        mCover.removeAllViews();
+        mCover.setVisibility(View.INVISIBLE);
+    }
+    // END_INCLUDE(on_transition_end)
+
+    @Override
+    public void onTransitionCancel(Transition transition) {
+    }
+
+    @Override
+    public void onTransitionPause(Transition transition) {
+    }
+
+    @Override
+    public void onTransitionResume(Transition transition) {
+    }
+
+    /**
+     * Toggle the UI between ListView and GridView.
+     */
+    private void toggle() {
+        // We use mCover as the overlay on which we carry out the transition.
+        mCover.setVisibility(View.VISIBLE);
+        // This FrameLayout holds all the visible views in the current list or grid. We use this as
+        // the starting Scene of the Transition later.
+        FrameLayout before = copyVisibleViews();
+        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
+        mCover.addView(before, params);
+        // Swap the actual list.
+        swapAbsListView();
+        // We also swap the icon for the toggle button.
+        ActivityCompat.invalidateOptionsMenu(getActivity());
+        // It is now ready to start the transition.
+        mAbsListView.post(new Runnable() {
+            @Override
+            public void run() {
+                // BEGIN_INCLUDE(transition_with_listener)
+                Scene scene = new Scene(mCover, copyVisibleViews());
+                Transition transition = new AutoTransition();
+                transition.addListener(AdapterTransitionFragment.this);
+                TransitionManager.go(scene, transition);
+                // END_INCLUDE(transition_with_listener)
+            }
+        });
+    }
+
+    /**
+     * Swap ListView with GridView, or GridView with ListView.
+     */
+    private void swapAbsListView() {
+        // We save the current scrolling position before removing the current list.
+        int first = mAbsListView.getFirstVisiblePosition();
+        // If the current list is a GridView, we replace it with a ListView. If it is a ListView,
+        // a GridView.
+        LayoutInflater inflater = LayoutInflater.from(getActivity());
+        if (mAbsListView instanceof GridView) {
+            mAbsListView = (AbsListView) inflater.inflate(
+                    R.layout.fragment_meat_list, (ViewGroup) mAbsListView.getParent(), false);
+            mAdapter = new MeatAdapter(inflater, R.layout.item_meat_list);
+        } else {
+            mAbsListView = (AbsListView) inflater.inflate(
+                    R.layout.fragment_meat_grid, (ViewGroup) mAbsListView.getParent(), false);
+            mAdapter = new MeatAdapter(inflater, R.layout.item_meat_grid);
+        }
+        mAbsListView.setAdapter(mAdapter);
+        // We restore the scrolling position here.
+        mAbsListView.setSelection(first);
+        // The new list is ready, and we replace the existing one with it.
+        mContent.removeAllViews();
+        mContent.addView(mAbsListView);
+    }
+
+    /**
+     * Copy all the visible views in the mAbsListView into a new FrameLayout and return it.
+     *
+     * @return a FrameLayout with all the visible views inside.
+     */
+    private FrameLayout copyVisibleViews() {
+        // This is the FrameLayout we return afterwards.
+        FrameLayout layout = new FrameLayout(getActivity());
+        // The transition framework requires to set ID for all views to be animated.
+        layout.setId(ROOT_ID);
+        // We only copy visible views.
+        int first = mAbsListView.getFirstVisiblePosition();
+        int index = 0;
+        while (true) {
+            // This is one of the views that we copy. Note that the argument for getChildAt is a
+            // zero-oriented index, and it doesn't usually match with its position in the list.
+            View source = mAbsListView.getChildAt(index);
+            if (null == source) {
+                break;
+            }
+            // This is the copy of the original view.
+            View destination = mAdapter.getView(first + index, null, layout);
+            assert destination != null;
+            destination.setId(ROOT_ID + first + index);
+            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                    source.getWidth(), source.getHeight());
+            params.leftMargin = (int) source.getX();
+            params.topMargin = (int) source.getY();
+            layout.addView(destination, params);
+            ++index;
+        }
+        return layout;
+    }
+
+}
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/Meat.java b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/Meat.java
new file mode 100644
index 0000000..bca1c5f
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/Meat.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 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.adaptertransition;
+
+/**
+ * Sample data.
+ */
+public class Meat {
+
+    public int resourceId;
+    public String title;
+
+    public Meat(int resourceId, String title) {
+        this.resourceId = resourceId;
+        this.title = title;
+    }
+
+    public static final Meat[] MEATS = {
+            new Meat(R.drawable.p1, "First"),
+            new Meat(R.drawable.p2, "Second"),
+            new Meat(R.drawable.p3, "Third"),
+            new Meat(R.drawable.p4, "Fourth"),
+            new Meat(R.drawable.p5, "Fifth"),
+            new Meat(R.drawable.p6, "Sixth"),
+            new Meat(R.drawable.p7, "Seventh"),
+            new Meat(R.drawable.p8, "Eighth"),
+            new Meat(R.drawable.p9, "Ninth"),
+            new Meat(R.drawable.p10, "Tenth"),
+            new Meat(R.drawable.p11, "Eleventh"),
+    };
+
+}
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/MeatAdapter.java b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/MeatAdapter.java
new file mode 100644
index 0000000..c7630ce
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/java/com/example/android/adaptertransition/MeatAdapter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 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.adaptertransition;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * This class provides data as Views. It is designed to support both ListView and GridView by
+ * changing a layout resource file to inflate.
+ */
+public class MeatAdapter extends BaseAdapter {
+
+    private final LayoutInflater mLayoutInflater;
+    private final int mResourceId;
+
+    /**
+     * Create a new instance of {@link MeatAdapter}.
+     *
+     * @param inflater   The layout inflater.
+     * @param resourceId The resource ID for the layout to be used. The layout should contain an
+     *                   ImageView with ID of "meat_image" and a TextView with ID of "meat_title".
+     */
+    public MeatAdapter(LayoutInflater inflater, int resourceId) {
+        mLayoutInflater = inflater;
+        mResourceId = resourceId;
+    }
+
+    @Override
+    public int getCount() {
+        return Meat.MEATS.length;
+    }
+
+    @Override
+    public Meat getItem(int position) {
+        return Meat.MEATS[position];
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return Meat.MEATS[position].resourceId;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        final View view;
+        final ViewHolder holder;
+        if (null == convertView) {
+            view = mLayoutInflater.inflate(mResourceId, parent, false);
+            holder = new ViewHolder();
+            assert view != null;
+            holder.image = (ImageView) view.findViewById(R.id.meat_image);
+            holder.title = (TextView) view.findViewById(R.id.meat_title);
+            view.setTag(holder);
+        } else {
+            view = convertView;
+            holder = (ViewHolder) view.getTag();
+        }
+        Meat meat = getItem(position);
+        holder.image.setImageResource(meat.resourceId);
+        holder.title.setText(meat.title);
+        return view;
+    }
+
+    private static class ViewHolder {
+        public ImageView image;
+        public TextView title;
+    }
+
+}
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_grid.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_grid.png
new file mode 100644
index 0000000..e04f4a7
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_grid.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_list.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_list.png
new file mode 100644
index 0000000..4131dba
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_action_list.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..b7a67c0
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_grid.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_grid.png
new file mode 100644
index 0000000..f2a83e3
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_grid.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_list.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_list.png
new file mode 100644
index 0000000..e248a48
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_action_list.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..1c9fc09
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p1.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p1.jpg
new file mode 100644
index 0000000..10f07ac
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p1.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p10.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p10.jpg
new file mode 100644
index 0000000..4272f4c
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p10.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p11.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p11.jpg
new file mode 100644
index 0000000..c5722b2
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p11.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p2.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p2.jpg
new file mode 100644
index 0000000..ca380ae
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p2.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p3.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p3.jpg
new file mode 100644
index 0000000..6fc71e7
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p3.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p4.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p4.jpg
new file mode 100644
index 0000000..153c1ff
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p4.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p5.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p5.jpg
new file mode 100644
index 0000000..46d6a13
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p5.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p6.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p6.jpg
new file mode 100644
index 0000000..89ccb83
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p6.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p7.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p7.jpg
new file mode 100644
index 0000000..7e9546d
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p7.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p8.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p8.jpg
new file mode 100644
index 0000000..21e25ba
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p8.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p9.jpg b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p9.jpg
new file mode 100644
index 0000000..79854cb
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-nodpi/p9.jpg
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_grid.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_grid.png
new file mode 100644
index 0000000..ecd39b5
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_grid.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_list.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_list.png
new file mode 100644
index 0000000..e7e510d
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_action_list.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..11b9928
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_grid.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_grid.png
new file mode 100644
index 0000000..3ba98fc
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_grid.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_list.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_list.png
new file mode 100644
index 0000000..d187732
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_action_list.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f136c9f
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_adapter_transition.xml b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_adapter_transition.xml
new file mode 100644
index 0000000..22ec090
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_adapter_transition.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context="com.example.android.adaptertransition.AdapterTransitionFragment">
+
+    <FrameLayout
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
+    <FrameLayout
+        android:id="@+id/cover"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#f3f3f3"
+        android:visibility="invisible"/>
+
+</FrameLayout>
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_grid.xml b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_grid.xml
new file mode 100644
index 0000000..9a4f7a1
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_grid.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 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.
+-->
+<GridView
+    android:id="@+id/abs_list_view"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipToPadding="false"
+    android:columnWidth="150dp"
+    android:horizontalSpacing="1dp"
+    android:numColumns="auto_fit"
+    android:padding="1dp"
+    android:scrollbars="none"
+    android:stretchMode="columnWidth"
+    android:verticalSpacing="1dp"/>
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_list.xml b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_list.xml
new file mode 100644
index 0000000..4523b26
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/fragment_meat_list.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 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.
+-->
+<ListView
+    android:id="@+id/abs_list_view"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_grid.xml b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_grid.xml
new file mode 100644
index 0000000..d7fb77a
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_grid.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 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.
+-->
+<RelativeLayout
+    android:id="@+id/meat_container"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="150dp">
+
+    <ImageView
+        android:id="@+id/meat_image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerCrop"
+        tools:src="@drawable/p1"/>
+
+    <TextView
+        android:id="@+id/meat_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentEnd="true"
+        android:layout_gravity="bottom|end"
+        android:layout_marginEnd="16dp"
+        android:layout_marginStart="16dp"
+        android:gravity="center_horizontal"
+        android:shadowColor="#000000"
+        android:shadowDx="0"
+        android:shadowDy="0"
+        android:shadowRadius="10"
+        android:textColor="#ffffff"
+        android:textSize="24sp"
+        android:textStyle="bold"
+        tools:text="Hello"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_list.xml b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_list.xml
new file mode 100644
index 0000000..8d75b90
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/layout/item_meat_list.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 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.
+-->
+<RelativeLayout
+    android:id="@+id/meat_container"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+    <ImageView
+        android:id="@+id/meat_image"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:scaleType="centerCrop"
+        tools:src="@drawable/p1"/>
+
+    <TextView
+        android:id="@+id/meat_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
+        android:layout_toEndOf="@id/meat_image"
+        android:layout_centerInParent="true"
+        android:gravity="center_vertical"
+        android:textSize="24sp"
+        tools:text="Title"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/menu/fragment_adapter_transition.xml b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/menu/fragment_adapter_transition.xml
new file mode 100644
index 0000000..2a51b11
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/menu/fragment_adapter_transition.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/action_toggle"
+        android:icon="@drawable/ic_action_grid"
+        android:showAsAction="always"
+        android:title="Toggle view"/>
+</menu>
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/values-w820dp/dimens.xml b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/values/dimens.xml b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..a0171a7
--- /dev/null
+++ b/ui/transition/AdapterTransition/AdapterTransitionSample/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+
+    </resources>
diff --git a/ui/transition/AdapterTransition/build.gradle b/ui/transition/AdapterTransition/build.gradle
new file mode 100644
index 0000000..cca9ac3
--- /dev/null
+++ b/ui/transition/AdapterTransition/build.gradle
@@ -0,0 +1,10 @@
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+  pathToBuild "../../../../../build"
+  pathToSamplesCommon "../../../common"
+}
+apply from: "../../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/ui/transition/AdapterTransition/buildSrc/build.gradle b/ui/transition/AdapterTransition/buildSrc/build.gradle
new file mode 100644
index 0000000..e344a8c
--- /dev/null
+++ b/ui/transition/AdapterTransition/buildSrc/build.gradle
@@ -0,0 +1,18 @@
+
+
+
+repositories {
+    mavenCentral()
+}
+dependencies {
+    compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+    main {
+        groovy {
+            srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+        }
+    }
+}
+
diff --git a/ui/transition/AdapterTransition/gradle/wrapper/gradle-wrapper.jar b/ui/transition/AdapterTransition/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/ui/transition/AdapterTransition/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/ui/transition/AdapterTransition/gradle/wrapper/gradle-wrapper.properties b/ui/transition/AdapterTransition/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..4f5d419
--- /dev/null
+++ b/ui/transition/AdapterTransition/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=http://services.gradle.org/distributions/gradle-1.10-bin.zip
diff --git a/ui/transition/AdapterTransition/gradlew b/ui/transition/AdapterTransition/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/ui/transition/AdapterTransition/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/ui/transition/AdapterTransition/gradlew.bat b/ui/transition/AdapterTransition/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/ui/transition/AdapterTransition/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/ui/transition/AdapterTransition/settings.gradle b/ui/transition/AdapterTransition/settings.gradle
new file mode 100644
index 0000000..3284a23
--- /dev/null
+++ b/ui/transition/AdapterTransition/settings.gradle
@@ -0,0 +1,4 @@
+
+
+
+include 'AdapterTransitionSample'
diff --git a/ui/transition/AdapterTransition/template-params.xml b/ui/transition/AdapterTransition/template-params.xml
new file mode 100644
index 0000000..d31b532
--- /dev/null
+++ b/ui/transition/AdapterTransition/template-params.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 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.
+-->
+
+
+
+<sample>
+    <name>AdapterTransition</name>
+    <group>UI</group>
+    <package>com.example.android.adaptertransition</package>
+
+    <!-- change minSdk if needed-->
+    <minSdk>19</minSdk>
+
+
+    <strings>
+        <intro>
+            <![CDATA[
+	    Transition cannot be directly applied to AdapterViews. In this sample, we demonstrate how to create a overlay layout and run a Transition on it.
+            ]]>
+        </intro>
+    </strings>
+
+    <template src="base"/>
+    <template src="FragmentView"/>
+    <common src="logger"/>
+    <common src="activities"/>
+    <common src="view"/>
+
+</sample>
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/AndroidManifest.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b4698d6
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.android.basictransition"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/basictransition/BasicTransitionFragment.java b/ui/transition/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/basictransition/BasicTransitionFragment.java
new file mode 100644
index 0000000..e67603d
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/java/com/example/android/basictransition/BasicTransitionFragment.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2013 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.basictransition;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.transition.Scene;
+import android.transition.TransitionInflater;
+import android.transition.TransitionManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RadioGroup;
+
+public class BasicTransitionFragment extends Fragment
+        implements RadioGroup.OnCheckedChangeListener {
+
+    // We transition between these Scenes
+    private Scene mScene1;
+    private Scene mScene2;
+    private Scene mScene3;
+
+    /** A custom TransitionManager */
+    private TransitionManager mTransitionManagerForScene3;
+
+    /** Transitions take place in this ViewGroup. We retain this for the dynamic transition on scene 4. */
+    private ViewGroup mSceneRoot;
+
+    public static BasicTransitionFragment newInstance() {
+        return new BasicTransitionFragment();
+    }
+
+    public BasicTransitionFragment() {
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_basic_transition, container, false);
+        assert view != null;
+        RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.select_scene);
+        radioGroup.setOnCheckedChangeListener(this);
+        mSceneRoot = (ViewGroup) view.findViewById(R.id.scene_root);
+
+        // BEGIN_INCLUDE(instantiation_from_view)
+        // A Scene can be instantiated from a live view hierarchy.
+        mScene1 = new Scene(mSceneRoot, (ViewGroup) mSceneRoot.findViewById(R.id.container));
+        // END_INCLUDE(instantiation_from_view)
+
+        // BEGIN_INCLUDE(instantiation_from_resource)
+        // You can also inflate a generate a Scene from a layout resource file.
+        mScene2 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene2, getActivity());
+        // END_INCLUDE(instantiation_from_resource)
+
+        // Another scene from a layout resource file.
+        mScene3 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene3, getActivity());
+
+        // BEGIN_INCLUDE(custom_transition_manager)
+        // We create a custom TransitionManager for Scene 3, in which ChangeBounds and Fade
+        // take place at the same time.
+        mTransitionManagerForScene3 = TransitionInflater.from(getActivity())
+                .inflateTransitionManager(R.transition.scene3_transition_manager, mSceneRoot);
+        // END_INCLUDE(custom_transition_manager)
+
+        return view;
+    }
+
+    @Override
+    public void onCheckedChanged(RadioGroup group, int checkedId) {
+        switch (checkedId) {
+            case R.id.select_scene_1: {
+                // BEGIN_INCLUDE(transition_simple)
+                // You can start an automatic transition with TransitionManager.go().
+                TransitionManager.go(mScene1);
+                // END_INCLUDE(transition_simple)
+                break;
+            }
+            case R.id.select_scene_2: {
+                TransitionManager.go(mScene2);
+                break;
+            }
+            case R.id.select_scene_3: {
+                // BEGIN_INCLUDE(transition_custom)
+                // You can also start a transition with a custom TransitionManager.
+                mTransitionManagerForScene3.transitionTo(mScene3);
+                // END_INCLUDE(transition_custom)
+                break;
+            }
+            case R.id.select_scene_4: {
+                // BEGIN_INCLUDE(transition_dynamic)
+                // Alternatively, transition can be invoked dynamically without a Scene.
+                // For this, we first call TransitionManager.beginDelayedTransition().
+                TransitionManager.beginDelayedTransition(mSceneRoot);
+                // Then, we can just change view properties as usual.
+                View square = mSceneRoot.findViewById(R.id.transition_square);
+                ViewGroup.LayoutParams params = square.getLayoutParams();
+                int newSize = getResources().getDimensionPixelSize(R.dimen.square_size_expanded);
+                params.width = newSize;
+                params.height = newSize;
+                square.setLayoutParams(params);
+                // END_INCLUDE(transition_dynamic)
+                break;
+            }
+        }
+    }
+
+}
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..0f5d360
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..72d85c9
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..cf93e69
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..149a984
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable/oval.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable/oval.xml
new file mode 100644
index 0000000..07f3abd
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/drawable/oval.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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="oval">
+    <solid android:color="#0000ff"/>
+</shape>
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/activity_basic_transition.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/activity_basic_transition.xml
new file mode 100644
index 0000000..f9a4cd2
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/activity_basic_transition.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.example.android.basictransition.BasicTransitionActivity"/>
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/fragment_basic_transition.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/fragment_basic_transition.xml
new file mode 100644
index 0000000..98999c8
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/fragment_basic_transition.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    tools:context="com.example.android.basictransition.BasicTransitionFragment">
+
+    <RadioGroup
+        android:id="@+id/select_scene"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:orientation="horizontal">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/scene"/>
+
+        <RadioButton
+            android:id="@+id/select_scene_1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:checked="true"
+            android:text="@string/scene_1"/>
+
+        <RadioButton
+            android:id="@+id/select_scene_2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/scene_2"/>
+
+        <RadioButton
+            android:id="@+id/select_scene_3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/scene_3"/>
+
+        <RadioButton
+            android:id="@+id/select_scene_4"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/scene_4"/>
+
+    </RadioGroup>
+
+    <FrameLayout
+        android:id="@+id/scene_root"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+
+        <include layout="@layout/scene1"/>
+
+    </FrameLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/scene1.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/scene1.xml
new file mode 100644
index 0000000..005bf3b
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/scene1.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<RelativeLayout
+    android:id="@+id/container"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <View
+        android:id="@+id/transition_square"
+        android:layout_width="@dimen/square_size_normal"
+        android:layout_height="@dimen/square_size_normal"
+        android:background="#990000"
+        android:gravity="center"/>
+
+    <ImageView
+        android:id="@+id/transition_image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/transition_square"
+        android:src="@drawable/ic_launcher"/>
+
+    <ImageView
+        android:id="@+id/transition_oval"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_below="@id/transition_image"
+        android:src="@drawable/oval"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/scene2.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/scene2.xml
new file mode 100644
index 0000000..38a809a
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/scene2.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<RelativeLayout
+    android:id="@+id/container"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <View
+        android:id="@+id/transition_square"
+        android:layout_width="@dimen/square_size_normal"
+        android:layout_height="@dimen/square_size_normal"
+        android:layout_alignParentBottom="true"
+        android:background="#990000"
+        android:gravity="center"/>
+
+    <ImageView
+        android:id="@+id/transition_image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:src="@drawable/ic_launcher"/>
+
+    <ImageView
+        android:id="@+id/transition_oval"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_centerHorizontal="true"
+        android:src="@drawable/oval"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/scene3.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/scene3.xml
new file mode 100644
index 0000000..06246da
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/layout/scene3.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<RelativeLayout
+    android:id="@+id/container"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <View
+        android:id="@+id/transition_square"
+        android:layout_width="@dimen/square_size_normal"
+        android:layout_height="@dimen/square_size_normal"
+        android:layout_centerHorizontal="true"
+        android:background="#990000"
+        android:gravity="center"/>
+
+    <ImageView
+        android:id="@+id/transition_image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:src="@drawable/ic_launcher"/>
+
+    <ImageView
+        android:id="@+id/transition_oval"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:src="@drawable/oval"/>
+
+    <TextView
+        android:id="@+id/transition_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:text="@string/this_is_scene_3"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/transition/changebounds_fadein_together.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/transition/changebounds_fadein_together.xml
new file mode 100644
index 0000000..062e012
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/transition/changebounds_fadein_together.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
+    <changeBounds/>
+    <fade android:fadingMode="fade_in">
+        <targets>
+            <target android:targetId="@id/transition_title" />
+        </targets>
+    </fade>
+</transitionSet>
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/transition/scene3_transition_manager.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/transition/scene3_transition_manager.xml
new file mode 100644
index 0000000..6189d61
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/transition/scene3_transition_manager.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<transitionManager xmlns:android="http://schemas.android.com/apk/res/android">
+    <transition
+        android:toScene="@layout/scene3"
+        android:transition="@transition/changebounds_fadein_together"/>
+</transitionManager>
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/values-w820dp/dimens.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..21e2968
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/values/dimens.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..45ccdbc
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<resources>
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+    <dimen name="square_size_normal">50dp</dimen>
+    <dimen name="square_size_expanded">100dp</dimen>
+</resources>
diff --git a/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/values/strings.xml b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..5b18de5
--- /dev/null
+++ b/ui/transition/BasicTransition/BasicTransitionSample/src/main/res/values/strings.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<resources>
+
+    <string name="scene">Scene</string>
+    <string name="scene_1">1</string>
+    <string name="scene_2">2</string>
+    <string name="scene_3">3</string>
+    <string name="scene_4">4</string>
+    <string name="hello">Hello!</string>
+    <string name="this_is_scene_3">This is scene #3.</string>
+
+</resources>
diff --git a/ui/transition/BasicTransition/build.gradle b/ui/transition/BasicTransition/build.gradle
new file mode 100644
index 0000000..cca9ac3
--- /dev/null
+++ b/ui/transition/BasicTransition/build.gradle
@@ -0,0 +1,10 @@
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+  pathToBuild "../../../../../build"
+  pathToSamplesCommon "../../../common"
+}
+apply from: "../../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/ui/transition/BasicTransition/buildSrc/build.gradle b/ui/transition/BasicTransition/buildSrc/build.gradle
new file mode 100644
index 0000000..e344a8c
--- /dev/null
+++ b/ui/transition/BasicTransition/buildSrc/build.gradle
@@ -0,0 +1,18 @@
+
+
+
+repositories {
+    mavenCentral()
+}
+dependencies {
+    compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+    main {
+        groovy {
+            srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+        }
+    }
+}
+
diff --git a/ui/transition/BasicTransition/gradle/wrapper/gradle-wrapper.jar b/ui/transition/BasicTransition/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/ui/transition/BasicTransition/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/ui/transition/BasicTransition/gradle/wrapper/gradle-wrapper.properties b/ui/transition/BasicTransition/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..56f685a
--- /dev/null
+++ b/ui/transition/BasicTransition/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=http\://services.gradle.org/distributions/gradle-1.10-bin.zip
diff --git a/ui/transition/BasicTransition/gradlew b/ui/transition/BasicTransition/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/ui/transition/BasicTransition/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/ui/transition/BasicTransition/gradlew.bat b/ui/transition/BasicTransition/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/ui/transition/BasicTransition/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/ui/transition/BasicTransition/settings.gradle b/ui/transition/BasicTransition/settings.gradle
new file mode 100644
index 0000000..fae2688
--- /dev/null
+++ b/ui/transition/BasicTransition/settings.gradle
@@ -0,0 +1 @@
+include 'BasicTransitionSample'
diff --git a/ui/transition/BasicTransition/template-params.xml b/ui/transition/BasicTransition/template-params.xml
new file mode 100644
index 0000000..0da7567
--- /dev/null
+++ b/ui/transition/BasicTransition/template-params.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2013 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.
+-->
+<sample>
+    <name>BasicTransition</name>
+    <group>UI</group>
+    <package>com.example.android.basictransition</package>
+
+    <!-- change minSdk if needed-->
+    <minSdk>19</minSdk>
+    <compileSdkVersion>19</compileSdkVersion>
+
+    <strings>
+        <intro>
+            <![CDATA[
+	    This sample demonstrates the basic use of the transition framework introduced in KitKat.
+	    Select each of the RadioButtons to switch between the Scenes.
+            ]]>
+        </intro>
+    </strings>
+
+    <template src="base"/>
+    <template src="FragmentView"/>
+    <common src="logger"/>
+    <common src="activities"/>
+    <common src="view"/>
+
+</sample>
diff --git a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/README-fragmentview.txt b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/README-fragmentview.txt
new file mode 100644
index 0000000..38d903f
--- /dev/null
+++ b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/README-fragmentview.txt
@@ -0,0 +1,37 @@
+<!--
+        Copyright 2013 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.
+-->
+
+Steps to implement FragmentView template:
+-in template-params.xml.ftl:
+    -add the following line to common imports
+        <common src="activities"/>
+
+-Add a Fragment to show behavior.  In your MainActivity.java class, it will reference a Fragment
+ called (yourProjectName)Fragment.java.  Create that file in your project, using the "main" source
+ folder instead of "common" or "templates".
+   For instance, if your package name is com.example.foo, create the file
+   src/main/java/com/example/foo/FooFragment.java
+
+
+-Within this fragment, make sure that the onCreate method has the line
+ "setHasOptionsMenu(true);", to enable the fragment to handle menu events.
+
+-In order to override menu events, override onOptionsItemSelected.
+
+-refer to sampleSamples/fragmentViewSample for a reference implementation of a
+project built on this template.
+
+
diff --git a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/build.gradle b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/build.gradle
index 69ba7dc..9f9663f 100644
--- a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/build.gradle
+++ b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/build.gradle
@@ -1,6 +1,3 @@
-
-
-
 buildscript {
     repositories {
         mavenCentral()
@@ -39,6 +36,7 @@
         }
         instrumentTest.setRoot('tests')
         instrumentTest.java.srcDirs = ['tests/src']
+
     }
 }
 // BEGIN_EXCLUDE
diff --git a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/AdvancedImmersiveModeFragment.java b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/AdvancedImmersiveModeFragment.java
index fe11ecb..6d38ce1 100644
--- a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/AdvancedImmersiveModeFragment.java
+++ b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/java/com/example/android/advancedimmersivemode/AdvancedImmersiveModeFragment.java
@@ -17,9 +17,10 @@
 
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
-import android.view.MenuItem;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
 import android.widget.CheckBox;
 
 import com.example.android.common.logger.Log;
@@ -46,49 +47,97 @@
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
+        final View flagsView = inflater.inflate(R.layout.fragment_flags, container, false);
+        mLowProfileCheckBox = (CheckBox) flagsView.findViewById(R.id.flag_enable_lowprof);
+        mHideNavCheckbox = (CheckBox) flagsView.findViewById(R.id.flag_hide_navbar);
+        mHideStatusBarCheckBox = (CheckBox) flagsView.findViewById(R.id.flag_hide_statbar);
+        mImmersiveModeCheckBox = (CheckBox) flagsView.findViewById(R.id.flag_enable_immersive);
+        mImmersiveModeStickyCheckBox =
+                (CheckBox) flagsView.findViewById(R.id.flag_enable_immersive_sticky);
 
-        final View decorView = getActivity().getWindow().getDecorView();
-        ViewGroup parentView = (ViewGroup) getActivity().getWindow().getDecorView()
-                .findViewById(R.id.sample_main_layout);
+        Button toggleFlagsButton = (Button) flagsView.findViewById(R.id.btn_changeFlags);
+        toggleFlagsButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                toggleUiFlags();
+            }
+        });
 
-        mLowProfileCheckBox = new CheckBox(getActivity());
-        mLowProfileCheckBox.setText("Enable Low Profile mode.");
-        parentView.addView(mLowProfileCheckBox);
+        Button presetsImmersiveModeButton = (Button) flagsView.findViewById(R.id.btn_immersive);
+        presetsImmersiveModeButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
 
-        mHideNavCheckbox = new CheckBox(getActivity());
-        mHideNavCheckbox.setChecked(true);
-        mHideNavCheckbox.setText("Hide Navigation bar");
-        parentView.addView(mHideNavCheckbox);
+                // BEGIN_INCLUDE(immersive_presets)
+                // For immersive mode, the FULLSCREEN, HIDE_HAVIGATION and IMMERSIVE
+                // flags should be set (you can use IMMERSIVE_STICKY instead of IMMERSIVE
+                // as appropriate for your app).  The LOW_PROFILE flag should be cleared.
 
-        mHideStatusBarCheckBox = new CheckBox(getActivity());
-        mHideStatusBarCheckBox.setChecked(true);
-        mHideStatusBarCheckBox.setText("Hide Status Bar");
-        parentView.addView(mHideStatusBarCheckBox);
+                // Immersive mode is primarily for situations where the user will be
+                // interacting with the screen, like games or reading books.
+                int uiOptions = flagsView.getSystemUiVisibility();
+                uiOptions &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;
+                uiOptions |= View.SYSTEM_UI_FLAG_FULLSCREEN;
+                uiOptions |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+                uiOptions |= View.SYSTEM_UI_FLAG_IMMERSIVE;
+                uiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+                flagsView.setSystemUiVisibility(uiOptions);
+                // END_INCLUDE(immersive_presets)
 
-        mImmersiveModeCheckBox = new CheckBox(getActivity());
-        mImmersiveModeCheckBox.setText("Enable Immersive Mode.");
-        parentView.addView(mImmersiveModeCheckBox);
+                // The below code just updates the checkboxes to reflect which flags have been set.
+                mLowProfileCheckBox.setChecked(false);
+                mHideNavCheckbox.setChecked(true);
+                mHideStatusBarCheckBox.setChecked(true);
+                mImmersiveModeCheckBox.setChecked(true);
+                mImmersiveModeStickyCheckBox.setChecked(false);
+            }
+        });
 
-        mImmersiveModeStickyCheckBox = new CheckBox(getActivity());
-        mImmersiveModeStickyCheckBox.setText("Enable Immersive Mode (Sticky)");
-        parentView.addView(mImmersiveModeStickyCheckBox);
 
-    }
+        Button presetsLeanbackModeButton = (Button) flagsView.findViewById(R.id.btn_leanback);
+        presetsLeanbackModeButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                // BEGIN_INCLUDE(leanback_presets)
+                // For leanback mode, only the HIDE_NAVE and HIDE_STATUSBAR flags
+                // should be checked.  In this case IMMERSIVE should *not* be set,
+                // since this mode is left as soon as the user touches the screen.
+                int uiOptions = flagsView.getSystemUiVisibility();
+                uiOptions &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;
+                uiOptions |= View.SYSTEM_UI_FLAG_FULLSCREEN;
+                uiOptions |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+                uiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE;
+                uiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+                flagsView.setSystemUiVisibility(uiOptions);
+                // END_INCLUDE(leanback_presets)
 
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        if (item.getItemId() == R.id.sample_action) {
-            toggleImmersiveMode();
-        }
-        return true;
+                // The below code just updates the checkboxes to reflect which flags have been set.
+                mLowProfileCheckBox.setChecked(false);
+                mHideNavCheckbox.setChecked(true);
+                mHideStatusBarCheckBox.setChecked(true);
+                mImmersiveModeCheckBox.setChecked(false);
+                mImmersiveModeStickyCheckBox.setChecked(false);
+                toggleUiFlags();
+            }
+        });
+
+        // Setting these flags makes the content appear under the navigation
+        // bars, so that showing/hiding the nav bars doesn't resize the content
+        // window, which can be jarring.
+        int uiOptions = flagsView.getSystemUiVisibility();
+        uiOptions |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+        uiOptions |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+        uiOptions |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        flagsView.setSystemUiVisibility(uiOptions);
+
+        return flagsView;
     }
 
     /**
      * Detects and toggles immersive mode (also known as "hidey bar" mode).
      */
-    public void toggleImmersiveMode() {
+    public void toggleUiFlags() {
 
         // BEGIN_INCLUDE (get_current_ui_flags)
         // The "Decor View" is the parent view of the Activity.  It's also conveniently the easiest
diff --git a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-hdpi/ic_launcher.png b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-hdpi/ic_launcher.png
index b1efaf4..b96e6a5 100644
--- a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-hdpi/ic_launcher.png
+++ b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-mdpi/ic_launcher.png b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-mdpi/ic_launcher.png
index f5f9244..1294d5b 100644
--- a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-mdpi/ic_launcher.png
+++ b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xhdpi/ic_launcher.png b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xhdpi/ic_launcher.png
index 5d07b3f..9f101ff 100644
--- a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xhdpi/ic_launcher.png
+++ b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xxhdpi/ic_launcher.png b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xxhdpi/ic_launcher.png
index 6ef21e1..7a195a1 100644
--- a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xxhdpi/ic_launcher.png
+++ b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/layout/fragment_flags.xml b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/layout/fragment_flags.xml
new file mode 100644
index 0000000..2c74e83
--- /dev/null
+++ b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/src/main/res/layout/fragment_flags.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <CheckBox
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/flag_enable_lowprof"
+        android:text="Enable Low Profile Mode" />
+
+    <CheckBox
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/flag_hide_navbar"
+        android:text="Hide Navigation bar" />
+
+    <CheckBox
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/flag_hide_statbar"
+        android:text="Hide Status Bar" />
+
+    <CheckBox
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/flag_enable_immersive"
+        android:text="Enable Immersive Mode" />
+
+    <CheckBox
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/flag_enable_immersive_sticky"
+        android:text="Enable Immersive Mode (Sticky)" />
+
+    <Button
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Do things!"
+        android:id="@+id/btn_changeFlags" />
+
+
+    <TextView
+        android:layout_marginTop="@dimen/margin_large"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Common flag presets"/>
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="horizontal" android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Immersive Mode"
+            android:id="@+id/btn_immersive" />
+
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Leanback Mode"
+            android:id="@+id/btn_leanback" />
+
+    </LinearLayout>
+
+
+
+</LinearLayout>
\ No newline at end of file
diff --git a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/tests/src/com/example/android/advancedimmersivemode/tests/SampleTests.java b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/tests/src/com/example/android/advancedimmersivemode/tests/SampleTests.java
index c51a488..541791a 100644
--- a/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/tests/src/com/example/android/advancedimmersivemode/tests/SampleTests.java
+++ b/ui/window/AdvancedImmersiveMode/AdvancedImmersiveModeSample/tests/src/com/example/android/advancedimmersivemode/tests/SampleTests.java
@@ -74,53 +74,8 @@
         mTestFragment.mImmersiveModeCheckBox.setChecked(true);
         mTestFragment.mHideNavCheckbox.setChecked(true);
         mTestFragment.mHideStatusBarCheckBox.setChecked(true);
-        mTestFragment.toggleImmersiveMode();
+        mTestFragment.toggleUiFlags();
         int newUiFlags = getActivity().getWindow().getDecorView().getSystemUiVisibility();
         assertTrue("UI Flags didn't toggle.", uiFlags != newUiFlags);
     }
-
-    /**
-     * Verify that the view's height actually changed when the toggle method is called.
-     * This should result in a change in height for the DecorView.
-     */
-    public void testDecorHeightExpanded() {
-        // Grab the initial height of the DecorWindow.
-        int startingHeight = getActivity().getWindow().getDecorView().getHeight();
-
-        // In order to test that this worked:  Need to toggle the immersive mode on the UI thread,
-        // wait a suitable amount of time (this test goes with 200 ms), then check to see if the
-        // height changed.
-        try {
-            Runnable testRunnable = (new Runnable() {
-                public void run() {
-                    // Toggle immersive mode
-                    mTestFragment.mImmersiveModeCheckBox.setChecked(true);
-                    mTestFragment.mHideNavCheckbox.setChecked(true);
-                    mTestFragment.mHideStatusBarCheckBox.setChecked(true);
-                    mTestFragment.toggleImmersiveMode();
-                    synchronized(this) {
-                        // Notify any thread waiting on this runnable that it can continue
-                        this.notify();
-                    }
-                }
-            });
-            synchronized(testRunnable) {
-                // Since toggling immersive mode makes changes to the view hierarchy, it needs to run
-                // on the UI thread, or crashing will occur.
-                mTestActivity.runOnUiThread(testRunnable);
-                testRunnable.wait();
-
-            }
-            synchronized(this) {
-                //Wait about 200ms for the change to take place
-                wait(200L);
-            }
-        } catch (Throwable throwable) {
-            fail(throwable.getMessage());
-        }
-
-        int expandedHeight = getActivity().getWindow().getDecorView().getHeight();
-        assertTrue("Bars aren't hidden.", expandedHeight != startingHeight);
-    }
-
 }
\ No newline at end of file
diff --git a/ui/window/AdvancedImmersiveMode/buildSrc/build.gradle b/ui/window/AdvancedImmersiveMode/buildSrc/build.gradle
index 7cebf71..490642d 100644
--- a/ui/window/AdvancedImmersiveMode/buildSrc/build.gradle
+++ b/ui/window/AdvancedImmersiveMode/buildSrc/build.gradle
@@ -1,15 +1,8 @@
 repositories {
     mavenCentral()
 }
+
 dependencies {
     compile 'org.freemarker:freemarker:2.3.20'
+    compile files("libs/buildSrc.jar")
 }
-
-sourceSets {
-    main {
-        groovy {
-            srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
-        }
-    }
-}
-
diff --git a/ui/window/AdvancedImmersiveMode/buildSrc/libs/buildSrc.jar b/ui/window/AdvancedImmersiveMode/buildSrc/libs/buildSrc.jar
new file mode 100644
index 0000000..8154696
--- /dev/null
+++ b/ui/window/AdvancedImmersiveMode/buildSrc/libs/buildSrc.jar
Binary files differ
diff --git a/ui/window/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.jar b/ui/window/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.jar
index 8c0fb64..5838598 100644
--- a/ui/window/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.jar
+++ b/ui/window/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/ui/window/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.properties b/ui/window/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.properties
index 861eddc..9e133a0 100644
--- a/ui/window/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.properties
+++ b/ui/window/AdvancedImmersiveMode/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Apr 10 15:27:10 PDT 2013
+#Tue Feb 11 09:26:00 PST 2014
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.8-bin.zip
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip
diff --git a/ui/window/AdvancedImmersiveMode/template-params.xml b/ui/window/AdvancedImmersiveMode/template-params.xml
index 0f23700..e60bc38 100644
--- a/ui/window/AdvancedImmersiveMode/template-params.xml
+++ b/ui/window/AdvancedImmersiveMode/template-params.xml
@@ -27,20 +27,17 @@
     <strings>
         <intro>
             <![CDATA[
-            \"Immersive Mode\" is a new UI mode which improves \"hide full screen\" and
+            \"Immersive Mode\", added in Android 4.4, improves the \"hide full screen\" and
             \"hide nav bar\" modes, by letting users swipe the bars in and out.  This sample
-            lets the user experiment with immersive mode by enabling it and seeing how it interacts
+            lets the user experiment with immersive mode by seeing how it interacts
             with some of the other UI flags related to full-screen apps.
-            \n\nThis sample also lets the user choose between normal immersive mode and "sticky"
-            immersive mode, which removes the status bar and nav bar
-            a few seconds after the user has swiped them back in.
             ]]>
         </intro>
         <sample_action>Try these settings!</sample_action>
     </strings>
 
     <template src="base"/>
-    <template src="SingleView"/>
+    <template src="FragmentView"/>
     <common src="logger"/>
     <common src="activities"/>