Merge "MediaSession2: Change controller's behavior when connected"
diff --git a/packages/MediaComponents/res/values/themes.xml b/packages/MediaComponents/res/values/themes.xml
index 51098e9..d9a754b 100644
--- a/packages/MediaComponents/res/values/themes.xml
+++ b/packages/MediaComponents/res/values/themes.xml
@@ -16,8 +16,8 @@
 
 <resources>
 
-    <style name="Theme.MediaRouter" parent="ThemeOverlay.AppCompat.Dark">
-        <item name="windowNoTitle">true</item>
+    <style name="Theme.MediaRouter" parent="android:Theme.Material">
+        <item name="android:windowNoTitle">true</item>
         <item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.MediaRouteButton</item>
 
         <item name="mediaRouteCloseDrawable">@drawable/mr_dialog_close_dark</item>
@@ -37,8 +37,8 @@
         <item name="mediaRouteControlPanelThemeOverlay">@style/ThemeOverlay.MediaRouter.Light</item>
     </style>
 
-    <style name="Theme.MediaRouter.Light" parent="ThemeOverlay.AppCompat.Light">
-        <item name="windowNoTitle">true</item>
+    <style name="Theme.MediaRouter.Light" parent="android:Theme.Material.Light">
+        <item name="android:windowNoTitle">true</item>
         <item name="mediaRouteButtonStyle">@style/Widget.MediaRouter.Light.MediaRouteButton</item>
 
         <item name="mediaRouteCloseDrawable">@drawable/mr_dialog_close_light</item>
@@ -58,14 +58,14 @@
         <item name="mediaRouteControlPanelThemeOverlay">@style/ThemeOverlay.MediaRouter.Dark</item>
     </style>
 
-    <style name="ThemeOverlay.MediaRouter.Dark" parent="ThemeOverlay.AppCompat.Dark">
+    <style name="ThemeOverlay.MediaRouter.Dark" parent="android:Theme.Material">
         <item name="mediaRoutePlayDrawable">@drawable/mr_media_play_dark</item>
         <item name="mediaRoutePauseDrawable">@drawable/mr_media_pause_dark</item>
         <item name="mediaRouteStopDrawable">@drawable/mr_media_stop_dark</item>
         <item name="mediaRouteAudioTrackDrawable">@drawable/mr_vol_type_audiotrack_dark</item>
 
     </style>
-    <style name="ThemeOverlay.MediaRouter.Light" parent="ThemeOverlay.AppCompat.Light">
+    <style name="ThemeOverlay.MediaRouter.Light" parent="android:Theme.Material.Light">
         <item name="mediaRoutePlayDrawable">@drawable/mr_media_play_light</item>
         <item name="mediaRoutePauseDrawable">@drawable/mr_media_pause_light</item>
         <item name="mediaRouteStopDrawable">@drawable/mr_media_stop_light</item>
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index d5bd354..99df372 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -40,6 +40,9 @@
     void connect(String callingPackage, IMediaSession2Callback callback);
     void release(IMediaSession2Callback caller);
 
