From e335f6bec8630cd1753c4a00e716105cf34c8c29 Mon Sep 17 00:00:00 2001 From: Michael W Date: Wed, 18 Dec 2024 14:58:51 +0100 Subject: [PATCH] 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 --- Android.bp | 1 + res/layout/gallery_grid_item_view.xml | 103 ------ res/layout/mediapicker_gallery_chooser.xml | 59 ++-- res/menu/gallery_picker_menu.xml | 35 -- .../messaging/datamodel/DataModel.java | 3 - .../messaging/datamodel/DataModelImpl.java | 6 - .../datamodel/GalleryBoundCursorLoader.java | 53 --- .../datamodel/data/GalleryGridItemData.java | 157 --------- .../datamodel/data/MediaPickerData.java | 129 +------ .../VideoThumbnailRequestDescriptor.java | 37 -- src/com/android/messaging/ui/UIIntents.java | 20 -- .../android/messaging/ui/UIIntentsImpl.java | 33 -- .../ui/conversation/ConversationActivity.java | 20 +- .../ui/conversation/ConversationFragment.java | 37 +- .../AbstractConversationListActivity.java | 5 - .../ui/mediapicker/ContactMediaChooser.java | 80 +++-- .../ui/mediapicker/DocumentImagePicker.java | 49 ++- .../ui/mediapicker/GalleryGridAdapter.java | 62 ---- .../ui/mediapicker/GalleryGridItemView.java | 206 ------------ .../ui/mediapicker/GalleryGridView.java | 317 ------------------ .../ui/mediapicker/GalleryMediaChooser.java | 188 +---------- .../ui/mediapicker/MediaChooser.java | 4 - .../messaging/ui/mediapicker/MediaPicker.java | 12 +- src/com/android/messaging/util/UriUtil.java | 13 - 24 files changed, 148 insertions(+), 1481 deletions(-) delete mode 100644 res/layout/gallery_grid_item_view.xml delete mode 100644 res/menu/gallery_picker_menu.xml delete mode 100644 src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java delete mode 100644 src/com/android/messaging/datamodel/data/GalleryGridItemData.java delete mode 100644 src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java delete mode 100644 src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java delete mode 100644 src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java delete mode 100644 src/com/android/messaging/ui/mediapicker/GalleryGridView.java diff --git a/Android.bp b/Android.bp index 505b1f4..d2612e0 100644 --- a/Android.bp +++ b/Android.bp @@ -59,6 +59,7 @@ android_app { defaults: ["messaging_defaults"], static_libs: [ + "androidx.activity_activity", "androidx.annotation_annotation", "androidx.appcompat_appcompat", "androidx.collection_collection", diff --git a/res/layout/gallery_grid_item_view.xml b/res/layout/gallery_grid_item_view.xml deleted file mode 100644 index f8f3920..0000000 --- a/res/layout/gallery_grid_item_view.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/layout/mediapicker_gallery_chooser.xml b/res/layout/mediapicker_gallery_chooser.xml index b4f3c01..6ff8cdf 100644 --- a/res/layout/mediapicker_gallery_chooser.xml +++ b/res/layout/mediapicker_gallery_chooser.xml @@ -1,6 +1,7 @@ + + - - - - \ No newline at end of file + android:layout_height="match_parent" > + + + + + + diff --git a/res/menu/gallery_picker_menu.xml b/res/menu/gallery_picker_menu.xml deleted file mode 100644 index 08134b9..0000000 --- a/res/menu/gallery_picker_menu.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - diff --git a/src/com/android/messaging/datamodel/DataModel.java b/src/com/android/messaging/datamodel/DataModel.java index 98055ad..7d442d7 100644 --- a/src/com/android/messaging/datamodel/DataModel.java +++ b/src/com/android/messaging/datamodel/DataModel.java @@ -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); diff --git a/src/com/android/messaging/datamodel/DataModelImpl.java b/src/com/android/messaging/datamodel/DataModelImpl.java index 471c868..e85ee77 100644 --- a/src/com/android/messaging/datamodel/DataModelImpl.java +++ b/src/com/android/messaging/datamodel/DataModelImpl.java @@ -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) { diff --git a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java b/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java deleted file mode 100644 index 1974a5e..0000000 --- a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java +++ /dev/null @@ -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) + ")"; - } -} diff --git a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java b/src/com/android/messaging/datamodel/data/GalleryGridItemData.java deleted file mode 100644 index 941d38d..0000000 --- a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java +++ /dev/null @@ -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; - } -} diff --git a/src/com/android/messaging/datamodel/data/MediaPickerData.java b/src/com/android/messaging/datamodel/data/MediaPickerData.java index ef94751..e06603e 100644 --- a/src/com/android/messaging/datamodel/data/MediaPickerData.java +++ b/src/com/android/messaging/datamodel/data/MediaPickerData.java @@ -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 { - @NonNull - @Override - public Loader 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 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 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 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() { + + } } diff --git a/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java b/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java deleted file mode 100644 index 22f1871..0000000 --- a/src/com/android/messaging/datamodel/media/VideoThumbnailRequestDescriptor.java +++ /dev/null @@ -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 buildSyncMediaRequest(Context context) { - return new VideoThumbnailRequest(context, this); - } -} diff --git a/src/com/android/messaging/ui/UIIntents.java b/src/com/android/messaging/ui/UIIntents.java index ee55ac2..b8a08fd 100644 --- a/src/com/android/messaging/ui/UIIntents.java +++ b/src/com/android/messaging/ui/UIIntents.java @@ -165,20 +165,6 @@ public abstract class UIIntents { */ public abstract void launchAddContactActivity(final Context context, final String destination); - /** - * Launch an activity to show the document picker to pick an image/video/audio. - * - * @param fragment the requesting fragment - */ - public abstract void launchDocumentImagePicker(final Fragment fragment); - - /** - * Launch an activity to show the contacts list to pick one. - * - * @param fragment the requesting fragment - */ - public abstract void launchContactCardPicker(final Fragment fragment); - /** * Launch an activity to show people & options for a given conversation. */ @@ -223,12 +209,6 @@ public abstract class UIIntents { */ public abstract void launchSaveVCardToContactsActivity(Context context, Uri vcardUri); - /** - * Launch an activity to let the user select & unselect the list of attachments to send. - */ - public abstract void launchAttachmentChooserActivity(final Activity activity, - final String conversationId, final int requestCode); - /** * Launch full screen video viewer. */ diff --git a/src/com/android/messaging/ui/UIIntentsImpl.java b/src/com/android/messaging/ui/UIIntentsImpl.java index d3b55c5..a7242d4 100644 --- a/src/com/android/messaging/ui/UIIntentsImpl.java +++ b/src/com/android/messaging/ui/UIIntentsImpl.java @@ -35,7 +35,6 @@ import android.provider.MediaStore; import androidx.annotation.Nullable; import androidx.core.app.TaskStackBuilder; -import androidx.fragment.app.Fragment; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.text.TextUtils; @@ -52,7 +51,6 @@ import com.android.messaging.sms.MmsSmsUtils; import com.android.messaging.ui.appsettings.ApplicationSettingsActivity; import com.android.messaging.ui.appsettings.PerSubscriptionSettingsActivity; import com.android.messaging.ui.appsettings.SettingsActivity; -import com.android.messaging.ui.attachmentchooser.AttachmentChooserActivity; import com.android.messaging.ui.conversation.ConversationActivity; import com.android.messaging.ui.conversation.LaunchConversationActivity; import com.android.messaging.ui.conversationlist.ArchivedConversationListActivity; @@ -216,29 +214,6 @@ public class UIIntentsImpl extends UIIntents { context.startActivity(intent); } - @Override - public void launchDocumentImagePicker(final Fragment fragment) { - final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.putExtra(Intent.EXTRA_MIME_TYPES, MessagePartData.ACCEPTABLE_GALLERY_MEDIA_TYPES); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType(ContentType.ANY_TYPE); - - fragment.startActivityForResult(intent, REQUEST_PICK_MEDIA_FROM_DOCUMENT_PICKER); - } - - @Override - public void launchContactCardPicker(final Fragment fragment) { - final Intent intent = new Intent(Intent.ACTION_PICK); - intent.setType(Contacts.CONTENT_TYPE); - - try { - fragment.startActivityForResult(intent, REQUEST_PICK_CONTACT_CARD); - } catch (final ActivityNotFoundException ex) { - LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't find activity:", ex); - UiUtils.showToastAtBottom(R.string.activity_not_found_message); - } - } - @Override public void launchPeopleAndOptionsActivity(final Activity activity, final String conversationId) { @@ -290,14 +265,6 @@ public class UIIntentsImpl extends UIIntents { startExternalActivity(context, intent); } - @Override - public void launchAttachmentChooserActivity(final Activity activity, - final String conversationId, final int requestCode) { - final Intent intent = new Intent(activity, AttachmentChooserActivity.class); - intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId); - activity.startActivityForResult(intent, requestCode); - } - @Override public void launchFullScreenVideoViewer(final Context context, final Uri videoUri) { final Intent intent = new Intent(Intent.ACTION_VIEW); diff --git a/src/com/android/messaging/ui/conversation/ConversationActivity.java b/src/com/android/messaging/ui/conversation/ConversationActivity.java index af99889..bdf17de 100644 --- a/src/com/android/messaging/ui/conversation/ConversationActivity.java +++ b/src/com/android/messaging/ui/conversation/ConversationActivity.java @@ -240,7 +240,7 @@ public class ConversationActivity extends BugleActionBarActivity ContactPickerFragment.FRAGMENT_TAG); } - private ConversationFragment getConversationFragment() { + public ConversationFragment getConversationFragment() { return (ConversationFragment) getSupportFragmentManager().findFragmentByTag( ConversationFragment.FRAGMENT_TAG); } @@ -366,22 +366,4 @@ public class ConversationActivity extends BugleActionBarActivity public boolean shouldResumeComposeMessage() { return mUiState.shouldResumeComposeMessage(); } - - @SuppressWarnings("MissingSuperCall") // TODO: fix me - @Override - protected void onActivityResult(final int requestCode, final int resultCode, - final Intent data) { - if (requestCode == ConversationFragment.REQUEST_CHOOSE_ATTACHMENTS && - resultCode == RESULT_OK) { - final ConversationFragment conversationFragment = getConversationFragment(); - if (conversationFragment != null) { - conversationFragment.onAttachmentChoosen(); - } else { - LogUtil.e(LogUtil.BUGLE_TAG, "ConversationFragment is missing after launching " + - "AttachmentChooserActivity!"); - } - } else if (resultCode == FINISH_RESULT_CODE) { - finish(); - } - } } diff --git a/src/com/android/messaging/ui/conversation/ConversationFragment.java b/src/com/android/messaging/ui/conversation/ConversationFragment.java index 128ed7f..e4cef8b 100644 --- a/src/com/android/messaging/ui/conversation/ConversationFragment.java +++ b/src/com/android/messaging/ui/conversation/ConversationFragment.java @@ -49,6 +49,10 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.TextView; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; @@ -87,6 +91,7 @@ import com.android.messaging.ui.ConversationDrawables; import com.android.messaging.ui.SnackBar; import com.android.messaging.ui.UIIntents; import com.android.messaging.ui.animation.PopupTransitionAnimation; +import com.android.messaging.ui.attachmentchooser.AttachmentChooserActivity; import com.android.messaging.ui.contact.AddContactsConfirmationDialog; import com.android.messaging.ui.conversation.ComposeMessageView.IComposeMessageViewHost; import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputHost; @@ -399,6 +404,25 @@ public class ConversationFragment extends Fragment implements ConversationDataLi } }; + private final ActivityResultLauncher mLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + final ConversationFragment conversationFragment = getConversationFragment(); + if (conversationFragment != null) { + conversationFragment.onAttachmentChoosen(); + } else { + LogUtil.e(LogUtil.BUGLE_TAG, + "ConversationFragment is missing after launching " + + "AttachmentChooserActivity!"); + } + } + }); + + public ConversationFragment getConversationFragment() { + return (ConversationFragment) getParentFragmentManager().findFragmentByTag( + ConversationFragment.FRAGMENT_TAG); + } + /** * {@inheritDoc} from Fragment */ @@ -429,8 +453,8 @@ public class ConversationFragment extends Fragment implements ConversationDataLi * loading when onActivityCreated() is called, which is guaranteed to happen after both. */ @Override - public void onActivityCreated(final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public void onViewCreated(@NonNull View view, final Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); // Delay showing the message list until the participant list is loaded. mRecyclerView.setVisibility(View.INVISIBLE); mBinding.ensureBound(); @@ -1448,7 +1472,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi getActivity(), tooManyVideos); } - public static void warnOfExceedingMessageLimit(final boolean sending, + public void warnOfExceedingMessageLimit(final boolean sending, final ComposeMessageView composeMessageView, final String conversationId, final Activity activity, final boolean tooManyVideos) { final AlertDialog.Builder builder = @@ -1478,10 +1502,11 @@ public class ConversationFragment extends Fragment implements ConversationDataLi showAttachmentChooser(mConversationId, getActivity()); } - public static void showAttachmentChooser(final String conversationId, + public void showAttachmentChooser(final String conversationId, final Activity activity) { - UIIntents.get().launchAttachmentChooserActivity(activity, - conversationId, REQUEST_CHOOSE_ATTACHMENTS); + final Intent intent = new Intent(activity, AttachmentChooserActivity.class); + intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId); + mLauncher.launch(intent); } private void updateActionAndStatusBarColor(final ActionBar actionBar) { diff --git a/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java index 4d30325..304879d 100644 --- a/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java +++ b/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java @@ -90,11 +90,6 @@ public abstract class AbstractConversationListActivity extends BugleActionBarAct return isInConversationListSelectMode(); } - @SuppressWarnings("MissingSuperCall") // TODO: fix me - @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - } - @Override public void onActionBarDelete(final Collection conversations) { if (!PhoneUtils.getDefault().isDefaultSmsApp()) { diff --git a/src/com/android/messaging/ui/mediapicker/ContactMediaChooser.java b/src/com/android/messaging/ui/mediapicker/ContactMediaChooser.java index 0d06443..892baf8 100644 --- a/src/com/android/messaging/ui/mediapicker/ContactMediaChooser.java +++ b/src/com/android/messaging/ui/mediapicker/ContactMediaChooser.java @@ -19,6 +19,7 @@ package com.android.messaging.ui.mediapicker; import android.Manifest; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; @@ -28,12 +29,16 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; + import com.android.messaging.R; import com.android.messaging.datamodel.data.PendingAttachmentData; -import com.android.messaging.ui.UIIntents; import com.android.messaging.util.ContactUtil; import com.android.messaging.util.ContentType; +import com.android.messaging.util.LogUtil; import com.android.messaging.util.SafeAsyncTask; +import com.android.messaging.util.UiUtils; /** * Chooser which allows the user to select an existing contact from contacts apps on this device. @@ -43,9 +48,42 @@ import com.android.messaging.util.SafeAsyncTask; class ContactMediaChooser extends MediaChooser { private View mEnabledView; private View mMissingPermissionView; + private final ActivityResultLauncher mPickerLauncher; ContactMediaChooser(final MediaPicker mediaPicker) { super(mediaPicker); + mPickerLauncher = mediaPicker.registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() != Activity.RESULT_OK || + result.getData() == null) { + return; + } + Uri contactUri = result.getData().getData(); + if (contactUri != null) { + String lookupKey = null; + try (final Cursor c = getContext().getContentResolver().query( + contactUri, + new String[]{Contacts.LOOKUP_KEY}, + null, + null, + null)) { + if (c != null) { + c.moveToFirst(); + lookupKey = c.getString(0); + } + } + final Uri vCardUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, + lookupKey); + if (vCardUri != null) { + SafeAsyncTask.executeOnThreadPool(() -> { + final PendingAttachmentData pendingItem = + PendingAttachmentData.createPendingAttachmentData( + ContentType.TEXT_X_VCARD.toLowerCase(), vCardUri); + mMediaPicker.dispatchPendingItemAdded(pendingItem); + }); + } + } + }); } @Override @@ -80,7 +118,14 @@ class ContactMediaChooser extends MediaChooser { mMissingPermissionView = view.findViewById(R.id.missing_permission_view); mEnabledView.setOnClickListener(v -> { // Launch an external picker to pick a contact as attachment. - UIIntents.get().launchContactCardPicker(mMediaPicker); + final Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI); + + try { + mPickerLauncher.launch(intent); + } catch (final ActivityNotFoundException ex) { + LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't find activity:", ex); + UiUtils.showToastAtBottom(R.string.activity_not_found_message); + } }); return view; } @@ -104,35 +149,4 @@ class ContactMediaChooser extends MediaChooser { mMissingPermissionView.setVisibility(permissionGranted ? View.GONE : View.VISIBLE); } } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == UIIntents.REQUEST_PICK_CONTACT_CARD - && resultCode == Activity.RESULT_OK) { - Uri contactUri = data.getData(); - if (contactUri != null) { - String lookupKey = null; - try (final Cursor c = getContext().getContentResolver().query( - contactUri, - new String[] {Contacts.LOOKUP_KEY}, - null, - null, - null)) { - if (c != null) { - c.moveToFirst(); - lookupKey = c.getString(0); - } - } - final Uri vCardUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey); - if (vCardUri != null) { - SafeAsyncTask.executeOnThreadPool(() -> { - final PendingAttachmentData pendingItem = - PendingAttachmentData.createPendingAttachmentData( - ContentType.TEXT_X_VCARD.toLowerCase(), vCardUri); - mMediaPicker.dispatchPendingItemAdded(pendingItem); - }); - } - } - } - } } diff --git a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java index a8083bc..ccfb090 100644 --- a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java +++ b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java @@ -20,16 +20,23 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.PickVisualMediaRequest; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.fragment.app.Fragment; import com.android.messaging.Factory; import com.android.messaging.datamodel.data.PendingAttachmentData; import com.android.messaging.ui.UIIntents; +import com.android.messaging.util.BugleGservicesKeys; import com.android.messaging.util.LogUtil; import com.android.messaging.util.FileUtil; import com.android.messaging.util.ImageUtils; import com.android.messaging.util.SafeAsyncTask; +import java.util.ArrayList; +import java.util.List; + /** * Wraps around the functionalities to allow the user to pick an image/video/audio from the document * picker. Instances of this class must be tied to a Fragment which is able to delegate activity @@ -58,6 +65,8 @@ public class DocumentImagePicker { private static final String EXTRA_PHOTO_URL = "photo_url"; + private final ActivityResultLauncher mPickMultipleMedia; + /** * Creates a new instance of DocumentImagePicker. * @param activity The activity that owns the picker, or the activity that hosts the owning @@ -67,39 +76,29 @@ public class DocumentImagePicker { final SelectionListener listener) { mFragment = fragment; mListener = listener; + + mPickMultipleMedia = mFragment.registerForActivityResult( + new ActivityResultContracts.PickMultipleVisualMedia( + BugleGservicesKeys.MMS_ATTACHMENT_LIMIT_DEFAULT), uris -> { + // Callback is invoked after the user selects media items or closes the + // photo picker. + if (!uris.isEmpty()) { + onDocumentsPicked(uris); + } + }); } /** * Intent out to open an image/video from document picker. */ public void launchPicker() { - UIIntents.get().launchDocumentImagePicker(mFragment); + mPickMultipleMedia.launch(new PickVisualMediaRequest.Builder() + .setMediaType(ActivityResultContracts.PickVisualMedia.ImageAndVideo.INSTANCE) + .build()); } - /** - * Must be called from the fragment/activity's onActivityResult(). - */ - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - // Sometimes called after media item has been picked from the document picker. - String url = data.getStringExtra(EXTRA_PHOTO_URL); - if (url == null) { - // we're using the builtin photo picker which supplies the return - // url as it's "data" - url = data.getDataString(); - if (url == null) { - final Bundle extras = data.getExtras(); - if (extras != null) { - final Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM); - if (uri != null) { - url = uri.toString(); - } - } - } - } - - // Guard against null uri cases for when the activity returns a null/invalid intent. - if (url != null) { - final Uri uri = Uri.parse(url); + public void onDocumentsPicked(List uris) { + for (Uri uri: uris) { prepareDocumentForAttachment(uri); } } diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java b/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java deleted file mode 100644 index fda3b19..0000000 --- a/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java +++ /dev/null @@ -1,62 +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.ui.mediapicker; - -import android.content.Context; -import android.database.Cursor; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; - -import com.android.messaging.R; -import com.android.messaging.ui.mediapicker.GalleryGridItemView.HostInterface; -import com.android.messaging.util.Assert; - -/** - * Bridges between the image cursor loaded by GalleryBoundCursorLoader and the GalleryGridView. - */ -public class GalleryGridAdapter extends CursorAdapter { - private GalleryGridItemView.HostInterface mGgivHostInterface; - - public GalleryGridAdapter(final Context context, final Cursor cursor) { - super(context, cursor, 0); - } - - public void setHostInterface(final HostInterface ggivHostInterface) { - mGgivHostInterface = ggivHostInterface; - } - - /** - * {@inheritDoc} - */ - @Override - public void bindView(final View view, final Context context, final Cursor cursor) { - Assert.isTrue(view instanceof GalleryGridItemView); - final GalleryGridItemView galleryImageView = (GalleryGridItemView) view; - galleryImageView.bind(cursor, mGgivHostInterface); - } - - /** - * {@inheritDoc} - */ - @Override - public View newView(final Context context, final Cursor cursor, final ViewGroup parent) { - final LayoutInflater layoutInflater = LayoutInflater.from(context); - return layoutInflater.inflate(R.layout.gallery_grid_item_view, parent, false); - } -} diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java deleted file mode 100644 index 08e8925..0000000 --- a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * Copyright (C) 2024 The LineageOS 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.ui.mediapicker; - -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.PorterDuff; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.TouchDelegate; -import android.view.View; -import android.widget.CheckBox; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; - -import com.android.messaging.R; -import com.android.messaging.datamodel.DataModel; -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.ui.AsyncImageView; -import com.android.messaging.ui.ConversationDrawables; -import com.android.messaging.util.ContentType; -import com.google.common.annotations.VisibleForTesting; - -import java.util.concurrent.TimeUnit; - -/** - * Shows an item in the gallery picker grid view. Hosts an FileImageView with a checkbox. - */ -public class GalleryGridItemView extends FrameLayout { - /** - * Implemented by the owner of this GalleryGridItemView instance to communicate on media - * picking and selection events. - */ - public interface HostInterface { - void onItemClicked(View view, GalleryGridItemData data, boolean longClick); - boolean isItemSelected(GalleryGridItemData data); - boolean isMultiSelectEnabled(); - } - - @VisibleForTesting - final - GalleryGridItemData mData; - private AsyncImageView mImageView; - private CheckBox mCheckBox; - private RelativeLayout mAdditionalInfo; - private ImageView mIcon; - private LinearLayout mFileInfo; - private TextView mFileName; - private TextView mFileType; - private HostInterface mHostInterface; - private final OnClickListener mOnClickListener = new OnClickListener() { - @Override - public void onClick(final View v) { - mHostInterface.onItemClicked(GalleryGridItemView.this, mData, false /*longClick*/); - } - }; - - public GalleryGridItemView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mData = DataModel.get().createGalleryGridItemData(); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mImageView = (AsyncImageView) findViewById(R.id.thumbnail); - mCheckBox = (CheckBox) findViewById(R.id.checkbox); - mCheckBox.setOnClickListener(mOnClickListener); - mAdditionalInfo = (RelativeLayout) findViewById(R.id.additional_info); - mIcon = (ImageView) findViewById(R.id.icon); - mFileInfo = (LinearLayout) findViewById(R.id.file_info); - mFileName = (TextView) findViewById(R.id.file_name); - mFileType = (TextView) findViewById(R.id.file_type); - setOnClickListener(mOnClickListener); - final OnLongClickListener longClickListener = v -> { - mHostInterface.onItemClicked(v, mData, true /* longClick */); - return true; - }; - setOnLongClickListener(longClickListener); - mCheckBox.setOnLongClickListener(longClickListener); - addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - // Enlarge the clickable region for the checkbox to fill the entire view. - final Rect region = new Rect(0, 0, getWidth(), getHeight()); - setTouchDelegate(new TouchDelegate(region, mCheckBox) { - @Override - public boolean onTouchEvent(@NonNull MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - setPressed(true); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - setPressed(false); - break; - } - return super.onTouchEvent(event); - } - }); - } - }); - } - - @Override - protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { - // The grid view auto-fit the columns, so we want to let the height match the width - // to make the image square. - super.onMeasure(widthMeasureSpec, widthMeasureSpec); - } - - public void bind(final Cursor cursor, final HostInterface hostInterface) { - final int desiredSize = getResources() - .getDimensionPixelSize(R.dimen.gallery_image_cell_size); - mData.bind(cursor, desiredSize, desiredSize); - mHostInterface = hostInterface; - updateViewState(); - } - - private void updateViewState() { - updateImageView(); - if (mHostInterface.isMultiSelectEnabled() && !mData.isDocumentPickerItem()) { - mCheckBox.setVisibility(VISIBLE); - mCheckBox.setClickable(true); - mCheckBox.setChecked(mHostInterface.isItemSelected(mData)); - } else { - mCheckBox.setVisibility(GONE); - mCheckBox.setClickable(false); - } - } - - private void updateImageView() { - if (mData.isDocumentPickerItem()) { - setBackgroundColor(ConversationDrawables.get().getConversationThemeColor()); - mIcon.setImageResource(R.drawable.ic_photo_library_light); - mIcon.clearColorFilter(); - mImageView.setVisibility(GONE); - mIcon.setVisibility(VISIBLE); - mFileInfo.setVisibility(GONE); - mAdditionalInfo.setVisibility(VISIBLE); - } else { - final String contentType = mData.getContentType(); - Resources.Theme theme = getContext().getTheme(); - if (ContentType.isAudioType(contentType)) { - setBackgroundColor( - getResources().getColor(R.color.gallery_image_default_background, theme)); - mIcon.setImageResource(R.drawable.ic_music); - mIcon.setColorFilter( - ConversationDrawables.get().getConversationThemeColor(), - PorterDuff.Mode.SRC_IN); - mFileName.setText(mData.getFileName()); - String[] type = contentType.split("/"); - mFileType.setText(type[1].toUpperCase() + " " + type[0]); - mImageView.setVisibility(GONE); - mIcon.setVisibility(VISIBLE); - mFileInfo.setVisibility(VISIBLE); - mAdditionalInfo.setVisibility(VISIBLE); - } else { // For image and video types - mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP); - setBackgroundColor( - getResources().getColor(R.color.gallery_image_default_background, theme)); - mImageView.setImageResourceId(mData.getImageRequestDescriptor()); - mImageView.setVisibility(VISIBLE); - if (ContentType.isVideoType(mData.getContentType())) { - mIcon.setImageResource(R.drawable.ic_play_light); - mIcon.clearColorFilter(); - mIcon.setVisibility(VISIBLE); - } else { - mIcon.setVisibility(GONE); - } - mFileInfo.setVisibility(GONE); - mAdditionalInfo.setVisibility(VISIBLE); - final long dateSeconds = mData.getDateSeconds(); - final boolean isValidDate = (dateSeconds > 0); - final int templateId = isValidDate ? - R.string.mediapicker_gallery_image_item_description : - R.string.mediapicker_gallery_image_item_description_no_date; - String contentDescription = String.format(getResources().getString(templateId), - dateSeconds * TimeUnit.SECONDS.toMillis(1)); - mImageView.setContentDescription(contentDescription); - } - } - } -} diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java deleted file mode 100644 index b85a5c9..0000000 --- a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * Copyright (C) 2024 The LineageOS 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.ui.mediapicker; - -import android.content.Context; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import androidx.collection.ArrayMap; -import android.util.AttributeSet; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; - -import com.android.messaging.R; -import com.android.messaging.datamodel.binding.BindingBase; -import com.android.messaging.datamodel.binding.ImmutableBindingRef; -import com.android.messaging.datamodel.data.DraftMessageData; -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener; -import com.android.messaging.ui.PersistentInstanceState; -import com.android.messaging.util.Assert; -import com.android.messaging.util.ContentType; -import com.android.messaging.util.LogUtil; - -import java.util.Iterator; -import java.util.Map; - -/** - * Shows a list of galley mediae from external storage in a GridView with multi-select capabilities, - * and with the option to intent out to a standalone media picker. - */ -public class GalleryGridView extends MediaPickerGridView implements - GalleryGridItemView.HostInterface, - PersistentInstanceState, - DraftMessageDataListener { - /** - * Implemented by the owner of this GalleryGridView instance to communicate on media picking and - * multi-media selection events. - */ - public interface GalleryGridViewListener { - void onDocumentPickerItemClicked(); - void onItemSelected(MessagePartData item); - void onItemUnselected(MessagePartData item); - void onConfirmSelection(); - void onUpdate(); - } - - private GalleryGridViewListener mListener; - - // TODO: Consider putting this into the data model object if we add more states. - private final ArrayMap mSelectedImages; - private boolean mIsMultiSelectMode = false; - private ImmutableBindingRef mDraftMessageDataModel; - - public GalleryGridView(final Context context, final AttributeSet attrs) { - super(context, attrs); - mSelectedImages = new ArrayMap<>(); - } - - public void setHostInterface(final GalleryGridViewListener hostInterface) { - mListener = hostInterface; - } - - public void setDraftMessageDataModel(final BindingBase dataModel) { - mDraftMessageDataModel = BindingBase.createBindingReference(dataModel); - mDraftMessageDataModel.getData().addListener(this); - } - - @Override - public void onItemClicked(final View view, final GalleryGridItemData data, - final boolean longClick) { - if (data.isDocumentPickerItem()) { - mListener.onDocumentPickerItemClicked(); - } else if (ContentType.isMediaType(data.getContentType())) { - if (longClick) { - // Turn on multi-select mode when an item is long-pressed. - setMultiSelectEnabled(true); - } - - final Rect startRect = new Rect(); - view.getGlobalVisibleRect(startRect); - if (isMultiSelectEnabled()) { - toggleItemSelection(startRect, data); - } else { - mListener.onItemSelected(data.constructMessagePartData(startRect)); - } - } else { - LogUtil.w(LogUtil.BUGLE_TAG, - "Selected item has invalid contentType " + data.getContentType()); - } - } - - @Override - public boolean isItemSelected(final GalleryGridItemData data) { - return mSelectedImages.containsKey(data.getImageUri()); - } - - int getSelectionCount() { - return mSelectedImages.size(); - } - - @Override - public boolean isMultiSelectEnabled() { - return mIsMultiSelectMode; - } - - private void toggleItemSelection(final Rect startRect, final GalleryGridItemData data) { - Assert.isTrue(isMultiSelectEnabled()); - if (isItemSelected(data)) { - final MessagePartData item = mSelectedImages.remove(data.getImageUri()); - mListener.onItemUnselected(item); - if (mSelectedImages.size() == 0) { - // No media is selected any more, turn off multi-select mode. - setMultiSelectEnabled(false); - } - } else { - final MessagePartData item = data.constructMessagePartData(startRect); - mSelectedImages.put(data.getImageUri(), item); - mListener.onItemSelected(item); - } - invalidateViews(); - } - - private void toggleMultiSelect() { - mIsMultiSelectMode = !mIsMultiSelectMode; - invalidateViews(); - } - - private void setMultiSelectEnabled(final boolean enabled) { - if (mIsMultiSelectMode != enabled) { - toggleMultiSelect(); - } - } - - private boolean canToggleMultiSelect() { - // We allow the user to toggle multi-select mode only when nothing has selected. If - // something has been selected, we show a confirm button instead. - return mSelectedImages.size() == 0; - } - - public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) { - inflater.inflate(R.menu.gallery_picker_menu, menu); - final MenuItem toggleMultiSelect = menu.findItem(R.id.action_multiselect); - final MenuItem confirmMultiSelect = menu.findItem(R.id.action_confirm_multiselect); - final boolean canToggleMultiSelect = canToggleMultiSelect(); - toggleMultiSelect.setVisible(canToggleMultiSelect); - confirmMultiSelect.setVisible(!canToggleMultiSelect); - } - - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.action_multiselect: - Assert.isTrue(canToggleMultiSelect()); - toggleMultiSelect(); - return true; - - case R.id.action_confirm_multiselect: - Assert.isTrue(!canToggleMultiSelect()); - mListener.onConfirmSelection(); - return true; - } - return false; - } - - - @Override - public void onDraftChanged(final DraftMessageData data, final int changeFlags) { - mDraftMessageDataModel.ensureBound(data); - // Whenever attachment changed, refresh selection state to remove those that are not - // selected. - if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) == - DraftMessageData.ATTACHMENTS_CHANGED) { - refreshImageSelectionStateOnAttachmentChange(); - } - } - - @Override - public void onDraftAttachmentLimitReached(final DraftMessageData data) { - mDraftMessageDataModel.ensureBound(data); - // Whenever draft attachment limit is reach, refresh selection state to remove those - // not actually added to draft. - refreshImageSelectionStateOnAttachmentChange(); - } - - @Override - public void onDraftAttachmentLoadFailed() { - // Nothing to do since the failed attachment gets removed automatically. - } - - private void refreshImageSelectionStateOnAttachmentChange() { - boolean changed = false; - final Iterator> iterator = - mSelectedImages.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - if (!mDraftMessageDataModel.getData().containsAttachment(entry.getKey())) { - iterator.remove(); - changed = true; - } - } - - if (changed) { - mListener.onUpdate(); - invalidateViews(); - } - } - - @Override // PersistentInstanceState - public Parcelable saveState() { - return onSaveInstanceState(); - } - - @Override // PersistentInstanceState - public void restoreState(final Parcelable restoredState) { - onRestoreInstanceState(restoredState); - invalidateViews(); - } - - @Override - public Parcelable onSaveInstanceState() { - final Parcelable superState = super.onSaveInstanceState(); - final SavedState savedState = new SavedState(superState); - savedState.isMultiSelectMode = mIsMultiSelectMode; - savedState.selectedImages = mSelectedImages.values() - .toArray(new MessagePartData[mSelectedImages.size()]); - return savedState; - } - - @Override - public void onRestoreInstanceState(final Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - - final SavedState savedState = (SavedState) state; - super.onRestoreInstanceState(savedState.getSuperState()); - mIsMultiSelectMode = savedState.isMultiSelectMode; - mSelectedImages.clear(); - for (int i = 0; i < savedState.selectedImages.length; i++) { - final MessagePartData selectedImage = savedState.selectedImages[i]; - mSelectedImages.put(selectedImage.getContentUri(), selectedImage); - } - } - - @Override // PersistentInstanceState - public void resetState() { - mSelectedImages.clear(); - mIsMultiSelectMode = false; - invalidateViews(); - } - - public static class SavedState extends BaseSavedState { - boolean isMultiSelectMode; - MessagePartData[] selectedImages; - - SavedState(final Parcelable superState) { - super(superState); - } - - private SavedState(final Parcel in) { - super(in); - isMultiSelectMode = in.readInt() == 1 ? true : false; - - // Read parts - final int partCount = in.readInt(); - selectedImages = new MessagePartData[partCount]; - for (int i = 0; i < partCount; i++) { - selectedImages[i] = ((MessagePartData) in.readParcelable( - MessagePartData.class.getClassLoader())); - } - } - - @Override - public void writeToParcel(final Parcel out, final int flags) { - super.writeToParcel(out, flags); - out.writeInt(isMultiSelectMode ? 1 : 0); - - // Write parts - out.writeInt(selectedImages.length); - for (final MessagePartData image : selectedImages) { - out.writeParcelable(image, flags); - } - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator<>() { - @Override - public SavedState createFromParcel(final Parcel in) { - return new SavedState(in); - } - - @Override - public SavedState[] newArray(final int size) { - return new SavedState[size]; - } - }; - } -} diff --git a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java index 4d73cdf..9dbc2ed 100644 --- a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java +++ b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java @@ -17,46 +17,21 @@ package com.android.messaging.ui.mediapicker; -import android.Manifest; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.database.MergeCursor; -import androidx.appcompat.app.ActionBar; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import com.android.messaging.Factory; import com.android.messaging.R; -import com.android.messaging.datamodel.data.GalleryGridItemData; -import com.android.messaging.datamodel.data.MediaPickerData; -import com.android.messaging.datamodel.data.MessagePartData; -import com.android.messaging.datamodel.data.MediaPickerData.MediaPickerDataListener; -import com.android.messaging.ui.UIIntents; -import com.android.messaging.util.Assert; -import com.android.messaging.util.OsUtil; /** * Chooser which allows the user to select one or more existing images or videos or audios. */ -class GalleryMediaChooser extends MediaChooser implements - GalleryGridView.GalleryGridViewListener, MediaPickerDataListener { - private final GalleryGridAdapter mAdapter; - private GalleryGridView mGalleryGridView; - private View mMissingPermissionView; - +class GalleryMediaChooser extends MediaChooser { /** Handles picking a media from the document picker. */ private final DocumentImagePicker mDocumentImagePicker; GalleryMediaChooser(final MediaPicker mediaPicker) { super(mediaPicker); - mAdapter = new GalleryGridAdapter(Factory.get().getApplicationContext(), null); mDocumentImagePicker = new DocumentImagePicker(mMediaPicker, data -> { if (mBindingRef.isBound()) { mMediaPicker.dispatchPendingItemAdded(data); @@ -71,17 +46,6 @@ class GalleryMediaChooser extends MediaChooser implements | MediaPicker.MEDIA_TYPE_AUDIO); } - @Override - public View destroyView() { - mGalleryGridView.setAdapter(null); - mAdapter.setHostInterface(null); - // The loader is started only if startMediaPickerDataLoader() is called - if (OsUtil.hasStoragePermission()) { - mBindingRef.getData().destroyLoader(MediaPickerData.GALLERY_MEDIA_LOADER); - } - return super.destroyView(); - } - @Override public int getIconResource() { return R.drawable.ic_image_light; @@ -92,45 +56,6 @@ class GalleryMediaChooser extends MediaChooser implements return R.string.mediapicker_galleryChooserDescription; } - @Override - public boolean canSwipeDown() { - return mGalleryGridView.canSwipeDown(); - } - - @Override - public void onItemSelected(final MessagePartData item) { - mMediaPicker.dispatchItemsSelected(item, !mGalleryGridView.isMultiSelectEnabled()); - } - - @Override - public void onItemUnselected(final MessagePartData item) { - mMediaPicker.dispatchItemUnselected(item); - } - - @Override - public void onConfirmSelection() { - // The user may only confirm if multiselect is enabled. - Assert.isTrue(mGalleryGridView.isMultiSelectEnabled()); - mMediaPicker.dispatchConfirmItemSelection(); - } - - @Override - public void onUpdate() { - mMediaPicker.invalidateOptionsMenu(); - } - - @Override - public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) { - if (mView != null) { - mGalleryGridView.onCreateOptionsMenu(inflater, menu); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - return (mView != null) ? mGalleryGridView.onOptionsItemSelected(item) : false; - } - @Override protected View createView(final ViewGroup container) { final LayoutInflater inflater = getLayoutInflater(); @@ -139,17 +64,11 @@ class GalleryMediaChooser extends MediaChooser implements container /* root */, false /* attachToRoot */); - mGalleryGridView = (GalleryGridView) view.findViewById(R.id.gallery_grid_view); - mAdapter.setHostInterface(mGalleryGridView); - mGalleryGridView.setAdapter(mAdapter); - mGalleryGridView.setHostInterface(this); - mGalleryGridView.setDraftMessageDataModel(mMediaPicker.getDraftMessageDataModel()); - if (OsUtil.hasStoragePermission()) { - startMediaPickerDataLoader(); - } - - mMissingPermissionView = view.findViewById(R.id.missing_permission_view); - updateForPermissionState(OsUtil.hasStoragePermission()); + final View enabledView = view.findViewById(R.id.mediapicker_enabled); + enabledView.setOnClickListener(v -> { + // Launch an external picker to pick item from document picker as attachment. + mDocumentImagePicker.launchPicker(); + }); return view; } @@ -157,99 +76,4 @@ class GalleryMediaChooser extends MediaChooser implements int getActionBarTitleResId() { return R.string.mediapicker_gallery_title; } - - @Override - public void onDocumentPickerItemClicked() { - // Launch an external picker to pick item from document picker as attachment. - mDocumentImagePicker.launchPicker(); - } - - @Override - void updateActionBar(final ActionBar actionBar) { - super.updateActionBar(actionBar); - if (mGalleryGridView == null) { - return; - } - final int selectionCount = mGalleryGridView.getSelectionCount(); - if (selectionCount > 0 && mGalleryGridView.isMultiSelectEnabled()) { - actionBar.setTitle(getContext().getResources().getString( - R.string.mediapicker_gallery_title_selection, - selectionCount)); - } - } - - @Override - public void onMediaPickerDataUpdated(final MediaPickerData mediaPickerData, final Object data, - final int loaderId) { - mBindingRef.ensureBound(mediaPickerData); - Assert.equals(MediaPickerData.GALLERY_MEDIA_LOADER, loaderId); - Cursor rawCursor = null; - if (data instanceof Cursor) { - rawCursor = (Cursor) data; - } - // Before delivering the cursor, wrap around the local gallery cursor - // with an extra item for document picker integration in the front. - final MatrixCursor specialItemsCursor = - new MatrixCursor(GalleryGridItemData.SPECIAL_ITEM_COLUMNS); - specialItemsCursor.addRow(new Object[] { GalleryGridItemData.ID_DOCUMENT_PICKER_ITEM }); - final MergeCursor cursor = - new MergeCursor(new Cursor[] { specialItemsCursor, rawCursor }); - mAdapter.swapCursor(cursor); - } - - @Override - public void onResume() { - if (OsUtil.hasStoragePermission()) { - // Work around a bug in MediaStore where cursors querying the Files provider don't get - // updated for changes to Images.Media or Video.Media. - startMediaPickerDataLoader(); - } - } - - @Override - protected void setSelected(final boolean selected) { - super.setSelected(selected); - if (selected && !OsUtil.hasStoragePermission()) { - mMediaPicker.requestPermissions( - new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, - MediaPicker.GALLERY_PERMISSION_REQUEST_CODE); - } - } - - private void startMediaPickerDataLoader() { - mBindingRef - .getData() - .startLoader(MediaPickerData.GALLERY_MEDIA_LOADER, mBindingRef, null, this); - } - - @Override - protected void onRequestPermissionsResult( - final int requestCode, final String[] permissions, final int[] grantResults) { - if (requestCode == MediaPicker.GALLERY_PERMISSION_REQUEST_CODE) { - final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED; - if (permissionGranted) { - startMediaPickerDataLoader(); - } - updateForPermissionState(permissionGranted); - } - } - - private void updateForPermissionState(final boolean granted) { - // onRequestPermissionsResult can sometimes get called before createView(). - if (mGalleryGridView == null) { - return; - } - - mGalleryGridView.setVisibility(granted ? View.VISIBLE : View.GONE); - mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE); - } - - @Override - protected void onActivityResult( - final int requestCode, final int resultCode, final Intent data) { - if (requestCode == UIIntents.REQUEST_PICK_MEDIA_FROM_DOCUMENT_PICKER - && resultCode == Activity.RESULT_OK) { - mDocumentImagePicker.onActivityResult(requestCode, resultCode, data); - } - } } diff --git a/src/com/android/messaging/ui/mediapicker/MediaChooser.java b/src/com/android/messaging/ui/mediapicker/MediaChooser.java index a3ed26d..047be38 100644 --- a/src/com/android/messaging/ui/mediapicker/MediaChooser.java +++ b/src/com/android/messaging/ui/mediapicker/MediaChooser.java @@ -18,7 +18,6 @@ package com.android.messaging.ui.mediapicker; import android.content.Context; -import android.content.Intent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -197,9 +196,6 @@ abstract class MediaChooser extends BasePagerViewHolder public void stopTouchHandling() { } - protected void onActivityResult( - final int requestCode, final int resultCode, final Intent data) {} - @Override public int getConversationSelfSubId() { return mMediaPicker.getConversationSelfSubId(); diff --git a/src/com/android/messaging/ui/mediapicker/MediaPicker.java b/src/com/android/messaging/ui/mediapicker/MediaPicker.java index 88a3b9c..a13eeb2 100644 --- a/src/com/android/messaging/ui/mediapicker/MediaPicker.java +++ b/src/com/android/messaging/ui/mediapicker/MediaPicker.java @@ -19,7 +19,6 @@ package com.android.messaging.ui.mediapicker; import android.app.Activity; import android.content.Context; -import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.view.LayoutInflater; @@ -34,7 +33,6 @@ import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.fragment.app.Fragment; -import androidx.loader.app.LoaderManager; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; @@ -45,10 +43,10 @@ import com.android.messaging.datamodel.binding.Binding; import com.android.messaging.datamodel.binding.BindingBase; import com.android.messaging.datamodel.binding.ImmutableBindingRef; import com.android.messaging.datamodel.data.DraftMessageData; +import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider; import com.android.messaging.datamodel.data.MediaPickerData; import com.android.messaging.datamodel.data.MessagePartData; import com.android.messaging.datamodel.data.PendingAttachmentData; -import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider; import com.android.messaging.ui.BugleActionBarActivity; import com.android.messaging.ui.FixedViewPagerAdapter; import com.android.messaging.util.AccessibilityUtil; @@ -202,7 +200,6 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mBinding.getData().init(LoaderManager.getInstance(this)); } @Override @@ -283,12 +280,6 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat } } - @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - mSelectedChooser.onActivityResult(requestCode, resultCode, data); - } - @Override public void onDestroy() { super.onDestroy(); @@ -442,7 +433,6 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat if (mBinding.isBound() && getActivity() != null) { mBinding.unbind(); mBinding.bind(DataModel.get().createMediaPickerData(getActivity())); - mBinding.getData().init(LoaderManager.getInstance(this)); } } diff --git a/src/com/android/messaging/util/UriUtil.java b/src/com/android/messaging/util/UriUtil.java index 7aaec85..0bb99fa 100644 --- a/src/com/android/messaging/util/UriUtil.java +++ b/src/com/android/messaging/util/UriUtil.java @@ -27,7 +27,6 @@ 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; @@ -136,18 +135,6 @@ public class UriUtil { 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.