blob: f92155f8a5179dfe88610a95a260ac330b7a240a [file] [log] [blame]
/*
* 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 androidx.annotation.NonNull;
import android.text.TextUtils;
import com.android.messaging.Factory;
import com.android.messaging.datamodel.GalleryBoundCursorLoader;
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));
private static final String SCHEME_HTTP = "http";
private static final String SCHEME_HTTPS = "https";
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);
}
/** Returns whether the given Uri is a file. */
public static boolean isFileUri(final Uri uri) {
return uri != null &&
uri.getScheme() != null &&
uri.getScheme().trim().toLowerCase().contains(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 content:// style URI for the given MediaStore row Id in the files table on the
* external volume.
*
* @param id the MediaStore row Id to get the URI for
* @return the URI to the files table on the external storage.
*/
public static Uri getContentUriForMediaStoreId(final long id) {
return MediaStore.Files.getContentUri(
GalleryBoundCursorLoader.MEDIA_SCANNER_VOLUME_EXTERNAL, id);
}
/**
* 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.
inputStream = getInputStreamFromRemoteUri(sourceUri);
if (inputStream == null) {
return null;
}
}
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);
}
}
}
}
@DoesNotRunOnMainThread
private static InputStream getInputStreamFromRemoteUri(final Uri sourceUri)
throws IOException {
if (isRemoteUri(sourceUri)) {
final URL url = new URL(sourceUri.toString());
final URLConnection ucon = url.openConnection();
return new BufferedInputStream(ucon.getInputStream());
} else {
return null;
}
}
private static boolean isRemoteUri(final Uri sourceUri) {
return sourceUri.getScheme().equals(SCHEME_HTTP)
|| sourceUri.getScheme().equals(SCHEME_HTTPS);
}
/**
* 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.
inputStream = getInputStreamFromRemoteUri(sourceUri);
if (inputStream == null) {
return null;
}
}
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:destination[,destination]?otherstuff
* where SCHEME is one of the supported sms/mms schemes.
*
* @param uri sms/mms uri
* @return a comma-separated list of 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(';', ',');
}
/**
* 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);
}
}