+    void setVolumeTo(IMediaSession2Callback caller, int value, int flags);
+    void adjustVolume(IMediaSession2Callback caller, int direction, int flags);
+
     //////////////////////////////////////////////////////////////////////////////////////////////
     // send command
     //////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 06883f2..3b31d9e 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -288,12 +288,32 @@
 
     @Override
     public void setVolumeTo_impl(int value, int flags) {
-        // TODO(jaewan): Implement
+        // TODO(hdmoon): sanity check
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder != null) {
+            try {
+                binder.setVolumeTo(mSessionCallbackStub, value, flags);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     @Override
     public void adjustVolume_impl(int direction, int flags) {
-        // TODO(jaewan): Implement
+        // TODO(hdmoon): sanity check
+        final IMediaSession2 binder = mSessionBinder;
+        if (binder != null) {
+            try {
+                binder.adjustVolume(mSessionCallbackStub, direction, flags);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     @Override
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index cde98d2..0b7c546 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -559,6 +559,10 @@
         return mCallback;
     }
 
+    VolumeProvider2 getVolumeProvider() {
+        return mVolumeProvider;
+    }
+
     private static class MyPlaybackListener implements MediaPlayerInterface.PlaybackListener {
         private final WeakReference<MediaSession2Impl> mSession;
         private final MediaPlayerInterface mPlayer;
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index 3909fe2..3391236 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -29,6 +29,7 @@
 import android.media.MediaSession2.ControllerInfo;
 import android.media.MediaSession2.PlaylistParams;
 import android.media.PlaybackState2;
+import android.media.VolumeProvider2;
 import android.media.update.MediaSession2Provider.CommandButtonProvider;
 import android.os.Binder;
 import android.os.Bundle;
@@ -165,6 +166,82 @@
     }
 
     @Override
+    public void setVolumeTo(IMediaSession2Callback caller, int value, int flags)
+            throws RuntimeException {
+        final MediaSession2Impl sessionImpl = getSession();
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+            }
+            return;
+        }
+        sessionImpl.getCallbackExecutor().execute(() -> {
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            // TODO(jaewan): Sanity check.
+            Command command = new Command(
+                    session.getContext(), MediaSession2.COMMAND_CODE_SET_VOLUME);
+            boolean accepted = session.getCallback().onCommandRequest(controller, command);
+            if (!accepted) {
+                // Don't run rejected command.
+                if (DEBUG) {
+                    Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_SET_VOLUME + " from "
+                            + controller + " was rejected by " + session);
+                }
+                return;
+            }
+
+            VolumeProvider2 volumeProvider = session.getVolumeProvider();
+            if (volumeProvider == null) {
+                // TODO(jaewan): Set local stream volume
+            } else {
+                volumeProvider.onSetVolumeTo(value);
+            }
+        });
+    }
+
+    @Override
+    public void adjustVolume(IMediaSession2Callback caller, int direction, int flags)
+            throws RuntimeException {
+        final MediaSession2Impl sessionImpl = getSession();
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "Command from a controller that hasn't connected. Ignore");
+            }
+            return;
+        }
+        sessionImpl.getCallbackExecutor().execute(() -> {
+            final MediaSession2Impl session = mSession.get();
+            if (session == null) {
+                return;
+            }
+            // TODO(jaewan): Sanity check.
+            Command command = new Command(
+                    session.getContext(), MediaSession2.COMMAND_CODE_SET_VOLUME);
+            boolean accepted = session.getCallback().onCommandRequest(controller, command);
+            if (!accepted) {
+                // Don't run rejected command.
+                if (DEBUG) {
+                    Log.d(TAG, "Command " + MediaSession2.COMMAND_CODE_SET_VOLUME + " from "
+                            + controller + " was rejected by " + session);
+                }
+                return;
+            }
+
+            VolumeProvider2 volumeProvider = session.getVolumeProvider();
+            if (volumeProvider == null) {
+                // TODO(jaewan): Adjust local stream volume
+            } else {
+                volumeProvider.onAdjustVolume(direction);
+            }
+        });
+    }
+
+    @Override
     public void sendCommand(IMediaSession2Callback caller, Bundle command, Bundle args)
             throws RuntimeException {
         // TODO(jaewan): Generic command
diff --git a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
index b0ca1bd..06f463f 100644
--- a/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
+++ b/packages/MediaComponents/src/com/android/media/update/ApiHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.media.update;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
@@ -24,6 +25,7 @@
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 
 import com.android.support.mediarouter.app.MediaRouteButton;
 
@@ -51,13 +53,23 @@
         return sInstance.mLibResources;
     }
 
