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