Embms download part 2 am: eab21d93e8
am: a74425d9c6
Change-Id: I78ec6f7dbd1b32d452b8a1558359eb308588687c
diff --git a/testapps/EmbmsServiceTestApp/Android.mk b/testapps/EmbmsServiceTestApp/Android.mk
index 874fad2..d8c4493 100644
--- a/testapps/EmbmsServiceTestApp/Android.mk
+++ b/testapps/EmbmsServiceTestApp/Android.mk
@@ -13,8 +13,7 @@
LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
-# Change the following to "debug" to build the EmbmsTestService into the userdebug build.
-LOCAL_MODULE_TAGS := optional
-#LOCAL_MODULE_TAGS := debug
+# Uncomment the following line to build the EmbmsTestService into the userdebug build.
+# LOCAL_MODULE_TAGS := debug
include $(BUILD_PACKAGE)
diff --git a/testapps/EmbmsServiceTestApp/res/raw/sheep.png b/testapps/EmbmsServiceTestApp/res/raw/sheep.png
new file mode 100644
index 0000000..650966c
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/res/raw/sheep.png
Binary files differ
diff --git a/testapps/EmbmsServiceTestApp/res/raw/snake.png b/testapps/EmbmsServiceTestApp/res/raw/snake.png
new file mode 100644
index 0000000..6fa6c8b
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/res/raw/snake.png
Binary files differ
diff --git a/testapps/EmbmsServiceTestApp/res/raw/s1.png b/testapps/EmbmsServiceTestApp/res/raw/suntree.png
similarity index 100%
rename from testapps/EmbmsServiceTestApp/res/raw/s1.png
rename to testapps/EmbmsServiceTestApp/res/raw/suntree.png
Binary files differ
diff --git a/testapps/EmbmsServiceTestApp/res/raw/unicorn.png b/testapps/EmbmsServiceTestApp/res/raw/unicorn.png
new file mode 100644
index 0000000..3146344
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/res/raw/unicorn.png
Binary files differ
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/AppActiveStreams.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/AppActiveStreams.java
index 6a0f61e..9b3cc6b 100644
--- a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/AppActiveStreams.java
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/AppActiveStreams.java
@@ -67,10 +67,10 @@
// Stores the state and callback per service ID.
private final Map<String, StreamCallbackWithState> mStreamStates = new HashMap<>();
- private final StreamingAppIdentifier mAppIdentifier;
+ private final FrontendAppIdentifier mAppIdentifier;
private final Random mRand = new Random();
- public AppActiveStreams(StreamingAppIdentifier appIdentifier) {
+ public AppActiveStreams(FrontendAppIdentifier appIdentifier) {
mAppIdentifier = appIdentifier;
}
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 360dd15..490f3fa 100644
--- a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsSampleDownloadService.java
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsSampleDownloadService.java
@@ -29,10 +29,13 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.telephony.MbmsDownloadManager;
import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.FileInfo;
+import android.telephony.mbms.FileServiceInfo;
import android.telephony.mbms.IDownloadCallback;
-import android.telephony.mbms.MbmsDownloadReceiver;
+import android.telephony.mbms.IMbmsDownloadManagerCallback;
import android.telephony.mbms.MbmsException;
import android.telephony.mbms.UriPathPair;
import android.telephony.mbms.vendor.IMbmsDownloadService;
@@ -44,33 +47,111 @@
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;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
public class EmbmsSampleDownloadService extends Service {
+ private static final Set<String> ALLOWED_PACKAGES = new HashSet<String>() {{
+ add("com.android.phone.testapps.embmsdownload");
+ }};
+
private static final String LOG_TAG = "EmbmsSampleDownload";
+ private static final long SEND_FILE_SERVICE_INFO_DELAY = 500;
private static final long DOWNLOAD_DELAY_MS = 1000;
+ private static final long FILE_SEPARATION_DELAY = 500;
private final IMbmsDownloadService mBinder = new MbmsDownloadServiceBase() {
@Override
- public int download(DownloadRequest downloadRequest, IDownloadCallback listener) {
- // TODO: move this package name finding logic to initialize()
+ public void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener) {
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");
+ boolean isUidAllowed = Arrays.stream(packageNames).anyMatch(ALLOWED_PACKAGES::contains);
+ if (!isUidAllowed) {
+ throw new SecurityException("No packages for your UID are allowed to use this " +
+ "service");
}
- String packageName = packageNames[0];
+ FrontendAppIdentifier appKey =
+ new FrontendAppIdentifier(Binder.getCallingUid(), appName, subId);
+ if (!mAppCallbacks.containsKey(appKey)) {
+ mAppCallbacks.put(appKey, listener);
+ ComponentName appReceiver = MbmsDownloadManager.getAppReceiverFromUid(
+ EmbmsSampleDownloadService.this, Binder.getCallingUid());
+ mAppReceivers.put(appKey, appReceiver);
+ } else {
+ // Stick the error callback on a different thread so that we're not calling back
+ // to the app on the same thread.
+ mHandler.post(() -> {
+ try {
+ listener.error(MbmsException.ERROR_ALREADY_INITIALIZED, "");
+ } catch (RemoteException e) {
+ // ignore, it was an error anyway
+ }
+ });
+ }
+ }
- mHandler.post(() -> sendFdRequest(downloadRequest, packageName, 1));
+ @Override
+ public int getFileServices(String appName, int subscriptionId,
+ List<String> serviceClasses) throws RemoteException {
+ FrontendAppIdentifier appKey =
+ new FrontendAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+ checkInitialized(appKey);
+
+ List<FileServiceInfo> serviceInfos =
+ FileServiceRepository.getInstance(EmbmsSampleDownloadService.this)
+ .getFileServicesForClasses(serviceClasses);
+
+ mHandler.postDelayed(() -> {
+ try {
+ IMbmsDownloadManagerCallback appCallback = mAppCallbacks.get(appKey);
+ appCallback.fileServicesUpdated(serviceInfos);
+ } catch (RemoteException e) {
+ // TODO: call dispose
+ }
+ }, SEND_FILE_SERVICE_INFO_DELAY);
+ return MbmsException.SUCCESS;
+ }
+
+ @Override
+ public int setTempFileRootDirectory(String appName, int subscriptionId,
+ String rootDirectoryPath) throws RemoteException {
+ FrontendAppIdentifier appKey =
+ new FrontendAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+ checkInitialized(appKey);
+
+ if (mDoesAppHaveActiveDownload.getOrDefault(appKey, false)) {
+ return MbmsException.ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT;
+ }
+ mAppTempFileRoots.put(appKey, rootDirectoryPath);
+ return MbmsException.SUCCESS;
+ }
+
+ @Override
+ public int download(DownloadRequest downloadRequest, IDownloadCallback listener) {
+ FrontendAppIdentifier appKey = new FrontendAppIdentifier(
+ Binder.getCallingUid(), downloadRequest.getAppName(),
+ downloadRequest.getSubscriptionId());
+ checkInitialized(appKey);
+
+ mHandler.post(() -> sendFdRequest(downloadRequest, appKey));
return MbmsException.SUCCESS;
}
};
+ 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<>();
+
private HandlerThread mHandlerThread;
private Handler mHandler;
@@ -82,14 +163,15 @@
return mBinder.asBinder();
}
- private void sendFdRequest(DownloadRequest request, String packageName, int numFds) {
+ 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_FD_COUNT, numFds);
- ComponentName mbmsReceiverComponent = new ComponentName(packageName,
- MbmsDownloadReceiver.class.getCanonicalName());
- requestIntent.setComponent(mbmsReceiverComponent);
+ requestIntent.putExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT,
+ mAppTempFileRoots.get(appKey));
+ requestIntent.setComponent(mAppReceivers.get(appKey));
// Send as an ordered broadcast, using a BroadcastReceiver to capture the result
// containing UriPathPairs.
@@ -102,7 +184,7 @@
// This delay is to emulate the time it'd usually take to fetch the file
// off the network.
mHandler.postDelayed(
- () -> performDownload(request, packageName, resultExtras),
+ () -> performDownload(request, appKey, resultExtras),
DOWNLOAD_DELAY_MS);
}
},
@@ -112,25 +194,52 @@
null /* initialExtras */);
}
- private void performDownload(DownloadRequest request, String packageName, Bundle extras) {
- int result = MbmsDownloadManager.RESULT_SUCCESSFUL;
+ private void performDownload(DownloadRequest request, FrontendAppIdentifier appKey,
+ Bundle extras) {
List<UriPathPair> tempFiles = extras.getParcelableArrayList(
MbmsDownloadManager.EXTRA_FREE_URI_LIST);
- Uri tempFilePathUri = tempFiles.get(0).getFilePathUri();
- Uri freeTempFileUri = tempFiles.get(0).getContentUri();
+ List<FileInfo> filesToDownload = request.getFileServiceInfo().getFiles();
+ if (tempFiles.size() != filesToDownload.size()) {
+ Log.w(LOG_TAG, "Different numbers of temp files and files to download...");
+ }
+
+ // Go through the files one-by-one and send them to the frontend app with a delay between
+ // each one.
+ mDoesAppHaveActiveDownload.put(appKey, true);
+ for (int i = 0; i < tempFiles.size(); i++) {
+ if (i >= filesToDownload.size()) {
+ break;
+ }
+ UriPathPair tempFile = tempFiles.get(i);
+ FileInfo fileToDownload = filesToDownload.get(i);
+ final boolean isLastFile = i == tempFiles.size() - 1;
+ mHandler.postDelayed(() -> {
+ downloadSingleFile(appKey, request, tempFile, fileToDownload);
+ if (isLastFile) {
+ mDoesAppHaveActiveDownload.put(appKey, false);
+ }
+ }, FILE_SEPARATION_DELAY * i);
+ }
+ }
+
+ private void downloadSingleFile(FrontendAppIdentifier appKey, DownloadRequest request,
+ UriPathPair tempFile, FileInfo fileToDownload) {
+ int result = MbmsDownloadManager.RESULT_SUCCESSFUL;
try {
// Get the ParcelFileDescriptor for the single temp file we requested
- ParcelFileDescriptor tempFile = getContentResolver().openFileDescriptor(
- freeTempFileUri, "rw");
+ ParcelFileDescriptor tempFileFd = getContentResolver().openFileDescriptor(
+ tempFile.getContentUri(), "rw");
OutputStream destinationStream =
- new ParcelFileDescriptor.AutoCloseOutputStream(tempFile);
+ new ParcelFileDescriptor.AutoCloseOutputStream(tempFileFd);
// This is how you get the native fd
- Log.i(LOG_TAG, "Native fd: " + tempFile.getFd());
+ Log.i(LOG_TAG, "Native fd: " + tempFileFd.getFd());
+ int resourceId = FileServiceRepository.getInstance(this)
+ .getResourceForFileUri(fileToDownload.getUri());
// Open the picture we have in our res/raw directory
- InputStream image = getResources().openRawResource(R.raw.s1);
+ InputStream image = getResources().openRawResource(resourceId);
// Copy it into the temp file in the app's file space (crudely)
byte[] imageBuffer = new byte[image.available()];
@@ -144,16 +253,17 @@
Intent downloadResultIntent =
new Intent(MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL);
downloadResultIntent.putExtra(MbmsDownloadManager.EXTRA_REQUEST, request);
- downloadResultIntent.putExtra(MbmsDownloadManager.EXTRA_FINAL_URI, tempFilePathUri);
+ downloadResultIntent.putExtra(MbmsDownloadManager.EXTRA_FINAL_URI,
+ tempFile.getFilePathUri());
+ downloadResultIntent.putExtra(MbmsDownloadManager.EXTRA_FILE_INFO, fileToDownload);
+ downloadResultIntent.putExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT,
+ mAppTempFileRoots.get(appKey));
ArrayList<Uri> tempFileList = new ArrayList<>(1);
- tempFileList.add(tempFilePathUri);
+ tempFileList.add(tempFile.getFilePathUri());
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);
+ downloadResultIntent.setComponent(mAppReceivers.get(appKey));
sendOrderedBroadcast(downloadResultIntent,
null, // receiverPermission
@@ -169,4 +279,14 @@
null, // initialData
null /* initialExtras */);
}
+
+ private void checkInitialized(FrontendAppIdentifier appKey) {
+ if (!mAppCallbacks.containsKey(appKey)) {
+ throw new IllegalStateException("Not yet initialized");
+ }
+ }
+
+ private int getNumFdsNeededForRequest(DownloadRequest request) {
+ return request.getFileServiceInfo().getFiles().size();
+ }
}
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsTestStreamingService.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsTestStreamingService.java
index 9758d49..11a9bde 100644
--- a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsTestStreamingService.java
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/EmbmsTestStreamingService.java
@@ -54,7 +54,7 @@
private static final int SEND_STREAMING_SERVICES_LIST = 1;
- private final Map<StreamingAppIdentifier, IMbmsStreamingManagerCallback> mAppCallbacks =
+ private final Map<FrontendAppIdentifier, IMbmsStreamingManagerCallback> mAppCallbacks =
new HashMap<>();
private HandlerThread mHandlerThread;
@@ -63,7 +63,7 @@
switch (msg.what) {
case SEND_STREAMING_SERVICES_LIST:
SomeArgs args = (SomeArgs) msg.obj;
- StreamingAppIdentifier appKey = (StreamingAppIdentifier) args.arg1;
+ FrontendAppIdentifier appKey = (FrontendAppIdentifier) args.arg1;
List<StreamingServiceInfo> services = (List) args.arg2;
IMbmsStreamingManagerCallback appCallback = mAppCallbacks.get(appKey);
if (appCallback != null) {
@@ -91,8 +91,8 @@
"service");
}
- StreamingAppIdentifier appKey =
- new StreamingAppIdentifier(Binder.getCallingUid(), appName, subId);
+ FrontendAppIdentifier appKey =
+ new FrontendAppIdentifier(Binder.getCallingUid(), appName, subId);
if (!mAppCallbacks.containsKey(appKey)) {
mAppCallbacks.put(appKey, listener);
} else {
@@ -104,8 +104,8 @@
@Override
public int getStreamingServices(String appName, int subscriptionId,
List<String> serviceClasses) {
- StreamingAppIdentifier appKey =
- new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+ FrontendAppIdentifier appKey =
+ new FrontendAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
checkInitialized(appKey);
List<StreamingServiceInfo> serviceInfos =
@@ -125,8 +125,8 @@
@Override
public int startStreaming(String appName, int subscriptionId, String serviceId,
IStreamingServiceCallback callback) {
- StreamingAppIdentifier appKey =
- new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+ FrontendAppIdentifier appKey =
+ new FrontendAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
checkInitialized(appKey);
checkServiceExists(serviceId);
@@ -143,8 +143,8 @@
@Override
public Uri getPlaybackUri(String appName, int subscriptionId, String serviceId) {
- StreamingAppIdentifier appKey =
- new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+ FrontendAppIdentifier appKey =
+ new FrontendAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
checkInitialized(appKey);
checkServiceExists(serviceId);
@@ -157,8 +157,8 @@
@Override
public void stopStreaming(String appName, int subscriptionId, String serviceId) {
- StreamingAppIdentifier appKey =
- new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+ FrontendAppIdentifier appKey =
+ new FrontendAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
checkInitialized(appKey);
checkServiceExists(serviceId);
@@ -167,8 +167,8 @@
@Override
public void disposeStream(String appName, int subscriptionId, String serviceId) {
- StreamingAppIdentifier appKey =
- new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+ FrontendAppIdentifier appKey =
+ new FrontendAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
checkInitialized(appKey);
checkServiceExists(serviceId);
@@ -178,8 +178,8 @@
@Override
public void dispose(String appName, int subscriptionId) {
- StreamingAppIdentifier appKey =
- new StreamingAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
+ FrontendAppIdentifier appKey =
+ new FrontendAppIdentifier(Binder.getCallingUid(), appName, subscriptionId);
checkInitialized(appKey);
Log.i(TAG, "Disposing app " + appName);
@@ -208,7 +208,7 @@
Log.d(TAG, s);
}
- private void checkInitialized(StreamingAppIdentifier appKey) {
+ private void checkInitialized(FrontendAppIdentifier appKey) {
if (!mAppCallbacks.containsKey(appKey)) {
throw new IllegalStateException("Not yet initialized");
}
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/FileServiceRepository.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/FileServiceRepository.java
new file mode 100644
index 0000000..4d0b6c3
--- /dev/null
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/FileServiceRepository.java
@@ -0,0 +1,137 @@
+/*
+ * 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.Context;
+import android.net.Uri;
+import android.telephony.mbms.FileInfo;
+import android.telephony.mbms.FileServiceInfo;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class FileServiceRepository {
+ private int sServiceIdCounter = 0;
+ private final Map<String, FileServiceInfo> mIdToServiceInfo = new HashMap<>();
+ private final Map<Uri, Integer> mFileUriToResource = new HashMap<>();
+
+ private static final String FILE_DOWNLOAD_SCHEME = "filedownload";
+ private static final String FILE_AUTHORITY = "com.android.phone.testapps";
+
+ private static FileServiceRepository sInstance;
+ public static FileServiceRepository getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new FileServiceRepository(context);
+ }
+ return sInstance;
+ }
+
+ private final Context mContext;
+
+ private FileServiceRepository(Context context) {
+ mContext = context;
+ Uri sunAndTree = initFile("sunAndTree.png", R.raw.suntree);
+ Uri snake = initFile("animals/snake.png", R.raw.snake);
+ Uri unicorn = initFile("animals/unicorn.png", R.raw.unicorn);
+ Uri sheep = initFile("animals/sheep.png", R.raw.sheep);
+
+ createFileService("Class1", sunAndTree);
+ createFileService("Class1", snake, unicorn, sheep);
+ }
+
+ public List<FileServiceInfo> getFileServicesForClasses(
+ List<String> serviceClasses) {
+ return mIdToServiceInfo.values().stream()
+ .filter((info) -> serviceClasses.contains(info.getClassName()))
+ .collect(Collectors.toList());
+ }
+
+ public FileServiceInfo getFileServiceInfoForId(String serviceId) {
+ return mIdToServiceInfo.getOrDefault(serviceId, null);
+ }
+
+ public int getResourceForFileUri(Uri uri) {
+ return mFileUriToResource.getOrDefault(uri, 0);
+ }
+
+ private void createFileService(String className, Uri... filesIncluded) {
+ sServiceIdCounter++;
+ String id = "FileServiceId[" + sServiceIdCounter + "]";
+ List<Locale> locales = new ArrayList<Locale>(2) {{
+ add(Locale.US);
+ add(Locale.UK);
+ }};
+ Map<Locale, String> localeDict = new HashMap<Locale, String>() {{
+ put(Locale.US, "File Source " + sServiceIdCounter);
+ put(Locale.UK, "File Source with extra vowels " + sServiceIdCounter);
+ }};
+ List<FileInfo> fileInfos = Arrays.stream(filesIncluded)
+ .map(this::getFileInfoForUri)
+ .collect(Collectors.toList());
+ FileServiceInfo info = new FileServiceInfo(localeDict, className, locales,
+ id, new Date(System.currentTimeMillis() - 10000),
+ new Date(System.currentTimeMillis() + 10000),
+ fileInfos);
+ mIdToServiceInfo.put(id, info);
+ }
+
+ private Uri initFile(String relPath, int resource) {
+ Uri uri = new Uri.Builder()
+ .scheme(FILE_DOWNLOAD_SCHEME)
+ .authority(FILE_AUTHORITY)
+ .path(relPath)
+ .build();
+ mFileUriToResource.put(uri, resource);
+ return uri;
+ }
+
+ private FileInfo getFileInfoForUri(Uri uri) {
+ if (!mFileUriToResource.containsKey(uri)) {
+ return null;
+ }
+
+ InputStream fileIn = mContext.getResources().openRawResource(mFileUriToResource.get(uri));
+ int fileSize;
+ byte[] buffer;
+ byte[] md5Sum;
+ try {
+ fileSize = fileIn.available();
+ buffer = new byte[fileIn.available()];
+ fileIn.read(buffer);
+ } catch (IOException e) {
+ // ignore and just return null
+ return null;
+ }
+ try {
+ md5Sum = MessageDigest.getInstance("MD5").digest(buffer);
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ }
+ return new FileInfo(uri, "application/octet-stream", fileSize, md5Sum);
+ }
+}
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamingAppIdentifier.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/FrontendAppIdentifier.java
similarity index 90%
rename from testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamingAppIdentifier.java
rename to testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/FrontendAppIdentifier.java
index 7cbb14a..4fb46aa 100644
--- a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamingAppIdentifier.java
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/FrontendAppIdentifier.java
@@ -16,12 +16,12 @@
package com.android.phone.testapps.embmsmw;
-public class StreamingAppIdentifier {
+public class FrontendAppIdentifier {
private final int uid;
private final String appName;
private final int subscriptionId;
- public StreamingAppIdentifier(int uid, String appName, int subscriptionId) {
+ public FrontendAppIdentifier(int uid, String appName, int subscriptionId) {
this.uid = uid;
this.appName = appName;
this.subscriptionId = subscriptionId;
@@ -48,7 +48,7 @@
return false;
}
- StreamingAppIdentifier that = (StreamingAppIdentifier) o;
+ FrontendAppIdentifier that = (FrontendAppIdentifier) o;
if (uid != that.uid) {
return false;
diff --git a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamStateTracker.java b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamStateTracker.java
index e340b11..bb9494d 100644
--- a/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamStateTracker.java
+++ b/testapps/EmbmsServiceTestApp/src/com/android/phone/testapps/embmsmw/StreamStateTracker.java
@@ -27,10 +27,10 @@
public class StreamStateTracker {
private static final String LOG_TAG = "MbmsStreamStateTracker";
- private static final Map<StreamingAppIdentifier, AppActiveStreams>
+ private static final Map<FrontendAppIdentifier, AppActiveStreams>
sPerAppStreamStates = new HashMap<>();
- public static int getStreamingState(StreamingAppIdentifier appIdentifier, String serviceId) {
+ public static int getStreamingState(FrontendAppIdentifier appIdentifier, String serviceId) {
AppActiveStreams appStreams = sPerAppStreamStates.get(appIdentifier);
if (appStreams == null) {
return StreamingService.STATE_STOPPED;
@@ -38,7 +38,7 @@
return appStreams.getStateForService(serviceId);
}
- public static void startStreaming(StreamingAppIdentifier appIdentifier, String serviceId,
+ public static void startStreaming(FrontendAppIdentifier appIdentifier, String serviceId,
IStreamingServiceCallback callback) {
AppActiveStreams appStreams = sPerAppStreamStates.get(appIdentifier);
if (appStreams == null) {
@@ -49,7 +49,7 @@
appStreams.startStreaming(serviceId, callback);
}
- public static void stopStreaming(StreamingAppIdentifier appIdentifier, String serviceId) {
+ public static void stopStreaming(FrontendAppIdentifier appIdentifier, String serviceId) {
Log.i(LOG_TAG, "Stopping stream " + serviceId);
AppActiveStreams appStreams = sPerAppStreamStates.get(appIdentifier);
if (appStreams == null) {
@@ -59,7 +59,7 @@
appStreams.stopStreaming(serviceId);
}
- public static void dispose(StreamingAppIdentifier appIdentifier, String serviceId) {
+ public static void dispose(FrontendAppIdentifier appIdentifier, String serviceId) {
AppActiveStreams appStreams = sPerAppStreamStates.get(appIdentifier);
if (appStreams == null) {
// We have no record of this app, so we can just move on.
@@ -68,7 +68,7 @@
appStreams.dispose(serviceId);
}
- public static void disposeAll(StreamingAppIdentifier appIdentifier) {
+ public static void disposeAll(FrontendAppIdentifier appIdentifier) {
sPerAppStreamStates.remove(appIdentifier);
}
diff --git a/testapps/EmbmsTestDownloadApp/Android.mk b/testapps/EmbmsTestDownloadApp/Android.mk
index 66ca25b..080e5b0 100644
--- a/testapps/EmbmsTestDownloadApp/Android.mk
+++ b/testapps/EmbmsTestDownloadApp/Android.mk
@@ -3,6 +3,10 @@
# Build the Sample Embms Download frontend
include $(CLEAR_VARS)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v7-recyclerview \
+ android-support-v4
+
src_dirs := src
res_dirs := res
diff --git a/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml b/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml
index 092a08a..07c7d37 100644
--- a/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml
+++ b/testapps/EmbmsTestDownloadApp/res/layout/activity_main.xml
@@ -25,17 +25,27 @@
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.support.v7.widget.RecyclerView
+ android:id="@+id/downloaded_images"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center"/>
-
+ 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/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"
diff --git a/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml b/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml
index 4683fa2..a29aea3 100644
--- a/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml
+++ b/testapps/EmbmsTestDownloadApp/res/values/donottranslate_strings.xml
@@ -18,4 +18,5 @@
<resources>
<string name="bind_button">Bind</string>
<string name="request_dl_button">Request DL</string>
+ <string name="get_file_services_button">Fetch file services</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
index b4cf1d4..ef9e672 100644
--- a/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/DownloadCompletionReceiver.java
+++ b/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/DownloadCompletionReceiver.java
@@ -19,12 +19,21 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
+import android.telephony.MbmsDownloadManager;
public class DownloadCompletionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (EmbmsTestDownloadApp.DOWNLOAD_DONE_ACTION.equals(intent.getAction())) {
- EmbmsTestDownloadApp.getInstance().onDownloadDone();
+ int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT,
+ MbmsDownloadManager.RESULT_CANCELLED);
+ if (result != MbmsDownloadManager.RESULT_SUCCESSFUL) {
+ EmbmsTestDownloadApp.getInstance().onDownloadFailed(result);
+ }
+ Uri completedFile = intent.getParcelableExtra(
+ MbmsDownloadManager.EXTRA_COMPLETED_FILE_URI);
+ EmbmsTestDownloadApp.getInstance().onDownloadDone(completedFile);
}
}
}
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 51e3a66..b7d3e01 100644
--- a/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/EmbmsTestDownloadApp.java
+++ b/testapps/EmbmsTestDownloadApp/src/com/android/phone/testapps/embmsdownload/EmbmsTestDownloadApp.java
@@ -17,41 +17,142 @@
package com.android.phone.testapps.embmsdownload;
import android.app.Activity;
-import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
import android.telephony.MbmsDownloadManager;
+import android.telephony.SubscriptionManager;
import android.telephony.mbms.DownloadCallback;
import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.FileServiceInfo;
import android.telephony.mbms.MbmsDownloadManagerCallback;
import android.telephony.mbms.MbmsException;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
public class EmbmsTestDownloadApp extends Activity {
+ private static final String LOG_TAG = "EmbmsDownloadApp";
+
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 final String CUSTOM_EMBMS_TEMP_FILE_LOCATION = "customEmbmsTempFiles";
+
+ private static final String FILE_AUTHORITY = "com.android.phone.testapps";
+ private static final String FILE_DOWNLOAD_SCHEME = "filedownload";
private static EmbmsTestDownloadApp sInstance;
- private MbmsDownloadManagerCallback mCallback = new MbmsDownloadManagerCallback() {};
+ private static final class ImageAdapter
+ extends RecyclerView.Adapter<ImageAdapter.ImageViewHolder> {
+ static class ImageViewHolder extends RecyclerView.ViewHolder {
+ public ImageView imageView;
+ public ImageViewHolder(ImageView view) {
+ super(view);
+ imageView = view;
+ }
+ }
+
+ private final List<Uri> mImageUris = new ArrayList<>();
+
+ @Override
+ public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ ImageView view = new ImageView(parent.getContext());
+ view.setAdjustViewBounds(true);
+ view.setMaxHeight(500);
+ return new ImageViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(ImageViewHolder holder, int position) {
+ holder.imageView.setImageURI(mImageUris.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return mImageUris.size();
+ }
+
+ public void addImage(Uri uri) {
+ mImageUris.add(uri);
+ notifyDataSetChanged();
+ }
+ }
+
+ private final class FileServiceInfoAdapter
+ extends ArrayAdapter<FileServiceInfo> {
+ public FileServiceInfoAdapter(Context context) {
+ super(context, android.R.layout.simple_spinner_item);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ FileServiceInfo info = getItem(position);
+ TextView result = new TextView(EmbmsTestDownloadApp.this);
+ result.setText(info.getNames().get(info.getLocales().get(0)));
+ return result;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ FileServiceInfo info = getItem(position);
+ TextView result = new TextView(EmbmsTestDownloadApp.this);
+ String text = "name="
+ + info.getNames().get(info.getLocales().get(0))
+ + ", "
+ + "numFiles="
+ + info.getFiles().size();
+ result.setText(text);
+ return result;
+ }
+
+ public void update(List<FileServiceInfo> services) {
+ clear();
+ addAll(services);
+ }
+ }
+
+ private MbmsDownloadManagerCallback mCallback = new MbmsDownloadManagerCallback() {
+ @Override
+ public void error(int errorCode, String message) {
+ runOnUiThread(() -> Toast.makeText(EmbmsTestDownloadApp.this,
+ "Error " + errorCode + ": " + message, Toast.LENGTH_SHORT).show());
+ }
+
+ @Override
+ public void fileServicesUpdated(List<FileServiceInfo> services) {
+ EmbmsTestDownloadApp.this.runOnUiThread(() ->
+ Toast.makeText(EmbmsTestDownloadApp.this,
+ "Got services length " + services.size(),
+ Toast.LENGTH_SHORT).show());
+ updateFileServicesList(services);
+ }
+ };
private MbmsDownloadManager mDownloadManager;
private Handler mHandler;
private HandlerThread mHandlerThread;
+ private FileServiceInfoAdapter mFileServiceInfoAdapter;
+ private ImageAdapter mImageAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -62,38 +163,61 @@
mHandlerThread = new HandlerThread("EmbmsDownloadWorker");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ mFileServiceInfoAdapter = new FileServiceInfoAdapter(this);
- 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();
+ RecyclerView downloadedImages = (RecyclerView) findViewById(R.id.downloaded_images);
+ downloadedImages.setLayoutManager(
+ new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
+ mImageAdapter = new ImageAdapter();
+ downloadedImages.setAdapter(mImageAdapter);
Button bindButton = (Button) findViewById(R.id.bind_button);
bindButton.setOnClickListener((view) -> mHandler.post(() -> {
try {
mDownloadManager = MbmsDownloadManager.createManager(this, mCallback, APP_NAME);
+ File downloadDir = new File(EmbmsTestDownloadApp.this.getFilesDir(),
+ CUSTOM_EMBMS_TEMP_FILE_LOCATION);
+ downloadDir.mkdirs();
+ mDownloadManager.setTempFileRootDirectory(downloadDir);
+ runOnUiThread(() -> Toast.makeText(EmbmsTestDownloadApp.this,
+ "Initialization done", Toast.LENGTH_SHORT).show());
} catch (MbmsException e) {
- Toast.makeText(EmbmsTestDownloadApp.this,
- "caught MbmsException: " + e.getErrorCode(), Toast.LENGTH_SHORT).show();
+ runOnUiThread(() -> Toast.makeText(EmbmsTestDownloadApp.this,
+ "caught MbmsException: " + e.getErrorCode(), Toast.LENGTH_SHORT).show());
}
}));
+ Button getFileServicesButton = (Button) findViewById(R.id.get_file_services_button);
+ getFileServicesButton.setOnClickListener((view) -> mHandler.post(() -> {
+ try {
+ mDownloadManager.getFileServices(Collections.singletonList("Class1"));
+ } catch (MbmsException e) {
+ runOnUiThread(() -> Toast.makeText(EmbmsTestDownloadApp.this,
+ "caught MbmsException: " + e.getErrorCode(), Toast.LENGTH_SHORT).show());
+ }
+ }));
+
+ final Spinner serviceSelector = (Spinner) findViewById(R.id.available_file_services);
+ mFileServiceInfoAdapter.setDropDownViewResource(
+ android.R.layout.simple_spinner_dropdown_item);
+ serviceSelector.setAdapter(mFileServiceInfoAdapter);
+
Button requestDlButton = (Button) findViewById(R.id.request_dl_button);
requestDlButton.setOnClickListener((view) -> {
- mDownloadManager.download(request, new DownloadCallback());
+ if (mDownloadManager == null) {
+ Toast.makeText(EmbmsTestDownloadApp.this,
+ "No download service bound", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ FileServiceInfo serviceInfo =
+ (FileServiceInfo) serviceSelector.getSelectedItem();
+ if (serviceInfo == null) {
+ Toast.makeText(EmbmsTestDownloadApp.this,
+ "No file service selected", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ performDownload(serviceInfo);
});
}
@@ -108,15 +232,64 @@
return sInstance;
}
+ public void onDownloadFailed(int result) {
+ runOnUiThread(() ->
+ Toast.makeText(this, "Download failed: " + result, Toast.LENGTH_SHORT).show());
+ }
+
// 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");
+ public void onDownloadDone(Uri fileLocation) {
+ Log.i(LOG_TAG, "File completed: " + fileLocation);
+ File imageFile = new File(fileLocation.getPath());
if (!imageFile.exists()) {
Toast.makeText(this, "Download done but destination doesn't exist", Toast.LENGTH_SHORT)
.show();
return;
}
- runOnUiThread(() -> picture.setImageURI(Uri.fromFile(imageFile)));
+ mImageAdapter.addImage(fileLocation);
+ }
+
+ private void updateFileServicesList(List<FileServiceInfo> services) {
+ runOnUiThread(() -> mFileServiceInfoAdapter.update(services));
+ }
+
+ private void performDownload(FileServiceInfo info) {
+ File destination = null;
+ try {
+ if (info.getFiles().size() > 1) {
+ destination = new File(getFilesDir(), "images/").getCanonicalFile();
+ destination.mkdirs();
+ } else {
+ destination = new File(getFilesDir(), "images/image.png").getCanonicalFile();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+
+ 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)
+ .setDest(Uri.fromFile(destination))
+ .setAppIntent(completionIntent)
+ .setSubscriptionId(SubscriptionManager.getDefaultSubscriptionId())
+ .build();
+
+ try {
+ mDownloadManager.download(request, null);
+ } catch (MbmsException e) {
+ Toast.makeText(EmbmsTestDownloadApp.this,
+ "caught MbmsException: " + e.getErrorCode(), Toast.LENGTH_SHORT).show();
+ }
+
}
}