Add "Select up to max items" label for max selection
Bug: 192823495
Test: manual (video attached to the bug)
Change-Id: I3fde3ad7e02f20aa8221910a81aee06c0e6e0978
Merged-In: I3fde3ad7e02f20aa8221910a81aee06c0e6e0978
(cherry picked from commit 5d766f0408eefd01ef8e4d20321a579fec875f39)
diff --git a/res/layout/item_message.xml b/res/layout/item_message.xml
new file mode 100644
index 0000000..84e29ed
--- /dev/null
+++ b/res/layout/item_message.xml
@@ -0,0 +1,23 @@
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/item_message"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/picker_message_height"
+ android:textAppearance="@style/PickerMessage"
+ android:gravity="center"
+/>
\ No newline at end of file
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
index b643c77..5380838 100644
--- a/res/values-night/colors.xml
+++ b/res/values-night/colors.xml
@@ -26,5 +26,6 @@
<color name="picker_date_header_text_color">@color/picker_default_white</color>
<color name="picker_toolbar_icon_color">#E8EAED</color>
<color name="picker_toolbar_chip_text_color">#E8EAED</color>
+ <color name="picker_message_text_color">#9AA0A6</color>
</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 243bd52..42be611 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -26,6 +26,7 @@
<color name="picker_background_color">@android:color/white</color>
<color name="picker_highlight_color">#E8F0FE</color>
<color name="picker_date_header_text_color">#3C4043</color>
+ <color name="picker_message_text_color">#5F6368</color>
<color name="picker_toolbar_icon_color">#3C4043</color>
<color name="picker_toolbar_chip_text_color">#5F6368</color>
<color name="photos_item_gradient_color">#42000000</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f4ee6a2..c0e8214 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -40,6 +40,8 @@
<dimen name="picker_date_header_height">56dp</dimen>
<dimen name="picker_date_header_padding">16dp</dimen>
+ <dimen name="picker_message_height">32dp</dimen>
+
<dimen name="picker_album_name_height">20dp</dimen>
<dimen name="picker_album_name_margin">8dp</dimen>
<dimen name="picker_album_item_count_height">16dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6228a2e..fb6a708 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -57,7 +57,7 @@
<!-- Text shown at the end of a list indicating that there are more items beyond the number currently displayed on the screen. [CHAR LIMIT=32] -->
<plurals name="permission_more_text">
- <item quantity="one">Plus <xliff:g id="count" example="42">^1</xliff:g> additional item</item>
+ <item quantity="one">Plus <xliff:g id="count" example="1">^1</xliff:g> additional item</item>
<item quantity="other">Plus <xliff:g id="count" example="42">^1</xliff:g> additional items</item>
</plurals>
@@ -88,6 +88,12 @@
<!-- Select button for PhotoPicker. [CHAR LIMIT=30] -->
<string name="select">Select</string>
+ <!-- Select up to max label message for PhotoPicker. [CHAR LIMIT=30] -->
+ <plurals name="select_up_to">
+ <item quantity="one">Select up to <xliff:g id="count" example="1">^1</xliff:g> item</item>
+ <item quantity="other">Select up to <xliff:g id="count" example="42">^1</xliff:g> items</item>
+ </plurals>
+
<!-- Recent header for PhotoPicker. [CHAR LIMIT=50] -->
<string name="recent">Recent</string>
diff --git a/res/values/styles_text.xml b/res/values/styles_text.xml
index 0f98f18..e89a43e 100644
--- a/res/values/styles_text.xml
+++ b/res/values/styles_text.xml
@@ -16,6 +16,10 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="PickerMessage" parent="@android:style/TextAppearance.Material.Caption">
+ <item name="android:textColor">@color/picker_message_text_color</item>
+ </style>
+
<style name="PickerDateHeader" parent="@android:style/TextAppearance.Material.Title">
<item name="android:textColor">@color/picker_date_header_text_color</item>
<item name="android:textSize">16sp</item>
diff --git a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
index b501709..b5c882d 100644
--- a/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
+++ b/src/com/android/providers/media/photopicker/PhotoPickerActivity.java
@@ -67,7 +67,12 @@
getSupportActionBar().setTitle("Photos & Videos");
mPickerViewModel = new ViewModelProvider(this).get(PickerViewModel.class);
- mPickerViewModel.parseValuesFromIntent(getIntent());
+ try {
+ mPickerViewModel.parseValuesFromIntent(getIntent());
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Finish activity due to: " + e);
+ setCancelledResultAndFinishSelf();
+ }
// only add the fragment when the activity is created at first time
if (savedInstanceState == null) {
@@ -154,4 +159,9 @@
shouldReturnPickerUris));
finish();
}
+
+ private void setCancelledResultAndFinishSelf() {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
}
\ No newline at end of file
diff --git a/src/com/android/providers/media/photopicker/data/model/Item.java b/src/com/android/providers/media/photopicker/data/model/Item.java
index 4cd57b8..da282af 100644
--- a/src/com/android/providers/media/photopicker/data/model/Item.java
+++ b/src/com/android/providers/media/photopicker/data/model/Item.java
@@ -71,6 +71,7 @@
private boolean mIsVideo;
private boolean mIsGif;
private boolean mIsDate;
+ private boolean mIsMessage;
private Item() {}
@@ -111,6 +112,10 @@
return mIsDate;
}
+ public boolean isMessage() {
+ return mIsMessage;
+ }
+
public Uri getContentUri() {
return mUri;
}
@@ -142,6 +147,15 @@
}
/**
+ * Return a message item.
+ */
+ public static Item createMessageItem() {
+ final Item item = new Item();
+ item.mIsMessage = true;
+ return item;
+ }
+
+ /**
* Return the date item. If dateTaken is 0, it is a recent item.
* @param dateTaken the time of date taken. The unit is in milliseconds since
* January 1, 1970 00:00:00.0 UTC.
diff --git a/src/com/android/providers/media/photopicker/ui/MessageHolder.java b/src/com/android/providers/media/photopicker/ui/MessageHolder.java
new file mode 100644
index 0000000..151aed1
--- /dev/null
+++ b/src/com/android/providers/media/photopicker/ui/MessageHolder.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 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.providers.media.photopicker.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.android.providers.media.R;
+
+/**
+ * ViewHolder of a message within a RecyclerView.
+ */
+public class MessageHolder extends BaseViewHolder {
+ private TextView mMessage;
+ private int mMaxCount;
+ public MessageHolder(@NonNull Context context, @NonNull ViewGroup parent, int maxCount) {
+ super(context, parent, R.layout.item_message);
+ mMaxCount = maxCount;
+ mMessage = (TextView) itemView.findViewById(R.id.item_message);
+ }
+
+ @Override
+ public void bind() {
+ final Resources res = itemView.getContext().getResources();
+ final CharSequence quantityText = res.getQuantityText(R.plurals.select_up_to, mMaxCount);
+ final CharSequence message = TextUtils.expandTemplate(quantityText,
+ String.valueOf(mMaxCount));
+ mMessage.setText(message);
+ }
+}
diff --git a/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java b/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
index b78bae9..00a83bc 100644
--- a/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
+++ b/src/com/android/providers/media/photopicker/ui/PhotosTabAdapter.java
@@ -36,6 +36,7 @@
public static final int ITEM_TYPE_DATE_HEADER = 0;
private static final int ITEM_TYPE_PHOTO = 1;
+ private static final int ITEM_TYPE_MESSAGE = 2;
public static final int COLUMN_COUNT = 3;
@@ -57,6 +58,10 @@
if (viewType == ITEM_TYPE_DATE_HEADER) {
return new DateHeaderHolder(viewGroup.getContext(), viewGroup);
}
+ if (viewType == ITEM_TYPE_MESSAGE) {
+ return new MessageHolder(viewGroup.getContext(), viewGroup,
+ mPickerViewModel.getMaxSelectionLimit());
+ }
return new PhotoGridHolder(viewGroup.getContext(), viewGroup, mImageLoader,
mPickerViewModel.canSelectMultiple());
}
@@ -83,6 +88,9 @@
@Override
public int getItemViewType(int position) {
+ if (getItem(position).isMessage()) {
+ return ITEM_TYPE_MESSAGE;
+ }
if (getItem(position).isDate()) {
return ITEM_TYPE_DATE_HEADER;
}
diff --git a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
index bd46bc6..1a8f2ee 100644
--- a/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
+++ b/src/com/android/providers/media/photopicker/ui/PhotosTabFragment.java
@@ -18,6 +18,7 @@
import static com.android.providers.media.photopicker.ui.PhotosTabAdapter.COLUMN_COUNT;
import android.os.Bundle;
+import android.text.TextUtils;
import android.view.View;
import androidx.annotation.NonNull;
@@ -66,10 +67,12 @@
mPickerViewModel.deleteSelectedItem((Item) view.getTag());
} else {
if (!mPickerViewModel.isSelectionAllowed()) {
- // TODO(b/192823495): Use R.string for translations and mimeType filters.
- Snackbar.make(view,
- "Select up to " + mPickerViewModel.getMaxSelectionLimit() + " photos",
- Snackbar.LENGTH_SHORT).show();
+ final int maxCount = mPickerViewModel.getMaxSelectionLimit();
+ final CharSequence quantityText = getResources().getQuantityText(
+ R.plurals.select_up_to, maxCount);
+ final CharSequence message = TextUtils.expandTemplate(quantityText,
+ String.valueOf(maxCount));
+ Snackbar.make(view, message, Snackbar.LENGTH_SHORT).show();
return;
} else {
mPickerViewModel.addSelectedItem((Item) view.getTag());
diff --git a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
index 1b6ee3b..16aa8f9 100644
--- a/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
+++ b/src/com/android/providers/media/photopicker/viewmodel/PickerViewModel.java
@@ -26,6 +26,7 @@
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
@@ -63,6 +64,9 @@
private int mMaxSelectionLimit = DEFAULT_MAX_SELECTION_LIMIT;
// This is set to false when max selection limit is reached.
private boolean mIsSelectionAllowed = true;
+ // Show max label text view if and only if caller sets acceptable value for
+ // {@link MediaStore#EXTRA_PICK_IMAGES_MAX}
+ private boolean mShowMaxLabel = false;
public PickerViewModel(@NonNull Application application) {
super(application);
@@ -166,6 +170,10 @@
int recentSize = 0;
long currentDateTaken = 0;
+ // add max label message header item
+ if (mShowMaxLabel) {
+ items.add(Item.createMessageItem());
+ }
// add Recent date header
items.add(Item.createDateItem(0));
while (cursor.moveToNext()) {
@@ -219,7 +227,7 @@
/**
* Parse values from Intent and set corresponding fields
*/
- public void parseValuesFromIntent(Intent intent) {
+ public void parseValuesFromIntent(Intent intent) throws IllegalArgumentException {
mSelectMultiple = intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
final String mimeType = intent.getType();
@@ -227,12 +235,23 @@
mMimeTypeFilter = mimeType;
}
- final int max = intent.getIntExtra(MediaStore.EXTRA_PICK_IMAGES_MAX,
- DEFAULT_MAX_SELECTION_LIMIT);
- // Multi selection limit should always be greater than 1, and less than global max values
- // allowed to select.
- if (max > 1 && max <= DEFAULT_MAX_SELECTION_LIMIT) {
- mMaxSelectionLimit = max;
+ final Bundle extras = intent.getExtras();
+ final boolean isExtraPickImagesMaxSet =
+ extras != null && extras.containsKey(MediaStore.EXTRA_PICK_IMAGES_MAX);
+ // 1. Check EXTRA_PICK_IMAGES_MAX only if EXTRA_ALLOW_MULTIPLE is set.
+ // 2. Do not show "Set up to max items" message if EXTRA_PICK_IMAGES_MAX is not set
+ if (mSelectMultiple && isExtraPickImagesMaxSet) {
+ final int extraMax = intent.getIntExtra(MediaStore.EXTRA_PICK_IMAGES_MAX,
+ /* defaultValue */ -1);
+ // Multi selection max limit should always be greater than 0
+ if (extraMax <= 0) {
+ throw new IllegalArgumentException("Invalid EXTRA_PICK_IMAGES_MAX value");
+ }
+ // Multi selection limit should always be less than global max values allowed to select.
+ if (extraMax <= DEFAULT_MAX_SELECTION_LIMIT) {
+ mMaxSelectionLimit = extraMax;
+ }
+ mShowMaxLabel = true;
}
}
diff --git a/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java b/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java
index cb2b9ef..70bd7a6 100644
--- a/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java
+++ b/tests/src/com/android/providers/media/photopicker/data/model/ItemTest.java
@@ -52,6 +52,12 @@
assertThat(item.getMimeType()).isEqualTo(mimeType);
assertThat(item.getVolumeName()).isEqualTo(volumeName);
assertThat(item.getDuration()).isEqualTo(duration);
+
+ assertThat(item.isMessage()).isFalse();
+ assertThat(item.isDate()).isFalse();
+ assertThat(item.isImage()).isTrue();
+ assertThat(item.isVideo()).isFalse();
+ assertThat(item.isGif()).isFalse();
}
@Test
@@ -66,6 +72,10 @@
dateTaken, duration);
assertThat(item.isImage()).isTrue();
+ assertThat(item.isMessage()).isFalse();
+ assertThat(item.isDate()).isFalse();
+ assertThat(item.isVideo()).isFalse();
+ assertThat(item.isGif()).isFalse();
}
@Test
@@ -80,6 +90,10 @@
dateTaken, duration);
assertThat(item.isVideo()).isTrue();
+ assertThat(item.isMessage()).isFalse();
+ assertThat(item.isDate()).isFalse();
+ assertThat(item.isImage()).isFalse();
+ assertThat(item.isGif()).isFalse();
}
@Test
@@ -94,6 +108,10 @@
dateTaken, duration);
assertThat(item.isGif()).isTrue();
+ assertThat(item.isMessage()).isFalse();
+ assertThat(item.isDate()).isFalse();
+ assertThat(item.isImage()).isFalse();
+ assertThat(item.isVideo()).isFalse();
}
@Test
@@ -106,6 +124,17 @@
assertThat(item.isDate()).isTrue();
}
+ @Test
+ public void testCreateMessageItem() {
+ final Item item = Item.createMessageItem();
+
+ assertThat(item.isMessage()).isTrue();
+ assertThat(item.isDate()).isFalse();
+ assertThat(item.isImage()).isFalse();
+ assertThat(item.isVideo()).isFalse();
+ assertThat(item.isGif()).isFalse();
+ }
+
private static Cursor generateCursorForItem(long id, String mimeType,
String displayName, String volumeName, long dateTaken, long duration) {
final MatrixCursor cursor = new MatrixCursor(