From fcbf24075dca399bbe94979aef770b4aef705db4 Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Wed, 7 Jun 2017 13:57:11 -0700 Subject: [PATCH 1/2] Embms download part 2 Add support for multi-part file downloads. Improves destination directory handling in the download process. Change-Id: Ibad57bab8804530ce09305424790d5520cd02071 --- .../telephony/MbmsDownloadManager.java | 255 +++++++++++++++--- .../telephony/mbms/DownloadRequest.java | 24 +- .../java/android/telephony/mbms/FileInfo.java | 38 ++- .../telephony/mbms/FileServiceInfo.java | 9 +- .../telephony/mbms/MbmsDownloadReceiver.java | 70 +++-- .../android/telephony/mbms/MbmsException.java | 1 + .../telephony/mbms/MbmsTempFileProvider.java | 45 ++-- .../mbms/vendor/IMbmsDownloadService.aidl | 1 + .../mbms/vendor/MbmsDownloadServiceBase.java | 14 +- 9 files changed, 348 insertions(+), 109 deletions(-) diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java index c8c6d014a059a..597756e7aaf3a 100644 --- a/telephony/java/android/telephony/MbmsDownloadManager.java +++ b/telephony/java/android/telephony/MbmsDownloadManager.java @@ -16,22 +16,32 @@ 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.content.pm.ServiceInfo; import android.net.Uri; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; 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; @@ -47,7 +57,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 +103,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 +153,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. @@ -168,7 +186,7 @@ public class MbmsDownloadManager { 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 final IMbmsDownloadManagerCallback mCallback; @@ -179,14 +197,12 @@ 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 #createManager(Context, IMbmsDownloadManagerCallback, String, int)} * * @hide */ @@ -202,17 +218,29 @@ public class MbmsDownloadManager { /** * 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. Since the + * framework notifies us that binding is complete on the main thread, this method should + * never be called from the main thread of one's app, or else an + * {@link IllegalStateException} 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) + IMbmsDownloadManagerCallback listener, String downloadAppName, int subscriptionId) throws MbmsException { + if (Looper.getMainLooper().isCurrentThread()) { + throw new IllegalStateException( + "createManager must not be called from the main thread."); + } + MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName, - subId); + subscriptionId); mdm.bindAndInitialize(); return mdm; } @@ -234,61 +262,182 @@ public class MbmsDownloadManager { }; Intent bindIntent = new Intent(); - bindIntent.setComponent(MbmsUtils.toComponentName( - MbmsUtils.getMiddlewareServiceInfo(mContext, MBMS_DOWNLOAD_SERVICE_ACTION))); + ServiceInfo mbmsServiceInfo = + MbmsUtils.getMiddlewareServiceInfo(mContext, MBMS_DOWNLOAD_SERVICE_ACTION); + + if (mbmsServiceInfo == null) { + throw new MbmsException(MbmsException.ERROR_NO_SERVICE_INSTALLED); + } + + bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo)); - // Kick off the binding, and synchronously wait until binding is complete mContext.bindService(bindIntent, bindListener, Context.BIND_AUTO_CREATE); + // Wait until binding is complete MbmsUtils.waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS); - // TODO: initialize + // Call initialize after binding finishes + synchronized (this) { + if (mService == null) { + throw new MbmsException(MbmsException.ERROR_BIND_TIMEOUT_OR_FAILURE); + } + + try { + mService.initialize(mDownloadAppName, mSubscriptionId, mCallback); + } catch (RemoteException e) { + mService = null; + Log.e(LOG_TAG, "Service died before initialization"); + throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); + } + } } /** - * 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 - *
  • SUCCESS
  • - *
  • ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED
  • + * 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 - *
  • ERROR_MSDC_UNABLE_TO_)START_SERVICE
  • - *
  • ERROR_MSDC_INVALID_SERVICE_ID
  • - *
  • ERROR_MSDC_END_OF_SESSION
  • + * 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 serviceClasses) { - return 0; + public void getFileServices(List classList) throws MbmsException { + if (mService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + try { + int returnCode = mService.getFileServices(mDownloadAppName, mSubscriptionId, classList); + if (returnCode != MbmsException.SUCCESS) { + throw new MbmsException(returnCode); + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + mService = 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 { + if (mService == 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 = mService.setTempFileRootDirectory( + mDownloadAppName, mSubscriptionId, filePath); + if (result != MbmsException.SUCCESS) { + throw new MbmsException(result); + } + } catch (RemoteException e) { + mService = 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) { + public void download(DownloadRequest request, IDownloadCallback callback) + throws MbmsException { + if (mService == 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 { - mService.download(request, listener); + mService.download(request, callback); } catch (RemoteException e) { mService = null; } - return request; } /** @@ -355,10 +504,40 @@ 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 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); + mService.dispose(mDownloadAppName, mSubscriptionId); } else { Log.i(LOG_TAG, "Service already dead"); } diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java index f3ca05840da21..c561741cc80c6 100644 --- a/telephony/java/android/telephony/mbms/DownloadRequest.java +++ b/telephony/java/android/telephony/mbms/DownloadRequest.java @@ -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() { diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java index d3888bdd2c9ae..1b873938a3f20 100644 --- a/telephony/java/android/telephony/mbms/FileInfo.java +++ b/telephony/java/android/telephony/mbms/FileInfo.java @@ -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 CREATOR = new Parcelable.Creator() { @@ -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; + } } diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java index 8e890fd580e37..6646dc8a56dfb 100644 --- a/telephony/java/android/telephony/mbms/FileServiceInfo.java +++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java @@ -30,13 +30,13 @@ import java.util.Map; * @hide */ public class FileServiceInfo extends ServiceInfo implements Parcelable { - public List files; + private final List files; public FileServiceInfo(Map newNames, String newClassName, List newLocales, String newServiceId, Date start, Date end, List newFiles) { super(newNames, newClassName, newLocales, newServiceId, start, end); - files = new ArrayList(newFiles); + files = new ArrayList<>(newFiles); } public static final Parcelable.Creator CREATOR = @@ -68,4 +68,9 @@ public class FileServiceInfo extends ServiceInfo implements Parcelable { public int describeContents() { return 0; } + + public List getFiles() { + return files; + } + } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java index c01ddaedbd889..b51c367deb36c 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java @@ -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 generateUrisForPausedFiles(Context context, DownloadRequest request, List 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 filePathComponents = info.uri.getPathSegments(); + List filePathComponents = info.getUri().getPathSegments(); List requestPathComponents = request.getSourceUri().getPathSegments(); Iterator filePathIter = filePathComponents.iterator(); Iterator requestPathIter = requestPathComponents.iterator(); - LinkedList 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()); diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java index 6b905921dba0b..c4611dc9dcd70 100644 --- a/telephony/java/android/telephony/mbms/MbmsException.java +++ b/telephony/java/android/telephony/mbms/MbmsException.java @@ -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; diff --git a/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java index 9842581cdc021..c4d033bf2886f 100644 --- a/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java +++ b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java @@ -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; } } diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl index 6c2b8167d519f..ff7d233bbf2c4 100755 --- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl +++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl @@ -47,6 +47,7 @@ interface IMbmsDownloadService */ int getFileServices(String appName, int subId, in List serviceClasses); + int setTempFileRootDirectory(String appName, int subId, String rootDirectoryPath); /** * should move the params into a DownloadRequest parcelable */ diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java index 505aeae15355b..9577dd2e3d782 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java @@ -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 serviceClasses) throws - RemoteException { + public int getFileServices(String appName, int subscriptionId, List serviceClasses) + throws RemoteException { + return 0; + } + + @Override + public int setTempFileRootDirectory(String appName, int subscriptionId, + String rootDirectoryPath) throws RemoteException { return 0; } From 727a05b23e4c5bcfba4ce8b8c1eaaa078122735d Mon Sep 17 00:00:00 2001 From: Hall Liu Date: Fri, 16 Jun 2017 14:59:57 -0700 Subject: [PATCH 2/2] Add callback for initialization done Add callback for initialization done in the framework, and listen to it in the testapps. Make initialization asynchronous as well for both download and streaming. Change-Id: Iea7f803df9d2752401b2eca9f6c7375007cac35e --- .../telephony/MbmsDownloadManager.java | 124 ++++++--------- .../telephony/MbmsStreamingManager.java | 144 +++++------------- .../mbms/IMbmsDownloadManagerCallback.aidl | 14 +- .../mbms/IMbmsStreamingManagerCallback.aidl | 23 +-- .../mbms/MbmsDownloadManagerCallback.java | 13 ++ .../android/telephony/mbms/MbmsException.java | 2 +- .../mbms/MbmsStreamingManagerCallback.java | 13 ++ .../android/telephony/mbms/MbmsUtils.java | 30 ++-- 8 files changed, 136 insertions(+), 227 deletions(-) diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java index 597756e7aaf3a..79ee37a168d44 100644 --- a/telephony/java/android/telephony/MbmsDownloadManager.java +++ b/telephony/java/android/telephony/MbmsDownloadManager.java @@ -23,10 +23,8 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.telephony.mbms.IDownloadCallback; import android.telephony.mbms.DownloadRequest; @@ -43,7 +41,7 @@ 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; @@ -183,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 mSubscriptionId = INVALID_SUBSCRIPTION_ID; - private IMbmsDownloadService mService; + private AtomicReference mService = new AtomicReference<>(null); private final IMbmsDownloadManagerCallback mCallback; private final String mDownloadAppName; @@ -202,26 +198,24 @@ public class MbmsDownloadManager { /** * Create a new MbmsDownloadManager using the system default data subscription ID. - * See {@link #createManager(Context, IMbmsDownloadManagerCallback, String, int)} + * 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. Since the - * framework notifies us that binding is complete on the main thread, this method should - * never be called from the main thread of one's app, or else an - * {@link IllegalStateException} will be thrown. + * 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}. * @@ -231,14 +225,9 @@ public class MbmsDownloadManager { * @param subscriptionId The data subscription ID to use * @hide */ - public static MbmsDownloadManager createManager(Context context, + public static MbmsDownloadManager create(Context context, IMbmsDownloadManagerCallback listener, String downloadAppName, int subscriptionId) throws MbmsException { - if (Looper.getMainLooper().isCurrentThread()) { - throw new IllegalStateException( - "createManager must not be called from the main thread."); - } - MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName, subscriptionId); mdm.bindAndInitialize(); @@ -246,50 +235,27 @@ public class MbmsDownloadManager { } 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(); - ServiceInfo mbmsServiceInfo = - MbmsUtils.getMiddlewareServiceInfo(mContext, MBMS_DOWNLOAD_SERVICE_ACTION); - - if (mbmsServiceInfo == null) { - throw new MbmsException(MbmsException.ERROR_NO_SERVICE_INSTALLED); - } - - bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo)); - - mContext.bindService(bindIntent, bindListener, Context.BIND_AUTO_CREATE); - - // Wait until binding is complete - MbmsUtils.waitOnLatchWithTimeout(latch, BIND_TIMEOUT_MS); - - // Call initialize after binding finishes - synchronized (this) { - if (mService == null) { - throw new MbmsException(MbmsException.ERROR_BIND_TIMEOUT_OR_FAILURE); - } - - try { - mService.initialize(mDownloadAppName, mSubscriptionId, mCallback); - } 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); + } + }); } /** @@ -314,17 +280,19 @@ public class MbmsDownloadManager { * {@link MbmsException#ERROR_END_OF_SESSION} */ public void getFileServices(List classList) throws MbmsException { - if (mService == null) { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); } try { - int returnCode = mService.getFileServices(mDownloadAppName, mSubscriptionId, classList); + 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 = null; + mService.set(null); throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); } } @@ -351,7 +319,8 @@ public class MbmsDownloadManager { */ public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory) throws MbmsException { - if (mService == null) { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); } if (!tempFileRootDirectory.exists()) { @@ -368,13 +337,13 @@ public class MbmsDownloadManager { } try { - int result = mService.setTempFileRootDirectory( + int result = downloadService.setTempFileRootDirectory( mDownloadAppName, mSubscriptionId, filePath); if (result != MbmsException.SUCCESS) { throw new MbmsException(result); } } catch (RemoteException e) { - mService = null; + mService.set(null); throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); } @@ -405,7 +374,8 @@ public class MbmsDownloadManager { */ public void download(DownloadRequest request, IDownloadCallback callback) throws MbmsException { - if (mService == null) { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); } @@ -434,9 +404,9 @@ public class MbmsDownloadManager { // TODO: check to make sure destination is clear // TODO: write download request token try { - mService.download(request, callback); + downloadService.download(request, callback); } catch (RemoteException e) { - mService = null; + mService.set(null); } } @@ -536,11 +506,13 @@ public class MbmsDownloadManager { public void dispose() { try { - if (mService != null) { - mService.dispose(mDownloadAppName, mSubscriptionId); - } 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"); diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java index f68e2439971f8..af7f333390d83 100644 --- a/telephony/java/android/telephony/MbmsStreamingManager.java +++ b/telephony/java/android/telephony/MbmsStreamingManager.java @@ -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 mServiceListeners = new LinkedList<>(); - + private AtomicReference 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 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); + } + }); } - } diff --git a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl index 03227d0d9f5ab..ac2f202438966 100755 --- a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl +++ b/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl @@ -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 services); + + void middlewareReady(); } diff --git a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl index cbf0fca461f0d..8116a7f0b7c41 100755 --- a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl +++ b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl @@ -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 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 services); + + void middlewareReady(); } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java index 16fafe415b0f3..5b22199bea1cd 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java @@ -48,4 +48,17 @@ public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.St public void fileServicesUpdated(List 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 + } } diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java index c4611dc9dcd70..8260b728a52f4 100644 --- a/telephony/java/android/telephony/mbms/MbmsException.java +++ b/telephony/java/android/telephony/mbms/MbmsException.java @@ -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; diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java index b3bc8146275eb..27d9878a1966b 100644 --- a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java +++ b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java @@ -61,4 +61,17 @@ public class MbmsStreamingManagerCallback extends IMbmsStreamingManagerCallback. public void activeStreamingServicesUpdated(List 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 + } } diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java index de308053df56d..7d4727563eee4 100644 --- a/telephony/java/android/telephony/mbms/MbmsUtils.java +++ b/telephony/java/android/telephony/mbms/MbmsUtils.java @@ -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); + } }