Merge "EMBMS API tweaks"
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -223,6 +275,13 @@ public final class DownloadRequest implements Parcelable {
|
||||
return sourceUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The destination {@link Uri} of the downloaded file.
|
||||
*/
|
||||
public Uri getDestinationUri() {
|
||||
return destinationUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The subscription ID on which to perform MBMS operations.
|
||||
*/
|
||||
@@ -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();
|
||||
@@ -298,15 +354,6 @@ public final class DownloadRequest implements Parcelable {
|
||||
return MAX_DESTINATION_URI_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user