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