Merge "Add OWNERS in packages/services/Telephony"
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
index 53f6f7e..e7b05ce 100644
--- a/src/com/android/phone/EmergencyDialer.java
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -513,11 +513,12 @@
      */
     private void placeCall() {
         mLastNumber = mDigits.getText().toString();
-        // Convert into emergency number if necessary
-        // This is required in some regions (e.g. Taiwan).
-        if (PhoneNumberUtils.isConvertToEmergencyNumberEnabled()) {
-            mLastNumber = PhoneNumberUtils.convertToEmergencyNumber(mLastNumber);
-        }
+
+        // Convert into emergency number according to emergency conversion map.
+        // If conversion map is not defined (this is default), this method does
+        // nothing and just returns input number.
+        mLastNumber = PhoneNumberUtils.convertToEmergencyNumber(this, mLastNumber);
+
         if (PhoneNumberUtils.isLocalEmergencyNumber(this, mLastNumber)) {
             if (DBG) Log.d(LOG_TAG, "placing call to " + mLastNumber);
 
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index a7d0205..f51422c 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -19,7 +19,6 @@
 import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
 
 import android.Manifest.permission;
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
@@ -29,6 +28,7 @@
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.net.NetworkStats;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Binder;
@@ -59,8 +59,8 @@
 import android.telephony.NetworkScanRequest;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
-import android.telephony.SmsManager;
 import android.telephony.SignalStrength;
+import android.telephony.SmsManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyHistogram;
@@ -3649,27 +3649,25 @@
     }
 
     /**
-     * Get aggregated video call data usage from all subscriptions since boot.
-     * @return total data usage in bytes
+     * Get aggregated video call data usage since boot.
+     *
+     * @param perUidStats True if requesting data usage per uid, otherwise overall usage.
+     * @return Snapshot of video call data usage
      * {@hide}
      */
     @Override
-    public long getVtDataUsage() {
+    public NetworkStats getVtDataUsage(int subId, boolean perUidStats) {
         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_NETWORK_USAGE_HISTORY,
                 null);
 
-        // NetworkStatsService keeps tracking the active network interface and identity. It will
-        // record the delta with the corresponding network identity. What we need to do here is
-        // returning total video call data usage from all subscriptions since boot.
-
-        // TODO: Add sub id support in the future. We'll need it when we support DSDA and
-        // simultaneous VT calls.
-        final Phone[] phones = PhoneFactory.getPhones();
-        long total = 0;
-        for (Phone phone : phones) {
-            total += phone.getVtDataUsage();
+        // NetworkStatsService keeps tracking the active network interface and identity. It
+        // records the delta with the corresponding network identity. We just return the total video
+        // call data usage snapshot since boot.
+        Phone phone = getPhone(subId);
+        if (phone != null) {
+            return phone.getVtDataUsage(perUidStats);
         }
-        return total;
+        return null;
     }
 
     /**
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index fcc37f4..727907f 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -20,9 +20,9 @@
 import android.media.ToneGenerator;
 import android.telecom.DisconnectCause;
 
+import com.android.phone.ImsUtil;
 import com.android.phone.PhoneGlobals;
 import com.android.phone.common.R;
-import com.android.phone.ImsUtil;
 
 public class DisconnectCauseUtil {
 
@@ -130,6 +130,7 @@
             case android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING:
             case android.telephony.DisconnectCause.IMEI_NOT_ACCEPTED:
             case android.telephony.DisconnectCause.WIFI_LOST:
+            case android.telephony.DisconnectCause.IMS_ACCESS_BLOCKED:
                 return DisconnectCause.ERROR;
 
             case android.telephony.DisconnectCause.DIALED_MMI:
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 9ac40fd..a403d1f 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -152,7 +152,8 @@
                     break;
                 case MSG_SUPP_SERVICE_NOTIFY:
                     Log.v(TelephonyConnection.this, "MSG_SUPP_SERVICE_NOTIFY on phoneId : "
-                            +getPhone().getPhoneId());
+                            + getPhone() != null ? Integer.toString(getPhone().getPhoneId())
+                            : "null");
                     SuppServiceNotification mSsNotification = null;
                     if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
                         mSsNotification =
@@ -1741,7 +1742,7 @@
         boolean isVoWifiEnabled = false;
         if (isIms) {
             ImsPhone imsPhone = (ImsPhone) phone;
-            isVoWifiEnabled = imsPhone.isWifiCallingEnabled();
+            isVoWifiEnabled = ImsUtil.isWfcEnabled(phone.getContext());
         }
         PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
                 .makePstnPhoneAccountHandle(phone.getDefaultPhone())
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 0148df6..148759e 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -196,8 +196,7 @@
 
         // Convert into emergency number if necessary
         // This is required in some regions (e.g. Taiwan).
-        if (!PhoneNumberUtils.isLocalEmergencyNumber(this, number) &&
-                PhoneNumberUtils.isConvertToEmergencyNumberEnabled()) {
+        if (!PhoneNumberUtils.isLocalEmergencyNumber(this, number)) {
             final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
             // We only do the conversion if the phone is not in service. The un-converted
             // emergency numbers will go to the correct destination when the phone is in-service,
@@ -205,7 +204,7 @@
             // service.
             if (phone == null || phone.getServiceState().getState()
                     != ServiceState.STATE_IN_SERVICE) {
-                String convertedNumber = PhoneNumberUtils.convertToEmergencyNumber(number);
+                String convertedNumber = PhoneNumberUtils.convertToEmergencyNumber(this, number);
                 if (!TextUtils.equals(convertedNumber, number)) {
                     Log.i(this, "onCreateOutgoingConnection, converted to emergency number");
                     number = convertedNumber;
diff --git a/testapps/EmbmsServiceTestApp/AndroidManifest.xml b/testapps/EmbmsServiceTestApp/AndroidManifest.xml
index 3adab28..526d3af 100644
--- a/testapps/EmbmsServiceTestApp/AndroidManifest.xml
+++ b/testapps/EmbmsServiceTestApp/AndroidManifest.xml
@@ -23,7 +23,14 @@
             android:launchMode="singleInstance"
             androidprv:systemUserOnly="true">
       <intent-filter>
-          <action android:name="android.telephony.action.EmbmsStreaming" />
+        <action android:name="android.telephony.action.EmbmsStreaming" />
+      </intent-filter>
+    </service>
+    <service android:name="com.android.phone.testapps.embmsmw.EmbmsSampleDownloadService"
+             android:launchMode="singleInstance"
+             androidprv:systemUserOnly="true">
+      <intent-filter>
+        <action android:name="android.telephony.action.EmbmsDownload" />
       </intent-filter>
     </service>
   </application>
diff --git a/testapps/EmbmsServiceTestApp/res/raw/s1.png b/testapps/EmbmsServiceTestApp/res/raw/s1.png
new file mode 100644
index 0000000..353e1b5
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/res/raw/s1.png
Binary files differ
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsSampleDownloadService.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsSampleDownloadService.java
new file mode 100644
index 0000000..360dd15
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsSampleDownloadService.java
@@ -0,0 +1,172 @@
+/*
+ * 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.app.Activity;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.telephony.MbmsDownloadManager;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.IDownloadCallback;
+import android.telephony.mbms.MbmsDownloadReceiver;
+import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.UriPathPair;
+import android.telephony.mbms.vendor.IMbmsDownloadService;
+import android.telephony.mbms.vendor.MbmsDownloadServiceBase;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class EmbmsSampleDownloadService extends Service {
+    private static final String LOG_TAG = "EmbmsSampleDownload";
+    private static final long DOWNLOAD_DELAY_MS = 1000;
+
+    private final IMbmsDownloadService mBinder = new MbmsDownloadServiceBase() {
+        @Override
+        public int download(DownloadRequest downloadRequest, IDownloadCallback listener) {
+            // TODO: move this package name finding logic to initialize()
+            String[] packageNames = getPackageManager().getPackagesForUid(Binder.getCallingUid());
+            if (packageNames == null) {
+                throw new SecurityException("No matching packages found for your UID");
+            }
+
+            if (packageNames.length != 1) {
+                throw new IllegalStateException("More than one package found for your UID");
+            }
+
+            String packageName = packageNames[0];
+
+            mHandler.post(() -> sendFdRequest(downloadRequest, packageName, 1));
+            return MbmsException.SUCCESS;
+        }
+    };
+
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        mHandlerThread = new HandlerThread("EmbmsTestDownloadServiceWorker");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        return mBinder.asBinder();
+    }
+
+    private void sendFdRequest(DownloadRequest request, String packageName, int numFds) {
+        // 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_FD_COUNT, numFds);
+        ComponentName mbmsReceiverComponent = new ComponentName(packageName,
+                MbmsDownloadReceiver.class.getCanonicalName());
+        requestIntent.setComponent(mbmsReceiverComponent);
+
+        // Send as an ordered broadcast, using a BroadcastReceiver to capture the result
+        // containing UriPathPairs.
+        sendOrderedBroadcast(requestIntent,
+                null, // receiverPermission
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        Bundle resultExtras = getResultExtras(false);
+                        // This delay is to emulate the time it'd usually take to fetch the file
+                        // off the network.
+                        mHandler.postDelayed(
+                                () -> performDownload(request, packageName, resultExtras),
+                                DOWNLOAD_DELAY_MS);
+                    }
+                },
+                null, // scheduler
+                Activity.RESULT_OK,
+                null, // initialData
+                null /* initialExtras */);
+    }
+
+    private void performDownload(DownloadRequest request, String packageName, Bundle extras) {
+        int result = MbmsDownloadManager.RESULT_SUCCESSFUL;
+        List<UriPathPair> tempFiles = extras.getParcelableArrayList(
+                MbmsDownloadManager.EXTRA_FREE_URI_LIST);
+        Uri tempFilePathUri = tempFiles.get(0).getFilePathUri();
+        Uri freeTempFileUri = tempFiles.get(0).getContentUri();
+
+        try {
+            // Get the ParcelFileDescriptor for the single temp file we requested
+            ParcelFileDescriptor tempFile = getContentResolver().openFileDescriptor(
+                    freeTempFileUri, "rw");
+            OutputStream destinationStream =
+                    new ParcelFileDescriptor.AutoCloseOutputStream(tempFile);
+
+            // This is how you get the native fd
+            Log.i(LOG_TAG, "Native fd: " + tempFile.getFd());
+
+            // Open the picture we have in our res/raw directory
+            InputStream image = getResources().openRawResource(R.raw.s1);
+
+            // Copy it into the temp file in the app's file space (crudely)
+            byte[] imageBuffer = new byte[image.available()];
+            image.read(imageBuffer);
+            destinationStream.write(imageBuffer);
+            destinationStream.flush();
+        } catch (IOException e) {
+            result = MbmsDownloadManager.RESULT_CANCELLED;
+        }
+
+        Intent downloadResultIntent =
+                new Intent(MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL);
+        downloadResultIntent.putExtra(MbmsDownloadManager.EXTRA_REQUEST, request);
+        downloadResultIntent.putExtra(MbmsDownloadManager.EXTRA_FINAL_URI, tempFilePathUri);
+        ArrayList<Uri> tempFileList = new ArrayList<>(1);
+        tempFileList.add(tempFilePathUri);
+        downloadResultIntent.getExtras().putParcelableArrayList(
+                MbmsDownloadManager.EXTRA_TEMP_LIST, tempFileList);
+        downloadResultIntent.putExtra(MbmsDownloadManager.EXTRA_RESULT, result);
+
+        ComponentName mbmsReceiverComponent = new ComponentName(packageName,
+                MbmsDownloadReceiver.class.getCanonicalName());
+        downloadResultIntent.setComponent(mbmsReceiverComponent);
+
+        sendOrderedBroadcast(downloadResultIntent,
+                null, // receiverPermission
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        int resultCode = getResultCode();
+                        Log.i(LOG_TAG, "Download result ack: " + resultCode);
+                    }
+                },
+                null, // scheduler
+                Activity.RESULT_OK,
+                null, // initialData
+                null /* initialExtras */);
+    }
+}
diff --git a/testapps/EmbmsTestDownloadApp/Android.mk b/testapps/EmbmsTestDownloadApp/Android.mk
new file mode 100644
index 0000000..66ca25b
--- /dev/null
+++ b/testapps/EmbmsTestDownloadApp/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+
+# Build the Sample Embms Download frontend
+include $(CLEAR_VARS)
+
+src_dirs := src
+res_dirs := res
+
+LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
+LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
+
+LOCAL_PACKAGE_NAME := EmbmsTestDownloadApp
+
+LOCAL_CERTIFICATE := platform
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/testapps/EmbmsTestDownloadApp/AndroidManifest.xml b/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
new file mode 100644
index 0000000..b81b928
--- /dev/null
+++ b/testapps/EmbmsTestDownloadApp/AndroidManifest.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.phone.testapps.embmsdownload">
+    <application android:label="EmbmsTestDownloadApp">
+        <activity
+            android:name=".EmbmsTestDownloadApp"
+            android:label="EmbmsDownloadFrontend">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <!-- This is the receiver defined by the MBMS api. -->
+        <!-- TODO: protect this with a permission -->
+        <receiver
+            android:name="android.telephony.mbms.MbmsDownloadReceiver"
+            android:enabled="true"
+            android:exported="true">
+        </receiver>
+
+        <!-- This is the receiver defined by app to receive the download-done intent that was
+         passed into DownloadRequest. -->
+        <receiver
+            android:name="com.android.phone.testapps.embmsdownload.DownloadCompletionReceiver"
+            android:enabled="true">
+        </receiver>
+
+        <!-- This is the provider that apps must declare in their manifest. It allows the
+        middleware to obtain file descriptors to temp files in the app's file space -->
+        <!-- grantUriPermissions must be set to true -->
+        <provider
+            android:name="android.telephony.mbms.MbmsTempFileProvider"
+            android:authorities="com.android.phone.testapps.embmsdownload"
+            android:exported="false"
+            android:grantUriPermissions="true">
+            <!-- This is a mandatory piece of metadata that contains the directory where temp
+            files should be put. It should be a relative path from Context.getFilesDir() or from
+            Context.getExternalStorageDir(null), depending on the value of the
+            use-external-storage metadata. -->
+            <meta-data android:name="temp-file-path" android:value="/mbms-temp/"/>
+
+            <!-- This tells the provider whether to use the sdcard partition for the temp files or
+            not. -->
+            <meta-data android:name="use-external-storage" android:value="false"/>
+        </provider>
+
+        <!-- This is a mandatory piece of metadata that contains the authority string for the
+        provider declared above -->
+        <meta-data
+            android:name="mbms-file-provider-authority"
+            android:value="com.android.phone.testapps.embmsdownload"/>
+    </application>
+</manifest>
+
diff --git a/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml b/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml
new file mode 100644
index 0000000..092a08a
--- /dev/null
+++ b/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/progress_window"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"/>
+    <ImageView
+        android:id="@+id/sample_picture"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_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/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" >
+    </GridLayout>
+</LinearLayout>
diff --git a/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml b/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml
new file mode 100644
index 0000000..4683fa2
--- /dev/null
+++ b/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <string name="bind_button">Bind</string>
+    <string name="request_dl_button">Request DL</string>
+</resources>
\ No newline at end of file
diff --git a/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/DownloadCompletionReceiver.java b/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/DownloadCompletionReceiver.java
new file mode 100644
index 0000000..b4cf1d4
--- /dev/null
+++ b/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/DownloadCompletionReceiver.java
@@ -0,0 +1,30 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class DownloadCompletionReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (EmbmsTestDownloadApp.DOWNLOAD_DONE_ACTION.equals(intent.getAction())) {
+            EmbmsTestDownloadApp.getInstance().onDownloadDone();
+        }
+    }
+}
diff --git a/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/EmbmsTestDownloadApp.java b/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/EmbmsTestDownloadApp.java
new file mode 100644
index 0000000..51e3a66
--- /dev/null
+++ b/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/EmbmsTestDownloadApp.java
@@ -0,0 +1,122 @@
+/*
+ * 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.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.telephony.MbmsDownloadManager;
+import android.telephony.mbms.DownloadCallback;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.MbmsDownloadManagerCallback;
+import android.telephony.mbms.MbmsException;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.IOException;
+
+public class EmbmsTestDownloadApp extends Activity {
+    public static final String DOWNLOAD_DONE_ACTION =
+            "com.android.phone.testapps.embmsdownload.DOWNLOAD_DONE";
+
+    private static final String TRIGGER_DOWNLOAD_ACTION =
+            "com.android.phone.testapps.embmsmw.TRIGGER_DOWNLOAD";
+    private static final String EXTRA_DOWNLOAD_REQUEST =
+            "com.android.phone.testapps.embmsmw.EXTRA_DOWNLOAD_REQUEST";
+    private static final String APP_NAME = "SampleAppName";
+
+    private static EmbmsTestDownloadApp sInstance;
+
+    private MbmsDownloadManagerCallback mCallback = new MbmsDownloadManagerCallback() {};
+
+    private MbmsDownloadManager mDownloadManager;
+    private Handler mHandler;
+    private HandlerThread mHandlerThread;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        sInstance = this;
+        mHandlerThread = new HandlerThread("EmbmsDownloadWorker");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+
+        File destination = null;
+        try {
+            destination = new File(getFilesDir(), "image.png").getCanonicalFile();
+        } catch (IOException e) {
+            // ignore, this is temp code
+        }
+
+        Intent completionIntent = new Intent(DOWNLOAD_DONE_ACTION);
+        completionIntent.setClass(this, DownloadCompletionReceiver.class);
+
+        DownloadRequest request = new DownloadRequest.Builder()
+                .setId(0)
+                .setServiceInfo(null) // TODO: this isn't supposed to be null, but not yet used
+                .setSource(null) // ditto
+                .setDest(Uri.fromFile(destination))
+                .setAppIntent(completionIntent)
+                .build();
+
+        Button bindButton = (Button) findViewById(R.id.bind_button);
+        bindButton.setOnClickListener((view) -> mHandler.post(() -> {
+            try {
+                mDownloadManager = MbmsDownloadManager.createManager(this, mCallback, APP_NAME);
+            } catch (MbmsException e) {
+                Toast.makeText(EmbmsTestDownloadApp.this,
+                        "caught MbmsException: " + e.getErrorCode(), Toast.LENGTH_SHORT).show();
+            }
+        }));
+
+        Button requestDlButton = (Button) findViewById(R.id.request_dl_button);
+        requestDlButton.setOnClickListener((view) ->  {
+            mDownloadManager.download(request, new DownloadCallback());
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mHandlerThread.quit();
+        sInstance = null;
+    }
+
+    public static EmbmsTestDownloadApp getInstance() {
+        return sInstance;
+    }
+
+    // TODO: assumes that process does not get killed. Replace with more robust alternative
+    public void onDownloadDone() {
+        ImageView picture = (ImageView) findViewById(R.id.sample_picture);
+        File imageFile = new File(getFilesDir(), "image.png");
+        if (!imageFile.exists()) {
+            Toast.makeText(this, "Download done but destination doesn't exist", Toast.LENGTH_SHORT)
+                    .show();
+            return;
+        }
+        runOnUiThread(() -> picture.setImageURI(Uri.fromFile(imageFile)));
+    }
+}