Merge "Implement set/getPlaybackSpeed" into pi-preview1-androidx-dev
diff --git a/media/src/androidTest/java/androidx/media/MediaController2Test.java b/media/src/androidTest/java/androidx/media/MediaController2Test.java
index 9e54276..f7ce737 100644
--- a/media/src/androidTest/java/androidx/media/MediaController2Test.java
+++ b/media/src/androidTest/java/androidx/media/MediaController2Test.java
@@ -41,6 +41,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.media.MediaController2.ControllerCallback;
+import androidx.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
 import androidx.media.MediaSession2.ControllerInfo;
 import androidx.media.MediaSession2.SessionCallback;
 import androidx.media.TestServiceRegistry.SessionServiceCallback;
@@ -1031,7 +1032,6 @@
         testConnectToService(MockMediaSessionService2.ID);
     }
 
-    @Ignore
     @Test
     public void testConnectToService_libraryService() throws InterruptedException {
         prepareLooper();
@@ -1041,7 +1041,7 @@
     public void testConnectToService(String id) throws InterruptedException {
         prepareLooper();
         final CountDownLatch latch = new CountDownLatch(1);
-        final SessionCallback sessionCallback = new SessionCallback() {
+        final MediaLibrarySessionCallback sessionCallback = new MediaLibrarySessionCallback() {
             @Override
             public SessionCommandGroup2 onConnect(@NonNull MediaSession2 session,
                     @NonNull ControllerInfo controller) {
diff --git a/media/src/androidTest/java/androidx/media/MockMediaLibraryService2.java b/media/src/androidTest/java/androidx/media/MockMediaLibraryService2.java
index 0c5fd73..8f5d5bb 100644
--- a/media/src/androidTest/java/androidx/media/MockMediaLibraryService2.java
+++ b/media/src/androidTest/java/androidx/media/MockMediaLibraryService2.java
@@ -25,6 +25,7 @@
 
 import androidx.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
 import androidx.media.MediaSession2.ControllerInfo;
+import androidx.media.MediaSession2.SessionCallback;
 import androidx.media.TestUtils.SyncHandler;
 
 import java.util.ArrayList;
@@ -108,10 +109,12 @@
                 handler.post(runnable);
             }
         };
-        MediaLibrarySessionCallback librarySessionCallback = (MediaLibrarySessionCallback)
-                TestServiceRegistry.getInstance().getSessionCallback();
-        if (librarySessionCallback == null) {
-            // Use default callback
+        SessionCallback callback = TestServiceRegistry.getInstance().getSessionCallback();
+        MediaLibrarySessionCallback librarySessionCallback;
+        if (callback instanceof MediaLibrarySessionCallback) {
+            librarySessionCallback = (MediaLibrarySessionCallback) callback;
+        } else {
+            // Callback hasn't set. Use default callback
             librarySessionCallback = new TestLibrarySessionCallback();
         }
         mSession = new MediaLibrarySession.Builder(MockMediaLibraryService2.this, executor,
diff --git a/media/src/androidTest/java/androidx/media/TestServiceRegistry.java b/media/src/androidTest/java/androidx/media/TestServiceRegistry.java
index 98a5b89..38286aa 100644
--- a/media/src/androidTest/java/androidx/media/TestServiceRegistry.java
+++ b/media/src/androidTest/java/androidx/media/TestServiceRegistry.java
@@ -21,7 +21,7 @@
 import android.os.Handler;
 
 import androidx.annotation.GuardedBy;
-import androidx.media.MediaSession2.SessionCallback;
+import androidx.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
 import androidx.media.TestUtils.SyncHandler;
 
 /**
@@ -38,7 +38,7 @@
     @GuardedBy("TestServiceRegistry.class")
     private SyncHandler mHandler;
     @GuardedBy("TestServiceRegistry.class")
-    private SessionCallback mSessionCallback;
+    private MediaLibrarySessionCallback mSessionCallback;
     @GuardedBy("TestServiceRegistry.class")
     private SessionServiceCallback mSessionServiceCallback;
 
@@ -77,13 +77,13 @@
         }
     }
 
-    public void setSessionCallback(SessionCallback sessionCallback) {
+    public void setSessionCallback(MediaLibrarySessionCallback sessionCallback) {
         synchronized (TestServiceRegistry.class) {
             mSessionCallback = sessionCallback;
         }
     }
 
-    public SessionCallback getSessionCallback() {
+    public MediaLibrarySessionCallback getSessionCallback() {
         synchronized (TestServiceRegistry.class) {
             return mSessionCallback;
         }
diff --git a/media/src/main/java/androidx/media/MediaBrowser2.java b/media/src/main/java/androidx/media/MediaBrowser2.java
index 80b63ff..6ef7fcf 100644
--- a/media/src/main/java/androidx/media/MediaBrowser2.java
+++ b/media/src/main/java/androidx/media/MediaBrowser2.java
@@ -362,12 +362,8 @@
     }
 
     private MediaBrowserCompat getBrowserCompat(Bundle extras) {
-        if (extras == sDefaultRootHints) {
-            return getBrowserCompat();
-        } else {
-            synchronized (mLock) {
-                return mBrowserCompats.get(extras);
-            }
+        synchronized (mLock) {
+            return mBrowserCompats.get(extras);
         }
     }
 
diff --git a/media/src/main/java/androidx/media/MediaConstants2.java b/media/src/main/java/androidx/media/MediaConstants2.java
index 2a168eb..f5ac1bc 100644
--- a/media/src/main/java/androidx/media/MediaConstants2.java
+++ b/media/src/main/java/androidx/media/MediaConstants2.java
@@ -91,6 +91,8 @@
     static final String ARGUMENT_PID = "androidx.media.argument.PID";
     static final String ARGUMENT_PACKAGE_NAME = "androidx.media.argument.PACKAGE_NAME";
 
+    static final String ROOT_EXTRA_DEFAULT = "androidx.media.root_default_root";
+
     private MediaConstants2() {
     }
 }
diff --git a/media/src/main/java/androidx/media/MediaController2.java b/media/src/main/java/androidx/media/MediaController2.java
index 6ca8267..0ab1374 100644
--- a/media/src/main/java/androidx/media/MediaController2.java
+++ b/media/src/main/java/androidx/media/MediaController2.java
@@ -642,8 +642,12 @@
     private static final String TAG = "MediaController2";
     private static final boolean DEBUG = true; // TODO(jaewan): Change
 
-    // TODO: Is null root suitable here?
-    static final Bundle sDefaultRootHints = null;
+    // Note: Using {@code null} doesn't helpful here because MediaBrowserServiceCompat always wraps
+    //       the rootHints so it becomes non-null.
+    static final Bundle sDefaultRootExtras = new Bundle();
+    static {
+        sDefaultRootExtras.putBoolean(MediaConstants2.ROOT_EXTRA_DEFAULT, true);
+    }
 
     private final Context mContext;
     private final Object mLock = new Object();
@@ -1638,7 +1642,7 @@
     private void connectToService() {
         synchronized (mLock) {
             mBrowserCompat = new MediaBrowserCompat(mContext, mToken.getComponentName(),
-                    new ConnectionCallback(), sDefaultRootHints);
+                    new ConnectionCallback(), sDefaultRootExtras);
             mBrowserCompat.connect();
         }
     }
diff --git a/media/src/main/java/androidx/media/MediaLibraryService2.java b/media/src/main/java/androidx/media/MediaLibraryService2.java
index 024c1bf..edd97c3 100644
--- a/media/src/main/java/androidx/media/MediaLibraryService2.java
+++ b/media/src/main/java/androidx/media/MediaLibraryService2.java
@@ -36,8 +36,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * @hide
@@ -70,6 +70,8 @@
      */
     public static final String SERVICE_INTERFACE = "android.media.MediaLibraryService2";
 
+    // TODO: Revisit this value.
+
     /**
      * Session for the {@link MediaLibraryService2}. Build this object with
      * {@link Builder} and return in {@link #onCreateSession(String)}.
@@ -86,6 +88,9 @@
              * to access browse media information before returning the root id; it
              * should return null if the client is not allowed to access this
              * information.
+             * <p>
+             * Note: this callback may be called on the main thread, regardless of the callback
+             * executor.
              *
              * @param session the session for this event
              * @param controllerInfo information of the controller requesting access to browse
@@ -269,7 +274,6 @@
 
         MediaLibrarySession(SupportLibraryImpl impl) {
             super(impl);
-
         }
 
         /**
@@ -342,6 +346,11 @@
     }
 
     @Override
+    int getSessionType() {
+        return SessionToken2.TYPE_LIBRARY_SERVICE;
+    }
+
+    @Override
     public void onCreate() {
         super.onCreate();
 
@@ -476,41 +485,37 @@
     }
 
     private class MyBrowserService extends MediaBrowserServiceCompat {
-        private final Object mWaitLock = new Object();
-
         @Override
         public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
                 final Bundle extras) {
+            if (MediaUtils2.isDefaultLibraryRootHint(extras)) {
+                // For connection request from the MediaController2. accept the connection from
+                // here, and let MediaLibrarySession decide whether to accept or reject the
+                // controller.
+                return sDefaultBrowserRoot;
+            }
+            final CountDownLatch latch = new CountDownLatch(1);
+            // TODO: Revisit this when we support caller information.
             final ControllerInfo info = new ControllerInfo(MediaLibraryService2.this, clientUid, -1,
                     clientPackageName, null);
-            final AtomicReference<LibraryRoot> root = new AtomicReference<>();
-            synchronized (mWaitLock) {
-                // TODO: (Post-P) Fix waiting on the main thread.
-                getLibrarySession().getCallbackExecutor().execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        MediaLibrarySession session = getLibrarySession();
-                        LibraryRoot libraryRoot = session.getCallback().onGetLibraryRoot(
-                                session, info, extras);
-                        root.set(libraryRoot);
-                        mWaitLock.notify();
-                    }
-                });
-                while (true) {
-                    if (root.get() != null) {
-                        break;
-                    }
-                    try {
-                        mWaitLock.wait();
-                    } catch (InterruptedException e) {
-                        e.printStackTrace();
-                    }
-                }
-            }
-            if (root.get() == null) {
+            MediaLibrarySession session = getLibrarySession();
+            // Call onGetLibraryRoot() directly instead of execute on the executor. Here's the
+            // reason.
+            // We need to return browser root here. So if we run the callback on the executor, we
+            // should wait for the completion.
+            // However, we cannot wait if the callback executor is the main executor, which posts
+            // the runnable to the main thread's. In that case, since this onGetRoot() always runs
+            // on the main thread, the posted runnable for calling onGetLibraryRoot() wouldn't run
+            // in here. Even worse, we cannot know whether it would be run on the main thread or
+            // not.
+            // Because of the reason, just call onGetLibraryRoot directly here. onGetLibraryRoot()
+            // has documentation that it may be called on the main thread.
+            LibraryRoot libraryRoot = session.getCallback().onGetLibraryRoot(
+                    session, info, extras);
+            if (libraryRoot == null) {
                 return null;
             }
-            return new BrowserRoot(root.get().getRootId(), root.get().getExtras());
+            return new BrowserRoot(libraryRoot.getRootId(), libraryRoot.getExtras());
         }
 
         @Override
diff --git a/media/src/main/java/androidx/media/MediaSessionService2.java b/media/src/main/java/androidx/media/MediaSessionService2.java
index 0077efd..7bad65c 100644
--- a/media/src/main/java/androidx/media/MediaSessionService2.java
+++ b/media/src/main/java/androidx/media/MediaSessionService2.java
@@ -32,6 +32,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
 import androidx.media.MediaSession2.ControllerInfo;
 import androidx.media.SessionToken2.TokenType;
 
@@ -128,6 +129,10 @@
      */
     public static final String SERVICE_META_DATA = "android.media.session";
 
+    // Stub BrowserRoot for accepting any connction here.
+    // See MyBrowserService#onGetRoot() for detail.
+    static final BrowserRoot sDefaultBrowserRoot = new BrowserRoot(SERVICE_INTERFACE, null);
+
     private final MediaBrowserServiceCompat mBrowserServiceCompat;
 
     private final Object mLock = new Object();
@@ -164,7 +169,8 @@
         SessionToken2 token = new SessionToken2(this,
                 new ComponentName(getPackageName(), getClass().getName()));
         if (token.getType() != getSessionType()) {
-            throw new RuntimeException("Expected session service, but was " + token.getType());
+            throw new RuntimeException("Expected session type " + getSessionType()
+                    + " but was " + token.getType());
         }
         MediaSession2 session = onCreateSession(token.getId());
         synchronized (mLock) {
@@ -312,7 +318,7 @@
             //      specific operations.
             // TODO: Revisit here API not to return stub root here. The fake media ID here may be
             //       used by the browser service for real.
-            return new BrowserRoot(SERVICE_INTERFACE, null);
+            return sDefaultBrowserRoot;
         }
 
         @Override
diff --git a/media/src/main/java/androidx/media/MediaUtils2.java b/media/src/main/java/androidx/media/MediaUtils2.java
index e1149bd..657e24d 100644
--- a/media/src/main/java/androidx/media/MediaUtils2.java
+++ b/media/src/main/java/androidx/media/MediaUtils2.java
@@ -424,4 +424,8 @@
         }
         return MediaPlayerBase.PLAYER_STATE_ERROR;
     }
+
+    static boolean isDefaultLibraryRootHint(Bundle bundle) {
+        return bundle != null && bundle.getBoolean(MediaConstants2.ROOT_EXTRA_DEFAULT, false);
+    }
 }