-    public static Resources.Theme getLibTheme() {
+    public static Theme getLibTheme() {
         return sInstance.mLibTheme;
     }
 
+    public static Theme getLibTheme(int themeId) {
+        Theme theme = sInstance.mLibResources.newTheme();
+        theme.applyStyle(themeId, true);
+        return theme;
+    }
+
     public static LayoutInflater getLayoutInflater(Context context) {
+        return getLayoutInflater(context, getLibTheme());
+    }
+
+    public static LayoutInflater getLayoutInflater(Context context, Theme theme) {
         LayoutInflater layoutInflater = LayoutInflater.from(context).cloneInContext(
-                new ContextThemeWrapper(context, getLibTheme()));
+                new ContextThemeWrapper(context, theme));
         layoutInflater.setFactory2(new LayoutInflater.Factory2() {
             @Override
             public View onCreateView(
@@ -77,8 +89,17 @@
     }
 
     public static View inflateLibLayout(Context context, int libResId) {
+        return inflateLibLayout(context, getLibTheme(), libResId, null, false);
+    }
+
+    public static View inflateLibLayout(Context context, Theme theme, int libResId) {
+        return inflateLibLayout(context, theme, libResId, null, false);
+    }
+
+    public static View inflateLibLayout(Context context, Theme theme, int libResId,
+            @Nullable ViewGroup root, boolean attachToRoot) {
         try (XmlResourceParser parser = getLibResources().getLayout(libResId)) {
-            return getLayoutInflater(context).inflate(parser, null);
+            return getLayoutInflater(context, theme).inflate(parser, root, attachToRoot);
         }
     }
 }
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
index 7fdcfe4..fa94a81 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteButton.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.app.Activity;
+import android.app.FragmentManager;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.res.ColorStateList;
@@ -27,8 +28,6 @@
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v7.widget.TooltipCompat;
 import android.util.AttributeSet;
@@ -259,7 +258,7 @@
             return false;
         }
 
-        final FragmentManager fm = getFragmentManager();
+        final FragmentManager fm = getActivity().getFragmentManager();
         if (fm == null) {
             throw new IllegalStateException("The activity must be a subclass of FragmentActivity");
         }
@@ -286,13 +285,6 @@
         return true;
     }
 
