• 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.util.Size;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.widget.CheckBox;
31 import android.widget.ImageView;
32 import android.widget.TextView;
33 
34 import androidx.annotation.Nullable;
35 
36 import com.android.intentresolver.R;
37 import com.android.intentresolver.widget.ActionRow;
38 import com.android.intentresolver.widget.ScrollableImagePreviewView;
39 
40 import kotlinx.coroutines.CoroutineScope;
41 
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.function.Consumer;
45 
46 /**
47  * FilesPlusTextContentPreviewUi is shown when the user is sending 1 or more files along with
48  * non-empty EXTRA_TEXT. The text can be toggled with a checkbox. If a single image file is being
49  * shared, it is shown in a preview (otherwise the headline summary is the sole indication of the
50  * file content).
51  */
52 class FilesPlusTextContentPreviewUi extends ContentPreviewUi {
53     private final CoroutineScope mScope;
54     @Nullable
55     private final String mIntentMimeType;
56     private final CharSequence mText;
57     private final ChooserContentPreviewUi.ActionFactory mActionFactory;
58     private final ImageLoader mImageLoader;
59     private final MimeTypeClassifier mTypeClassifier;
60     private final HeadlineGenerator mHeadlineGenerator;
61     @Nullable
62     private final CharSequence mMetadata;
63     private final boolean mIsSingleImage;
64     private final int mFileCount;
65     private final boolean mAllowTextToggle;
66     private ViewGroup mContentPreviewView;
67     private View mHeadliveView;
68     private boolean mIsMetadataUpdated = false;
69     @Nullable
70     private Uri mFirstFilePreviewUri;
71     private boolean mAllImages;
72     private boolean mAllVideos;
73     private int mPreviewSize;
74 
FilesPlusTextContentPreviewUi( CoroutineScope scope, boolean isSingleImage, int fileCount, CharSequence text, @Nullable String intentMimeType, ChooserContentPreviewUi.ActionFactory actionFactory, ImageLoader imageLoader, MimeTypeClassifier typeClassifier, HeadlineGenerator headlineGenerator, @Nullable CharSequence metadata, boolean allowTextToggle)75     FilesPlusTextContentPreviewUi(
76             CoroutineScope scope,
77             boolean isSingleImage,
78             int fileCount,
79             CharSequence text,
80             @Nullable String intentMimeType,
81             ChooserContentPreviewUi.ActionFactory actionFactory,
82             ImageLoader imageLoader,
83             MimeTypeClassifier typeClassifier,
84             HeadlineGenerator headlineGenerator,
85             @Nullable CharSequence metadata,
86             boolean allowTextToggle) {
87         if (isSingleImage && fileCount != 1) {
88             throw new IllegalArgumentException(
89                     "fileCount = " + fileCount + " and isSingleImage = true");
90         }
91         mScope = scope;
92         mIntentMimeType = intentMimeType;
93         mFileCount = fileCount;
94         mIsSingleImage = isSingleImage;
95         mText = text;
96         mActionFactory = actionFactory;
97         mImageLoader = imageLoader;
98         mTypeClassifier = typeClassifier;
99         mHeadlineGenerator = headlineGenerator;
100         mMetadata = metadata;
101         mAllowTextToggle = allowTextToggle;
102     }
103 
104     @Override
getType()105     public int getType() {
106         return mIsSingleImage ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
107     }
108 
109     @Override
display( Resources resources, LayoutInflater layoutInflater, ViewGroup parent, View headlineViewParent)110     public ViewGroup display(
111             Resources resources,
112             LayoutInflater layoutInflater,
113             ViewGroup parent,
114             View headlineViewParent) {
115         mPreviewSize = resources.getDimensionPixelSize(R.dimen.width_text_image_preview_size);
116         return displayInternal(layoutInflater, parent, headlineViewParent);
117     }
118 
updatePreviewMetadata(List<FileInfo> files)119     public void updatePreviewMetadata(List<FileInfo> files) {
120         boolean allImages = true;
121         boolean allVideos = true;
122         for (FileInfo fileInfo : files) {
123             ScrollableImagePreviewView.PreviewType previewType =
124                     getPreviewType(mTypeClassifier, fileInfo.getMimeType());
125             allImages = allImages && previewType == ScrollableImagePreviewView.PreviewType.Image;
126             allVideos = allVideos && previewType == ScrollableImagePreviewView.PreviewType.Video;
127         }
128         mAllImages = allImages;
129         mAllVideos = allVideos;
130         mFirstFilePreviewUri = files.isEmpty() ? null : files.get(0).getPreviewUri();
131         mIsMetadataUpdated = true;
132         if (mContentPreviewView != null) {
133             updateUiWithMetadata(mContentPreviewView, mHeadliveView);
134         }
135     }
136 
displayInternal( LayoutInflater layoutInflater, ViewGroup parent, View headlineViewParent)137     private ViewGroup displayInternal(
138             LayoutInflater layoutInflater,
139             ViewGroup parent,
140             View headlineViewParent) {
141         mContentPreviewView = (ViewGroup) layoutInflater.inflate(
142                 R.layout.chooser_grid_preview_files_text, parent, false);
143         mHeadliveView = headlineViewParent;
144         inflateHeadline(mHeadliveView);
145 
146         final ActionRow actionRow =
147                 mContentPreviewView.findViewById(com.android.internal.R.id.chooser_action_row);
148         List<ActionRow.Action> actions = mActionFactory.createCustomActions();
149         actionRow.setActions(actions);
150 
151         if (!mIsSingleImage) {
152             mContentPreviewView.requireViewById(R.id.image_view).setVisibility(View.GONE);
153         }
154         prepareTextPreview(mContentPreviewView, mHeadliveView, mActionFactory);
155         if (mIsMetadataUpdated) {
156             updateUiWithMetadata(mContentPreviewView, mHeadliveView);
157         } else {
158             updateHeadline(
159                     mHeadliveView,
160                     mFileCount,
161                     mTypeClassifier.isImageType(mIntentMimeType),
162                     mTypeClassifier.isVideoType(mIntentMimeType));
163         }
164 
165         return mContentPreviewView;
166     }
167 
updateUiWithMetadata(ViewGroup contentPreviewView, View headlineView)168     private void updateUiWithMetadata(ViewGroup contentPreviewView, View headlineView) {
169         prepareTextPreview(contentPreviewView, headlineView, mActionFactory);
170         updateHeadline(headlineView, mFileCount, mAllImages, mAllVideos);
171         ImageView imagePreview = mContentPreviewView.requireViewById(R.id.image_view);
172         if (mIsSingleImage && mFirstFilePreviewUri != null) {
173             mImageLoader.loadImage(
174                     mScope,
175                     mFirstFilePreviewUri,
176                     new Size(mPreviewSize, mPreviewSize),
177                     bitmap -> {
178                         if (bitmap == null) {
179                             imagePreview.setVisibility(View.GONE);
180                         } else {
181                             imagePreview.setImageBitmap(bitmap);
182                         }
183                     });
184         } else {
185             imagePreview.setVisibility(View.GONE);
186         }
187     }
188 
updateHeadline( View headlineView, int fileCount, boolean allImages, boolean allVideos)189     private void updateHeadline(
190             View headlineView, int fileCount, boolean allImages, boolean allVideos) {
191         CheckBox includeText = headlineView.requireViewById(R.id.include_text_action);
192         String headline;
193         if (includeText.getVisibility() == View.VISIBLE && includeText.isChecked()) {
194             if (allImages) {
195                 headline = mHeadlineGenerator.getImagesWithTextHeadline(mText, fileCount);
196             } else if (allVideos) {
197                 headline = mHeadlineGenerator.getVideosWithTextHeadline(mText, fileCount);
198             } else {
199                 headline = mHeadlineGenerator.getFilesWithTextHeadline(mText, fileCount);
200             }
201         } else {
202             if (allImages) {
203                 headline = mHeadlineGenerator.getImagesHeadline(fileCount);
204             } else if (allVideos) {
205                 headline = mHeadlineGenerator.getVideosHeadline(fileCount);
206             } else {
207                 headline = mHeadlineGenerator.getFilesHeadline(fileCount);
208             }
209         }
210 
211         displayHeadline(headlineView, headline);
212         displayMetadata(headlineView, mMetadata);
213     }
214 
prepareTextPreview( ViewGroup contentPreview, View headlineView, ChooserContentPreviewUi.ActionFactory actionFactory)215     private void prepareTextPreview(
216             ViewGroup contentPreview,
217             View headlineView,
218             ChooserContentPreviewUi.ActionFactory actionFactory) {
219         final TextView textView = contentPreview.requireViewById(R.id.content_preview_text);
220         CheckBox includeText = headlineView.requireViewById(R.id.include_text_action);
221         boolean isLink = HttpUriMatcher.isHttpUri(mText.toString());
222         textView.setAutoLinkMask(isLink ? Linkify.WEB_URLS : 0);
223         textView.setText(mText);
224 
225         final Consumer<Boolean> shareTextAction = actionFactory.getExcludeSharedTextAction();
226         includeText.setChecked(true);
227         includeText.setText(isLink ? R.string.include_link : R.string.include_text);
228         shareTextAction.accept(false);
229         includeText.setOnCheckedChangeListener((view, isChecked) -> {
230             if (isChecked) {
231                 textView.setText(mText);
232             } else {
233                 textView.setText(getNoTextString(contentPreview.getResources()));
234             }
235             shareTextAction.accept(!isChecked);
236             updateHeadline(headlineView, mFileCount, mAllImages, mAllVideos);
237         });
238         if (mAllowTextToggle) {
239             includeText.setVisibility(View.VISIBLE);
240         }
241     }
242 
getNoTextString(Resources resources)243     private String getNoTextString(Resources resources) {
244         int stringResource;
245 
246         if (mAllImages) {
247             stringResource = R.string.sharing_images_only;
248         } else if (mAllVideos) {
249             stringResource = R.string.sharing_videos_only;
250         } else {
251             stringResource = R.string.sharing_files_only;
252         }
253 
254         HashMap<String, Object> params = new HashMap<>();
255         params.put("count", mFileCount);
256 
257         return PluralsMessageFormatter.format(
258                 resources,
259                 params,
260                 stringResource
261         );
262     }
263 }
264