Home | History | Annotate | Download | only in vendor
      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;
     58         public VendorDownloadStatusListener(IDownloadStatusListener listener) {
     59             mListener = listener;
     60         }
     61 
     62         @Override
     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 
     72         protected abstract void onRemoteException(RemoteException e);
     73     }
     74 
     75     private abstract static class VendorDownloadProgressListener extends DownloadProgressListener {
     76         private final IDownloadProgressListener mListener;
     77 
     78         public VendorDownloadProgressListener(IDownloadProgressListener listener) {
     79             mListener = listener;
     80         }
     81 
     82         @Override
     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 
     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      */
    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
    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
    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
    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
    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      */
    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
    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      */
    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      */
    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      */
    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
    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      */
    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      */
    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
    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
    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
    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
    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
    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      */
    550     public void onAppCallbackDied(int uid, int subscriptionId) {
    551     }
    552 }
    553