Merge changes from topics 'embms-cleanup-action', 'embms-download-token'

* changes:
  Modify testapps to exercise ACTION_CLEANUP
  Change destination paths to comply with enforcement
diff --git a/testapps/EmbmsServiceTestApp/AndroidManifest.xml b/testapps/EmbmsServiceTestApp/AndroidManifest.xml
index 526d3af..edcb5cb 100644
--- a/testapps/EmbmsServiceTestApp/AndroidManifest.xml
+++ b/testapps/EmbmsServiceTestApp/AndroidManifest.xml
@@ -33,6 +33,10 @@
         <action android:name="android.telephony.action.EmbmsDownload" />
       </intent-filter>
     </service>
+
+    <receiver android:name="com.android.phone.testapps.embmsmw.SideChannelReceiver"
+              android:enabled="true"
+              android:exported="true"/>
   </application>
 </manifest>
 
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsSampleDownloadService.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsSampleDownloadService.java
index f7abeb9..67083d7 100644
--- a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsSampleDownloadService.java
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsSampleDownloadService.java
@@ -47,6 +47,7 @@
 import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -152,29 +153,98 @@
         }
     };
 
+    private static EmbmsSampleDownloadService sInstance = null;
+
     private final Map<FrontendAppIdentifier, IMbmsDownloadManagerCallback> mAppCallbacks =
             new HashMap<>();
     private final Map<FrontendAppIdentifier, ComponentName> mAppReceivers = new HashMap<>();
     private final Map<FrontendAppIdentifier, String> mAppTempFileRoots = new HashMap<>();
     private final Map<FrontendAppIdentifier, Boolean> mDoesAppHaveActiveDownload =
             new ConcurrentHashMap<>();
+    // A map of app-identifiers to (maps of service-ids to sets of temp file uris in use)
+    private final Map<FrontendAppIdentifier, Map<String, Set<Uri>>> mTempFilesInUse =
+            new ConcurrentHashMap<>();
 
     private HandlerThread mHandlerThread;
     private Handler mHandler;
+    private int mDownloadDelayFactor = 1;
 
     @Override
     public IBinder onBind(Intent intent) {
         mHandlerThread = new HandlerThread("EmbmsTestDownloadServiceWorker");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
+        sInstance = this;
         return mBinder.asBinder();
     }
 
