Home | History | Annotate | Download | only in mbms
      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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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      */
    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
    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 
    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 
    523     private String getFileProviderAuthorityCached(Context context) {
    524         if (mFileProviderAuthorityCache != null) {
    525             return mFileProviderAuthorityCache;
    526         }
    527 
    528         mFileProviderAuthorityCache = getFileProviderAuthority(context);
    529         return mFileProviderAuthorityCache;
    530     }
    531 
    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 
    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 
    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