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 activity.startActivityAsCaller(mResolvedIntent, options, false, userId); 209 return true; 210 } 211 212 @Override startAsUser(Activity activity, Bundle options, UserHandle user)213 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 214 TargetInfo.prepareIntentForCrossProfileLaunch(mResolvedIntent, user.getIdentifier()); 215 // TODO: is this equivalent to `startActivityAsCaller` with `ignoreTargetSecurity=true`? If 216 // so, we can consolidate on the one API method to show that this flag is the only 217 // distinction between `startAsCaller` and `startAsUser`. We can even bake that flag into 218 // the `TargetActivityStarter` upfront since it just reflects our "safe forwarding mode" -- 219 // which is constant for the duration of our lifecycle, leaving clients no other 220 // responsibilities in this logic. 221 activity.startActivityAsUser(mResolvedIntent, options, user); 222 return false; 223 } 224 225 @Override getTargetIntent()226 public Intent getTargetIntent() { 227 return mResolvedIntent; 228 } 229 isSuspended()230 public boolean isSuspended() { 231 return mIsSuspended; 232 } 233 234 @Override isPinned()235 public boolean isPinned() { 236 return mPinned; 237 } 238 setPinned(boolean pinned)239 public void setPinned(boolean pinned) { 240 mPinned = pinned; 241 } 242 } 243