1 /*
2  * Copyright (C) 2023 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 import android.app.Activity;
20 import android.app.prediction.AppTarget;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.ResolveInfo;
25 import android.content.pm.ShortcutInfo;
26 import android.os.Bundle;
27 import android.os.UserHandle;
28 import android.service.chooser.ChooserTarget;
29 import android.util.HashedStringCache;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.google.common.collect.ImmutableList;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 
40 /**
41  * An implementation of {@link TargetInfo} with immutable data. Any modifications must be made by
42  * creating a new instance (e.g., via {@link ImmutableTargetInfo#toBuilder()}).
43  */
44 public final class ImmutableTargetInfo implements TargetInfo {
45     private static final String TAG = "TargetInfo";
46 
47     /** Delegate interface to implement {@link TargetInfo#getHashedTargetIdForMetrics}. */
48     public interface TargetHashProvider {
49         /** Request a hash for the specified {@code target}. */
getHashedTargetIdForMetrics( TargetInfo target, Context context)50         HashedStringCache.HashResult getHashedTargetIdForMetrics(
51                 TargetInfo target, Context context);
52     }
53 
54     /** Delegate interface to request that the target be launched by a particular API. */
55     public interface TargetActivityStarter {
56         /**
57          * Request that the delegate use the {@link Activity#startActivityAsCaller} API to launch
58          * the specified {@code target}.
59          *
60          * @return true if the target was launched successfully.
61          */
startAsCaller(TargetInfo target, Activity activity, Bundle options, int userId)62         boolean startAsCaller(TargetInfo target, Activity activity, Bundle options, int userId);
63 
64         /**
65          * Request that the delegate use the {@link Activity#startActivityAsUser} API to launch the
66          * specified {@code target}.
67          *
68          * @return true if the target was launched successfully.
69          */
startAsUser(TargetInfo target, Activity activity, Bundle options, UserHandle user)70         boolean startAsUser(TargetInfo target, Activity activity, Bundle options, UserHandle user);
71     }
72 
73     enum LegacyTargetType {
74         NOT_LEGACY_TARGET,
75         EMPTY_TARGET_INFO,
76         PLACEHOLDER_TARGET_INFO,
77         SELECTABLE_TARGET_INFO,
78         DISPLAY_RESOLVE_INFO,
79         MULTI_DISPLAY_RESOLVE_INFO
80     };
81 
82     /** Builder API to construct {@code ImmutableTargetInfo} instances. */
83     public static class Builder {
84         @Nullable
85         private ComponentName mResolvedComponentName;
86 
87         @Nullable
88         private Intent mResolvedIntent;
89 
90         @Nullable
91         private Intent mBaseIntentToSend;
92 
93         @Nullable
94         private Intent mTargetIntent;
95 
96         @Nullable
97         private ComponentName mChooserTargetComponentName;
98 
99         @Nullable
100         private ShortcutInfo mDirectShareShortcutInfo;
101 
102         @Nullable
103         private AppTarget mDirectShareAppTarget;
104 
105         @Nullable
106         private DisplayResolveInfo mDisplayResolveInfo;
107 
108         @Nullable
109         private TargetHashProvider mHashProvider;
110 
111         @Nullable
112         private Intent mReferrerFillInIntent;
113 
114         @Nullable
115         private TargetActivityStarter mActivityStarter;
116 
117         @Nullable
118         private ResolveInfo mResolveInfo;
119 
120         @Nullable
121         private CharSequence mDisplayLabel;
122 
123         @Nullable
124         private CharSequence mExtendedInfo;
125 
126         @Nullable
127         private IconHolder mDisplayIconHolder;
128 
129         private boolean mIsSuspended;
130         private boolean mIsPinned;
131         private float mModifiedScore = -0.1f;
132         private LegacyTargetType mLegacyType = LegacyTargetType.NOT_LEGACY_TARGET;
133 
134         private ImmutableList<Intent> mAlternateSourceIntents = ImmutableList.of();
135         private ImmutableList<DisplayResolveInfo> mAllDisplayTargets = ImmutableList.of();
136 
137         /**
138          * Configure an {@link Intent} to be built in to the output target as the resolution for the
139          * requested target data.
140          */
setResolvedIntent(Intent resolvedIntent)141         public Builder setResolvedIntent(Intent resolvedIntent) {
142             mResolvedIntent = resolvedIntent;
143             return this;
144         }
145 
146         /**
147          * Configure an {@link Intent} to be built in to the output target as the "base intent to
148          * send," which may be a refinement of any of our source targets. This is private because
149          * it's only used internally by {@link #tryToCloneWithAppliedRefinement}; if it's ever
150          * expanded, the builder should probably be responsible for enforcing the refinement check.
151          */
setBaseIntentToSend(Intent baseIntent)152         private Builder setBaseIntentToSend(Intent baseIntent) {
153             mBaseIntentToSend = baseIntent;
154             return this;
155         }
156 
157         /**
158          * Configure an {@link Intent} to be built in to the output as the "target intent."
159          */
setTargetIntent(Intent targetIntent)160         public Builder setTargetIntent(Intent targetIntent) {
161             mTargetIntent = targetIntent;
162             return this;
163         }
164 
165         /**
166          * Configure a fill-in intent provided by the referrer to be used in populating the launch
167          * intent if the output target is ever selected.
168          *
169          * @see android.content.Intent#fillIn(Intent, int)
170          */
setReferrerFillInIntent(@ullable Intent referrerFillInIntent)171         public Builder setReferrerFillInIntent(@Nullable Intent referrerFillInIntent) {
172             mReferrerFillInIntent = referrerFillInIntent;
173             return this;
174         }
175 
176         /**
177          * Configure a {@link ComponentName} to be built in to the output target, as the real
178          * component we were able to resolve on this device given the available target data.
179          */
setResolvedComponentName(@ullable ComponentName resolvedComponentName)180         public Builder setResolvedComponentName(@Nullable ComponentName resolvedComponentName) {
181             mResolvedComponentName = resolvedComponentName;
182             return this;
183         }
184 
185         /**
186          * Configure a {@link ComponentName} to be built in to the output target, as the component
187          * supposedly associated with a {@link ChooserTarget} from which the builder data is being
188          * derived.
189          */
setChooserTargetComponentName(@ullable ComponentName componentName)190         public Builder setChooserTargetComponentName(@Nullable ComponentName componentName) {
191             mChooserTargetComponentName = componentName;
192             return this;
193         }
194 
195         /** Configure the {@link TargetActivityStarter} to be built in to the output target. */
setActivityStarter(TargetActivityStarter activityStarter)196         public Builder setActivityStarter(TargetActivityStarter activityStarter) {
197             mActivityStarter = activityStarter;
198             return this;
199         }
200 
201         /** Configure the {@link ResolveInfo} to be built in to the output target. */
setResolveInfo(ResolveInfo resolveInfo)202         public Builder setResolveInfo(ResolveInfo resolveInfo) {
203             mResolveInfo = resolveInfo;
204             return this;
205         }
206 
207         /** Configure the display label to be built in to the output target. */
setDisplayLabel(CharSequence displayLabel)208         public Builder setDisplayLabel(CharSequence displayLabel) {
209             mDisplayLabel = displayLabel;
210             return this;
211         }
212 
213         /** Configure the extended info to be built in to the output target. */
setExtendedInfo(CharSequence extendedInfo)214         public Builder setExtendedInfo(CharSequence extendedInfo) {
215             mExtendedInfo = extendedInfo;
216             return this;
217         }
218 
219         /** Configure the {@link IconHolder} to be built in to the output target. */
setDisplayIconHolder(IconHolder displayIconHolder)220         public Builder setDisplayIconHolder(IconHolder displayIconHolder) {
221             mDisplayIconHolder = displayIconHolder;
222             return this;
223         }
224 
225         /** Configure the list of alternate source intents we could resolve for this target. */
setAlternateSourceIntents(List<Intent> sourceIntents)226         public Builder setAlternateSourceIntents(List<Intent> sourceIntents) {
227             mAlternateSourceIntents = immutableCopyOrEmpty(sourceIntents);
228             return this;
229         }
230 
231        /**
232         * Configure the full list of source intents we could resolve for this target. This is
233         * effectively the same as calling {@link #setResolvedIntent} with the first element of
234         * the list, and {@link #setAlternateSourceIntents} with the remainder (or clearing those
235         * fields on the builder if there are no corresponding elements in the list).
236         */
setAllSourceIntents(List<Intent> sourceIntents)237         public Builder setAllSourceIntents(List<Intent> sourceIntents) {
238             if ((sourceIntents == null) || sourceIntents.isEmpty()) {
239                 setResolvedIntent(null);
240                 setAlternateSourceIntents(null);
241                 return this;
242             }
243 
244             setResolvedIntent(sourceIntents.get(0));
245             setAlternateSourceIntents(sourceIntents.subList(1, sourceIntents.size()));
246             return this;
247         }
248 
249         /** Configure the list of display targets to be built in to the output target. */
setAllDisplayTargets(List<DisplayResolveInfo> targets)250         public Builder setAllDisplayTargets(List<DisplayResolveInfo> targets) {
251             mAllDisplayTargets = immutableCopyOrEmpty(targets);
252             return this;
253         }
254 
255         /** Configure the is-suspended status to be built in to the output target. */
setIsSuspended(boolean isSuspended)256         public Builder setIsSuspended(boolean isSuspended) {
257             mIsSuspended = isSuspended;
258             return this;
259         }
260 
261         /** Configure the is-pinned status to be built in to the output target. */
setIsPinned(boolean isPinned)262         public Builder setIsPinned(boolean isPinned) {
263             mIsPinned = isPinned;
264             return this;
265         }
266 
267         /** Configure the modified score to be built in to the output target. */
setModifiedScore(float modifiedScore)268         public Builder setModifiedScore(float modifiedScore) {
269             mModifiedScore = modifiedScore;
270             return this;
271         }
272 
273         /** Configure the {@link ShortcutInfo} to be built in to the output target. */
setDirectShareShortcutInfo(@ullable ShortcutInfo shortcutInfo)274         public Builder setDirectShareShortcutInfo(@Nullable ShortcutInfo shortcutInfo) {
275             mDirectShareShortcutInfo = shortcutInfo;
276             return this;
277         }
278 
279         /** Configure the {@link AppTarget} to be built in to the output target. */
setDirectShareAppTarget(@ullable AppTarget appTarget)280         public Builder setDirectShareAppTarget(@Nullable AppTarget appTarget) {
281             mDirectShareAppTarget = appTarget;
282             return this;
283         }
284 
285         /** Configure the {@link DisplayResolveInfo} to be built in to the output target. */
setDisplayResolveInfo(@ullable DisplayResolveInfo displayResolveInfo)286         public Builder setDisplayResolveInfo(@Nullable DisplayResolveInfo displayResolveInfo) {
287             mDisplayResolveInfo = displayResolveInfo;
288             return this;
289         }
290 
291         /** Configure the {@link TargetHashProvider} to be built in to the output target. */
setHashProvider(@ullable TargetHashProvider hashProvider)292         public Builder setHashProvider(@Nullable TargetHashProvider hashProvider) {
293             mHashProvider = hashProvider;
294             return this;
295         }
296 
setLegacyType(@onNull LegacyTargetType legacyType)297         Builder setLegacyType(@NonNull LegacyTargetType legacyType) {
298             mLegacyType = legacyType;
299             return this;
300         }
301 
302         /** Construct an {@code ImmutableTargetInfo} with the current builder data. */
build()303         public ImmutableTargetInfo build() {
304             List<Intent> sourceIntents = new ArrayList<>();
305             if (mResolvedIntent != null) {
306                 sourceIntents.add(mResolvedIntent);
307             }
308             if (mAlternateSourceIntents != null) {
309                 sourceIntents.addAll(mAlternateSourceIntents);
310             }
311 
312             Intent baseIntentToSend = mBaseIntentToSend;
313             if ((baseIntentToSend == null) && !sourceIntents.isEmpty()) {
314                 baseIntentToSend = sourceIntents.get(0);
315             }
316             if (baseIntentToSend != null) {
317                 baseIntentToSend = new Intent(baseIntentToSend);
318                 if (mReferrerFillInIntent != null) {
319                     baseIntentToSend.fillIn(mReferrerFillInIntent, 0);
320                 }
321             }
322 
323             return new ImmutableTargetInfo(
324                     baseIntentToSend,
325                     ImmutableList.copyOf(sourceIntents),
326                     mTargetIntent,
327                     mReferrerFillInIntent,
328                     mResolvedComponentName,
329                     mChooserTargetComponentName,
330                     mActivityStarter,
331                     mResolveInfo,
332                     mDisplayLabel,
333                     mExtendedInfo,
334                     mDisplayIconHolder,
335                     mAllDisplayTargets,
336                     mIsSuspended,
337                     mIsPinned,
338                     mModifiedScore,
339                     mDirectShareShortcutInfo,
340                     mDirectShareAppTarget,
341                     mDisplayResolveInfo,
342                     mHashProvider,
343                     mLegacyType);
344         }
345     }
346 
347     @Nullable
348     private final Intent mReferrerFillInIntent;
349 
350     @Nullable
351     private final ComponentName mResolvedComponentName;
352 
353     @Nullable
354     private final ComponentName mChooserTargetComponentName;
355 
356     @Nullable
357     private final ShortcutInfo mDirectShareShortcutInfo;
358 
359     @Nullable
360     private final AppTarget mDirectShareAppTarget;
361 
362     @Nullable
363     private final DisplayResolveInfo mDisplayResolveInfo;
364 
365     @Nullable
366     private final TargetHashProvider mHashProvider;
367 
368     private final Intent mBaseIntentToSend;
369     private final ImmutableList<Intent> mSourceIntents;
370     private final Intent mTargetIntent;
371     private final TargetActivityStarter mActivityStarter;
372     private final ResolveInfo mResolveInfo;
373     private final CharSequence mDisplayLabel;
374     private final CharSequence mExtendedInfo;
375     private final IconHolder mDisplayIconHolder;
376     private final ImmutableList<DisplayResolveInfo> mAllDisplayTargets;
377     private final boolean mIsSuspended;
378     private final boolean mIsPinned;
379     private final float mModifiedScore;
380     private final LegacyTargetType mLegacyType;
381 
382     /** Construct a {@link Builder}. */
newBuilder()383     public static Builder newBuilder() {
384         return new Builder();
385     }
386 
387     /** Construct a {@link Builder} pre-initialized to match this target. */
toBuilder()388     public Builder toBuilder() {
389         return newBuilder()
390                 .setBaseIntentToSend(getBaseIntentToSend())
391                 .setResolvedIntent(getResolvedIntent())
392                 .setTargetIntent(getTargetIntent())
393                 .setReferrerFillInIntent(getReferrerFillInIntent())
394                 .setResolvedComponentName(getResolvedComponentName())
395                 .setChooserTargetComponentName(getChooserTargetComponentName())
396                 .setActivityStarter(mActivityStarter)
397                 .setResolveInfo(getResolveInfo())
398                 .setDisplayLabel(getDisplayLabel())
399                 .setExtendedInfo(getExtendedInfo())
400                 .setDisplayIconHolder(getDisplayIconHolder())
401                 .setAllSourceIntents(getAllSourceIntents())
402                 .setAllDisplayTargets(getAllDisplayTargets())
403                 .setIsSuspended(isSuspended())
404                 .setIsPinned(isPinned())
405                 .setModifiedScore(getModifiedScore())
406                 .setDirectShareShortcutInfo(getDirectShareShortcutInfo())
407                 .setDirectShareAppTarget(getDirectShareAppTarget())
408                 .setDisplayResolveInfo(getDisplayResolveInfo())
409                 .setHashProvider(getHashProvider())
410                 .setLegacyType(mLegacyType);
411     }
412 
413     @VisibleForTesting
getBaseIntentToSend()414     Intent getBaseIntentToSend() {
415         return mBaseIntentToSend;
416     }
417 
418     @Override
419     @Nullable
tryToCloneWithAppliedRefinement(Intent proposedRefinement)420     public ImmutableTargetInfo tryToCloneWithAppliedRefinement(Intent proposedRefinement) {
421         Intent matchingBase =
422                 getAllSourceIntents()
423                         .stream()
424                         .filter(i -> i.filterEquals(proposedRefinement))
425                         .findFirst()
426                         .orElse(null);
427         if (matchingBase == null) {
428             return null;
429         }
430 
431         Intent merged = TargetInfo.mergeRefinementIntoMatchingBaseIntent(
432                 matchingBase, proposedRefinement);
433         return toBuilder().setBaseIntentToSend(merged).build();
434     }
435 
436     @Override
getResolvedIntent()437     public Intent getResolvedIntent() {
438         return (mSourceIntents.isEmpty() ? null : mSourceIntents.get(0));
439     }
440 
441     @Override
getTargetIntent()442     public Intent getTargetIntent() {
443         return mTargetIntent;
444     }
445 
446     @Nullable
getReferrerFillInIntent()447     public Intent getReferrerFillInIntent() {
448         return mReferrerFillInIntent;
449     }
450 
451     @Override
452     @Nullable
getResolvedComponentName()453     public ComponentName getResolvedComponentName() {
454         return mResolvedComponentName;
455     }
456 
457     @Override
458     @Nullable
getChooserTargetComponentName()459     public ComponentName getChooserTargetComponentName() {
460         return mChooserTargetComponentName;
461     }
462 
463     @Override
startAsCaller(Activity activity, Bundle options, int userId)464     public boolean startAsCaller(Activity activity, Bundle options, int userId) {
465         // TODO: make sure that the component name is set in all cases
466         return mActivityStarter.startAsCaller(this, activity, options, userId);
467     }
468 
469     @Override
startAsUser(Activity activity, Bundle options, UserHandle user)470     public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
471         // TODO: make sure that the component name is set in all cases
472         return mActivityStarter.startAsUser(this, activity, options, user);
473     }
474 
475     @Override
getResolveInfo()476     public ResolveInfo getResolveInfo() {
477         return mResolveInfo;
478     }
479 
480     @Override
getDisplayLabel()481     public CharSequence getDisplayLabel() {
482         return mDisplayLabel;
483     }
484 
485     @Override
getExtendedInfo()486     public CharSequence getExtendedInfo() {
487         return mExtendedInfo;
488     }
489 
490     @Override
getDisplayIconHolder()491     public IconHolder getDisplayIconHolder() {
492         return mDisplayIconHolder;
493     }
494 
495     @Override
getAllSourceIntents()496     public List<Intent> getAllSourceIntents() {
497         return mSourceIntents;
498     }
499 
500     @Override
getAllDisplayTargets()501     public ArrayList<DisplayResolveInfo> getAllDisplayTargets() {
502         ArrayList<DisplayResolveInfo> targets = new ArrayList<>();
503         targets.addAll(mAllDisplayTargets);
504         return targets;
505     }
506 
507     @Override
isSuspended()508     public boolean isSuspended() {
509         return mIsSuspended;
510     }
511 
512     @Override
isPinned()513     public boolean isPinned() {
514         return mIsPinned;
515     }
516 
517     @Override
getModifiedScore()518     public float getModifiedScore() {
519         return mModifiedScore;
520     }
521 
522     @Override
523     @Nullable
getDirectShareShortcutInfo()524     public ShortcutInfo getDirectShareShortcutInfo() {
525         return mDirectShareShortcutInfo;
526     }
527 
528     @Override
529     @Nullable
getDirectShareAppTarget()530     public AppTarget getDirectShareAppTarget() {
531         return mDirectShareAppTarget;
532     }
533 
534     @Override
535     @Nullable
getDisplayResolveInfo()536     public DisplayResolveInfo getDisplayResolveInfo() {
537         return mDisplayResolveInfo;
538     }
539 
540     @Override
getHashedTargetIdForMetrics(Context context)541     public HashedStringCache.HashResult getHashedTargetIdForMetrics(Context context) {
542         return (mHashProvider == null)
543                 ? null : mHashProvider.getHashedTargetIdForMetrics(this, context);
544     }
545 
546     @VisibleForTesting
547     @Nullable
getHashProvider()548     TargetHashProvider getHashProvider() {
549         return mHashProvider;
550     }
551 
552     @Override
isEmptyTargetInfo()553     public boolean isEmptyTargetInfo() {
554         return mLegacyType == LegacyTargetType.EMPTY_TARGET_INFO;
555     }
556 
557     @Override
isPlaceHolderTargetInfo()558     public boolean isPlaceHolderTargetInfo() {
559         return mLegacyType == LegacyTargetType.PLACEHOLDER_TARGET_INFO;
560     }
561 
562     @Override
isNotSelectableTargetInfo()563     public boolean isNotSelectableTargetInfo() {
564         return isEmptyTargetInfo() || isPlaceHolderTargetInfo();
565     }
566 
567     @Override
isSelectableTargetInfo()568     public boolean isSelectableTargetInfo() {
569         return mLegacyType == LegacyTargetType.SELECTABLE_TARGET_INFO;
570     }
571 
572     @Override
isChooserTargetInfo()573     public boolean isChooserTargetInfo() {
574         return isNotSelectableTargetInfo() || isSelectableTargetInfo();
575     }
576 
577     @Override
isMultiDisplayResolveInfo()578     public boolean isMultiDisplayResolveInfo() {
579         return mLegacyType == LegacyTargetType.MULTI_DISPLAY_RESOLVE_INFO;
580     }
581 
582     @Override
isDisplayResolveInfo()583     public boolean isDisplayResolveInfo() {
584         return (mLegacyType == LegacyTargetType.DISPLAY_RESOLVE_INFO)
585                 || isMultiDisplayResolveInfo();
586     }
587 
ImmutableTargetInfo( Intent baseIntentToSend, ImmutableList<Intent> sourceIntents, Intent targetIntent, @Nullable Intent referrerFillInIntent, @Nullable ComponentName resolvedComponentName, @Nullable ComponentName chooserTargetComponentName, TargetActivityStarter activityStarter, ResolveInfo resolveInfo, CharSequence displayLabel, CharSequence extendedInfo, IconHolder iconHolder, ImmutableList<DisplayResolveInfo> allDisplayTargets, boolean isSuspended, boolean isPinned, float modifiedScore, @Nullable ShortcutInfo directShareShortcutInfo, @Nullable AppTarget directShareAppTarget, @Nullable DisplayResolveInfo displayResolveInfo, @Nullable TargetHashProvider hashProvider, LegacyTargetType legacyType)588     private ImmutableTargetInfo(
589             Intent baseIntentToSend,
590             ImmutableList<Intent> sourceIntents,
591             Intent targetIntent,
592             @Nullable Intent referrerFillInIntent,
593             @Nullable ComponentName resolvedComponentName,
594             @Nullable ComponentName chooserTargetComponentName,
595             TargetActivityStarter activityStarter,
596             ResolveInfo resolveInfo,
597             CharSequence displayLabel,
598             CharSequence extendedInfo,
599             IconHolder iconHolder,
600             ImmutableList<DisplayResolveInfo> allDisplayTargets,
601             boolean isSuspended,
602             boolean isPinned,
603             float modifiedScore,
604             @Nullable ShortcutInfo directShareShortcutInfo,
605             @Nullable AppTarget directShareAppTarget,
606             @Nullable DisplayResolveInfo displayResolveInfo,
607             @Nullable TargetHashProvider hashProvider,
608             LegacyTargetType legacyType) {
609         mBaseIntentToSend = baseIntentToSend;
610         mSourceIntents = sourceIntents;
611         mTargetIntent = targetIntent;
612         mReferrerFillInIntent = referrerFillInIntent;
613         mResolvedComponentName = resolvedComponentName;
614         mChooserTargetComponentName = chooserTargetComponentName;
615         mActivityStarter = activityStarter;
616         mResolveInfo = resolveInfo;
617         mDisplayLabel = displayLabel;
618         mExtendedInfo = extendedInfo;
619         mDisplayIconHolder = iconHolder;
620         mAllDisplayTargets = allDisplayTargets;
621         mIsSuspended = isSuspended;
622         mIsPinned = isPinned;
623         mModifiedScore = modifiedScore;
624         mDirectShareShortcutInfo = directShareShortcutInfo;
625         mDirectShareAppTarget = directShareAppTarget;
626         mDisplayResolveInfo = displayResolveInfo;
627         mHashProvider = hashProvider;
628         mLegacyType = legacyType;
629     }
630 
immutableCopyOrEmpty(@ullable List<E> source)631     private static <E> ImmutableList<E> immutableCopyOrEmpty(@Nullable List<E> source) {
632         return (source == null) ? ImmutableList.of() : ImmutableList.copyOf(source);
633     }
634 }
635