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