/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package android.telephony.mbms; import android.annotation.SystemApi; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.telephony.MbmsDownloadSession; import android.telephony.mbms.vendor.VendorUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.UUID; /** * The {@link BroadcastReceiver} responsible for handling intents sent from the middleware. Apps * that wish to download using MBMS APIs should declare this class in their AndroidManifest.xml as * follows:
{@code*/ public class MbmsDownloadReceiver extends BroadcastReceiver { /** @hide */ public static final String DOWNLOAD_TOKEN_SUFFIX = ".download_token"; /** @hide */ public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority"; private static final String EMBMS_INTENT_PERMISSION = "android.permission.SEND_EMBMS_INTENTS"; /** * Indicates that the requested operation completed without error. * @hide */ @SystemApi public static final int RESULT_OK = 0; /** * Indicates that the intent sent had an invalid action. This will be the result if * {@link Intent#getAction()} returns anything other than * {@link VendorUtils#ACTION_DOWNLOAD_RESULT_INTERNAL}, * {@link VendorUtils#ACTION_FILE_DESCRIPTOR_REQUEST}, or * {@link VendorUtils#ACTION_CLEANUP}. * This is a fatal result code and no result extras should be expected. * @hide */ @SystemApi public static final int RESULT_INVALID_ACTION = 1; /** * Indicates that the intent was missing some required extras. * This is a fatal result code and no result extras should be expected. * @hide */ @SystemApi public static final int RESULT_MALFORMED_INTENT = 2; /** * Indicates that the supplied value for {@link VendorUtils#EXTRA_TEMP_FILE_ROOT} * does not match what the app has stored. * This is a fatal result code and no result extras should be expected. * @hide */ @SystemApi public static final int RESULT_BAD_TEMP_FILE_ROOT = 3; /** * Indicates that the manager was unable to move the completed download to its final location. * This is a fatal result code and no result extras should be expected. * @hide */ @SystemApi public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4; /** * Indicates that the manager was unable to generate one or more of the requested file * descriptors. * This is a non-fatal result code -- some file descriptors may still be generated, but there * is no guarantee that they will be the same number as requested. * @hide */ @SystemApi public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5; /** * Indicates that the manager was unable to notify the app of the completed download. * This is a fatal result code and no result extras should be expected. * @hide */ @SystemApi public static final int RESULT_APP_NOTIFICATION_ERROR = 6; private static final String LOG_TAG = "MbmsDownloadReceiver"; private static final String TEMP_FILE_SUFFIX = ".embms.temp"; private static final String TEMP_FILE_STAGING_LOCATION = "staged_completed_files"; private static final int MAX_TEMP_FILE_RETRIES = 5; private String mFileProviderAuthorityCache = null; private String mMiddlewarePackageNameCache = null; /** @hide */ @Override public void onReceive(Context context, Intent intent) { verifyPermissionIntegrity(context); if (!verifyIntentContents(context, intent)) { setResultCode(RESULT_MALFORMED_INTENT); return; } if (!Objects.equals(intent.getStringExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT), MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) { setResultCode(RESULT_BAD_TEMP_FILE_ROOT); return; } if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) { moveDownloadedFile(context, intent); cleanupPostMove(context, intent); } else if (VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) { generateTempFiles(context, intent); } else if (VendorUtils.ACTION_CLEANUP.equals(intent.getAction())) { cleanupTempFiles(context, intent); } else { setResultCode(RESULT_INVALID_ACTION); } } private boolean verifyIntentContents(Context context, Intent intent) { if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) { if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT)) { Log.w(LOG_TAG, "Download result did not include a result code. Ignoring."); return false; } if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) { Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring."); return false; } // We do not need to verify below extras if the result is not success. if (MbmsDownloadSession.RESULT_SUCCESSFUL != intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, MbmsDownloadSession.RESULT_CANCELLED)) { return true; } if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) { Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring."); return false; } if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO)) { Log.w(LOG_TAG, "Download result did not include the associated file info. " + "Ignoring."); return false; } if (!intent.hasExtra(VendorUtils.EXTRA_FINAL_URI)) { Log.w(LOG_TAG, "Download result did not include the path to the final " + "temp file. Ignoring."); return false; } DownloadRequest request = intent.getParcelableExtra( MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, android.telephony.mbms.DownloadRequest.class); String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX; File expectedTokenFile = new File( MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()), expectedTokenFileName); if (!expectedTokenFile.exists()) { Log.w(LOG_TAG, "Supplied download request does not match a token that we have. " + "Expected " + expectedTokenFile); return false; } } else if (VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) { if (!intent.hasExtra(VendorUtils.EXTRA_SERVICE_ID)) { Log.w(LOG_TAG, "Temp file request did not include the associated service id." + " Ignoring."); return false; } if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) { Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring."); return false; } } else if (VendorUtils.ACTION_CLEANUP.equals(intent.getAction())) { if (!intent.hasExtra(VendorUtils.EXTRA_SERVICE_ID)) { Log.w(LOG_TAG, "Cleanup request did not include the associated service id." + " Ignoring."); return false; } if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) { Log.w(LOG_TAG, "Cleanup request did not include the temp file root. Ignoring."); return false; } if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILES_IN_USE)) { Log.w(LOG_TAG, "Cleanup request did not include the list of temp files in use. " + "Ignoring."); return false; } } return true; } private void moveDownloadedFile(Context context, Intent intent) { DownloadRequest request = intent.getParcelableExtra( MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, android.telephony.mbms.DownloadRequest.class); Intent intentForApp = request.getIntentForApp(); if (intentForApp == null) { Log.i(LOG_TAG, "Malformed app notification intent"); setResultCode(RESULT_APP_NOTIFICATION_ERROR); return; } int result = intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, MbmsDownloadSession.RESULT_CANCELLED); intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result); intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request); if (result != MbmsDownloadSession.RESULT_SUCCESSFUL) { Log.i(LOG_TAG, "Download request indicated a failed download. Aborting."); context.sendBroadcast(intentForApp); setResultCode(RESULT_OK); return; } Uri finalTempFile = intent.getParcelableExtra(VendorUtils.EXTRA_FINAL_URI, android.net.Uri.class); if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) { Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile); setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR); return; } FileInfo completedFileInfo = (FileInfo) intent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, android.telephony.mbms.FileInfo.class); Path appSpecifiedDestination = FileSystems.getDefault().getPath( request.getDestinationUri().getPath()); Uri finalLocation; try { String relativeLocation = getFileRelativePath(request.getSourceUri().getPath(), completedFileInfo.getUri().getPath()); finalLocation = moveToFinalLocation(finalTempFile, appSpecifiedDestination, relativeLocation); } catch (IOException e) { Log.w(LOG_TAG, "Failed to move temp file to final destination"); setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR); return; } intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI, finalLocation); intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo); context.sendBroadcast(intentForApp); setResultCode(RESULT_OK); } private void cleanupPostMove(Context context, Intent intent) { DownloadRequest request = intent.getParcelableExtra( MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, android.telephony.mbms.DownloadRequest.class); if (request == null) { Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring."); return; } List}