blob: 01777c681e2cd06de6d0303ff5ba9cc13723bc30 [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 android.telephony.cts.embmstestapp;
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.MbmsDownloadSession;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.DownloadStateCallback;
import android.telephony.mbms.FileInfo;
import android.telephony.mbms.FileServiceInfo;
import android.telephony.mbms.MbmsDownloadSessionCallback;
import android.telephony.mbms.MbmsErrors;
import android.telephony.mbms.UriPathPair;
import android.telephony.mbms.vendor.MbmsDownloadServiceBase;
import android.telephony.mbms.vendor.VendorUtils;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class CtsDownloadService extends Service {
private static final Set<String> ALLOWED_PACKAGES = new HashSet<String>() {{
add("android.telephony.cts");
}};
private static final String TAG = "EmbmsTestDownload";
public static final String METHOD_NAME = "method_name";
public static final String METHOD_INITIALIZE = "initialize";
public static final String METHOD_REQUEST_UPDATE_FILE_SERVICES =
"requestUpdateFileServices";
public static final String METHOD_SET_TEMP_FILE_ROOT = "setTempFileRootDirectory";
public static final String METHOD_RESET_DOWNLOAD_KNOWLEDGE = "resetDownloadKnowledge";
public static final String METHOD_GET_DOWNLOAD_STATUS = "getDownloadStatus";
public static final String METHOD_CANCEL_DOWNLOAD = "cancelDownload";
public static final String METHOD_CLOSE = "close";
// Not a method call, but it's a form of communication to the middleware so it's included
// here for convenience.
public static final String METHOD_DOWNLOAD_RESULT_ACK = "downloadResultAck";
public static final String ARGUMENT_SUBSCRIPTION_ID = "subscriptionId";
public static final String ARGUMENT_SERVICE_CLASSES = "serviceClasses";
public static final String ARGUMENT_ROOT_DIRECTORY_PATH = "rootDirectoryPath";
public static final String ARGUMENT_DOWNLOAD_REQUEST = "downloadRequest";
public static final String ARGUMENT_FILE_INFO = "fileInfo";
public static final String ARGUMENT_RESULT_CODE = "resultCode";
public static final String CONTROL_INTERFACE_ACTION =
"android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE";
public static final ComponentName CONTROL_INTERFACE_COMPONENT =
ComponentName.unflattenFromString(
"android.telephony.cts.embmstestapp/.CtsDownloadService");
public static final ComponentName CTS_TEST_RECEIVER_COMPONENT =
ComponentName.unflattenFromString(
"android.telephony.cts/android.telephony.mbms.MbmsDownloadReceiver");
public static final Uri DOWNLOAD_SOURCE_URI = Uri.parse("http://www.example.com/file_download");
public static final FileServiceInfo FILE_SERVICE_INFO;
public static final FileInfo FILE_INFO = new FileInfo(
DOWNLOAD_SOURCE_URI.buildUpon().appendPath("file1.txt").build(),
"text/plain");
public static final byte[] SAMPLE_FILE_DATA = "this is some sample file data".getBytes();
static {
String id = "FileServiceId";
Map<Locale, String> localeDict = new HashMap<Locale, String>() {{
put(Locale.US, "Entertainment Source 1");
put(Locale.CANADA, "Entertainment Source 1, eh?");
}};
List<Locale> locales = new ArrayList<Locale>() {{
add(Locale.CANADA);
add(Locale.US);
}};
FILE_SERVICE_INFO = new FileServiceInfo(localeDict, "class1", locales,
id, new Date(2017, 8, 21, 18, 20, 29),
new Date(2017, 8, 21, 18, 23, 9), Collections.singletonList(FILE_INFO));
}
private MbmsDownloadSessionCallback mAppCallback;
private DownloadStateCallback mDownloadStateCallback;
private HandlerThread mHandlerThread;
private Handler mHandler;
private List<Bundle> mReceivedCalls = new LinkedList<>();
private int mErrorCodeOverride = MbmsErrors.SUCCESS;
private List<DownloadRequest> mReceivedRequests = new LinkedList<>();
private String mTempFileRootDirPath = null;
private final MbmsDownloadServiceBase mDownloadServiceImpl = new MbmsDownloadServiceBase() {
@Override
public int initialize(int subscriptionId, MbmsDownloadSessionCallback callback) {
Bundle b = new Bundle();
b.putString(METHOD_NAME, METHOD_INITIALIZE);
b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
mReceivedCalls.add(b);
if (mErrorCodeOverride != MbmsErrors.SUCCESS) {
return mErrorCodeOverride;
}
int packageUid = Binder.getCallingUid();
String[] packageNames = getPackageManager().getPackagesForUid(packageUid);
if (packageNames == null) {
return MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED;
}
boolean isUidAllowed = Arrays.stream(packageNames).anyMatch(ALLOWED_PACKAGES::contains);
if (!isUidAllowed) {
return MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED;
}
mHandler.post(() -> {
if (mAppCallback == null) {
mAppCallback = callback;
} else {
callback.onError(
MbmsErrors.InitializationErrors.ERROR_DUPLICATE_INITIALIZE, "");
return;
}
callback.onMiddlewareReady();
});
return MbmsErrors.SUCCESS;
}
@Override
public int requestUpdateFileServices(int subscriptionId, List<String> serviceClasses) {
Bundle b = new Bundle();
b.putString(METHOD_NAME, METHOD_REQUEST_UPDATE_FILE_SERVICES);
b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
b.putStringArrayList(ARGUMENT_SERVICE_CLASSES, new ArrayList<>(serviceClasses));
mReceivedCalls.add(b);
if (mErrorCodeOverride != MbmsErrors.SUCCESS) {
return mErrorCodeOverride;
}
List<FileServiceInfo> serviceInfos = Collections.singletonList(FILE_SERVICE_INFO);
mHandler.post(() -> {
if (mAppCallback!= null) {
mAppCallback.onFileServicesUpdated(serviceInfos);
}
});
return MbmsErrors.SUCCESS;
}
@Override
public int download(DownloadRequest downloadRequest) {
mReceivedRequests.add(downloadRequest);
return MbmsErrors.SUCCESS;
}
@Override
public int setTempFileRootDirectory(int subscriptionId, String rootDirectoryPath) {
Bundle b = new Bundle();
b.putString(METHOD_NAME, METHOD_SET_TEMP_FILE_ROOT);
b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
b.putString(ARGUMENT_ROOT_DIRECTORY_PATH, rootDirectoryPath);
mReceivedCalls.add(b);
mTempFileRootDirPath = rootDirectoryPath;
return 0;
}
@Override
public int registerStateCallback(DownloadRequest downloadRequest,
DownloadStateCallback listener) throws RemoteException {
mDownloadStateCallback = listener;
return MbmsErrors.SUCCESS;
}
@Override
public void dispose(int subscriptionId) {
Bundle b = new Bundle();
b.putString(METHOD_NAME, METHOD_CLOSE);
b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
mReceivedCalls.add(b);
}
@Override
public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo) {
Bundle b = new Bundle();
b.putString(METHOD_NAME, METHOD_GET_DOWNLOAD_STATUS);
b.putParcelable(ARGUMENT_DOWNLOAD_REQUEST, downloadRequest);
b.putParcelable(ARGUMENT_FILE_INFO, fileInfo);
mReceivedCalls.add(b);
return MbmsDownloadSession.STATUS_ACTIVELY_DOWNLOADING;
}
@Override
public int cancelDownload(DownloadRequest request) {
Bundle b = new Bundle();
b.putString(METHOD_NAME, METHOD_CANCEL_DOWNLOAD);
b.putParcelable(ARGUMENT_DOWNLOAD_REQUEST, request);
mReceivedCalls.add(b);
mReceivedRequests.remove(request);
return MbmsErrors.SUCCESS;
}
@Override
public List<DownloadRequest> listPendingDownloads(int subscriptionId) {
return mReceivedRequests;
}
@Override
public int unregisterStateCallback(DownloadRequest downloadRequest,
DownloadStateCallback callback) {
mDownloadStateCallback = null;
return MbmsErrors.SUCCESS;
}
@Override
public int resetDownloadKnowledge(DownloadRequest downloadRequest) {
Bundle b = new Bundle();
b.putString(METHOD_NAME, METHOD_RESET_DOWNLOAD_KNOWLEDGE);
b.putParcelable(ARGUMENT_DOWNLOAD_REQUEST, downloadRequest);
mReceivedCalls.add(b);
return MbmsErrors.SUCCESS;
}
@Override
public void onAppCallbackDied(int uid, int subscriptionId) {
mAppCallback = null;
}
};
private final IBinder mControlInterface = new ICtsDownloadMiddlewareControl.Stub() {
@Override
public void reset() {
mReceivedCalls.clear();
mHandler.removeCallbacksAndMessages(null);
mAppCallback = null;
mErrorCodeOverride = MbmsErrors.SUCCESS;
mReceivedRequests.clear();
mDownloadStateCallback = null;
mTempFileRootDirPath = null;
}
@Override
public List<Bundle> getDownloadSessionCalls() {
return mReceivedCalls;
}
@Override
public void forceErrorCode(int error) {
mErrorCodeOverride = error;
}
@Override
public void fireErrorOnSession(int errorCode, String message) {
mHandler.post(() -> mAppCallback.onError(errorCode, message));
}
@Override
public void fireOnProgressUpdated(DownloadRequest request, FileInfo fileInfo,
int currentDownloadSize, int fullDownloadSize,
int currentDecodedSize, int fullDecodedSize) {
if (mDownloadStateCallback == null) {
return;
}
mHandler.post(() -> mDownloadStateCallback.onProgressUpdated(request, fileInfo,
currentDownloadSize, fullDownloadSize, currentDecodedSize, fullDecodedSize));
}
@Override
public void fireOnStateUpdated(DownloadRequest request, FileInfo fileInfo, int state) {
if (mDownloadStateCallback == null) {
return;
}
mHandler.post(() -> mDownloadStateCallback.onStateUpdated(request, fileInfo, state));
}
@Override
public void actuallyStartDownloadFlow() {
DownloadRequest request = mReceivedRequests.get(0);
// Compose the FILE_DESCRIPTOR_REQUEST_INTENT to get some FDs to write to
Intent requestIntent = new Intent(VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST);
requestIntent.putExtra(VendorUtils.EXTRA_SERVICE_ID, request.getFileServiceId());
requestIntent.putExtra(VendorUtils.EXTRA_FD_COUNT, 1);
requestIntent.putExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT, mTempFileRootDirPath);
requestIntent.setComponent(CTS_TEST_RECEIVER_COMPONENT);
// Send as an ordered broadcast, using a BroadcastReceiver to capture the result
// containing UriPathPairs.
logd("Sending fd-request broadcast");
sendOrderedBroadcast(requestIntent,
null, // receiverPermission
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
logd("Got file-descriptors");
Bundle extras = getResultExtras(false);
UriPathPair tempFile = (UriPathPair) extras.getParcelableArrayList(
VendorUtils.EXTRA_FREE_URI_LIST).get(0);
int result = MbmsDownloadSession.RESULT_SUCCESSFUL;
try {
ParcelFileDescriptor tempFileFd =
getContentResolver().openFileDescriptor(
tempFile.getContentUri(), "rw");
OutputStream destinationStream =
new ParcelFileDescriptor.AutoCloseOutputStream(tempFileFd);
destinationStream.write(SAMPLE_FILE_DATA);
destinationStream.flush();
} catch (IOException e) {
result = MbmsDownloadSession.RESULT_CANCELLED;
}
Intent downloadResultIntent =
new Intent(VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL);
downloadResultIntent.putExtra(
MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
downloadResultIntent.putExtra(VendorUtils.EXTRA_FINAL_URI,
tempFile.getFilePathUri());
downloadResultIntent.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO,
FILE_INFO);
downloadResultIntent.putExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT,
mTempFileRootDirPath);
downloadResultIntent.putExtra(
MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
downloadResultIntent.setComponent(CTS_TEST_RECEIVER_COMPONENT);
logd("Sending broadcast to app: " + downloadResultIntent.toString());
sendOrderedBroadcast(downloadResultIntent,
null, // receiverPermission
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle b = new Bundle();
b.putString(METHOD_NAME, METHOD_DOWNLOAD_RESULT_ACK);
b.putInt(ARGUMENT_RESULT_CODE, getResultCode());
mReceivedCalls.add(b);
}
},
null, // scheduler
Activity.RESULT_OK,
null, // initialData
null /* initialExtras */);
}
},
mHandler, // scheduler
Activity.RESULT_OK,
null, // initialData
null /* initialExtras */);
}
};
@Override
public void onDestroy() {
super.onCreate();
mHandlerThread.quitSafely();
logd("CtsDownloadService onDestroy");
}
@Override
public IBinder onBind(Intent intent) {
if (CONTROL_INTERFACE_ACTION.equals(intent.getAction())) {
logd("CtsDownloadService control interface bind");
return mControlInterface;
}
logd("CtsDownloadService onBind");
if (mHandlerThread != null && mHandlerThread.isAlive()) {
return mDownloadServiceImpl;
}
mHandlerThread = new HandlerThread("CtsDownloadServiceWorker");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
return mDownloadServiceImpl;
}
private static void logd(String s) {
Log.d(TAG, s);
}
private void checkInitialized() {
if (mAppCallback == null) {
throw new IllegalStateException("Not yet initialized");
}
}
}