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 android.content.ContentProvider.getUserIdFromUri; 20 21 import android.animation.ObjectAnimator; 22 import android.animation.ValueAnimator; 23 import android.content.res.Resources; 24 import android.graphics.Bitmap; 25 import android.net.Uri; 26 import android.os.UserHandle; 27 import android.util.Log; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.ViewStub; 32 import android.view.animation.DecelerateInterpolator; 33 34 import androidx.annotation.LayoutRes; 35 36 import com.android.intentresolver.R; 37 import com.android.intentresolver.flags.FeatureFlagRepository; 38 import com.android.intentresolver.flags.Flags; 39 import com.android.intentresolver.widget.ActionRow; 40 import com.android.intentresolver.widget.RoundedRectImageView; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 45 abstract class ContentPreviewUi { 46 private static final int IMAGE_FADE_IN_MILLIS = 150; 47 static final String TAG = "ChooserPreview"; 48 49 @ContentPreviewType getType()50 public abstract int getType(); 51 display( Resources resources, LayoutInflater layoutInflater, ViewGroup parent)52 public abstract ViewGroup display( 53 Resources resources, LayoutInflater layoutInflater, ViewGroup parent); 54 getActionRowLayout(FeatureFlagRepository featureFlagRepository)55 protected static int getActionRowLayout(FeatureFlagRepository featureFlagRepository) { 56 return featureFlagRepository.isEnabled(Flags.SHARESHEET_CUSTOM_ACTIONS) 57 ? R.layout.scrollable_chooser_action_row 58 : R.layout.chooser_action_row; 59 } 60 inflateActionRow(ViewGroup parent, @LayoutRes int actionRowLayout)61 protected static ActionRow inflateActionRow(ViewGroup parent, @LayoutRes int actionRowLayout) { 62 final ViewStub stub = parent.findViewById(com.android.intentresolver.R.id.action_row_stub); 63 if (stub != null) { 64 stub.setLayoutResource(actionRowLayout); 65 stub.inflate(); 66 } 67 return parent.findViewById(com.android.internal.R.id.chooser_action_row); 68 } 69 createActions( List<ActionRow.Action> systemActions, List<ActionRow.Action> customActions, FeatureFlagRepository featureFlagRepository)70 protected static List<ActionRow.Action> createActions( 71 List<ActionRow.Action> systemActions, 72 List<ActionRow.Action> customActions, 73 FeatureFlagRepository featureFlagRepository) { 74 ArrayList<ActionRow.Action> actions = 75 new ArrayList<>(systemActions.size() + customActions.size()); 76 actions.addAll(systemActions); 77 if (featureFlagRepository.isEnabled(Flags.SHARESHEET_CUSTOM_ACTIONS)) { 78 actions.addAll(customActions); 79 } 80 return actions; 81 } 82 83 /** 84 * Indicate if the incoming content URI should be allowed. 85 * 86 * @param uri the uri to test 87 * @return true if the URI is allowed for content preview 88 */ validForContentPreview(Uri uri)89 protected static boolean validForContentPreview(Uri uri) throws SecurityException { 90 if (uri == null) { 91 return false; 92 } 93 int userId = getUserIdFromUri(uri, UserHandle.USER_CURRENT); 94 if (userId != UserHandle.USER_CURRENT && userId != UserHandle.myUserId()) { 95 Log.e(ContentPreviewUi.TAG, "dropped invalid content URI belonging to user " + userId); 96 return false; 97 } 98 return true; 99 } 100 updateViewWithImage(RoundedRectImageView imageView, Bitmap image)101 protected static void updateViewWithImage(RoundedRectImageView imageView, Bitmap image) { 102 if (image == null) { 103 imageView.setVisibility(View.GONE); 104 return; 105 } 106 imageView.setVisibility(View.VISIBLE); 107 imageView.setAlpha(0.0f); 108 imageView.setImageBitmap(image); 109 110 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f, 1.0f); 111 fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f)); 112 fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS); 113 fadeAnim.start(); 114 } 115 displayPayloadReselectionAction( ViewGroup layout, ChooserContentPreviewUi.ActionFactory actionFactory, FeatureFlagRepository featureFlagRepository)116 protected static void displayPayloadReselectionAction( 117 ViewGroup layout, 118 ChooserContentPreviewUi.ActionFactory actionFactory, 119 FeatureFlagRepository featureFlagRepository) { 120 Runnable modifyShareAction = actionFactory.getModifyShareAction(); 121 if (modifyShareAction != null && layout != null 122 && featureFlagRepository.isEnabled(Flags.SHARESHEET_RESELECTION_ACTION)) { 123 View modifyShareView = layout.findViewById(R.id.reselection_action); 124 if (modifyShareView != null) { 125 modifyShareView.setVisibility(View.VISIBLE); 126 modifyShareView.setOnClickListener(view -> modifyShareAction.run()); 127 } 128 } 129 } 130 } 131