summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/ui/mediapicker
diff options
context:
space:
mode:
authorGary Kipnis <gkipnis@cyngn.com>2016-05-20 15:25:59 -0700
committerMichael Bestas <mkbestas@lineageos.org>2019-10-29 20:23:47 +0200
commit7d0e3e781baaf15601e71b3426d95a7d99bdcd82 (patch)
treef873e4e66aa1cceee832a5096c3049546351ba42 /src/com/android/messaging/ui/mediapicker
parent25dbc450baf129792b5fba9b252836a5dea44506 (diff)
downloadpackages_apps_Messaging-7d0e3e781baaf15601e71b3426d95a7d99bdcd82.tar.gz
packages_apps_Messaging-7d0e3e781baaf15601e71b3426d95a7d99bdcd82.tar.bz2
packages_apps_Messaging-7d0e3e781baaf15601e71b3426d95a7d99bdcd82.zip
Added support for video and audio mms attachments
Change-Id: I690f941ac80a861e57494028f676a7db658bdce1 Ticket-Id: FEIJ-128, FEIJ-143
Diffstat (limited to 'src/com/android/messaging/ui/mediapicker')
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioListAdapter.java61
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioListChooser.java216
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioListItemView.java111
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioListView.java311
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java71
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridView.java20
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java10
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPicker.java1
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPickerListView.java45
9 files changed, 842 insertions, 4 deletions
diff --git a/src/com/android/messaging/ui/mediapicker/AudioListAdapter.java b/src/com/android/messaging/ui/mediapicker/AudioListAdapter.java
new file mode 100644
index 0000000..2520d6b
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioListAdapter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 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.AudioListItemView.HostInterface;
+import com.android.messaging.util.Assert;
+
+/**
+ * Bridges between the image cursor loaded by GalleryBoundCursorLoader and the GalleryGridView.
+ */
+public class AudioListAdapter extends CursorAdapter {
+ private HostInterface mGgivHostInterface;
+
+ public AudioListAdapter(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 AudioListItemView);
+ final AudioListItemView audioListItemView = (AudioListItemView) view;
+ audioListItemView.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.audio_list_item_view, parent, false);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioListChooser.java b/src/com/android/messaging/ui/mediapicker/AudioListChooser.java
new file mode 100644
index 0000000..767e725
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioListChooser.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2016 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.Manifest;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+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.MediaPickerData;
+import com.android.messaging.datamodel.data.MediaPickerData.MediaPickerDataListener;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.OsUtil;
+
+/**
+ * Chooser which allows the user to select one or more existing audios
+ */
+class AudioListChooser extends MediaChooser implements
+ AudioListView.AudioListViewListener, MediaPickerDataListener {
+ private final AudioListAdapter mAdapter;
+ private AudioListView mAudioListView;
+ private View mMissingPermissionView;
+ private static final String TAG = AudioListChooser.class.getSimpleName();
+
+ AudioListChooser(final MediaPicker mediaPicker) {
+ super(mediaPicker);
+ mAdapter = new AudioListAdapter(Factory.get().getApplicationContext(), null);
+ }
+
+ @Override
+ public int getSupportedMediaTypes() {
+ return MediaPicker.MEDIA_TYPE_AUDIO;
+ }
+
+ @Override
+ public View destroyView() {
+ mAudioListView.setAdapter(null);
+ mAdapter.setHostInterface(null);
+ // The loader is started only if startMediaPickerDataLoader() is called
+ if (OsUtil.hasStoragePermission()) {
+ mBindingRef.getData().destroyLoader(MediaPickerData.GALLERY_AUDIO_LOADER);
+ }
+ return super.destroyView();
+ }
+
+ @Override
+ public int getIconResource() {
+ return R.drawable.ic_library_music_white_24px;
+ }
+
+ @Override
+ public int getIconDescriptionResource() {
+ return R.string.mediapicker_audioChooserDescription;
+ }
+
+ @Override
+ public boolean canSwipeDown() {
+ return mAudioListView.canSwipeDown();
+ }
+
+ @Override
+ public void onItemSelected(final MessagePartData item) {
+ mMediaPicker.dispatchItemsSelected(item, !mAudioListView.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(mAudioListView.isMultiSelectEnabled());
+ mMediaPicker.dispatchConfirmItemSelection();
+ }
+
+ @Override
+ public void onUpdate() {
+ mMediaPicker.invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
+ if (mView != null) {
+ mAudioListView.onCreateOptionsMenu(inflater, menu);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ return (mView != null) ? mAudioListView.onOptionsItemSelected(item) : false;
+ }
+
+ @Override
+ protected View createView(final ViewGroup container) {
+ final LayoutInflater inflater = getLayoutInflater();
+ final View view = inflater.inflate(
+ R.layout.mediapicker_audio_list_chooser,
+ container /* root */,
+ false /* attachToRoot */);
+
+ mAudioListView = (AudioListView) view.findViewById(R.id.audio_list_view);
+ mAdapter.setHostInterface(mAudioListView);
+ mAudioListView.setAdapter(mAdapter);
+ mAudioListView.setHostInterface(this);
+ mAudioListView.setDraftMessageDataModel(mMediaPicker.getDraftMessageDataModel());
+ if (OsUtil.hasStoragePermission()) {
+ startMediaPickerDataLoader();
+ }
+
+ mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
+ updateForPermissionState(OsUtil.hasStoragePermission());
+ return view;
+ }
+
+ @Override
+ int getActionBarTitleResId() {
+ return R.string.mediapicker_audio_list_title;
+ }
+
+ @Override
+ void updateActionBar(final ActionBar actionBar) {
+ super.updateActionBar(actionBar);
+ if (mAudioListView == null) {
+ return;
+ }
+ final int selectionCount = mAudioListView.getSelectionCount();
+ if (selectionCount > 0 && mAudioListView.isMultiSelectEnabled()) {
+ actionBar.setTitle(getContext().getResources().getString(
+ R.string.mediapicker_audio_list_title_selection,
+ selectionCount));
+ }
+ }
+
+ @Override
+ public void onMediaPickerDataUpdated(final MediaPickerData mediaPickerData, final Object data,
+ final int loaderId) {
+ mBindingRef.ensureBound(mediaPickerData);
+ Assert.equals(MediaPickerData.GALLERY_AUDIO_LOADER, loaderId);
+ Cursor rawCursor = null;
+ if (data instanceof Cursor) {
+ rawCursor = (Cursor) data;
+ }
+
+ mAdapter.swapCursor(rawCursor);
+ }
+
+ @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_AUDIO_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 (mAudioListView == null) {
+ return;
+ }
+
+ mAudioListView.setVisibility(granted ? View.VISIBLE : View.GONE);
+ mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioListItemView.java b/src/com/android/messaging/ui/mediapicker/AudioListItemView.java
new file mode 100644
index 0000000..d95f1fd
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioListItemView.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 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.util.AttributeSet;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.data.AudioListItemData;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Shows an item in the audio picker list view. Hosts an FileImageView with a checkbox.
+ */
+public class AudioListItemView extends LinearLayout {
+ private static final String TAG = AudioListItemView.class.getSimpleName();
+ /**
+ * Implemented by the owner of this ListItemView instance to communicate on media
+ * picking and selection events.
+ */
+ public interface HostInterface {
+ void onItemClicked(View view, AudioListItemData data, boolean longClick);
+ boolean isItemSelected(AudioListItemData data);
+ boolean isMultiSelectEnabled();
+ }
+
+ @VisibleForTesting
+ AudioListItemData mData;
+ TextView mAudioFilename;
+ CheckBox mCheckBox;
+ ImageView mImageIcon;
+
+ private HostInterface mHostInterface;
+ private final OnClickListener mOnClickListener = new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mHostInterface.onItemClicked(AudioListItemView.this, mData, false /*longClick*/);
+ }
+ };
+
+ public AudioListItemView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mData = DataModel.get().createAudioListItemData();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAudioFilename = (TextView)findViewById(R.id.audio_filename);
+ mImageIcon = (ImageView) findViewById(R.id.audio_button);
+
+ mCheckBox = (CheckBox)findViewById(R.id.audio_checkbox);
+ mCheckBox.setOnClickListener(mOnClickListener);
+ setOnClickListener(mOnClickListener);
+
+ final OnLongClickListener longClickListener = new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(final View v) {
+ mHostInterface.onItemClicked(v, mData, true /* longClick */);
+ return true;
+ }
+ };
+ setOnLongClickListener(longClickListener);
+ mCheckBox.setOnLongClickListener(longClickListener);
+ }
+
+
+ public void bind(final Cursor cursor, final HostInterface hostInterface) {
+ mData.bind(cursor);
+ mHostInterface = hostInterface;
+ updateViewState();
+ }
+
+ private void updateViewState() {
+ updateListItemView();
+ if (mHostInterface.isMultiSelectEnabled()) {
+ mCheckBox.setVisibility(VISIBLE);
+ mCheckBox.setClickable(true);
+ mCheckBox.setChecked(mHostInterface.isItemSelected(mData));
+ mImageIcon.setVisibility(GONE);
+ } else {
+ mCheckBox.setVisibility(GONE);
+ mCheckBox.setClickable(false);
+ mImageIcon.setVisibility(VISIBLE);
+ }
+ }
+
+ private void updateListItemView() {
+ mAudioFilename.setText(mData.getAudioFilename());
+ }
+
+}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioListView.java b/src/com/android/messaging/ui/mediapicker/AudioListView.java
new file mode 100644
index 0000000..7fc7d2c
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioListView.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2016 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.graphics.Rect;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.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.AudioListItemData;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
+import com.android.messaging.datamodel.data.MessagePartData;
+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 audio filenames from external storage in a ListView with multi-select
+ * capabilities
+ */
+public class AudioListView extends MediaPickerListView implements
+ AudioListItemView.HostInterface,
+ PersistentInstanceState,
+ DraftMessageDataListener {
+ /**
+ * Implemented by the owner of this GalleryGridView instance to communicate on image
+ * picking and multi-image selection events.
+ */
+ public interface AudioListViewListener {
+ void onItemSelected(MessagePartData item);
+ void onItemUnselected(MessagePartData item);
+ void onConfirmSelection();
+ void onUpdate();
+ }
+
+ private AudioListViewListener mListener;
+
+ // TODO: Consider putting this into the data model object if we add more states.
+ private final ArrayMap<Uri, MessagePartData> mSelectedAudios;
+ private boolean mIsMultiSelectMode = false;
+ private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
+
+ public AudioListView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mSelectedAudios = new ArrayMap<Uri, MessagePartData>();
+ }
+
+ public void setHostInterface(final AudioListViewListener 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 AudioListItemData data,
+ final boolean longClick) {
+ 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 AudioListItemData data) {
+ return mSelectedAudios.containsKey(data.getAudioUri());
+ }
+
+ int getSelectionCount() {
+ return mSelectedAudios.size();
+ }
+
+ @Override
+ public boolean isMultiSelectEnabled() {
+ return mIsMultiSelectMode;
+ }
+
+ private void toggleItemSelection(final Rect startRect, final AudioListItemData data) {
+ Assert.isTrue(isMultiSelectEnabled());
+ if (isItemSelected(data)) {
+ final MessagePartData item = mSelectedAudios.remove(data.getAudioUri());
+ mListener.onItemUnselected(item);
+ if (mSelectedAudios.size() == 0) {
+ // No image is selected any more, turn off multi-select mode.
+ setMultiSelectEnabled(false);
+ }
+ } else {
+ final MessagePartData item = data.constructMessagePartData(startRect);
+ mSelectedAudios.put(data.getAudioUri(), 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 mSelectedAudios.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 =
+ mSelectedAudios.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.selectedAudios = mSelectedAudios.values()
+ .toArray(new MessagePartData[mSelectedAudios.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;
+ mSelectedAudios.clear();
+ for (int i = 0; i < savedState.selectedAudios.length; i++) {
+ final MessagePartData selectedAudio = savedState.selectedAudios[i];
+ mSelectedAudios.put(selectedAudio.getContentUri(), selectedAudio);
+ }
+ }
+
+ @Override // PersistentInstanceState
+ public void resetState() {
+ mSelectedAudios.clear();
+ mIsMultiSelectMode = false;
+ invalidateViews();
+ }
+
+ public static class SavedState extends BaseSavedState {
+ boolean isMultiSelectMode;
+ MessagePartData[] selectedAudios;
+
+ 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();
+ selectedAudios = new MessagePartData[partCount];
+ for (int i = 0; i < partCount; i++) {
+ selectedAudios[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(selectedAudios.length);
+ for (final MessagePartData image : selectedAudios) {
+ out.writeParcelable(image, flags);
+ }
+ }
+
+ public static final Creator<SavedState> CREATOR =
+ new Creator<SavedState>() {
+ @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/GalleryGridItemView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
index 2006f57..eb66b80 100644
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
+++ b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
@@ -18,7 +18,9 @@ package com.android.messaging.ui.mediapicker;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Rect;
+import androidx.appcompat.mms.CarrierConfigValuesLoader;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View;
@@ -26,11 +28,16 @@ import android.widget.CheckBox;
import android.widget.FrameLayout;
import android.widget.ImageView.ScaleType;
+import android.widget.Toast;
import com.android.messaging.R;
import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.data.DraftMessageData;
import com.android.messaging.datamodel.data.GalleryGridItemData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsConfig;
import com.android.messaging.ui.AsyncImageView;
import com.android.messaging.ui.ConversationDrawables;
+import com.android.messaging.util.UriUtil;
import com.google.common.annotations.VisibleForTesting;
import java.util.concurrent.TimeUnit;
@@ -39,6 +46,7 @@ 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 {
+ private static final String TAG = GalleryGridItemView.class.getSimpleName();
/**
* Implemented by the owner of this GalleryGridItemView instance to communicate on media
* picking and selection events.
@@ -47,6 +55,7 @@ public class GalleryGridItemView extends FrameLayout {
void onItemClicked(View view, GalleryGridItemData data, boolean longClick);
boolean isItemSelected(GalleryGridItemData data);
boolean isMultiSelectEnabled();
+ int getSubscriptionProviderSubId();
}
@VisibleForTesting
@@ -55,10 +64,65 @@ public class GalleryGridItemView extends FrameLayout {
private AsyncImageView mImageView;
private CheckBox mCheckBox;
private HostInterface mHostInterface;
+ private static long mTotalContentSize = 0;
+ private static long mMaxMessageSize = 0;
+
+ private boolean checkSize() {
+ if (mData.isDocumentPickerItem()) {
+ return true;
+ }
+ // only perform the check for videos, since they are not being compressed
+ // images will be compressed, so exclude them from this check
+
+ // determine the maximum message size, this will be computed only once per this class
+ if (mMaxMessageSize == 0) {
+ int subId = mHostInterface.getSubscriptionProviderSubId();
+ mMaxMessageSize = MmsConfig.get(subId).getMaxMessageSize();
+ }
+
+ long contentSize = mData.getContentSize();
+ if (mHostInterface.isMultiSelectEnabled()) {
+ if (mData.isVideoItem()) {
+ if (mHostInterface.isItemSelected(mData)) {
+ // un-selecting
+ mTotalContentSize -= contentSize;
+ if (mTotalContentSize < 0) {
+ mTotalContentSize = 0;
+ }
+ } else {
+ // selecting
+ mTotalContentSize += contentSize;
+ }
+ }
+ } else {
+ // short click or first long click
+ if (mData.isVideoItem()) {
+ mTotalContentSize = contentSize;
+ } else {
+ mTotalContentSize = 0;
+ }
+ }
+
+ if (mTotalContentSize > mMaxMessageSize) {
+ mTotalContentSize -= contentSize;
+ if (mTotalContentSize < 0) {
+ mTotalContentSize = 0;
+ }
+
+ Toast.makeText(getContext(), getContext().
+ getString(R.string.mediapicker_gallery_image_item_attachment_too_large),
+ Toast.LENGTH_LONG).show();
+ return false;
+ }
+
+ return true;
+ }
private final OnClickListener mOnClickListener = new OnClickListener() {
@Override
public void onClick(final View v) {
- mHostInterface.onItemClicked(GalleryGridItemView.this, mData, false /*longClick*/);
+ if (checkSize()) {
+ mHostInterface.onItemClicked(GalleryGridItemView.this, mData, false /*longClick*/);
+ }
}
};
@@ -78,7 +142,9 @@ public class GalleryGridItemView extends FrameLayout {
final OnLongClickListener longClickListener = new OnLongClickListener() {
@Override
public boolean onLongClick(final View v) {
- mHostInterface.onItemClicked(v, mData, true /* longClick */);
+ if (checkSize()) {
+ mHostInterface.onItemClicked(v, mData, true /* longClick */);
+ }
return true;
}
};
@@ -159,7 +225,6 @@ public class GalleryGridItemView extends FrameLayout {
dateSeconds * TimeUnit.SECONDS.toMillis(1));
mImageView.setContentDescription(contentDescription);
} else {
- hideVideoPlayButtonOverlay();
mImageView.setScaleType(ScaleType.CENTER_CROP);
setBackgroundColor(getResources().getColor(R.color.gallery_image_default_background));
mImageView.setImageResourceId(mData.getImageRequestDescriptor());
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
index 2265dd5..a9bc8b5 100644
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
+++ b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
@@ -34,6 +34,7 @@ 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.datamodel.data.ParticipantData;
import com.android.messaging.ui.PersistentInstanceState;
import com.android.messaging.util.Assert;
import com.android.messaging.util.ContentType;
@@ -69,11 +70,20 @@ public class GalleryGridView extends MediaPickerGridView implements
private boolean mIsMultiSelectMode = false;
private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
+ /** Provides subscription-related data to access per-subscription configurations. */
+ private DraftMessageData.DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
+
+
public GalleryGridView(final Context context, final AttributeSet attrs) {
super(context, attrs);
mSelectedImages = new ArrayMap<Uri, MessagePartData>();
}
+ public void setSubscriptionProvider(DraftMessageData.DraftMessageSubscriptionDataProvider
+ provider) {
+ mSubscriptionDataProvider = provider;
+ }
+
public void setHostInterface(final GalleryGridViewListener hostInterface) {
mListener = hostInterface;
}
@@ -121,6 +131,16 @@ public class GalleryGridView extends MediaPickerGridView implements
return mIsMultiSelectMode;
}
+ @Override
+ public int getSubscriptionProviderSubId() {
+ if (mSubscriptionDataProvider != null) {
+ return mSubscriptionDataProvider.getConversationSelfSubId();
+ } else {
+ return ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+ }
+
+
private void toggleItemSelection(final Rect startRect, final GalleryGridItemData data) {
Assert.isTrue(isMultiSelectEnabled());
if (isItemSelected(data)) {
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
index 1b8c2dc..728de4c 100644
--- a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
+++ b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
@@ -21,7 +21,10 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
+import android.provider.Telephony;
import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.mms.CarrierConfigValuesLoader;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -29,6 +32,7 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Toast;
import com.android.messaging.Factory;
import com.android.messaging.R;
import com.android.messaging.datamodel.data.GalleryGridItemData;
@@ -37,6 +41,7 @@ import com.android.messaging.datamodel.data.MessagePartData;
import com.android.messaging.datamodel.data.MediaPickerData.MediaPickerDataListener;
import com.android.messaging.util.Assert;
import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UriUtil;
/**
* Chooser which allows the user to select one or more existing images or videos
@@ -46,6 +51,7 @@ class GalleryMediaChooser extends MediaChooser implements
private final GalleryGridAdapter mAdapter;
private GalleryGridView mGalleryGridView;
private View mMissingPermissionView;
+ private static final String TAG = GalleryMediaChooser.class.getSimpleName();
GalleryMediaChooser(final MediaPicker mediaPicker) {
super(mediaPicker);
@@ -75,7 +81,7 @@ class GalleryMediaChooser extends MediaChooser implements
@Override
public int getIconDescriptionResource() {
- return R.string.mediapicker_galleryChooserDescription;
+ return R.string.mediapicker_galleryChooserDescription_cm;
}
@Override
@@ -127,6 +133,7 @@ class GalleryMediaChooser extends MediaChooser implements
mGalleryGridView = (GalleryGridView) view.findViewById(R.id.gallery_grid_view);
mAdapter.setHostInterface(mGalleryGridView);
+ mGalleryGridView.setSubscriptionProvider(this);
mGalleryGridView.setAdapter(mAdapter);
mGalleryGridView.setHostInterface(this);
mGalleryGridView.setDraftMessageDataModel(mMediaPicker.getDraftMessageDataModel());
@@ -172,6 +179,7 @@ class GalleryMediaChooser extends MediaChooser implements
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 =
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPicker.java b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
index 8e5198b..5920e63 100644
--- a/src/com/android/messaging/ui/mediapicker/MediaPicker.java
+++ b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
@@ -178,6 +178,7 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
mChoosers = new MediaChooser[] {
new CameraMediaChooser(this),
new GalleryMediaChooser(this),
+ new AudioListChooser(this),
new AudioMediaChooser(this),
};
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPickerListView.java b/src/com/android/messaging/ui/mediapicker/MediaPickerListView.java
new file mode 100644
index 0000000..f448b87
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/MediaPickerListView.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util.AttributeSet;
+import android.widget.GridView;
+import android.widget.ListView;
+
+public class MediaPickerListView extends ListView {
+
+ public MediaPickerListView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Returns if the list view can be swiped down further. It cannot be swiped down
+ * if there's no item or if we are already at the top.
+ */
+ public boolean canSwipeDown() {
+ if (getAdapter() == null || getAdapter().getCount() == 0 || getChildCount() == 0) {
+ return false;
+ }
+
+ final int firstVisiblePosition = getFirstVisiblePosition();
+ if (firstVisiblePosition == 0 && getChildAt(0).getTop() >= 0) {
+ return false;
+ }
+ return true;
+ }
+}