/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.telephony; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.DeadObjectException; import android.os.IBinder; import android.os.RemoteException; import android.telephony.mbms.MbmsException; import android.telephony.mbms.MbmsStreamingManagerCallback; import android.telephony.mbms.StreamingService; import android.telephony.mbms.StreamingServiceCallback; import android.telephony.mbms.StreamingServiceInfo; import android.telephony.mbms.vendor.IMbmsStreamingService; import android.util.Log; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; /** @hide */ public class MbmsStreamingManager { private interface ServiceListener { void onServiceConnected(); void onServiceDisconnected(); } private static final String LOG_TAG = "MbmsStreamingManager"; public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming"; private static final boolean DEBUG = true; private static final int BIND_TIMEOUT_MS = 3000; private IMbmsStreamingService mService; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { if (service != null) { Log.i(LOG_TAG, String.format("Connected to service %s", name)); synchronized (MbmsStreamingManager.this) { mService = IMbmsStreamingService.Stub.asInterface(service); mServiceListeners.forEach(ServiceListener::onServiceConnected); } } } @Override public void onServiceDisconnected(ComponentName name) { Log.i(LOG_TAG, String.format("Disconnected from service %s", name)); synchronized (MbmsStreamingManager.this) { mService = null; mServiceListeners.forEach(ServiceListener::onServiceDisconnected); } } }; private List<ServiceListener> mServiceListeners = new LinkedList<>(); private MbmsStreamingManagerCallback mCallbackToApp; private final String mAppName; private final Context mContext; private int mSubscriptionId = INVALID_SUBSCRIPTION_ID; /** @hide */ private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback listener, String streamingAppName, int subscriptionId) { mContext = context; mAppName = streamingAppName; mCallbackToApp = listener; mSubscriptionId = subscriptionId; } /** * Create a new MbmsStreamingManager using the given subscription ID. * * Note that this call will bind a remote service. You may not call this method on your app's * main thread. This may throw an {@link MbmsException}, indicating errors that may happen * during the initialization or binding process. * * @param context The {@link Context} to use. * @param listener A callback object on which you wish to receive results of asynchronous * operations. * @param streamingAppName The name of the streaming app, as specified by the carrier. * @param subscriptionId The subscription ID to use. */ public static MbmsStreamingManager create(Context context, MbmsStreamingManagerCallback listener, String streamingAppName, int subscriptionId) throws MbmsException { MbmsStreamingManager manager = new MbmsStreamingManager(context, listener, streamingAppName, subscriptionId); manager.bindAndInitialize(); return manager; } /** * Create a new MbmsStreamingManager using the system default data subscription ID. * See {@link #create(Context, MbmsStreamingManagerCallback, String, int)}. */ public static MbmsStreamingManager create(Context context, MbmsStreamingManagerCallback listener, String streamingAppName) throws MbmsException { int subId = SubscriptionManager.getDefaultSubscriptionId(); MbmsStreamingManager manager = new MbmsStreamingManager(context, listener, streamingAppName, subId); manager.bindAndInitialize(); return manager; } /** * Terminates this instance, ending calls to the registered listener. Also terminates * any streaming services spawned from this instance. */ public synchronized void dispose() { if (mService == null) { // Ignore and return, assume already disposed. return; } try { mService.dispose(mAppName, mSubscriptionId); } catch (RemoteException e) { // Ignore for now } mService = null; } /** * An inspection API to retrieve the list of streaming media currently be advertised. * The results are returned asynchronously through the previously registered callback. * serviceClasses lets the app filter on types of programming and is opaque data between * the app and the carrier. * * Multiple calls replace the list of serviceClasses of interest. * * This may throw an {@link MbmsException} containing one of the following errors: * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} * {@link MbmsException#ERROR_UNKNOWN_REMOTE_EXCEPTION} * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED} * {@link MbmsException#ERROR_SERVICE_LOST} * * Asynchronous error codes via the {@link MbmsStreamingManagerCallback#error(int, String)} * callback can include any of the errors except: * {@link MbmsException#ERROR_UNABLE_TO_START_SERVICE} * {@link MbmsException#ERROR_END_OF_SESSION} */ public void getStreamingServices(List<String> classList) throws MbmsException { if (mService == null) { throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); } try { int returnCode = mService.getStreamingServices(mAppName, mSubscriptionId, classList); if (returnCode != MbmsException.SUCCESS) { throw new MbmsException(returnCode); } } catch (DeadObjectException e) { Log.w(LOG_TAG, "Remote process died"); mService = null; throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); } catch (RemoteException e) { throw new MbmsException(MbmsException.ERROR_UNKNOWN_REMOTE_EXCEPTION); } } /** * Starts streaming a requested service, reporting status to the indicated listener. * Returns an object used to control that stream. The stream may not be ready for consumption * immediately upon return from this method -- wait until the streaming state has been * reported via {@link android.telephony.mbms.StreamingServiceCallback#streamStateChanged(int)}. * * May throw an {@link MbmsException} containing any of the following error codes: * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} * {@link MbmsException#ERROR_UNKNOWN_REMOTE_EXCEPTION} * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED} * {@link MbmsException#ERROR_SERVICE_LOST} * * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException} * * Asynchronous errors through the listener include any of the errors */ public StreamingService startStreaming(StreamingServiceInfo serviceInfo, StreamingServiceCallback listener) throws MbmsException { if (mService == null) { throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); } try { int returnCode = mService.startStreaming( mAppName, mSubscriptionId, serviceInfo.getServiceId(), listener); if (returnCode != MbmsException.SUCCESS) { throw new MbmsException(returnCode); } } catch (DeadObjectException e) { Log.w(LOG_TAG, "Remote process died"); mService = null; throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); } catch (RemoteException e) { throw new MbmsException(MbmsException.ERROR_UNKNOWN_REMOTE_EXCEPTION); } return new StreamingService( mAppName, mSubscriptionId, mService, serviceInfo, listener); } private void bindAndInitialize() throws MbmsException { // Query for the proper service PackageManager packageManager = mContext.getPackageManager(); Intent queryIntent = new Intent(); queryIntent.setAction(MBMS_STREAMING_SERVICE_ACTION); List<ResolveInfo> streamingServices = packageManager.queryIntentServices(queryIntent, PackageManager.MATCH_SYSTEM_ONLY); if (streamingServices == null || streamingServices.size() == 0) { throw new MbmsException( MbmsException.ERROR_NO_SERVICE_INSTALLED); } if (streamingServices.size() > 1) { throw new MbmsException( MbmsException.ERROR_MULTIPLE_SERVICES_INSTALLED); } // Kick off the binding, and synchronously wait until binding is complete final CountDownLatch latch = new CountDownLatch(1); ServiceListener bindListener = new ServiceListener() { @Override public void onServiceConnected() { latch.countDown(); } @Override public void onServiceDisconnected() { } }; synchronized (this) { mServiceListeners.add(bindListener); } Intent bindIntent = new Intent(); bindIntent.setComponent(streamingServices.get(0).getComponentInfo().getComponentName()); mContext.bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE); waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS); // Remove the listener and call the initialization method through the interface. synchronized (this) { mServiceListeners.remove(bindListener); if (mService == null) { throw new MbmsException(MbmsException.ERROR_BIND_TIMEOUT_OR_FAILURE); } try { int returnCode = mService.initialize(mCallbackToApp, mAppName, mSubscriptionId); if (returnCode != MbmsException.SUCCESS) { throw new MbmsException(returnCode); } } catch (RemoteException e) { mService = null; Log.e(LOG_TAG, "Service died before initialization"); if (e instanceof DeadObjectException) { throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); } else { throw new MbmsException(MbmsException.ERROR_UNKNOWN_REMOTE_EXCEPTION); } } } } private static void waitOnLatchWithTimeout(CountDownLatch l, long timeoutMs) { long endTime = System.currentTimeMillis() + timeoutMs; while (System.currentTimeMillis() < endTime) { try { l.await(timeoutMs, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // keep waiting } if (l.getCount() <= 0) { return; } } } }