Merge "MediaBrowser2: Implement search()/getSearchResult()"
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
index 81e5547..9538c3d 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2.aidl
@@ -65,5 +65,8 @@
     void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints);
     void getItem(IMediaSession2Callback callback, String mediaId);
     void getChildren(IMediaSession2Callback callback, String parentId, int page, int pageSize,
-            in Bundle options);
+            in Bundle extras);
+    void search(IMediaSession2Callback callback, String query, in Bundle extras);
+    void getSearchResult(IMediaSession2Callback callback, String query, int page, int pageSize,
+            in Bundle extras);
 }
diff --git a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
index 7e76d1d..b3aa59c 100644
--- a/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
+++ b/packages/MediaComponents/src/com/android/media/IMediaSession2Callback.aidl
@@ -49,6 +49,8 @@
     //////////////////////////////////////////////////////////////////////////////////////////////
     void onGetRootResult(in Bundle rootHints, String rootMediaId, in Bundle rootExtra);
     void onItemLoaded(String mediaId, in Bundle result);
-    void onChildrenLoaded(String parentId, int page, int pageSize, in Bundle options,
+    void onChildrenLoaded(String parentId, int page, int pageSize, in Bundle extras,
+            in List<Bundle> result);
+    void onSearchResultLoaded(String query, int page, int pageSize, in Bundle extras,
             in List<Bundle> result);
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
index 5e88262..76da42b 100644
--- a/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaBrowser2Impl.java
@@ -63,12 +63,12 @@
     }
 
     @Override
