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.mbms;
18 
19 import android.annotation.SystemApi;
20 import android.content.BroadcastReceiver;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.ActivityInfo;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.telephony.MbmsDownloadSession;
31 import android.telephony.mbms.vendor.VendorUtils;
32 import android.util.Log;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 
36 import java.io.File;
37 import java.io.FileFilter;
38 import java.io.IOException;
39 import java.nio.file.FileSystems;
40 import java.nio.file.Files;
41 import java.nio.file.Path;
42 import java.nio.file.StandardCopyOption;
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.Objects;
46 import java.util.UUID;
47 
48 /**
49  * The {@link BroadcastReceiver} responsible for handling intents sent from the middleware. Apps
50  * that wish to download using MBMS APIs should declare this class in their AndroidManifest.xml as
51  * follows:
52 <pre>{@code
53 <receiver
54     android:name="android.telephony.mbms.MbmsDownloadReceiver"
55     android:permission="android.permission.SEND_EMBMS_INTENTS"
56     android:enabled="true"
57     android:exported="true">
58 </receiver>}</pre>
59  */
60 public class MbmsDownloadReceiver extends BroadcastReceiver {
61     /** @hide */
62     public static final String DOWNLOAD_TOKEN_SUFFIX = ".download_token";
63     /** @hide */
64     public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority";
65 
66     private static final String EMBMS_INTENT_PERMISSION = "android.permission.SEND_EMBMS_INTENTS";
67 
68     /**
69      * Indicates that the requested operation completed without error.
70      * @hide
71      */
72     @SystemApi
73     public static final int RESULT_OK = 0;
74 
75     /**
76      * Indicates that the intent sent had an invalid action. This will be the result if
77      * {@link Intent#getAction()} returns anything other than
78      * {@link VendorUtils#ACTION_DOWNLOAD_RESULT_INTERNAL},
79      * {@link VendorUtils#ACTION_FILE_DESCRIPTOR_REQUEST}, or
80      * {@link VendorUtils#ACTION_CLEANUP}.
81      * This is a fatal result code and no result extras should be expected.
82      * @hide
83      */
84     @SystemApi
85     public static final int RESULT_INVALID_ACTION = 1;
86 
87     /**
88      * Indicates that the intent was missing some required extras.
89      * This is a fatal result code and no result extras should be expected.
90      * @hide
91      */
92     @SystemApi
93     public static final int RESULT_MALFORMED_INTENT = 2;
94 
95     /**
96      * Indicates that the supplied value for {@link VendorUtils#EXTRA_TEMP_FILE_ROOT}
97      * does not match what the app has stored.
98      * This is a fatal result code and no result extras should be expected.
99      * @hide
100      */
101     @SystemApi
102     public static final int RESULT_BAD_TEMP_FILE_ROOT = 3;
103 
104     /**
105      * Indicates that the manager was unable to move the completed download to its final location.
106      * This is a fatal result code and no result extras should be expected.
107      * @hide
108      */
109     @SystemApi
110     public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4;
111 
112     /**
113      * Indicates that the manager was unable to generate one or more of the requested file
114      * descriptors.
115      * This is a non-fatal result code -- some file descriptors may still be generated, but there
116      * is no guarantee that they will be the same number as requested.
117      * @hide
118      */
119     @SystemApi
120     public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5;
121 
122     /**
123      * Indicates that the manager was unable to notify the app of the completed download.
124      * This is a fatal result code and no result extras should be expected.
125      * @hide
126      */
127     @SystemApi
128     public static final int RESULT_APP_NOTIFICATION_ERROR = 6;
129 
130 
131     private static final String LOG_TAG = "MbmsDownloadReceiver";
132     private static final String TEMP_FILE_SUFFIX = ".embms.temp";
133     private static final String TEMP_FILE_STAGING_LOCATION = "staged_completed_files";
134 
135     private static final int MAX_TEMP_FILE_RETRIES = 5;
136 
137     private String mFileProviderAuthorityCache = null;
138     private String mMiddlewarePackageNameCache = null;
139 
140     /** @hide */
141     @Override
onReceive(Context context, Intent intent)142     public void onReceive(Context context, Intent intent) {
143         verifyPermissionIntegrity(context);
144 
145         if (!verifyIntentContents(context, intent)) {
146             setResultCode(RESULT_MALFORMED_INTENT);
147             return;
148         }
149         if (!Objects.equals(intent.getStringExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT),
150                 MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) {
151             setResultCode(RESULT_BAD_TEMP_FILE_ROOT);
152             return;
153         }
154 
155         if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
156             moveDownloadedFile(context, intent);
157             cleanupPostMove(context, intent);
158         } else if (VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
159             generateTempFiles(context, intent);
160         } else if (VendorUtils.ACTION_CLEANUP.equals(intent.getAction())) {
161             cleanupTempFiles(context, intent);
162         } else {
163             setResultCode(RESULT_INVALID_ACTION);
164         }
165     }
166 
verifyIntentContents(Context context, Intent intent)167     private boolean verifyIntentContents(Context context, Intent intent) {
168         if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
169             if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT)) {
170                 Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
171                 return false;
172             }
173             if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
174                 Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
175                 return false;
176             }
177             // We do not need to verify below extras if the result is not success.
178             if (MbmsDownloadSession.RESULT_SUCCESSFUL !=
179                     intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
180                     MbmsDownloadSession.RESULT_CANCELLED)) {
181                 return true;
182             }
183             if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
184                 Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
185                 return false;
186             }
187             if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO)) {
188                 Log.w(LOG_TAG, "Download result did not include the associated file info. " +
189                         "Ignoring.");
190                 return false;
191             }
192             if (!intent.hasExtra(VendorUtils.EXTRA_FINAL_URI)) {
193                 Log.w(LOG_TAG, "Download result did not include the path to the final " +
194                         "temp file. Ignoring.");
195                 return false;
196             }
197             DownloadRequest request = intent.getParcelableExtra(
198                     MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
199             String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
200             File expectedTokenFile = new File(
201                     MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()),
202                     expectedTokenFileName);
203             if (!expectedTokenFile.exists()) {
204                 Log.w(LOG_TAG, "Supplied download request does not match a token that we have. " +
205                         "Expected " + expectedTokenFile);
206                 return false;
207             }
208         } else if (VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
209             if (!intent.hasExtra(VendorUtils.EXTRA_SERVICE_ID)) {
210                 Log.w(LOG_TAG, "Temp file request did not include the associated service id." +
211                         " Ignoring.");
212                 return false;
213             }
214             if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
215                 Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
216                 return false;
217             }
218         } else if (VendorUtils.ACTION_CLEANUP.equals(intent.getAction())) {
219             if (!intent.hasExtra(VendorUtils.EXTRA_SERVICE_ID)) {
220                 Log.w(LOG_TAG, "Cleanup request did not include the associated service id." +
221                         " Ignoring.");
222                 return false;
223             }
224             if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
225                 Log.w(LOG_TAG, "Cleanup request did not include the temp file root. Ignoring.");
226                 return false;
227             }
228             if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILES_IN_USE)) {
229                 Log.w(LOG_TAG, "Cleanup request did not include the list of temp files in use. " +
230                         "Ignoring.");
231                 return false;
232             }
233         }
234         return true;
235     }
236 
moveDownloadedFile(Context context, Intent intent)237     private void moveDownloadedFile(Context context, Intent intent) {
238         DownloadRequest request = intent.getParcelableExtra(
239                 MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
240         Intent intentForApp = request.getIntentForApp();
241         if (intentForApp == null) {
242             Log.i(LOG_TAG, "Malformed app notification intent");
243             setResultCode(RESULT_APP_NOTIFICATION_ERROR);
244             return;
245         }
246 
247         int result = intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
248                 MbmsDownloadSession.RESULT_CANCELLED);
249         intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
250         intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
251 
252         if (result != MbmsDownloadSession.RESULT_SUCCESSFUL) {
253             Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");
254             context.sendBroadcast(intentForApp);
255             setResultCode(RESULT_OK);
256             return;
257         }
258 
259         Uri finalTempFile = intent.getParcelableExtra(VendorUtils.EXTRA_FINAL_URI);
260         if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) {
261             Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
262             setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
263             return;
264         }
265 
266         FileInfo completedFileInfo =
267                 (FileInfo) intent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO);
268         Path appSpecifiedDestination = FileSystems.getDefault().getPath(
269                 request.getDestinationUri().getPath());
270 
271         Uri finalLocation;
272         try {
273             String relativeLocation = getFileRelativePath(request.getSourceUri().getPath(),
274                     completedFileInfo.getUri().getPath());
275             finalLocation = moveToFinalLocation(finalTempFile, appSpecifiedDestination,
276                     relativeLocation);
277         } catch (IOException e) {
278             Log.w(LOG_TAG, "Failed to move temp file to final destination");
279             setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
280             return;
281         }
282         intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI, finalLocation);
283         intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo);
284 
285         context.sendBroadcast(intentForApp);
286         setResultCode(RESULT_OK);
287     }
288 
cleanupPostMove(Context context, Intent intent)289     private void cleanupPostMove(Context context, Intent intent) {
290         DownloadRequest request = intent.getParcelableExtra(
291                 MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
292         if (request == null) {
293             Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
294             return;
295         }
296 
297         List<Uri> tempFiles = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_LIST);
298         if (tempFiles == null) {
299             return;
300         }
301 
302         for (Uri tempFileUri : tempFiles) {
303             if (verifyTempFilePath(context, request.getFileServiceId(), tempFileUri)) {
304                 File tempFile = new File(tempFileUri.getSchemeSpecificPart());
305                 if (!tempFile.delete()) {
306                     Log.w(LOG_TAG, "Failed to delete temp file at " + tempFile.getPath());
307                 }
308             }
309         }
310     }
311 
generateTempFiles(Context context, Intent intent)312     private void generateTempFiles(Context context, Intent intent) {
313         String serviceId = intent.getStringExtra(VendorUtils.EXTRA_SERVICE_ID);
314         if (serviceId == null) {
315             Log.w(LOG_TAG, "Temp file request did not include the associated service id. " +
316                     "Ignoring.");
317             setResultCode(RESULT_MALFORMED_INTENT);
318             return;
319         }
320         int fdCount = intent.getIntExtra(VendorUtils.EXTRA_FD_COUNT, 0);
321         List<Uri> pausedList = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_PAUSED_LIST);
322 
323         if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
324             Log.i(LOG_TAG, "No temp files actually requested. Ending.");
325             setResultCode(RESULT_OK);
326             setResultExtras(Bundle.EMPTY);
327             return;
328         }
329 
330         ArrayList<UriPathPair> freshTempFiles =
331                 generateFreshTempFiles(context, serviceId, fdCount);
332         ArrayList<UriPathPair> pausedFiles =
333                 generateUrisForPausedFiles(context, serviceId, pausedList);
334 
335         Bundle result = new Bundle();
336         result.putParcelableArrayList(VendorUtils.EXTRA_FREE_URI_LIST, freshTempFiles);
337         result.putParcelableArrayList(VendorUtils.EXTRA_PAUSED_URI_LIST, pausedFiles);
338         setResultCode(RESULT_OK);
339         setResultExtras(result);
340     }
341 
generateFreshTempFiles(Context context, String serviceId, int freshFdCount)342     private ArrayList<UriPathPair> generateFreshTempFiles(Context context, String serviceId,
343             int freshFdCount) {
344         File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceId);
345         if (!tempFileDir.exists()) {
346             tempFileDir.mkdirs();
347         }
348 
349         // Name the files with the template "N-UUID", where N is the request ID and UUID is a
350         // random uuid.
351         ArrayList<UriPathPair> result = new ArrayList<>(freshFdCount);
352         for (int i = 0; i < freshFdCount; i++) {
353             File tempFile = generateSingleTempFile(tempFileDir);
354             if (tempFile == null) {
355                 setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
356                 Log.w(LOG_TAG, "Failed to generate a temp file. Moving on.");
357                 continue;
358             }
359             Uri fileUri = Uri.fromFile(tempFile);
360             Uri contentUri = MbmsTempFileProvider.getUriForFile(
361                     context, getFileProviderAuthorityCached(context), tempFile);
362             context.grantUriPermission(getMiddlewarePackageCached(context), contentUri,
363                     Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
364             result.add(new UriPathPair(fileUri, contentUri));
365         }
366 
367         return result;
368     }
369 
generateSingleTempFile(File tempFileDir)370     private static File generateSingleTempFile(File tempFileDir) {
371         int numTries = 0;
372         while (numTries < MAX_TEMP_FILE_RETRIES) {
373             numTries++;
374             String fileName =  UUID.randomUUID() + TEMP_FILE_SUFFIX;
375             File tempFile = new File(tempFileDir, fileName);
376             try {
377                 if (tempFile.createNewFile()) {
378                     return tempFile.getCanonicalFile();
379                 }
380             } catch (IOException e) {
381                 continue;
382             }
383         }
384         return null;
385     }
386 
generateUrisForPausedFiles(Context context, String serviceId, List<Uri> pausedFiles)387     private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context,
388             String serviceId, List<Uri> pausedFiles) {
389         if (pausedFiles == null) {
390             return new ArrayList<>(0);
391         }
392         ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size());
393 
394         for (Uri fileUri : pausedFiles) {
395             if (!verifyTempFilePath(context, serviceId, fileUri)) {
396                 Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume");
397                 setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
398                 continue;
399             }
400             File tempFile = new File(fileUri.getSchemeSpecificPart());
401             if (!tempFile.exists()) {
402                 Log.w(LOG_TAG, "Supplied file " + fileUri + " does not exist.");
403                 setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR);
404                 continue;
405             }
406             Uri contentUri = MbmsTempFileProvider.getUriForFile(
407                     context, getFileProviderAuthorityCached(context), tempFile);
408             context.grantUriPermission(getMiddlewarePackageCached(context), contentUri,
409                     Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
410 
411             result.add(new UriPathPair(fileUri, contentUri));
412         }
413         return result;
414     }
415 
cleanupTempFiles(Context context, Intent intent)416     private void cleanupTempFiles(Context context, Intent intent) {
417         String serviceId = intent.getStringExtra(VendorUtils.EXTRA_SERVICE_ID);
418         File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceId);
419         final List<Uri> filesInUse =
420                 intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_FILES_IN_USE);
421         File[] filesToDelete = tempFileDir.listFiles(new FileFilter() {
422             @Override
423             public boolean accept(File file) {
424                 File canonicalFile;
425                 try {
426                     canonicalFile = file.getCanonicalFile();
427                 } catch (IOException e) {
428                     Log.w(LOG_TAG, "Got IOException canonicalizing " + file + ", not deleting.");
429                     return false;
430                 }
431                 // Reject all files that don't match what we think a temp file should look like
432                 // e.g. download tokens
433                 if (!canonicalFile.getName().endsWith(TEMP_FILE_SUFFIX)) {
434                     return false;
435                 }
436                 // If any of the files in use match the uri, return false to reject it from the
437                 // list to delete.
438                 Uri fileInUseUri = Uri.fromFile(canonicalFile);
439                 return !filesInUse.contains(fileInUseUri);
440             }
441         });
442         for (File fileToDelete : filesToDelete) {
443             fileToDelete.delete();
444         }
445     }
446 
447     /*
448      * Moves a tempfile located at fromPath to its final home where the app wants it
449      */
moveToFinalLocation(Uri fromPath, Path appSpecifiedPath, String relativeLocation)450     private static Uri moveToFinalLocation(Uri fromPath, Path appSpecifiedPath,
451             String relativeLocation) throws IOException {
452         if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) {
453             Log.w(LOG_TAG, "Downloaded file location uri " + fromPath +
454                     " does not have a file scheme");
455             return null;
456         }
457 
458         Path fromFile = FileSystems.getDefault().getPath(fromPath.getPath());
459         Path toFile = appSpecifiedPath.resolve(relativeLocation);
460 
461         if (!Files.isDirectory(toFile.getParent())) {
462             Files.createDirectories(toFile.getParent());
463         }
464         Path result = Files.move(fromFile, toFile,
465                 StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
466 
467         return Uri.fromFile(result.toFile());
468     }
469 
470     /**
471      * @hide
472      */
473     @VisibleForTesting
getFileRelativePath(String sourceUriPath, String fileInfoPath)474     public static String getFileRelativePath(String sourceUriPath, String fileInfoPath) {
475         if (sourceUriPath.endsWith("*")) {
476             // This is a wildcard path. Strip the last path component and use that as the root of
477             // the relative path.
478             int lastSlash = sourceUriPath.lastIndexOf('/');
479             sourceUriPath = sourceUriPath.substring(0, lastSlash);
480         }
481         if (!fileInfoPath.startsWith(sourceUriPath)) {
482             Log.e(LOG_TAG, "File location specified in FileInfo does not match the source URI."
483                     + " source: " + sourceUriPath + " fileinfo path: " + fileInfoPath);
484             return null;
485         }
486         if (fileInfoPath.length() == sourceUriPath.length()) {
487             // This is the single-file download case. Return the name of the file so that the
488             // receiver puts the file directly into the dest directory.
489             return sourceUriPath.substring(sourceUriPath.lastIndexOf('/') + 1);
490         }
491 
492         String prefixOmittedPath = fileInfoPath.substring(sourceUriPath.length());
493         if (prefixOmittedPath.startsWith("/")) {
494             prefixOmittedPath = prefixOmittedPath.substring(1);
495         }
496         return prefixOmittedPath;
497     }
498 
verifyTempFilePath(Context context, String serviceId, Uri filePath)499     private static boolean verifyTempFilePath(Context context, String serviceId,
500             Uri filePath) {
501         if (!ContentResolver.SCHEME_FILE.equals(filePath.getScheme())) {
502             Log.w(LOG_TAG, "Uri " + filePath + " does not have a file scheme");
503             return false;
504         }
505 
506         String path = filePath.getSchemeSpecificPart();
507         File tempFile = new File(path);
508         if (!tempFile.exists()) {
509             Log.w(LOG_TAG, "File at " + path + " does not exist.");
510             return false;
511         }
512 
513         if (!MbmsUtils.isContainedIn(
514                 MbmsUtils.getEmbmsTempFileDirForService(context, serviceId), tempFile)) {
515             Log.w(LOG_TAG, "File at " + path + " is not contained in the temp file root," +
516                     " which is " + MbmsUtils.getEmbmsTempFileDirForService(context, serviceId));
517             return false;
518         }
519 
520         return true;
521     }
522 
getFileProviderAuthorityCached(Context context)523     private String getFileProviderAuthorityCached(Context context) {
524         if (mFileProviderAuthorityCache != null) {
525             return mFileProviderAuthorityCache;
526         }
527 
528         mFileProviderAuthorityCache = getFileProviderAuthority(context);
529         return mFileProviderAuthorityCache;
530     }
531 
getFileProviderAuthority(Context context)532     private static String getFileProviderAuthority(Context context) {
533         ApplicationInfo appInfo;
534         try {
535             appInfo = context.getPackageManager()
536                     .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
537         } catch (PackageManager.NameNotFoundException e) {
538             throw new RuntimeException("Package manager couldn't find " + context.getPackageName());
539         }
540         if (appInfo.metaData == null) {
541             throw new RuntimeException("App must declare the file provider authority as metadata " +
542                     "in the manifest.");
543         }
544         String authority = appInfo.metaData.getString(MBMS_FILE_PROVIDER_META_DATA_KEY);
545         if (authority == null) {
546             throw new RuntimeException("App must declare the file provider authority as metadata " +
547                     "in the manifest.");
548         }
549         return authority;
550     }
551 
getMiddlewarePackageCached(Context context)552     private String getMiddlewarePackageCached(Context context) {
553         if (mMiddlewarePackageNameCache == null) {
554             mMiddlewarePackageNameCache = MbmsUtils.getMiddlewareServiceInfo(context,
555                     MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
556         }
557         return mMiddlewarePackageNameCache;
558     }
559 
verifyPermissionIntegrity(Context context)560     private void verifyPermissionIntegrity(Context context) {
561         PackageManager pm = context.getPackageManager();
562         Intent queryIntent = new Intent(context, MbmsDownloadReceiver.class);
563         List<ResolveInfo> infos = pm.queryBroadcastReceivers(queryIntent, 0);
564         if (infos.size() != 1) {
565             throw new IllegalStateException("Non-unique download receiver in your app");
566         }
567         ActivityInfo selfInfo = infos.get(0).activityInfo;
568         if (selfInfo == null) {
569             throw new IllegalStateException("Queried ResolveInfo does not contain a receiver");
570         }
571         if (MbmsUtils.getOverrideServiceName(context,
572                 MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION) != null) {
573             // If an override was specified, just make sure that the permission isn't null.
574             if (selfInfo.permission == null) {
575                 throw new IllegalStateException(
576                         "MbmsDownloadReceiver must require some permission");
577             }
578             return;
579         }
580         if (!Objects.equals(EMBMS_INTENT_PERMISSION, selfInfo.permission)) {
581             throw new IllegalStateException("MbmsDownloadReceiver must require the " +
582                     "SEND_EMBMS_INTENTS permission.");
583         }
584     }
585 }
586