summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
author Joshua Trask <joshtrask@google.com> 2023-02-07 18:37:20 +0000
committer Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> 2023-02-07 18:37:20 +0000
commite82f56e5baaecfbb5339b2beafb6e103a2f68331 (patch)
treeda5a9ef040a0850ba6ff5c488f09880eeea5628a /java
parent276b55adbeb06f273303ef83008a095911ffa8cb (diff)
parent2d6ec2c5793302a8adae46e614eab9d94798c658 (diff)
Merge "Introduce ImmutableTargetInfo." into tm-qpr-dev am: 0d66c83c4d am: 2d6ec2c579
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/IntentResolver/+/21161321 Change-Id: I4df5764b5939506f890c990826327552683a1042 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Diffstat (limited to 'java')
-rw-r--r--java/src/com/android/intentresolver/chooser/ImmutableTargetInfo.java596
-rw-r--r--java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt496
2 files changed, 1092 insertions, 0 deletions
diff --git a/java/src/com/android/intentresolver/chooser/ImmutableTargetInfo.java b/java/src/com/android/intentresolver/chooser/ImmutableTargetInfo.java
new file mode 100644
index 00000000..315cea4d
--- /dev/null
+++ b/java/src/com/android/intentresolver/chooser/ImmutableTargetInfo.java
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.chooser;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.prediction.AppTarget;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.HashedStringCache;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.intentresolver.ResolverActivity;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An implementation of {@link TargetInfo} with immutable data. Any modifications must be made by
+ * creating a new instance (e.g., via {@link ImmutableTargetInfo#toBuilder()}).
+ */
+public final class ImmutableTargetInfo implements TargetInfo {
+ private static final String TAG = "TargetInfo";
+
+ /** Delegate interface to implement {@link TargetInfo#getHashedTargetIdForMetrics()}. */
+ public interface TargetHashProvider {
+ /** Request a hash for the specified {@code target}. */
+ HashedStringCache.HashResult getHashedTargetIdForMetrics(
+ TargetInfo target, Context context);
+ }
+
+ /** Delegate interface to request that the target be launched by a particular API. */
+ public interface TargetActivityStarter {
+ /**
+ * Request that the delegate use the {@link Activity#startActivity()} API to launch the
+ * specified {@code target}.
+ *
+ * @return true if the target was launched successfully.
+ */
+ boolean start(TargetInfo target, Activity activity, Bundle options);
+
+
+ /**
+ * Request that the delegate use the {@link Activity#startAsCaller()} API to launch the
+ * specified {@code target}.
+ *
+ * @return true if the target was launched successfully.
+ */
+ boolean startAsCaller(TargetInfo target, Activity activity, Bundle options, int userId);
+
+ /**
+ * Request that the delegate use the {@link Activity#startAsUser()} API to launch the
+ * specified {@code target}.
+ *
+ * @return true if the target was launched successfully.
+ */
+ boolean startAsUser(TargetInfo target, Activity activity, Bundle options, UserHandle user);
+ }
+
+ enum LegacyTargetType {
+ NOT_LEGACY_TARGET,
+ EMPTY_TARGET_INFO,
+ PLACEHOLDER_TARGET_INFO,
+ SELECTABLE_TARGET_INFO,
+ DISPLAY_RESOLVE_INFO,
+ MULTI_DISPLAY_RESOLVE_INFO
+ };
+
+ /** Builder API to construct {@code ImmutableTargetInfo} instances. */
+ public static class Builder {
+ @Nullable
+ private ComponentName mResolvedComponentName;
+
+ @Nullable
+ private ComponentName mChooserTargetComponentName;
+
+ @Nullable
+ private ShortcutInfo mDirectShareShortcutInfo;
+
+ @Nullable
+ private AppTarget mDirectShareAppTarget;
+
+ @Nullable
+ private DisplayResolveInfo mDisplayResolveInfo;
+
+ @Nullable
+ private TargetHashProvider mHashProvider;
+
+ @Nullable
+ private Intent mReferrerFillInIntent;
+
+ private Intent mResolvedIntent;
+ private Intent mTargetIntent;
+ private TargetActivityStarter mActivityStarter;
+ private ResolveInfo mResolveInfo;
+ private CharSequence mDisplayLabel;
+ private CharSequence mExtendedInfo;
+ private IconHolder mDisplayIconHolder;
+ private List<Intent> mSourceIntents;
+ private List<DisplayResolveInfo> mAllDisplayTargets;
+ private boolean mIsSuspended;
+ private boolean mIsPinned;
+ private float mModifiedScore = -0.1f;
+ private LegacyTargetType mLegacyType = LegacyTargetType.NOT_LEGACY_TARGET;
+
+ /**
+ * Configure an {@link Intent} to be built in to the output target as the resolution for the
+ * requested target data.
+ */
+ public Builder setResolvedIntent(Intent resolvedIntent) {
+ mResolvedIntent = resolvedIntent;
+ return this;
+ }
+
+ /**
+ * Configure an {@link Intent} to be built in to the output as the "target intent."
+ */
+ public Builder setTargetIntent(Intent targetIntent) {
+ mTargetIntent = targetIntent;
+ return this;
+ }
+
+ /**
+ * Configure a fill-in intent provided by the referrer to be used in populating the launch
+ * intent if the output target is ever selected.
+ *
+ * @see android.content.Intent#fillIn(Intent, int)
+ */
+ public Builder setReferrerFillInIntent(@Nullable Intent referrerFillInIntent) {
+ mReferrerFillInIntent = referrerFillInIntent;
+ return this;
+ }
+
+ /**
+ * Configure a {@link ComponentName} to be built in to the output target, as the real
+ * component we were able to resolve on this device given the available target data.
+ */
+ public Builder setResolvedComponentName(@Nullable ComponentName resolvedComponentName) {
+ mResolvedComponentName = resolvedComponentName;
+ return this;
+ }
+
+ /**
+ * Configure a {@link ComponentName} to be built in to the output target, as the component
+ * supposedly associated with a {@link ChooserTarget} from which the builder data is being
+ * derived.
+ */
+ public Builder setChooserTargetComponentName(@Nullable ComponentName componentName) {
+ mChooserTargetComponentName = componentName;
+ return this;
+ }
+
+ /** Configure the {@link TargetActivityStarter} to be built in to the output target. */
+ public Builder setActivityStarter(TargetActivityStarter activityStarter) {
+ mActivityStarter = activityStarter;
+ return this;
+ }
+
+ /** Configure the {@link ResolveInfo} to be built in to the output target. */
+ public Builder setResolveInfo(ResolveInfo resolveInfo) {
+ mResolveInfo = resolveInfo;
+ return this;
+ }
+
+ /** Configure the display label to be built in to the output target. */
+ public Builder setDisplayLabel(CharSequence displayLabel) {
+ mDisplayLabel = displayLabel;
+ return this;
+ }
+
+ /** Configure the extended info to be built in to the output target. */
+ public Builder setExtendedInfo(CharSequence extendedInfo) {
+ mExtendedInfo = extendedInfo;
+ return this;
+ }
+
+ /** Configure the {@link IconHolder} to be built in to the output target. */
+ public Builder setDisplayIconHolder(IconHolder displayIconHolder) {
+ mDisplayIconHolder = displayIconHolder;
+ return this;
+ }
+
+ /** Configure the list of source intents to be built in to the output target. */
+ public Builder setAllSourceIntents(List<Intent> sourceIntents) {
+ mSourceIntents = sourceIntents;
+ return this;
+ }
+
+ /** Configure the list of display targets to be built in to the output target. */
+ public Builder setAllDisplayTargets(List<DisplayResolveInfo> targets) {
+ mAllDisplayTargets = targets;
+ return this;
+ }
+
+ /** Configure the is-suspended status to be built in to the output target. */
+ public Builder setIsSuspended(boolean isSuspended) {
+ mIsSuspended = isSuspended;
+ return this;
+ }
+
+ /** Configure the is-pinned status to be built in to the output target. */
+ public Builder setIsPinned(boolean isPinned) {
+ mIsPinned = isPinned;
+ return this;
+ }
+
+ /** Configure the modified score to be built in to the output target. */
+ public Builder setModifiedScore(float modifiedScore) {
+ mModifiedScore = modifiedScore;
+ return this;
+ }
+
+ /** Configure the {@link ShortcutInfo} to be built in to the output target. */
+ public Builder setDirectShareShortcutInfo(@Nullable ShortcutInfo shortcutInfo) {
+ mDirectShareShortcutInfo = shortcutInfo;
+ return this;
+ }
+
+ /** Configure the {@link AppTarget} to be built in to the output target. */
+ public Builder setDirectShareAppTarget(@Nullable AppTarget appTarget) {
+ mDirectShareAppTarget = appTarget;
+ return this;
+ }
+
+ /** Configure the {@link DisplayResolveInfo} to be built in to the output target. */
+ public Builder setDisplayResolveInfo(@Nullable DisplayResolveInfo displayResolveInfo) {
+ mDisplayResolveInfo = displayResolveInfo;
+ return this;
+ }
+
+ /** Configure the {@link TargetHashProvider} to be built in to the output target. */
+ public Builder setHashProvider(@Nullable TargetHashProvider hashProvider) {
+ mHashProvider = hashProvider;
+ return this;
+ }
+
+ Builder setLegacyType(LegacyTargetType legacyType) {
+ mLegacyType = legacyType;
+ return this;
+ }
+
+ /**
+ * Construct an {@code ImmutableTargetInfo} with the current builder data, where the
+ * provided intent is used to fill in missing values from the resolved intent before the
+ * target is (potentially) ever launched.
+ *
+ * @see android.content.Intent#fillIn(Intent, int)
+ */
+ public ImmutableTargetInfo buildWithFillInIntent(
+ @Nullable Intent fillInIntent, int fillInFlags) {
+ Intent baseIntentToSend = mResolvedIntent;
+ if (baseIntentToSend == null) {
+ Log.w(TAG, "No base intent to send");
+ } else {
+ baseIntentToSend = new Intent(baseIntentToSend);
+ if (fillInIntent != null) {
+ baseIntentToSend.fillIn(fillInIntent, fillInFlags);
+ }
+ if (mReferrerFillInIntent != null) {
+ baseIntentToSend.fillIn(mReferrerFillInIntent, 0);
+ }
+ }
+
+ return new ImmutableTargetInfo(
+ baseIntentToSend,
+ mResolvedIntent,
+ mTargetIntent,
+ mReferrerFillInIntent,
+ mResolvedComponentName,
+ mChooserTargetComponentName,
+ mActivityStarter,
+ mResolveInfo,
+ mDisplayLabel,
+ mExtendedInfo,
+ mDisplayIconHolder,
+ mSourceIntents,
+ mAllDisplayTargets,
+ mIsSuspended,
+ mIsPinned,
+ mModifiedScore,
+ mDirectShareShortcutInfo,
+ mDirectShareAppTarget,
+ mDisplayResolveInfo,
+ mHashProvider,
+ mLegacyType);
+ }
+
+ /** Construct an {@code ImmutableTargetInfo} with the current builder data. */
+ public ImmutableTargetInfo build() {
+ return buildWithFillInIntent(null, 0);
+ }
+ }
+
+ @Nullable
+ private final Intent mReferrerFillInIntent;
+
+ @Nullable
+ private final ComponentName mResolvedComponentName;
+
+ @Nullable
+ private final ComponentName mChooserTargetComponentName;
+
+ @Nullable
+ private final ShortcutInfo mDirectShareShortcutInfo;
+
+ @Nullable
+ private final AppTarget mDirectShareAppTarget;
+
+ @Nullable
+ private final DisplayResolveInfo mDisplayResolveInfo;
+
+ @Nullable
+ private final TargetHashProvider mHashProvider;
+
+ private final Intent mBaseIntentToSend;
+ private final Intent mResolvedIntent;
+ private final Intent mTargetIntent;
+ private final TargetActivityStarter mActivityStarter;
+ private final ResolveInfo mResolveInfo;
+ private final CharSequence mDisplayLabel;
+ private final CharSequence mExtendedInfo;
+ private final IconHolder mDisplayIconHolder;
+ private final ImmutableList<Intent> mSourceIntents;
+ private final ImmutableList<DisplayResolveInfo> mAllDisplayTargets;
+ private final boolean mIsSuspended;
+ private final boolean mIsPinned;
+ private final float mModifiedScore;
+ private final LegacyTargetType mLegacyType;
+
+ /** Construct a {@link Builder}. */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /** Construct a {@link Builder} pre-initialized to match this target. */
+ public Builder toBuilder() {
+ return newBuilder()
+ .setResolvedIntent(getResolvedIntent())
+ .setTargetIntent(getTargetIntent())
+ .setReferrerFillInIntent(getReferrerFillInIntent())
+ .setResolvedComponentName(getResolvedComponentName())
+ .setChooserTargetComponentName(getChooserTargetComponentName())
+ .setActivityStarter(mActivityStarter)
+ .setResolveInfo(getResolveInfo())
+ .setDisplayLabel(getDisplayLabel())
+ .setExtendedInfo(getExtendedInfo())
+ .setDisplayIconHolder(getDisplayIconHolder())
+ .setAllSourceIntents(getAllSourceIntents())
+ .setAllDisplayTargets(getAllDisplayTargets())
+ .setIsSuspended(isSuspended())
+ .setIsPinned(isPinned())
+ .setModifiedScore(getModifiedScore())
+ .setDirectShareShortcutInfo(getDirectShareShortcutInfo())
+ .setDirectShareAppTarget(getDirectShareAppTarget())
+ .setDisplayResolveInfo(getDisplayResolveInfo())
+ .setHashProvider(getHashProvider())
+ .setLegacyType(mLegacyType);
+ }
+
+ @VisibleForTesting
+ Intent getBaseIntentToSend() {
+ return mBaseIntentToSend;
+ }
+
+ @Override
+ public ImmutableTargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
+ return toBuilder().buildWithFillInIntent(fillInIntent, flags);
+ }
+
+ @Override
+ public Intent getResolvedIntent() {
+ return mResolvedIntent;
+ }
+
+ @Override
+ public Intent getTargetIntent() {
+ return mTargetIntent;
+ }
+
+ @Nullable
+ public Intent getReferrerFillInIntent() {
+ return mReferrerFillInIntent;
+ }
+
+ @Override
+ @Nullable
+ public ComponentName getResolvedComponentName() {
+ return mResolvedComponentName;
+ }
+
+ @Override
+ @Nullable
+ public ComponentName getChooserTargetComponentName() {
+ return mChooserTargetComponentName;
+ }
+
+ @Override
+ public boolean start(Activity activity, Bundle options) {
+ return mActivityStarter.start(this, activity, options);
+ }
+
+ @Override
+ public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+ return mActivityStarter.startAsCaller(this, activity, options, userId);
+ }
+
+ @Override
+ public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+ return mActivityStarter.startAsUser(this, activity, options, user);
+ }
+
+ @Override
+ public ResolveInfo getResolveInfo() {
+ return mResolveInfo;
+ }
+
+ @Override
+ public CharSequence getDisplayLabel() {
+ return mDisplayLabel;
+ }
+
+ @Override
+ public CharSequence getExtendedInfo() {
+ return mExtendedInfo;
+ }
+
+ @Override
+ public IconHolder getDisplayIconHolder() {
+ return mDisplayIconHolder;
+ }
+
+ @Override
+ public List<Intent> getAllSourceIntents() {
+ return mSourceIntents;
+ }
+
+ @Override
+ public ArrayList<DisplayResolveInfo> getAllDisplayTargets() {
+ ArrayList<DisplayResolveInfo> targets = new ArrayList<>();
+ targets.addAll(mAllDisplayTargets);
+ return targets;
+ }
+
+ @Override
+ public boolean isSuspended() {
+ return mIsSuspended;
+ }
+
+ @Override
+ public boolean isPinned() {
+ return mIsPinned;
+ }
+
+ @Override
+ public float getModifiedScore() {
+ return mModifiedScore;
+ }
+
+ @Override
+ @Nullable
+ public ShortcutInfo getDirectShareShortcutInfo() {
+ return mDirectShareShortcutInfo;
+ }
+
+ @Override
+ @Nullable
+ public AppTarget getDirectShareAppTarget() {
+ return mDirectShareAppTarget;
+ }
+
+ @Override
+ @Nullable
+ public DisplayResolveInfo getDisplayResolveInfo() {
+ return mDisplayResolveInfo;
+ }
+
+ @Override
+ public HashedStringCache.HashResult getHashedTargetIdForMetrics(Context context) {
+ return (mHashProvider == null)
+ ? null : mHashProvider.getHashedTargetIdForMetrics(this, context);
+ }
+
+ @VisibleForTesting
+ @Nullable
+ TargetHashProvider getHashProvider() {
+ return mHashProvider;
+ }
+
+ @Override
+ public boolean isEmptyTargetInfo() {
+ return mLegacyType == LegacyTargetType.EMPTY_TARGET_INFO;
+ }
+
+ @Override
+ public boolean isPlaceHolderTargetInfo() {
+ return mLegacyType == LegacyTargetType.PLACEHOLDER_TARGET_INFO;
+ }
+
+ @Override
+ public boolean isNotSelectableTargetInfo() {
+ return isEmptyTargetInfo() || isPlaceHolderTargetInfo();
+ }
+
+ @Override
+ public boolean isSelectableTargetInfo() {
+ return mLegacyType == LegacyTargetType.SELECTABLE_TARGET_INFO;
+ }
+
+ @Override
+ public boolean isChooserTargetInfo() {
+ return isNotSelectableTargetInfo() || isSelectableTargetInfo();
+ }
+
+ @Override
+ public boolean isMultiDisplayResolveInfo() {
+ return mLegacyType == LegacyTargetType.MULTI_DISPLAY_RESOLVE_INFO;
+ }
+
+ @Override
+ public boolean isDisplayResolveInfo() {
+ return (mLegacyType == LegacyTargetType.DISPLAY_RESOLVE_INFO)
+ || isMultiDisplayResolveInfo();
+ }
+
+ private ImmutableTargetInfo(
+ Intent baseIntentToSend,
+ Intent resolvedIntent,
+ Intent targetIntent,
+ @Nullable Intent referrerFillInIntent,
+ @Nullable ComponentName resolvedComponentName,
+ @Nullable ComponentName chooserTargetComponentName,
+ TargetActivityStarter activityStarter,
+ ResolveInfo resolveInfo,
+ CharSequence displayLabel,
+ CharSequence extendedInfo,
+ IconHolder iconHolder,
+ @Nullable List<Intent> sourceIntents,
+ @Nullable List<DisplayResolveInfo> allDisplayTargets,
+ boolean isSuspended,
+ boolean isPinned,
+ float modifiedScore,
+ @Nullable ShortcutInfo directShareShortcutInfo,
+ @Nullable AppTarget directShareAppTarget,
+ @Nullable DisplayResolveInfo displayResolveInfo,
+ @Nullable TargetHashProvider hashProvider,
+ LegacyTargetType legacyType) {
+ mBaseIntentToSend = baseIntentToSend;
+ mResolvedIntent = resolvedIntent;
+ mTargetIntent = targetIntent;
+ mReferrerFillInIntent = referrerFillInIntent;
+ mResolvedComponentName = resolvedComponentName;
+ mChooserTargetComponentName = chooserTargetComponentName;
+ mActivityStarter = activityStarter;
+ mResolveInfo = resolveInfo;
+ mDisplayLabel = displayLabel;
+ mExtendedInfo = extendedInfo;
+ mDisplayIconHolder = iconHolder;
+ mSourceIntents = immutableCopyOrEmpty(sourceIntents);
+ mAllDisplayTargets = immutableCopyOrEmpty(allDisplayTargets);
+ mIsSuspended = isSuspended;
+ mIsPinned = isPinned;
+ mModifiedScore = modifiedScore;
+ mDirectShareShortcutInfo = directShareShortcutInfo;
+ mDirectShareAppTarget = directShareAppTarget;
+ mDisplayResolveInfo = displayResolveInfo;
+ mHashProvider = hashProvider;
+ mLegacyType = legacyType;
+ }
+
+ private static <E> ImmutableList<E> immutableCopyOrEmpty(@Nullable List<E> source) {
+ return (source == null) ? ImmutableList.of() : ImmutableList.copyOf(source);
+ }
+}
diff --git a/java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt b/java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt
new file mode 100644
index 00000000..4d825f6b
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/chooser/ImmutableTargetInfoTest.kt
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *3
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver.chooser
+
+import android.app.Activity
+import android.app.prediction.AppTarget
+import android.app.prediction.AppTargetId
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ResolveInfo
+import android.os.Bundle
+import android.os.UserHandle
+import com.android.intentresolver.createShortcutInfo
+import com.android.intentresolver.mock
+import com.android.intentresolver.ResolverActivity
+import com.android.intentresolver.ResolverDataProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class ImmutableTargetInfoTest {
+ private val resolvedIntent = Intent("resolved")
+ private val targetIntent = Intent("target")
+ private val referrerFillInIntent = Intent("referrer_fillin")
+ private val resolvedComponentName = ComponentName("resolved", "component")
+ private val chooserTargetComponentName = ComponentName("chooser", "target")
+ private val resolveInfo = ResolverDataProvider.createResolveInfo(1, 0)
+ private val displayLabel: CharSequence = "Display Label"
+ private val extendedInfo: CharSequence = "Extended Info"
+ private val displayIconHolder: TargetInfo.IconHolder = mock()
+ private val sourceIntent1 = Intent("source1")
+ private val sourceIntent2 = Intent("source2")
+ private val displayTarget1 = DisplayResolveInfo.newDisplayResolveInfo(
+ Intent("display1"),
+ ResolverDataProvider.createResolveInfo(2, 0),
+ "display1 label",
+ "display1 extended info",
+ Intent("display1_resolved"),
+ /* resolveInfoPresentationGetter= */ null)
+ private val displayTarget2 = DisplayResolveInfo.newDisplayResolveInfo(
+ Intent("display2"),
+ ResolverDataProvider.createResolveInfo(3, 0),
+ "display2 label",
+ "display2 extended info",
+ Intent("display2_resolved"),
+ /* resolveInfoPresentationGetter= */ null)
+ private val directShareShortcutInfo = createShortcutInfo(
+ "shortcutid", ResolverDataProvider.createComponentName(4), 4)
+ private val directShareAppTarget = AppTarget(
+ AppTargetId("apptargetid"),
+ "test.directshare",
+ "target",
+ UserHandle.CURRENT)
+ private val displayResolveInfo = DisplayResolveInfo.newDisplayResolveInfo(
+ Intent("displayresolve"),
+ ResolverDataProvider.createResolveInfo(5, 0),
+ "displayresolve label",
+ "displayresolve extended info",
+ Intent("display_resolved"),
+ /* resolveInfoPresentationGetter= */ null)
+ private val hashProvider: ImmutableTargetInfo.TargetHashProvider = mock()
+
+ @Test
+ fun testBasicProperties() { // Fields that are reflected back w/o logic.
+ // TODO: we could consider passing copies of all the values into the builder so that we can
+ // verify that they're not mutated (e.g. no extras added to the intents). For now that
+ // should be obvious from the implementation.
+ val info = ImmutableTargetInfo.newBuilder()
+ .setResolvedIntent(resolvedIntent)
+ .setTargetIntent(targetIntent)
+ .setReferrerFillInIntent(referrerFillInIntent)
+ .setResolvedComponentName(resolvedComponentName)
+ .setChooserTargetComponentName(chooserTargetComponentName)
+ .setResolveInfo(resolveInfo)
+ .setDisplayLabel(displayLabel)
+ .setExtendedInfo(extendedInfo)
+ .setDisplayIconHolder(displayIconHolder)
+ .setAllSourceIntents(listOf(sourceIntent1, sourceIntent2))
+ .setAllDisplayTargets(listOf(displayTarget1, displayTarget2))
+ .setIsSuspended(true)
+ .setIsPinned(true)
+ .setModifiedScore(42.0f)
+ .setDirectShareShortcutInfo(directShareShortcutInfo)
+ .setDirectShareAppTarget(directShareAppTarget)
+ .setDisplayResolveInfo(displayResolveInfo)
+ .setHashProvider(hashProvider)
+ .build()
+
+ assertThat(info.resolvedIntent).isEqualTo(resolvedIntent)
+ assertThat(info.targetIntent).isEqualTo(targetIntent)
+ assertThat(info.referrerFillInIntent).isEqualTo(referrerFillInIntent)
+ assertThat(info.resolvedComponentName).isEqualTo(resolvedComponentName)
+ assertThat(info.chooserTargetComponentName).isEqualTo(chooserTargetComponentName)
+ assertThat(info.resolveInfo).isEqualTo(resolveInfo)
+ assertThat(info.displayLabel).isEqualTo(displayLabel)
+ assertThat(info.extendedInfo).isEqualTo(extendedInfo)
+ assertThat(info.displayIconHolder).isEqualTo(displayIconHolder)
+ assertThat(info.allSourceIntents).containsExactly(sourceIntent1, sourceIntent2)
+ assertThat(info.allDisplayTargets).containsExactly(displayTarget1, displayTarget2)
+ assertThat(info.isSuspended).isTrue()
+ assertThat(info.isPinned).isTrue()
+ assertThat(info.modifiedScore).isEqualTo(42.0f)
+ assertThat(info.directShareShortcutInfo).isEqualTo(directShareShortcutInfo)
+ assertThat(info.directShareAppTarget).isEqualTo(directShareAppTarget)
+ assertThat(info.displayResolveInfo).isEqualTo(displayResolveInfo)
+ assertThat(info.isEmptyTargetInfo).isFalse()
+ assertThat(info.isPlaceHolderTargetInfo).isFalse()
+ assertThat(info.isNotSelectableTargetInfo).isFalse()
+ assertThat(info.isSelectableTargetInfo).isFalse()
+ assertThat(info.isChooserTargetInfo).isFalse()
+ assertThat(info.isMultiDisplayResolveInfo).isFalse()
+ assertThat(info.isDisplayResolveInfo).isFalse()
+ assertThat(info.hashProvider).isEqualTo(hashProvider)
+ }
+
+ @Test
+ fun testToBuilderPreservesBasicProperties() {
+ // Note this is set up exactly as in `testBasicProperties`, but the assertions will be made
+ // against a *copy* of the object instead.
+ val infoToCopyFrom = ImmutableTargetInfo.newBuilder()
+ .setResolvedIntent(resolvedIntent)
+ .setTargetIntent(targetIntent)
+ .setReferrerFillInIntent(referrerFillInIntent)
+ .setResolvedComponentName(resolvedComponentName)
+ .setChooserTargetComponentName(chooserTargetComponentName)
+ .setResolveInfo(resolveInfo)
+ .setDisplayLabel(displayLabel)
+ .setExtendedInfo(extendedInfo)
+ .setDisplayIconHolder(displayIconHolder)
+ .setAllSourceIntents(listOf(sourceIntent1, sourceIntent2))
+ .setAllDisplayTargets(listOf(displayTarget1, displayTarget2))
+ .setIsSuspended(true)
+ .setIsPinned(true)
+ .setModifiedScore(42.0f)
+ .setDirectShareShortcutInfo(directShareShortcutInfo)
+ .setDirectShareAppTarget(directShareAppTarget)
+ .setDisplayResolveInfo(displayResolveInfo)
+ .setHashProvider(hashProvider)
+ .build()
+
+ val info = infoToCopyFrom.toBuilder().build()
+
+ assertThat(info.resolvedIntent).isEqualTo(resolvedIntent)
+ assertThat(info.targetIntent).isEqualTo(targetIntent)
+ assertThat(info.referrerFillInIntent).isEqualTo(referrerFillInIntent)
+ assertThat(info.resolvedComponentName).isEqualTo(resolvedComponentName)
+ assertThat(info.chooserTargetComponentName).isEqualTo(chooserTargetComponentName)
+ assertThat(info.resolveInfo).isEqualTo(resolveInfo)
+ assertThat(info.displayLabel).isEqualTo(displayLabel)
+ assertThat(info.extendedInfo).isEqualTo(extendedInfo)
+ assertThat(info.displayIconHolder).isEqualTo(displayIconHolder)
+ assertThat(info.allSourceIntents).containsExactly(sourceIntent1, sourceIntent2)
+ assertThat(info.allDisplayTargets).containsExactly(displayTarget1, displayTarget2)
+ assertThat(info.isSuspended).isTrue()
+ assertThat(info.isPinned).isTrue()
+ assertThat(info.modifiedScore).isEqualTo(42.0f)
+ assertThat(info.directShareShortcutInfo).isEqualTo(directShareShortcutInfo)
+ assertThat(info.directShareAppTarget).isEqualTo(directShareAppTarget)
+ assertThat(info.displayResolveInfo).isEqualTo(displayResolveInfo)
+ assertThat(info.isEmptyTargetInfo).isFalse()
+ assertThat(info.isPlaceHolderTargetInfo).isFalse()
+ assertThat(info.isNotSelectableTargetInfo).isFalse()
+ assertThat(info.isSelectableTargetInfo).isFalse()
+ assertThat(info.isChooserTargetInfo).isFalse()
+ assertThat(info.isMultiDisplayResolveInfo).isFalse()
+ assertThat(info.isDisplayResolveInfo).isFalse()
+ assertThat(info.hashProvider).isEqualTo(hashProvider)
+ }
+
+ @Test
+ fun testBaseIntentToSend_defaultsToResolvedIntent() {
+ val info = ImmutableTargetInfo.newBuilder().setResolvedIntent(resolvedIntent).build()
+ assertThat(info.baseIntentToSend.filterEquals(resolvedIntent)).isTrue()
+ }
+
+ @Test
+ fun testBaseIntentToSend_fillsInFromReferrerIntent() {
+ val originalIntent = Intent()
+ originalIntent.setPackage("original")
+
+ val referrerFillInIntent = Intent("REFERRER_FILL_IN")
+ referrerFillInIntent.setPackage("referrer")
+
+ val info = ImmutableTargetInfo.newBuilder()
+ .setResolvedIntent(originalIntent)
+ .setReferrerFillInIntent(referrerFillInIntent)
+ .build()
+
+ assertThat(info.baseIntentToSend.getPackage()).isEqualTo("original") // Only fill if empty.
+ assertThat(info.baseIntentToSend.action).isEqualTo("REFERRER_FILL_IN")
+ }
+
+ @Test
+ fun testBaseIntentToSend_fillsInFromCloneRequestIntent() {
+ val originalIntent = Intent()
+ originalIntent.setPackage("original")
+
+ val cloneFillInIntent = Intent("CLONE_FILL_IN")
+ cloneFillInIntent.setPackage("clone")
+
+ val originalInfo = ImmutableTargetInfo.newBuilder()
+ .setResolvedIntent(originalIntent)
+ .build()
+ val info = originalInfo.cloneFilledIn(cloneFillInIntent, 0)
+
+ assertThat(info.baseIntentToSend.getPackage()).isEqualTo("original") // Only fill if empty.
+ assertThat(info.baseIntentToSend.action).isEqualTo("CLONE_FILL_IN")
+ }
+
+ @Test
+ fun testBaseIntentToSend_twoFillInSourcesFavorsCloneRequest() {
+ val originalIntent = Intent()
+ originalIntent.setPackage("original")
+
+ val referrerFillInIntent = Intent("REFERRER_FILL_IN")
+ referrerFillInIntent.setPackage("referrer_pkg")
+ referrerFillInIntent.setType("test/referrer")
+
+ val infoWithReferrerFillIn = ImmutableTargetInfo.newBuilder()
+ .setResolvedIntent(originalIntent)
+ .setReferrerFillInIntent(referrerFillInIntent)
+ .build()
+
+ val cloneFillInIntent = Intent("CLONE_FILL_IN")
+ cloneFillInIntent.setPackage("clone")
+
+ val info = infoWithReferrerFillIn.cloneFilledIn(cloneFillInIntent, 0)
+
+ assertThat(info.baseIntentToSend.getPackage()).isEqualTo("original") // Set all along.
+ assertThat(info.baseIntentToSend.action).isEqualTo("CLONE_FILL_IN") // Clone wins.
+ assertThat(info.baseIntentToSend.type).isEqualTo("test/referrer") // Left for referrer.
+ }
+
+ @Test
+ fun testBaseIntentToSend_doubleCloningPreservesReferrerFillInButNotOriginalCloneFillIn() {
+ val originalIntent = Intent()
+ val referrerFillInIntent = Intent("REFERRER_FILL_IN")
+ val cloneFillInIntent1 = Intent()
+ cloneFillInIntent1.setPackage("clone1")
+ val cloneFillInIntent2 = Intent()
+ cloneFillInIntent2.setType("test/clone2")
+
+ val originalInfo = ImmutableTargetInfo.newBuilder()
+ .setResolvedIntent(originalIntent)
+ .setReferrerFillInIntent(referrerFillInIntent)
+ .build()
+
+ val clone1 = originalInfo.cloneFilledIn(cloneFillInIntent1, 0)
+ val clone2 = clone1.cloneFilledIn(cloneFillInIntent2, 0) // Clone-of-clone.
+
+ // Both clones get the same values filled in from the referrer intent.
+ assertThat(clone1.baseIntentToSend.action).isEqualTo("REFERRER_FILL_IN")
+ assertThat(clone2.baseIntentToSend.action).isEqualTo("REFERRER_FILL_IN")
+ // Each clone has the respective value that was set in the fill-in request.
+ assertThat(clone1.baseIntentToSend.getPackage()).isEqualTo("clone1")
+ assertThat(clone2.baseIntentToSend.type).isEqualTo("test/clone2")
+ // The clones don't have the data from each other's fill-in requests, even though the intent
+ // field is empty (thus able to be populated by filling-in).
+ assertThat(clone1.baseIntentToSend.type).isNull()
+ assertThat(clone2.baseIntentToSend.getPackage()).isNull()
+ }
+
+ @Test
+ fun testLegacySubclassRelationships_empty() {
+ val info = ImmutableTargetInfo.newBuilder()
+ .setLegacyType(ImmutableTargetInfo.LegacyTargetType.EMPTY_TARGET_INFO)
+ .build()
+
+ assertThat(info.isEmptyTargetInfo).isTrue()
+ assertThat(info.isPlaceHolderTargetInfo).isFalse()
+ assertThat(info.isNotSelectableTargetInfo).isTrue()
+ assertThat(info.isSelectableTargetInfo).isFalse()
+ assertThat(info.isChooserTargetInfo).isTrue()
+ assertThat(info.isMultiDisplayResolveInfo).isFalse()
+ assertThat(info.isDisplayResolveInfo).isFalse()
+ }
+
+ @Test
+ fun testLegacySubclassRelationships_placeholder() {
+ val info = ImmutableTargetInfo.newBuilder()
+ .setLegacyType(ImmutableTargetInfo.LegacyTargetType.PLACEHOLDER_TARGET_INFO)
+ .build()
+
+ assertThat(info.isEmptyTargetInfo).isFalse()
+ assertThat(info.isPlaceHolderTargetInfo).isTrue()
+ assertThat(info.isNotSelectableTargetInfo).isTrue()
+ assertThat(info.isSelectableTargetInfo).isFalse()
+ assertThat(info.isChooserTargetInfo).isTrue()
+ assertThat(info.isMultiDisplayResolveInfo).isFalse()
+ assertThat(info.isDisplayResolveInfo).isFalse()
+ }
+
+ @Test
+ fun testLegacySubclassRelationships_selectable() {
+ val info = ImmutableTargetInfo.newBuilder()
+ .setLegacyType(ImmutableTargetInfo.LegacyTargetType.SELECTABLE_TARGET_INFO)
+ .build()
+
+ assertThat(info.isEmptyTargetInfo).isFalse()
+ assertThat(info.isPlaceHolderTargetInfo).isFalse()
+ assertThat(info.isNotSelectableTargetInfo).isFalse()
+ assertThat(info.isSelectableTargetInfo).isTrue()
+ assertThat(info.isChooserTargetInfo).isTrue()
+ assertThat(info.isMultiDisplayResolveInfo).isFalse()
+ assertThat(info.isDisplayResolveInfo).isFalse()
+ }
+
+ @Test
+ fun testLegacySubclassRelationships_displayResolveInfo() {
+ val info = ImmutableTargetInfo.newBuilder()
+ .setLegacyType(ImmutableTargetInfo.LegacyTargetType.DISPLAY_RESOLVE_INFO)
+ .build()
+
+ assertThat(info.isEmptyTargetInfo).isFalse()
+ assertThat(info.isPlaceHolderTargetInfo).isFalse()
+ assertThat(info.isNotSelectableTargetInfo).isFalse()
+ assertThat(info.isSelectableTargetInfo).isFalse()
+ assertThat(info.isChooserTargetInfo).isFalse()
+ assertThat(info.isMultiDisplayResolveInfo).isFalse()
+ assertThat(info.isDisplayResolveInfo).isTrue()
+ }
+
+ @Test
+ fun testLegacySubclassRelationships_multiDisplayResolveInfo() {
+ val info = ImmutableTargetInfo.newBuilder()
+ .setLegacyType(ImmutableTargetInfo.LegacyTargetType.MULTI_DISPLAY_RESOLVE_INFO)
+ .build()
+
+ assertThat(info.isEmptyTargetInfo).isFalse()
+ assertThat(info.isPlaceHolderTargetInfo).isFalse()
+ assertThat(info.isNotSelectableTargetInfo).isFalse()
+ assertThat(info.isSelectableTargetInfo).isFalse()
+ assertThat(info.isChooserTargetInfo).isFalse()
+ assertThat(info.isMultiDisplayResolveInfo).isTrue()
+ assertThat(info.isDisplayResolveInfo).isTrue()
+ }
+
+ @Test
+ fun testActivityStarter_correctNumberOfInvocations_start() {
+ val activityStarter = object : TestActivityStarter() {
+ override fun startAsCaller(
+ target: TargetInfo, activity: Activity, options: Bundle, userId: Int): Boolean {
+ throw RuntimeException("Wrong API used: startAsCaller")
+ }
+
+ override fun startAsUser(
+ target: TargetInfo, activity: Activity, options: Bundle, user: UserHandle
+ ): Boolean {
+ throw RuntimeException("Wrong API used: startAsUser")
+ }
+ }
+
+ val info = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build()
+ val activity: Activity = mock()
+ val options = Bundle()
+ options.putInt("TEST_KEY", 1)
+
+ info.start(activity, options)
+
+ assertThat(activityStarter.totalInvocations).isEqualTo(1)
+ assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info)
+ assertThat(activityStarter.lastInvocationActivity).isEqualTo(activity)
+ assertThat(activityStarter.lastInvocationOptions).isEqualTo(options)
+ assertThat(activityStarter.lastInvocationUserId).isNull()
+ assertThat(activityStarter.lastInvocationAsCaller).isFalse()
+ }
+
+ @Test
+ fun testActivityStarter_correctNumberOfInvocations_startAsCaller() {
+ val activityStarter = object : TestActivityStarter() {
+ override fun start(target: TargetInfo, activity: Activity, options: Bundle): Boolean {
+ throw RuntimeException("Wrong API used: start")
+ }
+
+ override fun startAsUser(
+ target: TargetInfo, activity: Activity, options: Bundle, user: UserHandle
+ ): Boolean {
+ throw RuntimeException("Wrong API used: startAsUser")
+ }
+ }
+
+ val info = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build()
+ val activity: ResolverActivity = mock()
+ val options = Bundle()
+ options.putInt("TEST_KEY", 1)
+
+ info.startAsCaller(activity, options, 42)
+
+ assertThat(activityStarter.totalInvocations).isEqualTo(1)
+ assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info)
+ assertThat(activityStarter.lastInvocationActivity).isEqualTo(activity)
+ assertThat(activityStarter.lastInvocationOptions).isEqualTo(options)
+ assertThat(activityStarter.lastInvocationUserId).isEqualTo(42)
+ assertThat(activityStarter.lastInvocationAsCaller).isTrue()
+ }
+
+ @Test
+ fun testActivityStarter_correctNumberOfInvocations_startAsUser() {
+ val activityStarter = object : TestActivityStarter() {
+ override fun start(target: TargetInfo, activity: Activity, options: Bundle): Boolean {
+ throw RuntimeException("Wrong API used: start")
+ }
+
+ override fun startAsCaller(
+ target: TargetInfo, activity: Activity, options: Bundle, userId: Int): Boolean {
+ throw RuntimeException("Wrong API used: startAsCaller")
+ }
+ }
+
+ val info = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build()
+ val activity: Activity = mock()
+ val options = Bundle()
+ options.putInt("TEST_KEY", 1)
+
+ info.startAsUser(activity, options, UserHandle.of(42))
+
+ assertThat(activityStarter.totalInvocations).isEqualTo(1)
+ assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info)
+ assertThat(activityStarter.lastInvocationActivity).isEqualTo(activity)
+ assertThat(activityStarter.lastInvocationOptions).isEqualTo(options)
+ assertThat(activityStarter.lastInvocationUserId).isEqualTo(42)
+ assertThat(activityStarter.lastInvocationAsCaller).isFalse()
+ }
+
+ @Test
+ fun testActivityStarter_invokedWithRespectiveTargetInfoAfterCopy() {
+ val activityStarter = TestActivityStarter()
+ val info1 = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build()
+ val info2 = info1.toBuilder().build()
+
+ info1.start(mock(), Bundle())
+ assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info1)
+ info2.start(mock(), Bundle())
+ assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info2)
+ info2.startAsCaller(mock(), Bundle(), 42)
+ assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info2)
+ info2.startAsUser(mock(), Bundle(), UserHandle.of(42))
+ assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info2)
+
+ assertThat(activityStarter.totalInvocations).isEqualTo(4) // Instance is still shared.
+ }
+}
+
+private open class TestActivityStarter : ImmutableTargetInfo.TargetActivityStarter {
+ var totalInvocations = 0
+ var lastInvocationTargetInfo: TargetInfo? = null
+ var lastInvocationActivity: Activity? = null
+ var lastInvocationOptions: Bundle? = null
+ var lastInvocationUserId: Integer? = null
+ var lastInvocationAsCaller = false
+
+ override fun start(target: TargetInfo, activity: Activity, options: Bundle): Boolean {
+ ++totalInvocations
+ lastInvocationTargetInfo = target
+ lastInvocationActivity = activity
+ lastInvocationOptions = options
+ lastInvocationUserId = null
+ lastInvocationAsCaller = false
+ return true
+ }
+
+ override fun startAsCaller(
+ target: TargetInfo, activity: Activity, options: Bundle, userId: Int): Boolean {
+ ++totalInvocations
+ lastInvocationTargetInfo = target
+ lastInvocationActivity = activity
+ lastInvocationOptions = options
+ lastInvocationUserId = Integer(userId)
+ lastInvocationAsCaller = true
+ return true
+ }
+
+ override fun startAsUser(
+ target: TargetInfo, activity: Activity, options: Bundle, user: UserHandle): Boolean {
+ ++totalInvocations
+ lastInvocationTargetInfo = target
+ lastInvocationActivity = activity
+ lastInvocationOptions = options
+ lastInvocationUserId = Integer(user.identifier)
+ lastInvocationAsCaller = false
+ return true
+ }
+}