+    public static EmbmsSampleDownloadService getInstance() {
+        return sInstance;
+    }
+
+    public void requestCleanup() {
+        // Assume that there's only one app, and do it for all the services.
+        FrontendAppIdentifier registeredAppId = mAppReceivers.keySet().iterator().next();
+        ComponentName appReceiver = mAppReceivers.values().iterator().next();
+        for (FileServiceInfo fileServiceInfo :
+                FileServiceRepository.getInstance(this).getAllFileServices()) {
+            Intent cleanupIntent = new Intent(MbmsDownloadManager.ACTION_CLEANUP);
+            cleanupIntent.setComponent(appReceiver);
+            cleanupIntent.putExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO, fileServiceInfo);
+            cleanupIntent.putExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT,
+                    mAppTempFileRoots.get(registeredAppId));
+            Set<Uri> tempFilesInUse =
+                    mTempFilesInUse.getOrDefault(registeredAppId, Collections.emptyMap())
+                            .getOrDefault(fileServiceInfo.getServiceId(), Collections.emptySet());
+            cleanupIntent.putExtra(MbmsDownloadManager.EXTRA_TEMP_FILES_IN_USE,
+                    new ArrayList<>(tempFilesInUse));
+            sendBroadcast(cleanupIntent);
+        }
+    }
+
+    public void requestExtraTempFiles(FileServiceInfo serviceInfo) {
+        // Assume one app, and do it for the specified service.
+        FrontendAppIdentifier registeredAppId = mAppReceivers.keySet().iterator().next();
+        ComponentName appReceiver = mAppReceivers.values().iterator().next();
+        Intent fdRequestIntent = new Intent(MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST);
+        fdRequestIntent.putExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO, serviceInfo);
+        fdRequestIntent.putExtra(MbmsDownloadManager.EXTRA_FD_COUNT, 10);
+        fdRequestIntent.putExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT,
+                mAppTempFileRoots.get(registeredAppId));
+        fdRequestIntent.setComponent(appReceiver);
+
+        sendOrderedBroadcast(fdRequestIntent,
+                null, // receiverPermission
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        int result = getResultCode();
+                        Bundle extras = getResultExtras(false);
+                        Log.i(LOG_TAG, "Received extra temp files. Result " + result);
+                        if (extras != null) {
+                            Log.i(LOG_TAG, "Got "
+                                    + extras.getParcelableArrayList(
+                                    MbmsDownloadManager.EXTRA_FREE_URI_LIST).size()
+                                    + " fds");
+                        }
+                    }
+                },
+                null, // scheduler
+                Activity.RESULT_OK,
+                null, // initialData
+                null /* initialExtras */);
+    }
+
+    public void delayDownloads(int factor) {
+        mDownloadDelayFactor = factor;
+    }
+
     private void sendFdRequest(DownloadRequest request, FrontendAppIdentifier appKey) {
         int numFds = getNumFdsNeededForRequest(request);
         // Compose the FILE_DESCRIPTOR_REQUEST_INTENT
         Intent requestIntent = new Intent(MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST);
-        requestIntent.putExtra(MbmsDownloadManager.EXTRA_REQUEST, request);
+        requestIntent.putExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO,
+                request.getFileServiceInfo());
         requestIntent.putExtra(MbmsDownloadManager.EXTRA_FD_COUNT, numFds);
         requestIntent.putExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT,
                 mAppTempFileRoots.get(appKey));
@@ -219,14 +289,18 @@
                 break;
             }
             UriPathPair tempFile = tempFiles.get(i);
+            addTempFileInUse(appKey, request.getFileServiceInfo().getServiceId(),
+                    tempFile.getFilePathUri());
             FileInfo fileToDownload = filesToDownload.get(i);
             final boolean isLastFile = i == tempFiles.size() - 1;
             mHandler.postDelayed(() -> {
                 downloadSingleFile(appKey, request, tempFile, fileToDownload);
+                removeTempFileInUse(appKey, request.getFileServiceInfo().getServiceId(),
+                        tempFile.getFilePathUri());
                 if (isLastFile) {
                     mDoesAppHaveActiveDownload.put(appKey, false);
                 }
-            }, FILE_SEPARATION_DELAY * i);
+            }, FILE_SEPARATION_DELAY * i * mDownloadDelayFactor);
         }
     }
 
@@ -296,4 +370,30 @@
     private int getNumFdsNeededForRequest(DownloadRequest request) {
         return request.getFileServiceInfo().getFiles().size();
     }
+
+    private void addTempFileInUse(FrontendAppIdentifier appKey, String serviceId, Uri tempFileUri) {
+        Map<String, Set<Uri>> tempFileByService = mTempFilesInUse.get(appKey);
+        if (tempFileByService == null) {
+            tempFileByService = new ConcurrentHashMap<>();
+            mTempFilesInUse.put(appKey, tempFileByService);
+        }
+        Set<Uri> tempFilesInUse = tempFileByService.get(serviceId);
+        if (tempFilesInUse == null) {
+            tempFilesInUse = ConcurrentHashMap.newKeySet();
+            tempFileByService.put(serviceId, tempFilesInUse);
+        }
+        tempFilesInUse.add(tempFileUri);
+    }
+
+    private void removeTempFileInUse(FrontendAppIdentifier appKey, String serviceId,
+            Uri tempFileUri) {
+        Set<Uri> tempFilesInUse = mTempFilesInUse.getOrDefault(appKey, Collections.emptyMap())
+                .getOrDefault(serviceId, Collections.emptySet());
+        if (tempFilesInUse.contains(tempFileUri)) {
+            tempFilesInUse.remove(tempFileUri);
+        } else {
+            Log.w(LOG_TAG, "Trying to remove unknown temp file in use " + tempFileUri + " for app" +
+                    appKey + " and service id " + serviceId);
+        }
+    }
 }
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/FileServiceRepository.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/FileServiceRepository.java
index 4d0b6c3..a771a1a 100644
--- a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/FileServiceRepository.java
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/FileServiceRepository.java
@@ -71,6 +71,10 @@
                 .collect(Collectors.toList());
     }
 
