1 /*
2  * Copyright (C) 2017 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.mbms.vendor;
18 
19 import android.annotation.NonNull;
20 import android.annotation.SystemApi;
21 import android.content.Intent;
22 import android.os.Binder;
23 import android.os.IBinder;
24 import android.os.RemoteException;
25 import android.telephony.MbmsDownloadSession;
26 import android.telephony.mbms.DownloadProgressListener;
27 import android.telephony.mbms.DownloadRequest;
28 import android.telephony.mbms.DownloadStatusListener;
29 import android.telephony.mbms.FileInfo;
30 import android.telephony.mbms.FileServiceInfo;
31 import android.telephony.mbms.IDownloadProgressListener;
32 import android.telephony.mbms.IDownloadStatusListener;
33 import android.telephony.mbms.IMbmsDownloadSessionCallback;
34 import android.telephony.mbms.MbmsDownloadSessionCallback;
35 import android.telephony.mbms.MbmsErrors;
36 
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Map;
40 
41 /**
42  * Base class for MbmsDownloadService. The middleware should return an instance of this object from
43  * its {@link android.app.Service#onBind(Intent)} method.
44  * @hide
45  */
46 @SystemApi
47 public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
48     private final Map<IBinder, DownloadStatusListener> mDownloadStatusListenerBinderMap =
49             new HashMap<>();
50     private final Map<IBinder, DownloadProgressListener> mDownloadProgressListenerBinderMap =
51             new HashMap<>();
52     private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
53 
54     private abstract static class VendorDownloadStatusListener extends DownloadStatusListener {
55         private final IDownloadStatusListener mListener;
VendorDownloadStatusListener(IDownloadStatusListener listener)56         public VendorDownloadStatusListener(IDownloadStatusListener listener) {
57             mListener = listener;
58         }
59 
60         @Override
onStatusUpdated(DownloadRequest request, FileInfo fileInfo, @MbmsDownloadSession.DownloadStatus int state)61         public void onStatusUpdated(DownloadRequest request, FileInfo fileInfo,
62                 @MbmsDownloadSession.DownloadStatus int state) {
63             try {
64                 mListener.onStatusUpdated(request, fileInfo, state);
65             } catch (RemoteException e) {
66                 onRemoteException(e);
67             }
68         }
69 
onRemoteException(RemoteException e)70         protected abstract void onRemoteException(RemoteException e);
71     }
72 
73     private abstract static class VendorDownloadProgressListener extends DownloadProgressListener {
74         private final IDownloadProgressListener mListener;
75 
VendorDownloadProgressListener(IDownloadProgressListener listener)76         public VendorDownloadProgressListener(IDownloadProgressListener listener) {
77             mListener = listener;
78         }
79 
80         @Override
onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int fullDecodedSize)81         public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
82                 int currentDownloadSize, int fullDownloadSize, int currentDecodedSize,
83                 int fullDecodedSize) {
84             try {
85                 mListener.onProgressUpdated(request, fileInfo, currentDownloadSize,
86                         fullDownloadSize, currentDecodedSize, fullDecodedSize);
87             } catch (RemoteException e) {
88                 onRemoteException(e);
89             }
90         }
91 
onRemoteException(RemoteException e)92         protected abstract void onRemoteException(RemoteException e);
93     }
94 
95     /**
96      * Initialize the download service for this app and subId, registering the listener.
97      *
98      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which
99      * will be intercepted and passed to the app as
100      * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
101      *
102      * May return any value from {@link MbmsErrors.InitializationErrors}
103      * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
104      * {@link IMbmsDownloadSessionCallback#onError(int, String)}.
105      *
106      * @param callback The callback to use to communicate with the app.
107      * @param subscriptionId The subscription ID to use.
108      */
initialize(int subscriptionId, MbmsDownloadSessionCallback callback)109     public int initialize(int subscriptionId, MbmsDownloadSessionCallback callback)
110             throws RemoteException {
111         return 0;
112     }
113 
114     /**
115      * Actual AIDL implementation -- hides the callback AIDL from the API.
116      * @hide
117      */
118     @Override
initialize(final int subscriptionId, final IMbmsDownloadSessionCallback callback)119     public final int initialize(final int subscriptionId,
120             final IMbmsDownloadSessionCallback callback) throws RemoteException {
121         if (callback == null) {
122             throw new NullPointerException("Callback must not be null");
123         }
124 
125         final int uid = Binder.getCallingUid();
126 
127         int result = initialize(subscriptionId, new MbmsDownloadSessionCallback() {
128             @Override
129             public void onError(int errorCode, String message) {
130                 try {
131                     if (errorCode == MbmsErrors.UNKNOWN) {
132                         throw new IllegalArgumentException(
133                                 "Middleware cannot send an unknown error.");
134                     }
135                     callback.onError(errorCode, message);
136                 } catch (RemoteException e) {
137                     onAppCallbackDied(uid, subscriptionId);
138                 }
139             }
140 
141             @Override
142             public void onFileServicesUpdated(List<FileServiceInfo> services) {
143                 try {
144                     callback.onFileServicesUpdated(services);
145                 } catch (RemoteException e) {
146                     onAppCallbackDied(uid, subscriptionId);
147                 }
148             }
149 
150             @Override
151             public void onMiddlewareReady() {
152                 try {
153                     callback.onMiddlewareReady();
154                 } catch (RemoteException e) {
155                     onAppCallbackDied(uid, subscriptionId);
156                 }
157             }
158         });
159 
160         if (result == MbmsErrors.SUCCESS) {
161             callback.asBinder().linkToDeath(new DeathRecipient() {
162                 @Override
163                 public void binderDied() {
164                     onAppCallbackDied(uid, subscriptionId);
165                 }
166             }, 0);
167         }
168 
169         return result;
170     }
171 
172     /**
173      * Registers serviceClasses of interest with the appName/subId key.
174      * Starts async fetching data on streaming services of matching classes to be reported
175      * later via {@link IMbmsDownloadSessionCallback#onFileServicesUpdated(List)}
176      *
177      * Note that subsequent calls with the same uid and subId will replace
178      * the service class list.
179      *
180      * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
181      *
182      * @param subscriptionId The subscription id to use.
183      * @param serviceClasses The service classes that the app wishes to get info on. The strings
184      *                       may contain arbitrary data as negotiated between the app and the
185      *                       carrier.
186      * @return One of {@link MbmsErrors#SUCCESS} or
187      *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
188      */
189     @Override
requestUpdateFileServices(int subscriptionId, List<String> serviceClasses)190     public int requestUpdateFileServices(int subscriptionId, List<String> serviceClasses)
191             throws RemoteException {
192         return 0;
193     }
194 
195     /**
196      * Sets the temp file root directory for this app/subscriptionId combination. The middleware
197      * should persist {@code rootDirectoryPath} and send it back when sending intents to the
198      * app's {@link android.telephony.mbms.MbmsDownloadReceiver}.
199      *
200      * If the calling app (as identified by the calling UID) currently has any pending download
201      * requests that have not been canceled, the middleware must return
202      * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
203      *
204      * @param subscriptionId The subscription id the download is operating under.
205      * @param rootDirectoryPath The path to the app's temp file root directory.
206      * @return {@link MbmsErrors#SUCCESS},
207      *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
208      *         {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
209      */
210     @Override
setTempFileRootDirectory(int subscriptionId, String rootDirectoryPath)211     public int setTempFileRootDirectory(int subscriptionId,
212             String rootDirectoryPath) throws RemoteException {
213         return 0;
214     }
215 
216     /**
217      * Called when the client application wishes to receive file information according to a
218      * service announcement descriptor received from a group call server.
219      *
220      * The service announcement descriptor is in the format of a multipart MIME file with XML parts,
221      * though no validation is performed on the contents of the {@code contents} argument --
222      * implementing middleware applications should perform their own validation and return
223      * {@link MbmsErrors.DownloadErrors#ERROR_MALFORMED_SERVICE_ANNOUNCEMENT} if the descriptor is
224      * malformed.
225      *
226      * @param subscriptionId The subscription id the service announcement applies to.
227      * @param contents The contents of the service announcement descriptor.
228      * @return {@link MbmsErrors#SUCCESS}, or
229      *         {@link MbmsErrors.DownloadErrors#ERROR_MALFORMED_SERVICE_ANNOUNCEMENT}
230      */
231     // TODO: are there any public specifications of what the file format is that I can link to?
232     @Override
addServiceAnnouncement( int subscriptionId, @NonNull byte[] contents)233     public @MbmsErrors.MbmsError int addServiceAnnouncement(
234             int subscriptionId, @NonNull byte[] contents) {
235         throw new UnsupportedOperationException("addServiceAnnouncement not supported by"
236                 + " this middleware.");
237     }
238 
239     /**
240      * Issues a request to download a set of files.
241      *
242      * The middleware should expect that {@link #setTempFileRootDirectory(int, String)} has been
243      * called for this app between when the app was installed and when this method is called. If
244      * this is not the case, an {@link IllegalStateException} may be thrown.
245      *
246      * @param downloadRequest An object describing the set of files to be downloaded.
247      * @return Any error from {@link MbmsErrors.GeneralErrors}
248      *         or {@link MbmsErrors#SUCCESS}
249      */
250     @Override
download(DownloadRequest downloadRequest)251     public int download(DownloadRequest downloadRequest) throws RemoteException {
252         return 0;
253     }
254 
255     /**
256      * Registers a download status listener for the provided {@link DownloadRequest}.
257      *
258      * This method is called by the app when it wants to request updates on the status of
259      * the download.
260      *
261      * If the middleware is not aware of a download having been requested with the provided
262      * {@link DownloadRequest} in the past,
263      * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
264      * must be returned.
265      *
266      * @param downloadRequest The {@link DownloadRequest} that was used to initiate the download
267      *                        for which progress updates are being requested.
268      * @param listener The listener object to use.
269      */
addStatusListener(DownloadRequest downloadRequest, DownloadStatusListener listener)270     public int addStatusListener(DownloadRequest downloadRequest,
271             DownloadStatusListener listener) throws RemoteException {
272         return 0;
273     }
274 
275     /**
276      * Actual AIDL implementation -- hides the listener AIDL from the API.
277      * @hide
278      */
279     @Override
addStatusListener(final DownloadRequest downloadRequest, final IDownloadStatusListener listener)280     public final int addStatusListener(final DownloadRequest downloadRequest,
281             final IDownloadStatusListener listener) throws RemoteException {
282         final int uid = Binder.getCallingUid();
283         if (downloadRequest == null) {
284             throw new NullPointerException("Download request must not be null");
285         }
286         if (listener == null) {
287             throw new NullPointerException("Callback must not be null");
288         }
289 
290         DownloadStatusListener exposedCallback = new VendorDownloadStatusListener(listener) {
291             @Override
292             protected void onRemoteException(RemoteException e) {
293                 onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
294             }
295         };
296 
297         int result = addStatusListener(downloadRequest, exposedCallback);
298 
299         if (result == MbmsErrors.SUCCESS) {
300             DeathRecipient deathRecipient = new DeathRecipient() {
301                 @Override
302                 public void binderDied() {
303                     onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
304                     mDownloadStatusListenerBinderMap.remove(listener.asBinder());
305                     mDownloadCallbackDeathRecipients.remove(listener.asBinder());
306                 }
307             };
308             mDownloadCallbackDeathRecipients.put(listener.asBinder(), deathRecipient);
309             listener.asBinder().linkToDeath(deathRecipient, 0);
310             mDownloadStatusListenerBinderMap.put(listener.asBinder(), exposedCallback);
311         }
312 
313         return result;
314     }
315 
316     /**
317      * Un-registers a download status listener for the provided {@link DownloadRequest}.
318      *
319      * This method is called by the app when it no longer wants to request status updates on the
320      * download.
321      *
322      * If the middleware is not aware of a download having been requested with the provided
323      * {@link DownloadRequest} in the past,
324      * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
325      * must be returned.
326      *
327      * @param downloadRequest The {@link DownloadRequest} that was used to register the callback
328      * @param listener The callback object that
329      *                 {@link #addStatusListener(DownloadRequest, DownloadStatusListener)}
330      *                 was called with.
331      */
removeStatusListener(DownloadRequest downloadRequest, DownloadStatusListener listener)332     public int removeStatusListener(DownloadRequest downloadRequest,
333             DownloadStatusListener listener) throws RemoteException {
334         return 0;
335     }
336 
337     /**
338      * Actual AIDL implementation -- hides the listener AIDL from the API.
339      * @hide
340      */
removeStatusListener( final DownloadRequest downloadRequest, final IDownloadStatusListener listener)341     public final int removeStatusListener(
342             final DownloadRequest downloadRequest, final IDownloadStatusListener listener)
343             throws RemoteException {
344         if (downloadRequest == null) {
345             throw new NullPointerException("Download request must not be null");
346         }
347         if (listener == null) {
348             throw new NullPointerException("Callback must not be null");
349         }
350 
351         DeathRecipient deathRecipient =
352                 mDownloadCallbackDeathRecipients.remove(listener.asBinder());
353         if (deathRecipient == null) {
354             throw new IllegalArgumentException("Unknown listener");
355         }
356 
357         listener.asBinder().unlinkToDeath(deathRecipient, 0);
358 
359         DownloadStatusListener exposedCallback =
360                 mDownloadStatusListenerBinderMap.remove(listener.asBinder());
361         if (exposedCallback == null) {
362             throw new IllegalArgumentException("Unknown listener");
363         }
364 
365         return removeStatusListener(downloadRequest, exposedCallback);
366     }
367 
368     /**
369      * Registers a download progress listener for the provided {@link DownloadRequest}.
370      *
371      * This method is called by the app when it wants to request updates on the progress of
372      * the download.
373      *
374      * If the middleware is not aware of a download having been requested with the provided
375      * {@link DownloadRequest} in the past,
376      * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
377      * must be returned.
378      *
379      * @param downloadRequest The {@link DownloadRequest} that was used to initiate the download
380      *                        for which progress updates are being requested.
381      * @param listener The listener object to use.
382      */
addProgressListener(DownloadRequest downloadRequest, DownloadProgressListener listener)383     public int addProgressListener(DownloadRequest downloadRequest,
384             DownloadProgressListener listener) throws RemoteException {
385         return 0;
386     }
387 
388     /**
389      * Actual AIDL implementation -- hides the listener AIDL from the API.
390      * @hide
391      */
392     @Override
addProgressListener(final DownloadRequest downloadRequest, final IDownloadProgressListener listener)393     public final int addProgressListener(final DownloadRequest downloadRequest,
394             final IDownloadProgressListener listener) throws RemoteException {
395         final int uid = Binder.getCallingUid();
396         if (downloadRequest == null) {
397             throw new NullPointerException("Download request must not be null");
398         }
399         if (listener == null) {
400             throw new NullPointerException("Callback must not be null");
401         }
402 
403         DownloadProgressListener exposedCallback = new VendorDownloadProgressListener(listener) {
404             @Override
405             protected void onRemoteException(RemoteException e) {
406                 onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
407             }
408         };
409 
410         int result = addProgressListener(downloadRequest, exposedCallback);
411 
412         if (result == MbmsErrors.SUCCESS) {
413             DeathRecipient deathRecipient = new DeathRecipient() {
414                 @Override
415                 public void binderDied() {
416                     onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
417                     mDownloadProgressListenerBinderMap.remove(listener.asBinder());
418                     mDownloadCallbackDeathRecipients.remove(listener.asBinder());
419                 }
420             };
421             mDownloadCallbackDeathRecipients.put(listener.asBinder(), deathRecipient);
422             listener.asBinder().linkToDeath(deathRecipient, 0);
423             mDownloadProgressListenerBinderMap.put(listener.asBinder(), exposedCallback);
424         }
425 
426         return result;
427     }
428 
429     /**
430      * Un-registers a download progress listener for the provided {@link DownloadRequest}.
431      *
432      * This method is called by the app when it no longer wants to request progress updates on the
433      * download.
434      *
435      * If the middleware is not aware of a download having been requested with the provided
436      * {@link DownloadRequest} in the past,
437      * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
438      * must be returned.
439      *
440      * @param downloadRequest The {@link DownloadRequest} that was used to register the callback
441      * @param listener The callback object that
442      *                 {@link #addProgressListener(DownloadRequest, DownloadProgressListener)}
443      *                 was called with.
444      */
removeProgressListener(DownloadRequest downloadRequest, DownloadProgressListener listener)445     public int removeProgressListener(DownloadRequest downloadRequest,
446             DownloadProgressListener listener) throws RemoteException {
447         return 0;
448     }
449 
450     /**
451      * Actual AIDL implementation -- hides the listener AIDL from the API.
452      * @hide
453      */
removeProgressListener( final DownloadRequest downloadRequest, final IDownloadProgressListener listener)454     public final int removeProgressListener(
455             final DownloadRequest downloadRequest, final IDownloadProgressListener listener)
456             throws RemoteException {
457         if (downloadRequest == null) {
458             throw new NullPointerException("Download request must not be null");
459         }
460         if (listener == null) {
461             throw new NullPointerException("Callback must not be null");
462         }
463 
464         DeathRecipient deathRecipient =
465                 mDownloadCallbackDeathRecipients.remove(listener.asBinder());
466         if (deathRecipient == null) {
467             throw new IllegalArgumentException("Unknown listener");
468         }
469 
470         listener.asBinder().unlinkToDeath(deathRecipient, 0);
471 
472         DownloadProgressListener exposedCallback =
473                 mDownloadProgressListenerBinderMap.remove(listener.asBinder());
474         if (exposedCallback == null) {
475             throw new IllegalArgumentException("Unknown listener");
476         }
477 
478         return removeProgressListener(downloadRequest, exposedCallback);
479     }
480 
481     /**
482      * Returns a list of pending {@link DownloadRequest}s that originated from the calling
483      * application, identified by its uid. A pending request is one that was issued via
484      * {@link #download(DownloadRequest)} but not cancelled through
485      * {@link #cancelDownload(DownloadRequest)}.
486      * The middleware must return a non-null result synchronously or throw an exception
487      * inheriting from {@link RuntimeException}.
488      * @return A list, possibly empty, of {@link DownloadRequest}s
489      */
490     @Override
listPendingDownloads(int subscriptionId)491     public @NonNull List<DownloadRequest> listPendingDownloads(int subscriptionId)
492             throws RemoteException {
493         return null;
494     }
495 
496     /**
497      * Issues a request to cancel the specified download request.
498      *
499      * If the middleware is unable to cancel the request for whatever reason, it should return
500      * synchronously with an error. If this method returns {@link MbmsErrors#SUCCESS}, the app
501      * will no longer be expecting any more file-completed intents from the middleware for this
502      * {@link DownloadRequest}.
503      * @param downloadRequest The request to cancel
504      * @return {@link MbmsErrors#SUCCESS},
505      *         {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
506      *         {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
507      */
508     @Override
cancelDownload(DownloadRequest downloadRequest)509     public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException {
510         return 0;
511     }
512 
513     /**
514      * Requests information about the state of a file pending download.
515      *
516      * If the middleware has no records of the
517      * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
518      * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_FILE_INFO} must be returned.
519      *
520      * @param downloadRequest The download request to query.
521      * @param fileInfo The particular file within the request to get information on.
522      * @return {@link MbmsErrors#SUCCESS} if the request was successful, an error code otherwise.
523      */
524     @Override
requestDownloadState(DownloadRequest downloadRequest, FileInfo fileInfo)525     public int requestDownloadState(DownloadRequest downloadRequest, FileInfo fileInfo)
526             throws RemoteException {
527         return 0;
528     }
529 
530     /**
531      * Resets the middleware's knowledge of previously-downloaded files in this download request.
532      *
533      * When this method is called, the middleware must attempt to re-download all the files
534      * specified by the {@link DownloadRequest}, even if the files have not changed on the server.
535      * In addition, current in-progress downloads must not be interrupted.
536      *
537      * If the middleware is not aware of the specified download request, return
538      * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
539      *
540      * @param downloadRequest The request to re-download files for.
541      */
542     @Override
resetDownloadKnowledge(DownloadRequest downloadRequest)543     public int resetDownloadKnowledge(DownloadRequest downloadRequest)
544             throws RemoteException {
545         return 0;
546     }
547 
548     /**
549      * Signals that the app wishes to dispose of the session identified by the
550      * {@code subscriptionId} argument and the caller's uid. No notification back to the
551      * app is required for this operation, and the corresponding callback provided via
552      * {@link #initialize(int, IMbmsDownloadSessionCallback)} should no longer be used
553      * after this method has been called by the app.
554      *
555      * Any download requests issued by the app should remain in effect until the app calls
556      * {@link #cancelDownload(DownloadRequest)} on another session.
557      *
558      * May throw an {@link IllegalStateException}
559      *
560      * @param subscriptionId The subscription id to use.
561      */
562     @Override
dispose(int subscriptionId)563     public void dispose(int subscriptionId) throws RemoteException {
564     }
565 
566     /**
567      * Indicates that the app identified by the given UID and subscription ID has died.
568      * @param uid the UID of the app, as returned by {@link Binder#getCallingUid()}.
569      * @param subscriptionId The subscription ID the app is using.
570      */
onAppCallbackDied(int uid, int subscriptionId)571     public void onAppCallbackDied(int uid, int subscriptionId) {
572     }
573 
574     // Following two methods exist to workaround b/124210145
575     /** @hide */
576     @SystemApi
577     @Override
asBinder()578     public android.os.IBinder asBinder() {
579         return super.asBinder();
580     }
581 
582     /** @hide */
583     @SystemApi
584     @Override
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)585     public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,
586             int flags) throws RemoteException {
587         return super.onTransact(code, data, reply, flags);
588     }
589 }
590