• 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.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