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_IMAGE; 20 21 import android.content.res.Resources; 22 import android.util.Log; 23 import android.view.LayoutInflater; 24 import android.view.View; 25 import android.view.ViewGroup; 26 27 import androidx.annotation.Nullable; 28 29 import com.android.intentresolver.R; 30 import com.android.intentresolver.widget.ActionRow; 31 import com.android.intentresolver.widget.ImagePreviewView.TransitionElementStatusCallback; 32 import com.android.intentresolver.widget.ScrollableImagePreviewView; 33 34 import kotlinx.coroutines.CoroutineScope; 35 import kotlinx.coroutines.flow.Flow; 36 37 import java.util.List; 38 import java.util.Objects; 39 40 class UnifiedContentPreviewUi extends ContentPreviewUi { 41 private final boolean mShowEditAction; 42 @Nullable 43 private final String mIntentMimeType; 44 private final ChooserContentPreviewUi.ActionFactory mActionFactory; 45 private final ImageLoader mImageLoader; 46 private final MimeTypeClassifier mTypeClassifier; 47 private final TransitionElementStatusCallback mTransitionElementStatusCallback; 48 private final HeadlineGenerator mHeadlineGenerator; 49 @Nullable 50 private final CharSequence mMetadata; 51 private final Flow<FileInfo> mFileInfoFlow; 52 private final int mItemCount; 53 @Nullable 54 private List<FileInfo> mFiles; 55 @Nullable 56 private ViewGroup mContentPreviewView; 57 private View mHeadlineView; 58 UnifiedContentPreviewUi( CoroutineScope scope, boolean isSingleImage, @Nullable String intentMimeType, ChooserContentPreviewUi.ActionFactory actionFactory, ImageLoader imageLoader, MimeTypeClassifier typeClassifier, TransitionElementStatusCallback transitionElementStatusCallback, Flow<FileInfo> fileInfoFlow, int itemCount, HeadlineGenerator headlineGenerator, @Nullable CharSequence metadata)59 UnifiedContentPreviewUi( 60 CoroutineScope scope, 61 boolean isSingleImage, 62 @Nullable String intentMimeType, 63 ChooserContentPreviewUi.ActionFactory actionFactory, 64 ImageLoader imageLoader, 65 MimeTypeClassifier typeClassifier, 66 TransitionElementStatusCallback transitionElementStatusCallback, 67 Flow<FileInfo> fileInfoFlow, 68 int itemCount, 69 HeadlineGenerator headlineGenerator, 70 @Nullable CharSequence metadata) { 71 mShowEditAction = isSingleImage; 72 mIntentMimeType = intentMimeType; 73 mActionFactory = actionFactory; 74 mImageLoader = imageLoader; 75 mTypeClassifier = typeClassifier; 76 mTransitionElementStatusCallback = transitionElementStatusCallback; 77 mFileInfoFlow = fileInfoFlow; 78 mItemCount = itemCount; 79 mHeadlineGenerator = headlineGenerator; 80 mMetadata = metadata; 81 82 JavaFlowHelper.collectToList(scope, fileInfoFlow, this::setFiles); 83 } 84 85 @Override getType()86 public int getType() { 87 return CONTENT_PREVIEW_IMAGE; 88 } 89 90 @Override display( Resources resources, LayoutInflater layoutInflater, ViewGroup parent, View headlineViewParent)91 public ViewGroup display( 92 Resources resources, 93 LayoutInflater layoutInflater, 94 ViewGroup parent, 95 View headlineViewParent) { 96 return displayInternal(layoutInflater, parent, headlineViewParent); 97 } 98 setFiles(List<FileInfo> files)99 private void setFiles(List<FileInfo> files) { 100 mImageLoader.prePopulate(files.stream() 101 .map(FileInfo::getPreviewUri) 102 .filter(Objects::nonNull) 103 .toList()); 104 mFiles = files; 105 if (mContentPreviewView != null) { 106 updatePreviewWithFiles(mContentPreviewView, mHeadlineView, files); 107 } 108 } 109 displayInternal( LayoutInflater layoutInflater, ViewGroup parent, View headlineViewParent)110 private ViewGroup displayInternal( 111 LayoutInflater layoutInflater, ViewGroup parent, View headlineViewParent) { 112 mContentPreviewView = (ViewGroup) layoutInflater.inflate( 113 R.layout.chooser_grid_preview_image, parent, false); 114 mHeadlineView = headlineViewParent; 115 inflateHeadline(mHeadlineView); 116 117 final ActionRow actionRow = 118 mContentPreviewView.findViewById(com.android.internal.R.id.chooser_action_row); 119 List<ActionRow.Action> actions = mActionFactory.createCustomActions(); 120 actionRow.setActions(actions); 121 122 ScrollableImagePreviewView imagePreview = 123 mContentPreviewView.requireViewById(R.id.scrollable_image_preview); 124 imagePreview.setImageLoader(mImageLoader); 125 imagePreview.setOnNoPreviewCallback(() -> imagePreview.setVisibility(View.GONE)); 126 imagePreview.setTransitionElementStatusCallback(mTransitionElementStatusCallback); 127 imagePreview.setPreviews( 128 JavaFlowHelper.mapFileIntoToPreview( 129 mFileInfoFlow, 130 mTypeClassifier, 131 mShowEditAction ? mActionFactory.getEditButtonRunnable() : null), 132 mItemCount); 133 134 if (mFiles != null) { 135 updatePreviewWithFiles(mContentPreviewView, mHeadlineView, mFiles); 136 } else { 137 displayHeadline( 138 mHeadlineView, 139 mItemCount, 140 mTypeClassifier.isImageType(mIntentMimeType), 141 mTypeClassifier.isVideoType(mIntentMimeType)); 142 imagePreview.setLoading(mItemCount); 143 } 144 145 return mContentPreviewView; 146 } 147 updatePreviewWithFiles( ViewGroup contentPreviewView, View headlineView, List<FileInfo> files)148 private void updatePreviewWithFiles( 149 ViewGroup contentPreviewView, View headlineView, List<FileInfo> files) { 150 final int count = files.size(); 151 ScrollableImagePreviewView imagePreview = 152 contentPreviewView.requireViewById(R.id.scrollable_image_preview); 153 if (count == 0) { 154 Log.i( 155 TAG, 156 "Attempted to display image preview area with zero" 157 + " available images detected in EXTRA_STREAM list"); 158 imagePreview.setVisibility(View.GONE); 159 mTransitionElementStatusCallback.onAllTransitionElementsReady(); 160 return; 161 } 162 163 boolean allImages = true; 164 boolean allVideos = true; 165 for (FileInfo fileInfo : files) { 166 ScrollableImagePreviewView.PreviewType previewType = 167 getPreviewType(mTypeClassifier, fileInfo.getMimeType()); 168 allImages = allImages && previewType == ScrollableImagePreviewView.PreviewType.Image; 169 allVideos = allVideos && previewType == ScrollableImagePreviewView.PreviewType.Video; 170 } 171 172 displayHeadline(headlineView, count, allImages, allVideos); 173 } 174 displayHeadline( View layout, int count, boolean allImages, boolean allVideos)175 private void displayHeadline( 176 View layout, int count, boolean allImages, boolean allVideos) { 177 if (allImages) { 178 displayHeadline(layout, mHeadlineGenerator.getImagesHeadline(count)); 179 } else if (allVideos) { 180 displayHeadline(layout, mHeadlineGenerator.getVideosHeadline(count)); 181 } else { 182 displayHeadline(layout, mHeadlineGenerator.getFilesHeadline(count)); 183 } 184 displayMetadata(layout, mMetadata); 185 } 186 } 187