Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2015 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 package com.android.messaging.util;
     17 
     18 import android.content.ContentResolver;
     19 import android.content.Context;
     20 import android.content.res.AssetFileDescriptor;
     21 import android.media.MediaMetadataRetriever;
     22 import android.net.Uri;
     23 import android.os.ParcelFileDescriptor;
     24 import android.provider.MediaStore;
     25 import android.support.annotation.NonNull;
     26 import android.text.TextUtils;
     27 
     28 import com.android.messaging.Factory;
     29 import com.android.messaging.datamodel.MediaScratchFileProvider;
     30 import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
     31 import com.google.common.io.ByteStreams;
     32 
     33 import java.io.BufferedInputStream;
     34 import java.io.File;
     35 import java.io.FileNotFoundException;
     36 import java.io.IOException;
     37 import java.io.InputStream;
     38 import java.io.OutputStream;
     39 import java.net.URL;
     40 import java.net.URLConnection;
     41 import java.util.Arrays;
     42 import java.util.HashSet;
     43 
     44 public class UriUtil {
     45     private static final String SCHEME_SMS = "sms";
     46     private static final String SCHEME_SMSTO = "smsto";
     47     private static final String SCHEME_MMS = "mms";
     48     private static final String SCHEME_MMSTO = "smsto";
     49     public static final HashSet<String> SMS_MMS_SCHEMES = new HashSet<String>(
     50         Arrays.asList(SCHEME_SMS, SCHEME_MMS, SCHEME_SMSTO, SCHEME_MMSTO));
     51 
     52     public static final String SCHEME_BUGLE = "bugle";
     53     public static final HashSet<String> SUPPORTED_SCHEME = new HashSet<String>(
     54         Arrays.asList(ContentResolver.SCHEME_ANDROID_RESOURCE,
     55             ContentResolver.SCHEME_CONTENT,
     56             ContentResolver.SCHEME_FILE,
     57             SCHEME_BUGLE));
     58 
     59     public static final String SCHEME_TEL = "tel:";
     60 
     61     /**
     62      * Get a Uri representation of the file path of a resource file.
     63      */
     64     public static Uri getUriForResourceFile(final String path) {
     65         return TextUtils.isEmpty(path) ? null : Uri.fromFile(new File(path));
     66     }
     67 
     68     /**
     69      * Extract the path from a file:// Uri, or null if the uri is of other scheme.
     70      */
     71     public static String getFilePathFromUri(final Uri uri) {
     72         if (!isFileUri(uri)) {
     73             return null;
     74         }
     75         return uri.getPath();
     76     }
     77 
     78     /**
     79      * Returns whether the given Uri is local or remote.
     80      */
     81     public static boolean isLocalResourceUri(final Uri uri) {
     82         final String scheme = uri.getScheme();
     83         return TextUtils.equals(scheme, ContentResolver.SCHEME_ANDROID_RESOURCE) ||
     84                 TextUtils.equals(scheme, ContentResolver.SCHEME_CONTENT) ||
     85                 TextUtils.equals(scheme, ContentResolver.SCHEME_FILE);
     86     }
     87 
     88     /**
     89      * Returns whether the given Uri is part of Bugle's app package
     90      */
     91     public static boolean isBugleAppResource(final Uri uri) {
     92         final String scheme = uri.getScheme();
     93         return TextUtils.equals(scheme, ContentResolver.SCHEME_ANDROID_RESOURCE);
     94     }
     95 
     96     public static boolean isFileUri(final Uri uri) {
     97         return uri != null && TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_FILE);
     98     }
     99 
    100     /**
    101      * Constructs an android.resource:// uri for the given resource id.
    102      */
    103     public static Uri getUriForResourceId(final Context context, final int resId) {
    104         return new Uri.Builder()
    105                 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
    106                 .authority(context.getPackageName())
    107                 .appendPath(String.valueOf(resId))
    108                 .build();
    109     }
    110 
    111     /**
    112      * Returns whether the given Uri string is local.
    113      */
    114     public static boolean isLocalUri(@NonNull final Uri uri) {
    115         Assert.notNull(uri);
    116         return SUPPORTED_SCHEME.contains(uri.getScheme());
    117     }
    118 
    119     private static final String MEDIA_STORE_URI_KLP = "com.android.providers.media.documents";
    120 
    121     /**
    122      * Check if a URI is from the MediaStore
    123      */
    124     public static boolean isMediaStoreUri(final Uri uri) {
    125         final String uriAuthority = uri.getAuthority();
    126         return TextUtils.equals(ContentResolver.SCHEME_CONTENT, uri.getScheme())
    127                 && (TextUtils.equals(MediaStore.AUTHORITY, uriAuthority) ||
    128                 // KK changed the media store authority name
    129                 TextUtils.equals(MEDIA_STORE_URI_KLP, uriAuthority));
    130     }
    131 
    132     /**
    133      * Gets the size in bytes for the content uri. Currently we only support content in the
    134      * scratch space.
    135      */
    136     @DoesNotRunOnMainThread
    137     public static long getContentSize(final Uri uri) {
    138         Assert.isNotMainThread();
    139         if (isLocalResourceUri(uri)) {
    140             ParcelFileDescriptor pfd = null;
    141             try {
    142                 pfd = Factory.get().getApplicationContext()
    143                         .getContentResolver().openFileDescriptor(uri, "r");
    144                 return Math.max(pfd.getStatSize(), 0);
    145             } catch (final FileNotFoundException e) {
    146                 LogUtil.e(LogUtil.BUGLE_TAG, "Error getting content size", e);
    147             } finally {
    148                 if (pfd != null) {
    149                     try {
    150                         pfd.close();
    151                     } catch (final IOException e) {
    152                         // Do nothing.
    153                     }
    154                 }
    155             }
    156         } else {
    157             Assert.fail("Unsupported uri type!");
    158         }
    159         return 0;
    160     }
    161 
    162     /** @return duration in milliseconds or 0 if not able to determine */
    163     public static int getMediaDurationMs(final Uri uri) {
    164         final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
    165         try {
    166             retriever.setDataSource(uri);
    167             return retriever.extractInteger(MediaMetadataRetriever.METADATA_KEY_DURATION, 0);
    168         } catch (final IOException e) {
    169             LogUtil.e(LogUtil.BUGLE_TAG, "Unable extract duration from media file: " + uri, e);
    170             return 0;
    171         } finally {
    172             retriever.release();
    173         }
    174     }
    175 
    176     /**
    177      * Persist a piece of content from the given input stream, byte by byte to the scratch
    178      * directory.
    179      * @return the output Uri if the operation succeeded, or null if failed.
    180      */
    181     @DoesNotRunOnMainThread
    182     public static Uri persistContentToScratchSpace(final InputStream inputStream) {
    183         final Context context = Factory.get().getApplicationContext();
    184         final Uri scratchSpaceUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(null);
    185         return copyContent(context, inputStream, scratchSpaceUri);
    186     }
    187 
    188     /**
    189      * Persist a piece of content from the given sourceUri, byte by byte to the scratch
    190      * directory.
    191      * @return the output Uri if the operation succeeded, or null if failed.
    192      */
    193     @DoesNotRunOnMainThread
    194     public static Uri persistContentToScratchSpace(final Uri sourceUri) {
    195         InputStream inputStream = null;
    196         final Context context = Factory.get().getApplicationContext();
    197         try {
    198             if (UriUtil.isLocalResourceUri(sourceUri)) {
    199                 inputStream = context.getContentResolver().openInputStream(sourceUri);
    200             } else {
    201                 // The content is remote. Download it.
    202                 final URL url = new URL(sourceUri.toString());
    203                 final URLConnection ucon = url.openConnection();
    204                 inputStream = new BufferedInputStream(ucon.getInputStream());
    205             }
    206             return persistContentToScratchSpace(inputStream);
    207         } catch (final Exception ex) {
    208             LogUtil.e(LogUtil.BUGLE_TAG, "Error while retrieving media ", ex);
    209             return null;
    210         } finally {
    211             if (inputStream != null) {
    212                 try {
    213                     inputStream.close();
    214                 } catch (final IOException e) {
    215                     LogUtil.e(LogUtil.BUGLE_TAG, "error trying to close the inputStream", e);
    216                 }
    217             }
    218         }
    219     }
    220 
    221     /**
    222      * Persist a piece of content from the given input stream, byte by byte to the specified
    223      * directory.
    224      * @return the output Uri if the operation succeeded, or null if failed.
    225      */
    226     @DoesNotRunOnMainThread
    227     public static Uri persistContent(
    228             final InputStream inputStream, final File outputDir, final String contentType) {
    229         if (!outputDir.exists() && !outputDir.mkdirs()) {
    230             LogUtil.e(LogUtil.BUGLE_TAG, "Error creating " + outputDir.getAbsolutePath());
    231             return null;
    232         }
    233 
    234         final Context context = Factory.get().getApplicationContext();
    235         try {
    236             final Uri targetUri = Uri.fromFile(FileUtil.getNewFile(outputDir, contentType));
    237             return copyContent(context, inputStream, targetUri);
    238         } catch (final IOException e) {
    239             LogUtil.e(LogUtil.BUGLE_TAG, "Error creating file in " + outputDir.getAbsolutePath());
    240             return null;
    241         }
    242     }
    243 
    244     /**
    245      * Persist a piece of content from the given sourceUri, byte by byte to the
    246      * specified output directory.
    247      * @return the output Uri if the operation succeeded, or null if failed.
    248      */
    249     @DoesNotRunOnMainThread
    250     public static Uri persistContent(
    251             final Uri sourceUri, final File outputDir, final String contentType) {
    252         InputStream inputStream = null;
    253         final Context context = Factory.get().getApplicationContext();
    254         try {
    255             if (UriUtil.isLocalResourceUri(sourceUri)) {
    256                 inputStream = context.getContentResolver().openInputStream(sourceUri);
    257             } else {
    258                 // The content is remote. Download it.
    259                 final URL url = new URL(sourceUri.toString());
    260                 final URLConnection ucon = url.openConnection();
    261                 inputStream = new BufferedInputStream(ucon.getInputStream());
    262             }
    263             return persistContent(inputStream, outputDir, contentType);
    264         } catch (final Exception ex) {
    265             LogUtil.e(LogUtil.BUGLE_TAG, "Error while retrieving media ", ex);
    266             return null;
    267         } finally {
    268             if (inputStream != null) {
    269                 try {
    270                     inputStream.close();
    271                 } catch (final IOException e) {
    272                     LogUtil.e(LogUtil.BUGLE_TAG, "error trying to close the inputStream", e);
    273                 }
    274             }
    275         }
    276     }
    277 
    278     /** @return uri of target file, or null on error */
    279     @DoesNotRunOnMainThread
    280     private static Uri copyContent(
    281             final Context context, final InputStream inputStream, final Uri targetUri) {
    282         Assert.isNotMainThread();
    283         OutputStream outputStream = null;
    284         try {
    285             outputStream = context.getContentResolver().openOutputStream(targetUri);
    286             ByteStreams.copy(inputStream, outputStream);
    287         } catch (final Exception ex) {
    288             LogUtil.e(LogUtil.BUGLE_TAG, "Error while copying content ", ex);
    289             return null;
    290         } finally {
    291             if (outputStream != null) {
    292                 try {
    293                     outputStream.flush();
    294                 } catch (final IOException e) {
    295                     LogUtil.e(LogUtil.BUGLE_TAG, "error trying to flush the outputStream", e);
    296                     return null;
    297                 } finally {
    298                     try {
    299                         outputStream.close();
    300                     } catch (final IOException e) {
    301                         // Do nothing.
    302                     }
    303                 }
    304             }
    305         }
    306         return targetUri;
    307     }
    308 
    309     public static boolean isSmsMmsUri(final Uri uri) {
    310         return uri != null && SMS_MMS_SCHEMES.contains(uri.getScheme());
    311     }
    312 
    313     /**
    314      * Extract recipient destinations from Uri of form
    315      *     SCHEME:destionation[,destination]?otherstuff
    316      * where SCHEME is one of the supported sms/mms schemes.
    317      *
    318      * @param uri sms/mms uri
    319      * @return recipient destinations or null
    320      */
    321     public static String[] parseRecipientsFromSmsMmsUri(final Uri uri) {
    322         if (!isSmsMmsUri(uri)) {
    323             return null;
    324         }
    325         final String[] parts = uri.getSchemeSpecificPart().split("\\?");
    326         if (TextUtils.isEmpty(parts[0])) {
    327             return null;
    328         }
    329         // replaceUnicodeDigits will replace digits typed in other languages (i.e. Egyptian) with
    330         // the usual ascii equivalents.
    331         return TextUtil.replaceUnicodeDigits(parts[0]).replace(';', ',').split(",");
    332     }
    333 
    334     /**
    335      * Return the length of the file to which contentUri refers
    336      *
    337      * @param contentUri URI for the file of which we want the length
    338      * @return Length of the file or AssetFileDescriptor.UNKNOWN_LENGTH
    339      */
    340     public static long getUriContentLength(final Uri contentUri) {
    341         final Context context = Factory.get().getApplicationContext();
    342         AssetFileDescriptor afd = null;
    343         try {
    344             afd = context.getContentResolver().openAssetFileDescriptor(contentUri, "r");
    345             return afd.getLength();
    346         } catch (final FileNotFoundException e) {
    347             LogUtil.w(LogUtil.BUGLE_TAG, "Failed to query length of " + contentUri);
    348         } finally {
    349             if (afd != null) {
    350                 try {
    351                     afd.close();
    352                 } catch (final IOException e) {
    353                     LogUtil.w(LogUtil.BUGLE_TAG, "Failed to close afd for " + contentUri);
    354                 }
    355             }
    356         }
    357         return AssetFileDescriptor.UNKNOWN_LENGTH;
    358     }
    359 
    360     /** @return string representation of URI or null if URI was null */
    361     public static String stringFromUri(final Uri uri) {
    362         return uri == null ? null : uri.toString();
    363     }
    364 
    365     /** @return URI created from string or null if string was null or empty */
    366     public static Uri uriFromString(final String uriString) {
    367         return TextUtils.isEmpty(uriString) ? null : Uri.parse(uriString);
    368      }
    369 }
    370