-    public void subscribe_impl(String parentId, Bundle options) {
+    public void subscribe_impl(String parentId, Bundle extras) {
         // TODO(jaewan): Implement
     }
 
     @Override
-    public void unsubscribe_impl(String parentId, Bundle options) {
+    public void unsubscribe_impl(String parentId, Bundle extras) {
         // TODO(jaewan): Implement
     }
 
@@ -94,7 +94,7 @@
     }
 
     @Override
-    public void getChildren_impl(String parentId, int page, int pageSize, Bundle options) {
+    public void getChildren_impl(String parentId, int page, int pageSize, Bundle extras) {
         if (parentId == null) {
             throw new IllegalArgumentException("parentId shouldn't be null");
         }
@@ -105,7 +105,7 @@
         final IMediaSession2 binder = getSessionBinder();
         if (binder != null) {
             try {
-                binder.getChildren(getControllerStub(), parentId, page, pageSize, options);
+                binder.getChildren(getControllerStub(), parentId, page, pageSize, extras);
             } catch (RemoteException e) {
                 // TODO(jaewan): Handle disconnect.
                 if (DEBUG) {
@@ -118,8 +118,43 @@
     }
 
     @Override
-    public void search_impl(String query, int page, int pageSize, Bundle extras) {
-        // TODO(jaewan): Implement
+    public void search_impl(String query, Bundle extras) {
+        if (TextUtils.isEmpty(query)) {
+            throw new IllegalArgumentException("query shouldn't be empty");
+        }
+        final IMediaSession2 binder = getSessionBinder();
+        if (binder != null) {
+            try {
+                binder.search(getControllerStub(), query, extras);
+            } catch (RemoteException e) {
+                // TODO(jaewan): Handle disconnect.
+                if (DEBUG) {
+                    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 getSearchResult_impl(String query, int page, int pageSize, Bundle extras) {
+        if (TextUtils.isEmpty(query)) {
+            throw new IllegalArgumentException("query shouldn't be empty");
+        }
+        final IMediaSession2 binder = getSessionBinder();
+        if (binder != null) {
+            try {
+                binder.getSearchResult(getControllerStub(), query, page, pageSize, extras);
+            } catch (RemoteException e) {
+                // TODO(jaewan): Handle disconnect.
+                if (DEBUG) {
+                    Log.w(TAG, "Cannot connect to the service or the session is gone", e);
+                }
+            }
+        } else {
+            Log.w(TAG, "Session isn't active", new IllegalStateException());
+        }
     }
 
     public void onGetRootResult(
@@ -135,10 +170,17 @@
         });
     }
 
-    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle options,
+    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle extras,
             List<MediaItem2> result) {
         getCallbackExecutor().execute(() -> {
-            mCallback.onChildrenLoaded(parentId, page, pageSize, options, result);
+            mCallback.onChildrenLoaded(parentId, page, pageSize, extras, result);
+        });
+    }
+
+    public void onSearchResultLoaded(String query, int page, int pageSize, Bundle extras,
+            List<MediaItem2> result) {
+        getCallbackExecutor().execute(() -> {
+            mCallback.onSearchResultLoaded(query, page, pageSize, extras, result);
         });
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
index f51e246..4c4ef24 100644
--- a/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
+++ b/packages/MediaComponents/src/com/android/media/MediaItem2Impl.java
@@ -52,7 +52,7 @@
             throw new IllegalArgumentException("dsd shouldn't be null");
         }
         if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
-            throw new IllegalArgumentException("metadata's id should be match with the mediaid");
+            throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
         }
 
         mContext = context;
@@ -71,9 +71,9 @@
             throw new IllegalArgumentException("mediaId shouldn't be null");
         }
         if (metadata != null && !TextUtils.equals(mediaId, metadata.getMediaId())) {
-            throw new IllegalArgumentException("metadata's id should be match with the mediaid");
+            throw new IllegalArgumentException("metadata's id should be matched with the mediaid");
         }
-        mContext =context;
+        mContext = context;
         mId = mediaId;
         mMetadata = metadata;
         mFlags = flags;
@@ -136,24 +136,21 @@
     }
 
     @Override
-    public void setMetadata_impl(@NonNull MediaMetadata2 metadata) {
-        if (metadata == null) {
-            throw new IllegalArgumentException("metadata shouldn't be null");
-        }
-        if (TextUtils.isEmpty(metadata.getMediaId())) {
-            throw new IllegalArgumentException("metadata must have a non-empty media id");
+    public void setMetadata_impl(@Nullable MediaMetadata2 metadata) {
+        if (metadata != null && !TextUtils.equals(mId, metadata.getMediaId())) {
+            throw new IllegalArgumentException("metadata's id should be matched with the mediaId");
         }
         mMetadata = metadata;
     }
 
     @Override
-    public MediaMetadata2 getMetadata_impl() {
+    public @Nullable MediaMetadata2 getMetadata_impl() {
         return mMetadata;
     }
 
     @Override
-    public @Nullable String getMediaId_impl() {
-        return mMetadata.getMediaId();
+    public @NonNull String getMediaId_impl() {
+        return mId;
     }
 
     @Override
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
index 1f4d12b..852029a 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2CallbackStub.java
@@ -244,7 +244,7 @@
     }
 
     @Override
-    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle options,
+    public void onChildrenLoaded(String parentId, int page, int pageSize, Bundle extras,
             List<Bundle> itemBundleList) throws RuntimeException {
         final MediaBrowser2Impl browser;
         try {
@@ -265,6 +265,31 @@
                 result.add(MediaItem2.fromBundle(browser.getContext(), bundle));
             }
         }
-        browser.onChildrenLoaded(parentId, page, pageSize, options, result);
+        browser.onChildrenLoaded(parentId, page, pageSize, extras, result);
+    }
+
+    @Override
+    public void onSearchResultLoaded(String query, int page, int pageSize, Bundle extras,
+            List<Bundle> itemBundleList) throws RuntimeException {
+        final MediaBrowser2Impl browser;
+        try {
+            browser = getBrowser();
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Don't fail silently here. Highly likely a bug");
+            return;
+        }
+        if (browser == null) {
+            // TODO(jaewan): Revisit here. Could be a bug
+            return;
+        }
+
+        List<MediaItem2> result = null;
+        if (itemBundleList != null) {
+            result = new ArrayList<>();
+            for (Bundle bundle : itemBundleList) {
+                result.add(MediaItem2.fromBundle(browser.getContext(), bundle));
+            }
+        }
+        browser.onSearchResultLoaded(query, page, pageSize, extras, result);
     }
 }
diff --git a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
index f160ef8..759d580 100644
--- a/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
+++ b/packages/MediaComponents/src/com/android/media/MediaSession2Stub.java
@@ -36,6 +36,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.support.annotation.GuardedBy;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -574,7 +575,7 @@
 
     @Override
     public void getChildren(IMediaSession2Callback caller, String parentId, int page,
-            int pageSize, Bundle options) throws RuntimeException {
+            int pageSize, Bundle extras) throws RuntimeException {
         final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
         final ControllerInfo controller = getController(caller);
         if (controller == null) {
@@ -584,11 +585,15 @@
             return;
         }
         if (parentId == null) {
-            Log.d(TAG, "parentId shouldn't be null");
+            if (DEBUG) {
+                Log.d(TAG, "parentId shouldn't be null");
+            }
             return;
         }
         if (page < 1 || pageSize < 1) {
-            Log.d(TAG, "Neither page nor pageSize should be less than 1");
+            if (DEBUG) {
+                Log.d(TAG, "Neither page nor pageSize should be less than 1");
+            }
             return;
         }
 
@@ -599,7 +604,7 @@
             }
             final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
             List<MediaItem2> result = session.getCallback().onLoadChildren(
-                    controller, parentId, page, pageSize, options);
+                    controller, parentId, page, pageSize, extras);
             if (result != null && result.size() > pageSize) {
                 throw new IllegalArgumentException("onLoadChildren() shouldn't return media items "
                         + "more than pageSize. result.size()=" + result.size() + " pageSize="
@@ -616,7 +621,90 @@
 
             try {
                 controllerImpl.getControllerBinder().onChildrenLoaded(
-                        parentId, page, pageSize, options, bundleList);
+                        parentId, page, pageSize, extras, bundleList);
+            } catch (RemoteException e) {
+                // Controller may be died prematurely.
+                // TODO(jaewan): Handle this.
+            }
+        });
+    }
+
+    @Override
+    public void search(IMediaSession2Callback caller, String query, Bundle extras) {
+        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "search() from a controller that hasn't connected. Ignore");
+            }
+            return;
+        }
+        if (TextUtils.isEmpty(query)) {
+            if (DEBUG) {
+                Log.d(TAG, "query shouldn't be empty");
+            }
+            return;
+        }
+
+        sessionImpl.getCallbackExecutor().execute(() -> {
+            final MediaLibrarySessionImpl session = getLibrarySession();
+            if (session == null) {
+                return;
+            }
+            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
+            session.getCallback().onSearch(controller, query, extras);
+        });
+    }
+
+    @Override
+    public void getSearchResult(IMediaSession2Callback caller, String query, int page,
+            int pageSize, Bundle extras) {
+        final MediaLibrarySessionImpl sessionImpl = getLibrarySession();
+        final ControllerInfo controller = getController(caller);
+        if (controller == null) {
+            if (DEBUG) {
+                Log.d(TAG, "getSearchResult() from a controller that hasn't connected. Ignore");
+            }
+            return;
+        }
+        if (TextUtils.isEmpty(query)) {
+            if (DEBUG) {
+                Log.d(TAG, "query shouldn't be empty");
+            }
+            return;
+        }
+        if (page < 1 || pageSize < 1) {
+            if (DEBUG) {
+                Log.d(TAG, "Neither page nor pageSize should be less than 1");
+            }
+            return;
+        }
+
+        sessionImpl.getCallbackExecutor().execute(() -> {
+            final MediaLibrarySessionImpl session = getLibrarySession();
+            if (session == null) {
+                return;
+            }
+            final ControllerInfoImpl controllerImpl = ControllerInfoImpl.from(controller);
+            List<MediaItem2> result = session.getCallback().onLoadSearchResult(
+                    controller, query, page, pageSize, extras);
+            if (result != null && result.size() > pageSize) {
+                throw new IllegalArgumentException("onLoadSearchResult() shouldn't return media "
+                        + "items more than pageSize. result.size()=" + result.size() + " pageSize="
+                        + pageSize);
+            }
+
+            List<Bundle> bundleList = null;
+            if (result != null) {
+                bundleList = new ArrayList<>();
+                for (MediaItem2 item : result) {
+                    bundleList.add(item == null ? null : item.toBundle());
+                }
+            }
+
+            try {
+                controllerImpl.getControllerBinder().onSearchResultLoaded(
+                        query, page, pageSize, extras, bundleList);
             } catch (RemoteException e) {
                 // Controller may be died prematurely.
                 // TODO(jaewan): Handle this.