diff options
14 files changed, 383 insertions, 247 deletions
diff --git a/api/current.txt b/api/current.txt index 45f38ae9b0d0..10edfdc4bd05 100644 --- a/api/current.txt +++ b/api/current.txt @@ -40415,12 +40415,12 @@ package android.telephony { public class MbmsDownloadSession implements java.lang.AutoCloseable { method public int cancelDownload(android.telephony.mbms.DownloadRequest); method public void close(); - method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, android.os.Handler); - method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, int, android.os.Handler); + 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 java.io.File getTempFileRootDirectory(); method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads(); - method public int registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler); + method public int registerStateCallback(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadStateCallback); method public void requestDownloadState(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo); method public void requestUpdateFileServices(java.util.List<java.lang.String>); method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest); @@ -40448,10 +40448,10 @@ package android.telephony { public class MbmsStreamingSession implements java.lang.AutoCloseable { method public void close(); - method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, int, android.os.Handler); - method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler); + method public static android.telephony.MbmsStreamingSession create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsStreamingSessionCallback); + method public static android.telephony.MbmsStreamingSession create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsStreamingSessionCallback); method public void requestUpdateStreamingServices(java.util.List<java.lang.String>); - method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler); + method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback); } public class NeighboringCellInfo implements android.os.Parcelable { @@ -41279,20 +41279,23 @@ package android.telephony.gsm { package android.telephony.mbms { public final class DownloadRequest implements android.os.Parcelable { - method public static android.telephony.mbms.DownloadRequest copy(android.telephony.mbms.DownloadRequest); method public int describeContents(); + method public android.net.Uri getDestinationUri(); method public java.lang.String getFileServiceId(); method public static int getMaxAppIntentSize(); method public static int getMaxDestinationUriSize(); method public android.net.Uri getSourceUri(); method public int getSubscriptionId(); + method public byte[] toByteArray(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.mbms.DownloadRequest> CREATOR; } public static class DownloadRequest.Builder { - ctor public DownloadRequest.Builder(android.net.Uri); + ctor public DownloadRequest.Builder(android.net.Uri, android.net.Uri); method public android.telephony.mbms.DownloadRequest build(); + method public static android.telephony.mbms.DownloadRequest.Builder fromDownloadRequest(android.telephony.mbms.DownloadRequest); + method public static android.telephony.mbms.DownloadRequest.Builder fromSerializedRequest(byte[]); method public android.telephony.mbms.DownloadRequest.Builder setAppIntent(android.content.Intent); method public android.telephony.mbms.DownloadRequest.Builder setServiceInfo(android.telephony.mbms.FileServiceInfo); method public android.telephony.mbms.DownloadRequest.Builder setSubscriptionId(int); @@ -41388,10 +41391,10 @@ package android.telephony.mbms { method public java.util.Date getSessionStartTime(); } - public class StreamingService { + public class StreamingService implements java.lang.AutoCloseable { + method public void close(); method public android.telephony.mbms.StreamingServiceInfo getInfo(); method public android.net.Uri getPlaybackUri(); - method public void stopStreaming(); field public static final int BROADCAST_METHOD = 1; // 0x1 field public static final int REASON_BY_USER_REQUEST = 1; // 0x1 field public static final int REASON_END_OF_SESSION = 2; // 0x2 diff --git a/api/system-current.txt b/api/system-current.txt index d2c6bd064ac9..e3336db34aa8 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5287,12 +5287,7 @@ package android.telephony.ims.stub { package android.telephony.mbms { - public final class DownloadRequest implements android.os.Parcelable { - method public byte[] getOpaqueData(); - } - public static class DownloadRequest.Builder { - method public android.telephony.mbms.DownloadRequest.Builder setOpaqueData(byte[]); method public android.telephony.mbms.DownloadRequest.Builder setServiceId(java.lang.String); } diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java index da3d87bc7211..ce1b80c2c860 100644 --- a/telephony/java/android/telephony/MbmsDownloadSession.java +++ b/telephony/java/android/telephony/MbmsDownloadSession.java @@ -30,7 +30,6 @@ import android.content.SharedPreferences; import android.net.Uri; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.telephony.mbms.DownloadStateCallback; import android.telephony.mbms.FileInfo; @@ -53,6 +52,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -107,11 +107,8 @@ public class MbmsDownloadSession implements AutoCloseable { /** * {@link Uri} extra that Android will attach to the intent supplied via * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)} - * Indicates the location of the successfully downloaded file within the temp file root set - * via {@link #setTempFileRootDirectory(File)}. - * While you may use this file in-place, it is highly encouraged that you move - * this file to a different location after receiving the download completion intent, as this - * file resides within the temp file directory. + * Indicates the location of the successfully downloaded file within the directory that the + * app provided via the builder. * * Will always be set to a non-null value if * {@link #EXTRA_MBMS_DOWNLOAD_RESULT} is set to {@link #RESULT_SUCCESSFUL}. @@ -220,6 +217,8 @@ public class MbmsDownloadSession implements AutoCloseable { */ public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4; + private static final String DESTINATION_SANITY_CHECK_FILE_NAME = "destinationSanityCheckFile"; + private static AtomicBoolean sIsInitialized = new AtomicBoolean(false); private final Context mContext; @@ -236,23 +235,20 @@ public class MbmsDownloadSession implements AutoCloseable { private final Map<DownloadStateCallback, InternalDownloadStateCallback> mInternalDownloadCallbacks = new HashMap<>(); - private MbmsDownloadSession(Context context, MbmsDownloadSessionCallback callback, - int subscriptionId, Handler handler) { + private MbmsDownloadSession(Context context, Executor executor, int subscriptionId, + MbmsDownloadSessionCallback callback) { mContext = context; mSubscriptionId = subscriptionId; - if (handler == null) { - handler = new Handler(Looper.getMainLooper()); - } - mInternalCallback = new InternalDownloadSessionCallback(callback, handler); + mInternalCallback = new InternalDownloadSessionCallback(callback, executor); } /** * Create a new {@link MbmsDownloadSession} using the system default data subscription ID. - * See {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)} + * See {@link #create(Context, Executor, int, MbmsDownloadSessionCallback)} */ public static MbmsDownloadSession create(@NonNull Context context, - @NonNull MbmsDownloadSessionCallback callback, @NonNull Handler handler) { - return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler); + @NonNull Executor executor, @NonNull MbmsDownloadSessionCallback callback) { + return create(context, executor, SubscriptionManager.getDefaultSubscriptionId(), callback); } /** @@ -279,24 +275,24 @@ public class MbmsDownloadSession implements AutoCloseable { * {@link MbmsDownloadSession} that you received before calling this method again. * * @param context The instance of {@link Context} to use - * @param callback A callback to get asynchronous error messages and file service updates. + * @param executor The executor on which you wish to execute callbacks. * @param subscriptionId The data subscription ID to use - * @param handler The {@link Handler} on which callbacks should be enqueued. + * @param callback A callback to get asynchronous error messages and file service updates. * @return A new instance of {@link MbmsDownloadSession}, or null if an error occurred during * setup. */ public static @Nullable MbmsDownloadSession create(@NonNull Context context, - final @NonNull MbmsDownloadSessionCallback callback, - int subscriptionId, @NonNull Handler handler) { + @NonNull Executor executor, int subscriptionId, + final @NonNull MbmsDownloadSessionCallback callback) { if (!sIsInitialized.compareAndSet(false, true)) { throw new IllegalStateException("Cannot have two active instances"); } MbmsDownloadSession session = - new MbmsDownloadSession(context, callback, subscriptionId, handler); + new MbmsDownloadSession(context, executor, subscriptionId, callback); final int result = session.bindAndInitialize(); if (result != MbmsErrors.SUCCESS) { sIsInitialized.set(false); - handler.post(new Runnable() { + executor.execute(new Runnable() { @Override public void run() { callback.onError(result, null); @@ -500,6 +496,10 @@ public class MbmsDownloadSession implements AutoCloseable { * {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp * file root directory. * + * If the {@link DownloadRequest} has a destination that is not on the same filesystem as the + * temp file directory provided via {@link #getTempFileRootDirectory()}, an + * {@link IllegalArgumentException} will be thrown. + * * Asynchronous errors through the callback may include any error not specific to the * streaming use-case. * @param request The request that specifies what should be downloaded. @@ -522,6 +522,8 @@ public class MbmsDownloadSession implements AutoCloseable { setTempFileRootDirectory(tempRootDirectory); } + checkDownloadRequestDestination(request); + try { int result = downloadService.download(request); if (result == MbmsErrors.SUCCESS) { @@ -568,21 +570,21 @@ public class MbmsDownloadSession implements AutoCloseable { * this method will throw an {@link IllegalArgumentException}. * * @param request The {@link DownloadRequest} that you want updates on. + * @param executor The {@link Executor} on which calls to {@code callback} should be executed. * @param callback The callback that should be called when the middleware has information to * share on the download. - * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on. * @return {@link MbmsErrors#SUCCESS} if the operation did not encounter a synchronous error, * and some other error code otherwise. */ public int registerStateCallback(@NonNull DownloadRequest request, - @NonNull DownloadStateCallback callback, @NonNull Handler handler) { + @NonNull Executor executor, @NonNull DownloadStateCallback callback) { IMbmsDownloadService downloadService = mService.get(); if (downloadService == null) { throw new IllegalStateException("Middleware not yet bound"); } InternalDownloadStateCallback internalCallback = - new InternalDownloadStateCallback(callback, handler); + new InternalDownloadStateCallback(callback, executor); try { int result = downloadService.registerStateCallback(request, internalCallback, @@ -604,7 +606,7 @@ public class MbmsDownloadSession implements AutoCloseable { /** * Un-register a callback previously registered via - * {@link #registerStateCallback(DownloadRequest, DownloadStateCallback, Handler)}. After + * {@link #registerStateCallback(DownloadRequest, Executor, DownloadStateCallback)}. After * this method is called, no further callbacks will be enqueued on the {@link Handler} * provided upon registration, even if this method throws an exception. * @@ -692,7 +694,7 @@ public class MbmsDownloadSession implements AutoCloseable { * The state will be delivered as a callback via * {@link DownloadStateCallback#onStateUpdated(DownloadRequest, FileInfo, int)}. If no such * callback has been registered via - * {@link #registerStateCallback(DownloadRequest, DownloadStateCallback, Handler)}, this + * {@link #registerStateCallback(DownloadRequest, Executor, DownloadStateCallback)}, this * method will be a no-op. * * If the middleware has no record of the @@ -775,7 +777,7 @@ public class MbmsDownloadSession implements AutoCloseable { * instance of {@link MbmsDownloadSessionCallback}, but callbacks that have already been * enqueued will still be delivered. * - * It is safe to call {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)} to + * It is safe to call {@link #create(Context, Executor, int, MbmsDownloadSessionCallback)} to * obtain another instance of {@link MbmsDownloadSession} immediately after this method * returns. * @@ -831,6 +833,36 @@ public class MbmsDownloadSession implements AutoCloseable { } } + private void checkDownloadRequestDestination(DownloadRequest request) { + File downloadRequestDestination = new File(request.getDestinationUri().getPath()); + if (!downloadRequestDestination.isDirectory()) { + throw new IllegalArgumentException("The destination path must be a directory"); + } + // Check if the request destination is okay to use by attempting to rename an empty + // file to there. + File testFile = new File(MbmsTempFileProvider.getEmbmsTempFileDir(mContext), + DESTINATION_SANITY_CHECK_FILE_NAME); + File testFileDestination = new File(downloadRequestDestination, + DESTINATION_SANITY_CHECK_FILE_NAME); + + try { + if (!testFile.exists()) { + testFile.createNewFile(); + } + if (!testFile.renameTo(testFileDestination)) { + throw new IllegalArgumentException("Destination provided in the download request " + + "is invalid -- files in the temp file directory cannot be directly moved " + + "there."); + } + } catch (IOException e) { + throw new IllegalStateException("Got IOException while testing out the destination: " + + e); + } finally { + testFile.delete(); + testFileDestination.delete(); + } + } + private File getDownloadRequestTokenPath(DownloadRequest request) { File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext, request.getFileServiceId()); diff --git a/telephony/java/android/telephony/MbmsStreamingSession.java b/telephony/java/android/telephony/MbmsStreamingSession.java index fb2ff7b178b1..42c760d4dde8 100644 --- a/telephony/java/android/telephony/MbmsStreamingSession.java +++ b/telephony/java/android/telephony/MbmsStreamingSession.java @@ -24,9 +24,7 @@ import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.telephony.mbms.InternalStreamingSessionCallback; import android.telephony.mbms.InternalStreamingServiceCallback; @@ -42,6 +40,7 @@ import android.util.Log; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -89,14 +88,11 @@ public class MbmsStreamingSession implements AutoCloseable { private int mSubscriptionId = INVALID_SUBSCRIPTION_ID; /** @hide */ - private MbmsStreamingSession(Context context, MbmsStreamingSessionCallback callback, - int subscriptionId, Handler handler) { + private MbmsStreamingSession(Context context, Executor executor, int subscriptionId, + MbmsStreamingSessionCallback callback) { mContext = context; mSubscriptionId = subscriptionId; - if (handler == null) { - handler = new Handler(Looper.getMainLooper()); - } - mInternalCallback = new InternalStreamingSessionCallback(callback, handler); + mInternalCallback = new InternalStreamingSessionCallback(callback, executor); } /** @@ -117,25 +113,25 @@ public class MbmsStreamingSession implements AutoCloseable { * {@link MbmsStreamingSession} that you received before calling this method again. * * @param context The {@link Context} to use. + * @param executor The executor on which you wish to execute callbacks. + * @param subscriptionId The subscription ID to use. * @param callback A callback object on which you wish to receive results of asynchronous * operations. - * @param subscriptionId The subscription ID to use. - * @param handler The handler you wish to receive callbacks on. * @return An instance of {@link MbmsStreamingSession}, or null if an error occurred. */ public static @Nullable MbmsStreamingSession create(@NonNull Context context, - final @NonNull MbmsStreamingSessionCallback callback, int subscriptionId, - @NonNull Handler handler) { + @NonNull Executor executor, int subscriptionId, + final @NonNull MbmsStreamingSessionCallback callback) { if (!sIsInitialized.compareAndSet(false, true)) { throw new IllegalStateException("Cannot create two instances of MbmsStreamingSession"); } - MbmsStreamingSession session = new MbmsStreamingSession(context, callback, - subscriptionId, handler); + MbmsStreamingSession session = new MbmsStreamingSession(context, executor, + subscriptionId, callback); final int result = session.bindAndInitialize(); if (result != MbmsErrors.SUCCESS) { sIsInitialized.set(false); - handler.post(new Runnable() { + executor.execute(new Runnable() { @Override public void run() { callback.onError(result, null); @@ -148,22 +144,22 @@ public class MbmsStreamingSession implements AutoCloseable { /** * Create a new {@link MbmsStreamingSession} using the system default data subscription ID. - * See {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)}. + * See {@link #create(Context, Executor, int, MbmsStreamingSessionCallback)}. */ public static MbmsStreamingSession create(@NonNull Context context, - @NonNull MbmsStreamingSessionCallback callback, @NonNull Handler handler) { - return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler); + @NonNull Executor executor, @NonNull MbmsStreamingSessionCallback callback) { + return create(context, executor, SubscriptionManager.getDefaultSubscriptionId(), callback); } /** * Terminates this instance. Also terminates * any streaming services spawned from this instance as if - * {@link StreamingService#stopStreaming()} had been called on them. After this method returns, + * {@link StreamingService#close()} had been called on them. After this method returns, * no further callbacks originating from the middleware will be enqueued on the provided * instance of {@link MbmsStreamingSessionCallback}, but callbacks that have already been * enqueued will still be delivered. * - * It is safe to call {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)} to + * It is safe to call {@link #create(Context, Executor, int, MbmsStreamingSessionCallback)} to * obtain another instance of {@link MbmsStreamingSession} immediately after this method * returns. * @@ -237,20 +233,20 @@ public class MbmsStreamingSession implements AutoCloseable { * {@link MbmsErrors.StreamingErrors}. * * @param serviceInfo The information about the service to stream. + * @param executor The executor on which you wish to execute callbacks for this stream. * @param callback A callback that'll be called when something about the stream changes. - * @param handler A handler that calls to {@code callback} should be called on. * @return An instance of {@link StreamingService} through which the stream can be controlled. * May be {@code null} if an error occurred. */ public @Nullable StreamingService startStreaming(StreamingServiceInfo serviceInfo, - StreamingServiceCallback callback, @NonNull Handler handler) { + @NonNull Executor executor, StreamingServiceCallback callback) { IMbmsStreamingService streamingService = mService.get(); if (streamingService == null) { throw new IllegalStateException("Middleware not yet bound"); } InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback( - callback, handler); + callback, executor); StreamingService serviceForApp = new StreamingService( mSubscriptionId, streamingService, this, serviceInfo, serviceCallback); diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java index f0d60b68eb94..602c796aa188 100644 --- a/telephony/java/android/telephony/mbms/DownloadRequest.java +++ b/telephony/java/android/telephony/mbms/DownloadRequest.java @@ -27,10 +27,13 @@ import android.util.Log; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.Externalizable; +import java.io.File; import java.io.IOException; +import java.io.ObjectInput; import java.io.ObjectInputStream; +import java.io.ObjectOutput; import java.io.ObjectOutputStream; -import java.io.Serializable; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -54,34 +57,116 @@ public final class DownloadRequest implements Parcelable { public static final int MAX_DESTINATION_URI_SIZE = 50000; /** @hide */ - private static class OpaqueDataContainer implements Serializable { - private final String appIntent; - private final int version; + private static class SerializationDataContainer implements Externalizable { + private String fileServiceId; + private Uri source; + private Uri destination; + private int subscriptionId; + private String appIntent; + private int version; - public OpaqueDataContainer(String appIntent, int version) { - this.appIntent = appIntent; - this.version = version; + public SerializationDataContainer() {} + + SerializationDataContainer(DownloadRequest request) { + fileServiceId = request.fileServiceId; + source = request.sourceUri; + destination = request.destinationUri; + subscriptionId = request.subscriptionId; + appIntent = request.serializedResultIntentForApp; + version = request.version; + } + + @Override + public void writeExternal(ObjectOutput objectOutput) throws IOException { + objectOutput.write(version); + objectOutput.writeUTF(fileServiceId); + objectOutput.writeUTF(source.toString()); + objectOutput.writeUTF(destination.toString()); + objectOutput.write(subscriptionId); + objectOutput.writeUTF(appIntent); + } + + @Override + public void readExternal(ObjectInput objectInput) throws IOException { + version = objectInput.read(); + fileServiceId = objectInput.readUTF(); + source = Uri.parse(objectInput.readUTF()); + destination = Uri.parse(objectInput.readUTF()); + subscriptionId = objectInput.read(); + appIntent = objectInput.readUTF(); + // Do version checks here -- future versions may have other fields. } } public static class Builder { private String fileServiceId; private Uri source; + private Uri destination; private int subscriptionId; private String appIntent; private int version = CURRENT_VERSION; + /** + * Constructs a {@link Builder} from a {@link DownloadRequest} + * @param other The {@link DownloadRequest} from which the data for the {@link Builder} + * should come. + * @return An instance of {@link Builder} pre-populated with data from the provided + * {@link DownloadRequest}. + */ + public static Builder fromDownloadRequest(DownloadRequest other) { + Builder result = new Builder(other.sourceUri, other.destinationUri) + .setServiceId(other.fileServiceId) + .setSubscriptionId(other.subscriptionId); + result.appIntent = other.serializedResultIntentForApp; + // Version of the result is going to be the current version -- as this class gets + // updated, new fields will be set to default values in here. + return result; + } + + /** + * This method constructs a new instance of {@link Builder} based on the serialized data + * passed in. + * @param data A byte array, the contents of which should have been originally obtained + * from {@link DownloadRequest#toByteArray()}. + */ + public static Builder fromSerializedRequest(byte[] data) { + Builder builder; + try { + ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data)); + SerializationDataContainer dataContainer = + (SerializationDataContainer) stream.readObject(); + builder = new Builder(dataContainer.source, dataContainer.destination); + builder.version = dataContainer.version; + builder.appIntent = dataContainer.appIntent; + builder.fileServiceId = dataContainer.fileServiceId; + builder.subscriptionId = dataContainer.subscriptionId; + } catch (IOException e) { + // Really should never happen + Log.e(LOG_TAG, "Got IOException trying to parse opaque data"); + throw new IllegalArgumentException(e); + } catch (ClassNotFoundException e) { + Log.e(LOG_TAG, "Got ClassNotFoundException trying to parse opaque data"); + throw new IllegalArgumentException(e); + } + return builder; + } /** * Builds a new DownloadRequest. * @param sourceUri the source URI for the DownloadRequest to be built. This URI should * never be null. + * @param destinationUri The final location for the file(s) that are to be downloaded. It + * must be on the same filesystem as the temp file directory set via + * {@link android.telephony.MbmsDownloadSession#setTempFileRootDirectory(File)}. + * The provided path must be a directory that exists. An + * {@link IllegalArgumentException} will be thrown otherwise. */ - public Builder(@NonNull Uri sourceUri) { - if (sourceUri == null) { - throw new IllegalArgumentException("Source URI must be non-null."); + public Builder(@NonNull Uri sourceUri, @NonNull Uri destinationUri) { + if (sourceUri == null || destinationUri == null) { + throw new IllegalArgumentException("Source and destination URIs must be non-null."); } source = sourceUri; + destination = destinationUri; } /** @@ -130,68 +215,34 @@ public final class DownloadRequest implements Parcelable { return this; } - /** - * For use by the middleware to set the byte array of opaque data. The opaque data - * includes information about the download request that is used by the client app and the - * manager code, but is irrelevant to the middleware. - * @param data A byte array, the contents of which should have been originally obtained - * from {@link DownloadRequest#getOpaqueData()}. - * @hide - */ - @SystemApi - public Builder setOpaqueData(byte[] data) { - try { - ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data)); - OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject(); - version = dataContainer.version; - appIntent = dataContainer.appIntent; - } catch (IOException e) { - // Really should never happen - Log.e(LOG_TAG, "Got IOException trying to parse opaque data"); - throw new IllegalArgumentException(e); - } catch (ClassNotFoundException e) { - Log.e(LOG_TAG, "Got ClassNotFoundException trying to parse opaque data"); - throw new IllegalArgumentException(e); - } - return this; - } - public DownloadRequest build() { - return new DownloadRequest(fileServiceId, source, subscriptionId, appIntent, version); + return new DownloadRequest(fileServiceId, source, destination, + subscriptionId, appIntent, version); } } private final String fileServiceId; private final Uri sourceUri; + private final Uri destinationUri; private final int subscriptionId; private final String serializedResultIntentForApp; private final int version; private DownloadRequest(String fileServiceId, - Uri source, int sub, + Uri source, Uri destination, int sub, String appIntent, int version) { this.fileServiceId = fileServiceId; sourceUri = source; subscriptionId = sub; + destinationUri = destination; serializedResultIntentForApp = appIntent; this.version = version; } - public static DownloadRequest copy(DownloadRequest other) { - return new DownloadRequest(other); - } - - private DownloadRequest(DownloadRequest dr) { - fileServiceId = dr.fileServiceId; - sourceUri = dr.sourceUri; - subscriptionId = dr.subscriptionId; - serializedResultIntentForApp = dr.serializedResultIntentForApp; - version = dr.version; - } - private DownloadRequest(Parcel in) { fileServiceId = in.readString(); sourceUri = in.readParcelable(getClass().getClassLoader()); + destinationUri = in.readParcelable(getClass().getClassLoader()); subscriptionId = in.readInt(); serializedResultIntentForApp = in.readString(); version = in.readInt(); @@ -204,6 +255,7 @@ public final class DownloadRequest implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeString(fileServiceId); out.writeParcelable(sourceUri, flags); + out.writeParcelable(destinationUri, flags); out.writeInt(subscriptionId); out.writeString(serializedResultIntentForApp); out.writeInt(version); @@ -224,6 +276,13 @@ public final class DownloadRequest implements Parcelable { } /** + * @return The destination {@link Uri} of the downloaded file. + */ + public Uri getDestinationUri() { + return destinationUri; + } + + /** * @return The subscription ID on which to perform MBMS operations. */ public int getSubscriptionId() { @@ -244,19 +303,16 @@ public final class DownloadRequest implements Parcelable { } /** - * For use by the middleware only. The byte array returned from this method should be - * persisted and sent back to the app upon download completion or failure by passing it into - * {@link Builder#setOpaqueData(byte[])}. - * @return A byte array of opaque data to persist. - * @hide + * This method returns a byte array that may be persisted to disk and restored to a + * {@link DownloadRequest}. The instance of {@link DownloadRequest} persisted by this method + * may be recovered via {@link Builder#fromSerializedRequest(byte[])}. + * @return A byte array of data to persist. */ - @SystemApi - public byte[] getOpaqueData() { + public byte[] toByteArray() { try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream); - OpaqueDataContainer container = new OpaqueDataContainer( - serializedResultIntentForApp, version); + SerializationDataContainer container = new SerializationDataContainer(this); stream.writeObject(container); stream.flush(); return byteArrayOutputStream.toByteArray(); @@ -299,15 +355,6 @@ public final class DownloadRequest implements Parcelable { } /** - * @hide - */ - public boolean isMultipartDownload() { - // TODO: figure out what qualifies a request as a multipart download request. - return getSourceUri().getLastPathSegment() != null && - getSourceUri().getLastPathSegment().contains("*"); - } - - /** * Retrieves the hash string that should be used as the filename when storing a token for * this DownloadRequest. * @hide @@ -320,8 +367,9 @@ public final class DownloadRequest implements Parcelable { throw new RuntimeException("Could not get sha256 hash object"); } if (version >= 1) { - // Hash the source URI and the app intent + // Hash the source, destination, and the app intent digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8)); + digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8)); if (serializedResultIntentForApp != null) { digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8)); } @@ -344,12 +392,13 @@ public final class DownloadRequest implements Parcelable { version == request.version && Objects.equals(fileServiceId, request.fileServiceId) && Objects.equals(sourceUri, request.sourceUri) && + Objects.equals(destinationUri, request.destinationUri) && Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp); } @Override public int hashCode() { - return Objects.hash(fileServiceId, sourceUri, + return Objects.hash(fileServiceId, sourceUri, destinationUri, subscriptionId, serializedResultIntentForApp, version); } } diff --git a/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java index a7a5958fff56..c2a79d82f8b6 100644 --- a/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java +++ b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java @@ -16,22 +16,23 @@ package android.telephony.mbms; -import android.os.Handler; +import android.os.Binder; import android.os.RemoteException; import java.util.List; +import java.util.concurrent.Executor; /** @hide */ public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallback.Stub { - private final Handler mHandler; + private final Executor mExecutor; private final MbmsDownloadSessionCallback mAppCallback; private volatile boolean mIsStopped = false; public InternalDownloadSessionCallback(MbmsDownloadSessionCallback appCallback, - Handler handler) { + Executor executor) { mAppCallback = appCallback; - mHandler = handler; + mExecutor = executor; } @Override @@ -40,10 +41,15 @@ public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallbac return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onError(errorCode, message); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onError(errorCode, message); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } @@ -54,10 +60,15 @@ public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallbac return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onFileServicesUpdated(services); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onFileServicesUpdated(services); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } @@ -68,18 +79,19 @@ public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallbac return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onMiddlewareReady(); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onMiddlewareReady(); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } - public Handler getHandler() { - return mHandler; - } - public void stop() { mIsStopped = true; } diff --git a/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java index 8702952cf06b..f30ae27b19b1 100644 --- a/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java +++ b/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java @@ -16,20 +16,22 @@ package android.telephony.mbms; -import android.os.Handler; +import android.os.Binder; import android.os.RemoteException; +import java.util.concurrent.Executor; + /** * @hide */ public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub { - private final Handler mHandler; + private final Executor mExecutor; private final DownloadStateCallback mAppCallback; private volatile boolean mIsStopped = false; - public InternalDownloadStateCallback(DownloadStateCallback appCallback, Handler handler) { + public InternalDownloadStateCallback(DownloadStateCallback appCallback, Executor executor) { mAppCallback = appCallback; - mHandler = handler; + mExecutor = executor; } @Override @@ -40,11 +42,16 @@ public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub { return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onProgressUpdated(request, fileInfo, currentDownloadSize, - fullDownloadSize, currentDecodedSize, fullDecodedSize); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onProgressUpdated(request, fileInfo, currentDownloadSize, + fullDownloadSize, currentDecodedSize, fullDecodedSize); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } @@ -56,10 +63,15 @@ public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub { return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onStateUpdated(request, fileInfo, state); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onStateUpdated(request, fileInfo, state); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } diff --git a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java index eb6579cec471..e9f39ff959cc 100644 --- a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java +++ b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java @@ -16,18 +16,21 @@ package android.telephony.mbms; -import android.os.Handler; +import android.os.Binder; import android.os.RemoteException; +import java.util.concurrent.Executor; + /** @hide */ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.Stub { private final StreamingServiceCallback mAppCallback; - private final Handler mHandler; + private final Executor mExecutor; private volatile boolean mIsStopped = false; - public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, Handler handler) { + public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, + Executor executor) { mAppCallback = appCallback; - mHandler = handler; + mExecutor = executor; } @Override @@ -36,10 +39,15 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback. return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onError(errorCode, message); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onError(errorCode, message); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } @@ -50,10 +58,15 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback. return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onStreamStateUpdated(state, reason); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onStreamStateUpdated(state, reason); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } @@ -64,10 +77,15 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback. return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onMediaDescriptionUpdated(); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onMediaDescriptionUpdated(); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } @@ -78,10 +96,15 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback. return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onBroadcastSignalStrengthUpdated(signalStrength); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onBroadcastSignalStrengthUpdated(signalStrength); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } @@ -92,10 +115,15 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback. return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onStreamMethodUpdated(methodType); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onStreamMethodUpdated(methodType); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } diff --git a/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java index d782d12c00d6..d47f5adbaf91 100644 --- a/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java +++ b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java @@ -16,21 +16,22 @@ package android.telephony.mbms; -import android.os.Handler; +import android.os.Binder; import android.os.RemoteException; import java.util.List; +import java.util.concurrent.Executor; /** @hide */ public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallback.Stub { - private final Handler mHandler; + private final Executor mExecutor; private final MbmsStreamingSessionCallback mAppCallback; private volatile boolean mIsStopped = false; public InternalStreamingSessionCallback(MbmsStreamingSessionCallback appCallback, - Handler handler) { + Executor executor) { mAppCallback = appCallback; - mHandler = handler; + mExecutor = executor; } @Override @@ -39,10 +40,15 @@ public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallb return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onError(errorCode, message); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onError(errorCode, message); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } @@ -54,10 +60,15 @@ public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallb return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onStreamingServicesUpdated(services); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onStreamingServicesUpdated(services); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } @@ -68,18 +79,19 @@ public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallb return; } - mHandler.post(new Runnable() { + mExecutor.execute(new Runnable() { @Override public void run() { - mAppCallback.onMiddlewareReady(); + long token = Binder.clearCallingIdentity(); + try { + mAppCallback.onMiddlewareReady(); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } - public Handler getHandler() { - return mHandler; - } - public void stop() { mIsStopped = true; } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java index 9ef188cfd2b3..b0c00c6284a6 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java @@ -21,8 +21,10 @@ import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.telephony.MbmsDownloadSession; @@ -31,14 +33,11 @@ import android.util.Log; import java.io.File; import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -62,6 +61,8 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { /** @hide */ public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority"; + private static final String EMBMS_INTENT_PERMISSION = "android.permission.SEND_EMBMS_INTENTS"; + /** * Indicates that the requested operation completed without error. * @hide @@ -137,6 +138,8 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { /** @hide */ @Override public void onReceive(Context context, Intent intent) { + verifyPermissionIntegrity(context); + if (!verifyIntentContents(context, intent)) { setResultCode(RESULT_MALFORMED_INTENT); return; @@ -260,20 +263,18 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { FileInfo completedFileInfo = (FileInfo) intent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO); - Path stagingDirectory = FileSystems.getDefault().getPath( - MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath(), - TEMP_FILE_STAGING_LOCATION); + Path appSpecifiedDestination = FileSystems.getDefault().getPath( + request.getDestinationUri().getPath()); - Uri stagedFileLocation; + Uri finalLocation; try { - stagedFileLocation = stageTempFile(finalTempFile, stagingDirectory); + finalLocation = moveToFinalLocation(finalTempFile, appSpecifiedDestination); } catch (IOException e) { Log.w(LOG_TAG, "Failed to move temp file to final destination"); setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR); return; } - intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI, - stagedFileLocation); + intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI, finalLocation); intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo); context.sendBroadcast(intentForApp); @@ -437,19 +438,22 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } /* - * Moves a tempfile located at fromPath to a new location in the staging directory. + * Moves a tempfile located at fromPath to its final home where the app wants it */ - private static Uri stageTempFile(Uri fromPath, Path stagingDirectory) throws IOException { + private static Uri moveToFinalLocation(Uri fromPath, Path appSpecifiedPath) throws IOException { if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) { - Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme"); + Log.w(LOG_TAG, "Downloaded file location uri " + fromPath + + " does not have a file scheme"); return null; } Path fromFile = FileSystems.getDefault().getPath(fromPath.getPath()); - if (!Files.isDirectory(stagingDirectory)) { - Files.createDirectory(stagingDirectory); + if (!Files.isDirectory(appSpecifiedPath)) { + Files.createDirectory(appSpecifiedPath); } - Path result = Files.move(fromFile, stagingDirectory.resolve(fromFile.getFileName())); + // TODO: do we want to support directory trees within the download directory? + Path result = Files.move(fromFile, appSpecifiedPath.resolve(fromFile.getFileName()), + StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); return Uri.fromFile(result.toFile()); } @@ -513,39 +517,29 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { return mMiddlewarePackageNameCache; } - private static boolean manualMove(File src, File dst) { - InputStream in = null; - OutputStream out = null; - try { - if (!dst.exists()) { - dst.createNewFile(); - } - in = new FileInputStream(src); - out = new FileOutputStream(dst); - byte[] buffer = new byte[2048]; - int len; - do { - len = in.read(buffer); - out.write(buffer, 0, len); - } while (len > 0); - } catch (IOException e) { - Log.w(LOG_TAG, "Manual file move failed due to exception " + e); - if (dst.exists()) { - dst.delete(); - } - return false; - } finally { - try { - if (in != null) { - in.close(); - } - if (out != null) { - out.close(); - } - } catch (IOException e) { - Log.w(LOG_TAG, "Error closing streams: " + e); + private void verifyPermissionIntegrity(Context context) { + PackageManager pm = context.getPackageManager(); + Intent queryIntent = new Intent(context, MbmsDownloadReceiver.class); + List<ResolveInfo> infos = pm.queryBroadcastReceivers(queryIntent, 0); + if (infos.size() != 1) { + throw new IllegalStateException("Non-unique download receiver in your app"); + } + ActivityInfo selfInfo = infos.get(0).activityInfo; + if (selfInfo == null) { + throw new IllegalStateException("Queried ResolveInfo does not contain a receiver"); + } + if (MbmsUtils.getOverrideServiceName(context, + MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION) != null) { + // If an override was specified, just make sure that the permission isn't null. + if (selfInfo.permission == null) { + throw new IllegalStateException( + "MbmsDownloadReceiver must require some permission"); } + return; + } + if (!Objects.equals(EMBMS_INTENT_PERMISSION, selfInfo.permission)) { + throw new IllegalStateException("MbmsDownloadReceiver must require the " + + "SEND_EMBMS_INTENTS permission."); } - return true; } } diff --git a/telephony/java/android/telephony/mbms/MbmsErrors.java b/telephony/java/android/telephony/mbms/MbmsErrors.java index 75ca35e2342f..b5fec445d853 100644 --- a/telephony/java/android/telephony/mbms/MbmsErrors.java +++ b/telephony/java/android/telephony/mbms/MbmsErrors.java @@ -108,8 +108,8 @@ public class MbmsErrors { /** * Indicates that the app called - * {@link MbmsStreamingSession#startStreaming( - * StreamingServiceInfo, StreamingServiceCallback, android.os.Handler)} + * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo, + * java.util.concurrent.Executor, StreamingServiceCallback)} * more than once for the same {@link StreamingServiceInfo}. */ public static final int ERROR_DUPLICATE_START_STREAM = 303; diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java index 5c130a09773e..6e0395730aba 100644 --- a/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java +++ b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java @@ -22,11 +22,12 @@ import android.os.Handler; import android.telephony.MbmsStreamingSession; import java.util.List; +import java.util.concurrent.Executor; /** * A callback class that is used to receive information from the middleware on MBMS streaming * services. An instance of this object should be passed into - * {@link MbmsStreamingSession#create(Context, MbmsStreamingSessionCallback, int, Handler)}. + * {@link MbmsStreamingSession#create(Context, Executor, int, MbmsStreamingSessionCallback)}. */ public class MbmsStreamingSessionCallback { /** diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java index b4ad1d77760a..ef317eefb3e3 100644 --- a/telephony/java/android/telephony/mbms/MbmsUtils.java +++ b/telephony/java/android/telephony/mbms/MbmsUtils.java @@ -50,7 +50,7 @@ public class MbmsUtils { return new ComponentName(ci.packageName, ci.name); } - private static ComponentName getOverrideServiceName(Context context, String serviceAction) { + public static ComponentName getOverrideServiceName(Context context, String serviceAction) { String metaDataKey = null; switch (serviceAction) { case MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION: diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java index ec9134a4b855..b6239fe98a99 100644 --- a/telephony/java/android/telephony/mbms/StreamingService.java +++ b/telephony/java/android/telephony/mbms/StreamingService.java @@ -29,11 +29,11 @@ import java.lang.annotation.RetentionPolicy; /** * Class used to represent a single MBMS stream. After a stream has been started with - * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo, - * StreamingServiceCallback, android.os.Handler)}, + * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo, java.util.concurrent.Executor, + * StreamingServiceCallback)}, * this class is used to hold information about the stream and control it. */ -public class StreamingService { +public class StreamingService implements AutoCloseable { private static final String LOG_TAG = "MbmsStreamingService"; /** @@ -41,7 +41,7 @@ public class StreamingService { * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({STATE_STOPPED, STATE_STARTED, STATE_STALLED}) + @IntDef(prefix = { "STATE_" }, value = {STATE_STOPPED, STATE_STARTED, STATE_STALLED}) public @interface StreamingState {} public final static int STATE_STOPPED = 1; public final static int STATE_STARTED = 2; @@ -53,7 +53,8 @@ public class StreamingService { * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({REASON_BY_USER_REQUEST, REASON_END_OF_SESSION, REASON_FREQUENCY_CONFLICT, + @IntDef(prefix = { "REASON_" }, + value = {REASON_BY_USER_REQUEST, REASON_END_OF_SESSION, REASON_FREQUENCY_CONFLICT, REASON_OUT_OF_MEMORY, REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE, REASON_LEFT_MBMS_BROADCAST_AREA, REASON_NONE}) public @interface StreamingStateChangeReason {} @@ -64,9 +65,9 @@ public class StreamingService { public static final int REASON_NONE = 0; /** - * State changed due to a call to {@link #stopStreaming()} or + * State changed due to a call to {@link #close()} or * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo, - * StreamingServiceCallback, android.os.Handler)} + * java.util.concurrent.Executor, StreamingServiceCallback)} */ public static final int REASON_BY_USER_REQUEST = 1; @@ -161,7 +162,8 @@ public class StreamingService { * * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} */ - public void stopStreaming() { + @Override + public void close() { if (mService == null) { throw new IllegalStateException("No streaming service attached"); } |