UriUtil#parseRecipientsFromSmsMmsUri() will returns a comma-separated recipients for common use so that InsertNewMessageAction#createMessage() can create or get a conversation correctly for RESPOND_VIA_MESSAGE. In addition, mms/mmsto schemes are added. And it includes small cleanup intent-filters for LaunchConversationActivity. Test: adb shell am startservice -a android.intent.action.RESPOND_VIA_MESSAGE -d "sms:12345678,87654321" -e android.intent.extra.TEXT "Text" Change-Id: I291dc765a46846982d059016d44f90873c687867 Signed-off-by: Taesu Lee <taesu82.lee@samsung.com>
382 lines
14 KiB
Java
382 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: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);
|
|
}
|
|
}
|