1 /*
2  * Copyright (C) 2020 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 package android.app.search;
17 
18 import android.annotation.FloatRange;
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.StringDef;
23 import android.annotation.SystemApi;
24 import android.app.slice.SliceManager;
25 import android.appwidget.AppWidgetProviderInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ShortcutInfo;
28 import android.content.pm.ShortcutManager;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.Parcel;
32 import android.os.Parcelable;
33 import android.os.UserHandle;
34 
35 import java.lang.annotation.Retention;
36 import java.lang.annotation.RetentionPolicy;
37 import java.util.Objects;
38 
39 /**
40  * A representation of a search result. Search result can be expressed in one of the following:
41  * app icon, shortcut, slice, widget, or a custom object using {@link SearchAction}. While
42  * app icon ({@link PackageManager}, shortcut {@link ShortcutManager}, slice {@link SliceManager},
43  * or widget (@link AppWidgetManager} are published content backed by the system service,
44  * {@link SearchAction} is a custom object that the service can use to send search result to the
45  * client.
46  *
47  * These various types of Android primitives could be defined as {@link SearchResultType}. Some
48  * times, the result type can define the layout type that that this object can be rendered in.
49  * (e.g., app widget). Most times, {@link #getLayoutType()} assigned by the service
50  * can recommend which layout this target should be rendered in.
51  *
52  * The service can also use fields such as {@link #getScore()} to indicate
53  * how confidence the search result is and {@link #isHidden()} to indicate
54  * whether it is recommended to be shown by default.
55  *
56  * Finally, {@link #getId()} is the unique identifier of this search target and a single
57  * search target is defined by being able to express a single launcheable item. In case the
58  * service want to recommend how to combine multiple search target objects to render in a group
59  * (e.g., same row), {@link #getParentId()} can be assigned on the sub targets of the group
60  * using the primary search target's identifier.
61  *
62  * @hide
63  */
64 @SystemApi
65 public final class SearchTarget implements Parcelable {
66 
67     public static final int RESULT_TYPE_APPLICATION = 1 << 0;
68     public static final int RESULT_TYPE_SHORTCUT = 1 << 1;
69     public static final int RESULT_TYPE_SLICE = 1 << 2;
70     public static final int RESULT_TYPE_WIDGETS = 1 << 3;
71 
72     //     ------
73     //    | icon |
74     //     ------
75     //      text
76     public static final String LAYOUT_TYPE_ICON = "icon";
77 
78     //     ------                            ------   ------
79     //    |      | title                    |(opt)|  |(opt)|
80     //    | icon | subtitle (optional)      | icon|  | icon|
81     //     ------                            ------  ------
82     public static final String LAYOUT_TYPE_ICON_ROW = "icon_row";
83 
84     //     ------
85     //    | icon | title / subtitle (optional)
86     //     ------
87     public static final String LAYOUT_TYPE_SHORT_ICON_ROW = "short_icon_row";
88 
89     /**
90      * @hide
91      */
92     @IntDef(prefix = {"RESULT_TYPE_"}, value = {
93             RESULT_TYPE_APPLICATION,
94             RESULT_TYPE_SHORTCUT,
95             RESULT_TYPE_SLICE,
96             RESULT_TYPE_WIDGETS
97     })
98     @Retention(RetentionPolicy.SOURCE)
99     public @interface SearchResultType {}
100     private final int mResultType;
101 
102     /**
103      * @hide
104      */
105     @StringDef(prefix = {"LAYOUT_TYPE_"}, value = {
106             LAYOUT_TYPE_ICON,
107             LAYOUT_TYPE_ICON_ROW,
108             LAYOUT_TYPE_SHORT_ICON_ROW,
109     })
110     @Retention(RetentionPolicy.SOURCE)
111     public @interface SearchLayoutType {}
112 
113     /**
114      * Constant to express how the group of {@link SearchTarget} should be rendered on
115      * the client side. (e.g., "icon", "icon_row", "short_icon_row")
116      */
117     @NonNull
118     private final String mLayoutType;
119 
120     @NonNull
121     private final String mId;
122 
123     @Nullable
124     private String mParentId;
125 
126     private final float mScore;
127 
128     private final boolean mHidden;
129 
130     @NonNull
131     private final String mPackageName;
132     @NonNull
133     private final UserHandle mUserHandle;
134     @Nullable
135     private final SearchAction mSearchAction;
136     @Nullable
137     private final ShortcutInfo mShortcutInfo;
138     @Nullable
139     private final AppWidgetProviderInfo mAppWidgetProviderInfo;
140     @Nullable
141     private final Uri mSliceUri;
142 
143     @NonNull
144     private final Bundle mExtras;
145 
SearchTarget(Parcel parcel)146     private SearchTarget(Parcel parcel) {
147         mResultType = parcel.readInt();
148         mLayoutType = parcel.readString();
149         mId = parcel.readString();
150         mParentId = parcel.readString();
151         mScore = parcel.readFloat();
152         mHidden = parcel.readBoolean();
153 
154         mPackageName = parcel.readString();
155         mUserHandle = UserHandle.of(parcel.readInt());
156         mSearchAction = parcel.readTypedObject(SearchAction.CREATOR);
157         mShortcutInfo = parcel.readTypedObject(ShortcutInfo.CREATOR);
158         mAppWidgetProviderInfo = parcel.readTypedObject(AppWidgetProviderInfo.CREATOR);
159         mSliceUri = parcel.readTypedObject(Uri.CREATOR);
160         mExtras = parcel.readBundle(getClass().getClassLoader());
161     }
162 
SearchTarget( int resultType, @NonNull String layoutType, @NonNull String id, @Nullable String parentId, float score, boolean hidden, @NonNull String packageName, @NonNull UserHandle userHandle, @Nullable SearchAction action, @Nullable ShortcutInfo shortcutInfo, @Nullable Uri sliceUri, @Nullable AppWidgetProviderInfo appWidgetProviderInfo, @NonNull Bundle extras)163     private SearchTarget(
164             int resultType,
165             @NonNull String layoutType,
166             @NonNull String id,
167             @Nullable String parentId,
168             float score, boolean hidden,
169             @NonNull String packageName,
170             @NonNull UserHandle userHandle,
171             @Nullable SearchAction action,
172             @Nullable ShortcutInfo shortcutInfo,
173             @Nullable Uri sliceUri,
174             @Nullable AppWidgetProviderInfo appWidgetProviderInfo,
175             @NonNull Bundle extras) {
176         mResultType = resultType;
177         mLayoutType = Objects.requireNonNull(layoutType);
178         mId = Objects.requireNonNull(id);
179         mParentId = parentId;
180         mScore = score;
181         mHidden = hidden;
182         mPackageName = Objects.requireNonNull(packageName);
183         mUserHandle = Objects.requireNonNull(userHandle);
184         mSearchAction = action;
185         mShortcutInfo = shortcutInfo;
186         mAppWidgetProviderInfo = appWidgetProviderInfo;
187         mSliceUri = sliceUri;
188         mExtras = extras != null ? extras : new Bundle();
189     }
190 
191     /**
192      * Retrieves the result type {@see SearchResultType}.
193      */
getResultType()194     public @SearchResultType int getResultType() {
195         return mResultType;
196     }
197 
198     /**
199      * Retrieves the layout type.
200      */
201     @NonNull
getLayoutType()202     public @SearchLayoutType String getLayoutType() {
203         return mLayoutType;
204     }
205 
206     /**
207      * Retrieves the id of the target.
208      */
209     @NonNull
getId()210     public String getId() {
211         return mId;
212     }
213 
214     /**
215      * Retrieves the parent id of the target.
216      */
217     @NonNull
getParentId()218     public String getParentId() {
219         return mParentId;
220     }
221 
222     /**
223      * Retrieves the score of the target.
224      */
getScore()225     public float getScore() {
226         return mScore;
227     }
228 
229     /**
230      * Indicates whether this object should be hidden and shown only on demand.
231      *
232      * @deprecated will be removed once SDK drops
233      * @removed
234      */
235     @Deprecated
shouldHide()236     public boolean shouldHide() {
237         return mHidden;
238     }
239 
240     /**
241      * Indicates whether this object should be hidden and shown only on demand.
242      */
isHidden()243     public boolean isHidden() {
244         return mHidden;
245     }
246 
247     /**
248      * Retrieves the package name of the target.
249      */
250     @NonNull
getPackageName()251     public String getPackageName() {
252         return mPackageName;
253     }
254 
255     /**
256      * Retrieves the user handle of the target.
257      */
258     @NonNull
getUserHandle()259     public UserHandle getUserHandle() {
260         return mUserHandle;
261     }
262 
263     /**
264      * Retrieves the shortcut info of the target.
265      */
266     @Nullable
getShortcutInfo()267     public ShortcutInfo getShortcutInfo() {
268         return mShortcutInfo;
269     }
270 
271     /**
272      * Return a widget provider info.
273      */
274     @Nullable
getAppWidgetProviderInfo()275     public AppWidgetProviderInfo getAppWidgetProviderInfo() {
276         return mAppWidgetProviderInfo;
277     }
278 
279     /**
280      * Returns a slice uri.
281      */
282     @Nullable
getSliceUri()283     public Uri getSliceUri() {
284         return mSliceUri;
285     }
286 
287     /**
288      * Returns a search action.
289      */
290     @Nullable
getSearchAction()291     public SearchAction getSearchAction() {
292         return mSearchAction;
293     }
294 
295     /**
296      * Return extra bundle.
297      */
298     @NonNull
getExtras()299     public Bundle getExtras() {
300         return mExtras;
301     }
302 
303     @Override
describeContents()304     public int describeContents() {
305         return 0;
306     }
307 
308     @Override
writeToParcel(@onNull Parcel parcel, int flags)309     public void writeToParcel(@NonNull Parcel parcel, int flags) {
310         parcel.writeInt(mResultType);
311         parcel.writeString(mLayoutType);
312         parcel.writeString(mId);
313         parcel.writeString(mParentId);
314         parcel.writeFloat(mScore);
315         parcel.writeBoolean(mHidden);
316         parcel.writeString(mPackageName);
317         parcel.writeInt(mUserHandle.getIdentifier());
318         parcel.writeTypedObject(mSearchAction, flags);
319         parcel.writeTypedObject(mShortcutInfo, flags);
320         parcel.writeTypedObject(mAppWidgetProviderInfo, flags);
321         parcel.writeTypedObject(mSliceUri, flags);
322         parcel.writeBundle(mExtras);
323     }
324 
325     /**
326      * @see Parcelable.Creator
327      */
328     @NonNull
329     public static final Parcelable.Creator<SearchTarget> CREATOR =
330             new Parcelable.Creator<SearchTarget>() {
331                 public SearchTarget createFromParcel(Parcel parcel) {
332                     return new SearchTarget(parcel);
333                 }
334 
335                 public SearchTarget[] newArray(int size) {
336                     return new SearchTarget[size];
337                 }
338             };
339 
340     /**
341      * A builder for search target object.
342      *
343      * @hide
344      */
345     @SystemApi
346     public static final class Builder {
347         private int mResultType;
348         @NonNull
349         private String mLayoutType;
350         @NonNull
351         private String mId;
352         @Nullable
353         private String mParentId;
354         private float mScore;
355         private boolean mHidden;
356         @NonNull
357         private String mPackageName;
358         @NonNull
359         private UserHandle mUserHandle;
360         @Nullable
361         private SearchAction mSearchAction;
362         @Nullable
363         private ShortcutInfo mShortcutInfo;
364         @Nullable
365         private Uri mSliceUri;
366         @Nullable
367         private AppWidgetProviderInfo mAppWidgetProviderInfo;
368         @NonNull
369         private Bundle mExtras;
370 
Builder(@earchResultType int resultType, @SearchLayoutType @NonNull String layoutType, @NonNull String id)371         public Builder(@SearchResultType int resultType,
372                 @SearchLayoutType @NonNull String layoutType,
373                 @NonNull String id) {
374             mId = id;
375             mLayoutType = Objects.requireNonNull(layoutType);
376             mResultType = resultType;
377             mScore = 1f;
378             mHidden = false;
379         }
380 
381         /**
382          * Sets the parent id.
383          */
384         @NonNull
setParentId(@onNull String parentId)385         public Builder setParentId(@NonNull String parentId) {
386             mParentId = Objects.requireNonNull(parentId);
387             return this;
388         }
389 
390         /**
391          * Sets the package name.
392          */
393         @NonNull
setPackageName(@onNull String packageName)394         public Builder setPackageName(@NonNull String packageName) {
395             mPackageName = Objects.requireNonNull(packageName);
396             return this;
397         }
398 
399         /**
400          * Sets the user handle.
401          */
402         @NonNull
setUserHandle(@onNull UserHandle userHandle)403         public Builder setUserHandle(@NonNull UserHandle userHandle) {
404             mUserHandle = Objects.requireNonNull(userHandle);
405             return this;
406         }
407 
408         /**
409          * Sets the shortcut info.
410          */
411         @NonNull
setShortcutInfo(@onNull ShortcutInfo shortcutInfo)412         public Builder setShortcutInfo(@NonNull ShortcutInfo shortcutInfo) {
413             mShortcutInfo = Objects.requireNonNull(shortcutInfo);
414             if (mPackageName != null && !mPackageName.equals(shortcutInfo.getPackage())) {
415                 throw new IllegalStateException("SearchTarget packageName is different from "
416                         + "shortcut's packageName");
417             }
418             mPackageName = shortcutInfo.getPackage();
419             return this;
420         }
421 
422         /**
423          * Sets the app widget provider info.
424          */
425         @NonNull
setAppWidgetProviderInfo( @onNull AppWidgetProviderInfo appWidgetProviderInfo)426         public Builder setAppWidgetProviderInfo(
427                 @NonNull AppWidgetProviderInfo appWidgetProviderInfo) {
428             mAppWidgetProviderInfo = Objects.requireNonNull(appWidgetProviderInfo);
429             if (mPackageName != null
430                     && !mPackageName.equals(appWidgetProviderInfo.provider.getPackageName())) {
431                 throw new IllegalStateException("SearchTarget packageName is different from "
432                         + "appWidgetProviderInfo's packageName");
433             }
434             return this;
435         }
436 
437         /**
438          * Sets the slice URI.
439          */
440         @NonNull
setSliceUri(@onNull Uri sliceUri)441         public Builder setSliceUri(@NonNull Uri sliceUri) {
442             mSliceUri = sliceUri;
443             return this;
444         }
445 
446         /**
447          * Set the {@link SearchAction} object to this target.
448          */
449         @NonNull
setSearchAction(@ullable SearchAction searchAction)450         public Builder setSearchAction(@Nullable SearchAction searchAction) {
451             mSearchAction = searchAction;
452             return this;
453         }
454 
455         /**
456          * Set any extra information that needs to be shared between service and the client.
457          */
458         @NonNull
setExtras(@onNull Bundle extras)459         public Builder setExtras(@NonNull Bundle extras) {
460             mExtras = Objects.requireNonNull(extras);
461             return this;
462         }
463 
464         /**
465          * Sets the score of the object.
466          */
467         @NonNull
setScore(@loatRangefrom = 0.0f, to = 1.0f) float score)468         public Builder setScore(@FloatRange(from = 0.0f, to = 1.0f) float score) {
469             mScore = score;
470             return this;
471         }
472 
473         /**
474          * Sets whether the result should be hidden (e.g. not visible) by default inside client.
475          */
476         @NonNull
setHidden(boolean hidden)477         public Builder setHidden(boolean hidden) {
478             mHidden = hidden;
479             return this;
480         }
481 
482         /**
483          * Sets whether the result should be hidden by default inside client.
484          * @deprecated will be removed once SDK drops
485          * @removed
486          */
487         @NonNull
488         @Deprecated
setShouldHide(boolean shouldHide)489         public Builder setShouldHide(boolean shouldHide) {
490             mHidden = shouldHide;
491             return this;
492         }
493 
494         /**
495          * Builds a new SearchTarget instance.
496          *
497          * @throws IllegalStateException if no target is set
498          */
499         @NonNull
build()500         public SearchTarget build() {
501             return new SearchTarget(mResultType, mLayoutType, mId, mParentId, mScore, mHidden,
502                     mPackageName, mUserHandle,
503                     mSearchAction, mShortcutInfo, mSliceUri, mAppWidgetProviderInfo,
504                     mExtras);
505         }
506     }
507 }
508