• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.intentresolver.contentpreview;
18 
19 import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_FILE;
20 import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_IMAGE;
21 
22 import android.content.res.Resources;
23 import android.net.Uri;
24 import android.text.util.Linkify;
25 import android.util.PluralsMessageFormatter;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.CheckBox;
30 import android.widget.ImageView;
31 import android.widget.TextView;
32 
33 import androidx.annotation.Nullable;
34 import androidx.lifecycle.Lifecycle;
35 
36 import com.android.intentresolver.R;
37 import com.android.intentresolver.widget.ActionRow;
38 import com.android.intentresolver.widget.ScrollableImagePreviewView;
39 
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.function.Consumer;
43 
44 /**
45  * FilesPlusTextContentPreviewUi is shown when the user is sending 1 or more files along with
46  * non-empty EXTRA_TEXT. The text can be toggled with a checkbox. If a single image file is being
47  * shared, it is shown in a preview (otherwise the headline summary is the sole indication of the
48  * file content).
49  */
50 class FilesPlusTextContentPreviewUi extends ContentPreviewUi {
51     private final Lifecycle mLifecycle;
52     @Nullable
53     private final String mIntentMimeType;
54     private final CharSequence mText;
55     private final ChooserContentPreviewUi.ActionFactory mActionFactory;
56     private final ImageLoader mImageLoader;
57     private final MimeTypeClassifier mTypeClassifier;
58     private final HeadlineGenerator mHeadlineGenerator;
59     private final boolean mIsSingleImage;
60     private final int mFileCount;
61     private ViewGroup mContentPreviewView;
62     private boolean mIsMetadataUpdated = false;
63     @Nullable
64     private Uri mFirstFilePreviewUri;
65     private boolean mAllImages;
66     private boolean mAllVideos;
67     // TODO(b/285309527): make this a flag
68     private static final boolean SHOW_TOGGLE_CHECKMARK = false;
69 
FilesPlusTextContentPreviewUi( Lifecycle lifecycle, boolean isSingleImage, int fileCount, CharSequence text, @Nullable String intentMimeType, ChooserContentPreviewUi.ActionFactory actionFactory, ImageLoader imageLoader, MimeTypeClassifier typeClassifier, HeadlineGenerator headlineGenerator)70     FilesPlusTextContentPreviewUi(
71             Lifecycle lifecycle,
72             boolean isSingleImage,
73             int fileCount,
74             CharSequence text,
75             @Nullable String intentMimeType,
76             ChooserContentPreviewUi.ActionFactory actionFactory,
77             ImageLoader imageLoader,
78             MimeTypeClassifier typeClassifier,
79             HeadlineGenerator headlineGenerator) {
80         if (isSingleImage && fileCount != 1) {
81             throw new IllegalArgumentException(
82                     "fileCount = " + fileCount + " and isSingleImage = true");
83         }
84         mLifecycle = lifecycle;
85         mIntentMimeType = intentMimeType;
86         mFileCount = fileCount;
87         mIsSingleImage = isSingleImage;
88         mText = text;
89         mActionFactory = actionFactory;
90         mImageLoader = imageLoader;
91         mTypeClassifier = typeClassifier;
92         mHeadlineGenerator = headlineGenerator;
93     }
94 
95     @Override
getType()96     public int getType() {
97         return mIsSingleImage ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
98     }
99 
100     @Override
display(Resources resources, LayoutInflater layoutInflater, ViewGroup parent)101     public ViewGroup display(Resources resources, LayoutInflater layoutInflater, ViewGroup parent) {
102         ViewGroup layout = displayInternal(layoutInflater, parent);
103         displayModifyShareAction(layout, mActionFactory);
104         return layout;
105     }
106 
updatePreviewMetadata(List<FileInfo> files)107     public void updatePreviewMetadata(List<FileInfo> files) {
108         boolean allImages = true;
109         boolean allVideos = true;
110         for (FileInfo fileInfo : files) {
111             ScrollableImagePreviewView.PreviewType previewType =
112                     getPreviewType(mTypeClassifier, fileInfo.getMimeType());
113             allImages = allImages && previewType == ScrollableImagePreviewView.PreviewType.Image;
114             allVideos = allVideos && previewType == ScrollableImagePreviewView.PreviewType.Video;
115         }
116         mAllImages = allImages;
117         mAllVideos = allVideos;
118         mFirstFilePreviewUri = files.isEmpty() ? null : files.get(0).getPreviewUri();
119         mIsMetadataUpdated = true;
120         if (mContentPreviewView != null) {
121             updateUiWithMetadata(mContentPreviewView);
122         }
123     }
124 
displayInternal(LayoutInflater layoutInflater, ViewGroup parent)125     private ViewGroup displayInternal(LayoutInflater layoutInflater, ViewGroup parent) {
126         mContentPreviewView = (ViewGroup) layoutInflater.inflate(
127                 R.layout.chooser_grid_preview_files_text, parent, false);
128 
129         final ActionRow actionRow =
130                 mContentPreviewView.findViewById(com.android.internal.R.id.chooser_action_row);
131         List<ActionRow.Action> actions = mActionFactory.createCustomActions();
132         actionRow.setActions(actions);
133 
134         if (!mIsSingleImage) {
135             mContentPreviewView.requireViewById(R.id.image_view).setVisibility(View.GONE);
136         }
137         prepareTextPreview(mContentPreviewView, mActionFactory);
138         if (mIsMetadataUpdated) {
139             updateUiWithMetadata(mContentPreviewView);
140         } else {
141             updateHeadline(
142                     mContentPreviewView,
143                     mFileCount,
144                     mTypeClassifier.isImageType(mIntentMimeType),
145                     mTypeClassifier.isVideoType(mIntentMimeType));
146         }
147 
148         return mContentPreviewView;
149     }
150 
updateUiWithMetadata(ViewGroup contentPreviewView)151     private void updateUiWithMetadata(ViewGroup contentPreviewView) {
152         updateHeadline(contentPreviewView, mFileCount, mAllImages, mAllVideos);
153 
154         ImageView imagePreview = mContentPreviewView.requireViewById(R.id.image_view);
155         if (mIsSingleImage && mFirstFilePreviewUri != null) {
156             mImageLoader.loadImage(
157                     mLifecycle,
158                     mFirstFilePreviewUri,
159                     bitmap -> {
160                         if (bitmap == null) {
161                             imagePreview.setVisibility(View.GONE);
162                         } else {
163                             imagePreview.setImageBitmap(bitmap);
164                         }
165                     });
166         } else {
167             imagePreview.setVisibility(View.GONE);
168         }
169     }
170 
updateHeadline( ViewGroup contentPreview, int fileCount, boolean allImages, boolean allVideos)171     private void updateHeadline(
172             ViewGroup contentPreview, int fileCount, boolean allImages, boolean allVideos) {
173         CheckBox includeText = contentPreview.requireViewById(R.id.include_text_action);
174         String headline;
175         if (includeText.getVisibility() == View.VISIBLE && includeText.isChecked()) {
176             if (allImages) {
177                 headline = mHeadlineGenerator.getImagesWithTextHeadline(mText, fileCount);
178             } else if (allVideos) {
179                 headline = mHeadlineGenerator.getVideosWithTextHeadline(mText, fileCount);
180             } else {
181                 headline = mHeadlineGenerator.getFilesWithTextHeadline(mText, fileCount);
182             }
183         } else {
184             if (allImages) {
185                 headline = mHeadlineGenerator.getImagesHeadline(fileCount);
186             } else if (allVideos) {
187                 headline = mHeadlineGenerator.getVideosHeadline(fileCount);
188             } else {
189                 headline = mHeadlineGenerator.getFilesHeadline(fileCount);
190             }
191         }
192 
193         displayHeadline(contentPreview, headline);
194     }
195 
prepareTextPreview( ViewGroup contentPreview, ChooserContentPreviewUi.ActionFactory actionFactory)196     private void prepareTextPreview(
197             ViewGroup contentPreview,
198             ChooserContentPreviewUi.ActionFactory actionFactory) {
199         final TextView textView = contentPreview.requireViewById(R.id.content_preview_text);
200         CheckBox includeText = contentPreview.requireViewById(R.id.include_text_action);
201         boolean isLink = HttpUriMatcher.isHttpUri(mText.toString());
202         textView.setAutoLinkMask(isLink ? Linkify.WEB_URLS : 0);
203         textView.setText(mText);
204 
205         final Consumer<Boolean> shareTextAction = actionFactory.getExcludeSharedTextAction();
206         includeText.setChecked(true);
207         includeText.setText(isLink ? R.string.include_link : R.string.include_text);
208         shareTextAction.accept(false);
209         includeText.setOnCheckedChangeListener((view, isChecked) -> {
210             if (isChecked) {
211                 textView.setText(mText);
212             } else {
213                 textView.setText(getNoTextString(contentPreview.getResources()));
214             }
215             shareTextAction.accept(!isChecked);
216             updateHeadline(contentPreview, mFileCount, mAllImages, mAllVideos);
217         });
218         if (SHOW_TOGGLE_CHECKMARK) {
219             includeText.setVisibility(View.VISIBLE);
220         }
221     }
222 
getNoTextString(Resources resources)223     private String getNoTextString(Resources resources) {
224         int stringResource;
225 
226         if (mAllImages) {
227             stringResource = R.string.sharing_images_only;
228         } else if (mAllVideos) {
229             stringResource = R.string.sharing_videos_only;
230         } else {
231             stringResource = R.string.sharing_files_only;
232         }
233 
234         HashMap<String, Object> params = new HashMap<>();
235         params.put("count", mFileCount);
236 
237         return PluralsMessageFormatter.format(
238                 resources,
239                 params,
240                 stringResource
241         );
242     }
243 }
244