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