1 /* 2 * Copyright (C) 2019 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.chooser; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.Activity; 22 import android.content.ComponentName; 23 import android.content.Intent; 24 import android.content.pm.ActivityInfo; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.ResolveInfo; 27 import android.os.Bundle; 28 import android.os.UserHandle; 29 30 import com.android.intentresolver.TargetPresentationGetter; 31 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.List; 35 36 /** 37 * A TargetInfo plus additional information needed to render it (such as icon and label) and 38 * resolve it to an activity. 39 */ 40 public class DisplayResolveInfo implements TargetInfo { 41 private final ResolveInfo mResolveInfo; 42 private CharSequence mDisplayLabel; 43 private CharSequence mExtendedInfo; 44 private final Intent mResolvedIntent; 45 private final List<Intent> mSourceIntents = new ArrayList<>(); 46 private final boolean mIsSuspended; 47 private TargetPresentationGetter mPresentationGetter; 48 private boolean mPinned = false; 49 private final IconHolder mDisplayIconHolder = new SettableIconHolder(); 50 51 /** Create a new {@code DisplayResolveInfo} instance. */ newDisplayResolveInfo( Intent originalIntent, ResolveInfo resolveInfo, @NonNull Intent resolvedIntent, @Nullable TargetPresentationGetter presentationGetter)52 public static DisplayResolveInfo newDisplayResolveInfo( 53 Intent originalIntent, 54 ResolveInfo resolveInfo, 55 @NonNull Intent resolvedIntent, 56 @Nullable TargetPresentationGetter presentationGetter) { 57 return newDisplayResolveInfo( 58 originalIntent, 59 resolveInfo, 60 /* displayLabel=*/ null, 61 /* extendedInfo=*/ null, 62 resolvedIntent, 63 presentationGetter); 64 } 65 66 /** Create a new {@code DisplayResolveInfo} instance. */ newDisplayResolveInfo( Intent originalIntent, ResolveInfo resolveInfo, CharSequence displayLabel, CharSequence extendedInfo, @NonNull Intent resolvedIntent, @Nullable TargetPresentationGetter presentationGetter)67 public static DisplayResolveInfo newDisplayResolveInfo( 68 Intent originalIntent, 69 ResolveInfo resolveInfo, 70 CharSequence displayLabel, 71 CharSequence extendedInfo, 72 @NonNull Intent resolvedIntent, 73 @Nullable TargetPresentationGetter presentationGetter) { 74 return new DisplayResolveInfo( 75 originalIntent, 76 resolveInfo, 77 displayLabel, 78 extendedInfo, 79 resolvedIntent, 80 presentationGetter); 81 } 82 DisplayResolveInfo( Intent originalIntent, ResolveInfo resolveInfo, CharSequence displayLabel, CharSequence extendedInfo, @NonNull Intent resolvedIntent, @Nullable TargetPresentationGetter presentationGetter)83 private DisplayResolveInfo( 84 Intent originalIntent, 85 ResolveInfo resolveInfo, 86 CharSequence displayLabel, 87 CharSequence extendedInfo, 88 @NonNull Intent resolvedIntent, 89 @Nullable TargetPresentationGetter presentationGetter) { 90 mSourceIntents.add(originalIntent); 91 mResolveInfo = resolveInfo; 92 mDisplayLabel = displayLabel; 93 mExtendedInfo = extendedInfo; 94 mPresentationGetter = presentationGetter; 95 96 final ActivityInfo ai = mResolveInfo.activityInfo; 97 mIsSuspended = (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0; 98 99 mResolvedIntent = createResolvedIntent(resolvedIntent, ai); 100 } 101 DisplayResolveInfo( DisplayResolveInfo other, @Nullable Intent baseIntentToSend, TargetPresentationGetter presentationGetter)102 private DisplayResolveInfo( 103 DisplayResolveInfo other, 104 @Nullable Intent baseIntentToSend, 105 TargetPresentationGetter presentationGetter) { 106 mSourceIntents.addAll(other.getAllSourceIntents()); 107 mResolveInfo = other.mResolveInfo; 108 mIsSuspended = other.mIsSuspended; 109 mDisplayLabel = other.mDisplayLabel; 110 mExtendedInfo = other.mExtendedInfo; 111 112 mResolvedIntent = createResolvedIntent( 113 baseIntentToSend == null ? other.mResolvedIntent : baseIntentToSend, 114 mResolveInfo.activityInfo); 115 mPresentationGetter = presentationGetter; 116 117 mDisplayIconHolder.setDisplayIcon(other.mDisplayIconHolder.getDisplayIcon()); 118 } 119 DisplayResolveInfo(DisplayResolveInfo other)120 protected DisplayResolveInfo(DisplayResolveInfo other) { 121 mSourceIntents.addAll(other.getAllSourceIntents()); 122 mResolveInfo = other.mResolveInfo; 123 mIsSuspended = other.mIsSuspended; 124 mDisplayLabel = other.mDisplayLabel; 125 mExtendedInfo = other.mExtendedInfo; 126 mResolvedIntent = other.mResolvedIntent; 127 mPresentationGetter = other.mPresentationGetter; 128 129 mDisplayIconHolder.setDisplayIcon(other.mDisplayIconHolder.getDisplayIcon()); 130 } 131 createResolvedIntent(Intent resolvedIntent, ActivityInfo ai)132 private static Intent createResolvedIntent(Intent resolvedIntent, ActivityInfo ai) { 133 final Intent result = new Intent(resolvedIntent); 134 result.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 135 | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 136 result.setComponent(new ComponentName(ai.applicationInfo.packageName, ai.name)); 137 return result; 138 } 139 140 @Override isDisplayResolveInfo()141 public final boolean isDisplayResolveInfo() { 142 return true; 143 } 144 getResolveInfo()145 public ResolveInfo getResolveInfo() { 146 return mResolveInfo; 147 } 148 getDisplayLabel()149 public CharSequence getDisplayLabel() { 150 if (mDisplayLabel == null && mPresentationGetter != null) { 151 mDisplayLabel = mPresentationGetter.getLabel(); 152 mExtendedInfo = mPresentationGetter.getSubLabel(); 153 } 154 return mDisplayLabel; 155 } 156 hasDisplayLabel()157 public boolean hasDisplayLabel() { 158 return mDisplayLabel != null; 159 } 160 setDisplayLabel(CharSequence displayLabel)161 public void setDisplayLabel(CharSequence displayLabel) { 162 mDisplayLabel = displayLabel; 163 } 164 setExtendedInfo(CharSequence extendedInfo)165 public void setExtendedInfo(CharSequence extendedInfo) { 166 mExtendedInfo = extendedInfo; 167 } 168 169 @Override getDisplayIconHolder()170 public IconHolder getDisplayIconHolder() { 171 return mDisplayIconHolder; 172 } 173 174 @Override 175 @Nullable tryToCloneWithAppliedRefinement(Intent proposedRefinement)176 public DisplayResolveInfo tryToCloneWithAppliedRefinement(Intent proposedRefinement) { 177 Intent matchingBase = 178 getAllSourceIntents() 179 .stream() 180 .filter(i -> i.filterEquals(proposedRefinement)) 181 .findFirst() 182 .orElse(null); 183 if (matchingBase == null) { 184 return null; 185 } 186 187 return new DisplayResolveInfo( 188 this, 189 TargetInfo.mergeRefinementIntoMatchingBaseIntent(matchingBase, proposedRefinement), 190 mPresentationGetter); 191 } 192 193 @Override getAllSourceIntents()194 public List<Intent> getAllSourceIntents() { 195 return mSourceIntents; 196 } 197 198 @Override getAllDisplayTargets()199 public ArrayList<DisplayResolveInfo> getAllDisplayTargets() { 200 return new ArrayList<>(Arrays.asList(this)); 201 } 202 addAlternateSourceIntent(Intent alt)203 public void addAlternateSourceIntent(Intent alt) { 204 mSourceIntents.add(alt); 205 } 206 getExtendedInfo()207 public CharSequence getExtendedInfo() { 208 return mExtendedInfo; 209 } 210 getResolvedIntent()211 public Intent getResolvedIntent() { 212 return mResolvedIntent; 213 } 214 215 @Override getResolvedComponentName()216 public ComponentName getResolvedComponentName() { 217 return new ComponentName(mResolveInfo.activityInfo.packageName, 218 mResolveInfo.activityInfo.name); 219 } 220 221 @Override startAsCaller(Activity activity, Bundle options, int userId)222 public boolean startAsCaller(Activity activity, Bundle options, int userId) { 223 TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, userId); 224 activity.startActivityAsCaller(mResolvedIntent, options, false, userId); 225 return true; 226 } 227 228 @Override startAsUser(Activity activity, Bundle options, UserHandle user)229 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 230 TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier()); 231 // TODO: is this equivalent to `startActivityAsCaller` with `ignoreTargetSecurity=true`? If 232 // so, we can consolidate on the one API method to show that this flag is the only 233 // distinction between `startAsCaller` and `startAsUser`. We can even bake that flag into 234 // the `TargetActivityStarter` upfront since it just reflects our "safe forwarding mode" -- 235 // which is constant for the duration of our lifecycle, leaving clients no other 236 // responsibilities in this logic. 237 activity.startActivityAsUser(mResolvedIntent, options, user); 238 return false; 239 } 240 241 @Override getTargetIntent()242 public Intent getTargetIntent() { 243 return mResolvedIntent; 244 } 245 isSuspended()246 public boolean isSuspended() { 247 return mIsSuspended; 248 } 249 250 @Override isPinned()251 public boolean isPinned() { 252 return mPinned; 253 } 254 setPinned(boolean pinned)255 public void setPinned(boolean pinned) { 256 mPinned = pinned; 257 } 258 } 259