• 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.intentresolver.chooser;
18 
19 
20 import static android.security.Flags.preventIntentRedirect;
21 
22 import android.app.Activity;
23 import android.app.ActivityManager;
24 import android.app.prediction.AppTarget;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.SharedPreferences;
29 import android.content.pm.ResolveInfo;
30 import android.content.pm.ShortcutInfo;
31 import android.content.pm.ShortcutManager;
32 import android.graphics.drawable.Drawable;
33 import android.os.Bundle;
34 import android.os.RemoteException;
35 import android.os.UserHandle;
36 import android.service.chooser.ChooserTarget;
37 import android.text.TextUtils;
38 import android.util.HashedStringCache;
39 
40 import androidx.annotation.Nullable;
41 
42 import com.android.intentresolver.ChooserListAdapter;
43 import com.android.intentresolver.ChooserRefinementManager;
44 import com.android.intentresolver.ResolverActivity;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Objects;
49 
50 /**
51  * A single target as represented in the chooser.
52  */
53 public interface TargetInfo {
54 
55     /**
56      * Container for a {@link TargetInfo}'s (potentially) mutable icon state. This is provided to
57      * encapsulate the state so that the {@link TargetInfo} itself can be "immutable" (in some
58      * sense) as long as it always returns the same {@link IconHolder} instance.
59      *
60      * TODO: move "stateful" responsibilities out to clients; for more info see the Javadoc comment
61      * on {@link #getDisplayIconHolder()}.
62      */
63     interface IconHolder {
64         /** @return the icon (if it's already loaded, or statically available), or null. */
65         @Nullable
getDisplayIcon()66         Drawable getDisplayIcon();
67 
68         /**
69          * @param icon the icon to return on subsequent calls to {@link #getDisplayIcon()}.
70          * Implementations may discard this request as a no-op if they don't support setting.
71          */
setDisplayIcon(@ullable Drawable icon)72         void setDisplayIcon(@Nullable Drawable icon);
73     }
74 
75     /** A simple mutable-container implementation of {@link IconHolder}. */
76     final class SettableIconHolder implements IconHolder {
77         @Nullable
78         private Drawable mDisplayIcon;
79 
80         @Nullable
getDisplayIcon()81         public Drawable getDisplayIcon() {
82             return mDisplayIcon;
83         }
84 
setDisplayIcon(@ullable Drawable icon)85         public void setDisplayIcon(@Nullable Drawable icon) {
86             mDisplayIcon = icon;
87         }
88     }
89 
90     /**
91      * Get the resolved intent that represents this target. Note that this may not be the
92      * intent that will be launched by calling one of the <code>start</code> methods provided;
93      * this is the intent that will be credited with the launch.
94      *
95      * @return the resolved intent for this target
96      */
getResolvedIntent()97     Intent getResolvedIntent();
98 
99     /**
100      * Get the target intent, the one that will be used with one of the <code>start</code> methods.
101      * @return the intent with target will be launced with.
102      */
getTargetIntent()103     @Nullable Intent getTargetIntent();
104 
105     /**
106      * Get the resolved component name that represents this target. Note that this may not
107      * be the component that will be directly launched by calling one of the <code>start</code>
108      * methods provided; this is the component that will be credited with the launch. This may be
109      * null if the target was specified by a caller-provided {@link ChooserTarget} that we failed to
110      * resolve to a component on the system.
111      *
112      * @return the resolved ComponentName for this target
113      */
114     @Nullable
getResolvedComponentName()115     ComponentName getResolvedComponentName();
116 
117     /**
118      * If this target was historically built from a (now-deprecated) {@link ChooserTarget} record,
119      * get the {@link ComponentName} that would've been provided by that record.
120      *
121      * TODO: for (historical) {@link ChooserTargetInfo} targets, this differs from the result of
122      * {@link #getResolvedComponentName()} only for caller-provided targets that we fail to resolve;
123      * then this returns the name of the component that was requested, and the other returns null.
124      * At the time of writing, this method is only called in contexts where the client knows that
125      * the target was a historical {@link ChooserTargetInfo}. Thus this method could be removed and
126      * all clients consolidated on the other, if we have some alternate mechanism of tracking this
127      * discrepancy; or if we know that the distinction won't apply in the conditions when we call
128      * this method; or if we determine that tracking the distinction isn't a requirement for us.
129      */
130     @Nullable
getChooserTargetComponentName()131     default ComponentName getChooserTargetComponentName() {
132         return null;
133     }
134 
135     /**
136      * Start the activity referenced by this target as if the Activity's caller was performing the
137      * start operation.
138      *
139      * @param activity calling Activity (actually) performing the launch
140      * @param options ActivityOptions bundle
141      * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
142      * @return true if the start completed successfully
143      */
startAsCaller(Activity activity, Bundle options, int userId)144     boolean startAsCaller(Activity activity, Bundle options, int userId);
145 
146     /**
147      * Start the activity referenced by this target as a given user.
148      *
149      * @param activity calling activity performing the launch
150      * @param options ActivityOptions bundle
151      * @param user handle for the user to start the activity as
152      * @return true if the start completed successfully
153      */
startAsUser(Activity activity, Bundle options, UserHandle user)154     boolean startAsUser(Activity activity, Bundle options, UserHandle user);
155 
156     /**
157      * Return the ResolveInfo about how and why this target matched the original query
158      * for available targets.
159      *
160      * @return ResolveInfo representing this target's match
161      */
getResolveInfo()162     ResolveInfo getResolveInfo();
163 
164     /**
165      * Return the human-readable text label for this target.
166      *
167      * @return user-visible target label
168      */
getDisplayLabel()169     CharSequence getDisplayLabel();
170 
171     /**
172      * Return any extended info for this target. This may be used to disambiguate
173      * otherwise identical targets.
174      *
175      * @return human-readable disambig string or null if none present
176      */
getExtendedInfo()177     CharSequence getExtendedInfo();
178 
179     /**
180      * @return the {@link IconHolder} for the icon used to represent this target, including badge.
181      *
182      * TODO: while the {@link TargetInfo} may be immutable in always returning the same instance of
183      * {@link IconHolder} here, the holder itself is mutable state, and could become a problem if we
184      * ever rely on {@link TargetInfo} immutability elsewhere. Ideally, the {@link TargetInfo}
185      * should provide an immutable "spec" that tells clients <em>how</em> to load the appropriate
186      * icon, while leaving the load itself to some external component.
187      */
getDisplayIconHolder()188     IconHolder getDisplayIconHolder();
189 
190     /**
191      * @return true if display icon is available.
192      */
hasDisplayIcon()193     default boolean hasDisplayIcon() {
194         return getDisplayIconHolder().getDisplayIcon() != null;
195     }
196 
197     /**
198      * Attempt to apply a {@code proposedRefinement} that the {@link ChooserRefinementManager}
199      * received from the caller's refinement flow. This may succeed only if the target has a source
200      * intent that matches the filtering parameters of the proposed refinement (according to
201      * {@link Intent#filterEquals}). Then the first such match is the "base intent," and the
202      * proposed refinement is merged into that base (via {@link Intent#fillIn}; this can never
203      * result in a change to the {@link Intent#filterEquals} status of the base, but may e.g. add
204      * new "extras" that weren't previously given in the base intent).
205      *
206      * @return a copy of this {@link TargetInfo} where the "base intent to send" is the result of
207      * merging the refinement into the best-matching source intent, if possible. If there is no
208      * suitable match for the proposed refinement, or if merging fails for any other reason, this
209      * returns null.
210      *
211      * @see android.content.Intent#fillIn(Intent, int)
212      */
213     @Nullable
tryToCloneWithAppliedRefinement(Intent proposedRefinement)214     TargetInfo tryToCloneWithAppliedRefinement(Intent proposedRefinement);
215 
216     /**
217      * @return the list of supported source intents deduped against this single target
218      */
getAllSourceIntents()219     List<Intent> getAllSourceIntents();
220 
221     /**
222      * @return the one or more {@link DisplayResolveInfo}s that this target represents in the UI.
223      *
224      * TODO: clarify the semantics of the {@link DisplayResolveInfo} branch of {@link TargetInfo}'s
225      * class hierarchy. Why is it that {@link MultiDisplayResolveInfo} can stand in for some
226      * "virtual" {@link DisplayResolveInfo} targets that aren't individually represented in the UI,
227      * but OTOH a {@link ChooserTargetInfo} (which doesn't inherit from {@link DisplayResolveInfo})
228      * can't provide its own UI treatment, and instead needs us to reach into its composed-in
229      * info via {@link #getDisplayResolveInfo()}? It seems like {@link DisplayResolveInfo} may be
230      * required to populate views in our UI, while {@link ChooserTargetInfo} may carry some other
231      * metadata. For non-{@link ChooserTargetInfo} targets (e.g. in {@link ResolverActivity}) the
232      * "naked" {@link DisplayResolveInfo} might also be taken to provide some of this metadata, but
233      * this presents a denormalization hazard since the "UI info" ({@link DisplayResolveInfo}) that
234      * represents a {@link ChooserTargetInfo} might provide different values than its enclosing
235      * {@link ChooserTargetInfo} (as they both implement {@link TargetInfo}). We could try to
236      * address this by splitting {@link DisplayResolveInfo} into two types; one (which implements
237      * the same {@link TargetInfo} interface as {@link ChooserTargetInfo}) provides the previously-
238      * implicit "metadata", and the other provides only the UI treatment for a target of any type
239      * (taking over the respective methods that previously belonged to {@link TargetInfo}).
240      */
getAllDisplayTargets()241     ArrayList<DisplayResolveInfo> getAllDisplayTargets();
242 
243     /**
244      * @return true if this target cannot be selected by the user
245      */
isSuspended()246     boolean isSuspended();
247 
248     /**
249      * @return true if this target should be pinned to the front by the request of the user
250      */
isPinned()251     boolean isPinned();
252 
253     /**
254      * Determine whether two targets represent "similar" content that could be de-duped.
255      * Note an earlier version of this code cautioned maintainers,
256      * "do not label as 'equals', since this doesn't quite work as intended with java 8."
257      * This seems to refer to the rule that interfaces can't provide defaults that conflict with the
258      * definitions of "real" methods in {@code java.lang.Object}, and (if desired) it could be
259      * presumably resolved by converting {@code TargetInfo} from an interface to an abstract class.
260      */
isSimilar(TargetInfo other)261     default boolean isSimilar(TargetInfo other) {
262         if (other == null) {
263             return false;
264         }
265 
266         // TODO: audit usage and try to reconcile a behavior that doesn't depend on the legacy
267         // subclass type. Note that the `isSimilar()` method was pulled up from the legacy
268         // `ChooserTargetInfo`, so no legacy behavior currently depends on calling `isSimilar()` on
269         // an instance where `isChooserTargetInfo()` would return false (although technically it may
270         // have been possible for the `other` target to be of a different type). Thus we have
271         // flexibility in defining the similarity conditions between pairs of non "chooser" targets.
272         if (isChooserTargetInfo()) {
273             return other.isChooserTargetInfo()
274                     && Objects.equals(
275                             getChooserTargetComponentName(), other.getChooserTargetComponentName())
276                     && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel())
277                     && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo());
278         } else {
279             return !other.isChooserTargetInfo() && Objects.equals(this, other);
280         }
281     }
282 
283     /**
284      * @return the target score, including any Chooser-specific modifications that may have been
285      * applied (either overriding by special-case for "non-selectable" targets, or by twiddling the
286      * scores of "selectable" targets in {@link ChooserListAdapter}). Higher scores are "better."
287      * Targets that aren't intended for ranking/scoring should return a negative value.
288      */
getModifiedScore()289     default float getModifiedScore() {
290         return -0.1f;
291     }
292 
293     /**
294      * @return the {@link ShortcutInfo} for any shortcut associated with this target.
295      */
296     @Nullable
getDirectShareShortcutInfo()297     default ShortcutInfo getDirectShareShortcutInfo() {
298         return null;
299     }
300 
301     /**
302      * @return the ID of the shortcut represented by this target, or null if the target didn't come
303      * from a {@link ShortcutManager} shortcut.
304      */
305     @Nullable
getDirectShareShortcutId()306     default String getDirectShareShortcutId() {
307         ShortcutInfo shortcut = getDirectShareShortcutInfo();
308         if (shortcut == null) {
309             return null;
310         }
311         return shortcut.getId();
312     }
313 
314     /**
315      * @return the {@link AppTarget} metadata if this target was sourced from App Prediction
316      * service, or null otherwise.
317      */
318     @Nullable
getDirectShareAppTarget()319     default AppTarget getDirectShareAppTarget() {
320         return null;
321     }
322 
323     /**
324      * Get more info about this target in the form of a {@link DisplayResolveInfo}, if available.
325      * TODO: this seems to return non-null only for ChooserTargetInfo subclasses. Determine the
326      * meaning of a TargetInfo (ChooserTargetInfo) embedding another kind of TargetInfo
327      * (DisplayResolveInfo) in this way, and - at least - improve this documentation; OTOH this
328      * probably indicates an opportunity to simplify or better separate these APIs. (For example,
329      * targets that <em>don't</em> descend from ChooserTargetInfo instead descend directly from
330      * DisplayResolveInfo; should they return `this`? Do we always use DisplayResolveInfo to
331      * represent visual properties, and then either assume some implicit metadata properties *or*
332      * embed that visual representation within a ChooserTargetInfo to carry additional metadata? If
333      * that's the case, maybe we could decouple by saying that all TargetInfos compose-in their
334      * visual representation [as a DisplayResolveInfo, now the root of its own class hierarchy] and
335      * then add a new TargetInfo type that explicitly represents the "implicit metadata" that we
336      * previously assumed for "naked DisplayResolveInfo targets" that weren't wrapped as
337      * ChooserTargetInfos. Or does all this complexity disappear once we stop relying on the
338      * deprecated ChooserTarget type?)
339      */
340     @Nullable
getDisplayResolveInfo()341     default DisplayResolveInfo getDisplayResolveInfo() {
342         return null;
343     }
344 
345     /**
346      * @return true if this target represents a legacy {@code ChooserTargetInfo}. These objects were
347      * historically documented as representing "[a] TargetInfo for Direct Share." However, not all
348      * of these targets are actually *valid* for direct share; e.g. some represent "empty" items
349      * (although perhaps only for display in the Direct Share UI?). In even earlier versions, these
350      * targets may also have been results from peers in the (now-deprecated/unsupported)
351      * {@code ChooserTargetService} ecosystem; even though we no longer use these services, we're
352      * still shoehorning other target data into the deprecated {@link ChooserTarget} structure for
353      * compatibility with some internal APIs.
354      * TODO: refactor to clarify the semantics of any target for which this method returns true
355      * (e.g., are they characterized by their application in the Direct Share UI?), and to remove
356      * the scaffolding that adapts to and from the {@link ChooserTarget} structure. Eventually, we
357      * expect to remove this method (and others that strictly indicate legacy subclass roles) in
358      * favor of a more semantic design that expresses the purpose and distinctions in those roles.
359      */
isChooserTargetInfo()360     default boolean isChooserTargetInfo() {
361         return false;
362     }
363 
364     /**
365      * @return true if this target represents a legacy {@code DisplayResolveInfo}. These objects
366      * were historically documented as an augmented "TargetInfo plus additional information needed
367      * to render it (such as icon and label) and resolve it to an activity." That description in no
368      * way distinguishes from the base {@code TargetInfo} API. At the time of writing, these objects
369      * are most-clearly defined by their opposite; this returns true for exactly those instances of
370      * {@code TargetInfo} where {@link #isChooserTargetInfo()} returns false (these conditions are
371      * complementary because they correspond to the immediate {@code TargetInfo} child types that
372      * historically partitioned all concrete {@code TargetInfo} implementations). These may(?)
373      * represent any target displayed somewhere other than the Direct Share UI.
374      */
isDisplayResolveInfo()375     default boolean isDisplayResolveInfo() {
376         return false;
377     }
378 
379     /**
380      * @return true if this target represents a legacy {@code MultiDisplayResolveInfo}. These
381      * objects were historically documented as representing "a 'stack' of chooser targets for
382      * various activities within the same component." For historical reasons this currently can
383      * return true only if {@link #isDisplayResolveInfo()} returns true (because the legacy classes
384      * shared an inheritance relationship), but new code should avoid relying on that relationship
385      * since these APIs are "in transition."
386      */
isMultiDisplayResolveInfo()387     default boolean isMultiDisplayResolveInfo() {
388         return false;
389     }
390 
391     /**
392      * @return true if this target represents a legacy {@code SelectableTargetInfo}. Note that this
393      * is defined for legacy compatibility and may not conform to other notions of a "selectable"
394      * target. For historical reasons, this method and {@link #isNotSelectableTargetInfo()} only
395      * partition the {@code TargetInfo} instances for which {@link #isChooserTargetInfo()} returns
396      * true; otherwise <em>both</em> methods return false.
397      * TODO: define selectability for targets not historically from {@code ChooserTargetInfo},
398      * then attempt to replace this with a new method like {@code TargetInfo#isSelectable()} that
399      * actually partitions <em>all</em> target types (after updating client usage as needed).
400      */
isSelectableTargetInfo()401     default boolean isSelectableTargetInfo() {
402         return false;
403     }
404 
405     /**
406      * @return true if this target represents a legacy {@code NotSelectableTargetInfo} (i.e., a
407      * target where {@link #isChooserTargetInfo()} is true but {@link #isSelectableTargetInfo()} is
408      * false). For more information on how this divides the space of targets, see the Javadoc for
409      * {@link #isSelectableTargetInfo()}.
410      */
isNotSelectableTargetInfo()411     default boolean isNotSelectableTargetInfo() {
412         return false;
413     }
414 
415     /**
416      * @return true if this target represents a legacy {@code ChooserActivity#EmptyTargetInfo}. Note
417      * that this is defined for legacy compatibility and may not conform to other notions of an
418      * "empty" target.
419      */
isEmptyTargetInfo()420     default boolean isEmptyTargetInfo() {
421         return false;
422     }
423 
424     /**
425      * @return true if this target represents a legacy {@code ChooserActivity#PlaceHolderTargetInfo}
426      * (defined only for compatibility with historic use in {@link ChooserListAdapter}). For
427      * historic reasons (owing to a legacy subclass relationship) this can return true only if
428      * {@link #isNotSelectableTargetInfo()} also returns true.
429      */
isPlaceHolderTargetInfo()430     default boolean isPlaceHolderTargetInfo() {
431         return false;
432     }
433 
434     /**
435      * @return true if this target should be logged with the "direct_share" metrics category in
436      * {@link ResolverActivity#maybeLogCrossProfileTargetLaunch}. This is defined for legacy
437      * compatibility and is <em>not</em> likely to be a good indicator of whether this is actually a
438      * "direct share" target (e.g. because it historically also applies to "empty" and "placeholder"
439      * targets).
440      */
isInDirectShareMetricsCategory()441     default boolean isInDirectShareMetricsCategory() {
442         return isChooserTargetInfo();
443     }
444 
445     /**
446      * @param context caller's context, to provide the {@link SharedPreferences} for use by the
447      * {@link HashedStringCache}.
448      * @return a hashed ID that should be logged along with our target-selection metrics, or null.
449      * The contents of the plaintext are defined for historical reasons, "the package name + target
450      * name to answer the question if most users share to mostly the same person
451      * or to a bunch of different people." Clients should consider this as opaque data for logging
452      * only; they should not rely on any particular semantics about the value.
453      */
getHashedTargetIdForMetrics(Context context)454     default HashedStringCache.HashResult getHashedTargetIdForMetrics(Context context) {
455         return null;
456     }
457 
458     /**
459      * Fix the URIs in {@code intent} if cross-profile sharing is required. This should be called
460      * before launching the intent as another user.
461      */
prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId)462     static void prepareIntentForCrossProfileLaunch(Intent intent, int targetUserId) {
463         final int currentUserId = UserHandle.myUserId();
464         if (targetUserId != currentUserId) {
465             intent.fixUris(currentUserId);
466         }
467     }
468 
469     /**
470      * refreshes intent's creatorToken with its current intent key fields. This allows
471      * ChooserActivity to still keep original creatorToken's creator uid after making changes to
472      * the intent and still keep it valid.
473      * @param intent the intent's creatorToken needs to up refreshed.
474      */
refreshIntentCreatorToken(Intent intent)475     static void refreshIntentCreatorToken(Intent intent) {
476         if (!preventIntentRedirect()) return;
477         try {
478             intent.setCreatorToken(ActivityManager.getService().refreshIntentCreatorToken(
479                     intent.cloneForCreatorToken()));
480         } catch (RemoteException e) {
481             throw new RuntimeException("Failure from system", e);
482         }
483     }
484 
485     /**
486      * Derive a "complete" intent from a proposed `refinement` intent by merging it into a matching
487      * `base` intent, without modifying the filter-equality properties of the `base` intent, while
488      * still allowing the `refinement` to replace Share "payload" fields.
489      * Note! Callers are responsible for ensuring that the `base` is a suitable match for the given
490      * `refinement`, such that the two can be merged without modifying filter-equality properties.
491      */
mergeRefinementIntoMatchingBaseIntent(Intent base, Intent refinement)492     static Intent mergeRefinementIntoMatchingBaseIntent(Intent base, Intent refinement) {
493         Intent mergedIntent = new Intent(base);
494 
495         /* Copy over any fields from the `refinement` that weren't already specified by the `base`,
496          * along with the refined ClipData (if present, even if that overwrites data given in the
497          * `base` intent).
498          *
499          * Refinement may have modified the payload content stored in the ClipData; such changes
500          * are permitted in refinement since ClipData isn't a factor in the determination of
501          * `Intent.filterEquals()` (which must be preserved as an invariant of refinement). */
502         mergedIntent.fillIn(refinement, Intent.FILL_IN_CLIP_DATA);
503 
504         /* Refinement may also modify payload content held in the 'extras' representation, as again
505          * those attributes aren't a factor in determining filter-equality. There is no `FILL_IN_*`
506          * flag that would allow the refinement to overwrite existing keys in the `base` extras, so
507          * here we have to implement the logic ourselves.
508          *
509          * Note this still doesn't imply that the refined intent is the final authority on extras;
510          * in particular, `SelectableTargetInfo.mActivityStarter` uses `Intent.putExtras(Bundle)` to
511          * merge in the `mChooserTargetIntentExtras` (i.e., the `EXTRA_SHORTCUT_ID`), which will
512          * overwrite any existing value.
513          *
514          * TODO: formalize the precedence and make sure extras are set in the appropriate stages,
515          * instead of relying on maintainers to know that (e.g.) authoritative changes belong in the
516          * `TargetActivityStarter`. Otherwise, any extras-based data that Sharesheet adds internally
517          * might be susceptible to "spoofing" from the refinement activity. */
518         mergedIntent.putExtras(refinement);  // Re-merge extras to favor refinement.
519 
520         // TODO(b/279067078): consider how to populate the "merged" ClipData. The `base`
521         // already has non-null ClipData due to the implicit migration in Intent, so if the
522         // refinement modified any of the payload extras, they *must* also provide a modified
523         // ClipData, or else the updated "extras" payload will be inconsistent with the
524         // pre-refinement ClipData when they're merged together. We may be able to do better,
525         // but there are complicated tradeoffs.
526 
527         return mergedIntent;
528     }
529 }
530