From c21bf59665de6156433630d4e97fc1c0da31b0d1 Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Wed, 28 Mar 2018 15:54:07 -0700 Subject: [PATCH] Make changes to MBMS API following recs * Change all error handling logic to return an error code via the async callback * Add an UNKNOWN code for errors for future backwards compatibility, and prohibit the middleware from sending this code. * Add IntDef for errors through the async callback * Amend documentation for download() * Implement support for arranging downloaded files into a hierarchy following that of the server. Change-Id: I4d5c8f6229b216d9aa84397e628e62279033cc74 Fixes: 76449215 Test: CTS --- api/current.txt | 13 +- .../telephony/MbmsDownloadSession.java | 158 ++++++++++++------ .../telephony/MbmsStreamingSession.java | 22 ++- .../mbms/InternalDownloadSessionCallback.java | 7 +- .../telephony/mbms/MbmsDownloadReceiver.java | 48 +++++- .../mbms/MbmsDownloadSessionCallback.java | 25 ++- .../android/telephony/mbms/MbmsErrors.java | 7 + .../mbms/MbmsStreamingSessionCallback.java | 27 ++- .../android/telephony/mbms/MbmsUtils.java | 6 +- .../mbms/StreamingServiceCallback.java | 23 ++- .../mbms/vendor/MbmsDownloadServiceBase.java | 4 + .../mbms/vendor/MbmsStreamingServiceBase.java | 8 + 12 files changed, 275 insertions(+), 73 deletions(-) diff --git a/api/current.txt b/api/current.txt index 3b182171cf29e..095e841a48869 100644 --- a/api/current.txt +++ b/api/current.txt @@ -41928,17 +41928,17 @@ package android.telephony { } public class MbmsDownloadSession implements java.lang.AutoCloseable { - method public int addProgressListener(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadProgressListener); - method public int addStatusListener(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadStatusListener); - method public int cancelDownload(android.telephony.mbms.DownloadRequest); + method public void addProgressListener(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadProgressListener); + method public void addStatusListener(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadStatusListener); + method public void cancelDownload(android.telephony.mbms.DownloadRequest); method public void close(); method public static android.telephony.MbmsDownloadSession create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsDownloadSessionCallback); method public static android.telephony.MbmsDownloadSession create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsDownloadSessionCallback); - method public int download(android.telephony.mbms.DownloadRequest); + method public void download(android.telephony.mbms.DownloadRequest); method public java.io.File getTempFileRootDirectory(); method public java.util.List listPendingDownloads(); - method public int removeProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener); - method public int removeStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener); + method public void removeProgressListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadProgressListener); + method public void removeStatusListener(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStatusListener); method public void requestDownloadState(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo); method public void requestUpdateFileServices(java.util.List); method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest); @@ -42905,6 +42905,7 @@ package android.telephony.mbms { field public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; // 0x2 field public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; // 0x1 field public static final int SUCCESS = 0; // 0x0 + field public static final int UNKNOWN = -1; // 0xffffffff } public static class MbmsErrors.DownloadErrors { diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java index da04a0d1075cf..38a6593edac53 100644 --- a/telephony/java/android/telephony/MbmsDownloadSession.java +++ b/telephony/java/android/telephony/MbmsDownloadSession.java @@ -16,6 +16,8 @@ package android.telephony; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -32,14 +34,14 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.telephony.mbms.DownloadProgressListener; +import android.telephony.mbms.DownloadRequest; import android.telephony.mbms.DownloadStatusListener; import android.telephony.mbms.FileInfo; -import android.telephony.mbms.DownloadRequest; import android.telephony.mbms.InternalDownloadProgressListener; import android.telephony.mbms.InternalDownloadSessionCallback; import android.telephony.mbms.InternalDownloadStatusListener; -import android.telephony.mbms.MbmsDownloadSessionCallback; import android.telephony.mbms.MbmsDownloadReceiver; +import android.telephony.mbms.MbmsDownloadSessionCallback; import android.telephony.mbms.MbmsErrors; import android.telephony.mbms.MbmsTempFileProvider; import android.telephony.mbms.MbmsUtils; @@ -58,8 +60,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; - /** * This class provides functionality for file download over MBMS. */ @@ -337,6 +337,12 @@ public class MbmsDownloadSession implements AutoCloseable { sIsInitialized.set(false); return; } + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an" + + " unknown error code"); + } if (result != MbmsErrors.SUCCESS) { sendErrorToApp(result, "Error returned during initialization"); sIsInitialized.set(false); @@ -388,6 +394,11 @@ public class MbmsDownloadSession implements AutoCloseable { } try { int returnCode = downloadService.requestUpdateFileServices(mSubscriptionId, classList); + if (returnCode == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown error code"); + } if (returnCode != MbmsErrors.SUCCESS) { sendErrorToApp(returnCode, null); } @@ -443,6 +454,11 @@ public class MbmsDownloadSession implements AutoCloseable { try { int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath); + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown error code"); + } if (result != MbmsErrors.SUCCESS) { sendErrorToApp(result, null); return; @@ -514,11 +530,13 @@ public class MbmsDownloadSession implements AutoCloseable { * * Asynchronous errors through the callback may include any error not specific to the * streaming use-case. + * + * If no error is delivered via the callback after calling this method, that means that the + * middleware has successfully started the download or scheduled the download, if the download + * is at a future time. * @param request The request that specifies what should be downloaded. - * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, - * and some other error code otherwise. */ - public int download(@NonNull DownloadRequest request) { + public void download(@NonNull DownloadRequest request) { IMbmsDownloadService downloadService = mService.get(); if (downloadService == null) { throw new IllegalStateException("Middleware not yet bound"); @@ -540,12 +558,19 @@ public class MbmsDownloadSession implements AutoCloseable { int result = downloadService.download(request); if (result == MbmsErrors.SUCCESS) { writeDownloadRequestToken(request); + } else { + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown" + + " error code"); + } + sendErrorToApp(result, null); } - return result; } catch (RemoteException e) { mService.set(null); sIsInitialized.set(false); - return MbmsErrors.ERROR_MIDDLEWARE_LOST; + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); } } @@ -581,14 +606,15 @@ public class MbmsDownloadSession implements AutoCloseable { * If the middleware is not aware of the specified download request, * this method will throw an {@link IllegalArgumentException}. * + * If the operation encountered an error, the error code will be delivered via + * {@link MbmsDownloadSessionCallback#onError}. + * * @param request The {@link DownloadRequest} that you want updates on. * @param executor The {@link Executor} on which calls to {@code listener } should be executed. * @param listener The listener that should be called when the middleware has information to * share on the status download. - * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, - * and some other error code otherwise. */ - public int addStatusListener(@NonNull DownloadRequest request, + public void addStatusListener(@NonNull DownloadRequest request, @NonNull Executor executor, @NonNull DownloadStatusListener listener) { IMbmsDownloadService downloadService = mService.get(); if (downloadService == null) { @@ -600,20 +626,25 @@ public class MbmsDownloadSession implements AutoCloseable { try { int result = downloadService.addStatusListener(request, internalListener); + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown error code"); + } if (result != MbmsErrors.SUCCESS) { if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { throw new IllegalArgumentException("Unknown download request."); } - return result; + sendErrorToApp(result, null); + return; } } catch (RemoteException e) { mService.set(null); sIsInitialized.set(false); - return MbmsErrors.ERROR_MIDDLEWARE_LOST; + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); + return; } mInternalDownloadStatusListeners.put(listener, internalListener); - return MbmsErrors.SUCCESS; - } /** @@ -625,12 +656,13 @@ public class MbmsDownloadSession implements AutoCloseable { * If the middleware is not aware of the specified download request, * this method will throw an {@link IllegalArgumentException}. * + * If the operation encountered an error, the error code will be delivered via + * {@link MbmsDownloadSessionCallback#onError}. + * * @param request The {@link DownloadRequest} provided during registration * @param listener The listener provided during registration. - * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, - * and some other error code otherwise. */ - public int removeStatusListener(@NonNull DownloadRequest request, + public void removeStatusListener(@NonNull DownloadRequest request, @NonNull DownloadStatusListener listener) { try { IMbmsDownloadService downloadService = mService.get(); @@ -646,16 +678,24 @@ public class MbmsDownloadSession implements AutoCloseable { try { int result = downloadService.removeStatusListener(request, internalListener); + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an" + + " unknown error code"); + } if (result != MbmsErrors.SUCCESS) { if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { throw new IllegalArgumentException("Unknown download request."); } - return result; + sendErrorToApp(result, null); + return; } } catch (RemoteException e) { mService.set(null); sIsInitialized.set(false); - return MbmsErrors.ERROR_MIDDLEWARE_LOST; + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); + return; } } finally { InternalDownloadStatusListener internalCallback = @@ -664,7 +704,6 @@ public class MbmsDownloadSession implements AutoCloseable { internalCallback.stop(); } } - return MbmsErrors.SUCCESS; } /** @@ -676,14 +715,15 @@ public class MbmsDownloadSession implements AutoCloseable { * If the middleware is not aware of the specified download request, * this method will throw an {@link IllegalArgumentException}. * + * If the operation encountered an error, the error code will be delivered via + * {@link MbmsDownloadSessionCallback#onError}. + * * @param request The {@link DownloadRequest} that you want updates on. * @param executor The {@link Executor} on which calls to {@code listener} should be executed. * @param listener The listener that should be called when the middleware has information to * share on the progress of the download. - * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, - * and some other error code otherwise. */ - public int addProgressListener(@NonNull DownloadRequest request, + public void addProgressListener(@NonNull DownloadRequest request, @NonNull Executor executor, @NonNull DownloadProgressListener listener) { IMbmsDownloadService downloadService = mService.get(); if (downloadService == null) { @@ -695,19 +735,25 @@ public class MbmsDownloadSession implements AutoCloseable { try { int result = downloadService.addProgressListener(request, internalListener); + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown error code"); + } if (result != MbmsErrors.SUCCESS) { if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { throw new IllegalArgumentException("Unknown download request."); } - return result; + sendErrorToApp(result, null); + return; } } catch (RemoteException e) { mService.set(null); sIsInitialized.set(false); - return MbmsErrors.ERROR_MIDDLEWARE_LOST; + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); + return; } mInternalDownloadProgressListeners.put(listener, internalListener); - return MbmsErrors.SUCCESS; } /** @@ -719,12 +765,13 @@ public class MbmsDownloadSession implements AutoCloseable { * If the middleware is not aware of the specified download request, * this method will throw an {@link IllegalArgumentException}. * + * If the operation encountered an error, the error code will be delivered via + * {@link MbmsDownloadSessionCallback#onError}. + * * @param request The {@link DownloadRequest} provided during registration * @param listener The listener provided during registration. - * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, - * and some other error code otherwise. */ - public int removeProgressListener(@NonNull DownloadRequest request, + public void removeProgressListener(@NonNull DownloadRequest request, @NonNull DownloadProgressListener listener) { try { IMbmsDownloadService downloadService = mService.get(); @@ -740,16 +787,24 @@ public class MbmsDownloadSession implements AutoCloseable { try { int result = downloadService.removeProgressListener(request, internalListener); + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not" + + " return an unknown error code"); + } if (result != MbmsErrors.SUCCESS) { if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { throw new IllegalArgumentException("Unknown download request."); } - return result; + sendErrorToApp(result, null); + return; } } catch (RemoteException e) { mService.set(null); sIsInitialized.set(false); - return MbmsErrors.ERROR_MIDDLEWARE_LOST; + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); + return; } } finally { InternalDownloadProgressListener internalCallback = @@ -758,20 +813,17 @@ public class MbmsDownloadSession implements AutoCloseable { internalCallback.stop(); } } - return MbmsErrors.SUCCESS; } /** * Attempts to cancel the specified {@link DownloadRequest}. * - * If the middleware is not aware of the specified download request, - * this method will throw an {@link IllegalArgumentException}. + * If the operation encountered an error, the error code will be delivered via + * {@link MbmsDownloadSessionCallback#onError}. * * @param downloadRequest The download request that you wish to cancel. - * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, - * and some other error code otherwise. */ - public int cancelDownload(@NonNull DownloadRequest downloadRequest) { + public void cancelDownload(@NonNull DownloadRequest downloadRequest) { IMbmsDownloadService downloadService = mService.get(); if (downloadService == null) { throw new IllegalStateException("Middleware not yet bound"); @@ -779,18 +831,20 @@ public class MbmsDownloadSession implements AutoCloseable { try { int result = downloadService.cancelDownload(downloadRequest); + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown error code"); + } if (result != MbmsErrors.SUCCESS) { - if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { - throw new IllegalArgumentException("Unknown download request."); - } + sendErrorToApp(result, null); } else { deleteDownloadRequestToken(downloadRequest); } - return result; } catch (RemoteException e) { mService.set(null); sIsInitialized.set(false); - return MbmsErrors.ERROR_MIDDLEWARE_LOST; + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); } } @@ -818,6 +872,11 @@ public class MbmsDownloadSession implements AutoCloseable { try { int result = downloadService.requestDownloadState(downloadRequest, fileInfo); + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown error code"); + } if (result != MbmsErrors.SUCCESS) { if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { throw new IllegalArgumentException("Unknown download request."); @@ -862,6 +921,11 @@ public class MbmsDownloadSession implements AutoCloseable { try { int result = downloadService.resetDownloadKnowledge(downloadRequest); + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown error code"); + } if (result != MbmsErrors.SUCCESS) { if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) { throw new IllegalArgumentException("Unknown download request."); @@ -978,10 +1042,6 @@ public class MbmsDownloadSession implements AutoCloseable { } private void sendErrorToApp(int errorCode, String message) { - try { - mInternalCallback.onError(errorCode, message); - } catch (RemoteException e) { - // Ignore, should not happen locally. - } + mInternalCallback.onError(errorCode, message); } } diff --git a/telephony/java/android/telephony/MbmsStreamingSession.java b/telephony/java/android/telephony/MbmsStreamingSession.java index 42c760d4dde81..cd465d22d3312 100644 --- a/telephony/java/android/telephony/MbmsStreamingSession.java +++ b/telephony/java/android/telephony/MbmsStreamingSession.java @@ -16,6 +16,8 @@ package android.telephony; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; @@ -26,8 +28,8 @@ import android.content.Context; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; -import android.telephony.mbms.InternalStreamingSessionCallback; import android.telephony.mbms.InternalStreamingServiceCallback; +import android.telephony.mbms.InternalStreamingSessionCallback; import android.telephony.mbms.MbmsErrors; import android.telephony.mbms.MbmsStreamingSessionCallback; import android.telephony.mbms.MbmsUtils; @@ -44,8 +46,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; - /** * This class provides functionality for streaming media over MBMS. */ @@ -208,6 +208,11 @@ public class MbmsStreamingSession implements AutoCloseable { try { int returnCode = streamingService.requestUpdateStreamingServices( mSubscriptionId, serviceClassList); + if (returnCode == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown error code"); + } if (returnCode != MbmsErrors.SUCCESS) { sendErrorToApp(returnCode, null); } @@ -255,6 +260,11 @@ public class MbmsStreamingSession implements AutoCloseable { try { int returnCode = streamingService.startStreaming( mSubscriptionId, serviceInfo.getServiceId(), serviceCallback); + if (returnCode == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return an unknown error code"); + } if (returnCode != MbmsErrors.SUCCESS) { sendErrorToApp(returnCode, null); return null; @@ -301,6 +311,12 @@ public class MbmsStreamingSession implements AutoCloseable { sIsInitialized.set(false); return; } + if (result == MbmsErrors.UNKNOWN) { + // Unbind and throw an obvious error + close(); + throw new IllegalStateException("Middleware must not return" + + " an unknown error code"); + } if (result != MbmsErrors.SUCCESS) { sendErrorToApp(result, "Error returned during initialization"); sIsInitialized.set(false); diff --git a/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java index c2a79d82f8b63..2916f81c8cd27 100644 --- a/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java +++ b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java @@ -17,7 +17,6 @@ package android.telephony.mbms; import android.os.Binder; -import android.os.RemoteException; import java.util.List; import java.util.concurrent.Executor; @@ -36,7 +35,7 @@ public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallbac } @Override - public void onError(final int errorCode, final String message) throws RemoteException { + public void onError(final int errorCode, final String message) { if (mIsStopped) { return; } @@ -55,7 +54,7 @@ public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallbac } @Override - public void onFileServicesUpdated(final List services) throws RemoteException { + public void onFileServicesUpdated(final List services) { if (mIsStopped) { return; } @@ -74,7 +73,7 @@ public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallbac } @Override - public void onMiddlewareReady() throws RemoteException { + public void onMiddlewareReady() { if (mIsStopped) { return; } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java index fe7533f57b120..dd1061f3bd466 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java @@ -31,6 +31,8 @@ import android.telephony.MbmsDownloadSession; import android.telephony.mbms.vendor.VendorUtils; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import java.io.File; import java.io.FileFilter; import java.io.IOException; @@ -268,7 +270,10 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { Uri finalLocation; try { - finalLocation = moveToFinalLocation(finalTempFile, appSpecifiedDestination); + String relativeLocation = getFileRelativePath(request.getSourceUri().getPath(), + completedFileInfo.getUri().getPath()); + finalLocation = moveToFinalLocation(finalTempFile, appSpecifiedDestination, + relativeLocation); } catch (IOException e) { Log.w(LOG_TAG, "Failed to move temp file to final destination"); setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR); @@ -442,7 +447,8 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { /* * Moves a tempfile located at fromPath to its final home where the app wants it */ - private static Uri moveToFinalLocation(Uri fromPath, Path appSpecifiedPath) throws IOException { + private static Uri moveToFinalLocation(Uri fromPath, Path appSpecifiedPath, + String relativeLocation) throws IOException { if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) { Log.w(LOG_TAG, "Downloaded file location uri " + fromPath + " does not have a file scheme"); @@ -450,16 +456,46 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } Path fromFile = FileSystems.getDefault().getPath(fromPath.getPath()); - if (!Files.isDirectory(appSpecifiedPath)) { - Files.createDirectory(appSpecifiedPath); + Path toFile = appSpecifiedPath.resolve(relativeLocation); + + if (!Files.isDirectory(toFile.getParent())) { + Files.createDirectories(toFile.getParent()); } - // TODO: do we want to support directory trees within the download directory? - Path result = Files.move(fromFile, appSpecifiedPath.resolve(fromFile.getFileName()), + Path result = Files.move(fromFile, toFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); return Uri.fromFile(result.toFile()); } + /** + * @hide + */ + @VisibleForTesting + public static String getFileRelativePath(String sourceUriPath, String fileInfoPath) { + if (sourceUriPath.endsWith("*")) { + // This is a wildcard path. Strip the last path component and use that as the root of + // the relative path. + int lastSlash = sourceUriPath.lastIndexOf('/'); + sourceUriPath = sourceUriPath.substring(0, lastSlash); + } + if (!fileInfoPath.startsWith(sourceUriPath)) { + Log.e(LOG_TAG, "File location specified in FileInfo does not match the source URI." + + " source: " + sourceUriPath + " fileinfo path: " + fileInfoPath); + return null; + } + if (fileInfoPath.length() == sourceUriPath.length()) { + // This is the single-file download case. Return the name of the file so that the + // receiver puts the file directly into the dest directory. + return sourceUriPath.substring(sourceUriPath.lastIndexOf('/') + 1); + } + + String prefixOmittedPath = fileInfoPath.substring(sourceUriPath.length()); + if (prefixOmittedPath.startsWith("/")) { + prefixOmittedPath = prefixOmittedPath.substring(1); + } + return prefixOmittedPath; + } + private static boolean verifyTempFilePath(Context context, String serviceId, Uri filePath) { if (!ContentResolver.SCHEME_FILE.equals(filePath.getScheme())) { diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java index 77dea6f309e5c..5003b576173e7 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java @@ -16,8 +16,11 @@ package android.telephony.mbms; +import android.annotation.IntDef; import android.telephony.MbmsDownloadSession; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -25,6 +28,26 @@ import java.util.List; * cell-broadcast. */ public class MbmsDownloadSessionCallback { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE, + MbmsErrors.ERROR_MIDDLEWARE_LOST, + MbmsErrors.ERROR_MIDDLEWARE_NOT_BOUND, + MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED, + MbmsErrors.InitializationErrors.ERROR_DUPLICATE_INITIALIZE, + MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, + MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_NOT_YET_READY, + MbmsErrors.GeneralErrors.ERROR_OUT_OF_MEMORY, + MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE, + MbmsErrors.GeneralErrors.ERROR_IN_E911, + MbmsErrors.GeneralErrors.ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE, + MbmsErrors.GeneralErrors.ERROR_UNABLE_TO_READ_SIM, + MbmsErrors.GeneralErrors.ERROR_CARRIER_CHANGE_NOT_ALLOWED, + MbmsErrors.DownloadErrors.ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT, + MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST, + MbmsErrors.DownloadErrors.ERROR_UNKNOWN_FILE_INFO}, prefix = { "ERROR_" }) + private @interface DownloadError{} /** * Indicates that the middleware has encountered an asynchronous error. @@ -32,7 +55,7 @@ public class MbmsDownloadSessionCallback { * @param message A message, intended for debugging purposes, describing the error in further * detail. */ - public void onError(int errorCode, String message) { + public void onError(@DownloadError int errorCode, String message) { // default implementation empty } diff --git a/telephony/java/android/telephony/mbms/MbmsErrors.java b/telephony/java/android/telephony/mbms/MbmsErrors.java index b5fec445d853d..7c4321ba75c3a 100644 --- a/telephony/java/android/telephony/mbms/MbmsErrors.java +++ b/telephony/java/android/telephony/mbms/MbmsErrors.java @@ -19,6 +19,13 @@ package android.telephony.mbms; import android.telephony.MbmsStreamingSession; public class MbmsErrors { + /** + * Indicates that the middleware has sent an error code that is not defined in the version of + * the SDK targeted by your app. This is an illegal value for the middleware to return -- it + * should only ever be generated by the framework. + */ + public static final int UNKNOWN = -1; + /** Indicates that the operation was successful. */ public static final int SUCCESS = 0; diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java index 6e0395730aba4..1bdb20bf08237 100644 --- a/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java +++ b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java @@ -16,11 +16,13 @@ package android.telephony.mbms; +import android.annotation.IntDef; import android.annotation.Nullable; import android.content.Context; -import android.os.Handler; import android.telephony.MbmsStreamingSession; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.concurrent.Executor; @@ -30,13 +32,34 @@ import java.util.concurrent.Executor; * {@link MbmsStreamingSession#create(Context, Executor, int, MbmsStreamingSessionCallback)}. */ public class MbmsStreamingSessionCallback { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE, + MbmsErrors.ERROR_MIDDLEWARE_LOST, + MbmsErrors.ERROR_MIDDLEWARE_NOT_BOUND, + MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED, + MbmsErrors.InitializationErrors.ERROR_DUPLICATE_INITIALIZE, + MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, + MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_NOT_YET_READY, + MbmsErrors.GeneralErrors.ERROR_OUT_OF_MEMORY, + MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE, + MbmsErrors.GeneralErrors.ERROR_IN_E911, + MbmsErrors.GeneralErrors.ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE, + MbmsErrors.GeneralErrors.ERROR_UNABLE_TO_READ_SIM, + MbmsErrors.GeneralErrors.ERROR_CARRIER_CHANGE_NOT_ALLOWED, + MbmsErrors.StreamingErrors.ERROR_CONCURRENT_SERVICE_LIMIT_REACHED, + MbmsErrors.StreamingErrors.ERROR_UNABLE_TO_START_SERVICE, + MbmsErrors.StreamingErrors.ERROR_DUPLICATE_START_STREAM}, prefix = { "ERROR_" }) + private @interface StreamingError{} + /** * Called by the middleware when it has detected an error condition. The possible error codes * are listed in {@link MbmsErrors}. * @param errorCode The error code. * @param message A human-readable message generated by the middleware for debugging purposes. */ - public void onError(int errorCode, @Nullable String message) { + public void onError(@StreamingError int errorCode, @Nullable String message) { // default implementation empty } diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java index ef317eefb3e38..06b2120b59c1f 100644 --- a/telephony/java/android/telephony/mbms/MbmsUtils.java +++ b/telephony/java/android/telephony/mbms/MbmsUtils.java @@ -130,8 +130,12 @@ public class MbmsUtils { * Returns a File linked to the directory used to store temp files for this file service */ public static File getEmbmsTempFileDirForService(Context context, String serviceId) { + // Replace all non-alphanumerics/underscores with an underscore. Some filesystems don't + // like special characters. + String sanitizedServiceId = serviceId.replaceAll("[^a-zA-Z0-9_]", "_"); + File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context); - return new File(embmsTempFileDir, serviceId); + return new File(embmsTempFileDir, sanitizedServiceId); } } diff --git a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java index 09038244a16a1..c265db6fe1ca5 100644 --- a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java +++ b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java @@ -16,13 +16,34 @@ package android.telephony.mbms; +import android.annotation.IntDef; import android.annotation.Nullable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A callback class for use when the application is actively streaming content. The middleware * will provide updates on the status of the stream via this callback. */ public class StreamingServiceCallback { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE, + MbmsErrors.ERROR_MIDDLEWARE_LOST, + MbmsErrors.ERROR_MIDDLEWARE_NOT_BOUND, + MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_NOT_YET_READY, + MbmsErrors.GeneralErrors.ERROR_OUT_OF_MEMORY, + MbmsErrors.GeneralErrors.ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE, + MbmsErrors.GeneralErrors.ERROR_IN_E911, + MbmsErrors.GeneralErrors.ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE, + MbmsErrors.GeneralErrors.ERROR_UNABLE_TO_READ_SIM, + MbmsErrors.GeneralErrors.ERROR_CARRIER_CHANGE_NOT_ALLOWED, + MbmsErrors.StreamingErrors.ERROR_CONCURRENT_SERVICE_LIMIT_REACHED, + MbmsErrors.StreamingErrors.ERROR_UNABLE_TO_START_SERVICE, + MbmsErrors.StreamingErrors.ERROR_DUPLICATE_START_STREAM}, prefix = { "ERROR_" }) + private @interface StreamingServiceError{} /** * Indicates broadcast signal strength is not available for this service. @@ -39,7 +60,7 @@ public class StreamingServiceCallback { * @param errorCode The error code. * @param message A human-readable message generated by the middleware for debugging purposes. */ - public void onError(int errorCode, @Nullable String message) { + public void onError(@StreamingServiceError int errorCode, @Nullable String message) { // default implementation empty } diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java index f9d7161021e91..a9f10b1b460ff 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java @@ -130,6 +130,10 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { @Override public void onError(int errorCode, String message) { try { + if (errorCode == MbmsErrors.UNKNOWN) { + throw new IllegalArgumentException( + "Middleware cannot send an unknown error."); + } callback.onError(errorCode, message); } catch (RemoteException e) { onAppCallbackDied(uid, subscriptionId); diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java index db177c0c77688..5ce612db49d67 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java @@ -77,6 +77,10 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { @Override public void onError(final int errorCode, final String message) { try { + if (errorCode == MbmsErrors.UNKNOWN) { + throw new IllegalArgumentException( + "Middleware cannot send an unknown error."); + } callback.onError(errorCode, message); } catch (RemoteException e) { onAppCallbackDied(uid, subscriptionId); @@ -173,6 +177,10 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { @Override public void onError(final int errorCode, final String message) { try { + if (errorCode == MbmsErrors.UNKNOWN) { + throw new IllegalArgumentException( + "Middleware cannot send an unknown error."); + } callback.onError(errorCode, message); } catch (RemoteException e) { onAppCallbackDied(uid, subscriptionId);