• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_ADD_SERVICE_ANNOUNCEMENT = "addServiceAnnouncementFile";
71     public static final String METHOD_SET_TEMP_FILE_ROOT = "setTempFileRootDirectory";
72     public static final String METHOD_RESET_DOWNLOAD_KNOWLEDGE = "resetDownloadKnowledge";
73     public static final String METHOD_GET_DOWNLOAD_STATUS = "getDownloadStatus";
74     public static final String METHOD_CANCEL_DOWNLOAD = "cancelDownload";
75     public static final String METHOD_CLOSE = "close";
76     // Not a method call, but it's a form of communication to the middleware so it's included
77     // here for convenience.
78     public static final String METHOD_DOWNLOAD_RESULT_ACK = "downloadResultAck";
79 
80     public static final String ARGUMENT_SUBSCRIPTION_ID = "subscriptionId";
81     public static final String ARGUMENT_SERVICE_CLASSES = "serviceClasses";
82     public static final String ARGUMENT_ROOT_DIRECTORY_PATH = "rootDirectoryPath";
83     public static final String ARGUMENT_DOWNLOAD_REQUEST = "downloadRequest";
84     public static final String ARGUMENT_FILE_INFO = "fileInfo";
85     public static final String ARGUMENT_RESULT_CODE = "resultCode";
86     public static final String ARGUMENT_SERVICE_ANNOUNCEMENT_FILE = "serviceAnnouncementFile";
87 
88     public static final String CONTROL_INTERFACE_ACTION =
89             "android.telephony.cts.embmstestapp.ACTION_CONTROL_MIDDLEWARE";
90     public static final ComponentName CONTROL_INTERFACE_COMPONENT =
91             ComponentName.unflattenFromString(
92                     "android.telephony.cts.embmstestapp/.CtsDownloadService");
93     public static final ComponentName CTS_TEST_RECEIVER_COMPONENT =
94             ComponentName.unflattenFromString(
95                     "android.telephony.cts/android.telephony.mbms.MbmsDownloadReceiver");
96 
97     public static final Uri DOWNLOAD_SOURCE_URI_ROOT =
98             Uri.parse("http://www.example.com/file_download");
99     public static final FileServiceInfo FILE_SERVICE_INFO;
100     public static final FileInfo FILE_INFO_1 = new FileInfo(
101             DOWNLOAD_SOURCE_URI_ROOT.buildUpon().appendPath("file1.txt").build(),
102             "text/plain");
103     public static final FileInfo FILE_INFO_2 = new FileInfo(
104             DOWNLOAD_SOURCE_URI_ROOT.buildUpon().appendPath("sub_dir1")
105                     .appendPath("sub_dir2")
106                     .appendPath("file2.txt")
107                     .build(),
108             "text/plain");
109     public static final byte[] SAMPLE_FILE_DATA = "this is some sample file data".getBytes();
110 
111     // Define allowed source URIs so that we don't have to do the prefix matching calculation
112     public static final Uri SOURCE_URI_1 = DOWNLOAD_SOURCE_URI_ROOT.buildUpon()
113             .appendPath("file1.txt").build();
114     public static final Uri SOURCE_URI_2 = DOWNLOAD_SOURCE_URI_ROOT.buildUpon()
115             .appendPath("sub_dir1").appendPath("*").build();
116     public static final Uri SOURCE_URI_3 = DOWNLOAD_SOURCE_URI_ROOT.buildUpon()
117             .appendPath("*").build();
118 
119     static {
120         String id = "urn:3GPP:service_0-0";
121         Map<Locale, String> localeDict = new HashMap<Locale, String>() {{
122             put(Locale.US, "Entertainment Source 1");
123             put(Locale.CANADA, "Entertainment Source 1, eh?");
124         }};
125         List<Locale> locales = new ArrayList<Locale>() {{
126             add(Locale.CANADA);
127             add(Locale.US);
128         }};
129         List<FileInfo> files = new ArrayList<FileInfo>() {{
130             add(FILE_INFO_1);
131             add(FILE_INFO_2);
132         }};
133         FILE_SERVICE_INFO = new FileServiceInfo(localeDict, "class1", locales,
134                 id, new Date(2017, 8, 21, 18, 20, 29),
135                 new Date(2017, 8, 21, 18, 23, 9), files);
136     }
137 
138     private MbmsDownloadSessionCallback mAppCallback;
139     private DownloadStatusListener mDownloadStatusListener;
140     private DownloadProgressListener mDownloadProgressListener;
141 
142     private HandlerThread mHandlerThread;
143     private Handler mHandler;
144     private List<Bundle> mReceivedCalls = new LinkedList<>();
145     private int mErrorCodeOverride = MbmsErrors.SUCCESS;
146     private List<DownloadRequest> mReceivedRequests = new LinkedList<>();
147     private String mTempFileRootDirPath = null;
148 
149     private final MbmsDownloadServiceBase mDownloadServiceImpl = new MbmsDownloadServiceBase() {
150         @Override
151         public int initialize(int subscriptionId, MbmsDownloadSessionCallback callback)
152                 throws RemoteException {
153             super.initialize(subscriptionId, callback); // noop to placate the coverage tool
154             Bundle b = new Bundle();
155             b.putString(METHOD_NAME, METHOD_INITIALIZE);
156             b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
157             mReceivedCalls.add(b);
158 
159             if (mErrorCodeOverride != MbmsErrors.SUCCESS) {
160                 return mErrorCodeOverride;
161             }
162 
163             int packageUid = Binder.getCallingUid();
164             String[] packageNames = getPackageManager().getPackagesForUid(packageUid);
165             if (packageNames == null) {
166                 return MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED;
167             }
168             boolean isUidAllowed = Arrays.stream(packageNames).anyMatch(ALLOWED_PACKAGES::contains);
169             if (!isUidAllowed) {
170                 return MbmsErrors.InitializationErrors.ERROR_APP_PERMISSIONS_NOT_GRANTED;
171             }
172 
173             mHandler.post(() -> {
174                 if (mAppCallback == null) {
175                     mAppCallback = callback;
176                 } else {
177                     callback.onError(
178                             MbmsErrors.InitializationErrors.ERROR_DUPLICATE_INITIALIZE, "");
179                     return;
180                 }
181                 callback.onMiddlewareReady();
182             });
183             return MbmsErrors.SUCCESS;
184         }
185 
186         @Override
187         public int requestUpdateFileServices(int subscriptionId, List<String> serviceClasses)
188                 throws RemoteException {
189             // noop to placate the coverage tool
190             super.requestUpdateFileServices(subscriptionId, serviceClasses);
191 
192             Bundle b = new Bundle();
193             b.putString(METHOD_NAME, METHOD_REQUEST_UPDATE_FILE_SERVICES);
194             b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
195             b.putStringArrayList(ARGUMENT_SERVICE_CLASSES, new ArrayList<>(serviceClasses));
196             mReceivedCalls.add(b);
197 
198             if (mErrorCodeOverride != MbmsErrors.SUCCESS) {
199                 return mErrorCodeOverride;
200             }
201 
202             List<FileServiceInfo> serviceInfos = Collections.singletonList(FILE_SERVICE_INFO);
203 
204             mHandler.post(() -> {
205                 if (mAppCallback!= null) {
206                     mAppCallback.onFileServicesUpdated(serviceInfos);
207                 }
208             });
209 
210             return MbmsErrors.SUCCESS;
211         }
212 
213         @Override
214         public int download(DownloadRequest downloadRequest) throws RemoteException {
215             super.download(downloadRequest); // noop to placate the coverage tool
216             mReceivedRequests.add(downloadRequest);
217             return MbmsErrors.SUCCESS;
218         }
219 
220         @Override
221         public int setTempFileRootDirectory(int subscriptionId, String rootDirectoryPath)
222                 throws RemoteException {
223             // noop to placate the coverage tool
224             super.setTempFileRootDirectory(subscriptionId, rootDirectoryPath);
225             if (mErrorCodeOverride != MbmsErrors.SUCCESS) {
226                 return mErrorCodeOverride;
227             }
228 
229             Bundle b = new Bundle();
230             b.putString(METHOD_NAME, METHOD_SET_TEMP_FILE_ROOT);
231             b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
232             b.putString(ARGUMENT_ROOT_DIRECTORY_PATH, rootDirectoryPath);
233             mReceivedCalls.add(b);
234             mTempFileRootDirPath = rootDirectoryPath;
235             return 0;
236         }
237 
238         @Override
239         public int addProgressListener(DownloadRequest downloadRequest,
240                 DownloadProgressListener listener) throws RemoteException {
241             // noop to placate the coverage tool
242             super.addProgressListener(downloadRequest, listener);
243             mDownloadProgressListener = listener;
244             return MbmsErrors.SUCCESS;
245         }
246 
247         @Override
248         public int addStatusListener(DownloadRequest downloadRequest,
249                 DownloadStatusListener listener) throws RemoteException {
250             // noop to placate the coverage tool
251             super.addStatusListener(downloadRequest, listener);
252             mDownloadStatusListener = listener;
253             return MbmsErrors.SUCCESS;
254         }
255 
256         @Override
257         public void dispose(int subscriptionId) throws RemoteException {
258             // noop to placate the coverage tool
259             super.dispose(subscriptionId);
260             Bundle b = new Bundle();
261             b.putString(METHOD_NAME, METHOD_CLOSE);
262             b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
263             mReceivedCalls.add(b);
264         }
265 
266         @Override
267         public int requestDownloadState(DownloadRequest downloadRequest, FileInfo fileInfo)
268                 throws RemoteException {
269             // noop to placate the coverage tool
270             super.requestDownloadState(downloadRequest, fileInfo);
271             Bundle b = new Bundle();
272             b.putString(METHOD_NAME, METHOD_GET_DOWNLOAD_STATUS);
273             b.putParcelable(ARGUMENT_DOWNLOAD_REQUEST, downloadRequest);
274             b.putParcelable(ARGUMENT_FILE_INFO, fileInfo);
275             mReceivedCalls.add(b);
276             return MbmsDownloadSession.STATUS_ACTIVELY_DOWNLOADING;
277         }
278 
279         @Override
280         public int addServiceAnnouncement(int subscriptionId, byte[] announcementFile) {
281             try {
282                 // noop to placate the coverage tool
283                 super.addServiceAnnouncement(subscriptionId, announcementFile);
284             } catch (UnsupportedOperationException e) {
285                 // expected
286             }
287             Bundle b = new Bundle();
288             b.putString(METHOD_NAME, METHOD_ADD_SERVICE_ANNOUNCEMENT);
289             b.putInt(ARGUMENT_SUBSCRIPTION_ID, subscriptionId);
290             b.putByteArray(ARGUMENT_SERVICE_ANNOUNCEMENT_FILE, announcementFile);
291             mReceivedCalls.add(b);
292             return MbmsErrors.SUCCESS;
293         }
294 
295         @Override
296         public int cancelDownload(DownloadRequest request) throws RemoteException {
297             // noop to placate the coverage tool
298             super.cancelDownload(request);
299             Bundle b = new Bundle();
300             b.putString(METHOD_NAME, METHOD_CANCEL_DOWNLOAD);
301             b.putParcelable(ARGUMENT_DOWNLOAD_REQUEST, request);
302             mReceivedCalls.add(b);
303             mReceivedRequests.remove(request);
304             return MbmsErrors.SUCCESS;
305         }
306 
307         @Override
308         public List<DownloadRequest> listPendingDownloads(int subscriptionId)
309                 throws RemoteException {
310             // noop to placate the coverage tool
311             super.listPendingDownloads(subscriptionId);
312             return mReceivedRequests;
313         }
314 
315         @Override
316         public int removeStatusListener(DownloadRequest downloadRequest,
317                 DownloadStatusListener callback) throws RemoteException {
318             // noop to placate the coverage tool
319             super.removeStatusListener(downloadRequest, callback);
320             mDownloadStatusListener = null;
321             return MbmsErrors.SUCCESS;
322         }
323 
324         @Override
325         public int resetDownloadKnowledge(DownloadRequest downloadRequest) throws RemoteException {
326             // noop to placate the coverage tool
327             super.resetDownloadKnowledge(downloadRequest);
328             Bundle b = new Bundle();
329             b.putString(METHOD_NAME, METHOD_RESET_DOWNLOAD_KNOWLEDGE);
330             b.putParcelable(ARGUMENT_DOWNLOAD_REQUEST, downloadRequest);
331             mReceivedCalls.add(b);
332             return MbmsErrors.SUCCESS;
333         }
334 
335         @Override
336         public void onAppCallbackDied(int uid, int subscriptionId) {
337             // noop to placate the coverage tool
338             super.onAppCallbackDied(uid, subscriptionId);
339             mAppCallback = null;
340         }
341     };
342 
343     private final IBinder mControlInterface = new ICtsDownloadMiddlewareControl.Stub() {
344         @Override
345         public void reset() {
346             mReceivedCalls.clear();
347             mHandler.removeCallbacksAndMessages(null);
348             mAppCallback = null;
349             mErrorCodeOverride = MbmsErrors.SUCCESS;
350             mReceivedRequests.clear();
351             mDownloadStatusListener = null;
352             mTempFileRootDirPath = null;
353         }
354 
355         @Override
356         public List<Bundle> getDownloadSessionCalls() {
357             return mReceivedCalls;
358         }
359 
360         @Override
361         public void forceErrorCode(int error) {
362             mErrorCodeOverride = error;
363         }
364 
365         @Override
366         public void fireErrorOnSession(int errorCode, String message) {
367             mHandler.post(() -> mAppCallback.onError(errorCode, message));
368         }
369 
370         @Override
371         public void fireOnProgressUpdated(DownloadRequest request, FileInfo fileInfo,
372                 int currentDownloadSize, int fullDownloadSize,
373                 int currentDecodedSize, int fullDecodedSize) {
374             if (mDownloadStatusListener == null) {
375                 return;
376             }
377             mHandler.post(() -> mDownloadProgressListener.onProgressUpdated(request, fileInfo,
378                     currentDownloadSize, fullDownloadSize, currentDecodedSize, fullDecodedSize));
379         }
380 
381         @Override
382         public void fireOnStateUpdated(DownloadRequest request, FileInfo fileInfo, int state) {
383             if (mDownloadStatusListener == null) {
384                 return;
385             }
386             mHandler.post(() -> mDownloadStatusListener.onStatusUpdated(request, fileInfo, state));
387         }
388 
389         @Override
390         public void actuallyStartDownloadFlow() {
391             DownloadRequest request = mReceivedRequests.get(0);
392             List<FileInfo> requestedFiles = getRequestedFiles(request);
393             // Compose the FILE_DESCRIPTOR_REQUEST_INTENT to get some FDs to write to
394             Intent requestIntent = new Intent(VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST);
395             requestIntent.putExtra(VendorUtils.EXTRA_SERVICE_ID, request.getFileServiceId());
396 
397             requestIntent.putExtra(VendorUtils.EXTRA_FD_COUNT, requestedFiles.size());
398             requestIntent.putExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT, mTempFileRootDirPath);
399             requestIntent.setComponent(CTS_TEST_RECEIVER_COMPONENT);
400 
401             // Send as an ordered broadcast, using a BroadcastReceiver to capture the result
402             // containing UriPathPairs.
403             logd("Sending fd-request broadcast");
404             sendOrderedBroadcast(requestIntent,
405                     null, // receiverPermission
406                     new BroadcastReceiver() {
407                         @Override
408                         public void onReceive(Context context, Intent intent) {
409                             logd("Got file-descriptors");
410                             Bundle extras = getResultExtras(false);
411                             List<UriPathPair> tempFiles = extras.getParcelableArrayList(
412                                     VendorUtils.EXTRA_FREE_URI_LIST);
413 
414                             for (int i = 0; i < tempFiles.size(); i++) {
415                                 UriPathPair tempFile = tempFiles.get(i);
416                                 FileInfo requestedFile = requestedFiles.get(i);
417                                 int result = writeContentsToTempFile(tempFile);
418 
419                                 Intent downloadResultIntent = composeDownloadResultIntent(
420                                         tempFile, request, result, requestedFile);
421 
422                                 logd("Sending broadcast to app: "
423                                         + downloadResultIntent.toString());
424                                 sendOrderedBroadcast(downloadResultIntent,
425                                         null, // receiverPermission
426                                         new BroadcastReceiver() {
427                                             @Override
428                                             public void onReceive(Context context, Intent intent) {
429                                                 Bundle b = new Bundle();
430                                                 b.putString(METHOD_NAME,
431                                                         METHOD_DOWNLOAD_RESULT_ACK);
432                                                 b.putInt(ARGUMENT_RESULT_CODE, getResultCode());
433                                                 mReceivedCalls.add(b);
434                                             }
435                                         },
436                                         null, // scheduler
437                                         Activity.RESULT_OK,
438                                         null, // initialData
439                                         null /* initialExtras */);
440                         }
441                         }
442                     },
443                     mHandler, // scheduler
444                     Activity.RESULT_OK,
445                     null, // initialData
446                     null /* initialExtras */);
447 
448         }
449     };
450 
getRequestedFiles(DownloadRequest request)451     private List<FileInfo> getRequestedFiles(DownloadRequest request) {
452         if (SOURCE_URI_1.equals(request.getSourceUri())) {
453             return Collections.singletonList(FILE_INFO_1);
454         }
455         if (SOURCE_URI_2.equals(request.getSourceUri())) {
456             return Collections.singletonList(FILE_INFO_2);
457         }
458         if (SOURCE_URI_3.equals(request.getSourceUri())) {
459             return FILE_SERVICE_INFO.getFiles();
460         }
461         return Collections.emptyList();
462     }
463 
composeDownloadResultIntent(UriPathPair tempFile, DownloadRequest request, int result, FileInfo downloadedFile)464     private Intent composeDownloadResultIntent(UriPathPair tempFile, DownloadRequest request,
465             int result, FileInfo downloadedFile) {
466         Intent downloadResultIntent =
467                 new Intent(VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL);
468         downloadResultIntent.putExtra(
469                 MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
470         downloadResultIntent.putExtra(VendorUtils.EXTRA_FINAL_URI,
471                 tempFile.getFilePathUri());
472         downloadResultIntent.putExtra(
473                 MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, downloadedFile);
474         downloadResultIntent.putExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT,
475                 mTempFileRootDirPath);
476         downloadResultIntent.putExtra(
477                 MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
478         downloadResultIntent.setComponent(CTS_TEST_RECEIVER_COMPONENT);
479         return downloadResultIntent;
480     }
481 
writeContentsToTempFile(UriPathPair tempFile)482     private int writeContentsToTempFile(UriPathPair tempFile) {
483         int result = MbmsDownloadSession.RESULT_SUCCESSFUL;
484         try {
485             ParcelFileDescriptor tempFileFd =
486                     getContentResolver().openFileDescriptor(
487                             tempFile.getContentUri(), "rw");
488             OutputStream destinationStream =
489                     new ParcelFileDescriptor.AutoCloseOutputStream(tempFileFd);
490 
491             destinationStream.write(SAMPLE_FILE_DATA);
492             destinationStream.flush();
493         } catch (IOException e) {
494             result = MbmsDownloadSession.RESULT_CANCELLED;
495         }
496         return result;
497     }
498 
499     @Override
onDestroy()500     public void onDestroy() {
501         super.onCreate();
502         mHandlerThread.quitSafely();
503         logd("CtsDownloadService onDestroy");
504     }
505 
506     @Override
onBind(Intent intent)507     public IBinder onBind(Intent intent) {
508         if (CONTROL_INTERFACE_ACTION.equals(intent.getAction())) {
509             logd("CtsDownloadService control interface bind");
510             return mControlInterface;
511         }
512 
513         logd("CtsDownloadService onBind");
514         if (mHandlerThread != null && mHandlerThread.isAlive()) {
515             return mDownloadServiceImpl;
516         }
517 
518         mHandlerThread = new HandlerThread("CtsDownloadServiceWorker");
519         mHandlerThread.start();
520         mHandler = new Handler(mHandlerThread.getLooper());
521         return mDownloadServiceImpl;
522     }
523 
logd(String s)524     private static void logd(String s) {
525         Log.d(TAG, s);
526     }
527 
checkInitialized()528     private void checkInitialized() {
529         if (mAppCallback == null) {
530             throw new IllegalStateException("Not yet initialized");
531         }
532     }
533 }
534