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