API council suggested edits, part 3

* MbmsException no longer thrown from the common methods, and the error
codes are now containined in MbmsError.
* In order to avoid ANRs while processing download-done broadcasts,
don't perform arbitrary moves when a download is done. Instead, move
into a staging directory and let the app handle it.

Bug: 30981736
Test: testapps
Change-Id: I9416f28b4f24f89af1da6f56a93ea0f0e4ea3878
This commit is contained in:
Hall Liu
2017-09-08 18:02:38 -07:00
parent 1a5b130468
commit bfc5f1c9a8
14 changed files with 147 additions and 227 deletions

View File

@@ -38,7 +38,7 @@ import android.telephony.mbms.InternalDownloadSessionCallback;
import android.telephony.mbms.InternalDownloadStateCallback;
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;
@@ -98,13 +98,25 @@ public class MbmsDownloadSession implements AutoCloseable {
/**
* {@link Uri} extra that Android will attach to the intent supplied via
* {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
* Indicates the location of the successfully
* downloaded file. 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)} without first calling
@@ -176,7 +188,7 @@ public class MbmsDownloadSession implements AutoCloseable {
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");
}
};
@@ -243,7 +255,7 @@ public class MbmsDownloadSession implements AutoCloseable {
MbmsDownloadSession session =
new MbmsDownloadSession(context, callback, subscriptionId, handler);
final int result = session.bindAndInitialize();
if (result != MbmsException.SUCCESS) {
if (result != MbmsErrors.SUCCESS) {
sIsInitialized.set(false);
handler.post(new Runnable() {
@Override
@@ -273,12 +285,12 @@ public class MbmsDownloadSession implements AutoCloseable {
} 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;
@@ -286,7 +298,7 @@ public class MbmsDownloadSession implements AutoCloseable {
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;
@@ -328,13 +340,13 @@ public class MbmsDownloadSession implements AutoCloseable {
}
try {
int returnCode = downloadService.requestUpdateFileServices(mSubscriptionId, classList);
if (returnCode != MbmsException.SUCCESS) {
if (returnCode != MbmsErrors.SUCCESS) {
sendErrorToApp(returnCode, null);
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService.set(null);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
}
@@ -353,7 +365,7 @@ public class MbmsDownloadSession implements AutoCloseable {
* Before calling this method, the app must cancel all of its pending
* {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
* you will receive an asynchronous error with code
* {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
* {@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
@@ -382,12 +394,12 @@ public class MbmsDownloadSession implements AutoCloseable {
try {
int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
if (result != MbmsException.SUCCESS) {
if (result != MbmsErrors.SUCCESS) {
sendErrorToApp(result, null);
}
} catch (RemoteException e) {
mService.set(null);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return;
}
@@ -465,13 +477,12 @@ public class MbmsDownloadSession implements AutoCloseable {
setTempFileRootDirectory(tempRootDirectory);
}
checkValidDownloadDestination(request);
writeDownloadRequestToken(request);
try {
downloadService.download(request);
} catch (RemoteException e) {
mService.set(null);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
}
@@ -492,7 +503,7 @@ public class MbmsDownloadSession implements AutoCloseable {
return downloadService.listPendingDownloads(mSubscriptionId);
} catch (RemoteException e) {
mService.set(null);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return Collections.emptyList();
}
}
@@ -524,8 +535,8 @@ public class MbmsDownloadSession implements AutoCloseable {
try {
int result = downloadService.registerStateCallback(request, internalCallback);
if (result != MbmsException.SUCCESS) {
if (result == MbmsException.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
if (result != MbmsErrors.SUCCESS) {
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
}
sendErrorToApp(result, null);
@@ -533,7 +544,7 @@ public class MbmsDownloadSession implements AutoCloseable {
}
} catch (RemoteException e) {
mService.set(null);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return;
}
mInternalDownloadCallbacks.put(callback, internalCallback);
@@ -564,15 +575,15 @@ public class MbmsDownloadSession implements AutoCloseable {
try {
int result = downloadService.unregisterStateCallback(request, internalCallback);
if (result != MbmsException.SUCCESS) {
if (result == MbmsException.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
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(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
} finally {
InternalDownloadStateCallback internalCallback =
@@ -599,8 +610,8 @@ public class MbmsDownloadSession implements AutoCloseable {
try {
int result = downloadService.cancelDownload(downloadRequest);
if (result != MbmsException.SUCCESS) {
if (result == MbmsException.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
if (result != MbmsErrors.SUCCESS) {
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
}
sendErrorToApp(result, null);
@@ -608,7 +619,7 @@ public class MbmsDownloadSession implements AutoCloseable {
}
} catch (RemoteException e) {
mService.set(null);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return;
}
deleteDownloadRequestToken(downloadRequest);
@@ -636,7 +647,7 @@ public class MbmsDownloadSession implements AutoCloseable {
return downloadService.getDownloadStatus(downloadRequest, fileInfo);
} catch (RemoteException e) {
mService.set(null);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return STATUS_UNKNOWN;
}
}
@@ -669,15 +680,15 @@ public class MbmsDownloadSession implements AutoCloseable {
try {
int result = downloadService.resetDownloadKnowledge(downloadRequest);
if (result != MbmsException.SUCCESS) {
if (result == MbmsException.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
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(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
}
@@ -753,32 +764,6 @@ public class MbmsDownloadSession implements AutoCloseable {
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.onError(errorCode, message);

View File

@@ -29,7 +29,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.telephony.mbms.InternalStreamingSessionCallback;
import android.telephony.mbms.InternalStreamingServiceCallback;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.MbmsErrors;
import android.telephony.mbms.MbmsStreamingSessionCallback;
import android.telephony.mbms.MbmsUtils;
import android.telephony.mbms.StreamingService;
@@ -70,7 +70,7 @@ public class MbmsStreamingSession implements AutoCloseable {
@Override
public void binderDied() {
sIsInitialized.set(false);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
}
};
@@ -125,7 +125,7 @@ public class MbmsStreamingSession implements AutoCloseable {
subscriptionId, handler);
final int result = session.bindAndInitialize();
if (result != MbmsException.SUCCESS) {
if (result != MbmsErrors.SUCCESS) {
sIsInitialized.set(false);
handler.post(new Runnable() {
@Override
@@ -204,14 +204,14 @@ public class MbmsStreamingSession implements AutoCloseable {
try {
int returnCode = streamingService.requestUpdateStreamingServices(
mSubscriptionId, serviceClassList);
if (returnCode != MbmsException.SUCCESS) {
if (returnCode != MbmsErrors.SUCCESS) {
sendErrorToApp(returnCode, null);
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService.set(null);
sIsInitialized.set(false);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
}
}
@@ -225,8 +225,8 @@ public class MbmsStreamingSession implements AutoCloseable {
* 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.
@@ -251,7 +251,7 @@ public class MbmsStreamingSession implements AutoCloseable {
try {
int returnCode = streamingService.startStreaming(
mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
if (returnCode != MbmsException.SUCCESS) {
if (returnCode != MbmsErrors.SUCCESS) {
sendErrorToApp(returnCode, null);
return null;
}
@@ -259,7 +259,7 @@ public class MbmsStreamingSession implements AutoCloseable {
Log.w(LOG_TAG, "Remote process died");
mService.set(null);
sIsInitialized.set(false);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return null;
}
@@ -285,19 +285,19 @@ public class MbmsStreamingSession implements AutoCloseable {
} 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;
@@ -305,7 +305,7 @@ public class MbmsStreamingSession implements AutoCloseable {
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;

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

@@ -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;
@@ -153,7 +166,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
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;
}
@@ -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,8 +225,14 @@ 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(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
MbmsDownloadSession.RESULT_CANCELLED);
@@ -224,7 +244,6 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
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);
@@ -234,23 +253,30 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
FileInfo completedFileInfo =
(FileInfo) intent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO);
String relativePath = calculateDestinationFileRelativePath(request, completedFileInfo);
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(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI, finalFileLocation);
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,

View File

@@ -29,7 +29,7 @@ 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.
*/
@@ -43,7 +43,8 @@ public class MbmsDownloadSessionCallback {
* 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 MbmsDownloadSession#requestUpdateFileServices(List)}. If there are subsequent calls to
* {@link MbmsDownloadSession#requestUpdateFileServices(List)}, this method may not be called again if
* {@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 MbmsDownloadSessionCallback {
* 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 MbmsDownloadSession} 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

@@ -19,7 +19,7 @@ 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;
@@ -131,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

@@ -32,7 +32,7 @@ import java.util.List;
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.
*/
@@ -61,7 +61,7 @@ public class MbmsStreamingSessionCallback {
* Before this method is called, calling any method on an instance of
* {@link MbmsStreamingSession} will result in an {@link IllegalStateException} or an error
* delivered via {@link #onError(int, String)} with error code
* {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}.
* {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}.
*/
public void onMiddlewareReady() {
// default implementation empty

View File

@@ -75,13 +75,13 @@ public class MbmsUtils {
MbmsUtils.getMiddlewareServiceInfo(context, serviceAction);
if (mbmsServiceInfo == null) {
return 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 MbmsException.SUCCESS;
return MbmsErrors.SUCCESS;
}
/**

View File

@@ -28,6 +28,7 @@ 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;
@@ -128,13 +129,13 @@ public class ServiceInfo {
* 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 IllegalArgumentException} may be thrown otherwise.
* {@link java.util.NoSuchElementException} may be thrown otherwise.
* @return The {@link CharSequence} providing the name of the service in the given
* {@link Locale}
*/
public @NonNull CharSequence getNameForLocale(@NonNull Locale locale) {
if (!names.containsKey(locale)) {
throw new IllegalArgumentException("Locale not supported");
throw new NoSuchElementException("Locale not supported");
}
return names.get(locale);
}

View File

@@ -144,7 +144,7 @@ public class StreamingService {
Log.w(LOG_TAG, "Remote process died");
mService = null;
mParentSession.onStreamingServiceStopped(this);
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
return null;
}
}
@@ -172,7 +172,7 @@ public class StreamingService {
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, null);
sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
} finally {
mParentSession.onStreamingServiceStopped(this);
}

View File

@@ -36,7 +36,7 @@ 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.
*/

View File

@@ -30,7 +30,7 @@ import android.telephony.mbms.FileServiceInfo;
import android.telephony.mbms.IDownloadStateCallback;
import android.telephony.mbms.IMbmsDownloadSessionCallback;
import android.telephony.mbms.MbmsDownloadSessionCallback;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.MbmsErrors;
import java.util.HashMap;
import java.util.List;
@@ -51,10 +51,10 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
*
* 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
* 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.
@@ -124,8 +124,8 @@ 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 requestUpdateFileServices(int subscriptionId, List<String> serviceClasses)
@@ -140,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,
@@ -162,8 +162,8 @@ 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.
* @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}
*/
@Override
public int download(DownloadRequest downloadRequest) throws RemoteException {
@@ -179,7 +179,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* If the middleware is not aware of a download having been requested with the provided
*
* {@link DownloadRequest} in the past,
* {@link android.telephony.mbms.MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
* {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
* must be returned.
*
* @param downloadRequest The {@link DownloadRequest} that was used to initiate the download
@@ -249,7 +249,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
*
* If the middleware is not aware of a download having been requested with the provided
* {@link DownloadRequest} in the past,
* {@link android.telephony.mbms.MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
* {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
* must be returned.
*
* @param downloadRequest The {@link DownloadRequest} that was used to register the callback
@@ -306,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 {
@@ -344,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.
*/

View File

@@ -24,7 +24,7 @@ import android.os.Binder;
import android.os.RemoteException;
import android.telephony.mbms.IMbmsStreamingSessionCallback;
import android.telephony.mbms.IStreamingServiceCallback;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.MbmsErrors;
import android.telephony.mbms.MbmsStreamingSessionCallback;
import android.telephony.mbms.StreamingService;
import android.telephony.mbms.StreamingServiceCallback;
@@ -44,10 +44,10 @@ 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
* 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.
@@ -118,8 +118,8 @@ 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 requestUpdateStreamingServices(int subscriptionId,
@@ -137,7 +137,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
* @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 {

View File

@@ -23,7 +23,6 @@ import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.telephony.MbmsDownloadSession;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.MbmsDownloadReceiver;
import java.io.File;
@@ -42,7 +41,7 @@ public class VendorUtils {
* failed. Mandatory extras are
* {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
* {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
* {@link #EXTRA_REQUEST}
* {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
* {@link #EXTRA_TEMP_LIST}
* {@link #EXTRA_FINAL_URI}
*/
@@ -121,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.