+    public List<FileServiceInfo> getAllFileServices() {
+        return new ArrayList<>(mIdToServiceInfo.values());
+    }
+
     public FileServiceInfo getFileServiceInfoForId(String serviceId) {
         return mIdToServiceInfo.getOrDefault(serviceId, null);
     }
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/SideChannelReceiver.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/SideChannelReceiver.java
new file mode 100644
index 0000000..c9d38a0
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/SideChannelReceiver.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.testapps.embmsmw;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.telephony.mbms.FileServiceInfo;
+import android.util.Log;
+
+/**
+ * Class for triggering artificial events from the frontend app. These would normally not come
+ * from the frontend app in a real embms implementation.
+ */
+public class SideChannelReceiver extends BroadcastReceiver {
+    public static final String ACTION_TRIGGER_CLEANUP =
+            "com.android.phone.testapps.embmsmw.TRIGGER_CLEANUP";
+    public static final String ACTION_REQUEST_SPURIOUS_TEMP_FILES =
+            "com.android.phone.testapps.embmsmw.REQUEST_SPURIOUS_TEMP_FILES";
+    public static final String ACTION_DELAY_DOWNLOAD =
+            "com.android.phone.testapps.embmsmw.DELAY_DOWNLOAD";
+
+    public static final String EXTRA_SERVICE_INFO =
+            "com.android.phone.testapps.embmsmw.SERVICE_INFO";
+    public static final String EXTRA_DELAY_FACTOR =
+            "com.android.phone.testapps.embmsmw.DELAY_FACTOR";
+
+    private static final String LOG_TAG = "EmbmsSampleMwSC";
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        EmbmsSampleDownloadService downloadService = EmbmsSampleDownloadService.getInstance();
+        if (downloadService == null) {
+            Log.w(LOG_TAG, "don't have instance of dl service");
+            return;
+        }
+        switch (intent.getAction()) {
+            case ACTION_TRIGGER_CLEANUP:
+                downloadService.requestCleanup();
+                break;
+            case ACTION_REQUEST_SPURIOUS_TEMP_FILES:
+                FileServiceInfo serviceInfo = intent.getParcelableExtra(EXTRA_SERVICE_INFO);
+                downloadService.requestExtraTempFiles(serviceInfo);
+                break;
+            case ACTION_DELAY_DOWNLOAD:
+                // Increase download latency by a certain factor
+                downloadService.delayDownloads(intent.getIntExtra(EXTRA_DELAY_FACTOR, 1));
+                break;
+
+        }
+    }
+}
diff --git a/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml b/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml
index 048e3dc..bee6b5d 100644
--- a/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml
+++ b/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml
@@ -32,37 +32,71 @@
         android:scrollbars="horizontal"
         android:horizontalSpacing="10dp"
         android:gravity="center"/>
-    <Button
-        android:id="@+id/bind_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/bind_button" />
-    <Button
-        android:id="@+id/set_temp_root_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/set_temp_root_button" />
-    <Button
-        android:id="@+id/get_file_services_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/get_file_services_button" />
-    <Spinner
-        android:id="@+id/available_file_services"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"/>
-
-    <Button
-        android:id="@+id/request_dl_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/request_dl_button" />
-
     <GridLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:columnCount="2"
         android:orientation="vertical" >
