• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
MbmsStreamingSession(Context context, Executor executor, int subscriptionId, MbmsStreamingSessionCallback callback)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      */
create(@onNull Context context, @NonNull Executor executor, int subscriptionId, final @NonNull MbmsStreamingSessionCallback callback)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      */
create(@onNull Context context, @NonNull Executor executor, @NonNull MbmsStreamingSessionCallback callback)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      */
close()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      */
requestUpdateStreamingServices(List<String> serviceClassList)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      */
startStreaming(StreamingServiceInfo serviceInfo, @NonNull Executor executor, StreamingServiceCallback callback)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 */
onStreamingServiceStopped(StreamingService service)284     public void onStreamingServiceStopped(StreamingService service) {
285         mKnownActiveStreamingServices.remove(service);
286     }
287 
bindAndInitialize()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