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