Merge changes from topics 'embms-init-fix', 'embms-download-2'

am: e601160a96

Change-Id: Icb84816dc98198ad6ed85dd8b030fbd0198d73a4
This commit is contained in:
Hall Liu
2017-06-19 20:03:42 +00:00
committed by android-build-merger
15 changed files with 443 additions and 295 deletions

View File

@@ -16,10 +16,13 @@
package android.telephony;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.IBinder;
import android.os.RemoteException;
@@ -27,13 +30,18 @@ import android.telephony.mbms.IDownloadCallback;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.DownloadStatus;
import android.telephony.mbms.IMbmsDownloadManagerCallback;
import android.telephony.mbms.MbmsDownloadManagerCallback;
import android.telephony.mbms.MbmsDownloadReceiver;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.MbmsTempFileProvider;
import android.telephony.mbms.MbmsUtils;
import android.telephony.mbms.vendor.IMbmsDownloadService;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -47,7 +55,7 @@ public class MbmsDownloadManager {
* The MBMS middleware should send this when a download of single file has completed or
* failed. Mandatory extras are
* {@link #EXTRA_RESULT}
* {@link #EXTRA_INFO}
* {@link #EXTRA_FILE_INFO}
* {@link #EXTRA_REQUEST}
* {@link #EXTRA_TEMP_LIST}
* {@link #EXTRA_FINAL_URI}
@@ -93,7 +101,7 @@ public class MbmsDownloadManager {
* is for. Must not be null.
* TODO: future systemapi (here and and all extras) except the two for the app intent
*/
public static final String EXTRA_INFO = "android.telephony.mbms.extra.INFO";
public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO";
/**
* Extra containing the {@link DownloadRequest} for which the download result or file
@@ -143,6 +151,14 @@ public class MbmsDownloadManager {
public static final String EXTRA_PAUSED_URI_LIST =
"android.telephony.mbms.extra.PAUSED_URI_LIST";
/**
* Extra containing a string that points to the middleware's knowledge of where the temp file
* root for the app is. The path should be a canonical path as returned by
* {@link File#getCanonicalPath()}
*/
public static final String EXTRA_TEMP_FILE_ROOT =
"android.telephony.mbms.extra.TEMP_FILE_ROOT";
/**
* Extra containing a list of {@link Uri}s indicating temp files which the middleware is
* still using.
@@ -165,12 +181,10 @@ public class MbmsDownloadManager {
public static final int RESULT_EXPIRED = 3;
// TODO - more results!
private static final long BIND_TIMEOUT_MS = 3000;
private final Context mContext;
private int mSubId = INVALID_SUBSCRIPTION_ID;
private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
private IMbmsDownloadService mService;
private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
private final IMbmsDownloadManagerCallback mCallback;
private final String mDownloadAppName;
@@ -179,116 +193,221 @@ public class MbmsDownloadManager {
mContext = context;
mCallback = callback;
mDownloadAppName = downloadAppName;
mSubId = subId;
mSubscriptionId = subId;
}
/**
* Create a new MbmsDownloadManager using the system default data subscription ID.
*
* Note that this call will bind a remote service and that may take a bit. This
* may throw an Illegal ArgumentException or RemoteException.
* See {@link #create(Context, IMbmsDownloadManagerCallback, String, int)}
*
* @hide
*/
public static MbmsDownloadManager createManager(Context context,
public static MbmsDownloadManager create(Context context,
IMbmsDownloadManagerCallback listener, String downloadAppName)
throws MbmsException {
MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName,
return create(context, listener, downloadAppName,
SubscriptionManager.getDefaultSubscriptionId());
mdm.bindAndInitialize();
return mdm;
}
/**
* Create a new MbmsDownloadManager using the given subscription ID.
*
* Note that this call will bind a remote service and that may take a bit. This
* may throw an Illegal ArgumentException or RemoteException.
* 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 IMbmsDownloadManagerCallback#middlewareReady()} is called on the provided callback.
* If you attempt to use the manager before it is ready, a {@link MbmsException} will be thrown.
*
* This also may throw an {@link IllegalArgumentException} or a {@link MbmsException}.
*
* @param context The instance of {@link Context} to use
* @param listener A callback to get asynchronous error messages and file service updates.
* @param downloadAppName The app name, as negotiated with the eMBMS provider
* @param subscriptionId The data subscription ID to use
* @hide
*/
public static MbmsDownloadManager createManager(Context context,
IMbmsDownloadManagerCallback listener, String downloadAppName, int subId)
public static MbmsDownloadManager create(Context context,
IMbmsDownloadManagerCallback listener, String downloadAppName, int subscriptionId)
throws MbmsException {
MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName,
subId);
subscriptionId);
mdm.bindAndInitialize();
return mdm;
}
private void bindAndInitialize() throws MbmsException {
// TODO: fold binding for download and streaming into a common utils class.
final CountDownLatch latch = new CountDownLatch(1);
ServiceConnection bindListener = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IMbmsDownloadService.Stub.asInterface(service);
latch.countDown();
}
MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,
new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IMbmsDownloadService downloadService =
IMbmsDownloadService.Stub.asInterface(service);
try {
downloadService.initialize(
mDownloadAppName, mSubscriptionId, mCallback);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Service died before initialization");
return;
}
mService.set(downloadService);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
Intent bindIntent = new Intent();
bindIntent.setComponent(MbmsUtils.toComponentName(
MbmsUtils.getMiddlewareServiceInfo(mContext, MBMS_DOWNLOAD_SERVICE_ACTION)));
// Kick off the binding, and synchronously wait until binding is complete
mContext.bindService(bindIntent, bindListener, Context.BIND_AUTO_CREATE);
MbmsUtils.waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS);
// TODO: initialize
@Override
public void onServiceDisconnected(ComponentName name) {
mService.set(null);
}
});
}
/**
* Gets the list of files published for download.
* They may occur at times far in the future.
* servicesClasses lets the app filter on types of files and is opaque data between
* the app and the carrier
* 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 IMbmsDownloadManagerCallback#fileServicesUpdated(List)}
*
* Multiple calls replace trhe list of serviceClasses of interest.
* The serviceClasses argument lets the app filter on types of programming and is opaque data
* negotiated beforehand between the app and the carrier.
*
* May throw an IllegalArgumentException or RemoteException.
* Multiple calls replace the list of serviceClasses of interest.
*
* Synchronous responses include
* <li>SUCCESS</li>
* <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li>
* This may throw an {@link MbmsException} containing one of the following errors:
* {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
* {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED}
* {@link MbmsException#ERROR_SERVICE_LOST}
*
* Asynchronous errors through the listener include any of the errors except
* <li>ERROR_MSDC_UNABLE_TO_)START_SERVICE</li>
* <li>ERROR_MSDC_INVALID_SERVICE_ID</li>
* <li>ERROR_MSDC_END_OF_SESSION</li>
* Asynchronous error codes via the {@link MbmsDownloadManagerCallback#error(int, String)}
* callback can include any of the errors except:
* {@link MbmsException#ERROR_UNABLE_TO_START_SERVICE}
* {@link MbmsException#ERROR_END_OF_SESSION}
*/
public int getFileServices(List<String> serviceClasses) {
return 0;
public void getFileServices(List<String> classList) throws MbmsException {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
}
try {
int returnCode = downloadService.getFileServices(
mDownloadAppName, mSubscriptionId, classList);
if (returnCode != MbmsException.SUCCESS) {
throw new MbmsException(returnCode);
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService.set(null);
throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
}
}
/**
* Sets the temp file root for downloads.
* All temp files created for the middleware to write to will be contained in the specified
* directory. Applications that wish to specify a location only need to call this method once
* as long their data is persisted in storage -- the argument will be stored both in a
* 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, IDownloadCallback)}, the framework
* will default to a directory formed by the concatenation of the app's files directory and
* {@link android.telephony.mbms.MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
*
* This method may not be called while any download requests are still active. If this is
* the case, an {@link MbmsException} will be thrown with code
* {@link MbmsException#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
*
* The {@link File} supplied as a root temp file directory must already exist. If not, an
* {@link IllegalArgumentException} will be thrown.
* @param tempFileRootDirectory A directory to place temp files in.
*/
public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory)
throws MbmsException {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
}
if (!tempFileRootDirectory.exists()) {
throw new IllegalArgumentException("Provided directory does not exist");
}
if (!tempFileRootDirectory.isDirectory()) {
throw new IllegalArgumentException("Provided File is not a directory");
}
String filePath;
try {
filePath = tempFileRootDirectory.getCanonicalPath();
} catch (IOException e) {
throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e);
}
try {
int result = downloadService.setTempFileRootDirectory(
mDownloadAppName, mSubscriptionId, filePath);
if (result != MbmsException.SUCCESS) {
throw new MbmsException(result);
}
} catch (RemoteException e) {
mService.set(null);
throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
}
SharedPreferences prefs = mContext.getSharedPreferences(
MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();
}
/**
* Requests a future download.
* returns a token which may be used to cancel a download.
* Requests a download of a file that is available via multicast.
*
* downloadListener is an optional callback object which can be used to get progress reports
* of a currently occuring download. Note this can only run while the calling app
* is running, so future downloads will simply result in resultIntents being sent
* for completed or errored-out downloads. A NULL indicates no callbacks are needed.
*
* May throw an IllegalArgumentException or RemoteExcpetion.
* 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 MbmsTempFileProvider#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 callback Optional callback that will provide progress updates if the app is running.
*/
public DownloadRequest download(DownloadRequest request, IDownloadCallback listener) {
request.setAppName(mDownloadAppName);
try {
mService.download(request, listener);
} catch (RemoteException e) {
mService = null;
public void download(DownloadRequest request, IDownloadCallback callback)
throws MbmsException {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
}
// Check to see whether the app's set a temp root dir yet, and set it if not.
SharedPreferences prefs = mContext.getSharedPreferences(
MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
if (prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null) == null) {
File tempRootDirectory = new File(mContext.getFilesDir(),
MbmsTempFileProvider.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY);
tempRootDirectory.mkdirs();
setTempFileRootDirectory(tempRootDirectory);
}
request.setAppName(mDownloadAppName);
// Check if the request is a multipart download. If so, validate that the destination is
// a directory that exists.
// TODO: figure out what qualifies a request as a multipart download request.
if (request.getSourceUri().getLastPathSegment() != null &&
request.getSourceUri().getLastPathSegment().contains("*")) {
File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
if (!toFile.isDirectory()) {
throw new IllegalArgumentException("Multipart download must specify valid " +
"destination directory.");
}
}
// TODO: check to make sure destination is clear
// TODO: write download request token
try {
downloadService.download(request, callback);
} catch (RemoteException e) {
mService.set(null);
}
return request;
}
/**
@@ -355,13 +474,45 @@ public class MbmsDownloadManager {
return 0;
}
/**
* Retrieves the {@link ComponentName} for the {@link android.content.BroadcastReceiver} that
* the various intents from the middleware should be targeted towards.
* @param uid The uid of the frontend app.
* @return The component name of the receiver that the middleware should send its intents to,
* or null if the app didn't declare it in the manifest.
*
* @hide
* future systemapi
*/
public static ComponentName getAppReceiverFromUid(Context context, int uid) {
String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
if (packageNames == null) {
return null;
}
for (String packageName : packageNames) {
ComponentName candidate = new ComponentName(packageName,
MbmsDownloadReceiver.class.getCanonicalName());
Intent queryIntent = new Intent();
queryIntent.setComponent(candidate);
List<ResolveInfo> receivers =
context.getPackageManager().queryBroadcastReceivers(queryIntent, 0);
if (receivers != null && receivers.size() > 0) {
return candidate;
}
}
return null;
}
public void dispose() {
try {
if (mService != null) {
mService.dispose(mDownloadAppName, mSubId);
} else {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
Log.i(LOG_TAG, "Service already dead");
return;
}
downloadService.dispose(mDownloadAppName, mSubscriptionId);
mService.set(null);
} catch (RemoteException e) {
// Ignore
Log.i(LOG_TAG, "Remote exception while disposing of service");

View File

@@ -18,11 +18,7 @@ package android.telephony;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.mbms.MbmsException;
@@ -34,56 +30,18 @@ import android.telephony.mbms.StreamingServiceInfo;
import android.telephony.mbms.vendor.IMbmsStreamingService;
import android.util.Log;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
/** @hide */
public class MbmsStreamingManager {
private interface ServiceListener {
void onServiceConnected();
void onServiceDisconnected();
}
private static final String LOG_TAG = "MbmsStreamingManager";
public static final String MBMS_STREAMING_SERVICE_ACTION =
"android.telephony.action.EmbmsStreaming";
private static final boolean DEBUG = true;
private static final int BIND_TIMEOUT_MS = 3000;
private IMbmsStreamingService mService;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service != null) {
Log.i(LOG_TAG, String.format("Connected to service %s", name));
synchronized (MbmsStreamingManager.this) {
mService = IMbmsStreamingService.Stub.asInterface(service);
for (ServiceListener l : mServiceListeners) {
l.onServiceConnected();
}
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(LOG_TAG, String.format("Disconnected from service %s", name));
synchronized (MbmsStreamingManager.this) {
mService = null;
for (ServiceListener l : mServiceListeners) {
l.onServiceDisconnected();
}
}
}
};
private List<ServiceListener> mServiceListeners = new LinkedList<>();
private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
private MbmsStreamingManagerCallback mCallbackToApp;
private final String mAppName;
@@ -128,28 +86,26 @@ public class MbmsStreamingManager {
public static MbmsStreamingManager create(Context context,
MbmsStreamingManagerCallback listener, String streamingAppName)
throws MbmsException {
int subId = SubscriptionManager.getDefaultSubscriptionId();
MbmsStreamingManager manager = new MbmsStreamingManager(context, listener,
streamingAppName, subId);
manager.bindAndInitialize();
return manager;
return create(context, listener, streamingAppName,
SubscriptionManager.getDefaultSubscriptionId());
}
/**
* Terminates this instance, ending calls to the registered listener. Also terminates
* any streaming services spawned from this instance.
*/
public synchronized void dispose() {
if (mService == null) {
public void dispose() {
IMbmsStreamingService streamingService = mService.get();
if (streamingService == null) {
// Ignore and return, assume already disposed.
return;
}
try {
mService.dispose(mAppName, mSubscriptionId);
streamingService.dispose(mAppName, mSubscriptionId);
} catch (RemoteException e) {
// Ignore for now
}
mService = null;
mService.set(null);
}
/**
@@ -171,17 +127,19 @@ public class MbmsStreamingManager {
* {@link MbmsException#ERROR_END_OF_SESSION}
*/
public void getStreamingServices(List<String> classList) throws MbmsException {
if (mService == null) {
IMbmsStreamingService streamingService = mService.get();
if (streamingService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
}
try {
int returnCode = mService.getStreamingServices(mAppName, mSubscriptionId, classList);
int returnCode = streamingService.getStreamingServices(
mAppName, mSubscriptionId, classList);
if (returnCode != MbmsException.SUCCESS) {
throw new MbmsException(returnCode);
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
mService.set(null);
throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
}
}
@@ -190,7 +148,7 @@ public class MbmsStreamingManager {
* Starts streaming a requested service, reporting status to the indicated listener.
* Returns an object used to control that stream. The stream may not be ready for consumption
* immediately upon return from this method -- wait until the streaming state has been
* reported via {@link android.telephony.mbms.StreamingServiceCallback#streamStateChanged(int)}.
* reported via {@link android.telephony.mbms.StreamingServiceCallback#streamStateUpdated(int)}
*
* May throw an {@link MbmsException} containing any of the following error codes:
* {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
@@ -203,71 +161,47 @@ public class MbmsStreamingManager {
*/
public StreamingService startStreaming(StreamingServiceInfo serviceInfo,
StreamingServiceCallback listener) throws MbmsException {
if (mService == null) {
IMbmsStreamingService streamingService = mService.get();
if (streamingService == null) {
throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
}
try {
int returnCode = mService.startStreaming(
int returnCode = streamingService.startStreaming(
mAppName, mSubscriptionId, serviceInfo.getServiceId(), listener);
if (returnCode != MbmsException.SUCCESS) {
throw new MbmsException(returnCode);
}
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
mService.set(null);
throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
}
return new StreamingService(
mAppName, mSubscriptionId, mService, serviceInfo, listener);
mAppName, mSubscriptionId, streamingService, serviceInfo, listener);
}
private void bindAndInitialize() throws MbmsException {
// Kick off the binding, and synchronously wait until binding is complete
final CountDownLatch latch = new CountDownLatch(1);
ServiceListener bindListener = new ServiceListener() {
@Override
public void onServiceConnected() {
latch.countDown();
}
MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IMbmsStreamingService streamingService =
IMbmsStreamingService.Stub.asInterface(service);
try {
streamingService.initialize(mCallbackToApp, mAppName, mSubscriptionId);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Service died before initialization");
return;
}
mService.set(null);
}
@Override
public void onServiceDisconnected() {
}
};
synchronized (this) {
mServiceListeners.add(bindListener);
}
Intent bindIntent = new Intent();
bindIntent.setComponent(MbmsUtils.toComponentName(
MbmsUtils.getMiddlewareServiceInfo(mContext, MBMS_STREAMING_SERVICE_ACTION)));
mContext.bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
MbmsUtils.waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS);
// Remove the listener and call the initialization method through the interface.
synchronized (this) {
mServiceListeners.remove(bindListener);
if (mService == null) {
throw new MbmsException(MbmsException.ERROR_BIND_TIMEOUT_OR_FAILURE);
}
try {
int returnCode = mService.initialize(mCallbackToApp, mAppName, mSubscriptionId);
if (returnCode != MbmsException.SUCCESS) {
throw new MbmsException(returnCode);
}
} catch (RemoteException e) {
mService = null;
Log.e(LOG_TAG, "Service died before initialization");
throw new MbmsException(MbmsException.ERROR_SERVICE_LOST);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService.set(null);
}
});
}
}

View File

@@ -35,9 +35,8 @@ public class DownloadRequest implements Parcelable {
private FileServiceInfo serviceInfo;
private Uri source;
private Uri dest;
private int sub;
private int subscriptionId;
private String appIntent;
private String appName; // not the Android app Name, the embms app Name
public Builder setId(int id) {
this.id = id;
@@ -59,8 +58,8 @@ public class DownloadRequest implements Parcelable {
return this;
}
public Builder setSub(int sub) {
this.sub = sub;
public Builder setSubscriptionId(int sub) {
this.subscriptionId = sub;
return this;
}
@@ -70,7 +69,8 @@ public class DownloadRequest implements Parcelable {
}
public DownloadRequest build() {
return new DownloadRequest(id, serviceInfo, source, dest, sub, appIntent, appName);
return new DownloadRequest(id, serviceInfo, source, dest,
subscriptionId, appIntent, null);
}
}
@@ -78,7 +78,7 @@ public class DownloadRequest implements Parcelable {
private final FileServiceInfo fileServiceInfo;
private final Uri sourceUri;
private final Uri destinationUri;
private final int subId;
private final int subscriptionId;
private final String serializedResultIntentForApp;
private String appName; // not the Android app Name, the embms app name
@@ -89,7 +89,7 @@ public class DownloadRequest implements Parcelable {
fileServiceInfo = serviceInfo;
sourceUri = source;
destinationUri = dest;
subId = sub;
subscriptionId = sub;
serializedResultIntentForApp = appIntent;
appName = name;
}
@@ -103,7 +103,7 @@ public class DownloadRequest implements Parcelable {
fileServiceInfo = dr.fileServiceInfo;
sourceUri = dr.sourceUri;
destinationUri = dr.destinationUri;
subId = dr.subId;
subscriptionId = dr.subscriptionId;
serializedResultIntentForApp = dr.serializedResultIntentForApp;
appName = dr.appName;
}
@@ -113,7 +113,7 @@ public class DownloadRequest implements Parcelable {
fileServiceInfo = in.readParcelable(getClass().getClassLoader());
sourceUri = in.readParcelable(getClass().getClassLoader());
destinationUri = in.readParcelable(getClass().getClassLoader());
subId = in.readInt();
subscriptionId = in.readInt();
serializedResultIntentForApp = in.readString();
appName = in.readString();
}
@@ -127,7 +127,7 @@ public class DownloadRequest implements Parcelable {
out.writeParcelable(fileServiceInfo, flags);
out.writeParcelable(sourceUri, flags);
out.writeParcelable(destinationUri, flags);
out.writeInt(subId);
out.writeInt(subscriptionId);
out.writeString(serializedResultIntentForApp);
out.writeString(appName);
}
@@ -148,8 +148,8 @@ public class DownloadRequest implements Parcelable {
return destinationUri;
}
public int getSubId() {
return subId;
public int getSubscriptionId() {
return subscriptionId;
}
public Intent getIntentForApp() {

View File

@@ -31,29 +31,22 @@ public class FileInfo implements Parcelable {
* This is used internally but is also one of the few pieces of data about the content that is
* exposed and may be needed for disambiguation by the application.
*/
final Uri uri;
private final Uri uri;
/**
* The mime type of the content.
*/
final String mimeType;
private final String mimeType;
/**
* The size of the file in bytes.
*/
final long size;
private final long size;
/**
* The MD5 hash of the file.
*/
final byte md5Hash[];
/**
* Gets the parent service for this file.
*/
public FileServiceInfo getFileServiceInfo() {
return null;
}
private final byte md5Hash[];
public static final Parcelable.Creator<FileInfo> CREATOR =
new Parcelable.Creator<FileInfo>() {
@@ -68,6 +61,13 @@ public class FileInfo implements Parcelable {
}
};
public FileInfo(Uri uri, String mimeType, long size, byte[] md5Hash) {
this.uri = uri;
this.mimeType = mimeType;
this.size = size;
this.md5Hash = md5Hash;
}
private FileInfo(Parcel in) {
uri = in.readParcelable(null);
mimeType = in.readString();
@@ -90,4 +90,20 @@ public class FileInfo implements Parcelable {
public int describeContents() {
return 0;
}
public Uri getUri() {
return uri;
}
public String getMimeType() {
return mimeType;
}
public long getSize() {
return size;
}
public byte[] getMd5Hash() {
return md5Hash;
}
}

View File

@@ -30,13 +30,13 @@ import java.util.Map;
* @hide
*/
public class FileServiceInfo extends ServiceInfo implements Parcelable {
public List<FileInfo> files;
private final List<FileInfo> files;
public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
List<Locale> newLocales, String newServiceId, Date start, Date end,
List<FileInfo> newFiles) {
super(newNames, newClassName, newLocales, newServiceId, start, end);
files = new ArrayList(newFiles);
files = new ArrayList<>(newFiles);
}
public static final Parcelable.Creator<FileServiceInfo> CREATOR =
@@ -68,4 +68,9 @@ public class FileServiceInfo extends ServiceInfo implements Parcelable {
public int describeContents() {
return 0;
}
public List<FileInfo> getFiles() {
return files;
}
}

View File

@@ -24,19 +24,11 @@ import java.util.List;
* The interface the clients top-level file download listener will satisfy.
* @hide
*/
interface IMbmsDownloadManagerCallback
oneway interface IMbmsDownloadManagerCallback
{
void error(int errorCode, String message);
/**
* Called to indicate published File Services have changed.
*
* This will only be called after the application has requested
* a list of file services and specified a service class list
* of interest AND the results of a subsequent getFileServices
* call with the same service class list would
* return different
* results.
*/
void fileServicesUpdated(in List<FileServiceInfo> services);
void middlewareReady();
}

View File

@@ -24,30 +24,13 @@ import java.util.List;
* The interface the clients top-level streaming listener will satisfy.
* @hide
*/
interface IMbmsStreamingManagerCallback
oneway interface IMbmsStreamingManagerCallback
{
void error(int errorCode, String message);
/**
* Called to indicate published Streaming Services have changed.
*
* This will only be called after the application has requested
* a list of streaming services and specified a service class list
* of interest AND the results of a subsequent getStreamServices
* call with the same service class list would
* return different
* results.
*/
void streamingServicesUpdated(in List<StreamingServiceInfo> services);
/**
* Called to indicate the active Streaming Services have changed.
*
* This will be caused whenever a new service starts streaming or whenever
* MbmsStreamServiceManager.getActiveStreamingServices is called.
*
* @param services a list of StreamingServiceInfos. May be empty if
* there are no active StreamingServices
*/
void activeStreamingServicesUpdated(in List<StreamingServiceInfo> services);
void middlewareReady();
}

View File

@@ -48,4 +48,17 @@ public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.St
public void fileServicesUpdated(List<FileServiceInfo> services) {
// default implementation empty
}
/**
* 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#ERROR_MIDDLEWARE_NOT_YET_READY}
*/
@Override
public void middlewareReady() {
// default implementation empty
}
}

View File

@@ -31,8 +31,8 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
@@ -54,6 +54,11 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
setResultCode(1 /* TODO: define error constants */);
return;
}
if (!Objects.equals(intent.getStringExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT),
MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) {
setResultCode(1 /* TODO: define error constants */);
return;
}
if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
moveDownloadedFile(context, intent);
@@ -74,7 +79,11 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
return false;
}
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_INFO)) {
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) {
Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
return false;
}
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_FILE_INFO)) {
Log.w(LOG_TAG, "Download result did not include the associated file info. " +
"Ignoring.");
return false;
@@ -90,6 +99,10 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
Log.w(LOG_TAG, "Temp file request not include the associated request. Ignoring.");
return false;
}
if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) {
Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
return false;
}
return true;
}
@@ -121,12 +134,15 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
}
String relativePath = calculateDestinationFileRelativePath(request,
(FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_INFO));
(FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO));
if (!moveTempFile(finalTempFile, destinationUri, relativePath)) {
Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath);
if (finalFileLocation == null) {
Log.w(LOG_TAG, "Failed to move temp file to final destination");
// TODO: how do we notify the app of this?
setResultCode(1);
}
intentForApp.putExtra(MbmsDownloadManager.EXTRA_COMPLETED_FILE_URI, finalFileLocation);
context.sendBroadcast(intentForApp);
setResultCode(0);
@@ -226,7 +242,6 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
return null;
}
private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context,
DownloadRequest request, List<Uri> pausedFiles) {
if (pausedFiles == null) {
@@ -258,13 +273,15 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
private static String calculateDestinationFileRelativePath(DownloadRequest request,
FileInfo info) {
// TODO: determine whether this is actually the path determination scheme we want to use
List<String> filePathComponents = info.uri.getPathSegments();
List<String> filePathComponents = info.getUri().getPathSegments();
List<String> requestPathComponents = request.getSourceUri().getPathSegments();
Iterator<String> filePathIter = filePathComponents.iterator();
Iterator<String> requestPathIter = requestPathComponents.iterator();
LinkedList<String> relativePathComponents = new LinkedList<>();
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()) {
@@ -273,28 +290,44 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
continue;
}
}
relativePathComponents.add(currFilePathComponent);
pathBuilder.append(currFilePathComponent);
pathBuilder.append('/');
}
return String.join("/", relativePathComponents);
// remove the trailing slash
if (pathBuilder.length() > 0) {
pathBuilder.deleteCharAt(pathBuilder.length() - 1);
}
return pathBuilder.toString();
}
private static boolean moveTempFile(Uri fromPath, Uri toPath, String relativePath) {
/*
* 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.
*/
private static Uri moveTempFile(Uri fromPath, Uri toPath, String relativePath) {
if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) {
Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme");
return false;
return null;
}
if (!ContentResolver.SCHEME_FILE.equals(toPath.getScheme())) {
Log.w(LOG_TAG, "Moving destination uri " + toPath + " does not have a file scheme");
return false;
return null;
}
File fromFile = new File(fromPath.getSchemeSpecificPart());
File toFile = new File(toPath.getSchemeSpecificPart(), relativePath);
File toFile = new File(toPath.getSchemeSpecificPart());
if (toFile.isDirectory()) {
toFile = new File(toFile, relativePath);
}
toFile.getParentFile().mkdirs();
// TODO: This may not work if the two files are on different filesystems. Should we
// enforce that the temp file storage and the permanent storage are both in the same fs?
return fromFile.renameTo(toFile);
// TODO: This will not work if the two files are on different filesystems. Add manual
// copy later.
if (fromFile.renameTo(toFile)) {
return Uri.fromFile(toFile);
}
return null;
}
private static boolean verifyTempFilePath(Context context, DownloadRequest request,
@@ -323,8 +356,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
* Returns a File linked to the directory used to store temp files for this request
*/
private static File getEmbmsTempFileDirForRequest(Context context, DownloadRequest request) {
File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(
context, getFileProviderAuthority(context));
File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context);
// TODO: better naming scheme for temp file dirs
String tempFileDirName = String.valueOf(request.getFileServiceInfo().getServiceId());

View File

@@ -22,7 +22,7 @@ public class MbmsException extends Exception {
public static final int ERROR_NO_SERVICE_INSTALLED = 1;
public static final int ERROR_MULTIPLE_SERVICES_INSTALLED = 2;
public static final int ERROR_BIND_TIMEOUT_OR_FAILURE = 3;
public static final int ERROR_UNABLE_TO_INITIALIZE = 4;
public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 4;
public static final int ERROR_ALREADY_INITIALIZED = 5;
public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 6;
public static final int ERROR_MIDDLEWARE_NOT_BOUND = 7;
@@ -36,6 +36,7 @@ public class MbmsException extends Exception {
public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 15;
public static final int ERROR_UNABLE_TO_READ_SIM = 16;
public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 17;
public static final int ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT = 18;
private final int mErrorCode;

View File

@@ -61,4 +61,17 @@ public class MbmsStreamingManagerCallback extends IMbmsStreamingManagerCallback.
public void activeStreamingServicesUpdated(List<StreamingServiceInfo> services) {
// default implementation empty
}
/**
* 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#ERROR_MIDDLEWARE_NOT_YET_READY}
*/
@Override
public void middlewareReady() {
// default implementation empty
}
}

View File

@@ -22,6 +22,7 @@ import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
@@ -32,14 +33,15 @@ import android.os.ParcelFileDescriptor;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Objects;
/**
* @hide
*/
public class MbmsTempFileProvider extends ContentProvider {
public static final String META_DATA_USE_EXTERNAL_STORAGE = "use-external-storage";
public static final String META_DATA_TEMP_FILE_DIRECTORY = "temp-file-path";
public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
public static final String TEMP_FILE_ROOT_PREF_FILE_NAME = "MbmsTempFileRootPrefs";
public static final String TEMP_FILE_ROOT_PREF_NAME = "mbms_temp_file_root";
private String mAuthority;
private Context mContext;
@@ -114,7 +116,7 @@ public class MbmsTempFileProvider extends ContentProvider {
// Make sure the temp file is contained in the temp file directory as configured in the
// manifest
File tempFileDir = getEmbmsTempFileDir(context, authority);
File tempFileDir = getEmbmsTempFileDir(context);
if (!MbmsUtils.isContainedIn(tempFileDir, file)) {
throw new IllegalArgumentException("File " + file + " is not contained in the temp " +
"file directory, which is " + tempFileDir);
@@ -147,13 +149,17 @@ public class MbmsTempFileProvider extends ContentProvider {
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
throw new IllegalArgumentException("Uri must have scheme content");
}
if (!Objects.equals(authority, uri.getAuthority())) {
throw new IllegalArgumentException("Uri does not have a matching authority: " +
authority + ", " + uri.getAuthority());
}
String relPath = Uri.decode(uri.getEncodedPath());
File file;
File tempFileDir;
try {
tempFileDir = getEmbmsTempFileDir(context, authority).getCanonicalFile();
tempFileDir = getEmbmsTempFileDir(context).getCanonicalFile();
file = new File(tempFileDir, relPath).getCanonicalFile();
} catch (IOException e) {
throw new FileNotFoundException("Could not resolve paths");
@@ -169,25 +175,18 @@ public class MbmsTempFileProvider extends ContentProvider {
/**
* Returns a File for the directory used to store temp files for this app
*/
public static File getEmbmsTempFileDir(Context context, String authority) {
Bundle metadata = getMetadata(context, authority);
File parentDirectory;
if (metadata.getBoolean(META_DATA_USE_EXTERNAL_STORAGE, false)) {
parentDirectory = context.getExternalFilesDir(null);
} else {
parentDirectory = context.getFilesDir();
public static File getEmbmsTempFileDir(Context context) {
SharedPreferences prefs = context.getSharedPreferences(TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
String storedTempFileRoot = prefs.getString(TEMP_FILE_ROOT_PREF_NAME, null);
try {
if (storedTempFileRoot != null) {
return new File(storedTempFileRoot).getCanonicalFile();
} else {
return new File(context.getFilesDir(), DEFAULT_TOP_LEVEL_TEMP_DIRECTORY)
.getCanonicalFile();
}
} catch (IOException e) {
throw new RuntimeException("Unable to canonicalize temp file root path " + e);
}
String tmpFilePath = metadata.getString(META_DATA_TEMP_FILE_DIRECTORY);
if (tmpFilePath == null) {
tmpFilePath = DEFAULT_TOP_LEVEL_TEMP_DIRECTORY;
}
return new File(parentDirectory, tmpFilePath);
}
private static Bundle getMetadata(Context context, String authority) {
final ProviderInfo info = context.getPackageManager()
.resolveContentProvider(authority, PackageManager.GET_META_DATA);
return info.metaData;
}
}

View File

@@ -19,6 +19,7 @@ package android.telephony.mbms;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.*;
import android.content.pm.ServiceInfo;
import android.telephony.MbmsDownloadManager;
@@ -46,20 +47,6 @@ public class MbmsUtils {
}
}
public static void waitOnLatchWithTimeout(CountDownLatch l, long timeoutMs) {
long endTime = System.currentTimeMillis() + timeoutMs;
while (System.currentTimeMillis() < endTime) {
try {
l.await(timeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// keep waiting
}
if (l.getCount() <= 0) {
return;
}
}
}
public static ComponentName toComponentName(ComponentInfo ci) {
return new ComponentName(ci.packageName, ci.name);
}
@@ -83,4 +70,19 @@ public class MbmsUtils {
}
return downloadServices.get(0).serviceInfo;
}
public static void startBinding(Context context, String serviceAction,
ServiceConnection serviceConnection) throws MbmsException {
Intent bindIntent = new Intent();
ServiceInfo mbmsServiceInfo =
MbmsUtils.getMiddlewareServiceInfo(context, serviceAction);
if (mbmsServiceInfo == null) {
throw new MbmsException(MbmsException.ERROR_NO_SERVICE_INSTALLED);
}
bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo));
context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
}

View File

@@ -47,6 +47,7 @@ interface IMbmsDownloadService
*/
int getFileServices(String appName, int subId, in List<String> serviceClasses);
int setTempFileRootDirectory(String appName, int subId, String rootDirectoryPath);
/**
* should move the params into a DownloadRequest parcelable
*/

View File

@@ -32,13 +32,19 @@ import java.util.List;
*/
public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
@Override
public void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener)
throws RemoteException {
public void initialize(String appName, int subscriptionId,
IMbmsDownloadManagerCallback listener) throws RemoteException {
}
@Override
public int getFileServices(String appName, int subId, List<String> serviceClasses) throws
RemoteException {
public int getFileServices(String appName, int subscriptionId, List<String> serviceClasses)
throws RemoteException {
return 0;
}
@Override
public int setTempFileRootDirectory(String appName, int subscriptionId,
String rootDirectoryPath) throws RemoteException {
return 0;
}