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