Home | History | Annotate | Download | only in telephony
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.telephony;
     18 
     19 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     20 
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.annotation.SdkConstant;
     24 import android.annotation.SystemApi;
     25 import android.annotation.TestApi;
     26 import android.content.ComponentName;
     27 import android.content.Context;
     28 import android.content.ServiceConnection;
     29 import android.os.IBinder;
     30 import android.os.RemoteException;
     31 import android.telephony.mbms.InternalStreamingServiceCallback;
     32 import android.telephony.mbms.InternalStreamingSessionCallback;
     33 import android.telephony.mbms.MbmsErrors;
     34 import android.telephony.mbms.MbmsStreamingSessionCallback;
     35 import android.telephony.mbms.MbmsUtils;
     36 import android.telephony.mbms.StreamingService;
     37 import android.telephony.mbms.StreamingServiceCallback;
     38 import android.telephony.mbms.StreamingServiceInfo;
     39 import android.telephony.mbms.vendor.IMbmsStreamingService;
     40 import android.util.ArraySet;
     41 import android.util.Log;
     42 
     43 import java.util.List;
     44 import java.util.Set;
     45 import java.util.concurrent.Executor;
     46 import java.util.concurrent.atomic.AtomicBoolean;
     47 import java.util.concurrent.atomic.AtomicReference;
     48 
     49 /**
     50  * This class provides functionality for streaming media over MBMS.
     51  */
     52 public class MbmsStreamingSession implements AutoCloseable {
     53     private static final String LOG_TAG = "MbmsStreamingSession";
     54 
     55     /**
     56      * Service action which must be handled by the middleware implementing the MBMS streaming
     57      * interface.
     58      * @hide
     59      */
     60     @SystemApi
     61     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     62     public static final String MBMS_STREAMING_SERVICE_ACTION =
     63             "android.telephony.action.EmbmsStreaming";
     64 
     65     /**
     66      * Metadata key that specifies the component name of the service to bind to for file-download.
     67      * @hide
     68      */
     69     @TestApi
     70     public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA =
     71             "mbms-streaming-service-override";
     72 
     73     private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
     74 
     75     private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
     76     private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
     77         @Override
     78         public void binderDied() {
     79             sIsInitialized.set(false);
     80             sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
     81         }
     82     };
     83 
     84     private InternalStreamingSessionCallback mInternalCallback;
     85     private Set<StreamingService> mKnownActiveStreamingServices = new ArraySet<>();
     86 
     87     private final Context mContext;
     88     private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
     89 
     90     /** @hide */
     91     private MbmsStreamingSession(Context context, Executor executor, int subscriptionId,
     92             MbmsStreamingSessionCallback callback) {
     93         mContext = context;
     94         mSubscriptionId = subscriptionId;
     95         mInternalCallback = new InternalStreamingSessionCallback(callback, executor);
     96     }
     97 
     98     /**
     99      * Create a new {@link MbmsStreamingSession} using the given subscription ID.
    100      *
    101      * Note that this call will bind a remote service. You may not call this method on your app's
    102      * main thread.
    103      *
    104      * You may only have one instance of {@link MbmsStreamingSession} per UID. If you call this
    105      * method while there is an active instance of {@link MbmsStreamingSession} in your process
    106      * (in other words, one that has not had {@link #close()} called on it), this method will
    107      * throw an {@link IllegalStateException}. If you call this method in a different process
    108      * running under the same UID, an error will be indicated via
    109      * {@link MbmsStreamingSessionCallback#onError(int, String)}.
    110      *
    111      * Note that initialization may fail asynchronously. If you wish to try again after you
    112      * receive such an asynchronous error, you must call {@link #close()} on the instance of
    113      * {@link MbmsStreamingSession} that you received before calling this method again.
    114      *
    115      * @param context The {@link Context} to use.
    116      * @param executor The executor on which you wish to execute callbacks.
    117      * @param subscriptionId The subscription ID to use.
    118      * @param callback A callback object on which you wish to receive results of asynchronous
    119      *                 operations.
    120      * @return An instance of {@link MbmsStreamingSession}, or null if an error occurred.
    121      */
    122     public static @Nullable MbmsStreamingSession create(@NonNull Context context,
    123             @NonNull Executor executor, int subscriptionId,
    124             final @NonNull MbmsStreamingSessionCallback callback) {
    125         if (!sIsInitialized.compareAndSet(false, true)) {
    126             throw new IllegalStateException("Cannot create two instances of MbmsStreamingSession");
    127         }
    128         MbmsStreamingSession session = new MbmsStreamingSession(context, executor,
    129                 subscriptionId, callback);
    130 
    131         final int result = session.bindAndInitialize();
    132         if (result != MbmsErrors.SUCCESS) {
    133             sIsInitialized.set(false);
    134             executor.execute(new Runnable() {
    135                 @Override
    136                 public void run() {
    137                     callback.onError(result, null);
    138                 }
    139             });
    140             return null;
    141         }
    142         return session;
    143     }
    144 
    145     /**
    146      * Create a new {@link MbmsStreamingSession} using the system default data subscription ID.
    147      * See {@link #create(Context, Executor, int, MbmsStreamingSessionCallback)}.
    148      */
    149     public static MbmsStreamingSession create(@NonNull Context context,
    150             @NonNull Executor executor, @NonNull MbmsStreamingSessionCallback callback) {
    151         return create(context, executor, SubscriptionManager.getDefaultSubscriptionId(), callback);
    152     }
    153 
    154     /**
    155      * Terminates this instance. Also terminates
    156      * any streaming services spawned from this instance as if
    157      * {@link StreamingService#close()} had been called on them. After this method returns,
    158      * no further callbacks originating from the middleware will be enqueued on the provided
    159      * instance of {@link MbmsStreamingSessionCallback}, but callbacks that have already been
    160      * enqueued will still be delivered.
    161      *
    162      * It is safe to call {@link #create(Context, Executor, int, MbmsStreamingSessionCallback)} to
    163      * obtain another instance of {@link MbmsStreamingSession} immediately after this method
    164      * returns.
    165      *
    166      * May throw an {@link IllegalStateException}
    167      */
    168     public void close() {
    169         try {
    170             IMbmsStreamingService streamingService = mService.get();
    171             if (streamingService == null) {
    172                 // Ignore and return, assume already disposed.
    173                 return;
    174             }
    175             streamingService.dispose(mSubscriptionId);
    176             for (StreamingService s : mKnownActiveStreamingServices) {
    177                 s.getCallback().stop();
    178             }
    179             mKnownActiveStreamingServices.clear();
    180         } catch (RemoteException e) {
    181             // Ignore for now
    182         } finally {
    183             mService.set(null);
    184             sIsInitialized.set(false);
    185             mInternalCallback.stop();
    186         }
    187     }
    188 
    189     /**
    190      * An inspection API to retrieve the list of streaming media currently be advertised.
    191      * The results are returned asynchronously via
    192      * {@link MbmsStreamingSessionCallback#onStreamingServicesUpdated(List)} on the callback
    193      * provided upon creation.
    194      *
    195      * Multiple calls replace the list of service classes of interest.
    196      *
    197      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
    198      *
    199      * @param serviceClassList A list of streaming service classes that the app would like updates
    200      *                         on. The exact names of these classes should be negotiated with the
    201      *                         wireless carrier separately.
    202      */
    203     public void requestUpdateStreamingServices(List<String> serviceClassList) {
    204         IMbmsStreamingService streamingService = mService.get();
    205         if (streamingService == null) {
    206             throw new IllegalStateException("Middleware not yet bound");
    207         }
    208         try {
    209             int returnCode = streamingService.requestUpdateStreamingServices(
    210                     mSubscriptionId, serviceClassList);
    211             if (returnCode == MbmsErrors.UNKNOWN) {
    212                 // Unbind and throw an obvious error
    213                 close();
    214                 throw new IllegalStateException("Middleware must not return an unknown error code");
    215             }
    216             if (returnCode != MbmsErrors.SUCCESS) {
    217                 sendErrorToApp(returnCode, null);
    218             }
    219         } catch (RemoteException e) {
    220             Log.w(LOG_TAG, "Remote process died");
    221             mService.set(null);
    222             sIsInitialized.set(false);
    223             sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
    224         }
    225     }
    226 
    227     /**
    228      * Starts streaming a requested service, reporting status to the indicated callback.
    229      * Returns an object used to control that stream. The stream may not be ready for consumption
    230      * immediately upon return from this method -- wait until the streaming state has been
    231      * reported via
    232      * {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)}
    233      *
    234      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
    235      *
    236      * Asynchronous errors through the callback include any of the errors in
    237      * {@link MbmsErrors.GeneralErrors} or
    238      * {@link MbmsErrors.StreamingErrors}.
    239      *
    240      * @param serviceInfo The information about the service to stream.
    241      * @param executor The executor on which you wish to execute callbacks for this stream.
    242      * @param callback A callback that'll be called when something about the stream changes.
    243      * @return An instance of {@link StreamingService} through which the stream can be controlled.
    244      *         May be {@code null} if an error occurred.
    245      */
    246     public @Nullable StreamingService startStreaming(StreamingServiceInfo serviceInfo,
    247             @NonNull Executor executor, StreamingServiceCallback callback) {
    248         IMbmsStreamingService streamingService = mService.get();
    249         if (streamingService == null) {
    250             throw new IllegalStateException("Middleware not yet bound");
    251         }
    252 
    253         InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback(
    254                 callback, executor);
    255 
    256         StreamingService serviceForApp = new StreamingService(
    257                 mSubscriptionId, streamingService, this, serviceInfo, serviceCallback);
    258         mKnownActiveStreamingServices.add(serviceForApp);
    259 
    260         try {
    261             int returnCode = streamingService.startStreaming(
    262                     mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
    263             if (returnCode == MbmsErrors.UNKNOWN) {
    264                 // Unbind and throw an obvious error
    265                 close();
    266                 throw new IllegalStateException("Middleware must not return an unknown error code");
    267             }
    268             if (returnCode != MbmsErrors.SUCCESS) {
    269                 sendErrorToApp(returnCode, null);
    270                 return null;
    271             }
    272         } catch (RemoteException e) {
    273             Log.w(LOG_TAG, "Remote process died");
    274             mService.set(null);
    275             sIsInitialized.set(false);
    276             sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
    277             return null;
    278         }
    279 
    280         return serviceForApp;
    281     }
    282 
    283     /** @hide */
    284     public void onStreamingServiceStopped(StreamingService service) {
    285         mKnownActiveStreamingServices.remove(service);
    286     }
    287 
    288     private int bindAndInitialize() {
    289         return MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
    290                 new ServiceConnection() {
    291                     @Override
    292                     public void onServiceConnected(ComponentName name, IBinder service) {
    293                         IMbmsStreamingService streamingService =
    294                                 IMbmsStreamingService.Stub.asInterface(service);
    295                         int result;
    296                         try {
    297                             result = streamingService.initialize(mInternalCallback,
    298                                     mSubscriptionId);
    299                         } catch (RemoteException e) {
    300                             Log.e(LOG_TAG, "Service died before initialization");
    301                             sendErrorToApp(
    302                                     MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
    303                                     e.toString());
    304                             sIsInitialized.set(false);
    305                             return;
    306                         } catch (RuntimeException e) {
    307                             Log.e(LOG_TAG, "Runtime exception during initialization");
    308                             sendErrorToApp(
    309                                     MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
    310                                     e.toString());
    311                             sIsInitialized.set(false);
    312                             return;
    313                         }
    314                         if (result == MbmsErrors.UNKNOWN) {
    315                             // Unbind and throw an obvious error
    316                             close();
    317                             throw new IllegalStateException("Middleware must not return"
    318                                     + " an unknown error code");
    319                         }
    320                         if (result != MbmsErrors.SUCCESS) {
    321                             sendErrorToApp(result, "Error returned during initialization");
    322                             sIsInitialized.set(false);
    323                             return;
    324                         }
    325                         try {
    326                             streamingService.asBinder().linkToDeath(mDeathRecipient, 0);
    327                         } catch (RemoteException e) {
    328                             sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
    329                                     "Middleware lost during initialization");
    330                             sIsInitialized.set(false);
    331                             return;
    332                         }
    333                         mService.set(streamingService);
    334                     }
    335 
    336                     @Override
    337                     public void onServiceDisconnected(ComponentName name) {
    338                         sIsInitialized.set(false);
    339                         mService.set(null);
    340                     }
    341                 });
    342     }
    343 
    344     private void sendErrorToApp(int errorCode, String message) {
    345         try {
    346             mInternalCallback.onError(errorCode, message);
    347         } catch (RemoteException e) {
    348             // Ignore, should not happen locally.
    349         }
    350     }
    351 }
    352