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 1917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport 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; 2917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.media.session.MediaSession; 3017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.Binder; 3117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.Bundle; 3217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.IBinder; 3317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.Handler; 3417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.RemoteException; 353625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.service.media.IMediaBrowserService; 363625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.service.media.IMediaBrowserServiceCallbacks; 3717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.util.ArrayMap; 3817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.util.Log; 3917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 4017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport java.io.FileDescriptor; 4117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport java.io.PrintWriter; 4217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport java.util.HashSet; 4317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport java.util.List; 4417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 4517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen/** 4617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Base class for media browse services. 4717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * <p> 4817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Media browse services enable applications to browse media content provided by an application 4917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * and ask the application to start playing it. They may also be used to control content that 5017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * is already playing by way of a {@link MediaSession}. 5117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * </p> 5217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * 5317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * To extend this class, you must declare the service in your manifest file with 5492e565f71526141cb38f864dcb4eeb54d2cbf869RoboErik * an intent filter with the {@link #SERVICE_INTERFACE} action. 5517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * 5617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * For example: 5717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * </p><pre> 5817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * <service android:name=".MyMediaBrowserService" 5917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * android:label="@string/service_name" > 6017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * <intent-filter> 6117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * <action android:name="android.media.browse.MediaBrowserService" /> 6217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * </intent-filter> 6317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * </service> 6417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * </pre> 6517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * 6617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 6717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenpublic abstract class MediaBrowserService extends Service { 6817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen private static final String TAG = "MediaBrowserService"; 6969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato private static final boolean DBG = false; 7069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato 7169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato /** 7269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * The {@link Intent} that must be declared as handled by the service. 7369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato */ 7469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato @SdkConstant(SdkConstantType.SERVICE_ACTION) 7592e565f71526141cb38f864dcb4eeb54d2cbf869RoboErik public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService"; 7617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 7717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap(); 7817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen private final Handler mHandler = new Handler(); 7917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen private ServiceBinder mBinder; 8017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen MediaSession.Token mSession; 8117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 8217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen /** 8317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * All the info about a connection. 8417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 8517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen private class ConnectionRecord { 8617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen String pkg; 8717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen Bundle rootHints; 8817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen IMediaBrowserServiceCallbacks callbacks; 89319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen BrowserRoot root; 90c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik HashSet<String> subscriptions = new HashSet(); 9117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 9217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 9317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen /** 9469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * Completion handler for asynchronous callback methods in {@link MediaBrowserService}. 9569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * <p> 9669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * Each of the methods that takes one of these to send the result must call 9769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * {@link #sendResult} to respond to the caller with the given results. If those 9869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * functions return without calling {@link #sendResult}, they must instead call 9969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * {@link #detach} before returning, and then may call {@link #sendResult} when 10069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * they are done. If more than one of those methods is called, an exception will 10169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * be thrown. 10269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * 10369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * @see MediaBrowserService#onLoadChildren 10417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 10569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato public class Result<T> { 10669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato private Object mDebug; 10769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato private boolean mDetachCalled; 10869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato private boolean mSendResultCalled; 10969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato 11069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato Result(Object debug) { 11169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato mDebug = debug; 11269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 11369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato 11469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato /** 11569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * Send the result back to the caller. 11669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato */ 11769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato public void sendResult(T result) { 11869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato if (mSendResultCalled) { 11969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato throw new IllegalStateException("sendResult() called twice for: " + mDebug); 12069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 12169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato mSendResultCalled = true; 12269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato onResultSent(result); 12369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 12469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato 12569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato /** 12669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * Detach this message from the current thread and allow the {@link #sendResult} 12769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * call to happen later. 12869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato */ 12969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato public void detach() { 13069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato if (mDetachCalled) { 13169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato throw new IllegalStateException("detach() called when detach() had already" 13269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato + " been called for: " + mDebug); 13369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 13469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato if (mSendResultCalled) { 13569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato throw new IllegalStateException("detach() called when sendResult() had already" 13669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato + " been called for: " + mDebug); 13769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 13869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato mDetachCalled = true; 13969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 14069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato 14169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato boolean isDone() { 14269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato return mDetachCalled || mSendResultCalled; 14369b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 14469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato 14569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato /** 14669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * Called when the result is sent, after assertions about not being called twice 14769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * have happened. 14869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato */ 14969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato void onResultSent(T result) { 15069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 15169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 15217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 15317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen private class ServiceBinder extends IMediaBrowserService.Stub { 15417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 15517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen public void connect(final String pkg, final Bundle rootHints, 15617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final IMediaBrowserServiceCallbacks callbacks) { 15717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 15817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final int uid = Binder.getCallingUid(); 15917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen if (!isValidPackage(pkg, uid)) { 16017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen throw new IllegalArgumentException("Package/uid mismatch: uid=" + uid 16117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen + " package=" + pkg); 16217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 16317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 16417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen mHandler.post(new Runnable() { 16517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 16617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen public void run() { 16717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final IBinder b = callbacks.asBinder(); 16817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 16917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen // Clear out the old subscriptions. We are getting new ones. 17017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen mConnections.remove(b); 17117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 17217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final ConnectionRecord connection = new ConnectionRecord(); 17317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen connection.pkg = pkg; 17417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen connection.rootHints = rootHints; 17517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen connection.callbacks = callbacks; 17617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 17717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen connection.root = MediaBrowserService.this.onGetRoot(pkg, uid, rootHints); 17817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 17917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen // If they didn't return something, don't allow this client. 18017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen if (connection.root == null) { 18117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen Log.i(TAG, "No root for client " + pkg + " from service " 18217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen + getClass().getName()); 18317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen try { 18417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen callbacks.onConnectFailed(); 18517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } catch (RemoteException ex) { 18617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen Log.w(TAG, "Calling onConnectFailed() failed. Ignoring. " 18717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen + "pkg=" + pkg); 18817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 18917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } else { 19017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen try { 19117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen mConnections.put(b, connection); 192d64c425f32174a66a3974c63211bf457005a8d6aRoboErik if (mSession != null) { 193d64c425f32174a66a3974c63211bf457005a8d6aRoboErik callbacks.onConnect(connection.root.getRootId(), 194d64c425f32174a66a3974c63211bf457005a8d6aRoboErik mSession, connection.root.getExtras()); 195d64c425f32174a66a3974c63211bf457005a8d6aRoboErik } 19617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } catch (RemoteException ex) { 19717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen Log.w(TAG, "Calling onConnect() failed. Dropping client. " 19817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen + "pkg=" + pkg); 19917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen mConnections.remove(b); 20017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 20117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 20217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 20317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen }); 20417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 20517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 20617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 20717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen public void disconnect(final IMediaBrowserServiceCallbacks callbacks) { 20817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen mHandler.post(new Runnable() { 20917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 21017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen public void run() { 21117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final IBinder b = callbacks.asBinder(); 21217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 21317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen // Clear out the old subscriptions. We are getting new ones. 21417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final ConnectionRecord old = mConnections.remove(b); 21517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen if (old != null) { 21617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen // TODO 21717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 21817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 21917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen }); 22017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 22117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 22217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 22317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 224c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik public void addSubscription(final String id, final IMediaBrowserServiceCallbacks callbacks) { 22517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen mHandler.post(new Runnable() { 22617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 22717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen public void run() { 22817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final IBinder b = callbacks.asBinder(); 22917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 23017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen // Get the record for the connection 23117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final ConnectionRecord connection = mConnections.get(b); 23217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen if (connection == null) { 233c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik Log.w(TAG, "addSubscription for callback that isn't registered id=" 234c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik + id); 23517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen return; 23617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 23717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 238c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik MediaBrowserService.this.addSubscription(id, connection); 23917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 24017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen }); 24117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 24217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 24317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 244c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik public void removeSubscription(final String id, 24517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final IMediaBrowserServiceCallbacks callbacks) { 24617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen mHandler.post(new Runnable() { 24717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 24817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen public void run() { 24917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final IBinder b = callbacks.asBinder(); 25017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 25117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen ConnectionRecord connection = mConnections.get(b); 25217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen if (connection == null) { 253c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik Log.w(TAG, "removeSubscription for callback that isn't registered id=" 254c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik + id); 25517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen return; 25617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 257c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik if (!connection.subscriptions.remove(id)) { 258c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik Log.w(TAG, "removeSubscription called for " + id 25917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen + " which is not subscribed"); 26017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 26117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 26217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen }); 26317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 26417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 26517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 26617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 26717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen public void onCreate() { 26817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen super.onCreate(); 26917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen mBinder = new ServiceBinder(); 27017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 27117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 27217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 27317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen public IBinder onBind(Intent intent) { 27492e565f71526141cb38f864dcb4eeb54d2cbf869RoboErik if (SERVICE_INTERFACE.equals(intent.getAction())) { 27517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen return mBinder; 27617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 27717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen return null; 27817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 27917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 28017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen @Override 28117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 28217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 28317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 28417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen /** 285319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen * Called to get the root information for browsing by a particular client. 28617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * <p> 28717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * The implementation should verify that the client package has 28817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * permission to access browse media information before returning 289c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik * the root id; it should return null if the client is not 29017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * allowed to access this information. 29117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * </p> 29217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * 29317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * @param clientPackageName The package name of the application 29417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * which is requesting access to browse media. 29517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * @param clientUid The uid of the application which is requesting 29617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * access to browse media. 29717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * @param rootHints An optional bundle of service-specific arguments to send 298c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik * to the media browse service when connecting and retrieving the root id 29917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * for browsing, or null if none. The contents of this bundle may affect 30017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * the information returned when browsing. 30117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 3027e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen public abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName, 303319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen int clientUid, @Nullable Bundle rootHints); 30417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 30517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen /** 30617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Called to get information about the children of a media item. 30769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * <p> 30869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * Implementations must call result.{@link Result#sendResult result.sendResult} with the list 30969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * of children. If loading the children will be an expensive operation that should be performed 31069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * on another thread, result.{@link Result#detach result.detach} may be called before returning 31169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * from this function, and then {@link Result#sendResult result.sendResult} called when 31269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato * the loading is complete. 31317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * 314c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik * @param parentId The id of the parent media item whose 31517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * children are to be queried. 316c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik * @return The list of children, or null if the id is invalid. 31717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 318c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik public abstract void onLoadChildren(@NonNull String parentId, 3193625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik @NonNull Result<List<MediaBrowser.MediaItem>> result); 32017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 32117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen /** 32217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Call to set the media session. 32317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * <p> 324d64c425f32174a66a3974c63211bf457005a8d6aRoboErik * This should be called as soon as possible during the service's startup. 325d64c425f32174a66a3974c63211bf457005a8d6aRoboErik * It may only be called once. 32617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 327d64c425f32174a66a3974c63211bf457005a8d6aRoboErik public void setSessionToken(final MediaSession.Token token) { 32817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen if (token == null) { 329d64c425f32174a66a3974c63211bf457005a8d6aRoboErik throw new IllegalArgumentException("Session token may not be null."); 33017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 331fd228a383c0844d69da952460145b1aa3e00ffd7RoboErik if (mSession != null) { 332fd228a383c0844d69da952460145b1aa3e00ffd7RoboErik throw new IllegalStateException("The session token has already been set."); 333fd228a383c0844d69da952460145b1aa3e00ffd7RoboErik } 334fd228a383c0844d69da952460145b1aa3e00ffd7RoboErik mSession = token; 335d64c425f32174a66a3974c63211bf457005a8d6aRoboErik mHandler.post(new Runnable() { 336d64c425f32174a66a3974c63211bf457005a8d6aRoboErik @Override 337d64c425f32174a66a3974c63211bf457005a8d6aRoboErik public void run() { 338d64c425f32174a66a3974c63211bf457005a8d6aRoboErik for (IBinder key : mConnections.keySet()) { 339d64c425f32174a66a3974c63211bf457005a8d6aRoboErik ConnectionRecord connection = mConnections.get(key); 340d64c425f32174a66a3974c63211bf457005a8d6aRoboErik try { 341fd228a383c0844d69da952460145b1aa3e00ffd7RoboErik connection.callbacks.onConnect(connection.root.getRootId(), token, 342d64c425f32174a66a3974c63211bf457005a8d6aRoboErik connection.root.getExtras()); 343d64c425f32174a66a3974c63211bf457005a8d6aRoboErik } catch (RemoteException e) { 344d64c425f32174a66a3974c63211bf457005a8d6aRoboErik Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid."); 345d64c425f32174a66a3974c63211bf457005a8d6aRoboErik mConnections.remove(key); 346d64c425f32174a66a3974c63211bf457005a8d6aRoboErik } 347d64c425f32174a66a3974c63211bf457005a8d6aRoboErik } 348d64c425f32174a66a3974c63211bf457005a8d6aRoboErik } 349d64c425f32174a66a3974c63211bf457005a8d6aRoboErik }); 35017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 35117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 35217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen /** 35317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Gets the session token, or null if it has not yet been created 35417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * or if it has been destroyed. 35517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 35617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen public @Nullable MediaSession.Token getSessionToken() { 35717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen return mSession; 35817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 35917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 36017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen /** 36117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Notifies all connected media browsers that the children of 362c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik * the specified parent id have changed in some way. 36317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * This will cause browsers to fetch subscribed content again. 36417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * 365c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik * @param parentId The id of the parent media item whose 36617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * children changed. 36717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 368c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik public void notifyChildrenChanged(@NonNull final String parentId) { 369c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik if (parentId == null) { 370c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik throw new IllegalArgumentException("parentId cannot be null in notifyChildrenChanged"); 371319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen } 3727e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen mHandler.post(new Runnable() { 3737e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen @Override 3747e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen public void run() { 3757e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen for (IBinder binder : mConnections.keySet()) { 3767e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen ConnectionRecord connection = mConnections.get(binder); 377c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik if (connection.subscriptions.contains(parentId)) { 378c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik performLoadChildren(parentId, connection); 3797e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen } 380319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen } 381319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen } 3827e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen }); 38317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 38417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 38517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen /** 38617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Return whether the given package is one of the ones that is owned by the uid. 38717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 38817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen private boolean isValidPackage(String pkg, int uid) { 38917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen if (pkg == null) { 39017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen return false; 39117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 39217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final PackageManager pm = getPackageManager(); 39317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final String[] packages = pm.getPackagesForUid(uid); 39417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen final int N = packages.length; 39517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen for (int i=0; i<N; i++) { 39617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen if (packages[i].equals(pkg)) { 39717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen return true; 39817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 39917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 40017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen return false; 40117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 40217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 40317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen /** 40417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Save the subscription and if it is a new subscription send the results. 40517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 406c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik private void addSubscription(String id, ConnectionRecord connection) { 40717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen // Save the subscription 408c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik final boolean added = connection.subscriptions.add(id); 40917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 41017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen // If this is a new subscription, send the results 41117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen if (added) { 412c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik performLoadChildren(id, connection); 41317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 41417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 41517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen 41617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen /** 41717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Call onLoadChildren and then send the results back to the connection. 41817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * <p> 41917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Callers must make sure that this connection is still connected. 42017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */ 421c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik private void performLoadChildren(final String parentId, final ConnectionRecord connection) { 4223625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik final Result<List<MediaBrowser.MediaItem>> result 423c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik = new Result<List<MediaBrowser.MediaItem>>(parentId) { 42469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato @Override 4253625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik void onResultSent(List<MediaBrowser.MediaItem> list) { 42669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato if (list == null) { 427c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik throw new IllegalStateException("onLoadChildren sent null list for id " 428c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik + parentId); 42969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 43069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato if (mConnections.get(connection.callbacks.asBinder()) != connection) { 43169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato if (DBG) { 43269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato Log.d(TAG, "Not sending onLoadChildren result for connection that has" 433c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik + " been disconnected. pkg=" + connection.pkg + " id=" + parentId); 43469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 43569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato return; 43669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 43769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato 4383625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik final ParceledListSlice<MediaBrowser.MediaItem> pls = new ParceledListSlice(list); 43969b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato try { 440c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik connection.callbacks.onLoadChildren(parentId, pls); 44169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } catch (RemoteException ex) { 44269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato // The other side is in the process of crashing. 443c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik Log.w(TAG, "Calling onLoadChildren() failed for id=" + parentId 44469b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato + " package=" + connection.pkg); 44569b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 44669b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato } 44769b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato }; 44869b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato 449c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik onLoadChildren(parentId, result); 45069b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato 45169b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato if (!result.isDone()) { 45269b078599b8d8bc3e8f94d6cab881145f4e2c129Joe Onorato throw new IllegalStateException("onLoadChildren must call detach() or sendResult()" 453c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik + " before returning for package=" + connection.pkg + " id=" + parentId); 45417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 45517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen } 456319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen 4577e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen /** 4587e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen * Contains information that the browser service needs to send to the client 4597e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen * when first connected. 4607e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen */ 4617e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen public static final class BrowserRoot { 462c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik final private String mRootId; 463319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen final private Bundle mExtras; 4647e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen 4657e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen /** 4667e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen * Constructs a browser root. 467c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik * @param rootId The root id for browsing. 4687e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen * @param extras Any extras about the browser service. 4697e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen */ 470c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) { 471c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik if (rootId == null) { 472c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " + 473319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen "Use null for BrowserRoot instead."); 474319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen } 475c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik mRootId = rootId; 476319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen mExtras = extras; 477319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen } 478319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen 4797e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen /** 480c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik * Gets the root id for browsing. 4817e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen */ 482c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik public String getRootId() { 483c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik return mRootId; 484319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen } 485319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen 4867e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen /** 4877e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen * Gets any extras about the brwoser service. 4887e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen */ 4897e21fe8be9d374d31e75cdc2c9a11f977ba1faa0Yao Chen public Bundle getExtras() { 490319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen return mExtras; 491319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen } 492319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen } 49317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen} 494