+        <Button
+            android:id="@+id/bind_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_row="0"
+            android:layout_column="0"
+            android:text="@string/bind_button" />
+        <Button
+            android:id="@+id/set_temp_root_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_row="0"
+            android:layout_column="1"
+            android:text="@string/set_temp_root_button" />
+        <Button
+            android:id="@+id/get_file_services_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_row="1"
+            android:layout_column="0"
+            android:text="@string/get_file_services_button" />
+        <Button
+            android:id="@+id/request_dl_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_row="1"
+            android:layout_column="1"
+            android:text="@string/request_dl_button" />
+        <Button
+            android:id="@+id/request_cleanup_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_row="2"
+            android:layout_column="0"
+            android:text="@string/request_cleanup_button" />
+        <Button
+            android:id="@+id/request_spurious_temp_files_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_row="2"
+            android:layout_column="1"
+            android:text="@string/request_spurious_temp_files_button" />
+        <Button
+            android:id="@+id/delay_download_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_row="3"
+            android:layout_column="0"
+            android:text="@string/delay_download_button" />
+        <NumberPicker
+            android:id="@+id/delay_factor"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_row="3"
+            android:layout_column="1"/>
     </GridLayout>
+
+    <Spinner
+        android:id="@+id/available_file_services"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
 </LinearLayout>
diff --git a/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml b/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml
index 2d99962..7459591 100644
--- a/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml
+++ b/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml
@@ -20,4 +20,7 @@
     <string name="request_dl_button">Request DL</string>
     <string name="get_file_services_button">Fetch file services</string>
     <string name="set_temp_root_button">Set temp file root</string>
+    <string name="request_cleanup_button">Cleanup</string>
+    <string name="request_spurious_temp_files_button">Request more temp files</string>
+    <string name="delay_download_button">Delay download</string>
 </resources>
\ No newline at end of file
diff --git a/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/EmbmsTestDownloadApp.java b/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/EmbmsTestDownloadApp.java
index 3937bbd..fd7deee 100644
--- a/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/EmbmsTestDownloadApp.java
+++ b/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/EmbmsTestDownloadApp.java
@@ -38,6 +38,7 @@
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.ImageView;
+import android.widget.NumberPicker;
 import android.widget.Spinner;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -234,6 +235,25 @@
 
             performDownload(serviceInfo);
         });
+
+        Button requestCleanupButton = (Button) findViewById(R.id.request_cleanup_button);
+        requestCleanupButton.setOnClickListener((view) ->
+                SideChannel.triggerCleanup(EmbmsTestDownloadApp.this));
+
+        Button requestSpuriousTempFilesButton =
+                (Button) findViewById(R.id.request_spurious_temp_files_button);
+        requestSpuriousTempFilesButton.setOnClickListener((view) ->
+                SideChannel.requestSpuriousTempFiles(EmbmsTestDownloadApp.this,
+                        (FileServiceInfo) serviceSelector.getSelectedItem()));
+
+        NumberPicker downloadDelayPicker = (NumberPicker) findViewById(R.id.delay_factor);
+        downloadDelayPicker.setMinValue(1);
+        downloadDelayPicker.setMaxValue(50);
+
+        Button delayDownloadButton = (Button) findViewById(R.id.delay_download_button);
+        delayDownloadButton.setOnClickListener((view) ->
+                SideChannel.delayDownloads(EmbmsTestDownloadApp.this,
+                        downloadDelayPicker.getValue()));
     }
 
     @Override
