Messaging: Use ActivityResultContracts for media and contacts

Instead of handling showing and picking the media ourselves,
use existing AcitivityResultContracts to pick images, videos
or contacts

Change-Id: I20079ad94882cc6bbfeae8d57ca0e4aa30612c01
This commit is contained in:
Michael W
2024-12-26 15:54:37 +01:00
parent 7f9087a49e
commit e335f6bec8
24 changed files with 148 additions and 1481 deletions
@@ -36,7 +36,6 @@ import com.android.messaging.datamodel.data.ConversationData.ConversationDataLis
import com.android.messaging.datamodel.data.ConversationListData;
import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
import com.android.messaging.datamodel.data.DraftMessageData;
import com.android.messaging.datamodel.data.GalleryGridItemData;
import com.android.messaging.datamodel.data.LaunchConversationData;
import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener;
import com.android.messaging.datamodel.data.MediaPickerData;
@@ -81,8 +80,6 @@ public abstract class DataModel {
public abstract MediaPickerData createMediaPickerData(final Context context);
public abstract GalleryGridItemData createGalleryGridItemData();
public abstract LaunchConversationData createLaunchConversationData(
LaunchConversationDataListener listener);
@@ -36,7 +36,6 @@ import com.android.messaging.datamodel.data.ConversationData.ConversationDataLis
import com.android.messaging.datamodel.data.ConversationListData;
import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
import com.android.messaging.datamodel.data.DraftMessageData;
import com.android.messaging.datamodel.data.GalleryGridItemData;
import com.android.messaging.datamodel.data.LaunchConversationData;
import com.android.messaging.datamodel.data.LaunchConversationData.LaunchConversationDataListener;
import com.android.messaging.datamodel.data.MediaPickerData;
@@ -113,11 +112,6 @@ public class DataModelImpl extends DataModel {
return new MediaPickerData(context);
}
@Override
public GalleryGridItemData createGalleryGridItemData() {
return new GalleryGridItemData();
}
@Override
public LaunchConversationData createLaunchConversationData(
final LaunchConversationDataListener listener) {
@@ -1,53 +0,0 @@
/*
* 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.datamodel;
import android.content.Context;
import android.net.Uri;
import android.provider.MediaStore.Files;
import android.provider.MediaStore.Files.FileColumns;
import android.provider.MediaStore.MediaColumns;
import com.android.messaging.datamodel.data.GalleryGridItemData;
import com.android.messaging.datamodel.data.MessagePartData;
import com.google.common.base.Joiner;
/**
* A BoundCursorLoader that reads local media on the device.
*/
public class GalleryBoundCursorLoader extends BoundCursorLoader {
public static final String MEDIA_SCANNER_VOLUME_EXTERNAL = "external";
private static final Uri STORAGE_URI = Files.getContentUri(MEDIA_SCANNER_VOLUME_EXTERNAL);
private static final String SORT_ORDER = MediaColumns.DATE_MODIFIED + " DESC";
private static final String SELECTION = createSelection(
MessagePartData.ACCEPTABLE_GALLERY_MEDIA_TYPES,
new Integer[] {
FileColumns.MEDIA_TYPE_IMAGE,
FileColumns.MEDIA_TYPE_VIDEO,
FileColumns.MEDIA_TYPE_AUDIO
});
public GalleryBoundCursorLoader(final String bindingId, final Context context) {
super(bindingId, context, STORAGE_URI, GalleryGridItemData.MEDIA_PROJECTION, SELECTION,
null, SORT_ORDER);
}
private static String createSelection(final String[] mimeTypes, Integer[] mediaTypes) {
return MediaColumns.MIME_TYPE + " IN ('" + Joiner.on("','").join(mimeTypes) + "') AND "
+ FileColumns.MEDIA_TYPE + " IN (" + Joiner.on(',').join(mediaTypes) + ")";
}
}
@@ -1,157 +0,0 @@
/*
* 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.datamodel.data;
import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore.MediaColumns;
import android.text.TextUtils;
import com.android.messaging.datamodel.media.FileImageRequestDescriptor;
import com.android.messaging.datamodel.media.ImageRequest;
import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
import com.android.messaging.datamodel.media.VideoThumbnailRequestDescriptor;
import com.android.messaging.util.Assert;
import com.android.messaging.util.ContentType;
import com.android.messaging.util.UriUtil;
/**
* Provides data for GalleryGridItemView
*/
public class GalleryGridItemData {
public static final String[] MEDIA_PROJECTION = new String[] {
MediaColumns._ID,
MediaColumns.DATA,
MediaColumns.WIDTH,
MediaColumns.HEIGHT,
MediaColumns.MIME_TYPE,
MediaColumns.DATE_MODIFIED,
MediaColumns.DISPLAY_NAME};
public static final String[] SPECIAL_ITEM_COLUMNS = new String[] {
BaseColumns._ID
};
private static final int INDEX_ID = 0;
// For local media gallery.
private static final int INDEX_DATA_PATH = 1;
private static final int INDEX_WIDTH = 2;
private static final int INDEX_HEIGHT = 3;
private static final int INDEX_MIME_TYPE = 4;
private static final int INDEX_DATE_MODIFIED = 5;
private static final int INDEX_DISPLAY_NAME = 6;
/** A special item's id for picking a media from document picker */
public static final String ID_DOCUMENT_PICKER_ITEM = "-1";
private UriImageRequestDescriptor mImageData;
private String mContentType;
private boolean mIsDocumentPickerItem;
private long mDateSeconds;
private String mFileName;
private Uri mAudioUri;
public GalleryGridItemData() {
}
public void bind(final Cursor cursor, final int desiredWidth, final int desiredHeight) {
mIsDocumentPickerItem = TextUtils.equals(cursor.getString(INDEX_ID),
ID_DOCUMENT_PICKER_ITEM);
if (mIsDocumentPickerItem) {
mImageData = null;
mContentType = null;
} else {
mContentType = cursor.getString(INDEX_MIME_TYPE);
final String filePath = cursor.getString(INDEX_DATA_PATH);
final String dateModified = cursor.getString(INDEX_DATE_MODIFIED);
mDateSeconds = !TextUtils.isEmpty(dateModified) ? Long.parseLong(dateModified) : -1;
if (ContentType.isAudioType(mContentType)) {
mImageData = null;
mAudioUri = UriUtil.getUriForResourceFile(filePath);
mFileName = cursor.getString(INDEX_DISPLAY_NAME);
} else { // For image and video types
int sourceWidth = cursor.getInt(INDEX_WIDTH);
int sourceHeight = cursor.getInt(INDEX_HEIGHT);
// Guard against bad data
if (sourceWidth <= 0) {
sourceWidth = ImageRequest.UNSPECIFIED_SIZE;
}
if (sourceHeight <= 0) {
sourceHeight = ImageRequest.UNSPECIFIED_SIZE;
}
if (ContentType.isVideoType(mContentType)) {
mImageData = new VideoThumbnailRequestDescriptor(
cursor.getLong(INDEX_ID),
desiredWidth,
desiredHeight,
sourceWidth,
sourceHeight);
} else {
mImageData = new FileImageRequestDescriptor(
filePath,
desiredWidth,
desiredHeight,
sourceWidth,
sourceHeight,
true /* canUseThumbnail */,
true /* allowCompression */,
true /* isStatic */);
}
}
}
}
public boolean isDocumentPickerItem() {
return mIsDocumentPickerItem;
}
public Uri getImageUri() {
return ContentType.isAudioType(mContentType) ? mAudioUri : mImageData.uri;
}
public UriImageRequestDescriptor getImageRequestDescriptor() {
return mImageData;
}
public MessagePartData constructMessagePartData(final Rect startRect) {
Assert.isTrue(!mIsDocumentPickerItem);
return ContentType.isAudioType(mContentType)
? new MediaPickerMessagePartData(startRect, mContentType, mAudioUri, 0, 0)
: new MediaPickerMessagePartData(startRect, mContentType, mImageData.uri,
mImageData.sourceWidth, mImageData.sourceHeight);
}
/**
* @return The date in seconds. This can be negative if we could not retreive date info
*/
public long getDateSeconds() {
return mDateSeconds;
}
public String getContentType() {
return mContentType;
}
public String getFileName() {
return mFileName;
}
}
@@ -18,144 +18,19 @@
package com.android.messaging.datamodel.data;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import com.android.messaging.datamodel.BoundCursorLoader;
import com.android.messaging.datamodel.GalleryBoundCursorLoader;
import com.android.messaging.datamodel.binding.BindableData;
import com.android.messaging.datamodel.binding.BindingBase;
import com.android.messaging.util.Assert;
import com.android.messaging.util.BuglePrefs;
import com.android.messaging.util.BuglePrefsKeys;
import com.android.messaging.util.LogUtil;
/**
* Services data needs for MediaPicker.
*/
public class MediaPickerData extends BindableData {
public interface MediaPickerDataListener {
void onMediaPickerDataUpdated(MediaPickerData mediaPickerData, Object data, int loaderId);
}
private static final String BINDING_ID = "bindingId";
private final Context mContext;
private LoaderManager mLoaderManager;
private final GalleryLoaderCallbacks mGalleryLoaderCallbacks;
private MediaPickerDataListener mListener;
public MediaPickerData(final Context context) {
mContext = context;
mGalleryLoaderCallbacks = new GalleryLoaderCallbacks();
}
public static final int GALLERY_MEDIA_LOADER = 1;
/**
* A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
*/
private class GalleryLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
@NonNull
@Override
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
final String bindingId = args.getString(BINDING_ID);
// Check if data still bound to the requesting ui element
if (isBound(bindingId)) {
switch (id) {
case GALLERY_MEDIA_LOADER:
return new GalleryBoundCursorLoader(bindingId, mContext);
default:
Assert.fail("Unknown loader id for gallery picker!");
break;
}
} else {
LogUtil.w(LogUtil.BUGLE_TAG, "Loader created after unbinding the media picker");
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void onLoadFinished(@NonNull final Loader<Cursor> loader, final Cursor data) {
final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
if (isBound(cursorLoader.getBindingId())) {
switch (loader.getId()) {
case GALLERY_MEDIA_LOADER:
mListener.onMediaPickerDataUpdated(MediaPickerData.this, data,
GALLERY_MEDIA_LOADER);
break;
default:
Assert.fail("Unknown loader id for gallery picker!");
break;
}
} else {
LogUtil.w(LogUtil.BUGLE_TAG, "Loader finished after unbinding the media picker");
}
}
/**
* {@inheritDoc}
*/
@Override
public void onLoaderReset(@NonNull final Loader<Cursor> loader) {
final BoundCursorLoader cursorLoader = (BoundCursorLoader) loader;
if (isBound(cursorLoader.getBindingId())) {
switch (loader.getId()) {
case GALLERY_MEDIA_LOADER:
mListener.onMediaPickerDataUpdated(MediaPickerData.this, null,
GALLERY_MEDIA_LOADER);
break;
default:
Assert.fail("Unknown loader id for media picker!");
break;
}
} else {
LogUtil.w(LogUtil.BUGLE_TAG, "Loader reset after unbinding the media picker");
}
}
}
public void startLoader(final int loaderId, final BindingBase<MediaPickerData> binding,
@Nullable Bundle args, final MediaPickerDataListener listener) {
if (args == null) {
args = new Bundle();
}
args.putString(BINDING_ID, binding.getBindingId());
if (loaderId == GALLERY_MEDIA_LOADER) {
mLoaderManager.initLoader(loaderId, args, mGalleryLoaderCallbacks).forceLoad();
} else {
Assert.fail("Unsupported loader id for media picker!");
}
mListener = listener;
}
public void destroyLoader(final int loaderId) {
mLoaderManager.destroyLoader(loaderId);
}
public void init(final LoaderManager loaderManager) {
mLoaderManager = loaderManager;
}
@Override
protected void unregisterListeners() {
// This could be null if we bind but the caller doesn't init the BindableData
if (mLoaderManager != null) {
mLoaderManager.destroyLoader(GALLERY_MEDIA_LOADER);
mLoaderManager = null;
}
}
/**
@@ -176,4 +51,8 @@ public class MediaPickerData extends BindableData {
selectedIndex);
}
@Override
protected void unregisterListeners() {
}
}
@@ -1,37 +0,0 @@
/*
* 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.datamodel.media;
import android.content.Context;
import com.android.messaging.util.ImageUtils;
import com.android.messaging.util.UriUtil;
public class VideoThumbnailRequestDescriptor extends UriImageRequestDescriptor {
public VideoThumbnailRequestDescriptor(final long id, int desiredWidth, int desiredHeight,
int sourceWidth, int sourceHeight) {
super(UriUtil.getContentUriForMediaStoreId(id), desiredWidth, desiredHeight, sourceWidth,
sourceHeight, false /* canCompress */, false /* isStatic */,
false /* cropToCircle */,
ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
}
@Override
public MediaRequest<ImageResource> buildSyncMediaRequest(Context context) {
return new VideoThumbnailRequest(context, this);
}
}