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