diff options
| author | 2022-11-03 15:14:38 -0400 | |
|---|---|---|
| committer | 2022-11-03 20:58:04 +0000 | |
| commit | 53705d85ff4092c94592b281d3a05403bae01665 (patch) | |
| tree | 08762fd4d451322bb4fce6db8a1280d14bb5bc73 /java/src/com | |
| parent | 349cac312bb06df359699eff522b61e12e3e59c5 (diff) | |
Don't expose `ChooserTarget` from `TargetInfo` API.
`ChooserTarget` is a deprecated API left over from the (now long-gone)
`ChooserTargetService` system, but internally, `SelectableTargetInfo`
still relies on this type for some basic record-keeping. The
`ChooserTarget`s today come from two sources; they're either passed in
from the caller via `EXTRA_CHOOSER_TARGETS`, or synthesized internally
to shoehorn non-`ChooserTarget` data into a `SelectableTargetInfo`.
Prior to this CL, clients throughout the application might reach
through to the composed-in `ChooserTarget` for some fields stored in
that record type. After this CL, any fields accessed in that way are
instead lifted to the `TargetInfo` API so that clients can access them
directly, without going through a `ChooserTarget`. Thus "consuming"
clients don't need to be aware of the `ChooserTarget`, and from their
perspective it's an internal implementation detail of
`SelectableTargetInfo`.
In a first subsequent CL, these fields can be precomputed from the
`ChooserTarget` during the construction of the `SelectableTargetInfo`
so that we don't retain any instances once we've extracted the
relevant data. In a second subsequent CL, we can expose those fields
as initialization parameters; then instead of synthesizing
intermediate `ChooserTarget`s, we can build our synthetic targets as
`SelectableTargetInfo`s directly. The usage in `EXTRA_CHOOSER_TARGETS`
is regrettably part of our public API that was overlooked in the
`ChooserTargetService` deprecation and won't be going away any time
soon, but if we can adapt them to the `SelectableTargetInfo`
representation immediately when we read them out of the request, we
should be able to cut out any other remaining references to
`ChooserTarget`.
Test: atest IntentResolverUnitTests
Bug: 202167050
Change-Id: I931234b0912952a4ccf5f94a88694ac82b527ae4
Diffstat (limited to 'java/src/com')
5 files changed, 104 insertions, 73 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index 96f2f9c6..76c6fb29 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -1723,12 +1723,7 @@ public class ChooserActivity extends ResolverActivity implements targetInfo.isSelectableTargetInfo() ? getTargetIntentFilter() : null; String shortcutTitle = targetInfo.isSelectableTargetInfo() ? targetInfo.getDisplayLabel().toString() : null; - String shortcutIdKey = targetInfo.isSelectableTargetInfo() - ? targetInfo - .getChooserTarget() - .getIntentExtras() - .getString(Intent.EXTRA_SHORTCUT_ID) - : null; + String shortcutIdKey = targetInfo.getDirectShareShortcutId(); ChooserTargetActionsDialogFragment.show( getSupportFragmentManager(), @@ -1816,15 +1811,7 @@ public class ChooserActivity extends ResolverActivity implements switch (currentListAdapter.getPositionTargetType(which)) { case ChooserListAdapter.TARGET_SERVICE: cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET; - // Log the package name + target name to answer the question if most users - // share to mostly the same person or to a bunch of different people. - ChooserTarget target = targetInfo.getChooserTarget(); - directTargetHashed = HashedStringCache.getInstance().hashString( - this, - TAG, - target.getComponentName().getPackageName() - + target.getTitle().toString(), - mMaxHashSaltDays); + directTargetHashed = targetInfo.getHashedTargetIdForMetrics(this); directTargetAlsoRanked = getRankedPosition(targetInfo); if (mCallerChooserTargets != null) { @@ -1894,7 +1881,7 @@ public class ChooserActivity extends ResolverActivity implements private int getRankedPosition(TargetInfo targetInfo) { String targetPackageName = - targetInfo.getChooserTarget().getComponentName().getPackageName(); + targetInfo.getChooserTargetComponentName().getPackageName(); ChooserListAdapter currentListAdapter = mChooserMultiProfilePagerAdapter.getActiveListAdapter(); int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(), @@ -2165,7 +2152,7 @@ public class ChooserActivity extends ResolverActivity implements ShortcutInfo shortcutInfo = chooserTargetInfo.getDirectShareShortcutInfo(); if (shortcutInfo != null) { ComponentName componentName = - chooserTargetInfo.getChooserTarget().getComponentName(); + chooserTargetInfo.getChooserTargetComponentName(); targetIds.add(new AppTargetId( String.format( "%s/%s/%s", diff --git a/java/src/com/android/intentresolver/chooser/ChooserTargetInfo.java b/java/src/com/android/intentresolver/chooser/ChooserTargetInfo.java index 2de901cd..8b9bfb32 100644 --- a/java/src/com/android/intentresolver/chooser/ChooserTargetInfo.java +++ b/java/src/com/android/intentresolver/chooser/ChooserTargetInfo.java @@ -16,9 +16,6 @@ package com.android.intentresolver.chooser; -import android.service.chooser.ChooserTarget; -import android.text.TextUtils; - import java.util.ArrayList; import java.util.Arrays; @@ -42,24 +39,4 @@ public abstract class ChooserTargetInfo implements TargetInfo { } return new ArrayList<>(Arrays.asList(getDisplayResolveInfo())); } - - @Override - public boolean isSimilar(TargetInfo other) { - if (other == null) return false; - - ChooserTarget ct1 = getChooserTarget(); - ChooserTarget ct2 = other.getChooserTarget(); - - // If either is null, there is not enough info to make an informed decision - // about equality, so just exit - if (ct1 == null || ct2 == null) return false; - - if (ct1.getComponentName().equals(ct2.getComponentName()) - && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel()) - && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo())) { - return true; - } - - return false; - } } diff --git a/java/src/com/android/intentresolver/chooser/NotSelectableTargetInfo.java b/java/src/com/android/intentresolver/chooser/NotSelectableTargetInfo.java index 66e2022c..8ec52c8a 100644 --- a/java/src/com/android/intentresolver/chooser/NotSelectableTargetInfo.java +++ b/java/src/com/android/intentresolver/chooser/NotSelectableTargetInfo.java @@ -25,7 +25,6 @@ import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; -import android.service.chooser.ChooserTarget; import com.android.intentresolver.R; import com.android.intentresolver.ResolverActivity; @@ -131,10 +130,6 @@ public abstract class NotSelectableTargetInfo extends ChooserTargetInfo { return -0.1f; } - public ChooserTarget getChooserTarget() { - return null; - } - public boolean isSuspended() { return false; } diff --git a/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java b/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java index 3a3c3e64..7e6e49fb 100644 --- a/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java +++ b/java/src/com/android/intentresolver/chooser/SelectableTargetInfo.java @@ -34,8 +34,10 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.service.chooser.ChooserTarget; import android.text.SpannableStringBuilder; +import android.util.HashedStringCache; import android.util.Log; import com.android.intentresolver.ChooserActivity; @@ -43,6 +45,7 @@ import com.android.intentresolver.ResolverActivity; import com.android.intentresolver.ResolverListAdapter.ActivityInfoPresentationGetter; import com.android.intentresolver.SimpleIconFactory; import com.android.internal.annotations.GuardedBy; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import java.util.ArrayList; import java.util.List; @@ -54,6 +57,14 @@ import java.util.List; public final class SelectableTargetInfo extends ChooserTargetInfo { private static final String TAG = "SelectableTargetInfo"; + private static final String HASHED_STRING_CACHE_TAG = "ChooserActivity"; // For legacy reasons. + private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7; + + private final int mMaxHashSaltDays = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS, + DEFAULT_SALT_EXPIRATION_DAYS); + private final Context mContext; @Nullable private final DisplayResolveInfo mSourceInfo; @@ -280,6 +291,11 @@ public final class SelectableTargetInfo extends ChooserTargetInfo { return null; } + @Override + public ComponentName getChooserTargetComponentName() { + return mChooserTarget.getComponentName(); + } + private Intent getBaseIntentToSend() { Intent result = getResolvedIntent(); if (result == null) { @@ -359,11 +375,6 @@ public final class SelectableTargetInfo extends ChooserTargetInfo { } @Override - public ChooserTarget getChooserTarget() { - return mChooserTarget; - } - - @Override @Nullable public ShortcutInfo getDirectShareShortcutInfo() { return mShortcutInfo; @@ -395,6 +406,18 @@ public final class SelectableTargetInfo extends ChooserTargetInfo { return mIsPinned; } + @Override + public HashedStringCache.HashResult getHashedTargetIdForMetrics(Context context) { + final String plaintext = + mChooserTarget.getComponentName().getPackageName() + + mChooserTarget.getTitle().toString(); + return HashedStringCache.getInstance().hashString( + context, + HASHED_STRING_CACHE_TAG, + plaintext, + mMaxHashSaltDays); + } + @Nullable private static ApplicationInfo getApplicationInfoFromSource( @Nullable DisplayResolveInfo sourceInfo) { diff --git a/java/src/com/android/intentresolver/chooser/TargetInfo.java b/java/src/com/android/intentresolver/chooser/TargetInfo.java index 29c4cfc3..46cd53c6 100644 --- a/java/src/com/android/intentresolver/chooser/TargetInfo.java +++ b/java/src/com/android/intentresolver/chooser/TargetInfo.java @@ -29,6 +29,8 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; import android.service.chooser.ChooserTarget; +import android.text.TextUtils; +import android.util.HashedStringCache; import com.android.intentresolver.ResolverActivity; @@ -52,13 +54,34 @@ public interface TargetInfo { /** * Get the resolved component name that represents this target. Note that this may not * be the component that will be directly launched by calling one of the <code>start</code> - * methods provided; this is the component that will be credited with the launch. + * methods provided; this is the component that will be credited with the launch. This may be + * null if the target was specified by a caller-provided {@link ChooserTarget} that we failed to + * resolve to a component on the system. * * @return the resolved ComponentName for this target */ + @Nullable ComponentName getResolvedComponentName(); /** + * If this target was historically built from a (now-deprecated) {@link ChooserTarget} record, + * get the {@link ComponentName} that would've been provided by that record. + * + * TODO: for (historical) {@link ChooserTargetInfo} targets, this differs from the result of + * {@link #getResolvedComponentName()} only for caller-provided targets that we fail to resolve; + * then this returns the name of the component that was requested, and the other returns null. + * At the time of writing, this method is only called in contexts where the client knows that + * the target was a historical {@link ChooserTargetInfo}. Thus this method could be removed and + * all clients consolidated on the other, if we have some alternate mechanism of tracking this + * discrepancy; or if we know that the distinction won't apply in the conditions when we call + * this method; or if we determine that tracking the distinction isn't a requirement for us. + */ + @Nullable + default ComponentName getChooserTargetComponentName() { + return null; + } + + /** * Start the activity referenced by this target. * * @param activity calling Activity performing the launch @@ -172,7 +195,25 @@ public interface TargetInfo { * presumably resolved by converting {@code TargetInfo} from an interface to an abstract class. */ default boolean isSimilar(TargetInfo other) { - return Objects.equals(this, other); + if (other == null) { + return false; + } + + // TODO: audit usage and try to reconcile a behavior that doesn't depend on the legacy + // subclass type. Note that the `isSimilar()` method was pulled up from the legacy + // `ChooserTargetInfo`, so no legacy behavior currently depends on calling `isSimilar()` on + // an instance where `isChooserTargetInfo()` would return false (although technically it may + // have been possible for the `other` target to be of a different type). Thus we have + // flexibility in defining the similarity conditions between pairs of non "chooser" targets. + if (isChooserTargetInfo()) { + return other.isChooserTargetInfo() + && Objects.equals( + getChooserTargetComponentName(), other.getChooserTargetComponentName()) + && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel()) + && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo()); + } else { + return !other.isChooserTargetInfo() && Objects.equals(this, other); + } } /** @@ -186,29 +227,24 @@ public interface TargetInfo { } /** - * @return the {@link ChooserTarget} record that contains additional data about this target, if - * any. This is only non-null for selectable targets (and probably only Direct Share targets?). - * - * @deprecated {@link ChooserTarget} (and any other related {@code ChooserTargetService} APIs) - * got deprecated as part of sunsetting that old system design, but for historical reasons - * Chooser continues to shoehorn data from other sources into this representation to maintain - * compatibility with legacy internal APIs. New clients should avoid taking any further - * dependencies on the {@link ChooserTarget} type; any data they want to query from those - * records should instead be pulled up to new query methods directly on this class (or on the - * root {@link TargetInfo}). - */ - @Deprecated + * @return the {@link ShortcutManager} data for any shortcut associated with this target. + */ @Nullable - default ChooserTarget getChooserTarget() { + default ShortcutInfo getDirectShareShortcutInfo() { return null; } /** - * @return the {@link ShortcutManager} data for any shortcut associated with this target. + * @return the ID of the shortcut represented by this target, or null if the target didn't come + * from a {@link ShortcutManager} shortcut. */ @Nullable - default ShortcutInfo getDirectShareShortcutInfo() { - return null; + default String getDirectShareShortcutId() { + ShortcutInfo shortcut = getDirectShareShortcutInfo(); + if (shortcut == null) { + return null; + } + return shortcut.getId(); } /** @@ -255,11 +291,11 @@ public interface TargetInfo { * @return true if this target represents a legacy {@code ChooserTargetInfo}. These objects were * historically documented as representing "[a] TargetInfo for Direct Share." However, not all * of these targets are actually *valid* for direct share; e.g. some represent "empty" items - * (although perhaps only for display in the Direct Share UI?). {@link #getChooserTarget()} will - * return null for any of these "invalid" items. In even earlier versions, these targets may - * also have been results from (now-deprecated/unsupported) {@code ChooserTargetService} peers; - * even though we no longer use these services, we're still shoehorning other target data into - * the deprecated {@link ChooserTarget} structure for compatibility with some internal APIs. + * (although perhaps only for display in the Direct Share UI?). In even earlier versions, these + * targets may also have been results from peers in the (now-deprecated/unsupported) + * {@code ChooserTargetService} ecosystem; even though we no longer use these services, we're + * still shoehorning other target data into the deprecated {@link ChooserTarget} structure for + * compatibility with some internal APIs. * TODO: refactor to clarify the semantics of any target for which this method returns true * (e.g., are they characterized by their application in the Direct Share UI?), and to remove * the scaffolding that adapts to and from the {@link ChooserTarget} structure. Eventually, we @@ -352,6 +388,19 @@ public interface TargetInfo { } /** + * @param context caller's context, to provide the {@link SharedPreferences} for use by the + * {@link HashedStringCache}. + * @return a hashed ID that should be logged along with our target-selection metrics, or null. + * The contents of the plaintext are defined for historical reasons, "the package name + target + * name to answer the question if most users share to mostly the same person + * or to a bunch of different people." Clients should consider this as opaque data for logging + * only; they should not rely on any particular semantics about the value. + */ + default HashedStringCache.HashResult getHashedTargetIdForMetrics(Context context) { + return null; + } + + /** * Fix the URIs in {@code intent} if cross-profile sharing is required. This should be called * before launching the intent as another user. */ |