-    private FragmentManager getFragmentManager() {
-        Activity activity = getActivity();
-        if (activity instanceof FragmentActivity) {
-            return ((FragmentActivity)activity).getSupportFragmentManager();
-        }
-        return null;
-    }
 
     private Activity getActivity() {
         // Gross way of unwrapping the Activity so we can get the FragmentManager
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
index cc7c3d5..aeb4408 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialog.java
@@ -20,7 +20,9 @@
 import static com.android.support.mediarouter.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
 
 import android.annotation.NonNull;
+import android.app.Dialog;
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -31,6 +33,7 @@
 import android.support.v7.app.AppCompatDialog;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -41,6 +44,7 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
+import com.android.media.update.ApiHelper;
 import com.android.media.update.R;
 import com.android.support.mediarouter.media.MediaRouteSelector;
 import com.android.support.mediarouter.media.MediaRouter;
@@ -61,7 +65,7 @@
  * @see MediaRouteButton
  * @see MediaRouteActionProvider
  */
-public class MediaRouteChooserDialog extends AppCompatDialog {
+public class MediaRouteChooserDialog extends Dialog {
     static final String TAG = "MediaRouteChooserDialog";
 
     // Do not update the route list immediately to avoid unnatural dialog change.
@@ -94,8 +98,9 @@
     }
 
     public MediaRouteChooserDialog(Context context, int theme) {
-        super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, false),
-                MediaRouterThemeHelper.createThemedDialogStyle(context));
+        super(new ContextThemeWrapper(context,
+                ApiHelper.getLibTheme(MediaRouterThemeHelper.getRouterThemeId(context))),
+                theme == 0 ? android.R.style.Animation : theme);
         context = getContext();
 
         mRouter = MediaRouter.getInstance(context);
@@ -182,7 +187,9 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        setContentView(R.layout.mr_chooser_dialog);
+        setContentView(ApiHelper.inflateLibLayout(getContext(),
+                ApiHelper.getLibTheme(MediaRouterThemeHelper.getRouterThemeId(getContext())),
+                R.layout.mr_chooser_dialog));
 
         mRoutes = new ArrayList<>();
         mAdapter = new RouteAdapter(getContext(), mRoutes);
@@ -199,7 +206,7 @@
      * Sets the width of the dialog. Also called when configuration changes.
      */
     void updateLayout() {
-        getWindow().setLayout(MediaRouteDialogHelper.getDialogWidth(getContext()),
+        getWindow().setLayout(MediaRouteDialogHelper.getDialogWidth(),
                 ViewGroup.LayoutParams.WRAP_CONTENT);
     }
 
@@ -248,7 +255,6 @@
 
     private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo>
             implements ListView.OnItemClickListener {
-        private final LayoutInflater mInflater;
         private final Drawable mDefaultIcon;
         private final Drawable mTvIcon;
         private final Drawable mSpeakerIcon;
@@ -256,12 +262,16 @@
 
         public RouteAdapter(Context context, List<MediaRouter.RouteInfo> routes) {
             super(context, 0, routes);
-            mInflater = LayoutInflater.from(context);
-            TypedArray styledAttributes = getContext().obtainStyledAttributes(new int[] {
-                    R.attr.mediaRouteDefaultIconDrawable,
-                    R.attr.mediaRouteTvIconDrawable,
-                    R.attr.mediaRouteSpeakerIconDrawable,
-                    R.attr.mediaRouteSpeakerGroupIconDrawable});
+
+            TypedArray styledAttributes = ApiHelper.getLibTheme(
+                    MediaRouterThemeHelper.getRouterThemeId(context)).obtainStyledAttributes(
+                            new int[] {
+                                R.attr.mediaRouteDefaultIconDrawable,
+                                R.attr.mediaRouteTvIconDrawable,
+                                R.attr.mediaRouteSpeakerIconDrawable,
+                                R.attr.mediaRouteSpeakerGroupIconDrawable
+                            });
+
             mDefaultIcon = styledAttributes.getDrawable(0);
             mTvIcon = styledAttributes.getDrawable(1);
             mSpeakerIcon = styledAttributes.getDrawable(2);
@@ -283,7 +293,10 @@
         public View getView(int position, View convertView, ViewGroup parent) {
             View view = convertView;
             if (view == null) {
-                view = mInflater.inflate(R.layout.mr_chooser_list_item, parent, false);
+                view = ApiHelper.inflateLibLayout(getContext(),
+                        ApiHelper.getLibTheme(
+                                MediaRouterThemeHelper.getRouterThemeId(getContext())),
+                        R.layout.mr_chooser_list_item, parent, false);
             }
 
             MediaRouter.RouteInfo route = getItem(position);
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialogFragment.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialogFragment.java
index 2f85fb3..65e6b29 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialogFragment.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteChooserDialogFragment.java
@@ -17,10 +17,10 @@
 package com.android.support.mediarouter.app;
 
 import android.app.Dialog;
+import android.app.DialogFragment;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
 
 import com.android.support.mediarouter.media.MediaRouteSelector;
 
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
index 942797b..123ab21 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialog.java
@@ -455,7 +455,7 @@
      * Sets the width of the dialog. Also called when configuration changes.
      */
     void updateLayout() {
-        int width = MediaRouteDialogHelper.getDialogWidth(mContext);
+        int width = MediaRouteDialogHelper.getDialogWidth();
         getWindow().setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT);
 
         View decorView = getWindow().getDecorView();
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialogFragment.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialogFragment.java
index 9442df7..215d74f 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialogFragment.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteControllerDialogFragment.java
@@ -17,10 +17,10 @@
 package com.android.support.mediarouter.app;
 
 import android.app.Dialog;
+import android.app.DialogFragment;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
 
 /**
  * Media route controller dialog fragment.
diff --git a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
index 6f75b46..62c050b 100644
--- a/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
+++ b/packages/MediaComponents/src/com/android/support/mediarouter/app/MediaRouteDialogHelper.java
@@ -28,6 +28,7 @@
 import android.widget.ArrayAdapter;
 import android.widget.ListView;
 
+import com.android.media.update.ApiHelper;
 import com.android.media.update.R;
 
 import java.util.HashMap;
@@ -40,12 +41,12 @@
      * The framework should set the dialog width properly, but somehow it doesn't work, hence
      * duplicating a similar logic here to determine the appropriate dialog width.
      */
-    public static int getDialogWidth(Context context) {
-        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+    public static int getDialogWidth() {
+        DisplayMetrics metrics = ApiHelper.getLibResources().getDisplayMetrics();
         boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
 
         TypedValue value = new TypedValue();
-        context.getResources().getValue(isPortrait ? R.dimen.mr_dialog_fixed_width_minor
+        ApiHelper.getLibResources().getValue(isPortrait ? R.dimen.mr_dialog_fixed_width_minor
                 : R.dimen.mr_dialog_fixed_width_major, value, true);
         if (value.type == TypedValue.TYPE_DIMENSION) {
             return (int) value.getDimension(metrics);
diff --git a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
index 9f207b1..dea72e6 100644
--- a/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
+++ b/packages/MediaComponents/src/com/android/widget/VideoView2Impl.java
@@ -81,6 +81,8 @@
     private static final int STATE_PAUSED = 4;
     private static final int STATE_PLAYBACK_COMPLETED = 5;
 
+    private static final int INVALID_TRACK_INDEX = -1;
+
     private AudioManager mAudioManager;
     private AudioAttributes mAudioAttributes;
     private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain
@@ -117,7 +119,7 @@
     private int mVideoWidth;
     private int mVideoHeight;
 
-    private boolean mCCEnabled;
+    private boolean mSubtitleEnabled;
     private int mSelectedTrackIndex;
 
     private SubtitleView mSubtitleView;
@@ -138,6 +140,7 @@
         mVideoHeight = 0;
         mSpeed = 1.0f;
         mFallbackSpeed = mSpeed;
+        mSelectedTrackIndex = INVALID_TRACK_INDEX;
 
         mAudioManager = (AudioManager) mInstance.getContext()
                 .getSystemService(Context.AUDIO_SERVICE);
@@ -179,11 +182,11 @@
         if (enableControlView) {
             mMediaControlView = new MediaControlView2(mInstance.getContext());
         }
-        boolean showSubtitle = (attrs == null) || attrs.getAttributeBooleanValue(
+        boolean enableSubtitle = (attrs == null) || attrs.getAttributeBooleanValue(
                 "http://schemas.android.com/apk/res/android",
-                "showSubtitle", true);
-        if (showSubtitle) {
-            Log.d(TAG, "showSubtitle attribute is true.");
+                "enableSubtitle", true);
+        if (enableSubtitle) {
+            Log.d(TAG, "enableSubtitle attribute is true.");
             // TODO: implement
         }
         int viewType = (attrs == null) ? VideoView2.VIEW_TYPE_SURFACEVIEW
@@ -226,8 +229,8 @@
     }
 
     @Override
-    public void showSubtitle_impl(boolean show) {
-        if (show) {
+    public void setSubtitleEnabled_impl(boolean enable) {
+        if (enable) {
             // Retrieve all tracks that belong to the current video.
             MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
 
@@ -240,16 +243,21 @@
             }
             if (subtitleTrackIndices.size() > 0) {
                 // Select first subtitle track
-                mCCEnabled = true;
                 mSelectedTrackIndex = subtitleTrackIndices.get(0);
                 mMediaPlayer.selectTrack(mSelectedTrackIndex);
             }
         } else {
-            if (mCCEnabled) {
+            if (mSelectedTrackIndex != INVALID_TRACK_INDEX) {
                 mMediaPlayer.deselectTrack(mSelectedTrackIndex);
-                mCCEnabled = false;
+                mSelectedTrackIndex = INVALID_TRACK_INDEX;
             }
         }
+        mSubtitleEnabled = enable;
+    }
+
+    @Override
+    public boolean isSubtitleEnabled_impl() {
+        return mSubtitleEnabled;
     }
 
     // TODO: remove setSpeed_impl once MediaController2 is ready.
@@ -661,8 +669,12 @@
         }
         mStateBuilder.setState(getCorrespondingPlaybackState(),
                 mMediaPlayer.getCurrentPosition(), mSpeed);
-        mStateBuilder.setBufferedPosition(
-                (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
+        if (mCurrentState != STATE_ERROR
+            && mCurrentState != STATE_IDLE
+            && mCurrentState != STATE_PREPARING) {
+            mStateBuilder.setBufferedPosition(
+                    (long) (mCurrentBufferPercentage / 100.0) * mMediaPlayer.getDuration());
+        }
 
         // Set PlaybackState for MediaSession
         if (mMediaSession != null) {
@@ -928,10 +940,10 @@
             } else {
                 switch (command) {
                     case MediaControlView2.COMMAND_SHOW_SUBTITLE:
-                        mInstance.showSubtitle(true);
+                        mInstance.setSubtitleEnabled(true);
                         break;
                     case MediaControlView2.COMMAND_HIDE_SUBTITLE:
-                        mInstance.showSubtitle(false);
+                        mInstance.setSubtitleEnabled(false);
                         break;
                     case MediaControlView2.COMMAND_SET_FULLSCREEN:
                         if (mOnFullScreenRequestListener != null) {
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index 358beb7..f1fdf2e 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -16,7 +16,7 @@
 
 package android.media;
 
-import android.media.MediaController2.ControllerCallback;
+import android.content.Context;
 import android.media.MediaPlayerInterface.PlaybackListener;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.ControllerInfo;
@@ -223,6 +223,42 @@
     }
 
     @Test
+    public void testSetVolumeTo() throws Exception {
+        final int maxVolume = 100;
+        final int currentVolume = 23;
+        final int volumeControlType = VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
+        TestVolumeProvider volumeProvider =
+                new TestVolumeProvider(mContext, volumeControlType, maxVolume, currentVolume);
+
+        mSession.setPlayer(new MockPlayer(0), volumeProvider);
+        final MediaController2 controller = createController(mSession.getToken(), true, null);
+
+        final int targetVolume = 50;
+        controller.setVolumeTo(targetVolume, 0 /* flags */);
+        assertTrue(volumeProvider.mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        assertTrue(volumeProvider.mSetVolumeToCalled);
+        assertEquals(targetVolume, volumeProvider.mVolume);
+    }
+
+    @Test
+    public void testAdjustVolume() throws Exception {
+        final int maxVolume = 100;
+        final int currentVolume = 23;
+        final int volumeControlType = VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
+        TestVolumeProvider volumeProvider =
+                new TestVolumeProvider(mContext, volumeControlType, maxVolume, currentVolume);
+
+        mSession.setPlayer(new MockPlayer(0), volumeProvider);
+        final MediaController2 controller = createController(mSession.getToken(), true, null);
+
+        final int direction = AudioManager.ADJUST_RAISE;
+        controller.adjustVolume(direction, 0 /* flags */);
+        assertTrue(volumeProvider.mLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        assertTrue(volumeProvider.mAdjustVolumeCalled);
+        assertEquals(direction, volumeProvider.mDirection);
+    }
+
+    @Test
     public void testGetPackageName() {
         assertEquals(mContext.getPackageName(), mController.getSessionToken().getPackageName());
     }
@@ -563,4 +599,31 @@
 
     // TODO(jaewan): Add  test for service connect rejection, when we differentiate session
     //               active/inactive and connection accept/refuse
+
+    class TestVolumeProvider extends VolumeProvider2 {
+        final CountDownLatch mLatch = new CountDownLatch(1);
+        boolean mSetVolumeToCalled;
+        boolean mAdjustVolumeCalled;
+        int mVolume;
+        int mDirection;
+
+        public TestVolumeProvider(Context context, int controlType, int maxVolume,
+                int currentVolume) {
+            super(context, controlType, maxVolume, currentVolume);
+        }
+
+        @Override
+        public void onSetVolumeTo(int volume) {
+            mSetVolumeToCalled = true;
+            mVolume = volume;
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onAdjustVolume(int direction) {
+            mAdjustVolumeCalled = true;
+            mDirection = direction;
+            mLatch.countDown();
+        }
+    }
 }