Add download request tokens and enforce destination clear am: 2043f70acb am: 8763c625a3
am: fc156f357a

Change-Id: I929fd3306a7bb8bbc2eeb31eb1732c99ec0fb743
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
index 79ee37a..ee81fd0 100644
--- a/telephony/java/android/telephony/MbmsDownloadManager.java
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -388,21 +388,10 @@
             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
+
+        checkValidDownloadDestination(request);
+        writeDownloadRequestToken(request);
         try {
             downloadService.download(request, callback);
         } catch (RemoteException e) {
@@ -435,6 +424,7 @@
      * <li>ERROR_MSDC_UNKNOWN_REQUEST</li>
      */
     public int cancelDownload(DownloadRequest downloadRequest) {
+        // TODO: don't forget to delete the token
         return 0;
     }
 
@@ -518,4 +508,54 @@
             Log.i(LOG_TAG, "Remote exception while disposing of service");
         }
     }
+
+    private void writeDownloadRequestToken(DownloadRequest request) {
+        // TODO: figure out when this token eventually gets deleted
+        File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForRequest(mContext, request);
+        if (!tempFileLocation.exists()) {
+            tempFileLocation.mkdirs();
+        }
+        String downloadTokenFileName = request.getHash()
+                + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
+        File token = new File(tempFileLocation, downloadTokenFileName);
+        if (token.exists()) {
+            Log.w(LOG_TAG, "Download token " + downloadTokenFileName + " already exists");
+            return;
+        }
+        try {
+            if (!token.createNewFile()) {
+                throw new RuntimeException("Failed to create download token for request "
+                        + request);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to create download token for request " + request
+                    + " due to IOException " + e);
+        }
+    }
+
+    /**
+     * Verifies the following:
+     * If a request is multi-part,
+     *     1. Destination Uri must exist and be a directory
+     *     2. Directory specified must contain no files.
+     * Otherwise
+     *     1. The file specified by the destination Uri must not exist.
+     */
+    private void checkValidDownloadDestination(DownloadRequest request) {
+        File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
+        if (request.isMultipartDownload()) {
+            if (!toFile.isDirectory()) {
+                throw new IllegalArgumentException("Multipart download must specify valid " +
+                        "destination directory.");
+            }
+            if (toFile.listFiles().length > 0) {
+                throw new IllegalArgumentException("Destination directory must be clear of all " +
+                        "files.");
+            }
+        } else {
+            if (toFile.exists()) {
+                throw new IllegalArgumentException("Destination file must not exist.");
+            }
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index c561741..907b0cb 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -20,15 +20,22 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Base64;
 
 import java.lang.IllegalStateException;
 import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 
 /**
  * A Parcelable class describing a pending Cell-Broadcast download request
  * @hide
  */
 public class DownloadRequest implements Parcelable {
+    // Version code used to keep token calculation consistent.
+    private static final int CURRENT_VERSION = 1;
+
     /** @hide */
     public static class Builder {
         private int id;
@@ -37,6 +44,7 @@
         private Uri dest;
         private int subscriptionId;
         private String appIntent;
+        private int version = CURRENT_VERSION;
 
         public Builder setId(int id) {
             this.id = id;
@@ -68,9 +76,14 @@
             return this;
         }
 
+        public Builder setVersion(int version) {
+            this.version = version;
+            return this;
+        }
+
         public DownloadRequest build() {
             return new DownloadRequest(id, serviceInfo, source, dest,
-                    subscriptionId, appIntent, null);
+                    subscriptionId, appIntent, null, version);
         }
     }
 
@@ -80,11 +93,12 @@
     private final Uri destinationUri;
     private final int subscriptionId;
     private final String serializedResultIntentForApp;
+    private final int version;
     private String appName; // not the Android app Name, the embms app name
 
     private DownloadRequest(int id, FileServiceInfo serviceInfo,
             Uri source, Uri dest,
-            int sub, String appIntent, String name) {
+            int sub, String appIntent, String name, int version) {
         downloadId = id;
         fileServiceInfo = serviceInfo;
         sourceUri = source;
@@ -92,6 +106,7 @@
         subscriptionId = sub;
         serializedResultIntentForApp = appIntent;
         appName = name;
+        this.version = version;
     }
 
     public static DownloadRequest copy(DownloadRequest other) {
@@ -106,6 +121,7 @@
         subscriptionId = dr.subscriptionId;
         serializedResultIntentForApp = dr.serializedResultIntentForApp;
         appName = dr.appName;
+        version = dr.version;
     }
 
     private DownloadRequest(Parcel in) {
@@ -116,6 +132,7 @@
         subscriptionId = in.readInt();
         serializedResultIntentForApp = in.readString();
         appName = in.readString();
+        version = in.readInt();
     }
 
     public int describeContents() {
@@ -130,6 +147,7 @@
         out.writeInt(subscriptionId);
         out.writeString(serializedResultIntentForApp);
         out.writeString(appName);
+        out.writeInt(version);
     }
 
     public int getDownloadId() {
@@ -172,6 +190,10 @@
         return appName;
     }
 
+    public int getVersion() {
+        return version;
+    }
+
     public static final Parcelable.Creator<DownloadRequest> CREATOR =
             new Parcelable.Creator<DownloadRequest>() {
         public DownloadRequest createFromParcel(Parcel in) {
@@ -181,4 +203,35 @@
             return new DownloadRequest[size];
         }
     };
+
+    /**
+     * @hide
+     */
+    public boolean isMultipartDownload() {
+        // TODO: figure out what qualifies a request as a multipart download request.
+        return getSourceUri().getLastPathSegment() != null &&
+                getSourceUri().getLastPathSegment().contains("*");
+    }
+
+    /**
+     * Retrieves the hash string that should be used as the filename when storing a token for
+     * this DownloadRequest.
+     * @hide
+     */
+    public String getHash() {
+        MessageDigest digest;
+        try {
+            digest = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("Could not get sha256 hash object");
+        }
+        if (version >= 1) {
+            // Hash the source URI, destination URI, and the app intent
+            digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
+            digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
+            digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+        }
+        // Add updates for future versions here
+        return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP);
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index b51c367..6ff177c 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -41,6 +41,7 @@
 public class MbmsDownloadReceiver extends BroadcastReceiver {
     private static final String LOG_TAG = "MbmsDownloadReceiver";
     private static final String TEMP_FILE_SUFFIX = ".embms.temp";
+    public static final String DOWNLOAD_TOKEN_SUFFIX = ".download_token";
     private static final int MAX_TEMP_FILE_RETRIES = 5;
 
     public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
@@ -50,7 +51,7 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (!verifyIntentContents(intent)) {
+        if (!verifyIntentContents(context, intent)) {
             setResultCode(1 /* TODO: define error constants */);
             return;
         }
@@ -69,7 +70,7 @@
         // TODO: Add handling for ACTION_CLEANUP
     }
 
-    private boolean verifyIntentContents(Intent intent) {
+    private boolean verifyIntentContents(Context context, Intent intent) {
         if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
             if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) {
                 Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
@@ -93,8 +94,19 @@
                         "temp file. Ignoring.");
                 return false;
             }
+            DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
+            String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
+            File expectedTokenFile = new File(
+                    MbmsUtils.getEmbmsTempFileDirForRequest(context, request),
+                    expectedTokenFileName);
+            if (!expectedTokenFile.exists()) {
+                Log.w(LOG_TAG, "Supplied download request does not match a token that we have. " +
+                        "Expected " + expectedTokenFile);
+                return false;
+            }
             return true;
         } else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
+            // TODO: get rid of the request argument for a file descriptor request.
             if (!intent.hasExtra(MbmsDownloadManager.EXTRA_REQUEST)) {
                 Log.w(LOG_TAG, "Temp file request not include the associated request. Ignoring.");
                 return false;
@@ -112,7 +124,6 @@
 
     private void moveDownloadedFile(Context context, Intent intent) {
         DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
-        // TODO: check request against token
         Intent intentForApp = request.getIntentForApp();
 
         int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT,
@@ -149,7 +160,6 @@
     }
 
     private void cleanupPostMove(Context context, Intent intent) {
-        // TODO: account for in-use temp files
         DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
         if (request == null) {
             Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
@@ -199,7 +209,7 @@
 
     private ArrayList<UriPathPair> generateFreshTempFiles(Context context, DownloadRequest request,
             int freshFdCount) {
-        File tempFileDir = getEmbmsTempFileDirForRequest(context, request);
+        File tempFileDir = MbmsUtils.getEmbmsTempFileDirForRequest(context, request);
         if (!tempFileDir.exists()) {
             tempFileDir.mkdirs();
         }
@@ -345,24 +355,14 @@
             return false;
         }
 
-        if (!MbmsUtils.isContainedIn(getEmbmsTempFileDirForRequest(context, request), tempFile)) {
+        if (!MbmsUtils.isContainedIn(
+                MbmsUtils.getEmbmsTempFileDirForRequest(context, request), tempFile)) {
             return false;
         }
 
         return true;
     }
 
-    /**
-     * 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);
-
-        // TODO: better naming scheme for temp file dirs
-        String tempFileDirName = String.valueOf(request.getFileServiceInfo().getServiceId());
-        return new File(embmsTempFileDir, tempFileDirName);
-    }
-
     private String getFileProviderAuthorityCached(Context context) {
         if (mFileProviderAuthorityCache != null) {
             return mFileProviderAuthorityCache;
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index 7d47275..b332681 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -85,4 +85,14 @@
 
         context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
     }
+
+    /**
+     * Returns a File linked to the directory used to store temp files for this request
+     */
+    public static File getEmbmsTempFileDirForRequest(Context context, DownloadRequest request) {
+        File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context);
+
+        String tempFileDirName = String.valueOf(request.getFileServiceInfo().getServiceId());
+        return new File(embmsTempFileDir, tempFileDirName);
+    }
 }