117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen/*
217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Copyright (C) 2014 The Android Open Source Project
317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *
417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Licensed under the Apache License, Version 2.0 (the "License");
517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * you may not use this file except in compliance with the License.
617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * You may obtain a copy of the License at
717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *
817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *      http://www.apache.org/licenses/LICENSE-2.0
917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *
1017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Unless required by applicable law or agreed to in writing, software
1117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * distributed under the License is distributed on an "AS IS" BASIS,
1217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * See the License for the specific language governing permissions and
1417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * limitations under the License.
1517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */
1617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
173625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikpackage android.service.media;
1817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
198e3fb586219ecfa82cff841b121543752a79f1faSungsoo Limimport android.annotation.IntDef;
2017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.annotation.NonNull;
2117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.annotation.Nullable;
2217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.annotation.SdkConstant;
2317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.annotation.SdkConstant.SdkConstantType;
2417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.app.Service;
2517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.content.Intent;
2617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.content.pm.PackageManager;
2717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.content.pm.ParceledListSlice;
283625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.media.browse.MediaBrowser;
2941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Limimport android.media.browse.MediaBrowserUtils;
3017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.media.session.MediaSession;
3117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.Binder;
3217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.Bundle;
3317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.Handler;
3441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Limimport android.os.IBinder;
3517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.RemoteException;
36b1c88103fea75a19100d534e28366da0144d8138RoboErikimport android.os.ResultReceiver;
373625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.service.media.IMediaBrowserService;
383625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.service.media.IMediaBrowserServiceCallbacks;
39b1c88103fea75a19100d534e28366da0144d8138RoboErikimport android.text.TextUtils;
4017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.util.ArrayMap;
4117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.util.Log;
4284c462d3224ec640da9112362cb46ac502a39e7aSungsoo Limimport android.util.Pair;
4317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
4417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport java.io.FileDescriptor;
4517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport java.io.PrintWriter;
468e3fb586219ecfa82cff841b121543752a79f1faSungsoo Limimport java.lang.annotation.Retention;
478e3fb586219ecfa82cff841b121543752a79f1faSungsoo Limimport java.lang.annotation.RetentionPolicy;
4841c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Limimport java.util.ArrayList;
4964f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Limimport java.util.Collections;
5041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Limimport java.util.HashMap;
5117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport java.util.List;
5217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
5317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen/**
5417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Base class for media browse services.
5517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * <p>
5617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Media browse services enable applications to browse media content provided by an application
578e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim * and ask the application to start playing it. They may also be used to control content that
5817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * is already playing by way of a {@link MediaSession}.
5917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * </p>
6017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *
6117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * To extend this class, you must declare the service in your manifest file with
6292e565f71526141cb38f864dcb4eeb54d2cbf869RoboErik * an intent filter with the {@link #SERVICE_INTERFACE} action.
6317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *
6417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * For example:
6517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * </p><pre>
6617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * &lt;service android:name=".MyMediaBrowserService"
6717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *          android:label="&#64;string/service_name" >
6817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *     &lt;intent-filter>
6917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *         &lt;action android:name="android.media.browse.MediaBrowserService" />
7017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *     &lt;/intent-filter>
7117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * &lt;/service>
7217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * </pre>
7317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *
7417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */
7517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenpublic abstract class MediaBrowserService extends Service {
7617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private static final String TAG = "MediaBrowserService";
7769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato    private static final boolean DBG = false;
7869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato
7969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato    /**
8069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato     * The {@link Intent} that must be declared as handled by the service.
8169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato     */
8269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato    @SdkConstant(SdkConstantType.SERVICE_ACTION)
8392e565f71526141cb38f864dcb4eeb54d2cbf869RoboErik    public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
8417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
85b1c88103fea75a19100d534e28366da0144d8138RoboErik    /**
86b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown     * A key for passing the MediaItem to the ResultReceiver in getItem.
87b1c88103fea75a19100d534e28366da0144d8138RoboErik     *
88b1c88103fea75a19100d534e28366da0144d8138RoboErik     * @hide
89b1c88103fea75a19100d534e28366da0144d8138RoboErik     */
90b1c88103fea75a19100d534e28366da0144d8138RoboErik    public static final String KEY_MEDIA_ITEM = "media_item";
91b1c88103fea75a19100d534e28366da0144d8138RoboErik
9241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    private static final int RESULT_FLAG_OPTION_NOT_HANDLED = 0x00000001;
9341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim
948e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim    /** @hide */
958e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim    @Retention(RetentionPolicy.SOURCE)
968e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim    @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED })
978e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim    private @interface ResultFlags { }
988e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim
9941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>();
10051ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim    private ConnectionRecord mCurConnection;
10117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private final Handler mHandler = new Handler();
10217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private ServiceBinder mBinder;
10317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    MediaSession.Token mSession;
10417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
10517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
10617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * All the info about a connection.
10717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
10817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private class ConnectionRecord {
10917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        String pkg;
11017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        Bundle rootHints;
11117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        IMediaBrowserServiceCallbacks callbacks;
112319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        BrowserRoot root;
11384c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        HashMap<String, List<Pair<IBinder, Bundle>>> subscriptions = new HashMap<>();
11417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
11517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
11617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
11769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato     * Completion handler for asynchronous callback methods in {@link MediaBrowserService}.
11869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato     * <p>
11969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato     * Each of the methods that takes one of these to send the result must call
1208e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim     * {@link #sendResult} to respond to the caller with the given results. If those
12169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato     * functions return without calling {@link #sendResult}, they must instead call
12269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato     * {@link #detach} before returning, and then may call {@link #sendResult} when
1238e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim     * they are done. If more than one of those methods is called, an exception will
12469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato     * be thrown.
12569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato     *
12664f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim     * @see #onLoadChildren
12764f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim     * @see #onLoadItem
12817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
12969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato    public class Result<T> {
13069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        private Object mDebug;
13169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        private boolean mDetachCalled;
13269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        private boolean mSendResultCalled;
1338e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim        private int mFlags;
13469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato
13569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        Result(Object debug) {
13669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            mDebug = debug;
13769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        }
13869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato
13969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        /**
14069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato         * Send the result back to the caller.
14169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato         */
14269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        public void sendResult(T result) {
14369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            if (mSendResultCalled) {
14469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                throw new IllegalStateException("sendResult() called twice for: " + mDebug);
14569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            }
14669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            mSendResultCalled = true;
1478e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim            onResultSent(result, mFlags);
14869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        }
14969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato
15069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        /**
15169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato         * Detach this message from the current thread and allow the {@link #sendResult}
15269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato         * call to happen later.
15369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato         */
15469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        public void detach() {
15569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            if (mDetachCalled) {
15669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                throw new IllegalStateException("detach() called when detach() had already"
15769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                        + " been called for: " + mDebug);
15869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            }
15969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            if (mSendResultCalled) {
16069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                throw new IllegalStateException("detach() called when sendResult() had already"
16169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                        + " been called for: " + mDebug);
16269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            }
16369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            mDetachCalled = true;
16469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        }
16569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato
16669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        boolean isDone() {
16769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            return mDetachCalled || mSendResultCalled;
16869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        }
16969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato
1708e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim        void setFlags(@ResultFlags int flags) {
1718e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim            mFlags = flags;
17241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        }
17341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim
17469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        /**
17569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato         * Called when the result is sent, after assertions about not being called twice
17669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato         * have happened.
17769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato         */
1788e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim        void onResultSent(T result, @ResultFlags int flags) {
17969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        }
18069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato    }
18117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
18217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private class ServiceBinder extends IMediaBrowserService.Stub {
18317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        @Override
18417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        public void connect(final String pkg, final Bundle rootHints,
18517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                final IMediaBrowserServiceCallbacks callbacks) {
18617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
18717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            final int uid = Binder.getCallingUid();
18817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (!isValidPackage(pkg, uid)) {
18917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                throw new IllegalArgumentException("Package/uid mismatch: uid=" + uid
19017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        + " package=" + pkg);
19117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
19217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
19317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mHandler.post(new Runnable() {
19417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    @Override
19517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    public void run() {
19617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        final IBinder b = callbacks.asBinder();
19717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
1988e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim                        // Clear out the old subscriptions. We are getting new ones.
19917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        mConnections.remove(b);
20017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
20117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        final ConnectionRecord connection = new ConnectionRecord();
20217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        connection.pkg = pkg;
20317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        connection.rootHints = rootHints;
20417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        connection.callbacks = callbacks;
20517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
20617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        connection.root = MediaBrowserService.this.onGetRoot(pkg, uid, rootHints);
20717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
20817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        // If they didn't return something, don't allow this client.
20917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        if (connection.root == null) {
21017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            Log.i(TAG, "No root for client " + pkg + " from service "
21117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                                    + getClass().getName());
21217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            try {
21317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                                callbacks.onConnectFailed();
21417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            } catch (RemoteException ex) {
21517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                                Log.w(TAG, "Calling onConnectFailed() failed. Ignoring. "
21617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                                        + "pkg=" + pkg);
21717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            }
21817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        } else {
21917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            try {
22017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                                mConnections.put(b, connection);
221d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                                if (mSession != null) {
222d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                                    callbacks.onConnect(connection.root.getRootId(),
223d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                                            mSession, connection.root.getExtras());
224d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                                }
22517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            } catch (RemoteException ex) {
22617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                                Log.w(TAG, "Calling onConnect() failed. Dropping client. "
22717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                                        + "pkg=" + pkg);
22817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                                mConnections.remove(b);
22917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            }
23017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        }
23117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    }
23217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                });
23317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
23417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
23517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        @Override
23617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        public void disconnect(final IMediaBrowserServiceCallbacks callbacks) {
23717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mHandler.post(new Runnable() {
23817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    @Override
23917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    public void run() {
24017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        final IBinder b = callbacks.asBinder();
24117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
2428e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim                        // Clear out the old subscriptions. We are getting new ones.
24317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        final ConnectionRecord old = mConnections.remove(b);
24417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        if (old != null) {
24517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            // TODO
24617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        }
24717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    }
24817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                });
24917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
25017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
25141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        @Override
2526b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim        public void addSubscriptionDeprecated(String id, IMediaBrowserServiceCallbacks callbacks) {
2536b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim            // do-nothing
2546b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim        }
2556b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim
2566b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim        @Override
25784c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        public void addSubscription(final String id, final IBinder token, final Bundle options,
25841c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                final IMediaBrowserServiceCallbacks callbacks) {
25917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mHandler.post(new Runnable() {
26017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    @Override
26117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    public void run() {
26217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        final IBinder b = callbacks.asBinder();
26317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
26417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        // Get the record for the connection
26517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        final ConnectionRecord connection = mConnections.get(b);
26617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        if (connection == null) {
267c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                            Log.w(TAG, "addSubscription for callback that isn't registered id="
268c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                                + id);
26917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            return;
27017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        }
27117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
27284c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                        MediaBrowserService.this.addSubscription(id, connection, token, options);
27317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    }
27417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                });
27517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
27617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
27717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        @Override
2786b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim        public void removeSubscriptionDeprecated(String id, IMediaBrowserServiceCallbacks callbacks) {
2796b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim            // do-nothing
2806b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim        }
2816b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim
2826b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim        @Override
28384c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        public void removeSubscription(final String id, final IBinder token,
28441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                final IMediaBrowserServiceCallbacks callbacks) {
28517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mHandler.post(new Runnable() {
28617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                @Override
28717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                public void run() {
28817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    final IBinder b = callbacks.asBinder();
28917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
29017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    ConnectionRecord connection = mConnections.get(b);
29117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    if (connection == null) {
292c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                        Log.w(TAG, "removeSubscription for callback that isn't registered id="
293c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                                + id);
29417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        return;
29517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    }
29684c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                    if (!MediaBrowserService.this.removeSubscription(id, connection, token)) {
297c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                        Log.w(TAG, "removeSubscription called for " + id
29817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                                + " which is not subscribed");
29917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    }
30017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
30117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            });
30217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
303b1c88103fea75a19100d534e28366da0144d8138RoboErik
304b1c88103fea75a19100d534e28366da0144d8138RoboErik        @Override
30551ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim        public void getMediaItem(final String mediaId, final ResultReceiver receiver,
30651ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim                final IMediaBrowserServiceCallbacks callbacks) {
307b1c88103fea75a19100d534e28366da0144d8138RoboErik            if (TextUtils.isEmpty(mediaId) || receiver == null) {
308b1c88103fea75a19100d534e28366da0144d8138RoboErik                return;
309b1c88103fea75a19100d534e28366da0144d8138RoboErik            }
310b1c88103fea75a19100d534e28366da0144d8138RoboErik
311b1c88103fea75a19100d534e28366da0144d8138RoboErik            mHandler.post(new Runnable() {
312b1c88103fea75a19100d534e28366da0144d8138RoboErik                @Override
313b1c88103fea75a19100d534e28366da0144d8138RoboErik                public void run() {
31451ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim                    final IBinder b = callbacks.asBinder();
31551ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim                    ConnectionRecord connection = mConnections.get(b);
31651ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim                    if (connection == null) {
31751ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim                        Log.w(TAG, "getMediaItem for callback that isn't registered id=" + mediaId);
31851ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim                        return;
31951ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim                    }
32051ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim                    performLoadItem(mediaId, connection, receiver);
321b1c88103fea75a19100d534e28366da0144d8138RoboErik                }
322b1c88103fea75a19100d534e28366da0144d8138RoboErik            });
323b1c88103fea75a19100d534e28366da0144d8138RoboErik        }
32417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
32517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
32617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    @Override
32717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    public void onCreate() {
32817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        super.onCreate();
32917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mBinder = new ServiceBinder();
33017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
33117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
33217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    @Override
33317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    public IBinder onBind(Intent intent) {
33492e565f71526141cb38f864dcb4eeb54d2cbf869RoboErik        if (SERVICE_INTERFACE.equals(intent.getAction())) {
33517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            return mBinder;
33617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
33717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        return null;
33817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
33917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
34017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    @Override
34117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
34217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
34317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
34417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
345319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen     * Called to get the root information for browsing by a particular client.
34617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * <p>
347b1c88103fea75a19100d534e28366da0144d8138RoboErik     * The implementation should verify that the client package has permission
348b1c88103fea75a19100d534e28366da0144d8138RoboErik     * to access browse media information before returning the root id; it
349b1c88103fea75a19100d534e28366da0144d8138RoboErik     * should return null if the client is not allowed to access this
350b1c88103fea75a19100d534e28366da0144d8138RoboErik     * information.
35117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * </p>
35217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     *
353b1c88103fea75a19100d534e28366da0144d8138RoboErik     * @param clientPackageName The package name of the application which is
354b1c88103fea75a19100d534e28366da0144d8138RoboErik     *            requesting access to browse media.
355b1c88103fea75a19100d534e28366da0144d8138RoboErik     * @param clientUid The uid of the application which is requesting access to
356b1c88103fea75a19100d534e28366da0144d8138RoboErik     *            browse media.
35717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * @param rootHints An optional bundle of service-specific arguments to send
358b1c88103fea75a19100d534e28366da0144d8138RoboErik     *            to the media browse service when connecting and retrieving the
359b1c88103fea75a19100d534e28366da0144d8138RoboErik     *            root id for browsing, or null if none. The contents of this
360b1c88103fea75a19100d534e28366da0144d8138RoboErik     *            bundle may affect the information returned when browsing.
361b1c88103fea75a19100d534e28366da0144d8138RoboErik     * @return The {@link BrowserRoot} for accessing this app's content or null.
3620cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo     * @see BrowserRoot#EXTRA_RECENT
3630cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo     * @see BrowserRoot#EXTRA_OFFLINE
3640cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo     * @see BrowserRoot#EXTRA_SUGGESTED
36517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
3667e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen    public abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName,
367319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen            int clientUid, @Nullable Bundle rootHints);
36817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
36917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
37017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Called to get information about the children of a media item.
37169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato     * <p>
372b1c88103fea75a19100d534e28366da0144d8138RoboErik     * Implementations must call {@link Result#sendResult result.sendResult}
373b1c88103fea75a19100d534e28366da0144d8138RoboErik     * with the list of children. If loading the children will be an expensive
374b1c88103fea75a19100d534e28366da0144d8138RoboErik     * operation that should be performed on another thread,
375b1c88103fea75a19100d534e28366da0144d8138RoboErik     * {@link Result#detach result.detach} may be called before returning from
376b1c88103fea75a19100d534e28366da0144d8138RoboErik     * this function, and then {@link Result#sendResult result.sendResult}
377b1c88103fea75a19100d534e28366da0144d8138RoboErik     * called when the loading is complete.
37864f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim     * </p><p>
37964f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim     * In case the media item does not have any children, call {@link Result#sendResult}
38097243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * with an empty list. When the given {@code parentId} is invalid, implementations must
38197243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * call {@link Result#sendResult result.sendResult} with {@code null}, which will invoke
38297243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * {@link MediaBrowser.SubscriptionCallback#onError}.
38364f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim     * </p>
38417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     *
385b1c88103fea75a19100d534e28366da0144d8138RoboErik     * @param parentId The id of the parent media item whose children are to be
386b1c88103fea75a19100d534e28366da0144d8138RoboErik     *            queried.
38797243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * @param result The Result to send the list of children to.
38817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
389c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik    public abstract void onLoadChildren(@NonNull String parentId,
3903625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            @NonNull Result<List<MediaBrowser.MediaItem>> result);
39117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
39217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
39341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * Called to get information about the children of a media item.
39441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * <p>
39541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * Implementations must call {@link Result#sendResult result.sendResult}
39641c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * with the list of children. If loading the children will be an expensive
39741c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * operation that should be performed on another thread,
39841c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * {@link Result#detach result.detach} may be called before returning from
39941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * this function, and then {@link Result#sendResult result.sendResult}
40041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * called when the loading is complete.
40164f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim     * </p><p>
40264f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim     * In case the media item does not have any children, call {@link Result#sendResult}
40397243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * with an empty list. When the given {@code parentId} is invalid, implementations must
40497243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * call {@link Result#sendResult result.sendResult} with {@code null}, which will invoke
40597243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * {@link MediaBrowser.SubscriptionCallback#onError}.
40664f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim     * </p>
40741c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     *
40841c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * @param parentId The id of the parent media item whose children are to be
40941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     *            queried.
41097243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * @param result The Result to send the list of children to.
41141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * @param options A bundle of service-specific arguments sent from the media
41241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     *            browse. The information returned through the result should be
41341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     *            affected by the contents of this bundle.
41441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     */
41541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    public void onLoadChildren(@NonNull String parentId,
41641c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            @NonNull Result<List<MediaBrowser.MediaItem>> result, @NonNull Bundle options) {
41741c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        // To support backward compatibility, when the implementation of MediaBrowserService doesn't
41841c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        // override onLoadChildren() with options, onLoadChildren() without options will be used
41941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        // instead, and the options will be applied in the implementation of result.onResultSent().
4208e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim        result.setFlags(RESULT_FLAG_OPTION_NOT_HANDLED);
42141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        onLoadChildren(parentId, result);
42241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    }
42341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim
42441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    /**
425b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown     * Called to get information about a specific media item.
426b1c88103fea75a19100d534e28366da0144d8138RoboErik     * <p>
427b1c88103fea75a19100d534e28366da0144d8138RoboErik     * Implementations must call {@link Result#sendResult result.sendResult}. If
428b1c88103fea75a19100d534e28366da0144d8138RoboErik     * loading the item will be an expensive operation {@link Result#detach
429b1c88103fea75a19100d534e28366da0144d8138RoboErik     * result.detach} may be called before returning from this function, and
430b1c88103fea75a19100d534e28366da0144d8138RoboErik     * then {@link Result#sendResult result.sendResult} called when the item has
431b1c88103fea75a19100d534e28366da0144d8138RoboErik     * been loaded.
43297243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * </p><p>
43397243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * When the given {@code itemId} is invalid, implementations must call
43497243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * {@link Result#sendResult result.sendResult} with {@code null}, which will
43597243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * invoke {@link MediaBrowser.ItemCallback#onError}.
43697243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * </p><p>
43797243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * The default implementation calls {@link Result#sendResult result.sendResult}
43897243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * with {@code null}.
43997243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * </p>
440b1c88103fea75a19100d534e28366da0144d8138RoboErik     *
441b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown     * @param itemId The id for the specific
442b1c88103fea75a19100d534e28366da0144d8138RoboErik     *            {@link android.media.browse.MediaBrowser.MediaItem}.
44397243a8ff2829e3bb80abacc77a6c7a01a660877Sungsoo Lim     * @param result The Result to send the item to.
444b1c88103fea75a19100d534e28366da0144d8138RoboErik     */
445b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown    public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
446b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown        result.sendResult(null);
447b1c88103fea75a19100d534e28366da0144d8138RoboErik    }
448b1c88103fea75a19100d534e28366da0144d8138RoboErik
449b1c88103fea75a19100d534e28366da0144d8138RoboErik    /**
45017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Call to set the media session.
45117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * <p>
452d64c425f32174a66a3974c63211bf457005a8d6aRoboErik     * This should be called as soon as possible during the service's startup.
453d64c425f32174a66a3974c63211bf457005a8d6aRoboErik     * It may only be called once.
454b1c88103fea75a19100d534e28366da0144d8138RoboErik     *
455b1c88103fea75a19100d534e28366da0144d8138RoboErik     * @param token The token for the service's {@link MediaSession}.
45617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
457d64c425f32174a66a3974c63211bf457005a8d6aRoboErik    public void setSessionToken(final MediaSession.Token token) {
45817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (token == null) {
459d64c425f32174a66a3974c63211bf457005a8d6aRoboErik            throw new IllegalArgumentException("Session token may not be null.");
46017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
461fd228a383c0844d69da952460145b1aa3e00ffd7RoboErik        if (mSession != null) {
462fd228a383c0844d69da952460145b1aa3e00ffd7RoboErik            throw new IllegalStateException("The session token has already been set.");
463fd228a383c0844d69da952460145b1aa3e00ffd7RoboErik        }
464fd228a383c0844d69da952460145b1aa3e00ffd7RoboErik        mSession = token;
465d64c425f32174a66a3974c63211bf457005a8d6aRoboErik        mHandler.post(new Runnable() {
466d64c425f32174a66a3974c63211bf457005a8d6aRoboErik            @Override
467d64c425f32174a66a3974c63211bf457005a8d6aRoboErik            public void run() {
468d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                for (IBinder key : mConnections.keySet()) {
469d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                    ConnectionRecord connection = mConnections.get(key);
470d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                    try {
471fd228a383c0844d69da952460145b1aa3e00ffd7RoboErik                        connection.callbacks.onConnect(connection.root.getRootId(), token,
472d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                                connection.root.getExtras());
473d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                    } catch (RemoteException e) {
474d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                        Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid.");
475d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                        mConnections.remove(key);
476d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                    }
477d64c425f32174a66a3974c63211bf457005a8d6aRoboErik                }
478d64c425f32174a66a3974c63211bf457005a8d6aRoboErik            }
479d64c425f32174a66a3974c63211bf457005a8d6aRoboErik        });
48017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
48117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
48217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
48317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Gets the session token, or null if it has not yet been created
48417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * or if it has been destroyed.
48517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
48617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    public @Nullable MediaSession.Token getSessionToken() {
48717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        return mSession;
48817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
48917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
49017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
49151ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim     * Gets the root hints sent from the currently connected {@link MediaBrowser}.
492bd6c16908d922ecaf672d5f9a6d701fccb95b3ffJae Seo     * The root hints are service-specific arguments included in an optional bundle sent to the
493bd6c16908d922ecaf672d5f9a6d701fccb95b3ffJae Seo     * media browser service when connecting and retrieving the root id for browsing, or null if
494bd6c16908d922ecaf672d5f9a6d701fccb95b3ffJae Seo     * none. The contents of this bundle may affect the information returned when browsing.
49551ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim     *
49651ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim     * @throws IllegalStateException If this method is called outside of {@link #onLoadChildren}
49751ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim     *             or {@link #onLoadItem}
498bd6c16908d922ecaf672d5f9a6d701fccb95b3ffJae Seo     * @see MediaBrowserService.BrowserRoot#EXTRA_RECENT
499bd6c16908d922ecaf672d5f9a6d701fccb95b3ffJae Seo     * @see MediaBrowserService.BrowserRoot#EXTRA_OFFLINE
500bd6c16908d922ecaf672d5f9a6d701fccb95b3ffJae Seo     * @see MediaBrowserService.BrowserRoot#EXTRA_SUGGESTED
50151ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim     */
50251ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim    public final Bundle getBrowserRootHints() {
50351ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim        if (mCurConnection == null) {
50451ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim            throw new IllegalStateException("This should be called inside of onLoadChildren or"
50551ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim                    + " onLoadItem methods");
50651ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim        }
50751ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim        return mCurConnection.rootHints == null ? null : new Bundle(mCurConnection.rootHints);
50851ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim    }
50951ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim
51051ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim    /**
51117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Notifies all connected media browsers that the children of
512c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * the specified parent id have changed in some way.
51317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * This will cause browsers to fetch subscribed content again.
51417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     *
515c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * @param parentId The id of the parent media item whose
51617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * children changed.
51717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
51841c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    public void notifyChildrenChanged(@NonNull String parentId) {
51941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        notifyChildrenChangedInternal(parentId, null);
52041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    }
52141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim
52241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    /**
52341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * Notifies all connected media browsers that the children of
52441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * the specified parent id have changed in some way.
52541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * This will cause browsers to fetch subscribed content again.
52641c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     *
52741c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * @param parentId The id of the parent media item whose
52841c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     *            children changed.
52941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * @param options A bundle of service-specific arguments to send
53041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     *            to the media browse. The contents of this bundle may
53141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     *            contain the information about the change.
53241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     */
53341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    public void notifyChildrenChanged(@NonNull String parentId, @NonNull Bundle options) {
53441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        if (options == null) {
53541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            throw new IllegalArgumentException("options cannot be null in notifyChildrenChanged");
53641c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        }
53741c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        notifyChildrenChangedInternal(parentId, options);
53841c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    }
53941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim
54041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    private void notifyChildrenChangedInternal(final String parentId, final Bundle options) {
541c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        if (parentId == null) {
542c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            throw new IllegalArgumentException("parentId cannot be null in notifyChildrenChanged");
543319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        }
5447e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen        mHandler.post(new Runnable() {
5457e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen            @Override
5467e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen            public void run() {
5477e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen                for (IBinder binder : mConnections.keySet()) {
5487e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen                    ConnectionRecord connection = mConnections.get(binder);
54984c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                    List<Pair<IBinder, Bundle>> callbackList =
55084c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                            connection.subscriptions.get(parentId);
55184c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                    if (callbackList != null) {
55284c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                        for (Pair<IBinder, Bundle> callback : callbackList) {
55384c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                            if (MediaBrowserUtils.hasDuplicatedItems(options, callback.second)) {
55484c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                                performLoadChildren(parentId, connection, callback.second);
55541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                            }
55641c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                        }
5577e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen                    }
558319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen                }
559319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen            }
5607e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen        });
56117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
56217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
56317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
56417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Return whether the given package is one of the ones that is owned by the uid.
56517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
56617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private boolean isValidPackage(String pkg, int uid) {
56717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (pkg == null) {
56817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            return false;
56917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
57017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        final PackageManager pm = getPackageManager();
57117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        final String[] packages = pm.getPackagesForUid(uid);
57217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        final int N = packages.length;
57317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        for (int i=0; i<N; i++) {
57417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (packages[i].equals(pkg)) {
57517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                return true;
57617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
57717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
57817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        return false;
57917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
58017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
58117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
58217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Save the subscription and if it is a new subscription send the results.
58317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
58484c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim    private void addSubscription(String id, ConnectionRecord connection, IBinder token,
58584c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim            Bundle options) {
58617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // Save the subscription
58784c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id);
58884c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        if (callbackList == null) {
58984c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim            callbackList = new ArrayList<>();
59041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        }
59184c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        for (Pair<IBinder, Bundle> callback : callbackList) {
59284c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim            if (token == callback.first
59384c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                    && MediaBrowserUtils.areSameOptions(options, callback.second)) {
59441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                return;
59541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            }
59641c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        }
59784c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        callbackList.add(new Pair<>(token, options));
59884c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        connection.subscriptions.put(id, callbackList);
5997554d099e7f3a7cec166a999615245e7457bd620RoboErik        // send the results
60041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        performLoadChildren(id, connection, options);
60141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    }
60241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim
60341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    /**
60441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     * Remove the subscription.
60541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim     */
60684c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim    private boolean removeSubscription(String id, ConnectionRecord connection, IBinder token) {
60784c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        if (token == null) {
60888a03d6e62edaac715081e091da7e9abc21d2c0dSungsoo Lim            return connection.subscriptions.remove(id) != null;
60988a03d6e62edaac715081e091da7e9abc21d2c0dSungsoo Lim        }
61041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        boolean removed = false;
61184c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id);
61284c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim        if (callbackList != null) {
61384c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim            for (Pair<IBinder, Bundle> callback : callbackList) {
61484c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                if (token == callback.first) {
61541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                    removed = true;
61684c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim                    callbackList.remove(callback);
61741c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                }
61841c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            }
61984c462d3224ec640da9112362cb46ac502a39e7aSungsoo Lim            if (callbackList.size() == 0) {
62041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                connection.subscriptions.remove(id);
62141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            }
62241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        }
62341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        return removed;
62417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
62517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
62617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
62717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Call onLoadChildren and then send the results back to the connection.
62817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * <p>
62917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Callers must make sure that this connection is still connected.
63017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
63141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    private void performLoadChildren(final String parentId, final ConnectionRecord connection,
63241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            final Bundle options) {
6333625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        final Result<List<MediaBrowser.MediaItem>> result
634c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                = new Result<List<MediaBrowser.MediaItem>>(parentId) {
63569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            @Override
6368e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim            void onResultSent(List<MediaBrowser.MediaItem> list, @ResultFlags int flag) {
63769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                if (mConnections.get(connection.callbacks.asBinder()) != connection) {
63869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                    if (DBG) {
63969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                        Log.d(TAG, "Not sending onLoadChildren result for connection that has"
640c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                                + " been disconnected. pkg=" + connection.pkg + " id=" + parentId);
64169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                    }
64269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                    return;
64369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                }
64469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato
64541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                List<MediaBrowser.MediaItem> filteredList =
64641c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                        (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0
64741c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                        ? applyOptions(list, options) : list;
6482189f58ec5a4fa35b7e06d9d28abcef5d67f9c7fSungsoo Lim                final ParceledListSlice<MediaBrowser.MediaItem> pls =
64941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim                        filteredList == null ? null : new ParceledListSlice<>(filteredList);
65069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                try {
6516b7bda0bc36c1decf3272b5872f18f8044e6f1c6Sungsoo Lim                    connection.callbacks.onLoadChildrenWithOptions(parentId, pls, options);
65269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                } catch (RemoteException ex) {
65369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                    // The other side is in the process of crashing.
654c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                    Log.w(TAG, "Calling onLoadChildren() failed for id=" + parentId
65569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                            + " package=" + connection.pkg);
65669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato                }
65769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            }
65869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        };
65969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato
66051ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim        mCurConnection = connection;
66141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        if (options == null) {
66241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            onLoadChildren(parentId, result);
66341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        } else {
66441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            onLoadChildren(parentId, result, options);
66541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        }
66651ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim        mCurConnection = null;
66769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato
66869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato        if (!result.isDone()) {
66969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato            throw new IllegalStateException("onLoadChildren must call detach() or sendResult()"
670c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                    + " before returning for package=" + connection.pkg + " id=" + parentId);
67117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
67217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
673319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen
67441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    private List<MediaBrowser.MediaItem> applyOptions(List<MediaBrowser.MediaItem> list,
67541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            final Bundle options) {
67664f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim        if (list == null) {
67764f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim            return null;
67864f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim        }
67941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        int page = options.getInt(MediaBrowser.EXTRA_PAGE, -1);
68041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        int pageSize = options.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
68141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        if (page == -1 && pageSize == -1) {
68241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            return list;
68341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        }
6840799bd07d780141208be7826491b221506e66160Sungsoo Lim        int fromIndex = pageSize * page;
68541c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        int toIndex = fromIndex + pageSize;
6860799bd07d780141208be7826491b221506e66160Sungsoo Lim        if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
68764f16f1dc1a6bdc1e16c7017a057da1620fac88bSungsoo Lim            return Collections.EMPTY_LIST;
68841c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        }
68941c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        if (toIndex > list.size()) {
69041c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim            toIndex = list.size();
69141c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        }
69241c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim        return list.subList(fromIndex, toIndex);
69341c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim    }
69441c28a3eb18b01818197f331cb8c6596b17db735Sungsoo Lim
69551ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim    private void performLoadItem(String itemId, final ConnectionRecord connection,
69651ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim            final ResultReceiver receiver) {
697b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown        final Result<MediaBrowser.MediaItem> result =
698b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown                new Result<MediaBrowser.MediaItem>(itemId) {
699b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown            @Override
7008e3fb586219ecfa82cff841b121543752a79f1faSungsoo Lim            void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) {
701b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown                Bundle bundle = new Bundle();
702b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown                bundle.putParcelable(KEY_MEDIA_ITEM, item);
703b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown                receiver.send(0, bundle);
704b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown            }
705b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown        };
706b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown
70751ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim        mCurConnection = connection;
70851ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim        onLoadItem(itemId, result);
70951ccdb0f399c82858c979d7de27e59345fc5692fSungsoo Lim        mCurConnection = null;
710b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown
711b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown        if (!result.isDone()) {
712b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown            throw new IllegalStateException("onLoadItem must call detach() or sendResult()"
713b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown                    + " before returning for id=" + itemId);
714b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown        }
715b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown    }
716b7eff8828f0192bc4261a3142708c17c96bbe7b6Jeff Brown
7177e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen    /**
7187e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen     * Contains information that the browser service needs to send to the client
7197e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen     * when first connected.
7207e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen     */
7217e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen    public static final class BrowserRoot {
7220cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo        /**
7230cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * The lookup key for a boolean that indicates whether the browser service should return a
7240cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * browser root for recently played media items.
7250cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         *
7260cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * <p>When creating a media browser for a given media browser service, this key can be
7270cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * supplied as a root hint for retrieving media items that are recently played.
7280cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * If the media browser service can provide such media items, the implementation must return
7290cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
7300cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         *
7310cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * <p>The root hint may contain multiple keys.
7320cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         *
7330cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * @see #EXTRA_OFFLINE
7340cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * @see #EXTRA_SUGGESTED
7350cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         */
7360cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo        public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
7370cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo
7380cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo        /**
7390cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * The lookup key for a boolean that indicates whether the browser service should return a
7400cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * browser root for offline media items.
7410cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         *
7420cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * <p>When creating a media browser for a given media browser service, this key can be
7430cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * supplied as a root hint for retrieving media items that are can be played without an
7440cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * internet connection.
7450cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * If the media browser service can provide such media items, the implementation must return
7460cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
7470cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         *
7480cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * <p>The root hint may contain multiple keys.
7490cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         *
7500cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * @see #EXTRA_RECENT
7510cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * @see #EXTRA_SUGGESTED
7520cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         */
7530cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo        public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
7540cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo
7550cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo        /**
7560cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * The lookup key for a boolean that indicates whether the browser service should return a
7570cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * browser root for suggested media items.
7580cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         *
7590cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * <p>When creating a media browser for a given media browser service, this key can be
7600cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * supplied as a root hint for retrieving the media items suggested by the media browser
7610cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * service. The list of media items passed in {@link android.media.browse.MediaBrowser.SubscriptionCallback#onChildrenLoaded(String, List)}
7620cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * is considered ordered by relevance, first being the top suggestion.
7630cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * If the media browser service can provide such media items, the implementation must return
7640cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
7650cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         *
7660cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * <p>The root hint may contain multiple keys.
7670cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         *
7680cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * @see #EXTRA_RECENT
7690cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         * @see #EXTRA_OFFLINE
7700cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo         */
7710cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo        public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
7720cc5a9d8f66e84a181b54bfea2f257ee84f86de3Jae Seo
773c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        final private String mRootId;
774319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        final private Bundle mExtras;
7757e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen
7767e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen        /**
7777e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen         * Constructs a browser root.
778c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik         * @param rootId The root id for browsing.
7797e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen         * @param extras Any extras about the browser service.
7807e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen         */
781c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) {
782c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            if (rootId == null) {
783c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " +
784319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen                        "Use null for BrowserRoot instead.");
785319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen            }
786c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            mRootId = rootId;
787319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen            mExtras = extras;
788319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        }
789319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen
7907e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen        /**
791c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik         * Gets the root id for browsing.
7927e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen         */
793c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        public String getRootId() {
794c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            return mRootId;
795319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        }
796319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen
7977e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen        /**
798082e873999e293397703fb511ff2edbab306f180Jae Seo         * Gets any extras about the browser service.
7997e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen         */
8007e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen        public Bundle getExtras() {
801319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen            return mExtras;
802319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        }
803319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen    }
80417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen}
805