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
17package android.telephony.mbms;
18
19import android.annotation.SystemApi;
20import android.content.BroadcastReceiver;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.ActivityInfo;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.net.Uri;
29import android.os.Bundle;
30import android.telephony.MbmsDownloadSession;
31import android.telephony.mbms.vendor.VendorUtils;
32import android.util.Log;
33
34import com.android.internal.annotations.VisibleForTesting;
35
36import java.io.File;
37import java.io.FileFilter;
38import java.io.IOException;
39import java.nio.file.FileSystems;
40import java.nio.file.Files;
41import java.nio.file.Path;
42import java.nio.file.StandardCopyOption;
43import java.util.ArrayList;
44import java.util.List;
45import java.util.Objects;
46import 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 */
60public 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