API council review for SearchUiManager

Bug: 184265048
Test: atest CtsSearchUiServiceTestCases

Change-Id: Ib4dd0d3b29438418d4deb31e999a076e5eeff484
This commit is contained in:
Hyunyoung Song
2021-04-19 13:43:25 -07:00
parent 2dd3fdce69
commit 1717e02ea6
9 changed files with 242 additions and 70 deletions

View File

@@ -1448,11 +1448,12 @@ package android.app.prediction {
package android.app.search {
public final class Query implements android.os.Parcelable {
ctor public Query(@NonNull String, long, @Nullable android.os.Bundle);
ctor public Query(@NonNull String, long, @NonNull android.os.Bundle);
ctor public Query(@NonNull String, long);
method public int describeContents();
method @Nullable public android.os.Bundle getExtras();
method @NonNull public android.os.Bundle getExtras();
method @NonNull public String getInput();
method @NonNull public long getTimestamp();
method public long getTimestampMillis();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.search.Query> CREATOR;
}
@@ -1485,9 +1486,10 @@ package android.app.search {
}
public final class SearchContext implements android.os.Parcelable {
ctor public SearchContext(int, int, @Nullable android.os.Bundle);
ctor public SearchContext(int, int);
ctor public SearchContext(int, int, @NonNull android.os.Bundle);
method public int describeContents();
method @Nullable public android.os.Bundle getExtras();
method @NonNull public android.os.Bundle getExtras();
method @Nullable public String getPackageName();
method @NonNull public int getResultTypes();
method @NonNull public int getTimeoutMillis();
@@ -1497,7 +1499,6 @@ package android.app.search {
public final class SearchSession implements java.lang.AutoCloseable {
method public void close();
method public void destroy();
method protected void finalize();
method public void notifyEvent(@NonNull android.app.search.Query, @NonNull android.app.search.SearchTargetEvent);
method @Nullable public void query(@NonNull android.app.search.Query, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.search.SearchTarget>>);
@@ -1512,7 +1513,7 @@ package android.app.search {
public final class SearchTarget implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.appwidget.AppWidgetProviderInfo getAppWidgetProviderInfo();
method @Nullable public android.os.Bundle getExtras();
method @NonNull public android.os.Bundle getExtras();
method @NonNull public String getId();
method @NonNull public String getLayoutType();
method @NonNull public String getPackageName();
@@ -1526,13 +1527,17 @@ package android.app.search {
method public boolean shouldHide();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.search.SearchTarget> CREATOR;
field public static final int RESULT_TYPE_APPLICATION = 1; // 0x1
field public static final int RESULT_TYPE_SHORTCUT = 2; // 0x2
field public static final int RESULT_TYPE_SLICE = 4; // 0x4
field public static final int RESULT_TYPE_WIDGETS = 8; // 0x8
}
public static final class SearchTarget.Builder {
ctor public SearchTarget.Builder(int, @NonNull String, @NonNull String);
method @NonNull public android.app.search.SearchTarget build();
method @NonNull public android.app.search.SearchTarget.Builder setAppWidgetProviderInfo(@NonNull android.appwidget.AppWidgetProviderInfo);
method @NonNull public android.app.search.SearchTarget.Builder setExtras(@Nullable android.os.Bundle);
method @NonNull public android.app.search.SearchTarget.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.app.search.SearchTarget.Builder setPackageName(@NonNull String);
method @NonNull public android.app.search.SearchTarget.Builder setParentId(@NonNull String);
method @NonNull public android.app.search.SearchTarget.Builder setScore(float);
@@ -10260,10 +10265,10 @@ package android.service.search {
public abstract class SearchUiService extends android.app.Service {
ctor public SearchUiService();
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onCreateSearchSession(@NonNull android.app.search.SearchContext, @NonNull android.app.search.SearchSessionId);
method @MainThread public abstract void onDestroy(@NonNull android.app.search.SearchSessionId);
method @MainThread public abstract void onNotifyEvent(@NonNull android.app.search.SearchSessionId, @NonNull android.app.search.Query, @NonNull android.app.search.SearchTargetEvent);
method @MainThread public abstract void onQuery(@NonNull android.app.search.SearchSessionId, @NonNull android.app.search.Query, @NonNull java.util.function.Consumer<java.util.List<android.app.search.SearchTarget>>);
method public void onSearchSessionCreated(@NonNull android.app.search.SearchContext, @NonNull android.app.search.SearchSessionId);
}
}

View File

@@ -48,6 +48,18 @@ package android.app.prediction {
}
package android.app.search {
public final class Query implements android.os.Parcelable {
method @Deprecated @NonNull public long getTimestamp();
}
public final class SearchSession implements java.lang.AutoCloseable {
method @Deprecated public void destroy();
}
}
package android.bluetooth {
public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
@@ -163,6 +175,14 @@ package android.service.notification {
}
package android.service.search {
public abstract class SearchUiService extends android.app.Service {
method @Deprecated public void onCreateSearchSession(@NonNull android.app.search.SearchContext, @NonNull android.app.search.SearchSessionId);
}
}
package android.telecom {
public class TelecomManager {

View File

@@ -16,63 +16,108 @@
package android.app.search;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Query object is sent over from client to the service.
*
* Inside the query object, there is a timestamp that trackes when the query string was typed.
*
* If this object was created for the {@link SearchSession#query},
* the client expects first consumer to be returned
* within {@link #getTimestampMillis()} + {@link SearchContext#getTimeoutMillis()}
* Base of the timestamp should be SystemClock.elasedRealTime()
*
* @hide
*/
@SystemApi
public final class Query implements Parcelable {
/**
* Query string typed from the client.
* string typed from the client.
*/
@NonNull
private final String mInput;
/**
* The timestamp that the query string was typed. If this object was created for the
* {@link SearchSession#query}, the client expects first consumer to be returned
* within mTimestamp + {@link SearchContext#mTimeoutMillis}
*/
private final long mTimestamp;
private final long mTimestampMillis;
@Nullable
/**
* Contains other client UI constraints related data
*/
@NonNull
private final Bundle mExtras;
/**
* Query object used to pass search box input from client to service.
*
* @param input string typed from the client
* @param timestampMillis timestamp that query string was typed.
* @param extras bundle that contains other client UI constraints data
*/
public Query(@NonNull String input,
long timestamp,
@SuppressLint("NullableCollection")
@Nullable Bundle extras) {
long timestampMillis,
@NonNull Bundle extras) {
mInput = input;
mTimestamp = timestamp;
mExtras = extras;
mTimestampMillis = timestampMillis;
mExtras = extras == null ? extras : new Bundle();
}
/**
* Query object used to pass search box input from client to service.
*
* @param input string typed from the client
* @param timestampMillis timestamp that query string was typed
*/
public Query(@NonNull String input, long timestampMillis) {
this(input, timestampMillis, new Bundle());
}
private Query(Parcel parcel) {
mInput = parcel.readString();
mTimestamp = parcel.readLong();
mTimestampMillis = parcel.readLong();
mExtras = parcel.readBundle();
}
/**
* @return string typed from the client
*/
@NonNull
public String getInput() {
return mInput;
}
/**
* @deprecated Will be replaced by {@link #getTimestampMillis()} as soon as
* new SDK is adopted.
*
* @removed
*/
@Deprecated
@NonNull
public long getTimestamp() {
return mTimestamp;
return mTimestampMillis;
}
@Nullable
@SuppressLint("NullableCollection")
/**
* Base of the timestamp should be SystemClock.elasedRealTime()
*
* @return timestamp that query string was typed
*/
public long getTimestampMillis() {
return mTimestampMillis;
}
/**
* @return bundle that contains other client constraints related to the query
*/
@NonNull
public Bundle getExtras() {
if (mExtras == null) {
return new Bundle();
}
return mExtras;
}
@@ -84,7 +129,7 @@ public final class Query implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mInput);
dest.writeLong(mTimestamp);
dest.writeLong(mTimestampMillis);
dest.writeBundle(mExtras);
}

View File

@@ -17,13 +17,20 @@ package android.app.search;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* When {@link SearchSession} is created, {@link SearchContext} object is created
* to pass the result types from the {@link SearchSession#query(Query, Executor, Consumer)}
* method that the client wants.
*
* @hide
*/
@SystemApi
@@ -51,12 +58,25 @@ public final class SearchContext implements Parcelable {
@Nullable
private String mPackageName;
/**
* @param resultTypes {@link SearchTarget.SearchResultType}s combined using bit OR operation
* @param timeoutMillis timeout before client renders its own fallback result
*/
public SearchContext(int resultTypes, int timeoutMillis) {
this(resultTypes, timeoutMillis, new Bundle());
}
/**
* @param resultTypes {@link SearchTarget.SearchResultType}s combined using bit OR operation
* @param timeoutMillis timeout before client renders its own fallback result
* @param extras other client constraints (e.g., height of the search surface)
*/
public SearchContext(int resultTypes,
int queryTimeoutMillis,
@SuppressLint("NullableCollection") @Nullable Bundle extras) {
int timeoutMillis,
@NonNull Bundle extras) {
mResultTypes = resultTypes;
mTimeoutMillis = queryTimeoutMillis;
mExtras = extras;
mTimeoutMillis = timeoutMillis;
mExtras = Objects.requireNonNull(extras);
}
private SearchContext(Parcel parcel) {
@@ -74,7 +94,7 @@ public final class SearchContext implements Parcelable {
/**
* @hide
*/
public void setPackageName(@Nullable String packageName) {
void setPackageName(@Nullable String packageName) {
mPackageName = packageName;
}
@@ -83,8 +103,7 @@ public final class SearchContext implements Parcelable {
return mTimeoutMillis;
}
@Nullable
@SuppressLint("NullableCollection")
@NonNull
public Bundle getExtras() {
return mExtras;
}

View File

@@ -37,7 +37,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
* Client API to share information about the search UI state and execute query.
* Client needs to create {@link SearchSession} object from in order to execute
* {@link #query(Query, Executor, Consumer)} method and share client side signals
* back to the service using {@link #notifyEvent(Query, SearchTargetEvent)}.
*
* <p>
* Usage: <pre> {@code
@@ -60,7 +62,7 @@ import java.util.function.Consumer;
* }
*
* void onDestroy() {
* mSearchSession.destroy();
* mSearchSession.close();
* }
*
* }</pre>
@@ -108,7 +110,10 @@ public final class SearchSession implements AutoCloseable{
}
/**
* Notifies the search service of an search target event.
* Notifies the search service of an search target event (e.g., user interaction
* and lifecycle event of the search surface).
*
* {@see SearchTargetEvent}
*
* @param query input object associated with the event.
* @param event The {@link SearchTargetEvent} that represents the search target event.
@@ -153,7 +158,11 @@ public final class SearchSession implements AutoCloseable{
/**
* Destroys the client and unregisters the callback. Any method on this class after this call
* will throw {@link IllegalStateException}.
*
* @deprecated
* @removed
*/
@Deprecated
public void destroy() {
if (!mIsClosed.getAndSet(true)) {
mCloseGuard.close();
@@ -188,6 +197,11 @@ public final class SearchSession implements AutoCloseable{
}
}
/**
* Destroys the client and unregisters the callback. Any method on this class after this call
* will throw {@link IllegalStateException}.
*
*/
@Override
public void close() {
try {

View File

@@ -15,34 +15,73 @@
*/
package android.app.search;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.slice.SliceManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* A representation of a searchable item info.
* A representation of a search result. Search result can be expressed in one of the following:
* app icon, shortcut, slice, widget, or a custom object using {@link SearchAction}. While
* app icon ({@link PackageManager}, shortcut {@link ShortcutManager}, slice {@link SliceManager},
* or widget (@link AppWidgetManager} are published content backed by the system service,
* {@link SearchAction} is a custom object that the service can use to send search result to the
* client.
*
* These various types of Android primitives could be defined as {@link SearchResultType}. Some
* times, the result type can define the layout type that that this object can be rendered in.
* (e.g., app widget). Most times, {@link #getLayoutType()} assigned by the service
* can recommend which layout this target should be rendered in.
*
* The service can also use fields such as {@link #getScore()} to indicate
* how confidence the search result is and {@link #shouldHide()} to indicate
* whether it is recommended to be shown by default.
*
* Finally, {@link #getId()} is the unique identifier of this search target and a single
* search target is defined by being able to express a single launcheable item. In case the
* service want to recommend how to combine multiple search target objects to render in a group
* (e.g., same row), {@link #getParentId()} can be assigned on the sub targets of the group
* using the primary search target's identifier.
*
* @hide
*/
@SystemApi
public final class SearchTarget implements Parcelable {
@NonNull
public static final int RESULT_TYPE_APPLICATION = 1 << 0;
public static final int RESULT_TYPE_SHORTCUT = 1 << 1;
public static final int RESULT_TYPE_SLICE = 1 << 2;
public static final int RESULT_TYPE_WIDGETS = 1 << 3;
/**
* @hide
*/
@IntDef(prefix = {"RESULT_TYPE_"}, value = {
RESULT_TYPE_APPLICATION,
RESULT_TYPE_SHORTCUT,
RESULT_TYPE_SLICE,
RESULT_TYPE_WIDGETS
})
@Retention(RetentionPolicy.SOURCE)
public @interface SearchResultType {}
private final int mResultType;
/**
* Constant to express how the group of {@link SearchTarget} should be laid out.
* Constant to express how the group of {@link SearchTarget} should be rendered on
* the client side. (e.g., "icon", "icon_row", "short_icon_row")
*/
@NonNull
private final String mLayoutType;
@@ -69,13 +108,13 @@ public final class SearchTarget implements Parcelable {
private final AppWidgetProviderInfo mAppWidgetProviderInfo;
@Nullable
private final Uri mSliceUri;
@Nullable
@NonNull
private final Bundle mExtras;
private SearchTarget(Parcel parcel) {
mResultType = parcel.readInt();
mLayoutType = parcel.readString();
mId = parcel.readString();
mParentId = parcel.readString();
mScore = parcel.readFloat();
@@ -102,7 +141,7 @@ public final class SearchTarget implements Parcelable {
@Nullable ShortcutInfo shortcutInfo,
@Nullable Uri sliceUri,
@Nullable AppWidgetProviderInfo appWidgetProviderInfo,
@Nullable Bundle extras) {
@NonNull Bundle extras) {
mResultType = resultType;
mLayoutType = Objects.requireNonNull(layoutType);
mId = Objects.requireNonNull(id);
@@ -129,9 +168,9 @@ public final class SearchTarget implements Parcelable {
}
/**
* Retrieves the result type.
* Retrieves the result type {@see SearchResultType}.
*/
public int getResultType() {
public @SearchResultType int getResultType() {
return mResultType;
}
@@ -167,7 +206,7 @@ public final class SearchTarget implements Parcelable {
}
/**
* TODO: add comment
* Indicates whether this object should be hidden and shown only on demand.
*/
public boolean shouldHide() {
return mShouldHide;
@@ -198,7 +237,7 @@ public final class SearchTarget implements Parcelable {
}
/**
* Return widget provider info.
* Return a widget provider info.
*/
@Nullable
public AppWidgetProviderInfo getAppWidgetProviderInfo() {
@@ -206,7 +245,7 @@ public final class SearchTarget implements Parcelable {
}
/**
* Return slice uri.
* Returns a slice uri.
*/
@Nullable
public Uri getSliceUri() {
@@ -214,7 +253,7 @@ public final class SearchTarget implements Parcelable {
}
/**
* Return search action.
* Returns a search action.
*/
@Nullable
public SearchAction getSearchAction() {
@@ -224,8 +263,7 @@ public final class SearchTarget implements Parcelable {
/**
* Return extra bundle.
*/
@Nullable
@SuppressLint("NullableCollection")
@NonNull
public Bundle getExtras() {
return mExtras;
}
@@ -295,10 +333,10 @@ public final class SearchTarget implements Parcelable {
private Uri mSliceUri;
@Nullable
private AppWidgetProviderInfo mAppWidgetProviderInfo;
@Nullable
@NonNull
private Bundle mExtras;
public Builder(int resultType,
public Builder(@SearchResultType int resultType,
@NonNull String layoutType,
@NonNull String id) {
mId = id;
@@ -369,32 +407,30 @@ public final class SearchTarget implements Parcelable {
*/
@NonNull
public Builder setSliceUri(@NonNull Uri sliceUri) {
// TODO: add packageName check
mSliceUri = sliceUri;
return this;
}
/**
* TODO: add comment
* Set the {@link SearchAction} object to this target.
*/
@NonNull
public Builder setSearchAction(@Nullable SearchAction remoteAction) {
// TODO: add packageName check
mSearchAction = remoteAction;
public Builder setSearchAction(@Nullable SearchAction searchAction) {
mSearchAction = searchAction;
return this;
}
/**
* TODO: add comment
* Set any extra information that needs to be shared between service and the client.
*/
@NonNull
public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
mExtras = extras;
public Builder setExtras(@NonNull Bundle extras) {
mExtras = Objects.requireNonNull(extras);
return this;
}
/**
* TODO: add comment
* Sets the score of the object.
*/
@NonNull
public Builder setScore(float score) {
@@ -403,7 +439,7 @@ public final class SearchTarget implements Parcelable {
}
/**
* TODO: add comment
* Sets whether the result should be hidden by default inside client.
*/
@NonNull
public Builder setShouldHide(boolean shouldHide) {

View File

@@ -29,7 +29,11 @@ import java.util.List;
import java.util.Objects;
/**
* A representation of an app target event.
* A representation of an search target event.
*
* There are two types of events. First type of event correspends to the user interaction
* that happens on the search surface. (e.g., {@link #ACTION_TAP}. Second type of events
* correspends to the lifecycle event of the search surface {@link #ACTION_SURFACE_VISIBLE}.
*
* @hide
*/

View File

@@ -25,6 +25,12 @@ import java.util.Objects;
/**
* Class that provides methods to create search ui session clients.
*
* Usage: <pre> {@code
* mSearchUiManager = context.getSystemService(SearchUiManager.class);
* mSearchSession.createSearchSession(searchContext)
*
* }</pre>
*
* @hide
*/
@SystemApi

View File

@@ -45,6 +45,9 @@ import java.util.function.Consumer;
* A service used to share the lifecycle of search UI (open, close, interaction)
* and also to return search result on a query.
*
* To understand the lifecycle of search session and how a query get issued,
* {@see SearchSession}
*
* @hide
*/
@SystemApi
@@ -70,6 +73,10 @@ public abstract class SearchUiService extends Service {
@Override
public void onCreateSearchSession(SearchContext context, SearchSessionId sessionId) {
mHandler.sendMessage(
obtainMessage(SearchUiService::onSearchSessionCreated,
SearchUiService.this, context, sessionId));
// to be removed
mHandler.sendMessage(
obtainMessage(SearchUiService::onCreateSearchSession,
SearchUiService.this, context, sessionId));
@@ -119,10 +126,23 @@ public abstract class SearchUiService extends Service {
/**
* Creates a new search session.
*
* @deprecated this is method will be removed as soon as
* {@link #onSearchSessionCreated(SearchContext, SearchSessionId)}
* is adopted by the service.
*
* @removed
*/
@Deprecated
public void onCreateSearchSession(@NonNull SearchContext context,
@NonNull SearchSessionId sessionId) {}
/**
* A new search session is created.
*/
public void onSearchSessionCreated(@NonNull SearchContext context,
@NonNull SearchSessionId sessionId) {}
/**
* Called by the client to request search results using a query string.
*/
@@ -132,7 +152,10 @@ public abstract class SearchUiService extends Service {
@NonNull Consumer<List<SearchTarget>> callback);
/**
* Called by a client to indicate an interaction (tap, long press, drag, etc) on target(s).
* Called by a client to indicate an interaction (tap, long press, drag, etc) on target(s)
* and lifecycle event on the search surface (e.g., visibility change).
*
* {@see SearchTargetEvent}
*/
@MainThread
public abstract void onNotifyEvent(@NonNull SearchSessionId sessionId,