Embms Download setup
Adds the EmbmsTestDownloadApp as a frontend for the download flow
Add a EmbmsDownloadService to EmbmsServiceTestApp
Test: testapps
Change-Id: Ic5e7323ca217dbb4323eac00861788fa19c5f2ef
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)));
+ }
+}