blob: f7abeb9ce2838799c9d772e9a4a0249d165ba502 [file] [log] [blame]
/*
* 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.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.IMbmsDownloadManagerCallback;
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.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 INITIALIZATION_DELAY = 200;
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 void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener) {
int packageUid = Binder.getCallingUid();
String[] packageNames = getPackageManager().getPackagesForUid(packageUid);
if (packageNames == null) {
throw new SecurityException("No matching packages 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");
}
// Do initialization with a bit of a delay to simulate work being done.
mHandler.postDelayed(() -> {
FrontendAppIdentifier appKey =
new FrontendAppIdentifier(packageUid, appName, subId);
if (!mAppCallbacks.containsKey(appKey)) {
mAppCallbacks.put(appKey, listener);
ComponentName appReceiver = MbmsDownloadManager.getAppReceiverFromUid(
EmbmsSampleDownloadService.this, packageUid);
mAppReceivers.put(appKey, appReceiver);
} else {
try {
listener.error(MbmsException.ERROR_ALREADY_INITIALIZED, "");
} catch (RemoteException e) {
// ignore, it was an error anyway
}
return;
}
try {
listener.middlewareReady();
} catch (RemoteException e) {
// TODO: call dispose
}
}, INITIALIZATION_DELAY);
}
@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;
@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, 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);
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.
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, appKey, resultExtras),
DOWNLOAD_DELAY_MS);
}
},
null, // scheduler
Activity.RESULT_OK,
null, // initialData
null /* initialExtras */);
}
private void performDownload(DownloadRequest request, FrontendAppIdentifier appKey,
Bundle extras) {
List<UriPathPair> tempFiles = extras.getParcelableArrayList(
MbmsDownloadManager.EXTRA_FREE_URI_LIST);
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 tempFileFd = getContentResolver().openFileDescriptor(
tempFile.getContentUri(), "rw");
OutputStream destinationStream =
new ParcelFileDescriptor.AutoCloseOutputStream(tempFileFd);
// This is how you get the native fd
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(resourceId);
// 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,
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(tempFile.getFilePathUri());
downloadResultIntent.getExtras().putParcelableArrayList(
MbmsDownloadManager.EXTRA_TEMP_LIST, tempFileList);
downloadResultIntent.putExtra(MbmsDownloadManager.EXTRA_RESULT, result);
downloadResultIntent.setComponent(mAppReceivers.get(appKey));
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 */);
}
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();
}
}