diff options
9 files changed, 347 insertions, 108 deletions
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java index c8c6d014a059..597756e7aaf3 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 @@ -144,6 +154,14 @@ public class MbmsDownloadManager { "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 - * <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 { + 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<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); + 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 f3ca05840da2..c561741cc80c 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 d3888bdd2c9a..1b873938a3f2 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<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; + } } diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java index 8e890fd580e3..6646dc8a56df 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<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; + } + } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java index c01ddaedbd88..b51c367deb36 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<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()); diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java index 6b905921dba0..c4611dc9dcd7 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 9842581cdc02..c4d033bf2886 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(); - } - - String tmpFilePath = metadata.getString(META_DATA_TEMP_FILE_DIRECTORY); - if (tmpFilePath == null) { - tmpFilePath = DEFAULT_TOP_LEVEL_TEMP_DIRECTORY; + 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); } - 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 6c2b8167d519..ff7d233bbf2c 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<String> 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 505aeae15355..9577dd2e3d78 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) + public void initialize(String appName, int subscriptionId, + IMbmsDownloadManagerCallback listener) throws RemoteException { + } + + @Override + public int getFileServices(String appName, int subscriptionId, List<String> serviceClasses) throws RemoteException { + return 0; } @Override - public int getFileServices(String appName, int subId, List<String> serviceClasses) throws - RemoteException { + public int setTempFileRootDirectory(String appName, int subscriptionId, + String rootDirectoryPath) throws RemoteException { return 0; } |