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(