MediaSession2: Send session info when a controller is connected

Bug: 72547163
Test: Run all tests once
Change-Id: I55730536cce6a938f8117abb433b492ac8e36bfc
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
index b3c83e5..a443bf8 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
@@ -16,6 +16,7 @@
 
 package com.android.media;
 
+import android.app.PendingIntent;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 
@@ -33,8 +34,10 @@
     void onPlaylistParamsChanged(in Bundle params);
     void onPlaybackInfoChanged(in Bundle playbackInfo);
 
-
-    void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup, in Bundle playbackState);
+    // TODO(jaewan): Handle when the playlist becomes too huge.
+    void onConnected(IMediaSession2 sessionBinder, in Bundle commandGroup, in Bundle playbackState,
+            in Bundle playbackInfo, in Bundle params, in List<Bundle> playlist, int ratingType,
+            in PendingIntent sessionActivity);
     void onDisconnected();
 
     void onCustomLayoutChanged(in List<Bundle> commandButtonlist);
diff --git a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
index 3b31d9e..411aeec 100644
--- a/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaController2Impl.java
@@ -77,6 +77,10 @@
     @GuardedBy("mLock")
     private PlaybackInfo mPlaybackInfo;
     @GuardedBy("mLock")
+    private int mRatingType;
+    @GuardedBy("mLock")
+    private PendingIntent mSessionActivity;
+    @GuardedBy("mLock")
     private CommandGroup mCommandGroup;
 
     // Assignment should be used with the lock hold, but should be used without a lock to prevent
@@ -276,14 +280,12 @@
     //////////////////////////////////////////////////////////////////////////////////////
     @Override
     public PendingIntent getSessionActivity_impl() {
-        // TODO(jaewan): Implement
-        return null;
+        return mSessionActivity;
     }
 
     @Override
     public int getRatingType_impl() {
-        // TODO(jaewan): Implement
-        return 0;
+        return mRatingType;
     }
 
     @Override
