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