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.internal.app.chooser; 18 19 import android.annotation.Nullable; 20 import android.app.Activity; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.ActivityInfo; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.LauncherApps; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.content.pm.ShortcutInfo; 30 import android.graphics.Bitmap; 31 import android.graphics.drawable.BitmapDrawable; 32 import android.graphics.drawable.Drawable; 33 import android.graphics.drawable.Icon; 34 import android.os.Bundle; 35 import android.os.UserHandle; 36 import android.service.chooser.ChooserTarget; 37 import android.text.SpannableStringBuilder; 38 import android.util.Log; 39 40 import com.android.internal.app.ChooserActivity; 41 import com.android.internal.app.ChooserFlags; 42 import com.android.internal.app.ResolverActivity; 43 import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter; 44 import com.android.internal.app.SimpleIconFactory; 45 46 import java.util.ArrayList; 47 import java.util.List; 48 49 /** 50 * Live target, currently selectable by the user. 51 * @see NotSelectableTargetInfo 52 */ 53 public final class SelectableTargetInfo implements ChooserTargetInfo { 54 private static final String TAG = "SelectableTargetInfo"; 55 56 private final Context mContext; 57 private final DisplayResolveInfo mSourceInfo; 58 private final ResolveInfo mBackupResolveInfo; 59 private final ChooserTarget mChooserTarget; 60 private final String mDisplayLabel; 61 private final PackageManager mPm; 62 private final SelectableTargetInfoCommunicator mSelectableTargetInfoCommunicator; 63 private Drawable mBadgeIcon = null; 64 private CharSequence mBadgeContentDescription; 65 private Drawable mDisplayIcon; 66 private final Intent mFillInIntent; 67 private final int mFillInFlags; 68 private final float mModifiedScore; 69 private boolean mIsSuspended = false; 70 SelectableTargetInfo(Context context, DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget, float modifiedScore, SelectableTargetInfoCommunicator selectableTargetInfoComunicator, @Nullable ShortcutInfo shortcutInfo)71 public SelectableTargetInfo(Context context, DisplayResolveInfo sourceInfo, 72 ChooserTarget chooserTarget, 73 float modifiedScore, SelectableTargetInfoCommunicator selectableTargetInfoComunicator, 74 @Nullable ShortcutInfo shortcutInfo) { 75 mContext = context; 76 mSourceInfo = sourceInfo; 77 mChooserTarget = chooserTarget; 78 mModifiedScore = modifiedScore; 79 mPm = mContext.getPackageManager(); 80 mSelectableTargetInfoCommunicator = selectableTargetInfoComunicator; 81 if (sourceInfo != null) { 82 final ResolveInfo ri = sourceInfo.getResolveInfo(); 83 if (ri != null) { 84 final ActivityInfo ai = ri.activityInfo; 85 if (ai != null && ai.applicationInfo != null) { 86 final PackageManager pm = mContext.getPackageManager(); 87 mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo); 88 mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo); 89 mIsSuspended = 90 (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0; 91 } 92 } 93 } 94 // TODO(b/121287224): do this in the background thread, and only for selected targets 95 mDisplayIcon = getChooserTargetIconDrawable(chooserTarget, shortcutInfo); 96 97 if (sourceInfo != null) { 98 mBackupResolveInfo = null; 99 } else { 100 mBackupResolveInfo = 101 mContext.getPackageManager().resolveActivity(getResolvedIntent(), 0); 102 } 103 104 mFillInIntent = null; 105 mFillInFlags = 0; 106 107 mDisplayLabel = sanitizeDisplayLabel(chooserTarget.getTitle()); 108 } 109 SelectableTargetInfo(SelectableTargetInfo other, Intent fillInIntent, int flags)110 private SelectableTargetInfo(SelectableTargetInfo other, 111 Intent fillInIntent, int flags) { 112 mContext = other.mContext; 113 mPm = other.mPm; 114 mSelectableTargetInfoCommunicator = other.mSelectableTargetInfoCommunicator; 115 mSourceInfo = other.mSourceInfo; 116 mBackupResolveInfo = other.mBackupResolveInfo; 117 mChooserTarget = other.mChooserTarget; 118 mBadgeIcon = other.mBadgeIcon; 119 mBadgeContentDescription = other.mBadgeContentDescription; 120 mDisplayIcon = other.mDisplayIcon; 121 mFillInIntent = fillInIntent; 122 mFillInFlags = flags; 123 mModifiedScore = other.mModifiedScore; 124 125 mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle()); 126 } 127 sanitizeDisplayLabel(CharSequence label)128 private String sanitizeDisplayLabel(CharSequence label) { 129 SpannableStringBuilder sb = new SpannableStringBuilder(label); 130 sb.clearSpans(); 131 return sb.toString(); 132 } 133 isSuspended()134 public boolean isSuspended() { 135 return mIsSuspended; 136 } 137 getDisplayResolveInfo()138 public DisplayResolveInfo getDisplayResolveInfo() { 139 return mSourceInfo; 140 } 141 getChooserTargetIconDrawable(ChooserTarget target, @Nullable ShortcutInfo shortcutInfo)142 private Drawable getChooserTargetIconDrawable(ChooserTarget target, 143 @Nullable ShortcutInfo shortcutInfo) { 144 Drawable directShareIcon = null; 145 146 // First get the target drawable and associated activity info 147 final Icon icon = target.getIcon(); 148 if (icon != null) { 149 directShareIcon = icon.loadDrawable(mContext); 150 } else if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS && shortcutInfo != null) { 151 LauncherApps launcherApps = (LauncherApps) mContext.getSystemService( 152 Context.LAUNCHER_APPS_SERVICE); 153 directShareIcon = launcherApps.getShortcutIconDrawable(shortcutInfo, 0); 154 } 155 156 if (directShareIcon == null) return null; 157 158 ActivityInfo info = null; 159 try { 160 info = mPm.getActivityInfo(target.getComponentName(), 0); 161 } catch (PackageManager.NameNotFoundException error) { 162 Log.e(TAG, "Could not find activity associated with ChooserTarget"); 163 } 164 165 if (info == null) return null; 166 167 // Now fetch app icon and raster with no badging even in work profile 168 Bitmap appIcon = mSelectableTargetInfoCommunicator.makePresentationGetter(info) 169 .getIconBitmap(UserHandle.getUserHandleForUid(UserHandle.myUserId())); 170 171 // Raster target drawable with appIcon as a badge 172 SimpleIconFactory sif = SimpleIconFactory.obtain(mContext); 173 Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon); 174 sif.recycle(); 175 176 return new BitmapDrawable(mContext.getResources(), directShareBadgedIcon); 177 } 178 getModifiedScore()179 public float getModifiedScore() { 180 return mModifiedScore; 181 } 182 183 @Override getResolvedIntent()184 public Intent getResolvedIntent() { 185 if (mSourceInfo != null) { 186 return mSourceInfo.getResolvedIntent(); 187 } 188 189 final Intent targetIntent = new Intent(mSelectableTargetInfoCommunicator.getTargetIntent()); 190 targetIntent.setComponent(mChooserTarget.getComponentName()); 191 targetIntent.putExtras(mChooserTarget.getIntentExtras()); 192 return targetIntent; 193 } 194 195 @Override getResolvedComponentName()196 public ComponentName getResolvedComponentName() { 197 if (mSourceInfo != null) { 198 return mSourceInfo.getResolvedComponentName(); 199 } else if (mBackupResolveInfo != null) { 200 return new ComponentName(mBackupResolveInfo.activityInfo.packageName, 201 mBackupResolveInfo.activityInfo.name); 202 } 203 return null; 204 } 205 getBaseIntentToSend()206 private Intent getBaseIntentToSend() { 207 Intent result = getResolvedIntent(); 208 if (result == null) { 209 Log.e(TAG, "ChooserTargetInfo: no base intent available to send"); 210 } else { 211 result = new Intent(result); 212 if (mFillInIntent != null) { 213 result.fillIn(mFillInIntent, mFillInFlags); 214 } 215 result.fillIn(mSelectableTargetInfoCommunicator.getReferrerFillInIntent(), 0); 216 } 217 return result; 218 } 219 220 @Override start(Activity activity, Bundle options)221 public boolean start(Activity activity, Bundle options) { 222 throw new RuntimeException("ChooserTargets should be started as caller."); 223 } 224 225 @Override startAsCaller(ResolverActivity activity, Bundle options, int userId)226 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) { 227 final Intent intent = getBaseIntentToSend(); 228 if (intent == null) { 229 return false; 230 } 231 intent.setComponent(mChooserTarget.getComponentName()); 232 intent.putExtras(mChooserTarget.getIntentExtras()); 233 234 // Important: we will ignore the target security checks in ActivityManager 235 // if and only if the ChooserTarget's target package is the same package 236 // where we got the ChooserTargetService that provided it. This lets a 237 // ChooserTargetService provide a non-exported or permission-guarded target 238 // to the chooser for the user to pick. 239 // 240 // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere 241 // so we'll obey the caller's normal security checks. 242 final boolean ignoreTargetSecurity = mSourceInfo != null 243 && mSourceInfo.getResolvedComponentName().getPackageName() 244 .equals(mChooserTarget.getComponentName().getPackageName()); 245 return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId); 246 } 247 248 @Override startAsUser(Activity activity, Bundle options, UserHandle user)249 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 250 throw new RuntimeException("ChooserTargets should be started as caller."); 251 } 252 253 @Override getResolveInfo()254 public ResolveInfo getResolveInfo() { 255 return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo; 256 } 257 258 @Override getDisplayLabel()259 public CharSequence getDisplayLabel() { 260 return mDisplayLabel; 261 } 262 263 @Override getExtendedInfo()264 public CharSequence getExtendedInfo() { 265 // ChooserTargets have badge icons, so we won't show the extended info to disambiguate. 266 return null; 267 } 268 269 @Override getDisplayIcon(Context context)270 public Drawable getDisplayIcon(Context context) { 271 return mDisplayIcon; 272 } 273 getChooserTarget()274 public ChooserTarget getChooserTarget() { 275 return mChooserTarget; 276 } 277 278 @Override cloneFilledIn(Intent fillInIntent, int flags)279 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { 280 return new SelectableTargetInfo(this, fillInIntent, flags); 281 } 282 283 @Override getAllSourceIntents()284 public List<Intent> getAllSourceIntents() { 285 final List<Intent> results = new ArrayList<>(); 286 if (mSourceInfo != null) { 287 // We only queried the service for the first one in our sourceinfo. 288 results.add(mSourceInfo.getAllSourceIntents().get(0)); 289 } 290 return results; 291 } 292 293 @Override isPinned()294 public boolean isPinned() { 295 return mSourceInfo != null && mSourceInfo.isPinned(); 296 } 297 298 /** 299 * Necessary methods to communicate between {@link SelectableTargetInfo} 300 * and {@link ResolverActivity} or {@link ChooserActivity}. 301 */ 302 public interface SelectableTargetInfoCommunicator { 303 makePresentationGetter(ActivityInfo info)304 ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info); 305 getTargetIntent()306 Intent getTargetIntent(); 307 getReferrerFillInIntent()308 Intent getReferrerFillInIntent(); 309 } 310 } 311