Files
packages_apps_Messaging/src/com/android/messaging/util/UriUtil.java
Taesu Lee 6c77158718 Support video attachments through GalleryMediaChooser
GalleryMediaChooser supports video types and more image types also. And
video thumbnails are overlaid with the play button image to distinguish
between images and videos.

Note:
 1. EXTRA_ALLOW_MULTIPLE is not specified for ACTION_GET_CONTENT.
 2. Files, methods and variables' name are still including "image".
 3. Content descriptions are not updated.

Test: Manual

Change-Id: I961928f150e4ae8ee80a1fba2f20c37fb5426669
Signed-off-by: Taesu Lee <taesu82.lee@samsung.com>
2020-02-19 18:36:32 +09:00

383 lines
14 KiB
Java

/*
* 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));
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 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.
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);
}
}