Merge "Add Initial SearchUIManager"
This commit is contained in:
committed by
Android (Google) Code Review
commit
a2f98d16a6
@@ -129,6 +129,7 @@ package android {
|
||||
field public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS = "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS";
|
||||
field public static final String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
|
||||
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
|
||||
field public static final String MANAGE_SEARCH_UI = "android.permission.MANAGE_SEARCH_UI";
|
||||
field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
|
||||
field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
|
||||
field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
|
||||
@@ -1385,6 +1386,137 @@ package android.app.role {
|
||||
|
||||
}
|
||||
|
||||
package android.app.search {
|
||||
|
||||
public final class Query implements android.os.Parcelable {
|
||||
ctor public Query(@NonNull String, long, @Nullable android.os.Bundle);
|
||||
method public int describeContents();
|
||||
method @Nullable public android.os.Bundle getExtras();
|
||||
method @NonNull public String getInput();
|
||||
method @NonNull public long getTimestamp();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.app.search.Query> CREATOR;
|
||||
}
|
||||
|
||||
public final class SearchAction implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method @Nullable public CharSequence getContentDescription();
|
||||
method @Nullable public android.os.Bundle getExtras();
|
||||
method @Nullable public android.graphics.drawable.Icon getIcon();
|
||||
method @NonNull public String getId();
|
||||
method @Nullable public android.content.Intent getIntent();
|
||||
method @Nullable public android.app.PendingIntent getPendingIntent();
|
||||
method @Nullable public CharSequence getSubtitle();
|
||||
method @NonNull public CharSequence getTitle();
|
||||
method @Nullable public android.os.UserHandle getUserHandle();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.app.search.SearchAction> CREATOR;
|
||||
}
|
||||
|
||||
public static final class SearchAction.Builder {
|
||||
ctor public SearchAction.Builder(@NonNull String, @NonNull String);
|
||||
method @NonNull public android.app.search.SearchAction build();
|
||||
method @NonNull public android.app.search.SearchAction.Builder setContentDescription(@Nullable CharSequence);
|
||||
method @NonNull public android.app.search.SearchAction.Builder setExtras(@Nullable android.os.Bundle);
|
||||
method @NonNull public android.app.search.SearchAction.Builder setIcon(@Nullable android.graphics.drawable.Icon);
|
||||
method @NonNull public android.app.search.SearchAction.Builder setIntent(@Nullable android.content.Intent);
|
||||
method @NonNull public android.app.search.SearchAction.Builder setPendingIntent(@Nullable android.app.PendingIntent);
|
||||
method @NonNull public android.app.search.SearchAction.Builder setSubtitle(@Nullable CharSequence);
|
||||
method @NonNull public android.app.search.SearchAction.Builder setUserHandle(@Nullable android.os.UserHandle);
|
||||
}
|
||||
|
||||
public final class SearchContext implements android.os.Parcelable {
|
||||
ctor public SearchContext(int, int, @Nullable android.os.Bundle);
|
||||
method public int describeContents();
|
||||
method @Nullable public android.os.Bundle getExtras();
|
||||
method @Nullable public String getPackageName();
|
||||
method @NonNull public int getResultTypes();
|
||||
method @NonNull public int getTimeoutMillis();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.app.search.SearchContext> CREATOR;
|
||||
}
|
||||
|
||||
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>>);
|
||||
}
|
||||
|
||||
public final class SearchSessionId implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.app.search.SearchSessionId> CREATOR;
|
||||
}
|
||||
|
||||
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 String getId();
|
||||
method @NonNull public String getLayoutType();
|
||||
method @NonNull public String getPackageName();
|
||||
method @NonNull public String getParentId();
|
||||
method public int getResultType();
|
||||
method public float getScore();
|
||||
method @Nullable public android.app.search.SearchAction getSearchAction();
|
||||
method @Nullable public android.content.pm.ShortcutInfo getShortcutInfo();
|
||||
method @Nullable public android.net.Uri getSliceUri();
|
||||
method @NonNull public android.os.UserHandle getUserHandle();
|
||||
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;
|
||||
}
|
||||
|
||||
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 setPackageName(@NonNull String);
|
||||
method @NonNull public android.app.search.SearchTarget.Builder setParentId(@NonNull String);
|
||||
method @NonNull public android.app.search.SearchTarget.Builder setScore(float);
|
||||
method @NonNull public android.app.search.SearchTarget.Builder setSearchAction(@Nullable android.app.search.SearchAction);
|
||||
method @NonNull public android.app.search.SearchTarget.Builder setShortcutInfo(@NonNull android.content.pm.ShortcutInfo);
|
||||
method @NonNull public android.app.search.SearchTarget.Builder setShouldHide(boolean);
|
||||
method @NonNull public android.app.search.SearchTarget.Builder setSliceUri(@NonNull android.net.Uri);
|
||||
method @NonNull public android.app.search.SearchTarget.Builder setUserHandle(@NonNull android.os.UserHandle);
|
||||
}
|
||||
|
||||
public final class SearchTargetEvent implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public int getAction();
|
||||
method public int getFlags();
|
||||
method @Nullable public String getLaunchLocation();
|
||||
method @NonNull public String getTargetId();
|
||||
method @NonNull public java.util.List<java.lang.String> getTargetIds();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field public static final int ACTION_DRAGNDROP = 7; // 0x7
|
||||
field public static final int ACTION_LAUNCH_KEYBOARD_FOCUS = 6; // 0x6
|
||||
field public static final int ACTION_LAUNCH_TOUCH = 5; // 0x5
|
||||
field public static final int ACTION_LONGPRESS = 4; // 0x4
|
||||
field public static final int ACTION_SURFACE_INVISIBLE = 8; // 0x8
|
||||
field public static final int ACTION_SURFACE_VISIBLE = 1; // 0x1
|
||||
field public static final int ACTION_TAP = 3; // 0x3
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.app.search.SearchTargetEvent> CREATOR;
|
||||
field public static final int FLAG_IME_SHOWN = 1; // 0x1
|
||||
}
|
||||
|
||||
public static final class SearchTargetEvent.Builder {
|
||||
ctor public SearchTargetEvent.Builder(@NonNull String, int);
|
||||
ctor public SearchTargetEvent.Builder(@NonNull java.util.List<java.lang.String>, int);
|
||||
method @NonNull public android.app.search.SearchTargetEvent build();
|
||||
method @NonNull public android.app.search.SearchTargetEvent.Builder setFlags(int);
|
||||
method @NonNull public android.app.search.SearchTargetEvent.Builder setLaunchLocation(@Nullable String);
|
||||
}
|
||||
|
||||
public final class SearchUiManager {
|
||||
method @NonNull public android.app.search.SearchSession createSearchSession(@NonNull android.app.search.SearchContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.app.time {
|
||||
|
||||
public final class TimeManager {
|
||||
@@ -1806,6 +1938,7 @@ package android.content {
|
||||
field public static final String PERMISSION_SERVICE = "permission";
|
||||
field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
|
||||
field public static final String ROLLBACK_SERVICE = "rollback";
|
||||
field public static final String SEARCH_UI_SERVICE = "search_ui";
|
||||
field public static final String SECURE_ELEMENT_SERVICE = "secure_element";
|
||||
field public static final String STATS_MANAGER = "stats";
|
||||
field public static final String STATUS_BAR_SERVICE = "statusbar";
|
||||
@@ -9590,6 +9723,19 @@ package android.service.resolver {
|
||||
|
||||
}
|
||||
|
||||
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>>);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.service.settings.suggestions {
|
||||
|
||||
public final class Suggestion implements android.os.Parcelable {
|
||||
|
||||
@@ -19,6 +19,10 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize():
|
||||
|
||||
|
||||
|
||||
IntentBuilderName: android.app.search.SearchAction#getIntent():
|
||||
|
||||
|
||||
|
||||
KotlinKeyword: android.app.Notification#when:
|
||||
|
||||
|
||||
@@ -248,3 +252,9 @@ SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplora
|
||||
|
||||
SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams):
|
||||
|
||||
|
||||
|
||||
UserHandleName: android.app.search.SearchAction.Builder#setUserHandle(android.os.UserHandle):
|
||||
Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
|
||||
UserHandleName: android.app.search.SearchTarget.Builder#setUserHandle(android.os.UserHandle):
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.app.job.JobSchedulerFrameworkInitializer;
|
||||
import android.app.prediction.AppPredictionManager;
|
||||
import android.app.role.RoleControllerManager;
|
||||
import android.app.role.RoleManager;
|
||||
import android.app.search.SearchUiManager;
|
||||
import android.app.slice.SliceManager;
|
||||
import android.app.time.TimeManager;
|
||||
import android.app.timedetector.TimeDetector;
|
||||
@@ -1172,6 +1173,16 @@ public final class SystemServiceRegistry {
|
||||
return null;
|
||||
}});
|
||||
|
||||
registerService(Context.SEARCH_UI_SERVICE, SearchUiManager.class,
|
||||
new CachedServiceFetcher<SearchUiManager>() {
|
||||
@Override
|
||||
public SearchUiManager createService(ContextImpl ctx)
|
||||
throws ServiceNotFoundException {
|
||||
IBinder b = ServiceManager.getService(Context.SEARCH_UI_SERVICE);
|
||||
return b == null ? null : new SearchUiManager(ctx);
|
||||
}
|
||||
});
|
||||
|
||||
registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
|
||||
new CachedServiceFetcher<AppPredictionManager>() {
|
||||
@Override
|
||||
|
||||
27
core/java/android/app/search/ISearchCallback.aidl
Normal file
27
core/java/android/app/search/ISearchCallback.aidl
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.app.search;
|
||||
|
||||
import android.content.pm.ParceledListSlice;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
oneway interface ISearchCallback {
|
||||
|
||||
void onResult(in ParceledListSlice result);
|
||||
}
|
||||
40
core/java/android/app/search/ISearchUiManager.aidl
Normal file
40
core/java/android/app/search/ISearchUiManager.aidl
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.app.search;
|
||||
|
||||
import android.app.search.Query;
|
||||
import android.app.search.SearchTarget;
|
||||
import android.app.search.SearchTargetEvent;
|
||||
import android.app.search.SearchSessionId;
|
||||
import android.app.search.SearchContext;
|
||||
import android.app.search.ISearchCallback;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
interface ISearchUiManager {
|
||||
|
||||
void createSearchSession(in SearchContext context, in SearchSessionId sessionId,
|
||||
in IBinder token);
|
||||
|
||||
void query(in SearchSessionId sessionId, in Query input, in ISearchCallback callback);
|
||||
|
||||
void notifyEvent(in SearchSessionId sessionId, in Query input, in SearchTargetEvent event);
|
||||
|
||||
void destroySearchSession(in SearchSessionId sessionId);
|
||||
}
|
||||
19
core/java/android/app/search/Query.aidl
Normal file
19
core/java/android/app/search/Query.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2020, 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 android.app.search;
|
||||
|
||||
parcelable Query;
|
||||
102
core/java/android/app/search/Query.java
Normal file
102
core/java/android/app/search/Query.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.app.search;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class Query implements Parcelable {
|
||||
|
||||
/**
|
||||
* Query 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;
|
||||
|
||||
@Nullable
|
||||
private final Bundle mExtras;
|
||||
|
||||
public Query(@NonNull String input,
|
||||
long timestamp,
|
||||
@Nullable Bundle extras) {
|
||||
mInput = input;
|
||||
mTimestamp = timestamp;
|
||||
mExtras = extras;
|
||||
}
|
||||
|
||||
private Query(Parcel parcel) {
|
||||
mInput = parcel.readString();
|
||||
mTimestamp = parcel.readLong();
|
||||
mExtras = parcel.readBundle();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getInput() {
|
||||
return mInput;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public long getTimestamp() {
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeString(mInput);
|
||||
dest.writeLong(mTimestamp);
|
||||
dest.writeBundle(mExtras);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Creator
|
||||
*/
|
||||
@NonNull
|
||||
public static final Creator<Query> CREATOR =
|
||||
new Creator<Query>() {
|
||||
public Query createFromParcel(Parcel parcel) {
|
||||
return new Query(parcel);
|
||||
}
|
||||
|
||||
public Query[] newArray(int size) {
|
||||
return new Query[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
344
core/java/android/app/search/SearchAction.java
Normal file
344
core/java/android/app/search/SearchAction.java
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 android.app.search;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a searchable action info that can be called from another process
|
||||
* or within the client process.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class SearchAction implements Parcelable {
|
||||
|
||||
private static final String TAG = "SearchAction";
|
||||
|
||||
@NonNull
|
||||
private String mId;
|
||||
|
||||
@Nullable
|
||||
private final Icon mIcon;
|
||||
|
||||
@NonNull
|
||||
private final CharSequence mTitle;
|
||||
|
||||
@Nullable
|
||||
private final CharSequence mSubtitle;
|
||||
|
||||
@Nullable
|
||||
private final CharSequence mContentDescription;
|
||||
|
||||
@Nullable
|
||||
private final PendingIntent mPendingIntent;
|
||||
|
||||
@Nullable
|
||||
private final Intent mIntent;
|
||||
|
||||
@Nullable
|
||||
private final UserHandle mUserHandle;
|
||||
|
||||
@Nullable
|
||||
private Bundle mExtras;
|
||||
|
||||
SearchAction(Parcel in) {
|
||||
mId = in.readString();
|
||||
mIcon = Icon.CREATOR.createFromParcel(in);
|
||||
mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||||
mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||||
mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||||
mPendingIntent = PendingIntent.CREATOR.createFromParcel(in);
|
||||
mIntent = Intent.CREATOR.createFromParcel(in);
|
||||
mUserHandle = in.readTypedObject(UserHandle.CREATOR);
|
||||
mExtras = in.readBundle();
|
||||
}
|
||||
|
||||
private SearchAction(
|
||||
@NonNull String id,
|
||||
@Nullable Icon icon,
|
||||
@NonNull CharSequence title,
|
||||
@Nullable CharSequence subtitle,
|
||||
@Nullable CharSequence contentDescription,
|
||||
@Nullable PendingIntent pendingIntent,
|
||||
@Nullable Intent intent,
|
||||
@Nullable UserHandle userHandle,
|
||||
@Nullable Bundle extras) {
|
||||
mId = Objects.requireNonNull(id);
|
||||
mIcon = icon;
|
||||
mTitle = Objects.requireNonNull(title);
|
||||
mSubtitle = subtitle;
|
||||
mContentDescription = contentDescription;
|
||||
mPendingIntent = pendingIntent;
|
||||
mIntent = intent;
|
||||
mUserHandle = userHandle;
|
||||
mExtras = extras;
|
||||
|
||||
if (mPendingIntent == null && mIntent == null) {
|
||||
throw new IllegalStateException("At least one type of intent should be available.");
|
||||
}
|
||||
if (mPendingIntent != null && mIntent != null) {
|
||||
throw new IllegalStateException("Only one type of intent should be available.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique id of this object.
|
||||
*/
|
||||
public @NonNull String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an icon representing the action.
|
||||
*/
|
||||
public @Nullable Icon getIcon() {
|
||||
return mIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a title representing the action.
|
||||
*/
|
||||
public @NonNull CharSequence getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a subtitle representing the action.
|
||||
*/
|
||||
public @Nullable CharSequence getSubtitle() {
|
||||
return mSubtitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a content description representing the action.
|
||||
*/
|
||||
public @Nullable CharSequence getContentDescription() {
|
||||
return mContentDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action intent.
|
||||
*/
|
||||
public @Nullable PendingIntent getPendingIntent() {
|
||||
return mPendingIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the intent.
|
||||
*/
|
||||
public @Nullable Intent getIntent() {
|
||||
return mIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user handle.
|
||||
*/
|
||||
public @Nullable UserHandle getUserHandle() {
|
||||
return mUserHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extra bundle for this object.
|
||||
*/
|
||||
public @Nullable Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof SearchAction)) return false;
|
||||
SearchAction that = (SearchAction) o;
|
||||
return mId.equals(that.mId) && mTitle.equals(that.mTitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mId, mTitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel out, int flags) {
|
||||
out.writeString(mId);
|
||||
out.writeTypedObject(mIcon, flags);
|
||||
TextUtils.writeToParcel(mTitle, out, flags);
|
||||
TextUtils.writeToParcel(mSubtitle, out, flags);
|
||||
TextUtils.writeToParcel(mContentDescription, out, flags);
|
||||
out.writeTypedObject(mPendingIntent, flags);
|
||||
out.writeTypedObject(mIntent, flags);
|
||||
out.writeTypedObject(mUserHandle, flags);
|
||||
out.writeBundle(mExtras);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String str = "id=" + mId
|
||||
+ " title=" + mTitle
|
||||
+ " contentDescription=" + mContentDescription
|
||||
+ " subtitle=" + mSubtitle
|
||||
+ " icon=" + mIcon
|
||||
+ " pendingIntent=" + (mPendingIntent == null ? "" : mPendingIntent.getIntent())
|
||||
+ " intent=" + mIntent
|
||||
+ " userHandle=" + mUserHandle;
|
||||
return str;
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Parcelable.Creator<SearchAction> CREATOR =
|
||||
new Parcelable.Creator<SearchAction>() {
|
||||
public SearchAction createFromParcel(Parcel in) {
|
||||
return new SearchAction(in);
|
||||
}
|
||||
public SearchAction[] newArray(int size) {
|
||||
return new SearchAction[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A builder for search action object.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final class Builder {
|
||||
@NonNull
|
||||
private String mId;
|
||||
|
||||
@Nullable
|
||||
private Icon mIcon;
|
||||
|
||||
@NonNull
|
||||
private CharSequence mTitle;
|
||||
|
||||
@Nullable
|
||||
private CharSequence mSubtitle;
|
||||
|
||||
@Nullable
|
||||
private CharSequence mContentDescription;
|
||||
|
||||
@Nullable
|
||||
private PendingIntent mPendingIntent;
|
||||
|
||||
@Nullable
|
||||
private Intent mIntent;
|
||||
|
||||
@Nullable
|
||||
private UserHandle mUserHandle;
|
||||
|
||||
@Nullable
|
||||
private Bundle mExtras;
|
||||
|
||||
public Builder(@NonNull String id, @NonNull String title) {
|
||||
mId = Objects.requireNonNull(id);
|
||||
mTitle = Objects.requireNonNull(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtitle.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchAction.Builder setIcon(
|
||||
@Nullable Icon icon) {
|
||||
mIcon = icon;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the subtitle.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchAction.Builder setSubtitle(
|
||||
@Nullable CharSequence subtitle) {
|
||||
mSubtitle = subtitle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content description.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchAction.Builder setContentDescription(
|
||||
@Nullable CharSequence contentDescription) {
|
||||
mContentDescription = contentDescription;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pending intent.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchAction.Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
|
||||
mPendingIntent = pendingIntent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user handle.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchAction.Builder setUserHandle(@Nullable UserHandle userHandle) {
|
||||
mUserHandle = userHandle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the intent.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchAction.Builder setIntent(@Nullable Intent intent) {
|
||||
mIntent = intent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the extra.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchAction.Builder setExtras(@Nullable Bundle extras) {
|
||||
mExtras = extras;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new SearchAction instance.
|
||||
*
|
||||
* @throws IllegalStateException if no target is set
|
||||
*/
|
||||
@NonNull
|
||||
public SearchAction build() {
|
||||
return new SearchAction(mId, mIcon, mTitle, mSubtitle, mContentDescription,
|
||||
mPendingIntent, mIntent, mUserHandle, mExtras);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
core/java/android/app/search/SearchContext.aidl
Normal file
19
core/java/android/app/search/SearchContext.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2020, 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 android.app.search;
|
||||
|
||||
parcelable SearchContext;
|
||||
122
core/java/android/app/search/SearchContext.java
Normal file
122
core/java/android/app/search/SearchContext.java
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.app.search;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class SearchContext implements Parcelable {
|
||||
|
||||
/**
|
||||
* Result types the client UI is expecting the service to return.
|
||||
*/
|
||||
private final int mResultTypes;
|
||||
|
||||
/**
|
||||
* Timeout constraint imposed from the client UI for the first search result.
|
||||
*/
|
||||
private final int mTimeoutMillis;
|
||||
|
||||
/**
|
||||
* Send other client UI configurations in extras.
|
||||
*/
|
||||
@Nullable
|
||||
private final Bundle mExtras;
|
||||
|
||||
/**
|
||||
* Package name of the client.
|
||||
*/
|
||||
@Nullable
|
||||
private String mPackageName;
|
||||
|
||||
public SearchContext(int resultTypes,
|
||||
int queryTimeoutMillis,
|
||||
@Nullable Bundle extras) {
|
||||
mResultTypes = resultTypes;
|
||||
mTimeoutMillis = queryTimeoutMillis;
|
||||
mExtras = extras;
|
||||
}
|
||||
|
||||
private SearchContext(Parcel parcel) {
|
||||
mResultTypes = parcel.readInt();
|
||||
mTimeoutMillis = parcel.readInt();
|
||||
mPackageName = parcel.readString();
|
||||
mExtras = parcel.readBundle();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void setPackageName(@Nullable String packageName) {
|
||||
mPackageName = packageName;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public int getTimeoutMillis() {
|
||||
return mTimeoutMillis;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public int getResultTypes() {
|
||||
return mResultTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeInt(mResultTypes);
|
||||
dest.writeInt(mTimeoutMillis);
|
||||
dest.writeString(mPackageName);
|
||||
dest.writeBundle(mExtras);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Parcelable.Creator
|
||||
*/
|
||||
@NonNull
|
||||
public static final Parcelable.Creator<SearchContext> CREATOR =
|
||||
new Parcelable.Creator<SearchContext>() {
|
||||
public SearchContext createFromParcel(Parcel parcel) {
|
||||
return new SearchContext(parcel);
|
||||
}
|
||||
|
||||
public SearchContext[] newArray(int size) {
|
||||
return new SearchContext[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
224
core/java/android/app/search/SearchSession.java
Normal file
224
core/java/android/app/search/SearchSession.java
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.app.search;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.search.ISearchCallback.Stub;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import dalvik.system.CloseGuard;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Client API to share information about the search UI state and execute query.
|
||||
*
|
||||
* <p>
|
||||
* Usage: <pre> {@code
|
||||
*
|
||||
* class MyActivity {
|
||||
*
|
||||
* void onCreate() {
|
||||
* mSearchSession.createSearchSession(searchContext)
|
||||
* }
|
||||
*
|
||||
* void afterTextChanged(...) {
|
||||
* mSearchSession.query(...);
|
||||
* }
|
||||
*
|
||||
* void onTouch(...) OR
|
||||
* void onStateTransitionStarted(...) OR
|
||||
* void onResume(...) OR
|
||||
* void onStop(...) {
|
||||
* mSearchSession.notifyEvent(event);
|
||||
* }
|
||||
*
|
||||
* void onDestroy() {
|
||||
* mSearchSession.destroy();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class SearchSession implements AutoCloseable{
|
||||
|
||||
private static final String TAG = SearchSession.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final android.app.search.ISearchUiManager mInterface;
|
||||
private final CloseGuard mCloseGuard = CloseGuard.get();
|
||||
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
|
||||
|
||||
private final SearchSessionId mSessionId;
|
||||
private final IBinder mToken = new Binder();
|
||||
|
||||
/**
|
||||
* Creates a new search ui client.
|
||||
* <p>
|
||||
* The caller should call {@link SearchSession#destroy()} to dispose the client once it
|
||||
* no longer used.
|
||||
*
|
||||
* @param context the {@link Context} of the user of this {@link SearchSession}.
|
||||
* @param searchContext the search context.
|
||||
*/
|
||||
// b/175668315 Create weak reference child objects to not leak context.
|
||||
SearchSession(@NonNull Context context, @NonNull SearchContext searchContext) {
|
||||
IBinder b = ServiceManager.getService(Context.SEARCH_UI_SERVICE);
|
||||
mInterface = android.app.search.ISearchUiManager.Stub.asInterface(b);
|
||||
mSessionId = new SearchSessionId(
|
||||
context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
|
||||
// b/175527717 whitelist possible clients of this API
|
||||
searchContext.setPackageName(context.getPackageName());
|
||||
try {
|
||||
mInterface.createSearchSession(searchContext, mSessionId, mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to search session", e);
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
mCloseGuard.open("close");
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the search service of an search target event.
|
||||
*
|
||||
* @param query input object associated with the event.
|
||||
* @param event The {@link SearchTargetEvent} that represents the search target event.
|
||||
*/
|
||||
public void notifyEvent(@NonNull Query query, @NonNull SearchTargetEvent event) {
|
||||
if (mIsClosed.get()) {
|
||||
throw new IllegalStateException("This client has already been destroyed.");
|
||||
}
|
||||
|
||||
try {
|
||||
mInterface.notifyEvent(mSessionId, query, event);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to notify event", e);
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls consumer with list of {@link SearchTarget}s based on the input query.
|
||||
*
|
||||
* @param input query object to be used for the request.
|
||||
* @param callbackExecutor The callback executor to use when calling the callback.
|
||||
* @param callback The callback to return the list of search targets.
|
||||
*/
|
||||
@Nullable
|
||||
public void query(@NonNull Query input,
|
||||
@NonNull @CallbackExecutor Executor callbackExecutor,
|
||||
@NonNull Consumer<List<SearchTarget>> callback) {
|
||||
if (mIsClosed.get()) {
|
||||
throw new IllegalStateException("This client has already been destroyed.");
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
mInterface.query(mSessionId, input, new CallbackWrapper(callbackExecutor, callback));
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to sort targets", e);
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the client and unregisters the callback. Any method on this class after this call
|
||||
* will throw {@link IllegalStateException}.
|
||||
*/
|
||||
public void destroy() {
|
||||
if (!mIsClosed.getAndSet(true)) {
|
||||
mCloseGuard.close();
|
||||
|
||||
// Do destroy;
|
||||
try {
|
||||
mInterface.destroySearchSession(mSessionId);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to notify search target event", e);
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("This client has already been destroyed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() {
|
||||
try {
|
||||
if (mCloseGuard != null) {
|
||||
mCloseGuard.warnIfOpen();
|
||||
}
|
||||
if (!mIsClosed.get()) {
|
||||
destroy();
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
super.finalize();
|
||||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
finalize();
|
||||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static class CallbackWrapper extends Stub {
|
||||
|
||||
private final Consumer<List<SearchTarget>> mCallback;
|
||||
private final Executor mExecutor;
|
||||
|
||||
CallbackWrapper(@NonNull Executor callbackExecutor,
|
||||
@NonNull Consumer<List<SearchTarget>> callback) {
|
||||
mCallback = callback;
|
||||
mExecutor = callbackExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResult(ParceledListSlice result) {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
|
||||
}
|
||||
mExecutor.execute(() -> mCallback.accept(result.getList()));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
core/java/android/app/search/SearchSessionId.aidl
Normal file
19
core/java/android/app/search/SearchSessionId.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2020, 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 android.app.search;
|
||||
|
||||
parcelable SearchSessionId;
|
||||
99
core/java/android/app/search/SearchSessionId.java
Normal file
99
core/java/android/app/search/SearchSessionId.java
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.app.search;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The id for an search session. See {@link SearchSession}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class SearchSessionId implements Parcelable {
|
||||
|
||||
private final String mId;
|
||||
private final int mUserId;
|
||||
|
||||
/**
|
||||
* Creates a new id for a search session.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public SearchSessionId(@NonNull final String id, final int userId) {
|
||||
mId = id;
|
||||
mUserId = userId;
|
||||
}
|
||||
|
||||
private SearchSessionId(Parcel p) {
|
||||
mId = p.readString();
|
||||
mUserId = p.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public int getUserId() {
|
||||
return mUserId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (!getClass().equals(o != null ? o.getClass() : null)) return false;
|
||||
|
||||
SearchSessionId other = (SearchSessionId) o;
|
||||
return mId.equals(other.mId) && mUserId == other.mUserId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return mId + "," + mUserId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mId, mUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeString(mId);
|
||||
dest.writeInt(mUserId);
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<SearchSessionId> CREATOR =
|
||||
new Creator<SearchSessionId>() {
|
||||
public SearchSessionId createFromParcel(Parcel parcel) {
|
||||
return new SearchSessionId(parcel);
|
||||
}
|
||||
|
||||
public SearchSessionId[] newArray(int size) {
|
||||
return new SearchSessionId[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
19
core/java/android/app/search/SearchTarget.aidl
Normal file
19
core/java/android/app/search/SearchTarget.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2020, 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 android.app.search;
|
||||
|
||||
parcelable SearchTarget;
|
||||
425
core/java/android/app/search/SearchTarget.java
Normal file
425
core/java/android/app/search/SearchTarget.java
Normal file
@@ -0,0 +1,425 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.app.search;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A representation of a searchable item info.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class SearchTarget implements Parcelable {
|
||||
|
||||
|
||||
@NonNull
|
||||
private final int mResultType;
|
||||
|
||||
/**
|
||||
* Constant to express how the group of {@link SearchTarget} should be laid out.
|
||||
*/
|
||||
@NonNull
|
||||
private final String mLayoutType;
|
||||
|
||||
@NonNull
|
||||
private final String mId;
|
||||
|
||||
@Nullable
|
||||
private String mParentId;
|
||||
|
||||
private final float mScore;
|
||||
|
||||
private final boolean mShouldHide;
|
||||
|
||||
@NonNull
|
||||
private final String mPackageName;
|
||||
@NonNull
|
||||
private final UserHandle mUserHandle;
|
||||
@Nullable
|
||||
private final SearchAction mSearchAction;
|
||||
@Nullable
|
||||
private final ShortcutInfo mShortcutInfo;
|
||||
@Nullable
|
||||
private final AppWidgetProviderInfo mAppWidgetProviderInfo;
|
||||
@Nullable
|
||||
private final Uri mSliceUri;
|
||||
@Nullable
|
||||
private final Bundle mExtras;
|
||||
|
||||
private SearchTarget(Parcel parcel) {
|
||||
mResultType = parcel.readInt();
|
||||
mLayoutType = parcel.readString();
|
||||
|
||||
mId = parcel.readString();
|
||||
mParentId = parcel.readString();
|
||||
mScore = parcel.readFloat();
|
||||
mShouldHide = parcel.readBoolean();
|
||||
|
||||
mPackageName = parcel.readString();
|
||||
mUserHandle = UserHandle.of(parcel.readInt());
|
||||
mSearchAction = parcel.readTypedObject(SearchAction.CREATOR);
|
||||
mShortcutInfo = parcel.readTypedObject(ShortcutInfo.CREATOR);
|
||||
mAppWidgetProviderInfo = parcel.readTypedObject(AppWidgetProviderInfo.CREATOR);
|
||||
mSliceUri = parcel.readTypedObject(Uri.CREATOR);
|
||||
mExtras = parcel.readBundle(getClass().getClassLoader());
|
||||
}
|
||||
|
||||
private SearchTarget(
|
||||
int resultType,
|
||||
@NonNull String layoutType,
|
||||
@NonNull String id,
|
||||
@Nullable String parentId,
|
||||
float score, boolean shouldHide,
|
||||
@NonNull String packageName,
|
||||
@NonNull UserHandle userHandle,
|
||||
@Nullable SearchAction action,
|
||||
@Nullable ShortcutInfo shortcutInfo,
|
||||
@Nullable Uri sliceUri,
|
||||
@Nullable AppWidgetProviderInfo appWidgetProviderInfo,
|
||||
@Nullable Bundle extras) {
|
||||
mResultType = resultType;
|
||||
mLayoutType = Objects.requireNonNull(layoutType);
|
||||
mId = Objects.requireNonNull(id);
|
||||
mParentId = parentId;
|
||||
mScore = score;
|
||||
mShouldHide = shouldHide;
|
||||
mPackageName = Objects.requireNonNull(packageName);
|
||||
mUserHandle = Objects.requireNonNull(userHandle);
|
||||
mSearchAction = action;
|
||||
mShortcutInfo = shortcutInfo;
|
||||
mAppWidgetProviderInfo = appWidgetProviderInfo;
|
||||
mSliceUri = sliceUri;
|
||||
mExtras = extras;
|
||||
|
||||
int published = 0;
|
||||
if (mSearchAction != null) published++;
|
||||
if (mShortcutInfo != null) published++;
|
||||
if (mAppWidgetProviderInfo != null) published++;
|
||||
if (mSliceUri != null) published++;
|
||||
if (published > 1) {
|
||||
throw new IllegalStateException("Only one of SearchAction, ShortcutInfo,"
|
||||
+ " AppWidgetProviderInfo, SliceUri can be assigned in a SearchTarget.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the result type.
|
||||
*/
|
||||
public int getResultType() {
|
||||
return mResultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the layout type.
|
||||
*/
|
||||
@NonNull
|
||||
public String getLayoutType() {
|
||||
return mLayoutType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the id of the target.
|
||||
*/
|
||||
@NonNull
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the parent id of the target.
|
||||
*/
|
||||
@NonNull
|
||||
public String getParentId() {
|
||||
return mParentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the score of the target.
|
||||
*/
|
||||
public float getScore() {
|
||||
return mScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add comment
|
||||
*/
|
||||
public boolean shouldHide() {
|
||||
return mShouldHide;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the package name of the target.
|
||||
*/
|
||||
@NonNull
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the user handle of the target.
|
||||
*/
|
||||
@NonNull
|
||||
public UserHandle getUserHandle() {
|
||||
return mUserHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the shortcut info of the target.
|
||||
*/
|
||||
@Nullable
|
||||
public ShortcutInfo getShortcutInfo() {
|
||||
return mShortcutInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return widget provider info.
|
||||
*/
|
||||
@Nullable
|
||||
public AppWidgetProviderInfo getAppWidgetProviderInfo() {
|
||||
return mAppWidgetProviderInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return slice uri.
|
||||
*/
|
||||
@Nullable
|
||||
public Uri getSliceUri() {
|
||||
return mSliceUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return search action.
|
||||
*/
|
||||
@Nullable
|
||||
public SearchAction getSearchAction() {
|
||||
return mSearchAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return extra bundle.
|
||||
*/
|
||||
@Nullable
|
||||
public Bundle getExtras() {
|
||||
return mExtras;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int flags) {
|
||||
parcel.writeInt(mResultType);
|
||||
parcel.writeString(mLayoutType);
|
||||
parcel.writeString(mId);
|
||||
parcel.writeString(mParentId);
|
||||
parcel.writeFloat(mScore);
|
||||
parcel.writeBoolean(mShouldHide);
|
||||
parcel.writeString(mPackageName);
|
||||
parcel.writeInt(mUserHandle.getIdentifier());
|
||||
parcel.writeTypedObject(mSearchAction, flags);
|
||||
parcel.writeTypedObject(mShortcutInfo, flags);
|
||||
parcel.writeTypedObject(mAppWidgetProviderInfo, flags);
|
||||
parcel.writeTypedObject(mSliceUri, flags);
|
||||
parcel.writeBundle(mExtras);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Parcelable.Creator
|
||||
*/
|
||||
@NonNull
|
||||
public static final Parcelable.Creator<SearchTarget> CREATOR =
|
||||
new Parcelable.Creator<SearchTarget>() {
|
||||
public SearchTarget createFromParcel(Parcel parcel) {
|
||||
return new SearchTarget(parcel);
|
||||
}
|
||||
|
||||
public SearchTarget[] newArray(int size) {
|
||||
return new SearchTarget[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A builder for search target object.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final class Builder {
|
||||
private int mResultType;
|
||||
@NonNull
|
||||
private String mLayoutType;
|
||||
@NonNull
|
||||
private String mId;
|
||||
@Nullable
|
||||
private String mParentId;
|
||||
private float mScore;
|
||||
private boolean mShouldHide;
|
||||
@NonNull
|
||||
private String mPackageName;
|
||||
@NonNull
|
||||
private UserHandle mUserHandle;
|
||||
@Nullable
|
||||
private SearchAction mSearchAction;
|
||||
@Nullable
|
||||
private ShortcutInfo mShortcutInfo;
|
||||
@Nullable
|
||||
private Uri mSliceUri;
|
||||
@Nullable
|
||||
private AppWidgetProviderInfo mAppWidgetProviderInfo;
|
||||
@Nullable
|
||||
private Bundle mExtras;
|
||||
|
||||
public Builder(int resultType,
|
||||
@NonNull String layoutType,
|
||||
@NonNull String id) {
|
||||
mId = id;
|
||||
mLayoutType = Objects.requireNonNull(layoutType);
|
||||
mResultType = resultType;
|
||||
mScore = 1f;
|
||||
mShouldHide = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parent id.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setParentId(@NonNull String parentId) {
|
||||
mParentId = Objects.requireNonNull(parentId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the package name.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setPackageName(@NonNull String packageName) {
|
||||
mPackageName = Objects.requireNonNull(packageName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user handle.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setUserHandle(@NonNull UserHandle userHandle) {
|
||||
mUserHandle = Objects.requireNonNull(userHandle);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the shortcut info.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setShortcutInfo(@NonNull ShortcutInfo shortcutInfo) {
|
||||
mShortcutInfo = Objects.requireNonNull(shortcutInfo);
|
||||
if (mPackageName != null && !mPackageName.equals(shortcutInfo.getPackage())) {
|
||||
throw new IllegalStateException("SearchTarget packageName is different from "
|
||||
+ "shortcut's packageName");
|
||||
}
|
||||
mPackageName = shortcutInfo.getPackage();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the app widget provider info.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setAppWidgetProviderInfo(
|
||||
@NonNull AppWidgetProviderInfo appWidgetProviderInfo) {
|
||||
mAppWidgetProviderInfo = Objects.requireNonNull(appWidgetProviderInfo);
|
||||
if (mPackageName != null
|
||||
&& !mPackageName.equals(appWidgetProviderInfo.provider.getPackageName())) {
|
||||
throw new IllegalStateException("SearchTarget packageName is different from "
|
||||
+ "appWidgetProviderInfo's packageName");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the slice URI.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setSliceUri(@NonNull Uri sliceUri) {
|
||||
// TODO: add packageName check
|
||||
mSliceUri = sliceUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add comment
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setSearchAction(@Nullable SearchAction remoteAction) {
|
||||
// TODO: add packageName check
|
||||
mSearchAction = remoteAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add comment
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setExtras(@Nullable Bundle extras) {
|
||||
mExtras = extras;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add comment
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setScore(float score) {
|
||||
mScore = score;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add comment
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setShouldHide(boolean shouldHide) {
|
||||
mShouldHide = shouldHide;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new SearchTarget instance.
|
||||
*
|
||||
* @throws IllegalStateException if no target is set
|
||||
*/
|
||||
@NonNull
|
||||
public SearchTarget build() {
|
||||
return new SearchTarget(mResultType, mLayoutType, mId, mParentId, mScore, mShouldHide,
|
||||
mPackageName, mUserHandle,
|
||||
mSearchAction, mShortcutInfo, mSliceUri, mAppWidgetProviderInfo,
|
||||
mExtras);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
core/java/android/app/search/SearchTargetEvent.aidl
Normal file
19
core/java/android/app/search/SearchTargetEvent.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2020, 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 android.app.search;
|
||||
|
||||
parcelable SearchTargetEvent;
|
||||
267
core/java/android/app/search/SearchTargetEvent.java
Normal file
267
core/java/android/app/search/SearchTargetEvent.java
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.app.search;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A representation of an app target event.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class SearchTargetEvent implements Parcelable {
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@IntDef(prefix = {"ACTION_"}, value = {
|
||||
ACTION_SURFACE_VISIBLE,
|
||||
ACTION_TAP,
|
||||
ACTION_LONGPRESS,
|
||||
ACTION_LAUNCH_TOUCH,
|
||||
ACTION_LAUNCH_KEYBOARD_FOCUS,
|
||||
ACTION_DRAGNDROP,
|
||||
ACTION_SURFACE_INVISIBLE
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ActionType {}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@IntDef(prefix = {"FLAG_"}, value = {
|
||||
FLAG_IME_SHOWN
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface FlagType {}
|
||||
|
||||
@NonNull
|
||||
private final List<String> mTargetIds;
|
||||
@Nullable
|
||||
private final String mLocation;
|
||||
@ActionType
|
||||
private final int mAction;
|
||||
@FlagType
|
||||
private int mFlags;
|
||||
|
||||
/**
|
||||
* IME was shown when event happened.
|
||||
*/
|
||||
public static final int FLAG_IME_SHOWN = 1 << 0;
|
||||
|
||||
|
||||
/**
|
||||
* Search container was opened.
|
||||
*/
|
||||
public static final int ACTION_SURFACE_VISIBLE = 1;
|
||||
|
||||
/**
|
||||
* Constants that define tapping without closing the search surface.
|
||||
*/
|
||||
public static final int ACTION_TAP = 3;
|
||||
|
||||
/**
|
||||
* Constants that define long pressing without closing the search surface.
|
||||
*/
|
||||
public static final int ACTION_LONGPRESS = 4;
|
||||
|
||||
/**
|
||||
* Constants that define tapping on the touch target to launch.
|
||||
*/
|
||||
public static final int ACTION_LAUNCH_TOUCH = 5;
|
||||
|
||||
/**
|
||||
* Constants that define tapping on the soft keyboard confirm or search to launch.
|
||||
*/
|
||||
public static final int ACTION_LAUNCH_KEYBOARD_FOCUS = 6;
|
||||
|
||||
/**
|
||||
* Searcheable item was draged and dropped to another surface.
|
||||
*/
|
||||
public static final int ACTION_DRAGNDROP = 7;
|
||||
|
||||
/**
|
||||
* Search container was closed.
|
||||
*/
|
||||
public static final int ACTION_SURFACE_INVISIBLE = 8;
|
||||
|
||||
private SearchTargetEvent(@NonNull List<String> targetIds,
|
||||
@Nullable String location,
|
||||
@ActionType int actionType,
|
||||
@FlagType int flags) {
|
||||
mTargetIds = Objects.requireNonNull(targetIds);
|
||||
mLocation = location;
|
||||
mAction = actionType;
|
||||
mFlags = flags;
|
||||
}
|
||||
|
||||
private SearchTargetEvent(Parcel parcel) {
|
||||
mTargetIds = new ArrayList<>();
|
||||
parcel.readStringList(mTargetIds);
|
||||
mLocation = parcel.readString();
|
||||
mAction = parcel.readInt();
|
||||
mFlags = parcel.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary search target with interaction.
|
||||
*/
|
||||
@NonNull
|
||||
public String getTargetId() {
|
||||
return mTargetIds.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of search targets with visualization change.
|
||||
*/
|
||||
@NonNull
|
||||
public List<String> getTargetIds() {
|
||||
return mTargetIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the launch location.
|
||||
*/
|
||||
@Nullable
|
||||
public String getLaunchLocation() {
|
||||
return mLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action type.
|
||||
*/
|
||||
@ActionType
|
||||
public int getAction() {
|
||||
return mAction;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return mFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mTargetIds.get(0).hashCode() + mAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (!getClass().equals(o != null ? o.getClass() : null)) return false;
|
||||
|
||||
SearchTargetEvent other = (SearchTargetEvent) o;
|
||||
return mTargetIds.equals(other.mTargetIds)
|
||||
&& mAction == other.mAction
|
||||
&& mFlags == other.mFlags
|
||||
&& mLocation == null ? other.mLocation == null : mLocation.equals(other.mLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeStringList(mTargetIds);
|
||||
dest.writeString(mLocation);
|
||||
dest.writeInt(mAction);
|
||||
dest.writeInt(mFlags);
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<SearchTargetEvent> CREATOR =
|
||||
new Creator<SearchTargetEvent>() {
|
||||
public SearchTargetEvent createFromParcel(Parcel parcel) {
|
||||
return new SearchTargetEvent(parcel);
|
||||
}
|
||||
|
||||
public SearchTargetEvent[] newArray(int size) {
|
||||
return new SearchTargetEvent[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A builder for search target events.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final class Builder {
|
||||
@NonNull
|
||||
private List<String> mTargetIds;
|
||||
@Nullable
|
||||
private String mLocation;
|
||||
@ActionType
|
||||
private int mAction;
|
||||
private int mFlags;
|
||||
|
||||
/**
|
||||
* @param id The target id that is associated with this event.
|
||||
* @param actionType The event type
|
||||
*/
|
||||
public Builder(@NonNull String id, @ActionType int actionType) {
|
||||
mTargetIds = new ArrayList<>();
|
||||
mTargetIds.add(id);
|
||||
mAction = actionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ids The target ids that is associated with this event.
|
||||
* @param actionType The event type
|
||||
*/
|
||||
public Builder(@NonNull List<String> ids, @ActionType int actionType) {
|
||||
mTargetIds = ids;
|
||||
mAction = actionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the launch location.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setLaunchLocation(@Nullable String location) {
|
||||
mLocation = location;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the launch location.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setFlags(int flags) {
|
||||
mFlags = flags;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new event instance.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchTargetEvent build() {
|
||||
return new SearchTargetEvent(mTargetIds, mLocation, mAction, mFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
core/java/android/app/search/SearchUiManager.java
Normal file
50
core/java/android/app/search/SearchUiManager.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.app.search;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Class that provides methods to create search ui session clients.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class SearchUiManager {
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public SearchUiManager(Context context) {
|
||||
mContext = Objects.requireNonNull(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new search session.
|
||||
*/
|
||||
@NonNull
|
||||
public SearchSession createSearchSession(
|
||||
@NonNull SearchContext searchContext) {
|
||||
return new SearchSession(mContext, searchContext);
|
||||
}
|
||||
}
|
||||
@@ -4528,6 +4528,18 @@ public abstract class Context {
|
||||
@SystemApi
|
||||
public static final String APP_PREDICTION_SERVICE = "app_prediction";
|
||||
|
||||
/**
|
||||
* Official published name of the search ui service.
|
||||
*
|
||||
* <p><b>NOTE: </b> this service is optional; callers of
|
||||
* {@code Context.getSystemServiceName(SEARCH_UI_SERVICE)} should check for {@code null}.
|
||||
*
|
||||
* @hide
|
||||
* @see #getSystemService(String)
|
||||
*/
|
||||
@SystemApi
|
||||
public static final String SEARCH_UI_SERVICE = "search_ui";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to access the
|
||||
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
|
||||
|
||||
41
core/java/android/service/search/ISearchUiService.aidl
Normal file
41
core/java/android/service/search/ISearchUiService.aidl
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.service.search;
|
||||
|
||||
import android.app.search.Query;
|
||||
import android.app.search.SearchTarget;
|
||||
import android.app.search.SearchTargetEvent;
|
||||
import android.app.search.SearchSessionId;
|
||||
import android.app.search.SearchContext;
|
||||
import android.app.search.ISearchCallback;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
|
||||
/**
|
||||
* Interface from the system to a search service.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
oneway interface ISearchUiService {
|
||||
|
||||
void onCreateSearchSession(in SearchContext context, in SearchSessionId sessionId);
|
||||
|
||||
void onQuery(in SearchSessionId sessionId, in Query input, in ISearchCallback callback);
|
||||
|
||||
void onNotifyEvent(in SearchSessionId sessionId, in Query input, in SearchTargetEvent event);
|
||||
|
||||
void onDestroy(in SearchSessionId sessionId);
|
||||
}
|
||||
193
core/java/android/service/search/SearchUiService.java
Normal file
193
core/java/android/service/search/SearchUiService.java
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.service.search;
|
||||
|
||||
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
||||
|
||||
import android.annotation.CallSuper;
|
||||
import android.annotation.MainThread;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.Service;
|
||||
import android.app.search.ISearchCallback;
|
||||
import android.app.search.Query;
|
||||
import android.app.search.SearchContext;
|
||||
import android.app.search.SearchSessionId;
|
||||
import android.app.search.SearchTarget;
|
||||
import android.app.search.SearchTargetEvent;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.service.search.ISearchUiService.Stub;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.util.List;
|
||||
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.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public abstract class SearchUiService extends Service {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "SearchUiService";
|
||||
|
||||
/**
|
||||
* The {@link Intent} that must be declared as handled by the service.
|
||||
*
|
||||
* <p>The service must also require the {@link android.permission#MANAGE_SEARCH_UI}
|
||||
* permission.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String SERVICE_INTERFACE =
|
||||
"android.service.search.SearchUiService";
|
||||
|
||||
private Handler mHandler;
|
||||
|
||||
private final android.service.search.ISearchUiService mInterface = new Stub() {
|
||||
|
||||
@Override
|
||||
public void onCreateSearchSession(SearchContext context, SearchSessionId sessionId) {
|
||||
mHandler.sendMessage(
|
||||
obtainMessage(SearchUiService::onCreateSearchSession,
|
||||
SearchUiService.this, context, sessionId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQuery(SearchSessionId sessionId, Query input,
|
||||
ISearchCallback callback) {
|
||||
mHandler.sendMessage(
|
||||
obtainMessage(SearchUiService::onQuery,
|
||||
SearchUiService.this, sessionId, input,
|
||||
new CallbackWrapper(callback, null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyEvent(SearchSessionId sessionId, Query query, SearchTargetEvent event) {
|
||||
mHandler.sendMessage(
|
||||
obtainMessage(SearchUiService::onNotifyEvent,
|
||||
SearchUiService.this, sessionId, query, event));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(SearchSessionId sessionId) {
|
||||
mHandler.sendMessage(
|
||||
obtainMessage(SearchUiService::doDestroy,
|
||||
SearchUiService.this, sessionId));
|
||||
}
|
||||
};
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mHandler = new Handler(Looper.getMainLooper(), null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public final IBinder onBind(@NonNull Intent intent) {
|
||||
if (SERVICE_INTERFACE.equals(intent.getAction())) {
|
||||
return mInterface.asBinder();
|
||||
}
|
||||
Slog.w(TAG, "Tried to bind to wrong intent (should be "
|
||||
+ SERVICE_INTERFACE + ": " + intent);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new search session.
|
||||
*/
|
||||
public void onCreateSearchSession(@NonNull SearchContext context,
|
||||
@NonNull SearchSessionId sessionId) {}
|
||||
|
||||
/**
|
||||
* Called by the client to request search results using a query string.
|
||||
*/
|
||||
@MainThread
|
||||
public abstract void onQuery(@NonNull SearchSessionId sessionId,
|
||||
@NonNull Query query,
|
||||
@NonNull Consumer<List<SearchTarget>> callback);
|
||||
|
||||
/**
|
||||
* Called by a client to indicate an interaction (tap, long press, drag, etc) on target(s).
|
||||
*/
|
||||
@MainThread
|
||||
public abstract void onNotifyEvent(@NonNull SearchSessionId sessionId,
|
||||
@NonNull Query query,
|
||||
@NonNull SearchTargetEvent event);
|
||||
|
||||
private void doDestroy(@NonNull SearchSessionId sessionId) {
|
||||
super.onDestroy();
|
||||
onDestroy(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys a search session.
|
||||
*/
|
||||
@MainThread
|
||||
public abstract void onDestroy(@NonNull SearchSessionId sessionId);
|
||||
|
||||
private static final class CallbackWrapper implements Consumer<List<SearchTarget>>,
|
||||
IBinder.DeathRecipient {
|
||||
|
||||
private ISearchCallback mCallback;
|
||||
private final Consumer<CallbackWrapper> mOnBinderDied;
|
||||
|
||||
CallbackWrapper(ISearchCallback callback,
|
||||
@Nullable Consumer<CallbackWrapper> onBinderDied) {
|
||||
mCallback = callback;
|
||||
mOnBinderDied = onBinderDied;
|
||||
try {
|
||||
mCallback.asBinder().linkToDeath(this, 0);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to link to death: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(List<SearchTarget> searchTargets) {
|
||||
try {
|
||||
if (mCallback != null) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "CallbackWrapper.accept searchTargets=" + searchTargets);
|
||||
}
|
||||
mCallback.onResult(new ParceledListSlice(searchTargets));
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Error sending result:" + e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
mCallback.asBinder().unlinkToDeath(this, 0);
|
||||
mCallback = null;
|
||||
if (mOnBinderDied != null) {
|
||||
mOnBinderDied.accept(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5034,6 +5034,11 @@
|
||||
<permission android:name="android.permission.MANAGE_APP_PREDICTIONS"
|
||||
android:protectionLevel="signature|appPredictor" />
|
||||
|
||||
<!-- @SystemApi Allows an application to manage the search ui service.
|
||||
@hide <p>Not for use by third-party applications.</p> -->
|
||||
<permission android:name="android.permission.MANAGE_SEARCH_UI"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!-- Allows an app to set the theme overlay in /vendor/overlay
|
||||
being used.
|
||||
@hide <p>Not for use by third-party applications.</p> -->
|
||||
|
||||
@@ -3741,6 +3741,16 @@
|
||||
-->
|
||||
<string name="config_defaultContentSuggestionsService" translatable="false"></string>
|
||||
|
||||
<!-- The package name for the system's search ui service.
|
||||
This service returns search results when provided with an input string.
|
||||
|
||||
This service must be trusted, as it can be activated without explicit consent of the user.
|
||||
If no service with the specified name exists on the device, on device search wil be
|
||||
disabled.
|
||||
Example: "com.android.intelliegence/.SearchUiService"
|
||||
-->
|
||||
<string name="config_defaultSearchUiService" translatable="false"></string>
|
||||
|
||||
<string name="config_defaultMusicRecognitionService" translatable="false"></string>
|
||||
|
||||
<!-- The package name for the default retail demo app.
|
||||
|
||||
@@ -3477,6 +3477,7 @@
|
||||
<java-symbol type="string" name="config_defaultAugmentedAutofillService" />
|
||||
<java-symbol type="string" name="config_defaultAppPredictionService" />
|
||||
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
|
||||
<java-symbol type="string" name="config_defaultSearchUiService" />
|
||||
<java-symbol type="string" name="config_defaultMusicRecognitionService" />
|
||||
<java-symbol type="string" name="config_defaultAttentionService" />
|
||||
<java-symbol type="string" name="config_defaultSystemCaptionsService" />
|
||||
|
||||
@@ -27,6 +27,7 @@ filegroup {
|
||||
":services.print-sources",
|
||||
":services.profcollect-sources",
|
||||
":services.restrictions-sources",
|
||||
":services.searchui-sources",
|
||||
":services.startop.iorap-sources",
|
||||
":services.systemcaptions-sources",
|
||||
":services.usage-sources",
|
||||
@@ -72,6 +73,7 @@ java_library {
|
||||
"services.print",
|
||||
"services.profcollect",
|
||||
"services.restrictions",
|
||||
"services.searchui",
|
||||
"services.startop",
|
||||
"services.systemcaptions",
|
||||
"services.usage",
|
||||
|
||||
@@ -323,6 +323,8 @@ public final class SystemServer implements Dumpable {
|
||||
"com.android.server.appprediction.AppPredictionManagerService";
|
||||
private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
|
||||
"com.android.server.contentsuggestions.ContentSuggestionsManagerService";
|
||||
private static final String SEARCH_UI_MANAGER_SERVICE_CLASS =
|
||||
"com.android.server.searchui.SearchUiManagerService";
|
||||
private static final String DEVICE_IDLE_CONTROLLER_CLASS =
|
||||
"com.android.server.DeviceIdleController";
|
||||
private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
|
||||
@@ -1571,6 +1573,12 @@ public final class SystemServer implements Dumpable {
|
||||
Slog.d(TAG, "ContentSuggestionsService not defined by OEM");
|
||||
}
|
||||
|
||||
// Search UI manager service
|
||||
// TODO: add deviceHasConfigString(context, R.string.config_defaultSearchUiService)
|
||||
t.traceBegin("StartSearchUiService");
|
||||
mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("InitConnectivityModuleConnector");
|
||||
try {
|
||||
ConnectivityModuleConnector.getInstance().init(context);
|
||||
|
||||
13
services/searchui/Android.bp
Normal file
13
services/searchui/Android.bp
Normal file
@@ -0,0 +1,13 @@
|
||||
filegroup {
|
||||
name: "services.searchui-sources",
|
||||
srcs: ["java/**/*.java"],
|
||||
path: "java",
|
||||
visibility: ["//frameworks/base/services"],
|
||||
}
|
||||
|
||||
java_library_static {
|
||||
name: "services.searchui",
|
||||
defaults: ["platform_service_defaults"],
|
||||
srcs: [":services.searchui-sources"],
|
||||
libs: ["services.core"],
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.server.searchui;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.service.search.ISearchUiService;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
|
||||
|
||||
|
||||
/**
|
||||
* Proxy to the {@link android.service.searchui.SearchUiService} implementation in another
|
||||
* process.
|
||||
*/
|
||||
public class RemoteSearchUiService extends
|
||||
AbstractMultiplePendingRequestsRemoteService<RemoteSearchUiService,
|
||||
ISearchUiService> {
|
||||
|
||||
private static final String TAG = "RemoteSearchUiService";
|
||||
|
||||
private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
|
||||
|
||||
private final RemoteSearchUiServiceCallbacks mCallback;
|
||||
|
||||
public RemoteSearchUiService(Context context, String serviceInterface,
|
||||
ComponentName componentName, int userId,
|
||||
RemoteSearchUiServiceCallbacks callback, boolean bindInstantServiceAllowed,
|
||||
boolean verbose) {
|
||||
super(context, serviceInterface, componentName, userId, callback,
|
||||
context.getMainThreadHandler(),
|
||||
bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
|
||||
verbose, /* initialCapacity= */ 1);
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ISearchUiService getServiceInterface(IBinder service) {
|
||||
return ISearchUiService.Stub.asInterface(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getTimeoutIdleBindMillis() {
|
||||
return PERMANENT_BOUND_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getRemoteRequestMillis() {
|
||||
return TIMEOUT_REMOTE_REQUEST_MILLIS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a request to bind to the remote service.
|
||||
*/
|
||||
public void reconnect() {
|
||||
super.scheduleBind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule async request on remote service.
|
||||
*/
|
||||
public void scheduleOnResolvedService(@NonNull AsyncRequest<ISearchUiService> request) {
|
||||
scheduleAsyncRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute async request on remote service immediately instead of sending it to Handler queue.
|
||||
*/
|
||||
public void executeOnResolvedService(@NonNull AsyncRequest<ISearchUiService> request) {
|
||||
executeAsyncRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Failure callback
|
||||
*/
|
||||
public interface RemoteSearchUiServiceCallbacks
|
||||
extends VultureCallback<RemoteSearchUiService> {
|
||||
|
||||
/**
|
||||
* Notifies a the failure or timeout of a remote call.
|
||||
*/
|
||||
void onFailureOrTimeout(boolean timedOut);
|
||||
|
||||
/**
|
||||
* Notifies change in connected state of the remote service.
|
||||
*/
|
||||
void onConnectedStateChanged(boolean connected);
|
||||
}
|
||||
|
||||
@Override // from AbstractRemoteService
|
||||
protected void handleOnConnectedStateChanged(boolean connected) {
|
||||
if (mCallback != null) {
|
||||
mCallback.onConnectedStateChanged(connected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.server.searchui;
|
||||
|
||||
import static android.Manifest.permission.MANAGE_SEARCH_UI;
|
||||
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
|
||||
import static android.content.Context.SEARCH_UI_SERVICE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.search.ISearchCallback;
|
||||
import android.app.search.ISearchUiManager;
|
||||
import android.app.search.Query;
|
||||
import android.app.search.SearchContext;
|
||||
import android.app.search.SearchSessionId;
|
||||
import android.app.search.SearchTargetEvent;
|
||||
import android.content.Context;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.ShellCallback;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.infra.AbstractMasterSystemService;
|
||||
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
|
||||
import com.android.server.wm.ActivityTaskManagerInternal;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A service used to return search targets given a query.
|
||||
*/
|
||||
public class SearchUiManagerService extends
|
||||
AbstractMasterSystemService<SearchUiManagerService, SearchUiPerUserService> {
|
||||
|
||||
private static final String TAG = SearchUiManagerService.class.getSimpleName();
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
|
||||
|
||||
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
|
||||
|
||||
public SearchUiManagerService(Context context) {
|
||||
super(context, new FrameworkResourcesServiceNameResolver(context,
|
||||
com.android.internal.R.string.config_defaultSearchUiService), null,
|
||||
PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
|
||||
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SearchUiPerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
|
||||
return new SearchUiPerUserService(this, mLock, resolvedUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(SEARCH_UI_SERVICE, new SearchUiManagerStub());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enforceCallingPermissionForManagement() {
|
||||
getContext().enforceCallingPermission(MANAGE_SEARCH_UI, TAG);
|
||||
}
|
||||
|
||||
@Override // from AbstractMasterSystemService
|
||||
protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
|
||||
final SearchUiPerUserService service = peekServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
service.onPackageUpdatedLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from AbstractMasterSystemService
|
||||
protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
|
||||
final SearchUiPerUserService service = peekServiceForUserLocked(userId);
|
||||
if (service != null) {
|
||||
service.onPackageRestartedLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMaximumTemporaryServiceDurationMs() {
|
||||
return MAX_TEMP_SERVICE_DURATION_MS;
|
||||
}
|
||||
|
||||
private class SearchUiManagerStub extends ISearchUiManager.Stub {
|
||||
|
||||
@Override
|
||||
public void createSearchSession(@NonNull SearchContext context,
|
||||
@NonNull SearchSessionId sessionId, @NonNull IBinder token) {
|
||||
runForUserLocked("createSearchSession", sessionId, (service) ->
|
||||
service.onCreateSearchSessionLocked(context, sessionId, token));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyEvent(@NonNull SearchSessionId sessionId, @NonNull Query query,
|
||||
@NonNull SearchTargetEvent event) {
|
||||
runForUserLocked("notifyEvent", sessionId,
|
||||
(service) -> service.notifyLocked(sessionId, query, event));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void query(@NonNull SearchSessionId sessionId,
|
||||
@NonNull Query query,
|
||||
ISearchCallback callback) {
|
||||
runForUserLocked("query", sessionId,
|
||||
(service) -> service.queryLocked(sessionId, query, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroySearchSession(@NonNull SearchSessionId sessionId) {
|
||||
runForUserLocked("destroySearchSession", sessionId,
|
||||
(service) -> service.onDestroyLocked(sessionId));
|
||||
}
|
||||
|
||||
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
|
||||
@Nullable FileDescriptor err,
|
||||
@NonNull String[] args, @Nullable ShellCallback callback,
|
||||
@NonNull ResultReceiver resultReceiver) {
|
||||
new SearchUiManagerServiceShellCommand(SearchUiManagerService.this)
|
||||
.exec(this, in, out, err, args, callback, resultReceiver);
|
||||
}
|
||||
|
||||
private void runForUserLocked(@NonNull final String func,
|
||||
@NonNull final SearchSessionId sessionId,
|
||||
@NonNull final Consumer<SearchUiPerUserService> c) {
|
||||
ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
|
||||
final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
|
||||
sessionId.getUserId(), false, ALLOW_NON_FULL, null, null);
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
|
||||
+ ", uid=" + Binder.getCallingUid());
|
||||
}
|
||||
if (!(mServiceNameResolver.isTemporary(userId)
|
||||
|| mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
|
||||
|
||||
String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
|
||||
+ ", uid=" + Binder.getCallingUid();
|
||||
Slog.w(TAG, msg);
|
||||
throw new SecurityException(msg);
|
||||
}
|
||||
|
||||
final long origId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
final SearchUiPerUserService service = getServiceForUserLocked(userId);
|
||||
c.accept(service);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(origId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.server.searchui;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.ShellCommand;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* The shell command implementation for the SearchUiManagerService.
|
||||
*/
|
||||
public class SearchUiManagerServiceShellCommand extends ShellCommand {
|
||||
|
||||
private static final String TAG =
|
||||
SearchUiManagerServiceShellCommand.class.getSimpleName();
|
||||
|
||||
private final SearchUiManagerService mService;
|
||||
|
||||
public SearchUiManagerServiceShellCommand(@NonNull SearchUiManagerService service) {
|
||||
mService = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(String cmd) {
|
||||
if (cmd == null) {
|
||||
return handleDefaultCommands(cmd);
|
||||
}
|
||||
final PrintWriter pw = getOutPrintWriter();
|
||||
switch (cmd) {
|
||||
case "set": {
|
||||
final String what = getNextArgRequired();
|
||||
switch (what) {
|
||||
case "temporary-service": {
|
||||
final int userId = Integer.parseInt(getNextArgRequired());
|
||||
String serviceName = getNextArg();
|
||||
if (serviceName == null) {
|
||||
mService.resetTemporaryService(userId);
|
||||
pw.println("SearchUiService temporarily reset. ");
|
||||
return 0;
|
||||
}
|
||||
final int duration = Integer.parseInt(getNextArgRequired());
|
||||
mService.setTemporaryService(userId, serviceName, duration);
|
||||
pw.println("SearchUiService temporarily set to " + serviceName
|
||||
+ " for " + duration + "ms");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return handleDefaultCommands(cmd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHelp() {
|
||||
try (PrintWriter pw = getOutPrintWriter()) {
|
||||
pw.println("SearchUiManagerService commands:");
|
||||
pw.println(" help");
|
||||
pw.println(" Prints this help text.");
|
||||
pw.println("");
|
||||
pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
|
||||
pw.println(" Temporarily (for DURATION ms) changes the service implemtation.");
|
||||
pw.println(" To reset, call with just the USER_ID argument.");
|
||||
pw.println("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.server.searchui;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.search.ISearchCallback;
|
||||
import android.app.search.Query;
|
||||
import android.app.search.SearchContext;
|
||||
import android.app.search.SearchSessionId;
|
||||
import android.app.search.SearchTargetEvent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.service.search.ISearchUiService;
|
||||
import android.service.search.SearchUiService;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.infra.AbstractRemoteService;
|
||||
import com.android.server.infra.AbstractPerUserSystemService;
|
||||
|
||||
/**
|
||||
* Per-user instance of {@link SearchUiManagerService}.
|
||||
*/
|
||||
public class SearchUiPerUserService extends
|
||||
AbstractPerUserSystemService<SearchUiPerUserService, SearchUiManagerService>
|
||||
implements RemoteSearchUiService.RemoteSearchUiServiceCallbacks {
|
||||
|
||||
private static final String TAG = SearchUiPerUserService.class.getSimpleName();
|
||||
|
||||
@Nullable
|
||||
@GuardedBy("mLock")
|
||||
private RemoteSearchUiService mRemoteService;
|
||||
|
||||
/**
|
||||
* When {@code true}, remote service died but service state is kept so it's restored after
|
||||
* the system re-binds to it.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private boolean mZombie;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final ArrayMap<SearchSessionId, SearchSessionInfo> mSessionInfos =
|
||||
new ArrayMap<>();
|
||||
|
||||
protected SearchUiPerUserService(SearchUiManagerService master,
|
||||
Object lock, int userId) {
|
||||
super(master, lock, userId);
|
||||
}
|
||||
|
||||
@Override // from PerUserSystemService
|
||||
protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
|
||||
throws NameNotFoundException {
|
||||
|
||||
ServiceInfo si;
|
||||
try {
|
||||
si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
|
||||
PackageManager.GET_META_DATA, mUserId);
|
||||
} catch (RemoteException e) {
|
||||
throw new NameNotFoundException("Could not get service for " + serviceComponent);
|
||||
}
|
||||
// TODO(b/111701043): must check that either the service is from a system component,
|
||||
// or it matches a service set by shell cmd (so it can be used on CTS tests and when
|
||||
// OEMs are implementing the real service and also verify the proper permissions
|
||||
return si;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Override // from PerUserSystemService
|
||||
protected boolean updateLocked(boolean disabled) {
|
||||
final boolean enabledChanged = super.updateLocked(disabled);
|
||||
if (enabledChanged) {
|
||||
if (!isEnabledLocked()) {
|
||||
// Clear the remote service for the next call
|
||||
updateRemoteServiceLocked();
|
||||
}
|
||||
}
|
||||
return enabledChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the service of a new search session.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
public void onCreateSearchSessionLocked(@NonNull SearchContext context,
|
||||
@NonNull SearchSessionId sessionId, @NonNull IBinder token) {
|
||||
final boolean serviceExists = resolveService(sessionId,
|
||||
s -> s.onCreateSearchSession(context, sessionId));
|
||||
|
||||
if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
|
||||
final SearchSessionInfo sessionInfo = new SearchSessionInfo(
|
||||
sessionId, context, token, () -> {
|
||||
synchronized (mLock) {
|
||||
onDestroyLocked(sessionId);
|
||||
}
|
||||
});
|
||||
if (sessionInfo.linkToDeath()) {
|
||||
mSessionInfos.put(sessionId, sessionInfo);
|
||||
} else {
|
||||
// destroy the session if calling process is already dead
|
||||
onDestroyLocked(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records an app target event to the service.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
public void notifyLocked(@NonNull SearchSessionId sessionId, @NonNull Query query,
|
||||
@NonNull SearchTargetEvent event) {
|
||||
final SearchSessionInfo sessionInfo = mSessionInfos.get(sessionId);
|
||||
if (sessionInfo == null) return;
|
||||
resolveService(sessionId,
|
||||
s -> s.onNotifyEvent(sessionId, query, event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the service to return search results of an input query.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
public void queryLocked(@NonNull SearchSessionId sessionId,
|
||||
@NonNull Query input, @NonNull ISearchCallback callback) {
|
||||
final SearchSessionInfo sessionInfo = mSessionInfos.get(sessionId);
|
||||
if (sessionInfo == null) return;
|
||||
resolveService(sessionId,
|
||||
s -> s.onQuery(sessionId, input, callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the service of the end of an existing search session.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
public void onDestroyLocked(@NonNull SearchSessionId sessionId) {
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "onDestroyLocked(): sessionId=" + sessionId);
|
||||
}
|
||||
final SearchSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
|
||||
if (sessionInfo == null) return;
|
||||
resolveService(sessionId,
|
||||
s -> s.onDestroy(sessionId));
|
||||
sessionInfo.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailureOrTimeout(boolean timedOut) {
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
|
||||
}
|
||||
// Do nothing, we are just proxying to the search ui service
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectedStateChanged(boolean connected) {
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
|
||||
}
|
||||
if (connected) {
|
||||
synchronized (mLock) {
|
||||
if (mZombie) {
|
||||
// Validation check - shouldn't happen
|
||||
if (mRemoteService == null) {
|
||||
Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
|
||||
return;
|
||||
}
|
||||
mZombie = false;
|
||||
resurrectSessionsLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDied(RemoteSearchUiService service) {
|
||||
if (isDebug()) {
|
||||
Slog.w(TAG, "onServiceDied(): service=" + service);
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mZombie = true;
|
||||
}
|
||||
updateRemoteServiceLocked();
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updateRemoteServiceLocked() {
|
||||
if (mRemoteService != null) {
|
||||
mRemoteService.destroy();
|
||||
mRemoteService = null;
|
||||
}
|
||||
}
|
||||
|
||||
void onPackageUpdatedLocked() {
|
||||
if (isDebug()) {
|
||||
Slog.v(TAG, "onPackageUpdatedLocked()");
|
||||
}
|
||||
destroyAndRebindRemoteService();
|
||||
}
|
||||
|
||||
void onPackageRestartedLocked() {
|
||||
if (isDebug()) {
|
||||
Slog.v(TAG, "onPackageRestartedLocked()");
|
||||
}
|
||||
destroyAndRebindRemoteService();
|
||||
}
|
||||
|
||||
private void destroyAndRebindRemoteService() {
|
||||
if (mRemoteService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "Destroying the old remote service.");
|
||||
}
|
||||
mRemoteService.destroy();
|
||||
mRemoteService = null;
|
||||
|
||||
synchronized (mLock) {
|
||||
mZombie = true;
|
||||
}
|
||||
mRemoteService = getRemoteServiceLocked();
|
||||
if (mRemoteService != null) {
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "Rebinding to the new remote service.");
|
||||
}
|
||||
mRemoteService.reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the remote service connected, it's used to restore state from a 'zombie'
|
||||
* service (i.e., after it died).
|
||||
*/
|
||||
private void resurrectSessionsLocked() {
|
||||
final int numSessions = mSessionInfos.size();
|
||||
if (isDebug()) {
|
||||
Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
|
||||
+ numSessions + " sessions.");
|
||||
}
|
||||
|
||||
for (SearchSessionInfo sessionInfo : mSessionInfos.values()) {
|
||||
sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
protected boolean resolveService(
|
||||
@NonNull final SearchSessionId sessionId,
|
||||
@NonNull final AbstractRemoteService.AsyncRequest<ISearchUiService> cb) {
|
||||
|
||||
final RemoteSearchUiService service = getRemoteServiceLocked();
|
||||
if (service != null) {
|
||||
service.executeOnResolvedService(cb);
|
||||
}
|
||||
return service != null;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private RemoteSearchUiService getRemoteServiceLocked() {
|
||||
if (mRemoteService == null) {
|
||||
final String serviceName = getComponentNameLocked();
|
||||
if (serviceName == null) {
|
||||
if (mMaster.verbose) {
|
||||
Slog.v(TAG, "getRemoteServiceLocked(): not set");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
|
||||
|
||||
mRemoteService = new RemoteSearchUiService(getContext(),
|
||||
SearchUiService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
|
||||
mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
|
||||
}
|
||||
|
||||
return mRemoteService;
|
||||
}
|
||||
|
||||
private static final class SearchSessionInfo {
|
||||
private static final boolean DEBUG = true; // Do not submit with true
|
||||
|
||||
@NonNull
|
||||
private final SearchSessionId mSessionId;
|
||||
@NonNull
|
||||
private final SearchContext mSearchContext;
|
||||
@NonNull
|
||||
final IBinder mToken;
|
||||
@NonNull
|
||||
final IBinder.DeathRecipient mDeathRecipient;
|
||||
|
||||
private final RemoteCallbackList<ISearchCallback> mCallbacks =
|
||||
new RemoteCallbackList<ISearchCallback>() {
|
||||
@Override
|
||||
public void onCallbackDied(ISearchCallback callback) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Binder died for session Id=" + mSessionId
|
||||
+ " and callback=" + callback.asBinder());
|
||||
}
|
||||
if (mCallbacks.getRegisteredCallbackCount() == 0) {
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SearchSessionInfo(
|
||||
@NonNull final SearchSessionId id,
|
||||
@NonNull final SearchContext context,
|
||||
@NonNull final IBinder token,
|
||||
@NonNull final IBinder.DeathRecipient deathRecipient) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Creating SearchSessionInfo for session Id=" + id);
|
||||
}
|
||||
mSessionId = id;
|
||||
mSearchContext = context;
|
||||
mToken = token;
|
||||
mDeathRecipient = deathRecipient;
|
||||
}
|
||||
|
||||
boolean linkToDeath() {
|
||||
try {
|
||||
mToken.linkToDeath(mDeathRecipient, 0);
|
||||
} catch (RemoteException e) {
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "Caller is dead before session can be started, sessionId: "
|
||||
+ mSessionId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Removing all callbacks for session Id=" + mSessionId
|
||||
+ " and " + mCallbacks.getRegisteredCallbackCount() + " callbacks.");
|
||||
}
|
||||
if (mToken != null) {
|
||||
mToken.unlinkToDeath(mDeathRecipient, 0);
|
||||
}
|
||||
mCallbacks.kill();
|
||||
}
|
||||
|
||||
void resurrectSessionLocked(SearchUiPerUserService service, IBinder token) {
|
||||
int callbackCount = mCallbacks.getRegisteredCallbackCount();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
|
||||
+ ") for session Id=" + mSessionId + " and "
|
||||
+ callbackCount + " callbacks.");
|
||||
}
|
||||
service.onCreateSearchSessionLocked(mSearchContext, mSessionId, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user