Home | History | Annotate | Download | only in embmstestapp
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package android.telephony.cts.embmstestapp;
     18 
     19 import android.app.Activity;
     20 import android.app.Service;
     21 import android.content.BroadcastReceiver;
     22 import android.content.ComponentName;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.net.Uri;
     26 import android.os.Binder;
     27 import android.os.Bundle;
     28 import android.os.Handler;
     29 import android.os.HandlerThread;
     30 import android.os.IBinder;
     31 import android.os.ParcelFileDescriptor;
     32 import android.os.RemoteException;
     33 import android.telephony.MbmsDownloadSession;
     34 import android.telephony.mbms.DownloadProgressListener;
     35 import android.telephony.mbms.DownloadRequest;
     36 import android.telephony.mbms.DownloadStatusListener;
     37 import android.telephony.mbms.FileInfo;
     38 import android.telephony.mbms.FileServiceInfo;
     39 import android.telephony.mbms.MbmsDownloadSessionCallback;
     40 import android.telephony.mbms.MbmsErrors;
     41 import android.telephony.mbms.UriPathPair;
     42 import android.telephony.mbms.vendor.MbmsDownloadServiceBase;
     43 import android.telephony.mbms.vendor.VendorUtils;
     44 import android.util.Log;
     45 
     46 import java.io.IOException;
     47 import java.io.OutputStream;
     48 import java.util.ArrayList;
     49 import java.util.Arrays;
     50 import java.util.Collections;
     51 import java.util.Date;
     52 import java.util.HashMap;
     53 import java.util.HashSet;
     54 import java.util.LinkedList;
     55 import java.util.List;
     56 import java.util.Locale;
     57 import java.util.Map;
     58 import java.util.Set;
     59 
     60 public class CtsDownloadService extends Service {
     61     private static final Set<String> ALLOWED_PACKAGES = new HashSet<String>() {{
     62         add("android.telephony.cts");
     63     }};
     64     private static final String TAG = "EmbmsTestDownload";
     65 
     66     public static final String METHOD_NAME = "method_name";
     67     public static final String METHOD_INITIALIZE = "initialize";
     68     public static final String METHOD_REQUEST_UPDATE_FILE_SERVICES =
     69             "requestUpdateFileServices";
     70     public static final String METHOD_SET_TEMP_FILE_ROOT = "setTempFileRootDirectory";
     71     public static final String METHOD_RESET_DOWNLOAD_KNOWLEDGE = "resetDownloadKnowledge";
     72     public static final String METHOD_GET_DOWNLOAD_STATUS = "getDownloadStatus";
     73     public static final String METHOD_CANCEL_DOWNLOAD = "cancelDownload";
     74     public static final String METHOD_CLOSE = "close";
     75     // Not a method call, but it's a form of communication to the middleware so it's included
     76     // here for convenience.
     77     public static final String METHOD_DOWNLOAD_RESULT_ACK = "downloadResultAck";
     78 
     79     public static final String ARGUMENT_SUBSCRIPTION_ID = "subscriptionId";
     80     public static final String ARGUMENT_SERVICE_CLASSES = "serviceClasses";
     81     public static final String ARGUMENT_ROOT_DIRECTORY_PATH = "rootDirectoryPath";
     82     public static final String ARGUMENT_DOWNLOAD_REQUEST = "downloadRequest";
     83     public static final String ARGUMENT_FILE_INFO = "fileInfo";
     84     public static final String ARGUMENT_RESULT_CODE = "resultCode";
     85 
     86     public static final String CONTROL_INTERFACE_ACTION =
     87             "android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE";
     88     public static final ComponentName CONTROL_INTERFACE_COMPONENT =
     89             ComponentName.unflattenFromString(
     90                     "android.telephony.cts.embmstestapp/.CtsDownloadService");
     91     public static final ComponentName CTS_TEST_RECEIVER_COMPONENT =
     92             ComponentName.unflattenFromString(
     93                     "android.telephony.cts/android.telephony.mbms.MbmsDownloadReceiver");
     94 
     95     public static final Uri DOWNLOAD_SOURCE_URI_ROOT =
     96             Uri.parse("http://www.example.com/file_download");
     97     public static final FileServiceInfo FILE_SERVICE_INFO;
     98     public static final FileInfo FILE_INFO_1 = new FileInfo(
     99             DOWNLOAD_SOURCE_URI_ROOT.buildUpon().appendPath("file1.txt").build(),
    100             "text/plain");
    101     public static final FileInfo FILE_INFO_2 = new FileInfo(
    102             DOWNLOAD_SOURCE_URI_ROOT.buildUpon().appendPath("sub_dir1")
    103                     .appendPath("sub_dir2")
    104                     .appendPath("file2.txt")
    105                     .build(),
    106             "text/plain");
    107     public static final byte[] SAMPLE_FILE_DATA = "this is some sample file data".getBytes();
    108 
    109     // Define allowed source URIs so that we don't have to do the prefix matching calculation
    110     public static final Uri SOURCE_URI_1 = DOWNLOAD_SOURCE_URI_ROOT.buildUpon()
    111             .appendPath("file1.txt").build();
    112     public static final Uri SOURCE_URI_2 = DOWNLOAD_SOURCE_URI_ROOT.buildUpon()
    113             .appendPath("sub_dir1").appendPath("*").build();
    114     public static final Uri SOURCE_URI_3 = DOWNLOAD_SOURCE_URI_ROOT.buildUpon()
    115             .appendPath("*").build();
    116 
    117     static {
    118         String id = "urn:3GPP:service_0-0";
    119         Map<Locale, String> localeDict = new HashMap<Locale, String>() {{
    120             put(Locale.US, "Entertainment Source 1");
    121             put(Locale.CANADA, "Entertainment Source 1, eh?");
    122         }};
    123         List<Locale> locales = new ArrayList<Locale>() {{
    124             add(Locale.CANADA);
    125             add(Locale.US);
    126         }};
    127         List<FileInfo> files = new ArrayList<FileInfo>() {{
    128             add(FILE_INFO_1);
    129             add(FILE_INFO_2);
    130         }};
    131         FILE_SERVICE_INFO = new FileServiceInfo(localeDict, "class1", locales,
    132                 id, new Date(2017, 8, 21, 18, 20, 29),
    133                 new Date(2017, 8, 21, 18, 23, 9), files);
    134     }
    135 
    136     private MbmsDownloadSessionCallback mAppCallback;
    137     private DownloadStatusListener mDownloadStatusListener;
    138     private DownloadProgressListener mDownloadProgressListener;
    139 
    140     private HandlerThread mHandlerThread;
    141     private Handler mHandler;
    142     private List<Bundle> mReceivedCalls = new LinkedList<>();
    143     private int mErrorCodeOverride = MbmsErrors.SUCCESS;
    144     private List<DownloadRequest> mReceivedRequests = new LinkedList<>();
    145     private String mTempFileRootDirPath = null;
    146 
    147     private final MbmsDownloadServiceBase mDownloadServiceImpl = new MbmsDownloadServiceBase() {
    148         @Override
    149         public int initialize(int subscriptionId, MbmsDownloadSessionCallback callback) {
    150             Bundle b = new Bundle();
    151             b.putString(METHOD_NAME, METHOD_INITIALIZE);
    152             b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
    153             mReceivedCalls.add(b);
    154 
    155             if (mErrorCodeOverride != MbmsErrors.SUCCESS) {
    156                 return mErrorCodeOverride;
    157             }
    158 
    159             int packageUid = Binder.getCallingUid();
    160             String[] packageNames = getPackageManager().getPackagesForUid(packageUid);
    161             if (packageNames == null) {
    162                 return MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED;
    163             }
    164             boolean isUidAllowed = Arrays.stream(packageNames).anyMatch(ALLOWED_PACKAGES::contains);
    165             if (!isUidAllowed) {
    166                 return MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED;
    167             }
    168 
    169             mHandler.post(() -> {
    170                 if (mAppCallback == null) {
    171                     mAppCallback = callback;
    172                 } else {
    173                     callback.onError(
    174                             MbmsErrors.InitializationErrors.ERROR_DUPLICATE_INITIALIZE, "");
    175                     return;
    176                 }
    177                 callback.onMiddlewareReady();
    178             });
    179             return MbmsErrors.SUCCESS;
    180         }
    181 
    182         @Override
    183         public int requestUpdateFileServices(int subscriptionId, List<String> serviceClasses) {
    184             Bundle b = new Bundle();
    185             b.putString(METHOD_NAME, METHOD_REQUEST_UPDATE_FILE_SERVICES);
    186             b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
    187             b.putStringArrayList(ARGUMENT_SERVICE_CLASSES, new ArrayList<>(serviceClasses));
    188             mReceivedCalls.add(b);
    189 
    190             if (mErrorCodeOverride != MbmsErrors.SUCCESS) {
    191                 return mErrorCodeOverride;
    192             }
    193 
    194             List<FileServiceInfo> serviceInfos = Collections.singletonList(FILE_SERVICE_INFO);
    195 
    196             mHandler.post(() -> {
    197                 if (mAppCallback!= null) {
    198                     mAppCallback.onFileServicesUpdated(serviceInfos);
    199                 }
    200             });
    201 
    202             return MbmsErrors.SUCCESS;
    203         }
    204 
    205         @Override
    206         public int download(DownloadRequest downloadRequest) {
    207             mReceivedRequests.add(downloadRequest);
    208             return MbmsErrors.SUCCESS;
    209         }
    210 
    211         @Override
    212         public int setTempFileRootDirectory(int subscriptionId, String rootDirectoryPath) {
    213             if (mErrorCodeOverride != MbmsErrors.SUCCESS) {
    214                 return mErrorCodeOverride;
    215             }
    216 
    217             Bundle b = new Bundle();
    218             b.putString(METHOD_NAME, METHOD_SET_TEMP_FILE_ROOT);
    219             b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
    220             b.putString(ARGUMENT_ROOT_DIRECTORY_PATH, rootDirectoryPath);
    221             mReceivedCalls.add(b);
    222             mTempFileRootDirPath = rootDirectoryPath;
    223             return 0;
    224         }
    225 
    226         @Override
    227         public int addProgressListener(DownloadRequest downloadRequest,
    228                 DownloadProgressListener listener) throws RemoteException {
    229             mDownloadProgressListener = listener;
    230             return MbmsErrors.SUCCESS;
    231         }
    232 
    233         @Override
    234         public int addStatusListener(DownloadRequest downloadRequest,
    235                 DownloadStatusListener listener) throws RemoteException {
    236             mDownloadStatusListener = listener;
    237             return MbmsErrors.SUCCESS;
    238         }
    239 
    240         @Override
    241         public void dispose(int subscriptionId) {
    242             Bundle b = new Bundle();
    243             b.putString(METHOD_NAME, METHOD_CLOSE);
    244             b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
    245             mReceivedCalls.add(b);
    246         }
    247 
    248         @Override
    249         public int requestDownloadState(DownloadRequest downloadRequest, FileInfo fileInfo) {
    250             Bundle b = new Bundle();
    251             b.putString(METHOD_NAME, METHOD_GET_DOWNLOAD_STATUS);
    252             b.putParcelable(ARGUMENT_DOWNLOAD_REQUEST, downloadRequest);
    253             b.putParcelable(ARGUMENT_FILE_INFO, fileInfo);
    254             mReceivedCalls.add(b);
    255             return MbmsDownloadSession.STATUS_ACTIVELY_DOWNLOADING;
    256         }
    257 
    258         @Override
    259         public int cancelDownload(DownloadRequest request) {
    260             Bundle b = new Bundle();
    261             b.putString(METHOD_NAME, METHOD_CANCEL_DOWNLOAD);
    262             b.putParcelable(ARGUMENT_DOWNLOAD_REQUEST, request);
    263             mReceivedCalls.add(b);
    264             mReceivedRequests.remove(request);
    265             return MbmsErrors.SUCCESS;
    266         }
    267 
    268         @Override
    269         public List<DownloadRequest> listPendingDownloads(int subscriptionId) {
    270             return mReceivedRequests;
    271         }
    272 
    273         @Override
    274         public int removeStatusListener(DownloadRequest downloadRequest,
    275                 DownloadStatusListener callback) {
    276             mDownloadStatusListener = null;
    277             return MbmsErrors.SUCCESS;
    278         }
    279 
    280         @Override
    281         public int resetDownloadKnowledge(DownloadRequest downloadRequest) {
    282             Bundle b = new Bundle();
    283             b.putString(METHOD_NAME, METHOD_RESET_DOWNLOAD_KNOWLEDGE);
    284             b.putParcelable(ARGUMENT_DOWNLOAD_REQUEST, downloadRequest);
    285             mReceivedCalls.add(b);
    286             return MbmsErrors.SUCCESS;
    287         }
    288 
    289         @Override
    290         public void onAppCallbackDied(int uid, int subscriptionId) {
    291             mAppCallback = null;
    292         }
    293     };
    294 
    295     private final IBinder mControlInterface = new ICtsDownloadMiddlewareControl.Stub() {
    296         @Override
    297         public void reset() {
    298             mReceivedCalls.clear();
    299             mHandler.removeCallbacksAndMessages(null);
    300             mAppCallback = null;
    301             mErrorCodeOverride = MbmsErrors.SUCCESS;
    302             mReceivedRequests.clear();
    303             mDownloadStatusListener = null;
    304             mTempFileRootDirPath = null;
    305         }
    306 
    307         @Override
    308         public List<Bundle> getDownloadSessionCalls() {
    309             return mReceivedCalls;
    310         }
    311 
    312         @Override
    313         public void forceErrorCode(int error) {
    314             mErrorCodeOverride = error;
    315         }
    316 
    317         @Override
    318         public void fireErrorOnSession(int errorCode, String message) {
    319             mHandler.post(() -> mAppCallback.onError(errorCode, message));
    320         }
    321 
    322         @Override
    323         public void fireOnProgressUpdated(DownloadRequest request, FileInfo fileInfo,
    324                 int currentDownloadSize, int fullDownloadSize,
    325                 int currentDecodedSize, int fullDecodedSize) {
    326             if (mDownloadStatusListener == null) {
    327                 return;
    328             }
    329             mHandler.post(() -> mDownloadProgressListener.onProgressUpdated(request, fileInfo,
    330                     currentDownloadSize, fullDownloadSize, currentDecodedSize, fullDecodedSize));
    331         }
    332 
    333         @Override
    334         public void fireOnStateUpdated(DownloadRequest request, FileInfo fileInfo, int state) {
    335             if (mDownloadStatusListener == null) {
    336                 return;
    337             }
    338             mHandler.post(() -> mDownloadStatusListener.onStatusUpdated(request, fileInfo, state));
    339         }
    340 
    341         @Override
    342         public void actuallyStartDownloadFlow() {
    343             DownloadRequest request = mReceivedRequests.get(0);
    344             List<FileInfo> requestedFiles = getRequestedFiles(request);
    345             // Compose the FILE_DESCRIPTOR_REQUEST_INTENT to get some FDs to write to
    346             Intent requestIntent = new Intent(VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST);
    347             requestIntent.putExtra(VendorUtils.EXTRA_SERVICE_ID, request.getFileServiceId());
    348 
    349             requestIntent.putExtra(VendorUtils.EXTRA_FD_COUNT, requestedFiles.size());
    350             requestIntent.putExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT, mTempFileRootDirPath);
    351             requestIntent.setComponent(CTS_TEST_RECEIVER_COMPONENT);
    352 
    353             // Send as an ordered broadcast, using a BroadcastReceiver to capture the result
    354             // containing UriPathPairs.
    355             logd("Sending fd-request broadcast");
    356             sendOrderedBroadcast(requestIntent,
    357                     null, // receiverPermission
    358                     new BroadcastReceiver() {
    359                         @Override
    360                         public void onReceive(Context context, Intent intent) {
    361                             logd("Got file-descriptors");
    362                             Bundle extras = getResultExtras(false);
    363                             List<UriPathPair> tempFiles = extras.getParcelableArrayList(
    364                                     VendorUtils.EXTRA_FREE_URI_LIST);
    365 
    366                             for (int i = 0; i < tempFiles.size(); i++) {
    367                                 UriPathPair tempFile = tempFiles.get(i);
    368                                 FileInfo requestedFile = requestedFiles.get(i);
    369                                 int result = writeContentsToTempFile(tempFile);
    370 
    371                                 Intent downloadResultIntent = composeDownloadResultIntent(
    372                                         tempFile, request, result, requestedFile);
    373 
    374                                 logd("Sending broadcast to app: "
    375                                         + downloadResultIntent.toString());
    376                                 sendOrderedBroadcast(downloadResultIntent,
    377                                         null, // receiverPermission
    378                                         new BroadcastReceiver() {
    379                                             @Override
    380                                             public void onReceive(Context context, Intent intent) {
    381                                                 Bundle b = new Bundle();
    382                                                 b.putString(METHOD_NAME,
    383                                                         METHOD_DOWNLOAD_RESULT_ACK);
    384                                                 b.putInt(ARGUMENT_RESULT_CODE, getResultCode());
    385                                                 mReceivedCalls.add(b);
    386                                             }
    387                                         },
    388                                         null, // scheduler
    389                                         Activity.RESULT_OK,
    390                                         null, // initialData
    391                                         null /* initialExtras */);
    392                         }
    393                         }
    394                     },
    395                     mHandler, // scheduler
    396                     Activity.RESULT_OK,
    397                     null, // initialData
    398                     null /* initialExtras */);
    399 
    400         }
    401     };
    402 
    403     private List<FileInfo> getRequestedFiles(DownloadRequest request) {
    404         if (SOURCE_URI_1.equals(request.getSourceUri())) {
    405             return Collections.singletonList(FILE_INFO_1);
    406         }
    407         if (SOURCE_URI_2.equals(request.getSourceUri())) {
    408             return Collections.singletonList(FILE_INFO_2);
    409         }
    410         if (SOURCE_URI_3.equals(request.getSourceUri())) {
    411             return FILE_SERVICE_INFO.getFiles();
    412         }
    413         return Collections.emptyList();
    414     }
    415 
    416     private Intent composeDownloadResultIntent(UriPathPair tempFile, DownloadRequest request,
    417             int result, FileInfo downloadedFile) {
    418         Intent downloadResultIntent =
    419                 new Intent(VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL);
    420         downloadResultIntent.putExtra(
    421                 MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
    422         downloadResultIntent.putExtra(VendorUtils.EXTRA_FINAL_URI,
    423                 tempFile.getFilePathUri());
    424         downloadResultIntent.putExtra(
    425                 MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, downloadedFile);
    426         downloadResultIntent.putExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT,
    427                 mTempFileRootDirPath);
    428         downloadResultIntent.putExtra(
    429                 MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
    430         downloadResultIntent.setComponent(CTS_TEST_RECEIVER_COMPONENT);
    431         return downloadResultIntent;
    432     }
    433 
    434     private int writeContentsToTempFile(UriPathPair tempFile) {
    435         int result = MbmsDownloadSession.RESULT_SUCCESSFUL;
    436         try {
    437             ParcelFileDescriptor tempFileFd =
    438                     getContentResolver().openFileDescriptor(
    439                             tempFile.getContentUri(), "rw");
    440             OutputStream destinationStream =
    441                     new ParcelFileDescriptor.AutoCloseOutputStream(tempFileFd);
    442 
    443             destinationStream.write(SAMPLE_FILE_DATA);
    444             destinationStream.flush();
    445         } catch (IOException e) {
    446             result = MbmsDownloadSession.RESULT_CANCELLED;
    447         }
    448         return result;
    449     }
    450 
    451     @Override
    452     public void onDestroy() {
    453         super.onCreate();
    454         mHandlerThread.quitSafely();
    455         logd("CtsDownloadService onDestroy");
    456     }
    457 
    458     @Override
    459     public IBinder onBind(Intent intent) {
    460         if (CONTROL_INTERFACE_ACTION.equals(intent.getAction())) {
    461             logd("CtsDownloadService control interface bind");
    462             return mControlInterface;
    463         }
    464 
    465         logd("CtsDownloadService onBind");
    466         if (mHandlerThread != null && mHandlerThread.isAlive()) {
    467             return mDownloadServiceImpl;
    468         }
    469 
    470         mHandlerThread = new HandlerThread("CtsDownloadServiceWorker");
    471         mHandlerThread.start();
    472         mHandler = new Handler(mHandlerThread.getLooper());
    473         return mDownloadServiceImpl;
    474     }
    475 
    476     private static void logd(String s) {
    477         Log.d(TAG, s);
    478     }
    479 
    480     private void checkInitialized() {
    481         if (mAppCallback == null) {
    482             throw new IllegalStateException("Not yet initialized");
    483         }
    484     }
    485 }
    486