Merge changes from topic "embms-cherry-pick-3" into oc-mr1-dev

* changes:
  API council suggested edits, part 3
  Apply suggested edits to the file-download API
  Make changes to MBMS Streaming APIs per recommendations
This commit is contained in:
Hall Liu
2017-09-15 17:17:38 +00:00
committed by Android (Google) Code Review
28 changed files with 834 additions and 632 deletions

View File

@@ -502,8 +502,8 @@ LOCAL_SRC_FILES += \
telecomm/java/com/android/internal/telecom/IInCallService.aidl \
telecomm/java/com/android/internal/telecom/ITelecomService.aidl \
telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl \
telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl \
telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl \
telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl \
telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl \
telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl \
telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl \
telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl \

View File

@@ -34,11 +34,11 @@ import android.os.RemoteException;
import android.telephony.mbms.DownloadStateCallback;
import android.telephony.mbms.FileInfo;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.InternalDownloadManagerCallback;
import android.telephony.mbms.InternalDownloadSessionCallback;
import android.telephony.mbms.InternalDownloadStateCallback;
import android.telephony.mbms.MbmsDownloadManagerCallback;
import android.telephony.mbms.MbmsDownloadSessionCallback;
import android.telephony.mbms.MbmsDownloadReceiver;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.MbmsErrors;
import android.telephony.mbms.MbmsTempFileProvider;
import android.telephony.mbms.MbmsUtils;
import android.telephony.mbms.vendor.IMbmsDownloadService;
@@ -48,7 +48,10 @@ import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -58,8 +61,8 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
* This class provides functionality for file download over MBMS.
* @hide
*/
public class MbmsDownloadManager {
private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName();
public class MbmsDownloadSession implements AutoCloseable {
private static final String LOG_TAG = MbmsDownloadSession.class.getSimpleName();
/**
* Service action which must be handled by the middleware implementing the MBMS file download
@@ -95,16 +98,28 @@ public class MbmsDownloadManager {
/**
* {@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. Will always be set to a non-null value if
* 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.
*
* Will always be set to a non-null value if
* {@link #EXTRA_MBMS_DOWNLOAD_RESULT} is set to {@link #RESULT_SUCCESSFUL}.
*/
public static final String EXTRA_MBMS_COMPLETED_FILE_URI =
"android.telephony.extra.MBMS_COMPLETED_FILE_URI";
/**
* Extra containing the {@link DownloadRequest} for which the download result or file
* descriptor request is for. Must not be null.
*/
public static final String EXTRA_MBMS_DOWNLOAD_REQUEST =
"android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
/**
* The default directory name for all MBMS temp files. If you call
* {@link #download(DownloadRequest, DownloadStateCallback, Handler)} without first calling
* {@link #download(DownloadRequest)} without first calling
* {@link #setTempFileRootDirectory(File)}, this directory will be created for you under the
* path returned by {@link Context#getFilesDir()}.
*/
@@ -173,98 +188,88 @@ public class MbmsDownloadManager {
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
}
};
private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
private final InternalDownloadManagerCallback mInternalCallback;
private final InternalDownloadSessionCallback mInternalCallback;
private final Map<DownloadStateCallback, InternalDownloadStateCallback>
mInternalDownloadCallbacks = new HashMap<>();
private MbmsDownloadManager(Context context, MbmsDownloadManagerCallback callback,
private MbmsDownloadSession(Context context, MbmsDownloadSessionCallback callback,
int subscriptionId, Handler handler) {
mContext = context;
mSubscriptionId = subscriptionId;
if (handler == null) {
handler = new Handler(Looper.myLooper());
handler = new Handler(Looper.getMainLooper());
}
mInternalCallback = new InternalDownloadManagerCallback(callback, handler);
mInternalCallback = new InternalDownloadSessionCallback(callback, handler);
}
/**
* Create a new MbmsDownloadManager using the system default data subscription ID and default
* {@link Handler}
* See {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)}
* Create a new {@link MbmsDownloadSession} using the system default data subscription ID.
* See {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)}
*/
public static MbmsDownloadManager create(Context context,
MbmsDownloadManagerCallback callback)
throws MbmsException {
return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null);
}
/**
* Create a new MbmsDownloadManager using the system default data subscription ID.
* See {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)}
*/
public static MbmsDownloadManager create(Context context,
MbmsDownloadManagerCallback callback, Handler handler)
throws MbmsException {
public static MbmsDownloadSession create(@NonNull Context context,
@NonNull MbmsDownloadSessionCallback callback, @NonNull Handler handler) {
return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
}
/**
* Create a new MbmsDownloadManager using the default {@link Handler}
* See {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)}
*/
public static MbmsDownloadManager create(Context context,
MbmsDownloadManagerCallback callback, int subscriptionId)
throws MbmsException {
return create(context, callback, subscriptionId, null);
}
/**
* Create a new MbmsDownloadManager using the given subscription ID.
*
* Note that this call will bind a remote service and that may take a bit. The instance of
* {@link MbmsDownloadManager} that is returned will not be ready for use until
* {@link MbmsDownloadManagerCallback#onMiddlewareReady()} is called on the provided callback.
* If you attempt to use the manager before it is ready, a {@link MbmsException} will be thrown.
* {@link MbmsDownloadSession} that is returned will not be ready for use until
* {@link MbmsDownloadSessionCallback#onMiddlewareReady()} is called on the provided callback.
* If you attempt to use the instance before it is ready, an {@link IllegalStateException}
* will be thrown or an error will be delivered through
* {@link MbmsDownloadSessionCallback#onError(int, String)}.
*
* This also may throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
* This also may throw an {@link IllegalArgumentException}.
*
* You may only have one instance of {@link MbmsDownloadManager} per UID. If you call this
* method while there is an active instance of {@link MbmsDownloadManager} in your process
* (in other words, one that has not had {@link #dispose()} called on it), this method will
* throw an {@link MbmsException}. If you call this method in a different process
* You may only have one instance of {@link MbmsDownloadSession} per UID. If you call this
* method while there is an active instance of {@link MbmsDownloadSession} in your process
* (in other words, one that has not had {@link #close()} called on it), this method will
* throw an {@link IllegalStateException}. If you call this method in a different process
* running under the same UID, an error will be indicated via
* {@link MbmsDownloadManagerCallback#onError(int, String)}.
* {@link MbmsDownloadSessionCallback#onError(int, String)}.
*
* Note that initialization may fail asynchronously. If you wish to try again after you
* receive such an asynchronous error, you must call dispose() on the instance of
* {@link MbmsDownloadManager} that you received before calling this method again.
* receive such an asynchronous error, you must call {@link #close()} on the instance of
* {@link MbmsDownloadSession} that you received before calling this method again.
*
* @param context The instance of {@link Context} to use
* @param listener A callback to get asynchronous error messages and file service updates.
* @param callback A callback to get asynchronous error messages and file service updates.
* @param subscriptionId The data subscription ID to use
* @param handler The {@link Handler} on which callbacks should be enqueued.
* @return A new instance of {@link MbmsDownloadSession}, or null if an error occurred during
* setup.
*/
public static MbmsDownloadManager create(Context context,
MbmsDownloadManagerCallback listener, int subscriptionId, Handler handler)
throws MbmsException {
public static @Nullable MbmsDownloadSession create(@NonNull Context context,
final @NonNull MbmsDownloadSessionCallback callback,
int subscriptionId, @NonNull Handler handler) {
if (!sIsInitialized.compareAndSet(false, true)) {
throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
throw new IllegalStateException("Cannot have two active instances");
}
MbmsDownloadManager mdm =
new MbmsDownloadManager(context, listener, subscriptionId, handler);
try {
mdm.bindAndInitialize();
} catch (MbmsException e) {
MbmsDownloadSession session =
new MbmsDownloadSession(context, callback, subscriptionId, handler);
final int result = session.bindAndInitialize();
if (result != MbmsErrors.SUCCESS) {
sIsInitialized.set(false);
throw e;
handler.post(new Runnable() {
@Override
public void run() {
callback.onError(result, null);
}
});
return null;
}
return mdm;
return session;
}
private void bindAndInitialize() throws MbmsException {
MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,
private int bindAndInitialize() {
return MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,
new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -280,12 +285,12 @@ public class MbmsDownloadManager {
} catch (RuntimeException e) {
Log.e(LOG_TAG, "Runtime exception during initialization");
sendErrorToApp(
MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
e.toString());
sIsInitialized.set(false);
return;
}
if (result != MbmsException.SUCCESS) {
if (result != MbmsErrors.SUCCESS) {
sendErrorToApp(result, "Error returned during initialization");
sIsInitialized.set(false);
return;
@@ -293,7 +298,7 @@ public class MbmsDownloadManager {
try {
downloadService.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
"Middleware lost during initialization");
sIsInitialized.set(false);
return;
@@ -313,39 +318,35 @@ public class MbmsDownloadManager {
* An inspection API to retrieve the list of available
* {@link android.telephony.mbms.FileServiceInfo}s currently being advertised.
* The results are returned asynchronously via a call to
* {@link MbmsDownloadManagerCallback#onFileServicesUpdated(List)}
* {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)}
*
* The serviceClasses argument lets the app filter on types of programming and is opaque data
* negotiated beforehand between the app and the carrier.
* Asynchronous error codes via the {@link MbmsDownloadSessionCallback#onError(int, String)}
* callback may include any of the errors that are not specific to the streaming use-case.
*
* This may throw an {@link MbmsException} containing one of the following errors:
* {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
* {@link MbmsException#ERROR_MIDDLEWARE_LOST}
*
* Asynchronous error codes via the {@link MbmsDownloadManagerCallback#onError(int, String)}
* callback can include any of the errors except:
* {@link MbmsException.StreamingErrors#ERROR_UNABLE_TO_START_SERVICE}
* May throw an {@link IllegalStateException} or {@link IllegalArgumentException}.
*
* @param classList A list of service classes which the app wishes to receive
* {@link MbmsDownloadManagerCallback#onFileServicesUpdated(List)} callbacks
* {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)} callbacks
* about. Subsequent calls to this method will replace this list of service
* classes (i.e. the middleware will no longer send updates for services
* matching classes only in the old list).
* Values in this list should be negotiated with the wireless carrier prior
* to using this API.
*/
public void getFileServices(List<String> classList) throws MbmsException {
public void requestUpdateFileServices(@NonNull List<String> classList) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
throw new IllegalStateException("Middleware not yet bound");
}
try {
int returnCode = downloadService.getFileServices(mSubscriptionId, classList);
if (returnCode != MbmsException.SUCCESS) {
throw new MbmsException(returnCode);
int returnCode = downloadService.requestUpdateFileServices(mSubscriptionId, classList);
if (returnCode != MbmsErrors.SUCCESS) {
sendErrorToApp(returnCode, null);
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService.set(null);
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
}
@@ -357,31 +358,32 @@ public class MbmsDownloadManager {
* local instance of {@link android.content.SharedPreferences} and by the middleware.
*
* If this method is not called at least once before calling
* {@link #download(DownloadRequest, DownloadStateCallback, Handler)}, the framework
* {@link #download(DownloadRequest)}, the framework
* will default to a directory formed by the concatenation of the app's files directory and
* {@link MbmsDownloadManager#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
* {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
*
* Before calling this method, the app must cancel all of its pending
* {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
* an {@link MbmsException} will be thrown with code
* {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
* you will receive an asynchronous error with code
* {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
* provided directory is the same as what has been previously configured.
*
* The {@link File} supplied as a root temp file directory must already exist. If not, an
* {@link IllegalArgumentException} will be thrown.
* {@link IllegalArgumentException} will be thrown. In addition, as an additional sanity
* check, an {@link IllegalArgumentException} will be thrown if you attempt to set the temp
* file root directory to one of your data roots (the value of {@link Context#getDataDir()},
* {@link Context#getFilesDir()}, or {@link Context#getCacheDir()}).
* @param tempFileRootDirectory A directory to place temp files in.
*/
public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory)
throws MbmsException {
public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
throw new IllegalStateException("Middleware not yet bound");
}
if (!tempFileRootDirectory.exists()) {
throw new IllegalArgumentException("Provided directory does not exist");
}
if (!tempFileRootDirectory.isDirectory()) {
throw new IllegalArgumentException("Provided File is not a directory");
try {
validateTempFileRootSanity(tempFileRootDirectory);
} catch (IOException e) {
throw new IllegalStateException("Got IOException checking directory sanity");
}
String filePath;
try {
@@ -392,12 +394,13 @@ public class MbmsDownloadManager {
try {
int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
if (result != MbmsException.SUCCESS) {
throw new MbmsException(result);
if (result != MbmsErrors.SUCCESS) {
sendErrorToApp(result, null);
}
} catch (RemoteException e) {
mService.set(null);
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return;
}
SharedPreferences prefs = mContext.getSharedPreferences(
@@ -405,10 +408,28 @@ public class MbmsDownloadManager {
prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();
}
private void validateTempFileRootSanity(File tempFileRootDirectory) throws IOException {
if (!tempFileRootDirectory.exists()) {
throw new IllegalArgumentException("Provided directory does not exist");
}
if (!tempFileRootDirectory.isDirectory()) {
throw new IllegalArgumentException("Provided File is not a directory");
}
String canonicalTempFilePath = tempFileRootDirectory.getCanonicalPath();
if (mContext.getDataDir().getCanonicalPath().equals(canonicalTempFilePath)) {
throw new IllegalArgumentException("Temp file root cannot be your data dir");
}
if (mContext.getCacheDir().getCanonicalPath().equals(canonicalTempFilePath)) {
throw new IllegalArgumentException("Temp file root cannot be your cache dir");
}
if (mContext.getFilesDir().getCanonicalPath().equals(canonicalTempFilePath)) {
throw new IllegalArgumentException("Temp file root cannot be your files dir");
}
}
/**
* Retrieves the currently configured temp file root directory. Returns the file that was
* configured via {@link #setTempFileRootDirectory(File)} or the default directory
* {@link #download(DownloadRequest, DownloadStateCallback, Handler)} was called without ever
* {@link #download(DownloadRequest)} was called without ever
* setting the temp file root. If neither method has been called since the last time the app's
* shared preferences were reset, returns {@code null}.
*
@@ -426,31 +447,24 @@ public class MbmsDownloadManager {
}
/**
* Requests a download of a file that is available via multicast.
* Requests the download of a file or set of files that the carrier has indicated to be
* available.
*
* May throw an {@link IllegalArgumentException}
*
* If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed,
* this method will create a directory at the default location defined at
* {@link MbmsDownloadManager#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
* {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
* file root directory.
*
* Asynchronous errors through the listener include any of the errors
*
* @param request The request that specifies what should be downloaded
* @param stateCallback Optional listener that will be provided progress updates
* if the app is running. If {@code null}, no callbacks will be
* provided.
* @param handler A handler that calls to {@code stateCallback} should be called on. If
* null, defaults to the handler provided via
* {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)}
* 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.
*/
public void download(DownloadRequest request, @Nullable DownloadStateCallback stateCallback,
Handler handler)
throws MbmsException {
public void download(@NonNull DownloadRequest request) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
throw new IllegalStateException("Middleware not yet bound");
}
// Check to see whether the app's set a temp root dir yet, and set it if not.
@@ -462,67 +476,151 @@ public class MbmsDownloadManager {
tempRootDirectory.mkdirs();
setTempFileRootDirectory(tempRootDirectory);
}
InternalDownloadStateCallback internalCallback = null;
if (stateCallback != null) {
internalCallback = new InternalDownloadStateCallback(stateCallback,
handler == null ? mInternalCallback.getHandler() : handler);
}
checkValidDownloadDestination(request);
writeDownloadRequestToken(request);
try {
downloadService.download(request, internalCallback);
downloadService.download(request);
} catch (RemoteException e) {
mService.set(null);
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
}
/**
* Returns a list of pending {@link DownloadRequest}s that originated from this application.
* A pending request is one that was issued via
* {@link #download(DownloadRequest, DownloadStateCallback, Handler)} but not cancelled through
* {@link #download(DownloadRequest)} but not cancelled through
* {@link #cancelDownload(DownloadRequest)}.
* @return A list, possibly empty, of {@link DownloadRequest}s
*/
public @NonNull List<DownloadRequest> listPendingDownloads() throws MbmsException {
public @NonNull List<DownloadRequest> listPendingDownloads() {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
throw new IllegalStateException("Middleware not yet bound");
}
try {
return downloadService.listPendingDownloads(mSubscriptionId);
} catch (RemoteException e) {
mService.set(null);
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return Collections.emptyList();
}
}
/**
* Registers a callback for a {@link DownloadRequest} previously requested via
* {@link #download(DownloadRequest)}. This callback will only be called as long as both this
* app and the middleware are both running -- if either one stops, no further calls on the
* provided {@link DownloadStateCallback} will be enqueued.
*
* If the middleware is not aware of the specified download request,
* this method will throw an {@link IllegalArgumentException}.
*
* @param request The {@link DownloadRequest} that you want updates on.
* @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.
*/
public void registerStateCallback(@NonNull DownloadRequest request,
@NonNull DownloadStateCallback callback,
@NonNull Handler handler) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new IllegalStateException("Middleware not yet bound");
}
InternalDownloadStateCallback internalCallback =
new InternalDownloadStateCallback(callback, handler);
try {
int result = downloadService.registerStateCallback(request, internalCallback);
if (result != MbmsErrors.SUCCESS) {
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
}
sendErrorToApp(result, null);
return;
}
} catch (RemoteException e) {
mService.set(null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return;
}
mInternalDownloadCallbacks.put(callback, internalCallback);
}
/**
* Un-register a callback previously registered via
* {@link #registerStateCallback(DownloadRequest, DownloadStateCallback, Handler)}. 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.
*
* If the middleware is not aware of the specified download request,
* this method will throw an {@link IllegalArgumentException}.
*
* @param request The {@link DownloadRequest} provided during registration
* @param callback The callback provided during registration.
*/
public void unregisterStateCallback(@NonNull DownloadRequest request,
@NonNull DownloadStateCallback callback) {
try {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new IllegalStateException("Middleware not yet bound");
}
InternalDownloadStateCallback internalCallback =
mInternalDownloadCallbacks.get(callback);
try {
int result = downloadService.unregisterStateCallback(request, internalCallback);
if (result != MbmsErrors.SUCCESS) {
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
}
sendErrorToApp(result, null);
}
} catch (RemoteException e) {
mService.set(null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
} finally {
InternalDownloadStateCallback internalCallback =
mInternalDownloadCallbacks.remove(callback);
if (internalCallback != null) {
internalCallback.stop();
}
}
}
/**
* Attempts to cancel the specified {@link DownloadRequest}.
*
* If the middleware is not aware of the specified download request, an MbmsException will be
* thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
* If the middleware is not aware of the specified download request,
* this method will throw an {@link IllegalArgumentException}.
*
* If this method returns without throwing an exception, you may assume that cancellation
* was successful.
* @param downloadRequest The download request that you wish to cancel.
*/
public void cancelDownload(DownloadRequest downloadRequest) throws MbmsException {
public void cancelDownload(@NonNull DownloadRequest downloadRequest) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
throw new IllegalStateException("Middleware not yet bound");
}
try {
int result = downloadService.cancelDownload(downloadRequest);
if (result != MbmsException.SUCCESS) {
throw new MbmsException(result);
if (result != MbmsErrors.SUCCESS) {
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
}
sendErrorToApp(result, null);
return;
}
} catch (RemoteException e) {
mService.set(null);
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return;
}
deleteDownloadRequestToken(downloadRequest);
}
@@ -530,7 +628,7 @@ public class MbmsDownloadManager {
/**
* Gets information about the status of a file pending download.
*
* If the middleware has not yet been properly initialized or if it has no records of the
* If there was a problem communicating with the middleware or if it has no records of the
* file indicated by {@code fileInfo} being associated with {@code downloadRequest},
* {@link #STATUS_UNKNOWN} will be returned.
*
@@ -539,18 +637,18 @@ public class MbmsDownloadManager {
* @return The status of the download.
*/
@DownloadStatus
public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo)
throws MbmsException {
public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
throw new IllegalStateException("Middleware not yet bound");
}
try {
return downloadService.getDownloadStatus(downloadRequest, fileInfo);
} catch (RemoteException e) {
mService.set(null);
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return STATUS_UNKNOWN;
}
}
@@ -566,30 +664,50 @@ public class MbmsDownloadManager {
* when available.
* This will not interrupt in-progress downloads.
*
* If the middleware is not aware of the specified download request, an MbmsException will be
* thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
* This is distinct from cancelling and re-issuing the download request -- if you cancel and
* re-issue, the middleware will not clear its cache of download state information.
*
* If the middleware is not aware of the specified download request, an
* {@link IllegalArgumentException} will be thrown.
*
* May throw a {@link MbmsException} with error code
* @param downloadRequest The request to re-download files for.
*/
public void resetDownloadKnowledge(DownloadRequest downloadRequest) throws MbmsException {
public void resetDownloadKnowledge(DownloadRequest downloadRequest) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
throw new IllegalStateException("Middleware not yet bound");
}
try {
int result = downloadService.resetDownloadKnowledge(downloadRequest);
if (result != MbmsException.SUCCESS) {
throw new MbmsException(result);
if (result != MbmsErrors.SUCCESS) {
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
}
sendErrorToApp(result, null);
}
} catch (RemoteException e) {
mService.set(null);
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
}
public void dispose() {
/**
* Terminates this instance.
*
* After this method returns,
* no further callbacks originating from the middleware will be enqueued on the provided
* 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
* obtain another instance of {@link MbmsDownloadSession} immediately after this method
* returns.
*
* May throw an {@link IllegalStateException}
*/
@Override
public void close() {
try {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
@@ -603,6 +721,7 @@ public class MbmsDownloadManager {
} finally {
mService.set(null);
sIsInitialized.set(false);
mInternalCallback.stop();
}
}
@@ -645,35 +764,9 @@ public class MbmsDownloadManager {
return new File(tempFileLocation, downloadTokenFileName);
}
/**
* Verifies the following:
* If a request is multi-part,
* 1. Destination Uri must exist and be a directory
* 2. Directory specified must contain no files.
* Otherwise
* 1. The file specified by the destination Uri must not exist.
*/
private void checkValidDownloadDestination(DownloadRequest request) {
File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
if (request.isMultipartDownload()) {
if (!toFile.isDirectory()) {
throw new IllegalArgumentException("Multipart download must specify valid " +
"destination directory.");
}
if (toFile.listFiles().length > 0) {
throw new IllegalArgumentException("Destination directory must be clear of all " +
"files.");
}
} else {
if (toFile.exists()) {
throw new IllegalArgumentException("Destination file must not exist.");
}
}
}
private void sendErrorToApp(int errorCode, String message) {
try {
mInternalCallback.error(errorCode, message);
mInternalCallback.onError(errorCode, message);
} catch (RemoteException e) {
// Ignore, should not happen locally.
}

View File

@@ -16,6 +16,8 @@
package android.telephony;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.content.ComponentName;
@@ -25,18 +27,20 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.telephony.mbms.InternalStreamingManagerCallback;
import android.telephony.mbms.InternalStreamingSessionCallback;
import android.telephony.mbms.InternalStreamingServiceCallback;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.MbmsStreamingManagerCallback;
import android.telephony.mbms.MbmsErrors;
import android.telephony.mbms.MbmsStreamingSessionCallback;
import android.telephony.mbms.MbmsUtils;
import android.telephony.mbms.StreamingService;
import android.telephony.mbms.StreamingServiceCallback;
import android.telephony.mbms.StreamingServiceInfo;
import android.telephony.mbms.vendor.IMbmsStreamingService;
import android.util.ArraySet;
import android.util.Log;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -46,8 +50,8 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
* This class provides functionality for streaming media over MBMS.
* @hide
*/
public class MbmsStreamingManager {
private static final String LOG_TAG = "MbmsStreamingManager";
public class MbmsStreamingSession implements AutoCloseable {
private static final String LOG_TAG = "MbmsStreamingSession";
/**
* Service action which must be handled by the middleware implementing the MBMS streaming
@@ -66,98 +70,98 @@ public class MbmsStreamingManager {
@Override
public void binderDied() {
sIsInitialized.set(false);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
}
};
private InternalStreamingManagerCallback mInternalCallback;
private InternalStreamingSessionCallback mInternalCallback;
private Set<StreamingService> mKnownActiveStreamingServices = new ArraySet<>();
private final Context mContext;
private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
/** @hide */
private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback callback,
private MbmsStreamingSession(Context context, MbmsStreamingSessionCallback callback,
int subscriptionId, Handler handler) {
mContext = context;
mSubscriptionId = subscriptionId;
if (handler == null) {
handler = new Handler(Looper.getMainLooper());
}
mInternalCallback = new InternalStreamingManagerCallback(callback, handler);
mInternalCallback = new InternalStreamingSessionCallback(callback, handler);
}
/**
* Create a new MbmsStreamingManager using the given subscription ID.
* Create a new {@link MbmsStreamingSession} using the given subscription ID.
*
* Note that this call will bind a remote service. You may not call this method on your app's
* main thread. This may throw an {@link MbmsException}, indicating errors that may happen
* during the initialization or binding process.
* main thread.
*
*
* You may only have one instance of {@link MbmsStreamingManager} per UID. If you call this
* method while there is an active instance of {@link MbmsStreamingManager} in your process
* (in other words, one that has not had {@link #dispose()} called on it), this method will
* throw an {@link MbmsException}. If you call this method in a different process
* You may only have one instance of {@link MbmsStreamingSession} per UID. If you call this
* method while there is an active instance of {@link MbmsStreamingSession} in your process
* (in other words, one that has not had {@link #close()} called on it), this method will
* throw an {@link IllegalStateException}. If you call this method in a different process
* running under the same UID, an error will be indicated via
* {@link MbmsStreamingManagerCallback#onError(int, String)}.
* {@link MbmsStreamingSessionCallback#onError(int, String)}.
*
* Note that initialization may fail asynchronously. If you wish to try again after you
* receive such an asynchronous error, you must call dispose() on the instance of
* {@link MbmsStreamingManager} that you received before calling this method again.
* receive such an asynchronous error, you must call {@link #close()} on the instance of
* {@link MbmsStreamingSession} that you received before calling this method again.
*
* @param context The {@link Context} 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. If null, callbacks will be
* processed on the main looper (in other words, the looper returned from
* {@link Looper#getMainLooper()}).
* @param handler The handler you wish to receive callbacks on.
* @return An instance of {@link MbmsStreamingSession}, or null if an error occurred.
*/
public static MbmsStreamingManager create(Context context,
MbmsStreamingManagerCallback callback, int subscriptionId, Handler handler)
throws MbmsException {
public static @Nullable MbmsStreamingSession create(@NonNull Context context,
final @NonNull MbmsStreamingSessionCallback callback, int subscriptionId,
@NonNull Handler handler) {
if (!sIsInitialized.compareAndSet(false, true)) {
throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
throw new IllegalStateException("Cannot create two instances of MbmsStreamingSession");
}
MbmsStreamingManager manager = new MbmsStreamingManager(context, callback,
MbmsStreamingSession session = new MbmsStreamingSession(context, callback,
subscriptionId, handler);
try {
manager.bindAndInitialize();
} catch (MbmsException e) {
final int result = session.bindAndInitialize();
if (result != MbmsErrors.SUCCESS) {
sIsInitialized.set(false);
throw e;
handler.post(new Runnable() {
@Override
public void run() {
callback.onError(result, null);
}
});
return null;
}
return manager;
return session;
}
/**
* Create a new MbmsStreamingManager using the system default data subscription ID.
* See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
* Create a new {@link MbmsStreamingSession} using the system default data subscription ID.
* See {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)}.
*/
public static MbmsStreamingManager create(Context context,
MbmsStreamingManagerCallback callback, Handler handler)
throws MbmsException {
public static MbmsStreamingSession create(@NonNull Context context,
@NonNull MbmsStreamingSessionCallback callback, @NonNull Handler handler) {
return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
}
/**
* Create a new MbmsStreamingManager using the system default data subscription ID and
* default {@link Handler}.
* See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
*/
public static MbmsStreamingManager create(Context context,
MbmsStreamingManagerCallback callback)
throws MbmsException {
return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null);
}
/**
* Terminates this instance, ending calls to the registered listener. Also terminates
* any streaming services spawned from this instance.
* 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,
* 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
* obtain another instance of {@link MbmsStreamingSession} immediately after this method
* returns.
*
* May throw an {@link IllegalStateException}
*/
public void dispose() {
public void close() {
try {
IMbmsStreamingService streamingService = mService.get();
if (streamingService == null) {
@@ -165,47 +169,49 @@ public class MbmsStreamingManager {
return;
}
streamingService.dispose(mSubscriptionId);
for (StreamingService s : mKnownActiveStreamingServices) {
s.getCallback().stop();
}
mKnownActiveStreamingServices.clear();
} catch (RemoteException e) {
// Ignore for now
} finally {
mService.set(null);
sIsInitialized.set(false);
mInternalCallback.stop();
}
}
/**
* An inspection API to retrieve the list of streaming media currently be advertised.
* The results are returned asynchronously through the previously registered callback.
* serviceClasses lets the app filter on types of programming and is opaque data between
* the app and the carrier.
* The results are returned asynchronously via
* {@link MbmsStreamingSessionCallback#onStreamingServicesUpdated(List)} on the callback
* provided upon creation.
*
* Multiple calls replace the list of serviceClasses of interest.
* Multiple calls replace the list of service classes of interest.
*
* This may throw an {@link MbmsException} containing any error in
* {@link android.telephony.mbms.MbmsException.GeneralErrors},
* {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or
* {@link MbmsException#ERROR_MIDDLEWARE_LOST}.
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
*
* May also throw an unchecked {@link IllegalArgumentException} or an
* {@link IllegalStateException}
*
* @param classList A list of streaming service classes that the app would like updates on.
* @param serviceClassList A list of streaming service classes that the app would like updates
* on. The exact names of these classes should be negotiated with the
* wireless carrier separately.
*/
public void getStreamingServices(List<String> classList) throws MbmsException {
public void requestUpdateStreamingServices(List<String> serviceClassList) {
IMbmsStreamingService streamingService = mService.get();
if (streamingService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
throw new IllegalStateException("Middleware not yet bound");
}
try {
int returnCode = streamingService.getStreamingServices(mSubscriptionId, classList);
if (returnCode != MbmsException.SUCCESS) {
throw new MbmsException(returnCode);
int returnCode = streamingService.requestUpdateStreamingServices(
mSubscriptionId, serviceClassList);
if (returnCode != MbmsErrors.SUCCESS) {
sendErrorToApp(returnCode, null);
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService.set(null);
sIsInitialized.set(false);
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
}
@@ -216,56 +222,57 @@ public class MbmsStreamingManager {
* reported via
* {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)}
*
* May throw an
* {@link MbmsException} containing any of the error codes in
* {@link android.telephony.mbms.MbmsException.GeneralErrors},
* {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or
* {@link MbmsException#ERROR_MIDDLEWARE_LOST}.
*
* May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
* Asynchronous errors through the callback include any of the errors in
* {@link android.telephony.mbms.MbmsException.GeneralErrors} or
* {@link android.telephony.mbms.MbmsException.StreamingErrors}.
* {@link MbmsErrors.GeneralErrors} or
* {@link MbmsErrors.StreamingErrors}.
*
* @param serviceInfo The information about the service to 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. If null,
* defaults to the handler provided via
* {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
* @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 StreamingService startStreaming(StreamingServiceInfo serviceInfo,
StreamingServiceCallback callback, Handler handler) throws MbmsException {
public @Nullable StreamingService startStreaming(StreamingServiceInfo serviceInfo,
StreamingServiceCallback callback, @NonNull Handler handler) {
IMbmsStreamingService streamingService = mService.get();
if (streamingService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
throw new IllegalStateException("Middleware not yet bound");
}
InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback(
callback, handler == null ? mInternalCallback.getHandler() : handler);
callback, handler);
StreamingService serviceForApp = new StreamingService(
mSubscriptionId, streamingService, serviceInfo, serviceCallback);
mSubscriptionId, streamingService, this, serviceInfo, serviceCallback);
mKnownActiveStreamingServices.add(serviceForApp);
try {
int returnCode = streamingService.startStreaming(
mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
if (returnCode != MbmsException.SUCCESS) {
throw new MbmsException(returnCode);
if (returnCode != MbmsErrors.SUCCESS) {
sendErrorToApp(returnCode, null);
return null;
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService.set(null);
sIsInitialized.set(false);
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return null;
}
return serviceForApp;
}
private void bindAndInitialize() throws MbmsException {
MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
/** @hide */
public void onStreamingServiceStopped(StreamingService service) {
mKnownActiveStreamingServices.remove(service);
}
private int bindAndInitialize() {
return MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -278,19 +285,19 @@ public class MbmsStreamingManager {
} catch (RemoteException e) {
Log.e(LOG_TAG, "Service died before initialization");
sendErrorToApp(
MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
e.toString());
sIsInitialized.set(false);
return;
} catch (RuntimeException e) {
Log.e(LOG_TAG, "Runtime exception during initialization");
sendErrorToApp(
MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
e.toString());
sIsInitialized.set(false);
return;
}
if (result != MbmsException.SUCCESS) {
if (result != MbmsErrors.SUCCESS) {
sendErrorToApp(result, "Error returned during initialization");
sIsInitialized.set(false);
return;
@@ -298,7 +305,7 @@ public class MbmsStreamingManager {
try {
streamingService.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
"Middleware lost during initialization");
sIsInitialized.set(false);
return;
@@ -316,7 +323,7 @@ public class MbmsStreamingManager {
private void sendErrorToApp(int errorCode, String message) {
try {
mInternalCallback.error(errorCode, message);
mInternalCallback.onError(errorCode, message);
} catch (RemoteException e) {
// Ignore, should not happen locally.
}

View File

@@ -56,12 +56,10 @@ public final class DownloadRequest implements Parcelable {
/** @hide */
private static class OpaqueDataContainer implements Serializable {
private final String destinationUri;
private final String appIntent;
private final int version;
public OpaqueDataContainer(String destinationUri, String appIntent, int version) {
this.destinationUri = destinationUri;
public OpaqueDataContainer(String appIntent, int version) {
this.appIntent = appIntent;
this.version = version;
}
@@ -70,7 +68,6 @@ public final class DownloadRequest implements Parcelable {
public static class Builder {
private String fileServiceId;
private Uri source;
private Uri dest;
private int subscriptionId;
private String appIntent;
private int version = CURRENT_VERSION;
@@ -104,21 +101,6 @@ public final class DownloadRequest implements Parcelable {
return this;
}
/**
* Sets the destination URI for the download request to be built. The middleware should
* not set this directly.
* @param dest A URI obtained from {@link Uri#fromFile(File)}, denoting the requested
* final destination of the download.
*/
public Builder setDest(Uri dest) {
if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) {
throw new IllegalArgumentException("Destination uri must not exceed length " +
MAX_DESTINATION_URI_SIZE);
}
this.dest = dest;
return this;
}
/**
* Set the subscription ID on which the file(s) should be downloaded.
* @param subscriptionId
@@ -160,7 +142,6 @@ public final class DownloadRequest implements Parcelable {
OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject();
version = dataContainer.version;
appIntent = dataContainer.appIntent;
dest = Uri.parse(dataContainer.destinationUri);
} catch (IOException e) {
// Really should never happen
Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
@@ -173,24 +154,21 @@ public final class DownloadRequest implements Parcelable {
}
public DownloadRequest build() {
return new DownloadRequest(fileServiceId, source, dest,
subscriptionId, appIntent, version);
return new DownloadRequest(fileServiceId, source, 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, Uri dest,
int sub, String appIntent, int version) {
Uri source, int sub,
String appIntent, int version) {
this.fileServiceId = fileServiceId;
sourceUri = source;
destinationUri = dest;
subscriptionId = sub;
serializedResultIntentForApp = appIntent;
this.version = version;
@@ -203,7 +181,6 @@ public final class DownloadRequest implements Parcelable {
private DownloadRequest(DownloadRequest dr) {
fileServiceId = dr.fileServiceId;
sourceUri = dr.sourceUri;
destinationUri = dr.destinationUri;
subscriptionId = dr.subscriptionId;
serializedResultIntentForApp = dr.serializedResultIntentForApp;
version = dr.version;
@@ -212,7 +189,6 @@ public final class DownloadRequest implements Parcelable {
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();
@@ -225,7 +201,6 @@ 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);
@@ -245,14 +220,6 @@ public final class DownloadRequest implements Parcelable {
return sourceUri;
}
/**
* For use by the client app only.
* @return The URI of the final destination of the download.
*/
public Uri getDestinationUri() {
return destinationUri;
}
/**
* @return The subscription ID on which to perform MBMS operations.
*/
@@ -286,7 +253,7 @@ public final class DownloadRequest implements Parcelable {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
OpaqueDataContainer container = new OpaqueDataContainer(
destinationUri.toString(), serializedResultIntentForApp, version);
serializedResultIntentForApp, version);
stream.writeObject(container);
stream.flush();
return byteArrayOutputStream.toByteArray();
@@ -352,7 +319,6 @@ public final class DownloadRequest implements Parcelable {
if (version >= 1) {
// Hash the source URI, destination URI, and the app intent
digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
}
// Add updates for future versions here
@@ -373,13 +339,12 @@ 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, destinationUri,
return Objects.hash(fileServiceId, sourceUri,
subscriptionId, serializedResultIntentForApp, version);
}
}

View File

@@ -16,14 +16,12 @@
package android.telephony.mbms;
import android.os.Handler;
import android.telephony.MbmsDownloadManager;
import android.telephony.MbmsDownloadSession;
/**
* A optional listener class used by download clients to track progress. Apps should extend this
* class and pass an instance into
* {@link android.telephony.MbmsDownloadManager#download(
* DownloadRequest, DownloadStateCallback, Handler)}
* {@link MbmsDownloadSession#download(DownloadRequest)}
*
* This is optionally specified when requesting a download and will only be called while the app
* is running.
@@ -59,7 +57,7 @@ public class DownloadStateCallback {
* may not have been able to get a list of them in advance.
* @param state The current state of the download.
*/
public void onStateChanged(DownloadRequest request, FileInfo fileInfo,
@MbmsDownloadManager.DownloadStatus int state) {
public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
@MbmsDownloadSession.DownloadStatus int state) {
}
}

View File

@@ -59,7 +59,7 @@ public final class FileServiceInfo extends ServiceInfo implements Parcelable {
FileServiceInfo(Parcel in) {
super(in);
files = new ArrayList<FileInfo>();
in.readList(files, null);
in.readList(files, FileInfo.class.getClassLoader());
}
@Override

View File

@@ -29,8 +29,9 @@ interface IDownloadStateCallback
* Gives progress callbacks for a given DownloadRequest. Includes a FileInfo
* as the list of files may not have been known at request-time.
*/
void progress(in DownloadRequest request, in FileInfo fileInfo, int currentDownloadSize,
int fullDownloadSize, int currentDecodedSize, int fullDecodedSize);
void onProgressUpdated(in DownloadRequest request, in FileInfo fileInfo,
int currentDownloadSize, int fullDownloadSize,
int currentDecodedSize, int fullDecodedSize);
void state(in DownloadRequest request, in FileInfo fileInfo, int state);
void onStateUpdated(in DownloadRequest request, in FileInfo fileInfo, int state);
}

View File

@@ -24,11 +24,11 @@ import java.util.List;
* The interface the clients top-level file download listener will satisfy.
* @hide
*/
oneway interface IMbmsDownloadManagerCallback
oneway interface IMbmsDownloadSessionCallback
{
void error(int errorCode, String message);
void onError(int errorCode, String message);
void fileServicesUpdated(in List<FileServiceInfo> services);
void onFileServicesUpdated(in List<FileServiceInfo> services);
void middlewareReady();
void onMiddlewareReady();
}

View File

@@ -24,11 +24,11 @@ import java.util.List;
* The interface the clients top-level streaming listener will satisfy.
* @hide
*/
oneway interface IMbmsStreamingManagerCallback
oneway interface IMbmsStreamingSessionCallback
{
void error(int errorCode, String message);
void onError(int errorCode, String message);
void streamingServicesUpdated(in List<StreamingServiceInfo> services);
void onStreamingServicesUpdated(in List<StreamingServiceInfo> services);
void middlewareReady();
void onMiddlewareReady();
}

View File

@@ -20,9 +20,9 @@ package android.telephony.mbms;
* @hide
*/
oneway interface IStreamingServiceCallback {
void error(int errorCode, String message);
void streamStateUpdated(int state, int reason);
void mediaDescriptionUpdated();
void broadcastSignalStrengthUpdated(int signalStrength);
void streamMethodUpdated(int methodType);
void onError(int errorCode, String message);
void onStreamStateUpdated(int state, int reason);
void onMediaDescriptionUpdated();
void onBroadcastSignalStrengthUpdated(int signalStrength);
void onStreamMethodUpdated(int methodType);
}

View File

@@ -22,19 +22,24 @@ import android.os.RemoteException;
import java.util.List;
/** @hide */
public class InternalDownloadManagerCallback extends IMbmsDownloadManagerCallback.Stub {
public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallback.Stub {
private final Handler mHandler;
private final MbmsDownloadManagerCallback mAppCallback;
private final MbmsDownloadSessionCallback mAppCallback;
private volatile boolean mIsStopped = false;
public InternalDownloadManagerCallback(MbmsDownloadManagerCallback appCallback,
public InternalDownloadSessionCallback(MbmsDownloadSessionCallback appCallback,
Handler handler) {
mAppCallback = appCallback;
mHandler = handler;
}
@Override
public void error(final int errorCode, final String message) throws RemoteException {
public void onError(final int errorCode, final String message) throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -44,7 +49,11 @@ public class InternalDownloadManagerCallback extends IMbmsDownloadManagerCallbac
}
@Override
public void fileServicesUpdated(final List<FileServiceInfo> services) throws RemoteException {
public void onFileServicesUpdated(final List<FileServiceInfo> services) throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -54,7 +63,11 @@ public class InternalDownloadManagerCallback extends IMbmsDownloadManagerCallbac
}
@Override
public void middlewareReady() throws RemoteException {
public void onMiddlewareReady() throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -66,4 +79,8 @@ public class InternalDownloadManagerCallback extends IMbmsDownloadManagerCallbac
public Handler getHandler() {
return mHandler;
}
public void stop() {
mIsStopped = true;
}
}

View File

@@ -25,6 +25,7 @@ import android.os.RemoteException;
public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub {
private final Handler mHandler;
private final DownloadStateCallback mAppCallback;
private volatile boolean mIsStopped = false;
public InternalDownloadStateCallback(DownloadStateCallback appCallback, Handler handler) {
mAppCallback = appCallback;
@@ -32,9 +33,13 @@ public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub {
}
@Override
public void progress(final DownloadRequest request, final FileInfo fileInfo,
public void onProgressUpdated(final DownloadRequest request, final FileInfo fileInfo,
final int currentDownloadSize, final int fullDownloadSize, final int currentDecodedSize,
final int fullDecodedSize) throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -45,13 +50,21 @@ public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub {
}
@Override
public void state(final DownloadRequest request, final FileInfo fileInfo, final int state)
throws RemoteException {
public void onStateUpdated(final DownloadRequest request, final FileInfo fileInfo,
final int state) throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
mAppCallback.onStateChanged(request, fileInfo, state);
mAppCallback.onStateUpdated(request, fileInfo, state);
}
});
}
public void stop() {
mIsStopped = true;
}
}

View File

@@ -23,6 +23,7 @@ import android.os.RemoteException;
public class InternalStreamingServiceCallback extends IStreamingServiceCallback.Stub {
private final StreamingServiceCallback mAppCallback;
private final Handler mHandler;
private volatile boolean mIsStopped = false;
public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, Handler handler) {
mAppCallback = appCallback;
@@ -30,7 +31,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
@Override
public void error(int errorCode, String message) throws RemoteException {
public void onError(final int errorCode, final String message) throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -40,7 +45,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
@Override
public void streamStateUpdated(int state, int reason) throws RemoteException {
public void onStreamStateUpdated(final int state, final int reason) throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -50,7 +59,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
@Override
public void mediaDescriptionUpdated() throws RemoteException {
public void onMediaDescriptionUpdated() throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -60,7 +73,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
@Override
public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
public void onBroadcastSignalStrengthUpdated(final int signalStrength) throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -70,7 +87,11 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
@Override
public void streamMethodUpdated(int methodType) throws RemoteException {
public void onStreamMethodUpdated(final int methodType) throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -78,4 +99,8 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
}
});
}
public void stop() {
mIsStopped = true;
}
}

View File

@@ -18,25 +18,27 @@ package android.telephony.mbms;
import android.os.Handler;
import android.os.RemoteException;
import android.telephony.mbms.IMbmsStreamingManagerCallback;
import android.telephony.mbms.MbmsStreamingManagerCallback;
import android.telephony.mbms.StreamingServiceInfo;
import java.util.List;
/** @hide */
public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallback.Stub {
private final Handler mHandler;
private final MbmsStreamingManagerCallback mAppCallback;
private final MbmsStreamingSessionCallback mAppCallback;
private volatile boolean mIsStopped = false;
public InternalStreamingManagerCallback(MbmsStreamingManagerCallback appCallback,
public InternalStreamingSessionCallback(MbmsStreamingSessionCallback appCallback,
Handler handler) {
mAppCallback = appCallback;
mHandler = handler;
}
@Override
public void error(int errorCode, String message) throws RemoteException {
public void onError(final int errorCode, final String message) throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -46,8 +48,12 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb
}
@Override
public void streamingServicesUpdated(List<StreamingServiceInfo> services)
public void onStreamingServicesUpdated(final List<StreamingServiceInfo> services)
throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -57,7 +63,11 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb
}
@Override
public void middlewareReady() throws RemoteException {
public void onMiddlewareReady() throws RemoteException {
if (mIsStopped) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -69,4 +79,8 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb
public Handler getHandler() {
return mHandler;
}
public void stop() {
mIsStopped = true;
}
}

View File

@@ -25,7 +25,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.MbmsDownloadManager;
import android.telephony.MbmsDownloadSession;
import android.telephony.mbms.vendor.VendorUtils;
import android.util.Log;
@@ -36,8 +36,10 @@ 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.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@@ -115,8 +117,19 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
//@SystemApi
public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5;
/**
* Indicates that the manager was unable to notify the app of the completed download.
* This is a fatal result code and no result extras should be expected.
* @hide
*/
@SystemApi
public static final int RESULT_APP_NOTIFICATION_ERROR = 6;
private static final String LOG_TAG = "MbmsDownloadReceiver";
private static final String TEMP_FILE_SUFFIX = ".embms.temp";
private static final String TEMP_FILE_STAGING_LOCATION = "staged_completed_files";
private static final int MAX_TEMP_FILE_RETRIES = 5;
private String mFileProviderAuthorityCache = null;
@@ -149,11 +162,11 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
private boolean verifyIntentContents(Context context, Intent intent) {
if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_MBMS_DOWNLOAD_RESULT)) {
if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT)) {
Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
return false;
}
if (!intent.hasExtra(VendorUtils.EXTRA_REQUEST)) {
if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
return false;
}
@@ -161,7 +174,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
return false;
}
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_MBMS_FILE_INFO)) {
if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO)) {
Log.w(LOG_TAG, "Download result did not include the associated file info. " +
"Ignoring.");
return false;
@@ -171,7 +184,8 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
"temp file. Ignoring.");
return false;
}
DownloadRequest request = intent.getParcelableExtra(VendorUtils.EXTRA_REQUEST);
DownloadRequest request = intent.getParcelableExtra(
MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
File expectedTokenFile = new File(
MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()),
@@ -211,20 +225,25 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
private void moveDownloadedFile(Context context, Intent intent) {
DownloadRequest request = intent.getParcelableExtra(VendorUtils.EXTRA_REQUEST);
DownloadRequest request = intent.getParcelableExtra(
MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
Intent intentForApp = request.getIntentForApp();
if (intentForApp == null) {
Log.i(LOG_TAG, "Malformed app notification intent");
setResultCode(RESULT_APP_NOTIFICATION_ERROR);
return;
}
int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_MBMS_DOWNLOAD_RESULT,
MbmsDownloadManager.RESULT_CANCELLED);
intentForApp.putExtra(MbmsDownloadManager.EXTRA_MBMS_DOWNLOAD_RESULT, result);
int result = intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
MbmsDownloadSession.RESULT_CANCELLED);
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
if (result != MbmsDownloadManager.RESULT_SUCCESSFUL) {
if (result != MbmsDownloadSession.RESULT_SUCCESSFUL) {
Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");
context.sendBroadcast(intentForApp);
return;
}
Uri destinationUri = request.getDestinationUri();
Uri finalTempFile = intent.getParcelableExtra(VendorUtils.EXTRA_FINAL_URI);
if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) {
Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
@@ -233,24 +252,31 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
FileInfo completedFileInfo =
(FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_MBMS_FILE_INFO);
String relativePath = calculateDestinationFileRelativePath(request, completedFileInfo);
(FileInfo) intent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO);
Path stagingDirectory = FileSystems.getDefault().getPath(
MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath(),
TEMP_FILE_STAGING_LOCATION);
Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath);
if (finalFileLocation == null) {
Uri stagedFileLocation;
try {
stagedFileLocation = stageTempFile(finalTempFile, stagingDirectory);
} catch (IOException e) {
Log.w(LOG_TAG, "Failed to move temp file to final destination");
setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
return;
}
intentForApp.putExtra(MbmsDownloadManager.EXTRA_MBMS_COMPLETED_FILE_URI, finalFileLocation);
intentForApp.putExtra(MbmsDownloadManager.EXTRA_MBMS_FILE_INFO, completedFileInfo);
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI,
stagedFileLocation);
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo);
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
context.sendBroadcast(intentForApp);
setResultCode(RESULT_OK);
}
private void cleanupPostMove(Context context, Intent intent) {
DownloadRequest request = intent.getParcelableExtra(VendorUtils.EXTRA_REQUEST);
DownloadRequest request = intent.getParcelableExtra(
MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
if (request == null) {
Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
return;
@@ -404,63 +430,22 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
}
private static String calculateDestinationFileRelativePath(DownloadRequest request,
FileInfo info) {
List<String> filePathComponents = info.getUri().getPathSegments();
List<String> requestPathComponents = request.getSourceUri().getPathSegments();
Iterator<String> filePathIter = filePathComponents.iterator();
Iterator<String> requestPathIter = requestPathComponents.iterator();
StringBuilder pathBuilder = new StringBuilder();
// Iterate through the segments of the carrier's URI to the file, along with the segments
// of the source URI specified in the download request. The relative path is calculated
// as the tail of the file's URI that does not match the path segments in the source URI.
while (filePathIter.hasNext()) {
String currFilePathComponent = filePathIter.next();
if (requestPathIter.hasNext()) {
String requestFilePathComponent = requestPathIter.next();
if (requestFilePathComponent.equals(currFilePathComponent)) {
continue;
}
}
pathBuilder.append(currFilePathComponent);
pathBuilder.append('/');
}
// remove the trailing slash
if (pathBuilder.length() > 0) {
pathBuilder.deleteCharAt(pathBuilder.length() - 1);
}
return pathBuilder.toString();
}
/*
* Moves a tempfile located at fromPath to a new location at toPath. If
* toPath is a directory, the destination file will be located at relativePath
* underneath toPath.
* Moves a tempfile located at fromPath to a new location in the staging directory.
*/
private static Uri moveTempFile(Uri fromPath, Uri toPath, String relativePath) {
private static Uri stageTempFile(Uri fromPath, Path stagingDirectory) throws IOException {
if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) {
Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme");
return null;
}
if (!ContentResolver.SCHEME_FILE.equals(toPath.getScheme())) {
Log.w(LOG_TAG, "Moving destination uri " + toPath + " does not have a file scheme");
return null;
}
File fromFile = new File(fromPath.getSchemeSpecificPart());
File toFile = new File(toPath.getSchemeSpecificPart());
if (toFile.isDirectory()) {
toFile = new File(toFile, relativePath);
Path fromFile = FileSystems.getDefault().getPath(fromPath.getPath());
if (!Files.isDirectory(stagingDirectory)) {
Files.createDirectory(stagingDirectory);
}
toFile.getParentFile().mkdirs();
Path result = Files.move(fromFile, stagingDirectory.resolve(fromFile.getFileName()));
if (fromFile.renameTo(toFile)) {
return Uri.fromFile(toFile);
} else if (manualMove(fromFile, toFile)) {
return Uri.fromFile(toFile);
}
return null;
return Uri.fromFile(result.toFile());
}
private static boolean verifyTempFilePath(Context context, String serviceId,
@@ -512,7 +497,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
private String getMiddlewarePackageCached(Context context) {
if (mMiddlewarePackageNameCache == null) {
mMiddlewarePackageNameCache = MbmsUtils.getMiddlewareServiceInfo(context,
MbmsDownloadManager.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
}
return mMiddlewarePackageNameCache;
}

View File

@@ -16,7 +16,7 @@
package android.telephony.mbms;
import android.telephony.MbmsDownloadManager;
import android.telephony.MbmsDownloadSession;
import java.util.List;
@@ -25,11 +25,11 @@ import java.util.List;
* cell-broadcast.
* @hide
*/
public class MbmsDownloadManagerCallback {
public class MbmsDownloadSessionCallback {
/**
* Indicates that the middleware has encountered an asynchronous error.
* @param errorCode Any error code listed in {@link MbmsException}
* @param errorCode Any error code listed in {@link MbmsErrors}
* @param message A message, intended for debugging purposes, describing the error in further
* detail.
*/
@@ -42,8 +42,9 @@ public class MbmsDownloadManagerCallback {
*
* This will only be called after the application has requested a list of file services and
* specified a service class list of interest via
* {@link MbmsDownloadManager#getFileServices(List)}. If there are subsequent calls to
* {@link MbmsDownloadManager#getFileServices(List)}, this method may not be called again if
* {@link MbmsDownloadSession#requestUpdateFileServices(List)}. If there are subsequent calls to
* {@link MbmsDownloadSession#requestUpdateFileServices(List)},
* this method may not be called again if
* the list of service classes would remain the same.
*
* @param services The most recently updated list of available file services.
@@ -56,9 +57,9 @@ public class MbmsDownloadManagerCallback {
* Called to indicate that the middleware has been initialized and is ready.
*
* Before this method is called, calling any method on an instance of
* {@link android.telephony.MbmsDownloadManager} will result in an {@link MbmsException}
* being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
* or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
* {@link MbmsDownloadSession} will result in an {@link IllegalStateException}
* being thrown or {@link #onError(int, String)} being called with error code
* {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
*/
public void onMiddlewareReady() {
// default implementation empty

View File

@@ -16,8 +16,10 @@
package android.telephony.mbms;
import android.telephony.MbmsStreamingSession;
/** @hide */
public class MbmsException extends Exception {
public class MbmsErrors {
/** Indicates that the operation was successful. */
public static final int SUCCESS = 0;
@@ -31,8 +33,8 @@ public class MbmsException extends Exception {
/**
* Indicates that the app attempted to perform an operation on an instance of
* TODO link android.telephony.MbmsDownloadManager or
* {@link android.telephony.MbmsStreamingManager} without being bound to the middleware.
* {@link android.telephony.MbmsDownloadSession} or
* {@link MbmsStreamingSession} without being bound to the middleware.
*/
public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2;
@@ -47,8 +49,7 @@ public class MbmsException extends Exception {
private InitializationErrors() {}
/**
* Indicates that the app tried to create more than one instance each of
* {@link android.telephony.MbmsStreamingManager} or
* TODO link android.telephony.MbmsDownloadManager
* {@link MbmsStreamingSession} or {@link android.telephony.MbmsDownloadSession}.
*/
public static final int ERROR_DUPLICATE_INITIALIZE = 101;
/** Indicates that the app is not authorized to access media via MBMS.*/
@@ -65,8 +66,8 @@ public class MbmsException extends Exception {
private GeneralErrors() {}
/**
* Indicates that the app attempted to perform an operation before receiving notification
* that the middleware is ready via {@link MbmsStreamingManagerCallback#onMiddlewareReady()}
* or TODO: link MbmsDownloadManagerCallback#middlewareReady
* that the middleware is ready via {@link MbmsStreamingSessionCallback#onMiddlewareReady()}
* or {@link MbmsDownloadSessionCallback#onMiddlewareReady()}.
*/
public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201;
/**
@@ -108,7 +109,7 @@ public class MbmsException extends Exception {
/**
* Indicates that the app called
* {@link android.telephony.MbmsStreamingManager#startStreaming(
* {@link MbmsStreamingSession#startStreaming(
* StreamingServiceInfo, StreamingServiceCallback, android.os.Handler)}
* more than once for the same {@link StreamingServiceInfo}.
*/
@@ -130,15 +131,5 @@ public class MbmsException extends Exception {
public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 402;
}
private final int mErrorCode;
/** @hide */
public MbmsException(int errorCode) {
super();
mErrorCode = errorCode;
}
public int getErrorCode() {
return mErrorCode;
}
private MbmsErrors() {}
}

View File

@@ -16,26 +16,27 @@
package android.telephony.mbms;
import android.annotation.Nullable;
import android.content.Context;
import android.os.RemoteException;
import android.telephony.MbmsStreamingManager;
import android.os.Handler;
import android.telephony.MbmsStreamingSession;
import java.util.List;
/**
* 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 android.telephony.MbmsStreamingManager#create(Context, MbmsStreamingManagerCallback)}.
* {@link MbmsStreamingSession#create(Context, MbmsStreamingSessionCallback, int, Handler)}.
* @hide
*/
public class MbmsStreamingManagerCallback {
public class MbmsStreamingSessionCallback {
/**
* Called by the middleware when it has detected an error condition. The possible error codes
* are listed in {@link MbmsException}.
* are listed in {@link MbmsErrors}.
* @param errorCode The error code.
* @param message A human-readable message generated by the middleware for debugging purposes.
*/
public void onError(int errorCode, String message) {
public void onError(int errorCode, @Nullable String message) {
// default implementation empty
}
@@ -48,8 +49,7 @@ public class MbmsStreamingManagerCallback {
* call with the same service class list would return different
* results.
*
* @param services a List of StreamingServiceInfos
*
* @param services The list of available services.
*/
public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
// default implementation empty
@@ -59,9 +59,9 @@ public class MbmsStreamingManagerCallback {
* Called to indicate that the middleware has been initialized and is ready.
*
* Before this method is called, calling any method on an instance of
* {@link android.telephony.MbmsStreamingManager} will result in an {@link MbmsException}
* being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
* or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
* {@link MbmsStreamingSession} will result in an {@link IllegalStateException} or an error
* delivered via {@link #onError(int, String)} with error code
* {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}.
*/
public void onMiddlewareReady() {
// default implementation empty

View File

@@ -27,7 +27,7 @@ import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.telephony.MbmsDownloadManager;
import android.telephony.MbmsDownloadSession;
import java.io.File;
import java.io.FileNotFoundException;
@@ -181,7 +181,7 @@ public class MbmsTempFileProvider extends ContentProvider {
return new File(storedTempFileRoot).getCanonicalFile();
} else {
return new File(context.getFilesDir(),
MbmsDownloadManager.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY).getCanonicalFile();
MbmsDownloadSession.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY).getCanonicalFile();
}
} catch (IOException e) {
throw new RuntimeException("Unable to canonicalize temp file root path " + e);

View File

@@ -68,19 +68,20 @@ public class MbmsUtils {
return downloadServices.get(0).serviceInfo;
}
public static void startBinding(Context context, String serviceAction,
ServiceConnection serviceConnection) throws MbmsException {
public static int startBinding(Context context, String serviceAction,
ServiceConnection serviceConnection) {
Intent bindIntent = new Intent();
ServiceInfo mbmsServiceInfo =
MbmsUtils.getMiddlewareServiceInfo(context, serviceAction);
if (mbmsServiceInfo == null) {
throw new MbmsException(MbmsException.ERROR_NO_UNIQUE_MIDDLEWARE);
return MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE;
}
bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo));
context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
return MbmsErrors.SUCCESS;
}
/**

View File

@@ -16,6 +16,8 @@
package android.telephony.mbms;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -26,12 +28,13 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
/**
* Describes a cell-broadcast service. This class should not be instantiated directly -- use
* {@link StreamingServiceInfo} or TODO link FileServiceInfo
* {@link StreamingServiceInfo} or {@link FileServiceInfo}
* @hide
*/
public class ServiceInfo {
@@ -59,6 +62,13 @@ public class ServiceInfo {
if (newLocales.size() > MAP_LIMIT) {
throw new RuntimeException("bad locales length " + newLocales.size());
}
for (Locale l : newLocales) {
if (!newNames.containsKey(l)) {
throw new IllegalArgumentException("A name must be provided for each locale");
}
}
names = new HashMap(newNames.size());
names.putAll(newNames);
className = newClassName;
@@ -115,16 +125,25 @@ public class ServiceInfo {
}
/**
* User displayable names listed by language. Do not modify the map returned from this method.
* Get the user-displayable name for this cell-broadcast service corresponding to the
* provided {@link Locale}.
* @param locale The {@link Locale} in which you want the name of the service. This must be a
* value from the list returned by {@link #getLocales()} -- an
* {@link java.util.NoSuchElementException} may be thrown otherwise.
* @return The {@link CharSequence} providing the name of the service in the given
* {@link Locale}
*/
public Map<Locale, String> getNames() {
return names;
public @NonNull CharSequence getNameForLocale(@NonNull Locale locale) {
if (!names.containsKey(locale)) {
throw new NoSuchElementException("Locale not supported");
}
return names.get(locale);
}
/**
* The class name for this service - used to categorize and filter
*/
public String getClassName() {
public String getServiceClassName() {
return className;
}

View File

@@ -17,8 +17,10 @@
package android.telephony.mbms;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.net.Uri;
import android.os.RemoteException;
import android.telephony.MbmsStreamingSession;
import android.telephony.mbms.vendor.IMbmsStreamingService;
import android.util.Log;
@@ -27,7 +29,7 @@ import java.lang.annotation.RetentionPolicy;
/**
* Class used to represent a single MBMS stream. After a stream has been started with
* {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
* {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
* StreamingServiceCallback, android.os.Handler)},
* this class is used to hold information about the stream and control it.
* @hide
@@ -64,7 +66,7 @@ public class StreamingService {
/**
* State changed due to a call to {@link #stopStreaming()} or
* {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
* {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
* StreamingServiceCallback, android.os.Handler)}
*/
public static final int REASON_BY_USER_REQUEST = 1;
@@ -102,6 +104,7 @@ public class StreamingService {
public final static int UNICAST_METHOD = 2;
private final int mSubscriptionId;
private final MbmsStreamingSession mParentSession;
private final StreamingServiceInfo mServiceInfo;
private final InternalStreamingServiceCallback mCallback;
@@ -112,25 +115,25 @@ public class StreamingService {
*/
public StreamingService(int subscriptionId,
IMbmsStreamingService service,
MbmsStreamingSession session,
StreamingServiceInfo streamingServiceInfo,
InternalStreamingServiceCallback callback) {
mSubscriptionId = subscriptionId;
mParentSession = session;
mService = service;
mServiceInfo = streamingServiceInfo;
mCallback = callback;
}
/**
* Retreive the Uri used to play this stream.
* Retrieve the Uri used to play this stream.
*
* This may throw a {@link MbmsException} with the error code
* {@link MbmsException#ERROR_MIDDLEWARE_LOST}
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
*
* May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
* @return The {@link Uri} to pass to the streaming client.
* @return The {@link Uri} to pass to the streaming client, or {@code null} if an error
* occurred.
*/
public Uri getPlaybackUri() throws MbmsException {
public @Nullable Uri getPlaybackUri() {
if (mService == null) {
throw new IllegalStateException("No streaming service attached");
}
@@ -140,25 +143,26 @@ public class StreamingService {
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
mParentSession.onStreamingServiceStopped(this);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return null;
}
}
/**
* Retreive the info for this StreamingService.
* Retrieve the {@link StreamingServiceInfo} corresponding to this stream.
*/
public StreamingServiceInfo getInfo() {
return mServiceInfo;
}
/**
* Stop streaming this service.
* This may throw a {@link MbmsException} with the error code
* {@link MbmsException#ERROR_MIDDLEWARE_LOST}
* Stop streaming this service. Further operations on this object will fail with an
* {@link IllegalStateException}.
*
* May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*/
public void stopStreaming() throws MbmsException {
public void stopStreaming() {
if (mService == null) {
throw new IllegalStateException("No streaming service attached");
}
@@ -168,32 +172,22 @@ public class StreamingService {
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
} finally {
mParentSession.onStreamingServiceStopped(this);
}
}
/**
* Disposes of this stream. Further operations on this object will fail with an
* {@link IllegalStateException}.
*
* This may throw a {@link MbmsException} with the error code
* {@link MbmsException#ERROR_MIDDLEWARE_LOST}
* May also throw an {@link IllegalStateException}
*/
public void dispose() throws MbmsException {
if (mService == null) {
throw new IllegalStateException("No streaming service attached");
}
/** @hide */
public InternalStreamingServiceCallback getCallback() {
return mCallback;
}
private void sendErrorToApp(int errorCode, String message) {
try {
mService.disposeStream(mSubscriptionId, mServiceInfo.getServiceId());
mCallback.onError(errorCode, message);
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
} catch (IllegalArgumentException e) {
throw new IllegalStateException("StreamingService state inconsistent with middleware");
} finally {
mService = null;
// Ignore, should not happen locally.
}
}
}

View File

@@ -16,6 +16,8 @@
package android.telephony.mbms;
import android.annotation.Nullable;
/**
* A callback class for use when the application is actively streaming content. The middleware
* will provide updates on the status of the stream via this callback.
@@ -34,11 +36,11 @@ public class StreamingServiceCallback {
/**
* Called by the middleware when it has detected an error condition in this stream. The
* possible error codes are listed in {@link MbmsException}.
* possible error codes are listed in {@link MbmsErrors}.
* @param errorCode The error code.
* @param message A human-readable message generated by the middleware for debugging purposes.
*/
public void onError(int errorCode, String message) {
public void onError(int errorCode, @Nullable String message) {
// default implementation empty
}

View File

@@ -20,7 +20,7 @@ import android.app.PendingIntent;
import android.net.Uri;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.FileInfo;
import android.telephony.mbms.IMbmsDownloadManagerCallback;
import android.telephony.mbms.IMbmsDownloadSessionCallback;
import android.telephony.mbms.IDownloadStateCallback;
/**
@@ -28,13 +28,18 @@ import android.telephony.mbms.IDownloadStateCallback;
*/
interface IMbmsDownloadService
{
int initialize(int subId, IMbmsDownloadManagerCallback listener);
int initialize(int subId, IMbmsDownloadSessionCallback listener);
int getFileServices(int subId, in List<String> serviceClasses);
int requestUpdateFileServices(int subId, in List<String> serviceClasses);
int setTempFileRootDirectory(int subId, String rootDirectoryPath);
int download(in DownloadRequest downloadRequest, IDownloadStateCallback listener);
int download(in DownloadRequest downloadRequest);
int registerStateCallback(in DownloadRequest downloadRequest, IDownloadStateCallback listener);
int unregisterStateCallback(in DownloadRequest downloadRequest,
IDownloadStateCallback listener);
List<DownloadRequest> listPendingDownloads(int subscriptionId);

View File

@@ -17,7 +17,7 @@
package android.telephony.mbms.vendor;
import android.net.Uri;
import android.telephony.mbms.IMbmsStreamingManagerCallback;
import android.telephony.mbms.IMbmsStreamingSessionCallback;
import android.telephony.mbms.IStreamingServiceCallback;
import android.telephony.mbms.StreamingServiceInfo;
@@ -26,18 +26,16 @@ import android.telephony.mbms.StreamingServiceInfo;
*/
interface IMbmsStreamingService
{
int initialize(IMbmsStreamingManagerCallback listener, int subId);
int initialize(IMbmsStreamingSessionCallback callback, int subId);
int getStreamingServices(int subId, in List<String> serviceClasses);
int requestUpdateStreamingServices(int subId, in List<String> serviceClasses);
int startStreaming(int subId, String serviceId,
IStreamingServiceCallback listener);
IStreamingServiceCallback callback);
Uri getPlaybackUri(int subId, String serviceId);
void stopStreaming(int subId, String serviceId);
void disposeStream(int subId, String serviceId);
void dispose(int subId);
}

View File

@@ -20,17 +20,21 @@ import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.mbms.DownloadStateCallback;
import android.telephony.MbmsDownloadSession;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.DownloadStateCallback;
import android.telephony.mbms.FileInfo;
import android.telephony.mbms.FileServiceInfo;
import android.telephony.mbms.IDownloadStateCallback;
import android.telephony.mbms.IMbmsDownloadManagerCallback;
import android.telephony.mbms.MbmsDownloadManagerCallback;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.IMbmsDownloadSessionCallback;
import android.telephony.mbms.MbmsDownloadSessionCallback;
import android.telephony.mbms.MbmsErrors;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Base class for MbmsDownloadService. The middleware should return an instance of this object from
@@ -39,21 +43,24 @@ import java.util.List;
*/
//@SystemApi
public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
/**
* Initialize the download service for this app and subId, registering the listener.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which
* will be intercepted and passed to the app as
* {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
* {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
*
* May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors}
* or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
* {@link IMbmsDownloadManagerCallback#error(int, String)}.
* May return any value from {@link MbmsErrors.InitializationErrors}
* or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
* {@link IMbmsDownloadSessionCallback#onError(int, String)}.
*
* @param callback The callback to use to communicate with the app.
* @param subscriptionId The subscription ID to use.
*/
public int initialize(int subscriptionId, MbmsDownloadManagerCallback callback)
public int initialize(int subscriptionId, MbmsDownloadSessionCallback callback)
throws RemoteException {
return 0;
}
@@ -64,7 +71,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
*/
@Override
public final int initialize(final int subscriptionId,
final IMbmsDownloadManagerCallback callback) throws RemoteException {
final IMbmsDownloadSessionCallback callback) throws RemoteException {
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
@@ -73,11 +80,11 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
}
}, 0);
return initialize(subscriptionId, new MbmsDownloadManagerCallback() {
return initialize(subscriptionId, new MbmsDownloadSessionCallback() {
@Override
public void onError(int errorCode, String message) {
try {
callback.error(errorCode, message);
callback.onError(errorCode, message);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -86,7 +93,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
@Override
public void onFileServicesUpdated(List<FileServiceInfo> services) {
try {
callback.fileServicesUpdated(services);
callback.onFileServicesUpdated(services);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -95,7 +102,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
@Override
public void onMiddlewareReady() {
try {
callback.middlewareReady();
callback.onMiddlewareReady();
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -106,7 +113,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
/**
* Registers serviceClasses of interest with the appName/subId key.
* Starts async fetching data on streaming services of matching classes to be reported
* later via {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)}
* later via {@link IMbmsDownloadSessionCallback#onFileServicesUpdated(List)}
*
* Note that subsequent calls with the same uid and subId will replace
* the service class list.
@@ -117,11 +124,11 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* @param serviceClasses The service classes that the app wishes to get info on. The strings
* may contain arbitrary data as negotiated between the app and the
* carrier.
* @return One of {@link MbmsException#SUCCESS} or
* {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
* @return One of {@link MbmsErrors#SUCCESS} or
* {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
*/
@Override
public int getFileServices(int subscriptionId, List<String> serviceClasses)
public int requestUpdateFileServices(int subscriptionId, List<String> serviceClasses)
throws RemoteException {
return 0;
}
@@ -133,13 +140,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
*
* If the calling app (as identified by the calling UID) currently has any pending download
* requests that have not been canceled, the middleware must return
* {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
* {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
*
* @param subscriptionId The subscription id the download is operating under.
* @param rootDirectoryPath The path to the app's temp file root directory.
* @return {@link MbmsException#SUCCESS},
* {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
* {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
* @return {@link MbmsErrors#SUCCESS},
* {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
* {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
*/
@Override
public int setTempFileRootDirectory(int subscriptionId,
@@ -155,12 +162,32 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* this is not the case, an {@link IllegalStateException} may be thrown.
*
* @param downloadRequest An object describing the set of files to be downloaded.
* @param callback A callback through which the middleware can provide progress updates to
* the app while both are still running.
* @return Any error from {@link android.telephony.mbms.MbmsException.GeneralErrors}
* or {@link MbmsException#SUCCESS}
* @return Any error from {@link MbmsErrors.GeneralErrors}
* or {@link MbmsErrors#SUCCESS}
*/
public int download(DownloadRequest downloadRequest, DownloadStateCallback callback) {
@Override
public int download(DownloadRequest downloadRequest) throws RemoteException {
return 0;
}
/**
* Registers a download state callbacks for the provided {@link DownloadRequest}.
*
* This method is called by the app when it wants to request updates on the progress or
* status of the download.
*
* If the middleware is not aware of a download having been requested with the provided
*
* {@link DownloadRequest} in the past,
* {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
* must be returned.
*
* @param downloadRequest The {@link DownloadRequest} that was used to initiate the download
* for which progress updates are being requested.
* @param callback The callback object to use.
*/
public int registerStateCallback(DownloadRequest downloadRequest,
DownloadStateCallback callback) throws RemoteException {
return 0;
}
@@ -169,36 +196,101 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* @hide
*/
@Override
public final int download(DownloadRequest downloadRequest, IDownloadStateCallback callback)
public final int registerStateCallback(
final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
throws RemoteException {
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
DeathRecipient deathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
mDownloadCallbackBinderMap.remove(callback.asBinder());
mDownloadCallbackDeathRecipients.remove(callback.asBinder());
}
}, 0);
};
mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
callback.asBinder().linkToDeath(deathRecipient, 0);
return download(downloadRequest, new DownloadStateCallback() {
DownloadStateCallback exposedCallback = new DownloadStateCallback() {
@Override
public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
fullDecodedSize) {
try {
callback.progress(request, fileInfo, currentDownloadSize, fullDownloadSize,
callback.onProgressUpdated(request, fileInfo, currentDownloadSize,
fullDownloadSize,
currentDecodedSize, fullDecodedSize);
} catch (RemoteException e) {
onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
}
}
});
@Override
public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
@MbmsDownloadSession.DownloadStatus int state) {
try {
callback.onStateUpdated(request, fileInfo, state);
} catch (RemoteException e) {
onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
}
}
};
mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
return registerStateCallback(downloadRequest, exposedCallback);
}
/**
* Un-registers a download state callbacks for the provided {@link DownloadRequest}.
*
* This method is called by the app when it no longer wants to request updates on the
* download.
*
* If the middleware is not aware of a download having been requested with the provided
* {@link DownloadRequest} in the past,
* {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
* must be returned.
*
* @param downloadRequest The {@link DownloadRequest} that was used to register the callback
* @param callback The callback object that
* {@link #registerStateCallback(DownloadRequest, DownloadStateCallback)}
* was called with.
*/
public int unregisterStateCallback(DownloadRequest downloadRequest,
DownloadStateCallback callback) throws RemoteException {
return 0;
}
/**
* Actual AIDL implementation -- hides the callback AIDL from the API.
* @hide
*/
@Override
public final int unregisterStateCallback(
final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
throws RemoteException {
DeathRecipient deathRecipient =
mDownloadCallbackDeathRecipients.remove(callback.asBinder());
if (deathRecipient == null) {
throw new IllegalArgumentException("Unknown callback");
}
callback.asBinder().unlinkToDeath(deathRecipient, 0);
DownloadStateCallback exposedCallback =
mDownloadCallbackBinderMap.remove(callback.asBinder());
if (exposedCallback == null) {
throw new IllegalArgumentException("Unknown callback");
}
return unregisterStateCallback(downloadRequest, exposedCallback);
}
/**
* Returns a list of pending {@link DownloadRequest}s that originated from the calling
* application, identified by its uid. A pending request is one that was issued via
* {@link #download(DownloadRequest, DownloadStateCallback)} but not cancelled through
* {@link #download(DownloadRequest)} but not cancelled through
* {@link #cancelDownload(DownloadRequest)}.
* The middleware must return a non-null result synchronously or throw an exception
* inheriting from {@link RuntimeException}.
@@ -214,13 +306,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* Issues a request to cancel the specified download request.
*
* If the middleware is unable to cancel the request for whatever reason, it should return
* synchronously with an error. If this method returns {@link MbmsException#SUCCESS}, the app
* synchronously with an error. If this method returns {@link MbmsErrors#SUCCESS}, the app
* will no longer be expecting any more file-completed intents from the middleware for this
* {@link DownloadRequest}.
* @param downloadRequest The request to cancel
* @return {@link MbmsException#SUCCESS},
* {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
* {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
* @return {@link MbmsErrors#SUCCESS},
* {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
* {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
*/
@Override
public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException {
@@ -232,7 +324,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
*
* If the middleware has not yet been properly initialized or if it has no records of the
* file indicated by {@code fileInfo} being associated with {@code downloadRequest},
* {@link android.telephony.MbmsDownloadManager#STATUS_UNKNOWN} must be returned.
* {@link MbmsDownloadSession#STATUS_UNKNOWN} must be returned.
*
* @param downloadRequest The download request to query.
* @param fileInfo The particular file within the request to get information on.
@@ -252,7 +344,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* In addition, current in-progress downloads must not be interrupted.
*
* If the middleware is not aware of the specified download request, return
* {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
* {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
*
* @param downloadRequest The request to re-download files for.
*/
@@ -266,7 +358,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* Signals that the app wishes to dispose of the session identified by the
* {@code subscriptionId} argument and the caller's uid. No notification back to the
* app is required for this operation, and the corresponding callback provided via
* {@link #initialize(int, IMbmsDownloadManagerCallback)} should no longer be used
* {@link #initialize(int, IMbmsDownloadSessionCallback)} should no longer be used
* after this method has been called by the app.
*
* Any download requests issued by the app should remain in effect until the app calls

View File

@@ -22,10 +22,10 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
import android.os.RemoteException;
import android.telephony.mbms.IMbmsStreamingManagerCallback;
import android.telephony.mbms.IMbmsStreamingSessionCallback;
import android.telephony.mbms.IStreamingServiceCallback;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.MbmsStreamingManagerCallback;
import android.telephony.mbms.MbmsErrors;
import android.telephony.mbms.MbmsStreamingSessionCallback;
import android.telephony.mbms.StreamingService;
import android.telephony.mbms.StreamingServiceCallback;
import android.telephony.mbms.StreamingServiceInfo;
@@ -44,16 +44,16 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
*
* May throw an {@link IllegalArgumentException} or a {@link SecurityException}, which
* will be intercepted and passed to the app as
* {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
* {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
*
* May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors}
* or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
* {@link IMbmsStreamingManagerCallback#error(int, String)}.
* May return any value from {@link MbmsErrors.InitializationErrors}
* or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
* {@link IMbmsStreamingSessionCallback#onError(int, String)}.
*
* @param callback The callback to use to communicate with the app.
* @param subscriptionId The subscription ID to use.
*/
public int initialize(MbmsStreamingManagerCallback callback, int subscriptionId)
public int initialize(MbmsStreamingSessionCallback callback, int subscriptionId)
throws RemoteException {
return 0;
}
@@ -63,7 +63,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
* @hide
*/
@Override
public final int initialize(final IMbmsStreamingManagerCallback callback,
public final int initialize(final IMbmsStreamingSessionCallback callback,
final int subscriptionId) throws RemoteException {
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@@ -73,20 +73,20 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
}
}, 0);
return initialize(new MbmsStreamingManagerCallback() {
return initialize(new MbmsStreamingSessionCallback() {
@Override
public void onError(int errorCode, String message) {
public void onError(final int errorCode, final String message) {
try {
callback.error(errorCode, message);
callback.onError(errorCode, message);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
public void onStreamingServicesUpdated(final List<StreamingServiceInfo> services) {
try {
callback.streamingServicesUpdated(services);
callback.onStreamingServicesUpdated(services);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -95,7 +95,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
@Override
public void onMiddlewareReady() {
try {
callback.middlewareReady();
callback.onMiddlewareReady();
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -107,7 +107,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
/**
* Registers serviceClasses of interest with the appName/subId key.
* Starts async fetching data on streaming services of matching classes to be reported
* later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)}
* later via {@link IMbmsStreamingSessionCallback#onStreamingServicesUpdated(List)}
*
* Note that subsequent calls with the same uid and subId will replace
* the service class list.
@@ -118,11 +118,11 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
* @param serviceClasses The service classes that the app wishes to get info on. The strings
* may contain arbitrary data as negotiated between the app and the
* carrier.
* @return {@link MbmsException#SUCCESS} or any of the errors in
* {@link android.telephony.mbms.MbmsException.GeneralErrors}
* @return {@link MbmsErrors#SUCCESS} or any of the errors in
* {@link MbmsErrors.GeneralErrors}
*/
@Override
public int getStreamingServices(int subscriptionId,
public int requestUpdateStreamingServices(int subscriptionId,
List<String> serviceClasses) throws RemoteException {
return 0;
}
@@ -130,14 +130,14 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
/**
* Starts streaming on a particular service. This method may perform asynchronous work. When
* the middleware is ready to send bits to the frontend, it should inform the app via
* {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
* {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
* @param subscriptionId The subscription id to use.
* @param serviceId The ID of the streaming service that the app has requested.
* @param callback The callback object on which the app wishes to receive updates.
* @return Any error in {@link android.telephony.mbms.MbmsException.GeneralErrors}
* @return Any error in {@link MbmsErrors.GeneralErrors}
*/
public int startStreaming(int subscriptionId, String serviceId,
StreamingServiceCallback callback) throws RemoteException {
@@ -150,8 +150,8 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
* @hide
*/
@Override
public int startStreaming(int subscriptionId, String serviceId,
IStreamingServiceCallback callback) throws RemoteException {
public int startStreaming(final int subscriptionId, String serviceId,
final IStreamingServiceCallback callback) throws RemoteException {
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
@@ -162,19 +162,19 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
@Override
public void onError(int errorCode, String message) {
public void onError(final int errorCode, final String message) {
try {
callback.error(errorCode, message);
callback.onError(errorCode, message);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
public void onStreamStateUpdated(@StreamingService.StreamingState int state,
@StreamingService.StreamingStateChangeReason int reason) {
public void onStreamStateUpdated(@StreamingService.StreamingState final int state,
@StreamingService.StreamingStateChangeReason final int reason) {
try {
callback.streamStateUpdated(state, reason);
callback.onStreamStateUpdated(state, reason);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -183,25 +183,25 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
@Override
public void onMediaDescriptionUpdated() {
try {
callback.mediaDescriptionUpdated();
callback.onMediaDescriptionUpdated();
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
public void onBroadcastSignalStrengthUpdated(int signalStrength) {
public void onBroadcastSignalStrengthUpdated(final int signalStrength) {
try {
callback.broadcastSignalStrengthUpdated(signalStrength);
callback.onBroadcastSignalStrengthUpdated(signalStrength);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
public void onStreamMethodUpdated(int methodType) {
public void onStreamMethodUpdated(final int methodType) {
try {
callback.streamMethodUpdated(methodType);
callback.onStreamMethodUpdated(methodType);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -228,7 +228,11 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
/**
* Stop streaming the stream identified by {@code serviceId}. Notification of the resulting
* stream state change should be reported to the app via
* {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
* {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.
*
* In addition, the callback provided via
* {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be
* used after this method has called by the app.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
@@ -240,28 +244,11 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
throws RemoteException {
}
/**
* Dispose of the stream identified by {@code serviceId} for the app identified by the
* {@code appName} and {@code subscriptionId} arguments along with the caller's uid.
* No notification back to the app is required for this operation, and the callback provided via
* {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be
* used after this method has called by the app.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
* @param subscriptionId The subscription id to use.
* @param serviceId The ID of the streaming service that the app wishes to dispose of.
*/
@Override
public void disposeStream(int subscriptionId, String serviceId)
throws RemoteException {
}
/**
* Signals that the app wishes to dispose of the session identified by the
* {@code subscriptionId} argument and the caller's uid. No notification back to the
* app is required for this operation, and the corresponding callback provided via
* {@link #initialize(IMbmsStreamingManagerCallback, int)} should no longer be used
* {@link #initialize(IMbmsStreamingSessionCallback, int)} should no longer be used
* after this method has been called by the app.
*
* May throw an {@link IllegalStateException}

View File

@@ -22,7 +22,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.telephony.mbms.DownloadRequest;
import android.telephony.MbmsDownloadSession;
import android.telephony.mbms.MbmsDownloadReceiver;
import java.io.File;
@@ -39,9 +39,9 @@ public class VendorUtils {
/**
* The MBMS middleware should send this when a download of single file has completed or
* failed. Mandatory extras are
* {@link android.telephony.MbmsDownloadManager#EXTRA_MBMS_DOWNLOAD_RESULT}
* {@link android.telephony.MbmsDownloadManager#EXTRA_MBMS_FILE_INFO}
* {@link #EXTRA_REQUEST}
* {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
* {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
* {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
* {@link #EXTRA_TEMP_LIST}
* {@link #EXTRA_FINAL_URI}
*/
@@ -120,12 +120,6 @@ public class VendorUtils {
public static final String EXTRA_TEMP_FILES_IN_USE =
"android.telephony.mbms.extra.TEMP_FILES_IN_USE";
/**
* Extra containing the {@link DownloadRequest} for which the download result or file
* descriptor request is for. Must not be null.
*/
public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
/**
* Extra containing a single {@link Uri} indicating the path to the temp file in which the
* decoded downloaded file resides. Must not be null.