summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/util/UriUtil.java
blob: 7cd60621d427fff8d8a1f8be2a98de0a14425dba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
/*
 * Copyright (C) 2015 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 com.android.messaging.util;

import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.text.TextUtils;

import com.android.messaging.Factory;
import com.android.messaging.datamodel.MediaScratchFileProvider;
import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
import com.google.common.io.ByteStreams;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.HashSet;

public class UriUtil {
    private static final String SCHEME_SMS = "sms";
    private static final String SCHEME_SMSTO = "smsto";
    private static final String SCHEME_MMS = "mms";
    private static final String SCHEME_MMSTO = "smsto";
    public static final HashSet<String> SMS_MMS_SCHEMES = new HashSet<String>(
        Arrays.asList(SCHEME_SMS, SCHEME_MMS, SCHEME_SMSTO, SCHEME_MMSTO));

    public static final String SCHEME_BUGLE = "bugle";
    public static final HashSet<String> SUPPORTED_SCHEME = new HashSet<String>(
        Arrays.asList(ContentResolver.SCHEME_ANDROID_RESOURCE,
            ContentResolver.SCHEME_CONTENT,
            ContentResolver.SCHEME_FILE,
            SCHEME_BUGLE));

    public static final String SCHEME_TEL = "tel:";

    /**
     * Get a Uri representation of the file path of a resource file.
     */
    public static Uri getUriForResourceFile(final String path) {
        return TextUtils.isEmpty(path) ? null : Uri.fromFile(new File(path));
    }

    /**
     * Extract the path from a file:// Uri, or null if the uri is of other scheme.
     */
    public static String getFilePathFromUri(final Uri uri) {
        if (!isFileUri(uri)) {
            return null;
        }
        return uri.getPath();
    }

    /**
     * Returns whether the given Uri is local or remote.
     */
    public static boolean isLocalResourceUri(final Uri uri) {
        final String scheme = uri.getScheme();
        return TextUtils.equals(scheme, ContentResolver.SCHEME_ANDROID_RESOURCE) ||
                TextUtils.equals(scheme, ContentResolver.SCHEME_CONTENT) ||
                TextUtils.equals(scheme, ContentResolver.SCHEME_FILE);
    }

    /**
     * Returns whether the given Uri is part of Bugle's app package
     */
    public static boolean isBugleAppResource(final Uri uri) {
        final String scheme = uri.getScheme();
        return TextUtils.equals(scheme, ContentResolver.SCHEME_ANDROID_RESOURCE);
    }

    public static boolean isFileUri(final Uri uri) {
        return uri != null && TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_FILE);
    }

    /**
     * Constructs an android.resource:// uri for the given resource id.
     */
    public static Uri getUriForResourceId(final Context context, final int resId) {
        return new Uri.Builder()
                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
                .authority(context.getPackageName())
                .appendPath(String.valueOf(resId))
                .build();
    }

    /**
     * Returns whether the given Uri string is local.
     */
    public static boolean isLocalUri(@NonNull final Uri uri) {
        Assert.notNull(uri);
        return SUPPORTED_SCHEME.contains(uri.getScheme());
    }

    private static final String MEDIA_STORE_URI_KLP = "com.android.providers.media.documents";

    /**
     * Check if a URI is from the MediaStore
     */
    public static boolean isMediaStoreUri(final Uri uri) {
        final String uriAuthority = uri.getAuthority();
        return TextUtils.equals(ContentResolver.SCHEME_CONTENT, uri.getScheme())
                && (TextUtils.equals(MediaStore.AUTHORITY, uriAuthority) ||
                // KK changed the media store authority name
                TextUtils.equals(MEDIA_STORE_URI_KLP, uriAuthority));
    }

    /**
     * Gets the size in bytes for the content uri. Currently we only support content in the
     * scratch space.
     */
    @DoesNotRunOnMainThread
    public static long getContentSize(final Uri uri) {
        Assert.isNotMainThread();
        if (isLocalResourceUri(uri)) {
            ParcelFileDescriptor pfd = null;
            try {
                pfd = Factory.get().getApplicationContext()
                        .getContentResolver().openFileDescriptor(uri, "r");
                return Math.max(pfd.getStatSize(), 0);
            } catch (final FileNotFoundException e) {
                LogUtil.e(LogUtil.BUGLE_TAG, "Error getting content size", e);
            } finally {
                if (pfd != null) {
                    try {
                        pfd.close();
                    } catch (final IOException e) {
                        // Do nothing.
                    }
                }
            }
        } else {
            Assert.fail("Unsupported uri type!");
        }
        return 0;
    }

    /** @return duration in milliseconds or 0 if not able to determine */
    public static int getMediaDurationMs(final Uri uri) {
        final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
        try {
            retriever.setDataSource(uri);
            return retriever.extractInteger(MediaMetadataRetriever.METADATA_KEY_DURATION, 0);
        } catch (final IOException e) {
            LogUtil.e(LogUtil.BUGLE_TAG, "Unable extract duration from media file: " + uri, e);
            return 0;
        } finally {
            retriever.release();
        }
    }

    /**
     * Persist a piece of content from the given input stream, byte by byte to the scratch
     * directory.
     * @return the output Uri if the operation succeeded, or null if failed.
     */
    @DoesNotRunOnMainThread
    public static Uri persistContentToScratchSpace(final InputStream inputStream) {
        final Context context = Factory.get().getApplicationContext();
        final Uri scratchSpaceUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(null);
        return copyContent(context, inputStream, scratchSpaceUri);
    }

    /**
     * Persist a piece of content from the given sourceUri, byte by byte to the scratch
     * directory.
     * @return the output Uri if the operation succeeded, or null if failed.
     */
    @DoesNotRunOnMainThread
    public static Uri persistContentToScratchSpace(final Uri sourceUri) {
        InputStream inputStream = null;
        final Context context = Factory.get().getApplicationContext();
        try {
            if (UriUtil.isLocalResourceUri(sourceUri)) {
                inputStream = context.getContentResolver().openInputStream(sourceUri);
            } else {
                // The content is remote. Download it.
                final URL url = new URL(sourceUri.toString());
                final URLConnection ucon = url.openConnection();
                inputStream = new BufferedInputStream(ucon.getInputStream());
            }
            return persistContentToScratchSpace(inputStream);
        } catch (final Exception ex) {
            LogUtil.e(LogUtil.BUGLE_TAG, "Error while retrieving media ", ex);
            return null;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (final IOException e) {
                    LogUtil.e(LogUtil.BUGLE_TAG, "error trying to close the inputStream", e);
                }
            }
        }
    }

    /**
     * Persist a piece of content from the given input stream, byte by byte to the specified
     * directory.
     * @return the output Uri if the operation succeeded, or null if failed.
     */
    @DoesNotRunOnMainThread
    public static Uri persistContent(
            final InputStream inputStream, final File outputDir, final String contentType) {
        if (!outputDir.exists() && !outputDir.mkdirs()) {
            LogUtil.e(LogUtil.BUGLE_TAG, "Error creating " + outputDir.getAbsolutePath());
            return null;
        }

        final Context context = Factory.get().getApplicationContext();
        try {
            final Uri targetUri = Uri.fromFile(FileUtil.getNewFile(outputDir, contentType));
            return copyContent(context, inputStream, targetUri);
        } catch (final IOException e) {
            LogUtil.e(LogUtil.BUGLE_TAG, "Error creating file in " + outputDir.getAbsolutePath());
            return null;
        }
    }

    /**
     * Persist a piece of content from the given sourceUri, byte by byte to the
     * specified output directory.
     * @return the output Uri if the operation succeeded, or null if failed.
     */
    @DoesNotRunOnMainThread
    public static Uri persistContent(
            final Uri sourceUri, final File outputDir, final String contentType) {
        InputStream inputStream = null;
        final Context context = Factory.get().getApplicationContext();
        try {
            if (UriUtil.isLocalResourceUri(sourceUri)) {
                inputStream = context.getContentResolver().openInputStream(sourceUri);
            } else {
                // The content is remote. Download it.
                final URL url = new URL(sourceUri.toString());
                final URLConnection ucon = url.openConnection();
                inputStream = new BufferedInputStream(ucon.getInputStream());
            }
            return persistContent(inputStream, outputDir, contentType);
        } catch (final Exception ex) {
            LogUtil.e(LogUtil.BUGLE_TAG, "Error while retrieving media ", ex);
            return null;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (final IOException e) {
                    LogUtil.e(LogUtil.BUGLE_TAG, "error trying to close the inputStream", e);
                }
            }
        }
    }

    /** @return uri of target file, or null on error */
    @DoesNotRunOnMainThread
    private static Uri copyContent(
            final Context context, final InputStream inputStream, final Uri targetUri) {
        Assert.isNotMainThread();
        OutputStream outputStream = null;
        try {
            outputStream = context.getContentResolver().openOutputStream(targetUri);
            ByteStreams.copy(inputStream, outputStream);
        } catch (final Exception ex) {
            LogUtil.e(LogUtil.BUGLE_TAG, "Error while copying content ", ex);
            return null;
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.flush();
                } catch (final IOException e) {
                    LogUtil.e(LogUtil.BUGLE_TAG, "error trying to flush the outputStream", e);
                    return null;
                } finally {
                    try {
                        outputStream.close();
                    } catch (final IOException e) {
                        // Do nothing.
                    }
                }
            }
        }
        return targetUri;
    }

    public static boolean isSmsMmsUri(final Uri uri) {
        return uri != null && SMS_MMS_SCHEMES.contains(uri.getScheme());
    }

    /**
     * Extract recipient destinations from Uri of form
     *     SCHEME:destionation[,destination]?otherstuff
     * where SCHEME is one of the supported sms/mms schemes.
     *
     * @param uri sms/mms uri
     * @return recipient destinations or null
     */
    public static String[] parseRecipientsFromSmsMmsUri(final Uri uri) {
        if (!isSmsMmsUri(uri)) {
            return null;
        }
        final String[] parts = uri.getSchemeSpecificPart().split("\\?");
        if (TextUtils.isEmpty(parts[0])) {
            return null;
        }
        // replaceUnicodeDigits will replace digits typed in other languages (i.e. Egyptian) with
        // the usual ascii equivalents.
        return TextUtil.replaceUnicodeDigits(parts[0]).replace(';', ',').split(",");
    }

    /**
     * Return the length of the file to which contentUri refers
     *
     * @param contentUri URI for the file of which we want the length
     * @return Length of the file or AssetFileDescriptor.UNKNOWN_LENGTH
     */
    public static long getUriContentLength(final Uri contentUri) {
        final Context context = Factory.get().getApplicationContext();
        AssetFileDescriptor afd = null;
        try {
            afd = context.getContentResolver().openAssetFileDescriptor(contentUri, "r");
            return afd.getLength();
        } catch (final FileNotFoundException e) {
            LogUtil.w(LogUtil.BUGLE_TAG, "Failed to query length of " + contentUri);
        } finally {
            if (afd != null) {
                try {
                    afd.close();
                } catch (final IOException e) {
                    LogUtil.w(LogUtil.BUGLE_TAG, "Failed to close afd for " + contentUri);
                }
            }
        }
        return AssetFileDescriptor.UNKNOWN_LENGTH;
    }

    /** @return string representation of URI or null if URI was null */
    public static String stringFromUri(final Uri uri) {
        return uri == null ? null : uri.toString();
    }

    /** @return URI created from string or null if string was null or empty */
    public static Uri uriFromString(final String uriString) {
        return TextUtils.isEmpty(uriString) ? null : Uri.parse(uriString);
     }
}