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