@@ -270,12 +290,19 @@
 
     private void performDownload(FileServiceInfo info) {
         File destination = null;
+        Uri.Builder sourceUriBuilder = new Uri.Builder()
+                .scheme(FILE_DOWNLOAD_SCHEME)
+                .authority(FILE_AUTHORITY);
         try {
             if (info.getFiles().size() > 1) {
-                destination = new File(getFilesDir(), "images/").getCanonicalFile();
+                destination = new File(getFilesDir(), "images/animals/").getCanonicalFile();
                 destination.mkdirs();
+                clearDirectory(destination);
+                sourceUriBuilder.path("/*");
             } else {
                 destination = new File(getFilesDir(), "images/image.png").getCanonicalFile();
+                destination.delete();
+                sourceUriBuilder.path("/image.png");
             }
         } catch (IOException e) {
             // ignore
@@ -284,16 +311,10 @@
         Intent completionIntent = new Intent(DOWNLOAD_DONE_ACTION);
         completionIntent.setClass(this, DownloadCompletionReceiver.class);
 
-        Uri sourceUri = new Uri.Builder()
-                .scheme(FILE_DOWNLOAD_SCHEME)
-                .authority(FILE_AUTHORITY)
-                .path("/")
-                .build();
-
         DownloadRequest request = new DownloadRequest.Builder()
                 .setId(0)
                 .setServiceInfo(info)
-                .setSource(sourceUri)
+                .setSource(sourceUriBuilder.build())
                 .setDest(Uri.fromFile(destination))
                 .setAppIntent(completionIntent)
                 .setSubscriptionId(SubscriptionManager.getDefaultSubscriptionId())
@@ -305,6 +326,14 @@
             Toast.makeText(EmbmsTestDownloadApp.this,
                     "caught MbmsException: " + e.getErrorCode(), Toast.LENGTH_SHORT).show();
         }
+    }
 
+    private static void clearDirectory(File directory) {
+        for (File file: directory.listFiles()) {
+            if (file.isDirectory()) {
+                clearDirectory(file);
+            }
+            file.delete();
+        }
     }
 }
diff --git a/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/SideChannel.java b/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/SideChannel.java
new file mode 100644
index 0000000..6e7ebe9
--- /dev/null
+++ b/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/SideChannel.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.testapps.embmsdownload;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.telephony.mbms.FileServiceInfo;
+
+public class SideChannel {
+    public static final String ACTION_TRIGGER_CLEANUP =
+            "com.android.phone.testapps.embmsmw.TRIGGER_CLEANUP";
+    public static final String ACTION_REQUEST_SPURIOUS_TEMP_FILES =
+            "com.android.phone.testapps.embmsmw.REQUEST_SPURIOUS_TEMP_FILES";
+    public static final String ACTION_DELAY_DOWNLOAD =
+            "com.android.phone.testapps.embmsmw.DELAY_DOWNLOAD";
+
+    public static final String EXTRA_SERVICE_INFO =
+            "com.android.phone.testapps.embmsmw.SERVICE_INFO";
+    public static final String EXTRA_DELAY_FACTOR =
+            "com.android.phone.testapps.embmsmw.DELAY_FACTOR";
+
+    public static final ComponentName MIDDLEWARE_RECEIVER = new ComponentName(
+            "com.android.phone.testapps.embmsmw",
+            "com.android.phone.testapps.embmsmw.SideChannelReceiver");
+
+    public static void triggerCleanup(Context context) {
+        Intent intent  = new Intent(ACTION_TRIGGER_CLEANUP);
+        intent.setComponent(MIDDLEWARE_RECEIVER);
+        context.sendBroadcast(intent);
+    }
+
+    public static void requestSpuriousTempFiles(Context context, FileServiceInfo serviceInfo) {
+        Intent intent = new Intent(ACTION_REQUEST_SPURIOUS_TEMP_FILES);
+        intent.putExtra(EXTRA_SERVICE_INFO, serviceInfo);
+        intent.setComponent(MIDDLEWARE_RECEIVER);
+        context.sendBroadcast(intent);
+    }
+
+    public static void delayDownloads(Context context, int delay) {
+        Intent intent = new Intent(ACTION_DELAY_DOWNLOAD);
+        intent.putExtra(EXTRA_DELAY_FACTOR, delay);
+        intent.setComponent(MIDDLEWARE_RECEIVER);
+        context.sendBroadcast(intent);
+    }
+}