@@ -508,7 +510,9 @@
 
     // Should be used without a lock to prevent potential deadlock.
     private void onConnectedNotLocked(IMediaSession2 sessionBinder,
-            final CommandGroup commandGroup, final PlaybackState2 state) {
+            final CommandGroup commandGroup, final PlaybackState2 state, final PlaybackInfo info,
+            final PlaylistParams params, final List<MediaItem2> playlist, final int ratingType,
+            final PendingIntent sessionActivity) {
         if (DEBUG) {
             Log.d(TAG, "onConnectedNotLocked sessionBinder=" + sessionBinder
                     + ", commands=" + commandGroup);
@@ -532,6 +536,11 @@
                 }
                 mCommandGroup = commandGroup;
                 mPlaybackState = state;
+                mPlaybackInfo = info;
+                mPlaylistParams = params;
+                mPlaylist = playlist;
+                mRatingType = ratingType;
+                mSessionActivity = sessionActivity;
                 mSessionBinder = sessionBinder;
                 try {
                     // Implementation for the local binder is no-op,
@@ -645,6 +654,9 @@
 
         @Override
         public void onPlaybackInfoChanged(Bundle playbackInfo) throws RuntimeException {
+            if (DEBUG) {
+                Log.d(TAG, "onPlaybackInfoChanged");
+            }
             final MediaController2Impl controller;
             try {
                 controller = getController();
@@ -658,7 +670,8 @@
 
         @Override
         public void onConnected(IMediaSession2 sessionBinder, Bundle commandGroup,
-                Bundle playbackState) {
+                Bundle playbackState, Bundle playbackInfo, Bundle playlistParams, List<Bundle>
+                playlist, int ratingType, PendingIntent sessionActivity) {
             final MediaController2Impl controller = mController.get();
             if (controller == null) {
                 if (DEBUG) {
@@ -666,9 +679,20 @@
                 }
                 return;
             }
+            final Context context = controller.getContext();
+            List<MediaItem2> list = new ArrayList<>();
+            for (int i = 0; i < playlist.size(); i++) {
+                MediaItem2 item = MediaItem2.fromBundle(context, playlist.get(i));
+                if (item != null) {
+                    list.add(item);
+                }
+            }
             controller.onConnectedNotLocked(sessionBinder,
-                    CommandGroup.fromBundle(controller.getContext(), commandGroup),
-                    PlaybackState2.fromBundle(controller.getContext(), playbackState));
+                    CommandGroup.fromBundle(context, commandGroup),
+                    PlaybackState2.fromBundle(context, playbackState),
+                    PlaybackInfoImpl.fromBundle(context, playbackInfo),
+                    PlaylistParams.fromBundle(context, playlistParams),
+                    list, ratingType, sessionActivity);
         }
 
         @Override
@@ -706,6 +730,7 @@
                 // Illegal call. Ignore
                 return;
             }
+            // TODO(jaewan): Fix here. It's controller feature so shouldn't use browser
             final MediaBrowser2Impl browser;
             try {
                 browser = getBrowser();
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
index 0b7c546..f323a17 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Impl.java
@@ -85,6 +85,8 @@
     private final SessionToken2 mSessionToken;
     private final AudioManager mAudioManager;
     private final List<PlaybackListenerHolder> mListeners = new ArrayList<>();
+    private final int mRatingType;
+    private final PendingIntent mSessionActivity;
 
     @GuardedBy("mLock")
     private MediaPlayerInterface mPlayer;
@@ -102,7 +104,6 @@
     /**
      * Can be only called by the {@link Builder#build()}.
      *
-     * @param instance
      * @param context
      * @param player
      * @param id
@@ -123,6 +124,8 @@
         mId = id;
         mCallback = callback;
         mCallbackExecutor = callbackExecutor;
+        mRatingType = ratingType;
+        mSessionActivity = sessionActivity;
         mSessionStub = new MediaSession2Stub(this);
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
 
@@ -195,9 +198,6 @@
         if (player == null) {
             throw new IllegalArgumentException("player shouldn't be null");
         }
-        if (player == mPlayer) {
-            return;
-        }
         PlaybackInfo info =
                 createPlaybackInfo(null /* VolumeProvider */, player.getAudioAttributes());
         synchronized (mLock) {
@@ -218,10 +218,6 @@
         if (volumeProvider == null) {
             throw new IllegalArgumentException("volumeProvider shouldn't be null");
         }
-        if (player == mPlayer) {
-            return;
-        }
-
         PlaybackInfo info = createPlaybackInfo(volumeProvider, player.getAudioAttributes());
         synchronized (mLock) {
             setPlayerLocked(player);
@@ -244,12 +240,25 @@
     private PlaybackInfo createPlaybackInfo(VolumeProvider2 volumeProvider, AudioAttributes attrs) {
         PlaybackInfo info;
         if (volumeProvider == null) {
-            int stream = attrs == null ? AudioManager.STREAM_MUSIC : attrs.getVolumeControlStream();
+            int stream;
+            if (attrs == null) {
+                stream = AudioManager.STREAM_MUSIC;
+            } else {
+                stream = attrs.getVolumeControlStream();
+                if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+                    // It may happen if the AudioAttributes doesn't have usage.
+                    // Change it to the STREAM_MUSIC because it's not supported by audio manager
+                    // for querying volume level.
+                    stream = AudioManager.STREAM_MUSIC;
+                }
+            }
             info = PlaybackInfoImpl.createPlaybackInfo(
                     mContext,
                     PlaybackInfo.PLAYBACK_TYPE_LOCAL,
                     attrs,
-                    VolumeProvider2.VOLUME_CONTROL_ABSOLUTE,
+                    mAudioManager.isVolumeFixed()
+                            ? VolumeProvider2.VOLUME_CONTROL_FIXED
+                            : VolumeProvider2.VOLUME_CONTROL_ABSOLUTE,
                     mAudioManager.getStreamMaxVolume(stream),
                     mAudioManager.getStreamVolume(stream));
         } else {
@@ -563,6 +572,20 @@
         return mVolumeProvider;
     }
 
+    PlaybackInfo getPlaybackInfo() {
+        synchronized (mLock) {
+            return mPlaybackInfo;
+        }
+    }
+
+    int getRatingType() {
+        return mRatingType;
+    }
+
+    PendingIntent getSessionActivity() {
+        return mSessionActivity;
+    }
+
     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 3391236..114cffb 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -16,6 +16,7 @@
 
 package com.android.media;
 
+import android.app.PendingIntent;
 import android.content.Context;
 import android.media.MediaController2;
 import android.media.MediaController2.PlaybackInfo;
@@ -133,10 +134,36 @@
                 // TODO(jaewan): Should we protect getting playback state?
                 final PlaybackState2 state = session.getInstance().getPlaybackState();
                 final Bundle playbackStateBundle = (state != null) ? state.toBundle() : null;
+                final Bundle playbackInfoBundle =
+                        ((PlaybackInfoImpl) session.getPlaybackInfo().getProvider()).toBundle();
+                final PlaylistParams params = session.getInstance().getPlaylistParams();
+                final Bundle paramsBundle = (params != null) ? params.toBundle() : null;
+                final int ratingType = session.getRatingType();
+                final PendingIntent sessionActivity = session.getSessionActivity();
+                final List<MediaItem2> playlist = session.getInstance().getPlaylist();
+                final List<Bundle> playlistBundle = new ArrayList<>();
+                if (playlist != null) {
+                    // TODO(jaewan): Find a way to avoid concurrent modification exception.
+                    for (int i = 0; i < playlist.size(); i++) {
+                        final MediaItem2 item = playlist.get(i);
+                        if (item != null) {
+                            final Bundle itemBundle = item.toBundle();
+                            if (itemBundle != null) {
+                                playlistBundle.add(itemBundle);
+                            }
+                        }
+                    }
+                }
 
+                // Double check if session is still there, because close() can be called in another
+                // thread.
+                if (mSession.get() == null) {
+                    return;
+                }
                 try {
                     callback.onConnected(MediaSession2Stub.this,
-                            allowedCommands.toBundle(), playbackStateBundle);
+                            allowedCommands.toBundle(), playbackStateBundle, playbackInfoBundle,
+                            paramsBundle, playlistBundle, ratingType, sessionActivity);
                 } catch (RemoteException e) {
                     // Controller may be died prematurely.
                     // TODO(jaewan): Handle here.
diff --git a/packages/MediaComponents/test/AndroidManifest.xml b/packages/MediaComponents/test/AndroidManifest.xml
index 30bac87..48e4292 100644
--- a/packages/MediaComponents/test/AndroidManifest.xml
+++ b/packages/MediaComponents/test/AndroidManifest.xml
@@ -20,14 +20,7 @@
     <application android:label="Media API Test">
         <uses-library android:name="android.test.runner" />
 
-        <activity android:name="android.widget2.VideoView2TestActivity"
-                  android:configChanges="keyboardHidden|orientation|screenSize"
-                  android:label="VideoView2TestActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
-            </intent-filter>
-          </activity>
+        <activity android:name="android.media.MockActivity" />
 
         <!-- Keep the test services synced together with the TestUtils.java -->
         <service android:name="android.media.MockMediaSessionService2">
diff --git a/packages/MediaComponents/test/src/android/media/MediaController2Test.java b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
index f1fdf2e..139cc65 100644
--- a/packages/MediaComponents/test/src/android/media/MediaController2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaController2Test.java
@@ -16,7 +16,9 @@
 
 package android.media;
 
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.media.MediaPlayerInterface.PlaybackListener;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.ControllerInfo;
@@ -56,7 +58,9 @@
 @FlakyTest
 public class MediaController2Test extends MediaSession2TestBase {
     private static final String TAG = "MediaController2Test";
+    private static final int DEFAULT_RATING_TYPE = Rating2.RATING_5_STARS;
 
+    PendingIntent mIntent;
     MediaSession2 mSession;
     MediaController2 mController;
     MockPlayer mPlayer;
@@ -65,10 +69,15 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
+        final Intent sessionActivity = new Intent(mContext, MockActivity.class);
         // Create this test specific MediaSession2 to use our own Handler.
+        mIntent = PendingIntent.getActivity(mContext, 0, sessionActivity, 0);
+
         mPlayer = new MockPlayer(1);
         mSession = new MediaSession2.Builder(mContext, mPlayer)
                 .setSessionCallback(sHandlerExecutor, new SessionCallback(mContext))
+                .setRatingType(DEFAULT_RATING_TYPE)
+                .setSessionActivity(mIntent)
                 .setId(TAG).build();
         mController = createController(mSession.getToken());
         TestServiceRegistry.getInstance().setHandler(sHandler);
@@ -199,6 +208,18 @@
     }
 
     @Test
+    public void testGetRatingType() throws InterruptedException {
+        assertEquals(DEFAULT_RATING_TYPE, mController.getRatingType());
+    }
+
+    @Test
+    public void testGetSessionActivity() throws InterruptedException {
+        PendingIntent sessionActivity = mController.getSessionActivity();
+        assertEquals(mContext.getPackageName(), sessionActivity.getCreatorPackage());
+        assertEquals(Process.myUid(), sessionActivity.getCreatorUid());
+    }
+
+    @Test
     public void testGetSetPlaylistParams() throws Exception {
         final PlaylistParams params = new PlaylistParams(mContext,
                 PlaylistParams.REPEAT_MODE_ALL,
diff --git a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
index 43a6c2c..b00633b 100644
--- a/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
+++ b/packages/MediaComponents/test/src/android/media/MediaSession2Test.java
@@ -27,6 +27,9 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaController2.PlaybackInfo;
 import android.media.MediaPlayerInterface.PlaybackListener;
 import android.media.MediaSession2.Builder;
 import android.media.MediaSession2.Command;
@@ -99,17 +102,15 @@
 
     @Test
     public void testSetPlayer() throws Exception {
-        sHandler.postAndSync(() -> {
-            MockPlayer player = new MockPlayer(0);
-            // Test if setPlayer doesn't crash with various situations.
-            mSession.setPlayer(mPlayer);
-            mSession.setPlayer(player);
-            mSession.close();
-        });
+        MockPlayer player = new MockPlayer(0);
+        // Test if setPlayer doesn't crash with various situations.
+        mSession.setPlayer(mPlayer);
+        mSession.setPlayer(player);
+        mSession.close();
     }
 
     @Test
-    public void testSetPlayerWithVolumeProvider() throws Exception {
+    public void testSetPlayer_playbackInfo() throws Exception {
         MockPlayer player = new MockPlayer(0);
         AudioAttributes attrs = new AudioAttributes.Builder()
                 .setContentType(CONTENT_TYPE_MUSIC)
@@ -125,7 +126,7 @@
         final CountDownLatch latch = new CountDownLatch(1);
         final TestControllerCallbackInterface callback = new TestControllerCallbackInterface() {
             @Override
-            public void onPlaybackInfoChanged(MediaController2.PlaybackInfo info) {
+            public void onPlaybackInfoChanged(PlaybackInfo info) {
                 assertEquals(MediaController2.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
                         info.getPlaybackType());
                 assertEquals(attrs, info.getAudioAttributes());
@@ -136,18 +137,30 @@
             }
         };
 
+        mSession.setPlayer(player);
+
         final MediaController2 controller = createController(mSession.getToken(), true, callback);
-        assertNull(controller.getPlaybackInfo());
+        PlaybackInfo info = controller.getPlaybackInfo();
+        assertNotNull(info);
+        assertEquals(PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
+        assertEquals(attrs, info.getAudioAttributes());
+        AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        int localVolumeControlType = manager.isVolumeFixed()
+                ? VolumeProvider2.VOLUME_CONTROL_FIXED : VolumeProvider2.VOLUME_CONTROL_ABSOLUTE;
+        assertEquals(localVolumeControlType, info.getControlType());
+        assertEquals(manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), info.getMaxVolume());
+        assertEquals(manager.getStreamVolume(AudioManager.STREAM_MUSIC), info.getCurrentVolume());
 
         mSession.setPlayer(player, volumeProvider);
         assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
 
-        MediaController2.PlaybackInfo infoOut = controller.getPlaybackInfo();
-        assertEquals(MediaController2.PlaybackInfo.PLAYBACK_TYPE_REMOTE, infoOut.getPlaybackType());
-        assertEquals(attrs, infoOut.getAudioAttributes());
-        assertEquals(volumeControlType, infoOut.getPlaybackType());
-        assertEquals(maxVolume, infoOut.getMaxVolume());
-        assertEquals(currentVolume, infoOut.getCurrentVolume());
+        info = controller.getPlaybackInfo();
+        assertNotNull(info);
+        assertEquals(PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
+        assertEquals(attrs, info.getAudioAttributes());
+        assertEquals(volumeControlType, info.getControlType());
+        assertEquals(maxVolume, info.getMaxVolume());
+        assertEquals(currentVolume, info.getCurrentVolume());
     }
 
     @Test
diff --git a/packages/MediaComponents/test/src/android/media/MockActivity.java b/packages/MediaComponents/test/src/android/media/MockActivity.java
new file mode 100644
index 0000000..4627530
--- /dev/null
+++ b/packages/MediaComponents/test/src/android/media/MockActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2018 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 android.media;
+
+import android.app.Activity;
+
+public class MockActivity extends Activity {
+}