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 
264         // Important: we will ignore the target security checks in ActivityManager
265         // if and only if the ChooserTarget's target package is the same package
266         // where we got the ChooserTargetService that provided it. This lets a
267         // ChooserTargetService provide a non-exported or permission-guarded target
268         // to the chooser for the user to pick.
269         //
270         // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
271         // so we'll obey the caller's normal security checks.
272         final boolean ignoreTargetSecurity = mSourceInfo != null
273                 && mSourceInfo.getResolvedComponentName().getPackageName()
274                 .equals(mChooserTarget.getComponentName().getPackageName());
275         activity.startActivityAsCaller(intent, options, ignoreTargetSecurity, userId);
276         return true;
277     }
278 
279     @Override
startAsUser(Activity activity, Bundle options, UserHandle user)280     public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
281         throw new RuntimeException("ChooserTargets should be started as caller.");
282     }
283 
284     @Override
getResolveInfo()285     public ResolveInfo getResolveInfo() {
286         return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
287     }
288 
289     @Override
getDisplayLabel()290     public CharSequence getDisplayLabel() {
291         return mDisplayLabel;
292     }
293 
294     @Override
getExtendedInfo()295     public CharSequence getExtendedInfo() {
296         // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
297         return null;
298     }
299 
300     @Override
getDisplayIcon(Context context)301     public synchronized Drawable getDisplayIcon(Context context) {
302         return mDisplayIcon;
303     }
304 
305     /**
306      * @return true if display icon is available
307      */
hasDisplayIcon()308     public synchronized boolean hasDisplayIcon() {
309         return mDisplayIcon != null;
310     }
311 
getChooserTarget()312     public ChooserTarget getChooserTarget() {
313         return mChooserTarget;
314     }
315 
316     @Override
cloneFilledIn(Intent fillInIntent, int flags)317     public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
318         return new SelectableTargetInfo(this, fillInIntent, flags);
319     }
320 
321     @Override
getAllSourceIntents()322     public List<Intent> getAllSourceIntents() {
323         final List<Intent> results = new ArrayList<>();
324         if (mSourceInfo != null) {
325             // We only queried the service for the first one in our sourceinfo.
326             results.add(mSourceInfo.getAllSourceIntents().get(0));
327         }
328         return results;
329     }
330 
331     @Override
isPinned()332     public boolean isPinned() {
333         return mIsPinned;
334     }
335 
336     /**
337      * Necessary methods to communicate between {@link SelectableTargetInfo}
338      * and {@link ResolverActivity} or {@link ChooserActivity}.
339      */
340     public interface SelectableTargetInfoCommunicator {
341 
makePresentationGetter(ActivityInfo info)342         ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info);
343 
getTargetIntent()344         Intent getTargetIntent();
345 
getReferrerFillInIntent()346         Intent getReferrerFillInIntent();
347     }
348 }
349