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;      }  |