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"/>