Initial checkin of AOSP Messaging app.
b/23110861 Change-Id: I11db999bd10656801e618f78ab2b2ef74136fff1
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* 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 com.google.common.io.Files;
|
||||
import com.google.common.io.Resources;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download data from the given url to the given local file.
|
||||
* @return true if the download was successful.
|
||||
*
|
||||
* TODO: Add retry/exponential backoff logic.
|
||||
*/
|
||||
@DoesNotRunOnMainThread
|
||||
public static boolean downloadDataFromUrl(final String urlString, final File localFile) {
|
||||
Assert.isNotMainThread();
|
||||
LogUtil.i(LogUtil.BUGLE_TAG, "Downloading from " + urlString + " to " + localFile);
|
||||
try {
|
||||
Files.createParentDirs(localFile);
|
||||
final URL inUrl = new URL(urlString);
|
||||
ByteStreams.copy(Resources.newInputStreamSupplier(inUrl),
|
||||
Files.newOutputStreamSupplier(localFile));
|
||||
return true;
|
||||
} catch (final IOException ex) {
|
||||
LogUtil.e(LogUtil.BUGLE_TAG, "Error downloading from " + urlString, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user