summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/res/layout/chooser_grid_preview_file.xml10
-rw-r--r--java/res/layout/chooser_grid_preview_image.xml16
-rw-r--r--java/res/layout/chooser_grid_preview_text.xml10
-rw-r--r--java/res/layout/chooser_headline_row.xml57
-rw-r--r--java/res/values/strings.xml30
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java4
-rw-r--r--java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java42
-rw-r--r--java/src/com/android/intentresolver/contentpreview/ContentPreviewUi.java17
-rw-r--r--java/src/com/android/intentresolver/contentpreview/FileContentPreviewUi.java9
-rw-r--r--java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt35
-rw-r--r--java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt67
-rw-r--r--java/src/com/android/intentresolver/contentpreview/ImageContentPreviewUi.java21
-rw-r--r--java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java9
-rw-r--r--java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java100
-rw-r--r--java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java39
-rw-r--r--java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt34
-rw-r--r--java/tests/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImplTest.kt49
17 files changed, 454 insertions, 95 deletions
diff --git a/java/res/layout/chooser_grid_preview_file.xml b/java/res/layout/chooser_grid_preview_file.xml
index 6ba06b3d..036c5318 100644
--- a/java/res/layout/chooser_grid_preview_file.xml
+++ b/java/res/layout/chooser_grid_preview_file.xml
@@ -27,6 +27,8 @@
android:paddingBottom="@dimen/chooser_view_spacing"
android:background="?android:attr/colorBackground">
+ <include layout="@layout/chooser_headline_row" />
+
<LinearLayout
android:layout_width="@dimen/chooser_preview_width"
android:layout_height="wrap_content"
@@ -69,14 +71,6 @@
android:textAppearance="@style/TextAppearance.ChooserDefault" />
</LinearLayout>
- <TextView
- android:id="@+id/reselection_action"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:gravity="center"
- style="@style/ReselectionAction" />
-
<ViewStub
android:id="@+id/action_row_stub"
android:layout_width="match_parent"
diff --git a/java/res/layout/chooser_grid_preview_image.xml b/java/res/layout/chooser_grid_preview_image.xml
index 1c0e4c2e..9ad594e8 100644
--- a/java/res/layout/chooser_grid_preview_image.xml
+++ b/java/res/layout/chooser_grid_preview_image.xml
@@ -24,13 +24,7 @@
android:orientation="vertical"
android:background="?android:attr/colorBackground">
- <CheckBox
- android:id="@+id/include_text_action"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="end"
- android:layout_marginEnd="@dimen/chooser_edge_margin_normal"
- android:visibility="gone" />
+ <include layout="@layout/chooser_headline_row" />
<LinearLayout
android:layout_width="match_parent"
@@ -59,14 +53,6 @@
android:textAppearance="@style/TextAppearance.ChooserDefault" />
</LinearLayout>
- <TextView
- android:id="@+id/reselection_action"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:gravity="center"
- style="@style/ReselectionAction" />
-
<ViewStub
android:id="@+id/action_row_stub"
android:layout_width="match_parent"
diff --git a/java/res/layout/chooser_grid_preview_text.xml b/java/res/layout/chooser_grid_preview_text.xml
index f521e31d..47beaa5a 100644
--- a/java/res/layout/chooser_grid_preview_text.xml
+++ b/java/res/layout/chooser_grid_preview_text.xml
@@ -26,6 +26,8 @@
android:orientation="vertical"
android:background="?android:attr/colorBackground">
+ <include layout="@layout/chooser_headline_row" />
+
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -82,14 +84,6 @@
android:focusable="true"/>
</RelativeLayout>
- <TextView
- android:id="@+id/reselection_action"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:gravity="center"
- style="@style/ReselectionAction" />
-
<ViewStub
android:id="@+id/action_row_stub"
android:layout_width="match_parent"
diff --git a/java/res/layout/chooser_headline_row.xml b/java/res/layout/chooser_headline_row.xml
new file mode 100644
index 00000000..9dfab892
--- /dev/null
+++ b/java/res/layout/chooser_headline_row.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 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.
+ -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="@dimen/chooser_edge_margin_normal"
+>
+ <TextView
+ android:id="@+id/headline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_alignParentStart="true"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@androidprv:string/config_headlineFontFamily"
+ android:textSize="18sp"
+ android:paddingBottom="16dp"
+ />
+
+ <TextView
+ android:id="@+id/reselection_action"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:visibility="gone"
+ android:paddingBottom="16dp"
+ style="@style/ReselectionAction" />
+
+ <!-- This is only relevant for image+text preview, but needs to be in this layout so it can
+ stay at the top if there's no reselection action. -->
+ <CheckBox
+ android:id="@+id/include_text_action"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/reselection_action"
+ android:layout_alignWithParentIfMissing="true"
+ android:visibility="gone" />
+
+</RelativeLayout>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index d1c97c7e..f38666e4 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -137,6 +137,36 @@
}
</string>
+ <!-- Title atop a sharing UI indicating that text is being shared [CHAR_LIMIT=25] -->
+ <string name="sharing_text">Sharing text</string>
+ <!-- Title atop a sharing UI indicating that a link (URL) is being shared [CHAR_LIMIT=25] -->
+ <string name="sharing_link">Sharing link</string>
+ <!-- Title atop a sharing UI indicating that some images are being shared [CHAR_LIMIT=25] -->
+ <string name="sharing_images">{count, plural,
+ =1 {Sharing image}
+ other {Sharing # images}
+ }
+ </string>
+ <!-- Title atop a sharing UI indicating that some videos are being shared [CHAR_LIMIT=25] -->
+ <string name="sharing_videos">{count, plural,
+ =1 {Sharing video}
+ other {Sharing # videos}
+ }
+ </string>
+ <!-- Title atop a sharing UI indicating that some number of items are being shared
+ (for example: sharing a mixture of photos and videos [CHAR_LIMIT=25] -->
+ <string name="sharing_items">{count, plural,
+ =1 {Sharing # item}
+ other {Sharing # items}
+ }
+ </string>
+ <!-- Title atop a sharing UI indicating that an image is being shared with text attached.
+ [CHAR_LIMIT=25] -->
+ <string name="sharing_image_with_text">Sharing image with text</string>
+ <!-- Title atop a sharing UI indicating that an image is being shared with a link (URL)
+ attached. [CHAR_LIMIT=25] -->
+ <string name="sharing_image_with_link">Sharing image with link</string>
+
<!-- ChooserActivity - No direct share targets are available. [CHAR LIMIT=NONE] -->
<string name="chooser_no_direct_share_targets">No recommended people to share with</string>
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index dc61c725..4565aa0b 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -84,6 +84,7 @@ import com.android.intentresolver.chooser.DisplayResolveInfo;
import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
import com.android.intentresolver.chooser.TargetInfo;
import com.android.intentresolver.contentpreview.ChooserContentPreviewUi;
+import com.android.intentresolver.contentpreview.HeadlineGeneratorImpl;
import com.android.intentresolver.flags.FeatureFlagRepository;
import com.android.intentresolver.flags.FeatureFlagRepositoryFactory;
import com.android.intentresolver.flags.Flags;
@@ -291,7 +292,8 @@ public class ChooserActivity extends ResolverActivity implements
createPreviewImageLoader(),
createChooserActionFactory(),
mEnterTransitionAnimationDelegate,
- mFeatureFlagRepository);
+ mFeatureFlagRepository,
+ new HeadlineGeneratorImpl(this));
setAdditionalTargets(mChooserRequest.getAdditionalTargets());
diff --git a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java
index de454cfd..6892b32c 100644
--- a/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUi.java
@@ -103,7 +103,8 @@ public final class ChooserContentPreviewUi {
ImageLoader imageLoader,
ActionFactory actionFactory,
TransitionElementStatusCallback transitionElementStatusCallback,
- FeatureFlagRepository featureFlagRepository) {
+ FeatureFlagRepository featureFlagRepository,
+ HeadlineGenerator headlineGenerator) {
mContentPreviewUi = createContentPreview(
targetIntent,
@@ -112,7 +113,8 @@ public final class ChooserContentPreviewUi {
imageLoader,
actionFactory,
transitionElementStatusCallback,
- featureFlagRepository);
+ featureFlagRepository,
+ headlineGenerator);
if (mContentPreviewUi.getType() != CONTENT_PREVIEW_IMAGE) {
transitionElementStatusCallback.onAllTransitionElementsReady();
}
@@ -125,7 +127,8 @@ public final class ChooserContentPreviewUi {
ImageLoader imageLoader,
ActionFactory actionFactory,
TransitionElementStatusCallback transitionElementStatusCallback,
- FeatureFlagRepository featureFlagRepository) {
+ FeatureFlagRepository featureFlagRepository,
+ HeadlineGenerator headlineGenerator) {
/* In {@link android.content.Intent#getType}, the app may specify a very general mime type
* that broadly covers all data being shared, such as {@literal *}/* when sending an image
@@ -139,12 +142,20 @@ public final class ChooserContentPreviewUi {
if (!(isSend || isSendMultiple)
|| (type != null && ClipDescription.compareMimeTypes(type, "text/*"))) {
return createTextPreview(
- targetIntent, actionFactory, imageLoader, featureFlagRepository);
+ targetIntent,
+ actionFactory,
+ imageLoader,
+ featureFlagRepository,
+ headlineGenerator);
}
List<Uri> uris = extractContentUris(targetIntent);
if (uris.isEmpty()) {
return createTextPreview(
- targetIntent, actionFactory, imageLoader, featureFlagRepository);
+ targetIntent,
+ actionFactory,
+ imageLoader,
+ featureFlagRepository,
+ headlineGenerator);
}
ArrayList<FileInfo> files = new ArrayList<>(uris.size());
int previewCount = readFileInfo(contentResolver, typeClassifier, uris, files);
@@ -153,7 +164,8 @@ public final class ChooserContentPreviewUi {
files,
actionFactory,
imageLoader,
- featureFlagRepository);
+ featureFlagRepository,
+ headlineGenerator);
}
if (featureFlagRepository.isEnabled(Flags.SHARESHEET_SCROLLABLE_IMAGE_PREVIEW)) {
return new UnifiedContentPreviewUi(
@@ -163,14 +175,16 @@ public final class ChooserContentPreviewUi {
imageLoader,
typeClassifier,
transitionElementStatusCallback,
- featureFlagRepository);
+ featureFlagRepository,
+ headlineGenerator);
}
if (previewCount < uris.size()) {
return new FileContentPreviewUi(
files,
actionFactory,
imageLoader,
- featureFlagRepository);
+ featureFlagRepository,
+ headlineGenerator);
}
// The legacy (3-image) image preview is on it's way out and it's unlikely that we'd end up
// here. To preserve the legacy behavior, before using it, check that all uris are images.
@@ -180,7 +194,8 @@ public final class ChooserContentPreviewUi {
files,
actionFactory,
imageLoader,
- featureFlagRepository);
+ featureFlagRepository,
+ headlineGenerator);
}
}
return new ImageContentPreviewUi(
@@ -192,7 +207,8 @@ public final class ChooserContentPreviewUi {
actionFactory,
imageLoader,
transitionElementStatusCallback,
- featureFlagRepository);
+ featureFlagRepository,
+ headlineGenerator);
}
public int getPreferredContentPreview() {
@@ -307,7 +323,8 @@ public final class ChooserContentPreviewUi {
Intent targetIntent,
ChooserContentPreviewUi.ActionFactory actionFactory,
ImageLoader imageLoader,
- FeatureFlagRepository featureFlagRepository) {
+ FeatureFlagRepository featureFlagRepository,
+ HeadlineGenerator headlineGenerator) {
CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
ClipData previewData = targetIntent.getClipData();
@@ -324,7 +341,8 @@ public final class ChooserContentPreviewUi {
previewThumbnail,
actionFactory,
imageLoader,
- featureFlagRepository);
+ featureFlagRepository,
+ headlineGenerator);
}
private static List<Uri> extractContentUris(Intent targetIntent) {
diff --git a/java/src/com/android/intentresolver/contentpreview/ContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ContentPreviewUi.java
index 79444b4e..a6bc2164 100644
--- a/java/src/com/android/intentresolver/contentpreview/ContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/ContentPreviewUi.java
@@ -24,6 +24,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -114,7 +115,21 @@ abstract class ContentPreviewUi {
fadeAnim.start();
}
- protected static void displayPayloadReselectionAction(
+ protected static void displayHeadline(ViewGroup layout, String headline) {
+ if (layout != null) {
+ TextView titleView = layout.findViewById(R.id.headline);
+ if (titleView != null) {
+ if (!TextUtils.isEmpty(headline)) {
+ titleView.setText(headline);
+ titleView.setVisibility(View.VISIBLE);
+ } else {
+ titleView.setVisibility(View.GONE);
+ }
+ }
+ }
+ }
+
+ protected static void displayModifyShareAction(
ViewGroup layout,
ChooserContentPreviewUi.ActionFactory actionFactory,
FeatureFlagRepository featureFlagRepository) {
diff --git a/java/src/com/android/intentresolver/contentpreview/FileContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/FileContentPreviewUi.java
index 2c5def8b..52e20cf0 100644
--- a/java/src/com/android/intentresolver/contentpreview/FileContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/FileContentPreviewUi.java
@@ -45,16 +45,19 @@ class FileContentPreviewUi extends ContentPreviewUi {
private final ChooserContentPreviewUi.ActionFactory mActionFactory;
private final ImageLoader mImageLoader;
private final FeatureFlagRepository mFeatureFlagRepository;
+ private final HeadlineGenerator mHeadlineGenerator;
FileContentPreviewUi(
List<FileInfo> files,
ChooserContentPreviewUi.ActionFactory actionFactory,
ImageLoader imageLoader,
- FeatureFlagRepository featureFlagRepository) {
+ FeatureFlagRepository featureFlagRepository,
+ HeadlineGenerator headlineGenerator) {
mFiles = files;
mActionFactory = actionFactory;
mImageLoader = imageLoader;
mFeatureFlagRepository = featureFlagRepository;
+ mHeadlineGenerator = headlineGenerator;
}
@Override
@@ -65,7 +68,7 @@ class FileContentPreviewUi extends ContentPreviewUi {
@Override
public ViewGroup display(Resources resources, LayoutInflater layoutInflater, ViewGroup parent) {
ViewGroup layout = displayInternal(resources, layoutInflater, parent);
- displayPayloadReselectionAction(layout, mActionFactory, mFeatureFlagRepository);
+ displayModifyShareAction(layout, mActionFactory, mFeatureFlagRepository);
return layout;
}
@@ -77,6 +80,8 @@ class FileContentPreviewUi extends ContentPreviewUi {
final int uriCount = mFiles.size();
+ displayHeadline(contentPreviewLayout, mHeadlineGenerator.getItemsHeadline(mFiles.size()));
+
if (uriCount == 0) {
contentPreviewLayout.setVisibility(View.GONE);
Log.i(TAG, "Appears to be no uris available in EXTRA_STREAM,"
diff --git a/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt b/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt
new file mode 100644
index 00000000..e32bb5c4
--- /dev/null
+++ b/java/src/com/android/intentresolver/contentpreview/HeadlineGenerator.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.intentresolver.contentpreview
+
+private const val PLURALS_COUNT = "count"
+
+/**
+ * HeadlineGenerator generates the text to show at the top of the sharesheet as a brief
+ * description of the content being shared.
+ */
+interface HeadlineGenerator {
+ fun getTextHeadline(text: CharSequence): String
+
+ fun getImageWithTextHeadline(text: CharSequence): String
+
+ fun getImagesHeadline(count: Int): String
+
+ fun getVideosHeadline(count: Int): String
+
+ fun getItemsHeadline(count: Int): String
+}
diff --git a/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt b/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt
new file mode 100644
index 00000000..ae44294c
--- /dev/null
+++ b/java/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImpl.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.intentresolver.contentpreview
+
+import android.content.Context
+import com.android.intentresolver.R
+import android.util.PluralsMessageFormatter
+
+private const val PLURALS_COUNT = "count"
+
+/**
+ * HeadlineGenerator generates the text to show at the top of the sharesheet as a brief
+ * description of the content being shared.
+ */
+class HeadlineGeneratorImpl(private val context: Context) : HeadlineGenerator {
+ override fun getTextHeadline(text: CharSequence): String {
+ if (text.toString().isHttpUri()) {
+ return context.getString(R.string.sharing_link)
+ }
+ return context.getString(R.string.sharing_text)
+ }
+
+ override fun getImageWithTextHeadline(text: CharSequence): String {
+ if (text.toString().isHttpUri()) {
+ return context.getString(R.string.sharing_image_with_link)
+ }
+ return context.getString(R.string.sharing_image_with_text)
+ }
+
+ override fun getImagesHeadline(count: Int): String {
+ return PluralsMessageFormatter.format(
+ context.resources,
+ mapOf(PLURALS_COUNT to count),
+ R.string.sharing_images
+ )
+ }
+
+ override fun getVideosHeadline(count: Int): String {
+ return PluralsMessageFormatter.format(
+ context.resources,
+ mapOf(PLURALS_COUNT to count),
+ R.string.sharing_videos
+ )
+ }
+
+ override fun getItemsHeadline(count: Int): String {
+ return PluralsMessageFormatter.format(
+ context.resources,
+ mapOf(PLURALS_COUNT to count),
+ R.string.sharing_items
+ )
+ }
+}
diff --git a/java/src/com/android/intentresolver/contentpreview/ImageContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/ImageContentPreviewUi.java
index 5f3bdf40..f2c0564a 100644
--- a/java/src/com/android/intentresolver/contentpreview/ImageContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/ImageContentPreviewUi.java
@@ -54,6 +54,7 @@ class ImageContentPreviewUi extends ContentPreviewUi {
private final ImageLoader mImageLoader;
private final TransitionElementStatusCallback mTransitionElementStatusCallback;
private final FeatureFlagRepository mFeatureFlagRepository;
+ private final HeadlineGenerator mHeadlineGenerator;
ImageContentPreviewUi(
List<Uri> imageUris,
@@ -61,13 +62,15 @@ class ImageContentPreviewUi extends ContentPreviewUi {
ChooserContentPreviewUi.ActionFactory actionFactory,
ImageLoader imageLoader,
TransitionElementStatusCallback transitionElementStatusCallback,
- FeatureFlagRepository featureFlagRepository) {
+ FeatureFlagRepository featureFlagRepository,
+ HeadlineGenerator headlineGenerator) {
mImageUris = imageUris;
mText = text;
mActionFactory = actionFactory;
mImageLoader = imageLoader;
mTransitionElementStatusCallback = transitionElementStatusCallback;
mFeatureFlagRepository = featureFlagRepository;
+ mHeadlineGenerator = headlineGenerator;
mImageLoader.prePopulate(mImageUris);
}
@@ -80,7 +83,7 @@ class ImageContentPreviewUi extends ContentPreviewUi {
@Override
public ViewGroup display(Resources resources, LayoutInflater layoutInflater, ViewGroup parent) {
ViewGroup layout = displayInternal(layoutInflater, parent);
- displayPayloadReselectionAction(layout, mActionFactory, mFeatureFlagRepository);
+ displayModifyShareAction(layout, mActionFactory, mFeatureFlagRepository);
return layout;
}
@@ -113,6 +116,8 @@ class ImageContentPreviewUi extends ContentPreviewUi {
imagePreview.setTransitionElementStatusCallback(mTransitionElementStatusCallback);
imagePreview.setImages(mImageUris, mImageLoader);
+ updateHeadline(contentPreviewLayout);
+
return contentPreviewLayout;
}
@@ -140,6 +145,17 @@ class ImageContentPreviewUi extends ContentPreviewUi {
com.android.internal.R.id.content_preview_image_area);
}
+ private void updateHeadline(ViewGroup contentPreview) {
+ CheckBox includeTextCheckbox = contentPreview.requireViewById(R.id.include_text_action);
+ if (includeTextCheckbox.getVisibility() == View.VISIBLE
+ && includeTextCheckbox.isChecked()) {
+ displayHeadline(contentPreview, mHeadlineGenerator.getImageWithTextHeadline(mText));
+ } else {
+ displayHeadline(
+ contentPreview, mHeadlineGenerator.getImagesHeadline(mImageUris.size()));
+ }
+ }
+
private void setTextInImagePreviewVisibility(
ViewGroup contentPreview, ChooserContentPreviewUi.ActionFactory actionFactory) {
int visibility = mFeatureFlagRepository.isEnabled(Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW)
@@ -169,6 +185,7 @@ class ImageContentPreviewUi extends ContentPreviewUi {
TransitionManager.beginDelayedTransition((ViewGroup) textView.getParent());
textView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
shareTextAction.accept(!isChecked);
+ updateHeadline(contentPreview);
});
}
actionView.setVisibility(visibility);
diff --git a/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java
index e143954e..d0cba5bb 100644
--- a/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/TextContentPreviewUi.java
@@ -46,6 +46,7 @@ class TextContentPreviewUi extends ContentPreviewUi {
private final ImageLoader mImageLoader;
private final ChooserContentPreviewUi.ActionFactory mActionFactory;
private final FeatureFlagRepository mFeatureFlagRepository;
+ private final HeadlineGenerator mHeadlineGenerator;
TextContentPreviewUi(
@Nullable CharSequence sharingText,
@@ -53,13 +54,15 @@ class TextContentPreviewUi extends ContentPreviewUi {
@Nullable Uri previewThumbnail,
ChooserContentPreviewUi.ActionFactory actionFactory,
ImageLoader imageLoader,
- FeatureFlagRepository featureFlagRepository) {
+ FeatureFlagRepository featureFlagRepository,
+ HeadlineGenerator headlineGenerator) {
mSharingText = sharingText;
mPreviewTitle = previewTitle;
mPreviewThumbnail = previewThumbnail;
mImageLoader = imageLoader;
mActionFactory = actionFactory;
mFeatureFlagRepository = featureFlagRepository;
+ mHeadlineGenerator = headlineGenerator;
}
@Override
@@ -70,7 +73,7 @@ class TextContentPreviewUi extends ContentPreviewUi {
@Override
public ViewGroup display(Resources resources, LayoutInflater layoutInflater, ViewGroup parent) {
ViewGroup layout = displayInternal(layoutInflater, parent);
- displayPayloadReselectionAction(layout, mActionFactory, mFeatureFlagRepository);
+ displayModifyShareAction(layout, mActionFactory, mFeatureFlagRepository);
return layout;
}
@@ -122,6 +125,8 @@ class TextContentPreviewUi extends ContentPreviewUi {
bitmap));
}
+ displayHeadline(contentPreviewLayout, mHeadlineGenerator.getTextHeadline(mSharingText));
+
return contentPreviewLayout;
}
diff --git a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java
index c4e6feb7..2d2ae52b 100644
--- a/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java
+++ b/java/src/com/android/intentresolver/contentpreview/UnifiedContentPreviewUi.java
@@ -55,6 +55,7 @@ class UnifiedContentPreviewUi extends ContentPreviewUi {
private final MimeTypeClassifier mTypeClassifier;
private final TransitionElementStatusCallback mTransitionElementStatusCallback;
private final FeatureFlagRepository mFeatureFlagRepository;
+ private final HeadlineGenerator mHeadlineGenerator;
UnifiedContentPreviewUi(
List<FileInfo> files,
@@ -63,7 +64,8 @@ class UnifiedContentPreviewUi extends ContentPreviewUi {
ImageLoader imageLoader,
MimeTypeClassifier typeClassifier,
TransitionElementStatusCallback transitionElementStatusCallback,
- FeatureFlagRepository featureFlagRepository) {
+ FeatureFlagRepository featureFlagRepository,
+ HeadlineGenerator headlineGenerator) {
mFiles = files;
mText = text;
mActionFactory = actionFactory;
@@ -71,6 +73,7 @@ class UnifiedContentPreviewUi extends ContentPreviewUi {
mTypeClassifier = typeClassifier;
mTransitionElementStatusCallback = transitionElementStatusCallback;
mFeatureFlagRepository = featureFlagRepository;
+ mHeadlineGenerator = headlineGenerator;
mImageLoader.prePopulate(mFiles.stream()
.map(FileInfo::getPreviewUri)
@@ -86,7 +89,7 @@ class UnifiedContentPreviewUi extends ContentPreviewUi {
@Override
public ViewGroup display(Resources resources, LayoutInflater layoutInflater, ViewGroup parent) {
ViewGroup layout = displayInternal(layoutInflater, parent);
- displayPayloadReselectionAction(layout, mActionFactory, mFeatureFlagRepository);
+ displayModifyShareAction(layout, mActionFactory, mFeatureFlagRepository);
return layout;
}
@@ -115,20 +118,47 @@ class UnifiedContentPreviewUi extends ContentPreviewUi {
return contentPreviewLayout;
}
- setTextInImagePreviewVisibility(contentPreviewLayout, mActionFactory);
imagePreview.setTransitionElementStatusCallback(mTransitionElementStatusCallback);
- List<ScrollableImagePreviewView.Preview> previews = mFiles.stream()
- .filter(fileInfo -> fileInfo.getPreviewUri() != null)
- .map(fileInfo ->
- new ScrollableImagePreviewView.Preview(
- getPreviewType(fileInfo.getMimeType()),
- fileInfo.getPreviewUri()))
- .toList();
+
+ List<ScrollableImagePreviewView.Preview> previews = new ArrayList<>();
+ boolean allImages = !mFiles.isEmpty();
+ boolean allVideos = !mFiles.isEmpty();
+ for (FileInfo fileInfo : mFiles) {
+ ScrollableImagePreviewView.PreviewType previewType =
+ getPreviewType(fileInfo.getMimeType());
+ allImages = allImages && previewType == ScrollableImagePreviewView.PreviewType.Image;
+ allVideos = allVideos && previewType == ScrollableImagePreviewView.PreviewType.Video;
+
+ if (fileInfo.getPreviewUri() != null) {
+ previews.add(new ScrollableImagePreviewView.Preview(
+ previewType,
+ fileInfo.getPreviewUri()));
+ }
+ }
imagePreview.setPreviews(
previews,
mFiles.size() - previews.size(),
mImageLoader);
+ if (mFeatureFlagRepository.isEnabled(Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW)
+ && !TextUtils.isEmpty(mText)
+ && mFiles.size() == 1
+ && allImages) {
+ setTextInImagePreviewVisibility(contentPreviewLayout, mActionFactory);
+ updateTextWithImageHeadline(contentPreviewLayout);
+ } else {
+ if (allImages) {
+ displayHeadline(
+ contentPreviewLayout, mHeadlineGenerator.getImagesHeadline(mFiles.size()));
+ } else if (allVideos) {
+ displayHeadline(
+ contentPreviewLayout, mHeadlineGenerator.getVideosHeadline(mFiles.size()));
+ } else {
+ displayHeadline(
+ contentPreviewLayout, mHeadlineGenerator.getItemsHeadline(mFiles.size()));
+ }
+ }
+
return contentPreviewLayout;
}
@@ -155,38 +185,42 @@ class UnifiedContentPreviewUi extends ContentPreviewUi {
return previewLayout.findViewById(R.id.scrollable_image_preview);
}
+ private void updateTextWithImageHeadline(ViewGroup contentPreview) {
+ CheckBox actionView = contentPreview.requireViewById(R.id.include_text_action);
+ if (actionView.getVisibility() == View.VISIBLE && actionView.isChecked()) {
+ displayHeadline(contentPreview, mHeadlineGenerator.getImageWithTextHeadline(mText));
+ } else {
+ displayHeadline(
+ contentPreview, mHeadlineGenerator.getImagesHeadline(mFiles.size()));
+ }
+ }
+
private void setTextInImagePreviewVisibility(
ViewGroup contentPreview, ChooserContentPreviewUi.ActionFactory actionFactory) {
- int visibility = mFeatureFlagRepository.isEnabled(Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW)
- && !TextUtils.isEmpty(mText)
- ? View.VISIBLE
- : View.GONE;
-
final TextView textView = contentPreview
.requireViewById(com.android.internal.R.id.content_preview_text);
CheckBox actionView = contentPreview
.requireViewById(R.id.include_text_action);
- textView.setVisibility(visibility);
- boolean isLink = visibility == View.VISIBLE && HttpUriMatcher.isHttpUri(mText.toString());
+ textView.setVisibility(View.VISIBLE);
+ boolean isLink = HttpUriMatcher.isHttpUri(mText.toString());
textView.setAutoLinkMask(isLink ? Linkify.WEB_URLS : 0);
textView.setText(mText);
- if (visibility == View.VISIBLE) {
- final int[] actionLabels = isLink
- ? new int[] { R.string.include_link, R.string.exclude_link }
- : new int[] { R.string.include_text, R.string.exclude_text };
- final Consumer<Boolean> shareTextAction = actionFactory.getExcludeSharedTextAction();
- actionView.setChecked(true);
- actionView.setText(actionLabels[1]);
- shareTextAction.accept(false);
- actionView.setOnCheckedChangeListener((view, isChecked) -> {
- view.setText(actionLabels[isChecked ? 1 : 0]);
- TransitionManager.beginDelayedTransition((ViewGroup) textView.getParent());
- textView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
- shareTextAction.accept(!isChecked);
- });
- }
- actionView.setVisibility(visibility);
+ final int[] actionLabels = isLink
+ ? new int[] { R.string.include_link, R.string.exclude_link }
+ : new int[] { R.string.include_text, R.string.exclude_text };
+ final Consumer<Boolean> shareTextAction = actionFactory.getExcludeSharedTextAction();
+ actionView.setChecked(true);
+ actionView.setText(actionLabels[1]);
+ shareTextAction.accept(false);
+ actionView.setOnCheckedChangeListener((view, isChecked) -> {
+ view.setText(actionLabels[isChecked ? 1 : 0]);
+ TransitionManager.beginDelayedTransition((ViewGroup) textView.getParent());
+ textView.setVisibility(isChecked ? View.VISIBLE : View.GONE);
+ shareTextAction.accept(!isChecked);
+ updateTextWithImageHeadline(contentPreview);
+ });
+ actionView.setVisibility(View.VISIBLE);
}
private ScrollableImagePreviewView.PreviewType getPreviewType(String mimeType) {
diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
index 7837a39d..6744d625 100644
--- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
+++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java
@@ -26,6 +26,7 @@ import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.hasSibling;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
@@ -98,6 +99,7 @@ import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.espresso.matcher.BoundedDiagnosingMatcher;
+import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
@@ -1074,6 +1076,43 @@ public class UnbundledChooserActivityTest {
}
@Test
+ @RequireFeatureFlags(
+ flags = { Flags.SHARESHEET_IMAGE_AND_TEXT_PREVIEW_NAME },
+ values = { true })
+ public void testNoTextPreviewWhenTextIsSharedWithMultipleImages() {
+ final Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
+ + R.drawable.test320x240);
+ final String sharedText = "text-" + System.currentTimeMillis();
+
+ ArrayList<Uri> uris = new ArrayList<>();
+ uris.add(uri);
+ uris.add(uri);
+
+ Intent sendIntent = createSendUriIntentWithPreview(uris);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, sharedText);
+ ChooserActivityOverrideData.getInstance().previewThumbnail = createBitmap();
+ ChooserActivityOverrideData.getInstance().isImageType = true;
+
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(
+ ChooserActivityOverrideData
+ .getInstance()
+ .resolverListController
+ .getResolversForIntentAsUser(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class),
+ Mockito.any(UserHandle.class)))
+ .thenReturn(resolvedComponentInfos);
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+ onView(withId(com.android.internal.R.id.content_preview_text))
+ .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
+ }
+
+ @Test
public void testOnCreateLogging() {
Intent sendIntent = createSendTextIntent();
sendIntent.setType(TEST_MIME_TYPE);
diff --git a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt
index 58b8a21d..82bf94c4 100644
--- a/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt
+++ b/java/tests/src/com/android/intentresolver/contentpreview/ChooserContentPreviewUiTest.kt
@@ -44,6 +44,7 @@ class ChooserContentPreviewUiTest {
private val imageClassifier = MimeTypeClassifier { mimeType ->
mimeType != null && ClipDescription.compareMimeTypes(mimeType, "image/*")
}
+ private val headlineGenerator = mock<HeadlineGenerator>()
private val imageLoader = object : ImageLoader {
override fun loadImage(uri: Uri, callback: Consumer<Bitmap?>) {
callback.accept(null)
@@ -76,7 +77,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT)
@@ -96,7 +98,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_TEXT)
@@ -117,7 +120,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE)
@@ -138,7 +142,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE)
@@ -160,7 +165,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE)
@@ -185,7 +191,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE)
@@ -208,7 +215,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE)
@@ -237,7 +245,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE)
@@ -266,7 +275,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE)
@@ -297,7 +307,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_IMAGE)
@@ -326,7 +337,8 @@ class ChooserContentPreviewUiTest {
imageLoader,
actionFactory,
transitionCallback,
- featureFlagRepository
+ featureFlagRepository,
+ headlineGenerator
)
assertThat(testSubject.preferredContentPreview)
.isEqualTo(ContentPreviewType.CONTENT_PREVIEW_FILE)
diff --git a/java/tests/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImplTest.kt b/java/tests/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImplTest.kt
new file mode 100644
index 00000000..9becce99
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/contentpreview/HeadlineGeneratorImplTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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.intentresolver.contentpreview
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Test
+import org.junit.runner.RunWith
+import com.google.common.truth.Truth.assertThat
+
+@RunWith(AndroidJUnit4::class)
+class HeadlineGeneratorImplTest {
+ @Test
+ fun testHeadlineGeneration() {
+ val generator = HeadlineGeneratorImpl(
+ InstrumentationRegistry.getInstrumentation().getTargetContext())
+ val str = "Some sting"
+ val url = "http://www.google.com"
+
+ assertThat(generator.getTextHeadline(str)).isEqualTo("Sharing text")
+ assertThat(generator.getTextHeadline(url)).isEqualTo("Sharing link")
+
+ assertThat(generator.getImageWithTextHeadline(str)).isEqualTo("Sharing image with text")
+ assertThat(generator.getImageWithTextHeadline(url)).isEqualTo("Sharing image with link")
+
+ assertThat(generator.getImagesHeadline(1)).isEqualTo("Sharing image")
+ assertThat(generator.getImagesHeadline(4)).isEqualTo("Sharing 4 images")
+
+ assertThat(generator.getVideosHeadline(1)).isEqualTo("Sharing video")
+ assertThat(generator.getVideosHeadline(4)).isEqualTo("Sharing 4 videos")
+
+ assertThat(generator.getItemsHeadline(1)).isEqualTo("Sharing 1 item")
+ assertThat(generator.getItemsHeadline(4)).isEqualTo("Sharing 4 items")
+ }
+}