• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_PAYLOAD_SELECTION;
22 import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_TEXT;
23 
24 import android.content.ClipData;
25 import android.content.res.Resources;
26 import android.net.Uri;
27 import android.text.TextUtils;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.ViewGroup;
31 
32 import androidx.annotation.Nullable;
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.intentresolver.ContentTypeHint;
36 import com.android.intentresolver.data.model.ChooserRequest;
37 import com.android.intentresolver.widget.ActionRow;
38 import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback;
39 
40 import kotlinx.coroutines.CoroutineScope;
41 
42 import java.util.List;
43 import java.util.function.Consumer;
44 import java.util.function.Supplier;
45 
46 /**
47  * Collection of helpers for building the content preview UI displayed in
48  * {@link com.android.intentresolver.ChooserActivity}.
49  * A content preview façade.
50  */
51 public final class ChooserContentPreviewUi {
52 
53     private final CoroutineScope mScope;
54 
55     /**
56      * Delegate to build the default system action buttons to display in the preview layout, if/when
57      * they're determined to be appropriate for the particular preview we display.
58      * TODO: clarify why action buttons are part of preview logic.
59      */
60     public interface ActionFactory {
61         /**
62          * @return Runnable to be run when an edit button is clicked (if available).
63          */
64         @Nullable
getEditButtonRunnable()65         Runnable getEditButtonRunnable();
66 
67         /**
68          * @return Runnable to be run when a copy button is clicked (if available).
69          */
70         @Nullable
getCopyButtonRunnable()71         Runnable getCopyButtonRunnable();
72 
73         /** Create custom actions */
createCustomActions()74         List<ActionRow.Action> createCustomActions();
75 
76         /**
77          * Provides a share modification action, if any.
78          */
79         @Nullable
getModifyShareAction()80         default ActionRow.Action getModifyShareAction() {
81             return null;
82         }
83 
84         /**
85          * <p>
86          * Creates an exclude-text action that can be called when the user changes shared text
87          * status in the Media + Text preview.
88          * </p>
89          * <p>
90          * <code>true</code> argument value indicates that the text should be excluded.
91          * </p>
92          */
getExcludeSharedTextAction()93         Consumer<Boolean> getExcludeSharedTextAction();
94     }
95 
96     @VisibleForTesting
97     final ContentPreviewUi mContentPreviewUi;
98     private final Supplier</*@Nullable*/ActionRow.Action> mModifyShareActionFactory;
99     private View mHeadlineParent;
100 
ChooserContentPreviewUi( CoroutineScope scope, PreviewDataProvider previewData, ChooserRequest chooserRequest, ImageLoader imageLoader, ActionFactory actionFactory, Supplier< ActionRow.Action> modifyShareActionFactory, TransitionElementStatusCallback transitionElementStatusCallback, HeadlineGenerator headlineGenerator, ContentTypeHint contentTypeHint, @Nullable CharSequence metadata)101     public ChooserContentPreviewUi(
102             CoroutineScope scope,
103             PreviewDataProvider previewData,
104             ChooserRequest chooserRequest,
105             ImageLoader imageLoader,
106             ActionFactory actionFactory,
107             Supplier</*@Nullable*/ActionRow.Action> modifyShareActionFactory,
108             TransitionElementStatusCallback transitionElementStatusCallback,
109             HeadlineGenerator headlineGenerator,
110             ContentTypeHint contentTypeHint,
111             @Nullable CharSequence metadata) {
112         mScope = scope;
113         mModifyShareActionFactory = modifyShareActionFactory;
114         mContentPreviewUi = createContentPreview(
115                 previewData,
116                 chooserRequest,
117                 DefaultMimeTypeClassifier.INSTANCE,
118                 imageLoader,
119                 actionFactory,
120                 transitionElementStatusCallback,
121                 headlineGenerator,
122                 contentTypeHint,
123                 metadata
124         );
125         if (mContentPreviewUi.getType() != CONTENT_PREVIEW_IMAGE) {
126             transitionElementStatusCallback.onAllTransitionElementsReady();
127         }
128     }
129 
createContentPreview( PreviewDataProvider previewData, ChooserRequest chooserRequest, MimeTypeClassifier typeClassifier, ImageLoader imageLoader, ActionFactory actionFactory, TransitionElementStatusCallback transitionElementStatusCallback, HeadlineGenerator headlineGenerator, ContentTypeHint contentTypeHint, @Nullable CharSequence metadata )130     private ContentPreviewUi createContentPreview(
131             PreviewDataProvider previewData,
132             ChooserRequest chooserRequest,
133             MimeTypeClassifier typeClassifier,
134             ImageLoader imageLoader,
135             ActionFactory actionFactory,
136             TransitionElementStatusCallback transitionElementStatusCallback,
137             HeadlineGenerator headlineGenerator,
138             ContentTypeHint contentTypeHint,
139             @Nullable CharSequence metadata
140     ) {
141         int previewType = previewData.getPreviewType();
142         if (previewType == CONTENT_PREVIEW_TEXT) {
143             return createTextPreview(
144                     mScope,
145                     chooserRequest.getTargetIntent().getClipData(),
146                     chooserRequest.getSharedText(),
147                     chooserRequest.getSharedTextTitle(),
148                     actionFactory,
149                     imageLoader,
150                     headlineGenerator,
151                     contentTypeHint,
152                     metadata
153             );
154         }
155         if (previewType == CONTENT_PREVIEW_FILE) {
156             FileContentPreviewUi fileContentPreviewUi = new FileContentPreviewUi(
157                     previewData.getUriCount(),
158                     actionFactory,
159                     headlineGenerator,
160                     metadata
161             );
162             if (previewData.getUriCount() > 0) {
163                 previewData.getFirstFileName(mScope, fileContentPreviewUi::setFirstFileName);
164             }
165             return fileContentPreviewUi;
166         }
167 
168         if (previewType == CONTENT_PREVIEW_PAYLOAD_SELECTION) {
169             transitionElementStatusCallback.onAllTransitionElementsReady(); // TODO
170             return new ShareouselContentPreviewUi();
171         }
172 
173         boolean isSingleImageShare = previewData.getUriCount() == 1
174                 && typeClassifier.isImageType(previewData.getFirstFileInfo().getMimeType());
175         if (!TextUtils.isEmpty(chooserRequest.getSharedText())) {
176             FilesPlusTextContentPreviewUi previewUi =
177                     new FilesPlusTextContentPreviewUi(
178                             mScope,
179                             isSingleImageShare,
180                             previewData.getUriCount(),
181                             chooserRequest.getSharedText(),
182                             chooserRequest.getTargetType(),
183                             actionFactory,
184                             imageLoader,
185                             typeClassifier,
186                             headlineGenerator,
187                             metadata,
188                             chooserRequest.getCallerAllowsTextToggle()
189                     );
190             if (previewData.getUriCount() > 0) {
191                 JavaFlowHelper.collectToList(
192                         mScope,
193                         previewData.getImagePreviewFileInfoFlow(),
194                         previewUi::updatePreviewMetadata);
195             }
196             return previewUi;
197         }
198 
199         return new UnifiedContentPreviewUi(
200                 mScope,
201                 isSingleImageShare,
202                 chooserRequest.getTargetType(),
203                 actionFactory,
204                 imageLoader,
205                 typeClassifier,
206                 transitionElementStatusCallback,
207                 previewData.getImagePreviewFileInfoFlow(),
208                 previewData.getUriCount(),
209                 headlineGenerator,
210                 metadata
211         );
212     }
213 
getPreferredContentPreview()214     public int getPreferredContentPreview() {
215         return mContentPreviewUi.getType();
216     }
217 
218     /**
219      * Display a content preview of the specified {@code previewType} to preview the content of the
220      * specified {@code intent}.
221      */
displayContentPreview( Resources resources, LayoutInflater layoutInflater, ViewGroup parent, View headlineViewParent)222     public ViewGroup displayContentPreview(
223             Resources resources,
224             LayoutInflater layoutInflater,
225             ViewGroup parent,
226             View headlineViewParent) {
227 
228         ViewGroup layout =
229                 mContentPreviewUi.display(resources, layoutInflater, parent, headlineViewParent);
230         mHeadlineParent = headlineViewParent;
231         ContentPreviewUi.displayModifyShareAction(mHeadlineParent, mModifyShareActionFactory.get());
232         return layout;
233     }
234 
235     /**
236      * Update Modify Share Action, if it is inflated.
237      */
updateModifyShareAction()238     public void updateModifyShareAction() {
239         ContentPreviewUi.displayModifyShareAction(mHeadlineParent, mModifyShareActionFactory.get());
240     }
241 
createTextPreview( CoroutineScope scope, ClipData previewData, @Nullable CharSequence sharingText, @Nullable CharSequence previewTitle, ChooserContentPreviewUi.ActionFactory actionFactory, ImageLoader imageLoader, HeadlineGenerator headlineGenerator, ContentTypeHint contentTypeHint, @Nullable CharSequence metadata )242     private static TextContentPreviewUi createTextPreview(
243             CoroutineScope scope,
244             ClipData previewData,
245             @Nullable CharSequence sharingText,
246             @Nullable CharSequence previewTitle,
247             ChooserContentPreviewUi.ActionFactory actionFactory,
248             ImageLoader imageLoader,
249             HeadlineGenerator headlineGenerator,
250             ContentTypeHint contentTypeHint,
251             @Nullable CharSequence metadata
252     ) {
253         Uri previewThumbnail = null;
254         if (previewData != null) {
255             if (previewData.getItemCount() > 0) {
256                 ClipData.Item previewDataItem = previewData.getItemAt(0);
257                 previewThumbnail = previewDataItem.getUri();
258             }
259         }
260 
261         return new TextContentPreviewUi(
262                 scope,
263                 sharingText,
264                 previewTitle,
265                 metadata,
266                 previewThumbnail,
267                 actionFactory,
268                 imageLoader,
269                 headlineGenerator,
270                 contentTypeHint);
271     }
272 }
273