• 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 androidx.lifecycle.LifecycleKt.getCoroutineScope;
20 
21 import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_FILE;
22 import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_IMAGE;
23 import static com.android.intentresolver.contentpreview.ContentPreviewType.CONTENT_PREVIEW_TEXT;
24 
25 import android.content.ClipData;
26 import android.content.Intent;
27 import android.content.res.Resources;
28 import android.net.Uri;
29 import android.text.TextUtils;
30 import android.view.LayoutInflater;
31 import android.view.ViewGroup;
32 
33 import androidx.annotation.Nullable;
34 import androidx.annotation.VisibleForTesting;
35 import androidx.lifecycle.Lifecycle;
36 
37 import com.android.intentresolver.widget.ActionRow;
38 import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback;
39 
40 import java.util.List;
41 import java.util.function.Consumer;
42 
43 /**
44  * Collection of helpers for building the content preview UI displayed in
45  * {@link com.android.intentresolver.ChooserActivity}.
46  * A content preview façade.
47  */
48 public final class ChooserContentPreviewUi {
49 
50     private final Lifecycle mLifecycle;
51 
52     /**
53      * Delegate to build the default system action buttons to display in the preview layout, if/when
54      * they're determined to be appropriate for the particular preview we display.
55      * TODO: clarify why action buttons are part of preview logic.
56      */
57     public interface ActionFactory {
58         /**
59          * @return Runnable to be run when an edit button is clicked (if available).
60          */
61         @Nullable
getEditButtonRunnable()62         Runnable getEditButtonRunnable();
63 
64         /**
65          * @return Runnable to be run when a copy button is clicked (if available).
66          */
67         @Nullable
getCopyButtonRunnable()68         Runnable getCopyButtonRunnable();
69 
70         /** Create custom actions */
createCustomActions()71         List<ActionRow.Action> createCustomActions();
72 
73         /**
74          * Provides a share modification action, if any.
75          */
76         @Nullable
getModifyShareAction()77         ActionRow.Action getModifyShareAction();
78 
79         /**
80          * <p>
81          * Creates an exclude-text action that can be called when the user changes shared text
82          * status in the Media + Text preview.
83          * </p>
84          * <p>
85          * <code>true</code> argument value indicates that the text should be excluded.
86          * </p>
87          */
getExcludeSharedTextAction()88         Consumer<Boolean> getExcludeSharedTextAction();
89     }
90 
91     @VisibleForTesting
92     final ContentPreviewUi mContentPreviewUi;
93 
ChooserContentPreviewUi( Lifecycle lifecycle, PreviewDataProvider previewData, Intent targetIntent, ImageLoader imageLoader, ActionFactory actionFactory, TransitionElementStatusCallback transitionElementStatusCallback, HeadlineGenerator headlineGenerator)94     public ChooserContentPreviewUi(
95             Lifecycle lifecycle,
96             PreviewDataProvider previewData,
97             Intent targetIntent,
98             ImageLoader imageLoader,
99             ActionFactory actionFactory,
100             TransitionElementStatusCallback transitionElementStatusCallback,
101             HeadlineGenerator headlineGenerator) {
102         mLifecycle = lifecycle;
103         mContentPreviewUi = createContentPreview(
104                 previewData,
105                 targetIntent,
106                 DefaultMimeTypeClassifier.INSTANCE,
107                 imageLoader,
108                 actionFactory,
109                 transitionElementStatusCallback,
110                 headlineGenerator);
111         if (mContentPreviewUi.getType() != CONTENT_PREVIEW_IMAGE) {
112             transitionElementStatusCallback.onAllTransitionElementsReady();
113         }
114     }
115 
createContentPreview( PreviewDataProvider previewData, Intent targetIntent, MimeTypeClassifier typeClassifier, ImageLoader imageLoader, ActionFactory actionFactory, TransitionElementStatusCallback transitionElementStatusCallback, HeadlineGenerator headlineGenerator)116     private ContentPreviewUi createContentPreview(
117             PreviewDataProvider previewData,
118             Intent targetIntent,
119             MimeTypeClassifier typeClassifier,
120             ImageLoader imageLoader,
121             ActionFactory actionFactory,
122             TransitionElementStatusCallback transitionElementStatusCallback,
123             HeadlineGenerator headlineGenerator) {
124 
125         int previewType = previewData.getPreviewType();
126         if (previewType == CONTENT_PREVIEW_TEXT) {
127             return createTextPreview(
128                     mLifecycle,
129                     targetIntent,
130                     actionFactory,
131                     imageLoader,
132                     headlineGenerator);
133         }
134         if (previewType == CONTENT_PREVIEW_FILE) {
135             FileContentPreviewUi fileContentPreviewUi = new FileContentPreviewUi(
136                     previewData.getUriCount(),
137                     actionFactory,
138                     headlineGenerator);
139             if (previewData.getUriCount() > 0) {
140                 previewData.getFirstFileName(
141                         mLifecycle, fileContentPreviewUi::setFirstFileName);
142             }
143             return fileContentPreviewUi;
144         }
145         boolean isSingleImageShare = previewData.getUriCount() == 1
146                         && typeClassifier.isImageType(previewData.getFirstFileInfo().getMimeType());
147         CharSequence text = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
148         if (!TextUtils.isEmpty(text)) {
149             FilesPlusTextContentPreviewUi previewUi =
150                     new FilesPlusTextContentPreviewUi(
151                             mLifecycle,
152                             isSingleImageShare,
153                             previewData.getUriCount(),
154                             targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT),
155                             targetIntent.getType(),
156                             actionFactory,
157                             imageLoader,
158                             typeClassifier,
159                             headlineGenerator);
160             if (previewData.getUriCount() > 0) {
161                 JavaFlowHelper.collectToList(
162                         getCoroutineScope(mLifecycle),
163                         previewData.getImagePreviewFileInfoFlow(),
164                         previewUi::updatePreviewMetadata);
165             }
166             return previewUi;
167         }
168 
169         return new UnifiedContentPreviewUi(
170                 getCoroutineScope(mLifecycle),
171                 isSingleImageShare,
172                 targetIntent.getType(),
173                 actionFactory,
174                 imageLoader,
175                 typeClassifier,
176                 transitionElementStatusCallback,
177                 previewData.getImagePreviewFileInfoFlow(),
178                 previewData.getUriCount(),
179                 headlineGenerator);
180     }
181 
getPreferredContentPreview()182     public int getPreferredContentPreview() {
183         return mContentPreviewUi.getType();
184     }
185 
186     /**
187      * Display a content preview of the specified {@code previewType} to preview the content of the
188      * specified {@code intent}.
189      */
displayContentPreview( Resources resources, LayoutInflater layoutInflater, ViewGroup parent)190     public ViewGroup displayContentPreview(
191             Resources resources, LayoutInflater layoutInflater, ViewGroup parent) {
192 
193         return mContentPreviewUi.display(resources, layoutInflater, parent);
194     }
195 
createTextPreview( Lifecycle lifecycle, Intent targetIntent, ChooserContentPreviewUi.ActionFactory actionFactory, ImageLoader imageLoader, HeadlineGenerator headlineGenerator)196     private static TextContentPreviewUi createTextPreview(
197             Lifecycle lifecycle,
198             Intent targetIntent,
199             ChooserContentPreviewUi.ActionFactory actionFactory,
200             ImageLoader imageLoader,
201             HeadlineGenerator headlineGenerator) {
202         CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
203         String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
204         ClipData previewData = targetIntent.getClipData();
205         Uri previewThumbnail = null;
206         if (previewData != null) {
207             if (previewData.getItemCount() > 0) {
208                 ClipData.Item previewDataItem = previewData.getItemAt(0);
209                 previewThumbnail = previewDataItem.getUri();
210             }
211         }
212         return new TextContentPreviewUi(
213                 lifecycle,
214                 sharingText,
215                 previewTitle,
216                 previewThumbnail,
217                 actionFactory,
218                 imageLoader,
219                 headlineGenerator);
220     }
221 }
222