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