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:
@@ -59,6 +59,7 @@ android_app {
|
||||
defaults: ["messaging_defaults"],
|
||||
|
||||
static_libs: [
|
||||
"androidx.activity_activity",
|
||||
"androidx.annotation_annotation",
|
||||
"androidx.appcompat_appcompat",
|
||||
"androidx.collection_collection",
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<com.android.messaging.ui.mediapicker.GalleryGridItemView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/gallery_image_cell_size"
|
||||
android:background="@color/gallery_image_default_background"
|
||||
android:clickable="true">
|
||||
|
||||
<!-- Thumbnail for image and video contents. -->
|
||||
<com.android.messaging.ui.AsyncImageView
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<!-- Additional info such as icon, name and etc. -->
|
||||
<RelativeLayout
|
||||
android:id="@+id/additional_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" >
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@id/file_info" >
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="@dimen/gallery_icon_size"
|
||||
android:layout_height="@dimen/gallery_icon_size"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@color/background_item_transparent"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<!-- File info for audio contents only -->
|
||||
<LinearLayout
|
||||
android:id="@+id/file_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@android:style/TextAppearance.Material.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/file_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="right"
|
||||
android:textAppearance="@android:style/TextAppearance.Material.Caption" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:focusableInTouchMode="true"
|
||||
android:background="@drawable/gallery_image_background_selector" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
style="@style/GalleryGridItemViewCheckBoxStyle"
|
||||
android:button="@drawable/gallery_checkbox_selector"
|
||||
android:background="@null"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:paddingTop="4dp"
|
||||
android:visibility="gone"
|
||||
android:contentDescription="@string/gallery_checkbox_content_description" />
|
||||
|
||||
</com.android.messaging.ui.mediapicker.GalleryGridItemView>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
@@ -15,30 +16,36 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/mediapicker_enabled"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<com.android.messaging.ui.mediapicker.GalleryGridView
|
||||
android:id="@+id/gallery_grid_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="6dp"
|
||||
android:columnWidth="@dimen/gallery_image_cell_size"
|
||||
android:numColumns="auto_fit"
|
||||
android:verticalSpacing="6dp"
|
||||
android:horizontalSpacing="6dp"
|
||||
android:stretchMode="columnWidth"
|
||||
android:gravity="center"
|
||||
android:clipToPadding="false"
|
||||
android:background="@android:color/white" />
|
||||
<!-- This view will hide all other views if the required permission is not granted -->
|
||||
<TextView
|
||||
android:id="@+id/missing_permission_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/enable_permission_procedure"
|
||||
android:contentDescription="@string/enable_permission_procedure_description"
|
||||
android:background="@android:color/white"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<TextView
|
||||
style="@style/ContactPickerHintText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|center"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/mediapicker_galleryChooserDescription"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_image_light"
|
||||
android:scaleX="1.33"
|
||||
android:scaleY="1.33"
|
||||
app:tint="@color/primary_color"
|
||||
android:importantForAccessibility="no"
|
||||
android:contentDescription="@null" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:appcompat="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<item
|
||||
android:id="@+id/action_multiselect"
|
||||
android:icon="@drawable/ic_checkbox_outline_light"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/action_multiselect"
|
||||
appcompat:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_confirm_multiselect"
|
||||
android:icon="@drawable/ic_checkmark_light"
|
||||
android:orderInCategory="101"
|
||||
android:title="@string/action_confirm_multiselect"
|
||||
appcompat:showAsAction="always"/>
|
||||
</menu>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Intent> 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) {
|
||||
|
||||
@@ -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<SelectedConversation> conversations) {
|
||||
if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
|
||||
|
||||
@@ -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<Intent> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PickVisualMediaRequest> 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<Uri> uris) {
|
||||
for (Uri uri: uris) {
|
||||
prepareDocumentForAttachment(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Uri, MessagePartData> mSelectedImages;
|
||||
private boolean mIsMultiSelectMode = false;
|
||||
private ImmutableBindingRef<DraftMessageData> 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<DraftMessageData> 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<Map.Entry<Uri, MessagePartData>> iterator =
|
||||
mSelectedImages.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<Uri, MessagePartData> 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<SavedState> 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user