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 Intent merged = new Intent(matchingBase); 188 merged.fillIn(proposedRefinement, 0); 189 return new DisplayResolveInfo(this, merged, mPresentationGetter); 190 } 191 192 @Override getAllSourceIntents()193 public List<Intent> getAllSourceIntents() { 194 return mSourceIntents; 195 } 196 197 @Override getAllDisplayTargets()198 public ArrayList<DisplayResolveInfo> getAllDisplayTargets() { 199 return new ArrayList<>(Arrays.asList(this)); 200 } 201 addAlternateSourceIntent(Intent alt)202 public void addAlternateSourceIntent(Intent alt) { 203 mSourceIntents.add(alt); 204 } 205 getExtendedInfo()206 public CharSequence getExtendedInfo() { 207 return mExtendedInfo; 208 } 209 getResolvedIntent()210 public Intent getResolvedIntent() { 211 return mResolvedIntent; 212 } 213 214 @Override getResolvedComponentName()215 public ComponentName getResolvedComponentName() { 216 return new ComponentName(mResolveInfo.activityInfo.packageName, 217 mResolveInfo.activityInfo.name); 218 } 219 220 @Override startAsCaller(Activity activity, Bundle options, int userId)221 public boolean startAsCaller(Activity activity, Bundle options, int userId) { 222 TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, userId); 223 activity.startActivityAsCaller(mResolvedIntent, options, false, userId); 224 return true; 225 } 226 227 @Override startAsUser(Activity activity, Bundle options, UserHandle user)228 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 229 TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier()); 230 // TODO: is this equivalent to `startActivityAsCaller` with `ignoreTargetSecurity=true`? If 231 // so, we can consolidate on the one API method to show that this flag is the only 232 // distinction between `startAsCaller` and `startAsUser`. We can even bake that flag into 233 // the `TargetActivityStarter` upfront since it just reflects our "safe forwarding mode" -- 234 // which is constant for the duration of our lifecycle, leaving clients no other 235 // responsibilities in this logic. 236 activity.startActivityAsUser(mResolvedIntent, options, user); 237 return false; 238 } 239 240 @Override getTargetIntent()241 public Intent getTargetIntent() { 242 return mResolvedIntent; 243 } 244 isSuspended()245 public boolean isSuspended() { 246 return mIsSuspended; 247 } 248 249 @Override isPinned()250 public boolean isPinned() { 251 return mPinned; 252 } 253 setPinned(boolean pinned)254 public void setPinned(boolean pinned) { 255 mPinned = pinned; 256 } 257 } 258