MediaBrowserCompat: Support pagination on N devices
Bug: 23289404, Bug: 25564520
Change-Id: Ia129b551bac7c6e1448e1468fd78f233eea2b92d
diff --git a/v4/api21/android/support/v4/media/ServiceCallbacksAdapterApi21.java b/v4/api21/android/support/v4/media/ServiceCallbacksAdapterApi21.java
index a9136b1..4bd7dbb 100644
--- a/v4/api21/android/support/v4/media/ServiceCallbacksAdapterApi21.java
+++ b/v4/api21/android/support/v4/media/ServiceCallbacksAdapterApi21.java
@@ -17,6 +17,7 @@
package android.support.v4.media;
import android.media.session.MediaSession;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
diff --git a/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java b/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java
new file mode 100644
index 0000000..0b4f34b
--- /dev/null
+++ b/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 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.support.v4.media;
+
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.annotation.NonNull;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+
+class MediaBrowserCompatApi24 {
+ // TODO: Remove reflections once N released.
+ private static Method sSubscribeMethod;
+ private static Method sUnsubscribeMethod;
+ static {
+ try {
+ sSubscribeMethod = MediaBrowser.class.getMethod("subscribe", new Class[] {
+ String.class, Bundle.class, MediaBrowser.SubscriptionCallback.class});
+ sUnsubscribeMethod = MediaBrowser.class.getMethod("unsubscribe",
+ new Class[] {String.class, Bundle.class});
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static Object createSubscriptionCallback(SubscriptionCallback callback) {
+ return new SubscriptionCallbackProxy<>(callback);
+ }
+
+ public static void subscribe(
+ Object browserObj, String parentId, Bundle options, Object subscriptionCallbackObj) {
+ try {
+ sSubscribeMethod.invoke(browserObj, parentId, options, subscriptionCallbackObj);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void unsubscribe(Object browserObj, String parentId, Bundle options) {
+ try {
+ sUnsubscribeMethod.invoke(browserObj, parentId, options);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+
+ interface SubscriptionCallback extends MediaBrowserCompatApi21.SubscriptionCallback {
+ void onChildrenLoaded(@NonNull String parentId, List<Parcel> children,
+ @NonNull Bundle options);
+ void onError(@NonNull String parentId, @NonNull Bundle options);
+ }
+
+ static class SubscriptionCallbackProxy<T extends SubscriptionCallback>
+ extends MediaBrowserCompatApi21.SubscriptionCallbackProxy<T> {
+ public SubscriptionCallbackProxy(T callback) {
+ super(callback);
+ }
+
+ public void onChildrenLoaded(@NonNull String parentId,
+ List<MediaBrowser.MediaItem> children, @NonNull Bundle options) {
+ mSubscriptionCallback.onChildrenLoaded(
+ parentId, itemListToParcelList(children), options);
+ }
+
+ public void onError(@NonNull String parentId, @NonNull Bundle options) {
+ mSubscriptionCallback.onError(parentId, options);
+ }
+ }
+}
diff --git a/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java b/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java
new file mode 100644
index 0000000..543776e
--- /dev/null
+++ b/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 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.support.v4.media;
+
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+import java.util.List;
+
+class MediaBrowserServiceCompatApi24 extends MediaBrowserServiceCompatApi23 {
+ public static Object createService() {
+ return new MediaBrowserServiceAdaptorApi24();
+ }
+
+ public interface ServiceImplApi24 extends ServiceImplApi23 {
+ void connect(String pkg, Bundle rootHints, ServiceCallbacksApi24 callbacks);
+ void addSubscription(String id, Bundle options, ServiceCallbacksApi24 callbacks);
+ void removeSubscription(String id, Bundle options, ServiceCallbacksApi24 callbacks);
+ }
+
+ public interface ServiceCallbacksApi24 extends ServiceCallbacksApi21 {
+ void onLoadChildren(String mediaId, List<Parcel> list, Bundle options)
+ throws RemoteException;
+ }
+
+ public static class ServiceCallbacksImplApi24 extends ServiceCallbacksImplApi21
+ implements ServiceCallbacksApi24 {
+ ServiceCallbacksImplApi24(Object callbacksObj) {
+ super(callbacksObj);
+ }
+
+ @Override
+ public void onLoadChildren(String mediaId, List<Parcel> list, Bundle options)
+ throws RemoteException {
+ ((ServiceCallbacksAdapterApi24)mCallbacks).onLoadChildrenWithOptions(
+ mediaId, parcelListToParceledListSliceObject(list), options);
+ }
+
+ @Override
+ ServiceCallbacksAdapterApi24 createCallbacks(Object callbacksObj) {
+ return new ServiceCallbacksAdapterApi24(callbacksObj);
+ }
+ }
+
+ static class MediaBrowserServiceAdaptorApi24 extends MediaBrowserServiceAdaptorApi23 {
+ protected Binder createServiceBinder(ServiceImplApi21 serviceImpl) {
+ return new ServiceBinderAdapterApi24((ServiceImplApi24) serviceImpl);
+ }
+ }
+}
diff --git a/v4/api24/android/support/v4/media/ServiceBinderAdapterApi24.java b/v4/api24/android/support/v4/media/ServiceBinderAdapterApi24.java
new file mode 100644
index 0000000..1a6fef8
--- /dev/null
+++ b/v4/api24/android/support/v4/media/ServiceBinderAdapterApi24.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 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.support.v4.media;
+
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+/**
+ * A class for presenting a service binder for API 24.
+ *
+ * MediaBrowserServiceCompat should inherit a private class, MediaBrowserService.ServiceBinder,
+ * which extends IMediaBrowserService.Stub. As a way to inherit the private class,
+ * ServiceBinderAdapterApi21 implements all necessary functionalities including the codes in the
+ * auto generated hidden class, IMediaBrowserService.Stub for API 24.
+ */
+class ServiceBinderAdapterApi24 extends ServiceBinderAdapterApi23 {
+ // Following TRANSACTION_XXX values are synchronized with the auto generated java file
+ // from IMediaBrowserService.aidl
+ private static final int TRANSACTION_addSubscriptionWithOptions =
+ IBinder.FIRST_CALL_TRANSACTION + 5;
+ private static final int TRANSACTION_removeSubscriptionWithOptions =
+ IBinder.FIRST_CALL_TRANSACTION + 6;
+
+ final MediaBrowserServiceCompatApi24.ServiceImplApi24 mServiceImpl;
+
+ public ServiceBinderAdapterApi24(
+ MediaBrowserServiceCompatApi24.ServiceImplApi24 serviceImpl) {
+ super(serviceImpl);
+ mServiceImpl = serviceImpl;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ switch (code) {
+ case TRANSACTION_addSubscriptionWithOptions: {
+ data.enforceInterface(DESCRIPTOR);
+ String arg0 = data.readString();
+ Bundle arg1 = (data.readInt() == 0)
+ ? null : Bundle.CREATOR.createFromParcel(data);
+ Object arg2 = ServiceCallbacksAdapterApi24.Stub.asInterface(
+ data.readStrongBinder());
+ addSubscription(arg0, arg1, arg2);
+ return true;
+ }
+ case TRANSACTION_removeSubscriptionWithOptions: {
+ data.enforceInterface(DESCRIPTOR);
+ String arg0 = data.readString();
+ Bundle arg1 = (data.readInt() == 0)
+ ? null : Bundle.CREATOR.createFromParcel(data);
+ Object arg2 = ServiceCallbacksAdapterApi24.Stub.asInterface(
+ data.readStrongBinder());
+ removeSubscription(arg0, arg1, arg2);
+ return true;
+ }
+ }
+ return super.onTransact(code, data, reply, flags);
+ }
+
+ @Override
+ void connect(String pkg, Bundle rootHints, Object callbacks) {
+ mServiceImpl.connect(pkg, rootHints,
+ new MediaBrowserServiceCompatApi24.ServiceCallbacksImplApi24(callbacks));
+ }
+
+ void addSubscription(String id, Bundle options, Object callbacks) {
+ mServiceImpl.addSubscription(id, options,
+ new MediaBrowserServiceCompatApi24.ServiceCallbacksImplApi24(callbacks));
+ }
+
+ void removeSubscription(String id, Bundle options, Object callbacks) {
+ mServiceImpl.removeSubscription(id, options,
+ new MediaBrowserServiceCompatApi24.ServiceCallbacksImplApi24(callbacks));
+ }
+}
+
diff --git a/v4/api24/android/support/v4/media/ServiceCallbacksAdapterApi24.java b/v4/api24/android/support/v4/media/ServiceCallbacksAdapterApi24.java
new file mode 100644
index 0000000..741cabe
--- /dev/null
+++ b/v4/api24/android/support/v4/media/ServiceCallbacksAdapterApi24.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 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.support.v4.media;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * An adapter class for accessing the hidden framework classes, IMediaBrowserServiceCallbacks and
+ * IMediaBrowserServiceCallbacks.Stub using reflection.
+ */
+class ServiceCallbacksAdapterApi24 extends ServiceCallbacksAdapterApi21 {
+ private Method mOnLoadChildrenMethodWithOptionsMethod;
+
+ ServiceCallbacksAdapterApi24(Object callbackObject) {
+ super(callbackObject);
+ try {
+ Class theClass = Class.forName("android.service.media.IMediaBrowserServiceCallbacks");
+ Class parceledListSliceClass = Class.forName("android.content.pm.ParceledListSlice");
+ mOnLoadChildrenMethodWithOptionsMethod = theClass.getMethod("onLoadChildrenWithOptions",
+ new Class[] { String.class, parceledListSliceClass, Bundle.class });
+ } catch (ClassNotFoundException | NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ }
+
+ void onLoadChildrenWithOptions(String mediaId, Object parceledListSliceObj, Bundle options)
+ throws RemoteException {
+ try {
+ mOnLoadChildrenMethodWithOptionsMethod.invoke(
+ mCallbackObject, mediaId, parceledListSliceObj, options);
+ } catch (IllegalAccessException | InvocationTargetException | NullPointerException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/v4/java/android/support/v4/media/MediaBrowserCompat.java b/v4/java/android/support/v4/media/MediaBrowserCompat.java
index 90232cc..8d3c87e 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -93,7 +93,9 @@
*/
public MediaBrowserCompat(Context context, ComponentName serviceComponent,
ConnectionCallback callback, Bundle rootHints) {
- if (Build.VERSION.SDK_INT >= 23) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ mImpl = new MediaBrowserImplApi24(context, serviceComponent, callback, rootHints);
+ } else if (Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaBrowserImplApi23(context, serviceComponent, callback, rootHints);
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);
@@ -534,8 +536,13 @@
public SubscriptionCallbackApi21(SubscriptionCallback callback, Bundle options) {
mSubscriptionCallback = callback;
mOptions = options;
- mSubscriptionCallbackObj =
- MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
+ if (Build.VERSION.SDK_INT >= 24) {
+ mSubscriptionCallbackObj =
+ MediaBrowserCompatApi24.createSubscriptionCallback(new StubApi24());
+ } else {
+ mSubscriptionCallbackObj =
+ MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
+ }
}
/**
@@ -629,6 +636,21 @@
return items;
}
}
+
+ private class StubApi24 extends StubApi21
+ implements MediaBrowserCompatApi24.SubscriptionCallback {
+ @Override
+ public void onChildrenLoaded(@NonNull String parentId, @NonNull List<Parcel> children,
+ @NonNull Bundle options) {
+ SubscriptionCallbackApi21.this.onChildrenLoaded(
+ parentId, parcelListToItemList(children), mOptions);
+ }
+
+ @Override
+ public void onError(@NonNull String parentId, @NonNull Bundle options) {
+ SubscriptionCallbackApi21.this.onError(parentId, mOptions);
+ }
+ }
}
/**
@@ -1486,6 +1508,35 @@
}
}
+ static class MediaBrowserImplApi24 extends MediaBrowserImplApi23 {
+ public MediaBrowserImplApi24(Context context, ComponentName serviceComponent,
+ ConnectionCallback callback, Bundle rootHints) {
+ super(context, serviceComponent, callback, rootHints);
+ }
+
+ @Override
+ public void subscribe(@NonNull String parentId, @NonNull Bundle options,
+ @NonNull SubscriptionCallback callback) {
+ SubscriptionCallbackApi21 cb21 = new SubscriptionCallbackApi21(callback, options);
+ if (options == null) {
+ MediaBrowserCompatApi21.subscribe(
+ mBrowserObj, parentId, cb21.mSubscriptionCallbackObj);
+ } else {
+ MediaBrowserCompatApi24.subscribe(
+ mBrowserObj, parentId, options, cb21.mSubscriptionCallbackObj);
+ }
+ }
+
+ @Override
+ public void unsubscribe(@NonNull String parentId, Bundle options) {
+ if (options == null) {
+ MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ } else {
+ MediaBrowserCompatApi24.unsubscribe(mBrowserObj, parentId, options);
+ }
+ }
+ }
+
private static class Subscription {
private final List<SubscriptionCallback> mCallbacks;
private final List<Bundle> mOptionsList;
diff --git a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
index 8acaf75..61e87b7 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -151,6 +151,21 @@
}
}
+ class MediaBrowserServiceImplApi24 implements MediaBrowserServiceImpl {
+ private Object mServiceObj;
+
+ @Override
+ public void onCreate() {
+ mServiceObj = MediaBrowserServiceCompatApi24.createService();
+ MediaBrowserServiceCompatApi24.onCreate(mServiceObj, new ServiceImplApi24());
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return MediaBrowserServiceCompatApi23.onBind(mServiceObj, intent);
+ }
+ }
+
private final class ServiceHandler extends Handler {
private final ServiceImpl mServiceImpl = new ServiceImpl();
@@ -481,6 +496,27 @@
}
}
+ private class ServiceImplApi24 extends ServiceImplApi23
+ implements MediaBrowserServiceCompatApi24.ServiceImplApi24 {
+ @Override
+ public void connect(String pkg, Bundle rootHints,
+ MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
+ mServiceImpl.connect(pkg, Binder.getCallingUid(), rootHints,
+ new ServiceCallbacksApi24(callbacks));
+ }
+
+ @Override
+ public void addSubscription(String id, Bundle options,
+ MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
+ mServiceImpl.addSubscription(id, options, new ServiceCallbacksApi24(callbacks));
+ }
+
+ public void removeSubscription(String id, Bundle options,
+ MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
+ mServiceImpl.removeSubscription(id, options, new ServiceCallbacksApi24(callbacks));
+ }
+ }
+
private interface ServiceCallbacks {
IBinder asBinder();
void onConnect(String root, MediaSessionCompat.Token session, Bundle extras)
@@ -581,10 +617,39 @@
}
}
+ private class ServiceCallbacksApi24 extends ServiceCallbacksApi21 {
+ final MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 mCallbacks;
+
+ ServiceCallbacksApi24(MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
+ super(callbacks);
+ mCallbacks = callbacks;
+ }
+
+ public void onLoadChildren(String mediaId, List<MediaBrowserCompat.MediaItem> list,
+ Bundle options) throws RemoteException {
+ List<Parcel> parcelList = null;
+ if (list != null) {
+ parcelList = new ArrayList<>();
+ for (MediaBrowserCompat.MediaItem item : list) {
+ Parcel parcel = Parcel.obtain();
+ item.writeToParcel(parcel, 0);
+ parcelList.add(parcel);
+ }
+ }
+ if (options == null) {
+ mCallbacks.onLoadChildren(mediaId, parcelList);
+ } else {
+ mCallbacks.onLoadChildren(mediaId, parcelList, options);
+ }
+ }
+ }
+
@Override
public void onCreate() {
super.onCreate();
- if (Build.VERSION.SDK_INT >= 23) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ mImpl = new MediaBrowserServiceImplApi24();
+ } else if (Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaBrowserServiceImplApi23();
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaBrowserServiceImplApi21();