SmartspaceManager initial implementation
Bug: 176851064
Test: Deployed on Phone,CTS tests
Change-Id: I3e03628d4d0e0aec0b6442f0ee8949ac378a1572
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0a0f77e..fbfd354 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -138,6 +138,7 @@
field public static final String MANAGE_ROTATION_RESOLVER = "android.permission.MANAGE_ROTATION_RESOLVER";
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_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE";
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";
field public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS";
@@ -1493,6 +1494,169 @@
}
+package android.app.smartspace {
+
+ public final class SmartspaceAction 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.smartspace.SmartspaceAction> CREATOR;
+ }
+
+ public static final class SmartspaceAction.Builder {
+ ctor public SmartspaceAction.Builder(@NonNull String, @NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceAction build();
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setContentDescription(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setIntent(@Nullable android.content.Intent);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setPendingIntent(@Nullable android.app.PendingIntent);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setSubtitle(@Nullable CharSequence);
+ method @NonNull public android.app.smartspace.SmartspaceAction.Builder setUserHandle(@Nullable android.os.UserHandle);
+ }
+
+ public final class SmartspaceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.os.Bundle getExtras();
+ method @NonNull public String getPackageName();
+ method @NonNull public int getSmartspaceTargetCount();
+ method @NonNull public String getUiSurface();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceConfig> CREATOR;
+ }
+
+ public static final class SmartspaceConfig.Builder {
+ ctor public SmartspaceConfig.Builder(@NonNull android.content.Context, @NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceConfig build();
+ method @NonNull public android.app.smartspace.SmartspaceConfig.Builder setExtras(@NonNull android.os.Bundle);
+ method @NonNull public android.app.smartspace.SmartspaceConfig.Builder setSmartspaceTargetCount(int);
+ }
+
+ public final class SmartspaceManager {
+ method @NonNull public android.app.smartspace.SmartspaceSession createSmartspaceSession(@NonNull android.app.smartspace.SmartspaceConfig);
+ }
+
+ public final class SmartspaceSession implements java.lang.AutoCloseable {
+ method public void close();
+ method public void destroy();
+ method protected void finalize();
+ method public void notifySmartspaceEvent(@NonNull android.app.smartspace.SmartspaceTargetEvent);
+ method public void registerSmartspaceUpdates(@NonNull java.util.concurrent.Executor, @NonNull android.app.smartspace.SmartspaceSession.Callback);
+ method public void requestSmartspaceUpdate();
+ method public void unregisterSmartspaceUpdates(@NonNull android.app.smartspace.SmartspaceSession.Callback);
+ }
+
+ public static interface SmartspaceSession.Callback {
+ method public void onTargetsAvailable(@NonNull java.util.List<android.app.smartspace.SmartspaceTarget>);
+ }
+
+ public final class SmartspaceSessionId implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getId();
+ method @NonNull public int getUserId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceSessionId> CREATOR;
+ }
+
+ public final class SmartspaceTarget implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getActionChips();
+ method @Nullable public String getAssociatedSmartspaceTargetId();
+ method @Nullable public android.app.smartspace.SmartspaceAction getBaseAction();
+ method @NonNull public android.content.ComponentName getComponentName();
+ method @NonNull public long getCreationTimeMillis();
+ method @NonNull public long getExpiryTimeMillis();
+ method @NonNull public int getFeatureType();
+ method @Nullable public android.app.smartspace.SmartspaceAction getHeaderAction();
+ method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getIconGrid();
+ method @NonNull public float getScore();
+ method @Nullable public android.net.Uri getSliceUri();
+ method @NonNull public String getSmartspaceTargetId();
+ method @Nullable public String getSourceNotificationKey();
+ method @NonNull public android.os.UserHandle getUserHandle();
+ method @Nullable public android.appwidget.AppWidgetProviderInfo getWidgetId();
+ method @NonNull public boolean isSensitive();
+ method @NonNull public boolean shouldShowExpanded();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceTarget> CREATOR;
+ field public static final int FEATURE_ALARM = 7; // 0x7
+ field public static final int FEATURE_BEDTIME_ROUTINE = 16; // 0x10
+ field public static final int FEATURE_CALENDAR = 2; // 0x2
+ field public static final int FEATURE_COMMUTE_TIME = 3; // 0x3
+ field public static final int FEATURE_CONSENT = 11; // 0xb
+ field public static final int FEATURE_ETA_MONITORING = 18; // 0x12
+ field public static final int FEATURE_FITNESS_TRACKING = 17; // 0x11
+ field public static final int FEATURE_FLIGHT = 4; // 0x4
+ field public static final int FEATURE_LOYALTY_CARD = 14; // 0xe
+ field public static final int FEATURE_MEDIA = 15; // 0xf
+ field public static final int FEATURE_MISSED_CALL = 19; // 0x13
+ field public static final int FEATURE_ONBOARDING = 8; // 0x8
+ field public static final int FEATURE_PACKAGE_TRACKING = 20; // 0x14
+ field public static final int FEATURE_REMINDER = 6; // 0x6
+ field public static final int FEATURE_SHOPPING_LIST = 13; // 0xd
+ field public static final int FEATURE_SPORTS = 9; // 0x9
+ field public static final int FEATURE_STOCK_PRICE_CHANGE = 12; // 0xc
+ field public static final int FEATURE_STOPWATCH = 22; // 0x16
+ field public static final int FEATURE_TIMER = 21; // 0x15
+ field public static final int FEATURE_TIPS = 5; // 0x5
+ field public static final int FEATURE_UNDEFINED = 0; // 0x0
+ field public static final int FEATURE_UPCOMING_ALARM = 23; // 0x17
+ field public static final int FEATURE_WEATHER = 1; // 0x1
+ field public static final int FEATURE_WEATHER_ALERT = 10; // 0xa
+ }
+
+ public static final class SmartspaceTarget.Builder {
+ ctor public SmartspaceTarget.Builder(@NonNull String, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
+ method @NonNull public android.app.smartspace.SmartspaceTarget build();
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setActionChips(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setAssociatedSmartspaceTargetId(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setBaseAction(@NonNull android.app.smartspace.SmartspaceAction);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setCreationTimeMillis(@NonNull long);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setExpiryTimeMillis(@NonNull long);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setFeatureType(@NonNull int);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setHeaderAction(@NonNull android.app.smartspace.SmartspaceAction);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setIconGrid(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setScore(@NonNull float);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSensitive(@NonNull boolean);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(@NonNull boolean);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSliceUri(@NonNull android.net.Uri);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSourceNotificationKey(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setWidgetId(@NonNull android.appwidget.AppWidgetProviderInfo);
+ }
+
+ public final class SmartspaceTargetEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public int getEventType();
+ method @Nullable public String getSmartspaceActionId();
+ method @Nullable public android.app.smartspace.SmartspaceTarget getSmartspaceTarget();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceTargetEvent> CREATOR;
+ field public static final int EVENT_TARGET_BLOCK = 5; // 0x5
+ field public static final int EVENT_TARGET_DISMISS = 4; // 0x4
+ field public static final int EVENT_TARGET_INTERACTION = 1; // 0x1
+ field public static final int EVENT_TARGET_IN_VIEW = 2; // 0x2
+ field public static final int EVENT_TARGET_OUT_OF_VIEW = 3; // 0x3
+ field public static final int EVENT_UI_SURFACE_IN_VIEW = 6; // 0x6
+ field public static final int EVENT_UI_SURFACE_OUT_OF_VIEW = 7; // 0x7
+ }
+
+ public static final class SmartspaceTargetEvent.Builder {
+ ctor public SmartspaceTargetEvent.Builder(int);
+ method @NonNull public android.app.smartspace.SmartspaceTargetEvent build();
+ method @NonNull public android.app.smartspace.SmartspaceTargetEvent.Builder setSmartspaceActionId(@NonNull String);
+ method @NonNull public android.app.smartspace.SmartspaceTargetEvent.Builder setSmartspaceTarget(@NonNull android.app.smartspace.SmartspaceTarget);
+ }
+
+}
+
package android.app.time {
public final class TimeManager {
@@ -1953,6 +2117,7 @@
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 SMARTSPACE_SERVICE = "smartspace";
field public static final String STATS_MANAGER = "stats";
field public static final String STATUS_BAR_SERVICE = "statusbar";
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
@@ -10057,6 +10222,21 @@
}
+package android.service.smartspace {
+
+ public abstract class SmartspaceService extends android.app.Service {
+ ctor public SmartspaceService();
+ method @MainThread public abstract void notifySmartspaceEvent(@NonNull android.app.smartspace.SmartspaceSessionId, @NonNull android.app.smartspace.SmartspaceTargetEvent);
+ method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onCreateSmartspaceSession(@NonNull android.app.smartspace.SmartspaceConfig, @NonNull android.app.smartspace.SmartspaceSessionId);
+ method @MainThread public abstract void onDestroy(@NonNull android.app.smartspace.SmartspaceSessionId);
+ method public abstract void onDestroySmartspaceSession(@NonNull android.app.smartspace.SmartspaceSessionId);
+ method @MainThread public abstract void onRequestSmartspaceUpdate(@NonNull android.app.smartspace.SmartspaceSessionId);
+ method public final void updateSmartspaceTargets(@NonNull android.app.smartspace.SmartspaceSessionId, @NonNull java.util.List<android.app.smartspace.SmartspaceTarget>);
+ }
+
+}
+
package android.service.storage {
public abstract class ExternalStorageService extends android.app.Service {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 8b3cee4..58f6c52 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -6,9 +6,11 @@
BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
+
ExecutorRegistration: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
Registration methods should have overload that accepts delivery Executor: `setOnRtpRxNoticeListener`
-
+
+
GenericException: android.app.prediction.AppPredictor#finalize():
GenericException: android.hardware.location.ContextHubClient#finalize():
@@ -21,6 +23,8 @@
IntentBuilderName: android.app.search.SearchAction#getIntent():
+IntentBuilderName: android.app.smartspace.SmartspaceAction#getIntent():
+ Methods creating an Intent should be named `create<Foo>Intent()`, was `getIntent`
KotlinKeyword: android.app.Notification#when:
@@ -83,6 +87,10 @@
+OnNameExpected: android.service.smartspace.SmartspaceService#notifySmartspaceEvent(android.app.smartspace.SmartspaceSessionId, android.app.smartspace.SmartspaceTargetEvent):
+ Methods implemented by developers should follow the on<Something> style, was `notifySmartspaceEvent`
+
+
ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
@@ -187,11 +195,10 @@
SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
-SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
- SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-
SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
+ SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
@@ -258,3 +265,5 @@
Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
UserHandleName: android.app.search.SearchTarget.Builder#setUserHandle(android.os.UserHandle):
+UserHandleName: android.app.smartspace.SmartspaceAction.Builder#setUserHandle(android.os.UserHandle):
+ Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d5f0149..518feee 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,7 @@
import android.app.role.RoleFrameworkInitializer;
import android.app.search.SearchUiManager;
import android.app.slice.SliceManager;
+import android.app.smartspace.SmartspaceManager;
import android.app.time.TimeManager;
import android.app.timedetector.TimeDetector;
import android.app.timedetector.TimeDetectorImpl;
@@ -1223,6 +1224,16 @@
}
});
+ registerService(Context.SMARTSPACE_SERVICE, SmartspaceManager.class,
+ new CachedServiceFetcher<SmartspaceManager>() {
+ @Override
+ public SmartspaceManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
+ return b == null ? null : new SmartspaceManager(ctx);
+ }
+ });
+
registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
new CachedServiceFetcher<AppPredictionManager>() {
@Override
diff --git a/core/java/android/app/smartspace/ISmartspaceCallback.aidl b/core/java/android/app/smartspace/ISmartspaceCallback.aidl
new file mode 100644
index 0000000..df105f9
--- /dev/null
+++ b/core/java/android/app/smartspace/ISmartspaceCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+oneway interface ISmartspaceCallback {
+
+ void onResult(in ParceledListSlice result);
+}
diff --git a/core/java/android/app/smartspace/ISmartspaceManager.aidl b/core/java/android/app/smartspace/ISmartspaceManager.aidl
new file mode 100644
index 0000000..e7ec889
--- /dev/null
+++ b/core/java/android/app/smartspace/ISmartspaceManager.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.ISmartspaceCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+interface ISmartspaceManager {
+
+ void createSmartspaceSession(in SmartspaceConfig config, in SmartspaceSessionId sessionId,
+ in IBinder token);
+
+ void notifySmartspaceEvent(in SmartspaceSessionId sessionId, in SmartspaceTargetEvent event);
+
+ void requestSmartspaceUpdate(in SmartspaceSessionId sessionId);
+
+ void registerSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void unregisterSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void destroySmartspaceSession(in SmartspaceSessionId sessionId);
+}
diff --git a/core/java/android/app/smartspace/SmartspaceAction.java b/core/java/android/app/smartspace/SmartspaceAction.java
new file mode 100644
index 0000000..033cda1
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceAction.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+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;
+
+/**
+ * A {@link SmartspaceAction} represents an action which can be taken by a user by tapping on either
+ * the title, the subtitle or on the icon. Supported instances are Intents, PendingIntents or a
+ * ShortcutInfo (by putting the ShortcutInfoId in the bundle). These actions can be called from
+ * another process or within the client process.
+ *
+ * Clients can also receive conditional Intents/PendingIntents in the extras bundle which are
+ * supposed to be fired when the conditions are met. For example, a user can invoke a dismiss/block
+ * action on a game score card but the intention is to only block the team and not the entire
+ * feature.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceAction implements Parcelable {
+
+ private static final String TAG = "SmartspaceAction";
+
+ /** A unique Id of this {@link SmartspaceAction}. */
+ @NonNull
+ private final String mId;
+
+ /** An Icon which can be displayed in the UI. */
+ @Nullable
+ private final Icon mIcon;
+
+ /** Title associated with an action. */
+ @NonNull
+ private final CharSequence mTitle;
+
+ /** Subtitle associated with an action. */
+ @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;
+
+ SmartspaceAction(Parcel in) {
+ mId = in.readString();
+ mIcon = in.readTypedObject(Icon.CREATOR);
+ mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mIntent = in.readTypedObject(Intent.CREATOR);
+ mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+ mExtras = in.readTypedObject(Bundle.CREATOR);
+ }
+
+ private SmartspaceAction(
+ @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;
+ }
+
+ /**
+ * 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 SmartspaceAction)) return false;
+ SmartspaceAction that = (SmartspaceAction) o;
+ return mId.equals(that.mId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId);
+ }
+
+ @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() {
+ return "SmartspaceAction{"
+ + "mId='" + mId + '\''
+ + ", mIcon=" + mIcon
+ + ", mTitle=" + mTitle
+ + ", mSubtitle=" + mSubtitle
+ + ", mContentDescription=" + mContentDescription
+ + ", mPendingIntent=" + mPendingIntent
+ + ", mIntent=" + mIntent
+ + ", mUserHandle=" + mUserHandle
+ + ", mExtras=" + mExtras
+ + '}';
+ }
+
+ public static final @NonNull Creator<SmartspaceAction> CREATOR =
+ new Creator<SmartspaceAction>() {
+ public SmartspaceAction createFromParcel(Parcel in) {
+ return new SmartspaceAction(in);
+ }
+ public SmartspaceAction[] newArray(int size) {
+ return new SmartspaceAction[size];
+ }
+ };
+
+ /**
+ * A builder for Smartspace 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;
+
+ /**
+ * Id and title are required.
+ */
+ public Builder(@NonNull String id, @NonNull String title) {
+ mId = Objects.requireNonNull(id);
+ mTitle = Objects.requireNonNull(title);
+ }
+
+ /**
+ * Sets the icon.
+ */
+ @NonNull
+ public Builder setIcon(
+ @Nullable Icon icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets the subtitle.
+ */
+ @NonNull
+ public Builder setSubtitle(
+ @Nullable CharSequence subtitle) {
+ mSubtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Sets the content description.
+ */
+ @NonNull
+ public Builder setContentDescription(
+ @Nullable CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Sets the pending intent.
+ */
+ @NonNull
+ public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the user handle.
+ */
+ @NonNull
+ public Builder setUserHandle(@Nullable UserHandle userHandle) {
+ mUserHandle = userHandle;
+ return this;
+ }
+
+ /**
+ * Sets the intent.
+ */
+ @NonNull
+ public Builder setIntent(@Nullable Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets the extra.
+ */
+ @NonNull
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds a new SmartspaceAction instance.
+ *
+ * @throws IllegalStateException if no target is set
+ */
+ @NonNull
+ public SmartspaceAction build() {
+ return new SmartspaceAction(mId, mIcon, mTitle, mSubtitle, mContentDescription,
+ mPendingIntent, mIntent, mUserHandle, mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceConfig.aidl b/core/java/android/app/smartspace/SmartspaceConfig.aidl
new file mode 100644
index 0000000..136b6f4
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceConfig.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, 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.smartspace;
+
+parcelable SmartspaceConfig;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceConfig.java b/core/java/android/app/smartspace/SmartspaceConfig.java
new file mode 100644
index 0000000..1f9cbb5
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceConfig.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceConfig} instance is supposed to be created by a smartspace client for each
+ * UISurface. The client can specify some initialization conditions for the UISurface like its name,
+ * expected number of smartspace cards etc. The clients can also specify if they want periodic
+ * updates or their desired maximum refresh frequency.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceConfig implements Parcelable {
+
+ /**
+ * The least number of smartspace targets expected to be predicted by the backend. The backend
+ * will always try to satisfy this threshold but it is not guaranteed to always meet it.
+ */
+ private final int mSmartspaceTargetCount;
+
+ /**
+ * A {@link mUiSurface} is the name of the surface which will be used to display the cards. A
+ * few examples are homescreen, lockscreen, aod etc.
+ */
+ @NonNull
+ private final String mUiSurface;
+
+ /** Package name of the client. */
+ @NonNull
+ private String mPackageName;
+
+ /** Send other client UI configurations in extras.
+ *
+ * This can include:
+ *
+ * - Desired maximum update frequency
+ * - Request to get periodic updates
+ * - Request to support multiple clients for the same UISurface.
+ */
+ @Nullable
+ private final Bundle mExtras;
+
+ private SmartspaceConfig(@NonNull String uiSurface, int numPredictedTargets,
+ @NonNull String packageName, @Nullable Bundle extras) {
+ mUiSurface = uiSurface;
+ mSmartspaceTargetCount = numPredictedTargets;
+ mPackageName = packageName;
+ mExtras = extras;
+ }
+
+ private SmartspaceConfig(Parcel parcel) {
+ mUiSurface = parcel.readString();
+ mSmartspaceTargetCount = parcel.readInt();
+ mPackageName = parcel.readString();
+ mExtras = parcel.readBundle();
+ }
+
+ /** Returns the package name of the prediction context. */
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /** Returns the number of smartspace targets requested by the user. */
+ @NonNull
+ public int getSmartspaceTargetCount() {
+ return mSmartspaceTargetCount;
+ }
+
+ /** Returns the UISurface requested by the client. */
+ @NonNull
+ public String getUiSurface() {
+ return mUiSurface;
+ }
+
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mUiSurface);
+ dest.writeInt(mSmartspaceTargetCount);
+ dest.writeString(mPackageName);
+ dest.writeBundle(mExtras);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SmartspaceConfig that = (SmartspaceConfig) o;
+ return mSmartspaceTargetCount == that.mSmartspaceTargetCount
+ && Objects.equals(mUiSurface, that.mUiSurface)
+ && Objects.equals(mPackageName, that.mPackageName)
+ && Objects.equals(mExtras, that.mExtras);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSmartspaceTargetCount, mUiSurface, mPackageName, mExtras);
+ }
+
+ /**
+ * @see Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceConfig> CREATOR =
+ new Creator<SmartspaceConfig>() {
+ public SmartspaceConfig createFromParcel(Parcel parcel) {
+ return new SmartspaceConfig(parcel);
+ }
+
+ public SmartspaceConfig[] newArray(int size) {
+ return new SmartspaceConfig[size];
+ }
+ };
+
+ /**
+ * A builder for {@link SmartspaceConfig}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @NonNull
+ private int mSmartspaceTargetCount = 5; // Default count is 5
+ @NonNull
+ private final String mUiSurface;
+ @NonNull
+ private final String mPackageName;
+ @NonNull
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * @param context The {@link Context} which is used to fetch the package name.
+ * @param uiSurface the UI Surface name associated with this context.
+ * @hide
+ */
+ @SystemApi
+ public Builder(@NonNull Context context, @NonNull String uiSurface) {
+ mPackageName = context.getPackageName();
+ this.mUiSurface = uiSurface;
+ }
+
+ /**
+ * Used to set the expected number of cards for this context.
+ */
+ @NonNull
+ public Builder setSmartspaceTargetCount(int smartspaceTargetCount) {
+ this.mSmartspaceTargetCount = smartspaceTargetCount;
+ return this;
+ }
+
+ /**
+ * Used to send a bundle containing extras for the {@link SmartspaceConfig}.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ this.mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Returns an instance of {@link SmartspaceConfig}.
+ */
+ @NonNull
+ public SmartspaceConfig build() {
+ return new SmartspaceConfig(mUiSurface, mSmartspaceTargetCount, mPackageName, mExtras);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceManager.java b/core/java/android/app/smartspace/SmartspaceManager.java
new file mode 100644
index 0000000..ff5eb10
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceManager.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+
+import java.util.Objects;
+
+/**
+ * Smartspace is a container in Android which is used to show contextual content powered by the
+ * intelligence service running on the device. A smartspace container can be on AoD, lockscreen or
+ * on the homescreen and can show personalized cards which are either derived from on device or
+ * online signals.
+ *
+ * {@link SmartspaceManager} is a system service that provides methods to create Smartspace session
+ * clients. An instance of this class is returned when a client calls
+ * <code> context.getSystemService("smartspace"); </code>.
+ *
+ * After receiving the service, a client must call
+ * {@link SmartspaceManager#createSmartspaceSession(SmartspaceConfig)} with a corresponding
+ * {@link SmartspaceConfig} to get an instance of {@link SmartspaceSession}.
+ * This session is then a client's point of contact with the api. They can send events, request for
+ * updates using the session. It is client's duty to call {@link SmartspaceSession#destroy()} to
+ * destroy the session once they no longer need it.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceManager {
+
+ private final Context mContext;
+
+ /**
+ * @hide
+ */
+ public SmartspaceManager(Context context) {
+ mContext = Objects.requireNonNull(context);
+ }
+
+ /**
+ * Creates a new Smartspace session.
+ */
+ @NonNull
+ public SmartspaceSession createSmartspaceSession(
+ @NonNull SmartspaceConfig smartspaceConfig) {
+ return new SmartspaceSession(mContext, smartspaceConfig);
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceSession.java b/core/java/android/app/smartspace/SmartspaceSession.java
new file mode 100644
index 0000000..16def61
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSession.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.smartspace.ISmartspaceCallback.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.ArrayMap;
+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 Smartspace UI state and execute query.
+ *
+ * <p>
+ * Usage: <pre> {@code
+ *
+ * class MyActivity {
+ * private SmartspaceSession mSmartspaceSession;
+ *
+ * void onCreate() {
+ * mSmartspaceSession = mSmartspaceManager.createSmartspaceSession(smartspaceConfig)
+ * mSmartspaceSession.registerSmartspaceUpdates(...)
+ * }
+ *
+ * void onStart() {
+ * mSmartspaceSession.requestSmartspaceUpdate()
+ * }
+ *
+ * void onTouch(...) OR
+ * void onStateTransitionStarted(...) OR
+ * void onResume(...) OR
+ * void onStop(...) {
+ * mSmartspaceSession.notifyEvent(event);
+ * }
+ *
+ * void onDestroy() {
+ * mSmartspaceSession.unregisterPredictionUpdates()
+ * mSmartspaceSession.destroy();
+ * }
+ *
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSession implements AutoCloseable {
+
+ private static final String TAG = SmartspaceSession.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private final android.app.smartspace.ISmartspaceManager mInterface;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+ private final SmartspaceSessionId mSessionId;
+ private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
+ private final IBinder mToken = new Binder();
+
+ /**
+ * Creates a new Smartspace ui client.
+ * <p>
+ * The caller should call {@link SmartspaceSession#destroy()} to dispose the client once it
+ * no longer used.
+ *
+ * @param context the {@link Context} of the user of this {@link SmartspaceSession}.
+ * @param smartspaceConfig the Smartspace context.
+ */
+ // b/177858121 Create weak reference child objects to not leak context.
+ SmartspaceSession(@NonNull Context context, @NonNull SmartspaceConfig smartspaceConfig) {
+ IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
+ mInterface = android.app.smartspace.ISmartspaceManager.Stub.asInterface(b);
+ mSessionId = new SmartspaceSessionId(
+ context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
+ try {
+ mInterface.createSmartspaceSession(smartspaceConfig, mSessionId, mToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to cerate Smartspace session", e);
+ e.rethrowFromSystemServer();
+ }
+
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * Notifies the Smartspace service of a Smartspace target event.
+ *
+ * @param event The {@link SmartspaceTargetEvent} that represents the Smartspace target event.
+ */
+ public void notifySmartspaceEvent(@NonNull SmartspaceTargetEvent event) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+ try {
+ mInterface.notifySmartspaceEvent(mSessionId, event);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify event", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests the smartspace service for an update.
+ */
+ public void requestSmartspaceUpdate() {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+ try {
+ mInterface.requestSmartspaceUpdate(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to request update.", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests the smartspace service provide continuous updates of smartspace cards via the
+ * provided callback, until the given callback is unregistered.
+ *
+ * @param callbackExecutor The callback executor to use when calling the callback.
+ * @param callback The Callback to be called when updates of Smartspace targets are
+ * available.
+ */
+ public void registerSmartspaceUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull Callback callback) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+
+ if (mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback is already registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor,
+ callback::onTargetsAvailable);
+ mRegisteredCallbacks.put(callback, callbackWrapper);
+ mInterface.registerSmartspaceUpdates(mSessionId, callbackWrapper);
+ mInterface.requestSmartspaceUpdate(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register for smartspace updates", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Requests the smartspace service to stop providing continuous updates to the provided
+ * callback until the callback is re-registered.
+ *
+ * @see {@link SmartspaceSession#registerSmartspaceUpdates(Executor, Callback)}.
+ *
+ * @param callback The callback to be unregistered.
+ */
+ public void unregisterSmartspaceUpdates(@NonNull Callback callback) {
+ if (mIsClosed.get()) {
+ throw new IllegalStateException("This client has already been destroyed.");
+ }
+
+ if (!mRegisteredCallbacks.containsKey(callback)) {
+ // Skip if this callback was never registered
+ return;
+ }
+ try {
+ final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback);
+ mInterface.unregisterSmartspaceUpdates(mSessionId, callbackWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unregister for smartspace updates", e);
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * 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.destroySmartspaceSession(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify Smartspace 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();
+ }
+ }
+
+ /**
+ * Callback for receiving smartspace updates.
+ */
+ public interface Callback {
+
+ /**
+ * Called when a new set of smartspace targets are available.
+ *
+ * @param targets Sorted list of smartspace targets.
+ */
+ void onTargetsAvailable(@NonNull List<SmartspaceTarget> targets);
+ }
+
+ static class CallbackWrapper extends Stub {
+
+ private final Consumer<List<SmartspaceTarget>> mCallback;
+ private final Executor mExecutor;
+
+ CallbackWrapper(@NonNull Executor callbackExecutor,
+ @NonNull Consumer<List<SmartspaceTarget>> 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);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceSessionId.aidl b/core/java/android/app/smartspace/SmartspaceSessionId.aidl
new file mode 100644
index 0000000..a864412
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSessionId.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, 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.smartspace;
+
+parcelable SmartspaceSessionId;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceSessionId.java b/core/java/android/app/smartspace/SmartspaceSessionId.java
new file mode 100644
index 0000000..5220c35
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSessionId.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+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 Smartspace session. See {@link SmartspaceSession}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSessionId implements Parcelable {
+
+ @NonNull
+ private final String mId;
+
+ @NonNull
+ private final int mUserId;
+
+ /**
+ * Creates a new id for a Smartspace session.
+ *
+ * @hide
+ */
+ public SmartspaceSessionId(@NonNull final String id, @NonNull final int userId) {
+ mId = id;
+ mUserId = userId;
+ }
+
+ private SmartspaceSessionId(Parcel p) {
+ mId = p.readString();
+ mUserId = p.readInt();
+ }
+
+ /**
+ * Returns a {@link String} Id of this sessionId.
+ */
+ @Nullable
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the userId associated with this sessionId.
+ */
+ @NonNull
+ public int getUserId() {
+ return mUserId;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+ SmartspaceSessionId other = (SmartspaceSessionId) o;
+ return mId.equals(other.mId) && mUserId == other.mUserId;
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceSessionId{"
+ + "mId='" + mId + '\''
+ + ", mUserId=" + 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<SmartspaceSessionId> CREATOR =
+ new Creator<SmartspaceSessionId>() {
+ public SmartspaceSessionId createFromParcel(Parcel parcel) {
+ return new SmartspaceSessionId(parcel);
+ }
+
+ public SmartspaceSessionId[] newArray(int size) {
+ return new SmartspaceSessionId[size];
+ }
+ };
+}
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.aidl b/core/java/android/app/smartspace/SmartspaceTarget.aidl
new file mode 100644
index 0000000..3442cf2
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTarget.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, 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.smartspace;
+
+parcelable SmartspaceTarget;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
new file mode 100644
index 0000000..ce5040e
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -0,0 +1,660 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceTarget} is a data class which holds all properties necessary to inflate a
+ * smartspace card. It contains data and related metadata which is supposed to be utilized by
+ * smartspace clients based on their own UI/UX requirements. Some of the properties have
+ * {@link SmartspaceAction} as their type because they can have associated actions.
+ *
+ * <p><b>NOTE: </b>
+ * If {@link mWidgetId} is set, it should be preferred over all other properties.
+ * Else, if {@link mSliceUri} is set, it should be preferred over all other data properties.
+ * Otherwise, the instance should be treated as a data object.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTarget implements Parcelable {
+
+ /** A unique Id for an instance of {@link SmartspaceTarget}. */
+ @NonNull
+ private final String mSmartspaceTargetId;
+
+ /** A {@link SmartspaceAction} for the header in the Smartspace card. */
+ @Nullable
+ private final SmartspaceAction mHeaderAction;
+
+ /** A {@link SmartspaceAction} for the base action in the Smartspace card. */
+ @Nullable
+ private final SmartspaceAction mBaseAction;
+
+ /** A timestamp indicating when the card was created. */
+ @NonNull
+ private final long mCreationTimeMillis;
+
+ /**
+ * A timestamp indicating when the card should be removed from view, in case the service
+ * disconnects or restarts.
+ */
+ @NonNull
+ private final long mExpiryTimeMillis;
+
+ /** A score assigned to a target. */
+ @NonNull
+ private final float mScore;
+
+ /** A {@link List<SmartspaceAction>} containing all action chips. */
+ @NonNull
+ private final List<SmartspaceAction> mActionChips;
+
+ /** A {@link List<SmartspaceAction>} containing all icons for the grid. */
+ @NonNull
+ private final List<SmartspaceAction> mIconGrid;
+
+ /**
+ * {@link FeatureType} indicating the feature type of this card.
+ *
+ * @see FeatureType
+ */
+ @FeatureType
+ @NonNull
+ private final int mFeatureType;
+
+ /**
+ * Indicates whether the content is sensitive. Certain UI surfaces may choose to skip rendering
+ * real content until the device is unlocked.
+ */
+ @NonNull
+ private final boolean mSensitive;
+
+ /** Indicating if the UI should show this target in its expanded state. */
+ @NonNull
+ private final boolean mShouldShowExpanded;
+
+ /** A Notification key if the target was generated using a notification. */
+ @Nullable
+ private final String mSourceNotificationKey;
+
+ /** {@link ComponentName} for this target. */
+ @NonNull
+ private final ComponentName mComponentName;
+
+ /** {@link UserHandle} for this target. */
+ @NonNull
+ private final UserHandle mUserHandle;
+
+ /** Target Ids of other {@link SmartspaceTarget}s if they are associated with this target. */
+ @Nullable
+ private final String mAssociatedSmartspaceTargetId;
+
+ /** {@link Uri} Slice Uri if this target is a slice. */
+ @Nullable
+ private final Uri mSliceUri;
+
+ /** {@link AppWidgetProviderInfo} if this target is a widget. */
+ @Nullable
+ private final AppWidgetProviderInfo mWidgetId;
+
+ public static final int FEATURE_UNDEFINED = 0;
+ public static final int FEATURE_WEATHER = 1;
+ public static final int FEATURE_CALENDAR = 2;
+ public static final int FEATURE_COMMUTE_TIME = 3;
+ public static final int FEATURE_FLIGHT = 4;
+ public static final int FEATURE_TIPS = 5;
+ public static final int FEATURE_REMINDER = 6;
+ public static final int FEATURE_ALARM = 7;
+ public static final int FEATURE_ONBOARDING = 8;
+ public static final int FEATURE_SPORTS = 9;
+ public static final int FEATURE_WEATHER_ALERT = 10;
+ public static final int FEATURE_CONSENT = 11;
+ public static final int FEATURE_STOCK_PRICE_CHANGE = 12;
+ public static final int FEATURE_SHOPPING_LIST = 13;
+ public static final int FEATURE_LOYALTY_CARD = 14;
+ public static final int FEATURE_MEDIA = 15;
+ public static final int FEATURE_BEDTIME_ROUTINE = 16;
+ public static final int FEATURE_FITNESS_TRACKING = 17;
+ public static final int FEATURE_ETA_MONITORING = 18;
+ public static final int FEATURE_MISSED_CALL = 19;
+ public static final int FEATURE_PACKAGE_TRACKING = 20;
+ public static final int FEATURE_TIMER = 21;
+ public static final int FEATURE_STOPWATCH = 22;
+ public static final int FEATURE_UPCOMING_ALARM = 23;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"FEATURE_"}, value = {
+ FEATURE_UNDEFINED,
+ FEATURE_WEATHER,
+ FEATURE_CALENDAR,
+ FEATURE_COMMUTE_TIME,
+ FEATURE_FLIGHT,
+ FEATURE_TIPS,
+ FEATURE_REMINDER,
+ FEATURE_ALARM,
+ FEATURE_ONBOARDING,
+ FEATURE_SPORTS,
+ FEATURE_WEATHER_ALERT,
+ FEATURE_CONSENT,
+ FEATURE_STOCK_PRICE_CHANGE,
+ FEATURE_SHOPPING_LIST,
+ FEATURE_LOYALTY_CARD,
+ FEATURE_MEDIA,
+ FEATURE_BEDTIME_ROUTINE,
+ FEATURE_FITNESS_TRACKING,
+ FEATURE_ETA_MONITORING,
+ FEATURE_MISSED_CALL,
+ FEATURE_PACKAGE_TRACKING,
+ FEATURE_TIMER,
+ FEATURE_STOPWATCH,
+ FEATURE_UPCOMING_ALARM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FeatureType {
+ }
+
+ private SmartspaceTarget(Parcel in) {
+ this.mSmartspaceTargetId = in.readString();
+ this.mHeaderAction = in.readTypedObject(SmartspaceAction.CREATOR);
+ this.mBaseAction = in.readTypedObject(SmartspaceAction.CREATOR);
+ this.mCreationTimeMillis = in.readLong();
+ this.mExpiryTimeMillis = in.readLong();
+ this.mScore = in.readFloat();
+ this.mActionChips = in.createTypedArrayList(SmartspaceAction.CREATOR);
+ this.mIconGrid = in.createTypedArrayList(SmartspaceAction.CREATOR);
+ this.mFeatureType = in.readInt();
+ this.mSensitive = in.readBoolean();
+ this.mShouldShowExpanded = in.readBoolean();
+ this.mSourceNotificationKey = in.readString();
+ this.mComponentName = in.readTypedObject(ComponentName.CREATOR);
+ this.mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+ this.mAssociatedSmartspaceTargetId = in.readString();
+ this.mSliceUri = in.readTypedObject(Uri.CREATOR);
+ this.mWidgetId = in.readTypedObject(AppWidgetProviderInfo.CREATOR);
+ }
+
+ private SmartspaceTarget(String smartspaceTargetId,
+ SmartspaceAction headerAction, SmartspaceAction baseAction, long creationTimeMillis,
+ long expiryTimeMillis, float score,
+ List<SmartspaceAction> actionChips,
+ List<SmartspaceAction> iconGrid, int featureType, boolean sensitive,
+ boolean shouldShowExpanded, String sourceNotificationKey,
+ ComponentName componentName, UserHandle userHandle,
+ String associatedSmartspaceTargetId, Uri sliceUri,
+ AppWidgetProviderInfo widgetId) {
+ mSmartspaceTargetId = smartspaceTargetId;
+ mHeaderAction = headerAction;
+ mBaseAction = baseAction;
+ mCreationTimeMillis = creationTimeMillis;
+ mExpiryTimeMillis = expiryTimeMillis;
+ mScore = score;
+ mActionChips = actionChips;
+ mIconGrid = iconGrid;
+ mFeatureType = featureType;
+ mSensitive = sensitive;
+ mShouldShowExpanded = shouldShowExpanded;
+ mSourceNotificationKey = sourceNotificationKey;
+ mComponentName = componentName;
+ mUserHandle = userHandle;
+ mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
+ mSliceUri = sliceUri;
+ mWidgetId = widgetId;
+ }
+
+ /**
+ * Returns the Id of the target.
+ */
+ @NonNull
+ public String getSmartspaceTargetId() {
+ return mSmartspaceTargetId;
+ }
+
+ /**
+ * Returns the header action of the target.
+ */
+ @Nullable
+ public SmartspaceAction getHeaderAction() {
+ return mHeaderAction;
+ }
+
+ /**
+ * Returns the base action of the target.
+ */
+ @Nullable
+ public SmartspaceAction getBaseAction() {
+ return mBaseAction;
+ }
+
+ /**
+ * Returns the creation time of the target.
+ */
+ @NonNull
+ public long getCreationTimeMillis() {
+ return mCreationTimeMillis;
+ }
+
+ /**
+ * Returns the expiry time of the target.
+ */
+ @NonNull
+ public long getExpiryTimeMillis() {
+ return mExpiryTimeMillis;
+ }
+
+ /**
+ * Returns the score of the target.
+ */
+ @NonNull
+ public float getScore() {
+ return mScore;
+ }
+
+ /**
+ * Return the action chips of the target.
+ */
+ @NonNull
+ public List<SmartspaceAction> getActionChips() {
+ return mActionChips;
+ }
+
+ /**
+ * Return the icons of the target.
+ */
+ @NonNull
+ public List<SmartspaceAction> getIconGrid() {
+ return mIconGrid;
+ }
+
+ /**
+ * Returns the feature type of the target.
+ */
+ @NonNull
+ public int getFeatureType() {
+ return mFeatureType;
+ }
+
+ /**
+ * Returns whether the target is sensitive or not.
+ */
+ @NonNull
+ public boolean isSensitive() {
+ return mSensitive;
+ }
+
+ /**
+ * Returns whether the target should be shown in expanded state.
+ */
+ @NonNull
+ public boolean shouldShowExpanded() {
+ return mShouldShowExpanded;
+ }
+
+ /**
+ * Returns the source notification key of the target.
+ */
+ @Nullable
+ public String getSourceNotificationKey() {
+ return mSourceNotificationKey;
+ }
+
+ /**
+ * Returns the component name of the target.
+ */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * Returns the user handle of the target.
+ */
+ @NonNull
+ public UserHandle getUserHandle() {
+ return mUserHandle;
+ }
+
+ /**
+ * Returns the id of a target associated with this instance.
+ */
+ @Nullable
+ public String getAssociatedSmartspaceTargetId() {
+ return mAssociatedSmartspaceTargetId;
+ }
+
+ /**
+ * Returns the slice uri, if the target is a slice.
+ */
+ @Nullable
+ public Uri getSliceUri() {
+ return mSliceUri;
+ }
+
+ /**
+ * Returns the AppWidgetProviderInfo, if the target is a widget.
+ */
+ @Nullable
+ public AppWidgetProviderInfo getWidgetId() {
+ return mWidgetId;
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceTarget> CREATOR = new Creator<SmartspaceTarget>() {
+ @Override
+ public SmartspaceTarget createFromParcel(Parcel source) {
+ return new SmartspaceTarget(source);
+ }
+
+ @Override
+ public SmartspaceTarget[] newArray(int size) {
+ return new SmartspaceTarget[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(this.mSmartspaceTargetId);
+ dest.writeTypedObject(this.mHeaderAction, flags);
+ dest.writeTypedObject(this.mBaseAction, flags);
+ dest.writeLong(this.mCreationTimeMillis);
+ dest.writeLong(this.mExpiryTimeMillis);
+ dest.writeFloat(this.mScore);
+ dest.writeTypedList(this.mActionChips);
+ dest.writeTypedList(this.mIconGrid);
+ dest.writeInt(this.mFeatureType);
+ dest.writeBoolean(this.mSensitive);
+ dest.writeBoolean(this.mShouldShowExpanded);
+ dest.writeString(this.mSourceNotificationKey);
+ dest.writeTypedObject(this.mComponentName, flags);
+ dest.writeTypedObject(this.mUserHandle, flags);
+ dest.writeString(this.mAssociatedSmartspaceTargetId);
+ dest.writeTypedObject(this.mSliceUri, flags);
+ dest.writeTypedObject(this.mWidgetId, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "SmartspaceTarget{"
+ + "mSmartspaceTargetId='" + mSmartspaceTargetId + '\''
+ + ", mHeaderAction=" + mHeaderAction
+ + ", mBaseAction=" + mBaseAction
+ + ", mCreationTimeMillis=" + mCreationTimeMillis
+ + ", mExpiryTimeMillis=" + mExpiryTimeMillis
+ + ", mScore=" + mScore
+ + ", mActionChips=" + mActionChips
+ + ", mIconGrid=" + mIconGrid
+ + ", mFeatureType=" + mFeatureType
+ + ", mSensitive=" + mSensitive
+ + ", mShouldShowExpanded=" + mShouldShowExpanded
+ + ", mSourceNotificationKey='" + mSourceNotificationKey + '\''
+ + ", mComponentName=" + mComponentName
+ + ", mUserHandle=" + mUserHandle
+ + ", mAssociatedSmartspaceTargetId='" + mAssociatedSmartspaceTargetId + '\''
+ + ", mSliceUri=" + mSliceUri
+ + ", mWidgetId=" + mWidgetId
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SmartspaceTarget that = (SmartspaceTarget) o;
+ return mCreationTimeMillis == that.mCreationTimeMillis
+ && mExpiryTimeMillis == that.mExpiryTimeMillis
+ && Float.compare(that.mScore, mScore) == 0
+ && mFeatureType == that.mFeatureType
+ && mSensitive == that.mSensitive
+ && mShouldShowExpanded == that.mShouldShowExpanded
+ && mSmartspaceTargetId.equals(that.mSmartspaceTargetId)
+ && Objects.equals(mHeaderAction, that.mHeaderAction)
+ && Objects.equals(mBaseAction, that.mBaseAction)
+ && Objects.equals(mActionChips, that.mActionChips)
+ && Objects.equals(mIconGrid, that.mIconGrid)
+ && Objects.equals(mSourceNotificationKey, that.mSourceNotificationKey)
+ && mComponentName.equals(that.mComponentName)
+ && mUserHandle.equals(that.mUserHandle)
+ && Objects.equals(mAssociatedSmartspaceTargetId,
+ that.mAssociatedSmartspaceTargetId)
+ && Objects.equals(mSliceUri, that.mSliceUri)
+ && Objects.equals(mWidgetId, that.mWidgetId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis,
+ mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive,
+ mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle,
+ mAssociatedSmartspaceTargetId, mSliceUri, mWidgetId);
+ }
+
+ /**
+ * A builder for {@link SmartspaceTarget} object.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ private final String mSmartspaceTargetId;
+ private SmartspaceAction mHeaderAction;
+ private SmartspaceAction mBaseAction;
+ private long mCreationTimeMillis;
+ private long mExpiryTimeMillis;
+ private float mScore;
+ private List<SmartspaceAction> mActionChips = new ArrayList<>();
+ private List<SmartspaceAction> mIconGrid = new ArrayList<>();
+ private int mFeatureType;
+ private boolean mSensitive;
+ private boolean mShouldShowExpanded;
+ private String mSourceNotificationKey;
+ private final ComponentName mComponentName;
+ private final UserHandle mUserHandle;
+ private String mAssociatedSmartspaceTargetId;
+ private Uri mSliceUri;
+ private AppWidgetProviderInfo mWidgetId;
+
+ /**
+ * A builder for {@link SmartspaceTarget}.
+ *
+ * @param smartspaceTargetId the id of this target
+ * @param componentName the componentName of this target
+ * @param userHandle the userHandle of this target
+ */
+ public Builder(@NonNull String smartspaceTargetId,
+ @NonNull ComponentName componentName, @NonNull UserHandle userHandle) {
+ this.mSmartspaceTargetId = smartspaceTargetId;
+ this.mComponentName = componentName;
+ this.mUserHandle = userHandle;
+ }
+
+ /**
+ * Sets the header action.
+ */
+ @NonNull
+ public Builder setHeaderAction(@NonNull SmartspaceAction headerAction) {
+ this.mHeaderAction = headerAction;
+ return this;
+ }
+
+ /**
+ * Sets the base action.
+ */
+ @NonNull
+ public Builder setBaseAction(@NonNull SmartspaceAction baseAction) {
+ this.mBaseAction = baseAction;
+ return this;
+ }
+
+ /**
+ * Sets the creation time.
+ */
+ @NonNull
+ public Builder setCreationTimeMillis(@NonNull long creationTimeMillis) {
+ this.mCreationTimeMillis = creationTimeMillis;
+ return this;
+ }
+
+ /**
+ * Sets the expiration time.
+ */
+ @NonNull
+ public Builder setExpiryTimeMillis(@NonNull long expiryTimeMillis) {
+ this.mExpiryTimeMillis = expiryTimeMillis;
+ return this;
+ }
+
+ /**
+ * Sets the score.
+ */
+ @NonNull
+ public Builder setScore(@NonNull float score) {
+ this.mScore = score;
+ return this;
+ }
+
+ /**
+ * Sets the action chips.
+ */
+ @NonNull
+ public Builder setActionChips(@NonNull List<SmartspaceAction> actionChips) {
+ this.mActionChips = actionChips;
+ return this;
+ }
+
+ /**
+ * Sets the icon grid.
+ */
+ @NonNull
+ public Builder setIconGrid(@NonNull List<SmartspaceAction> iconGrid) {
+ this.mIconGrid = iconGrid;
+ return this;
+ }
+
+ /**
+ * Sets the feature type.
+ */
+ @NonNull
+ public Builder setFeatureType(@NonNull int featureType) {
+ this.mFeatureType = featureType;
+ return this;
+ }
+
+ /**
+ * Sets whether the contents are sensitive.
+ */
+ @NonNull
+ public Builder setSensitive(@NonNull boolean sensitive) {
+ this.mSensitive = sensitive;
+ return this;
+ }
+
+ /**
+ * Sets whether to show the card as expanded.
+ */
+ @NonNull
+ public Builder setShouldShowExpanded(@NonNull boolean shouldShowExpanded) {
+ this.mShouldShowExpanded = shouldShowExpanded;
+ return this;
+ }
+
+ /**
+ * Sets the source notification key.
+ */
+ @NonNull
+ public Builder setSourceNotificationKey(@NonNull String sourceNotificationKey) {
+ this.mSourceNotificationKey = sourceNotificationKey;
+ return this;
+ }
+
+ /**
+ * Sets the associated smartspace target id.
+ */
+ @NonNull
+ public Builder setAssociatedSmartspaceTargetId(
+ @NonNull String associatedSmartspaceTargetId) {
+ this.mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
+ return this;
+ }
+
+ /**
+ * Sets the slice uri.
+ *
+ * <p><b>NOTE: </b> If {@link mWidgetId} is also set, {@link mSliceUri} should be ignored.
+ */
+ @NonNull
+ public Builder setSliceUri(@NonNull Uri sliceUri) {
+ this.mSliceUri = sliceUri;
+ return this;
+ }
+
+ /**
+ * Sets the widget id.
+ *
+ * <p><b>NOTE: </b> If {@link mWidgetId} is set, all other @Nullable params should be
+ * ignored.
+ */
+ @NonNull
+ public Builder setWidgetId(@NonNull AppWidgetProviderInfo widgetId) {
+ this.mWidgetId = widgetId;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link SmartspaceTarget}.
+ *
+ * @throws IllegalStateException when non null fields are set as null.
+ */
+ @NonNull
+ public SmartspaceTarget build() {
+ if (mSmartspaceTargetId == null
+ || mComponentName == null
+ || mUserHandle == null) {
+ throw new IllegalStateException("Please assign a value to all @NonNull args.");
+ }
+ return new SmartspaceTarget(mSmartspaceTargetId,
+ mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore,
+ mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded,
+ mSourceNotificationKey, mComponentName, mUserHandle,
+ mAssociatedSmartspaceTargetId, mSliceUri, mWidgetId);
+ }
+ }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl b/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
new file mode 100644
index 0000000..e797a9b
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2021, 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.smartspace;
+
+parcelable SmartspaceTargetEvent;
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
new file mode 100644
index 0000000..1e0653d
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+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;
+
+/**
+ * A representation of a smartspace event.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTargetEvent implements Parcelable {
+
+ /**
+ * User interacted with the target.
+ */
+ public static final int EVENT_TARGET_INTERACTION = 1;
+
+ /**
+ * Smartspace target was brought into view.
+ */
+ public static final int EVENT_TARGET_IN_VIEW = 2;
+ /**
+ * Smartspace target went out of view.
+ */
+ public static final int EVENT_TARGET_OUT_OF_VIEW = 3;
+ /**
+ * A dismiss action was issued by the user.
+ */
+ public static final int EVENT_TARGET_DISMISS = 4;
+ /**
+ * A block action was issued by the user.
+ */
+ public static final int EVENT_TARGET_BLOCK = 5;
+ /**
+ * The Ui surface came into view.
+ */
+ public static final int EVENT_UI_SURFACE_IN_VIEW = 6;
+ /**
+ * The Ui surface went out of view.
+ */
+ public static final int EVENT_UI_SURFACE_OUT_OF_VIEW = 7;
+
+ /**
+ * @see Parcelable.Creator
+ */
+ @NonNull
+ public static final Creator<SmartspaceTargetEvent> CREATOR =
+ new Creator<SmartspaceTargetEvent>() {
+ public SmartspaceTargetEvent createFromParcel(Parcel parcel) {
+ return new SmartspaceTargetEvent(parcel);
+ }
+
+ public SmartspaceTargetEvent[] newArray(int size) {
+ return new SmartspaceTargetEvent[size];
+ }
+ };
+
+ @Nullable
+ private final SmartspaceTarget mSmartspaceTarget;
+
+ @Nullable
+ private final String mSmartspaceActionId;
+
+ @EventType
+ private final int mEventType;
+
+ private SmartspaceTargetEvent(@Nullable SmartspaceTarget smartspaceTarget,
+ @Nullable String smartspaceActionId,
+ @EventType int eventType) {
+ mSmartspaceTarget = smartspaceTarget;
+ mSmartspaceActionId = smartspaceActionId;
+ mEventType = eventType;
+ }
+
+ private SmartspaceTargetEvent(Parcel parcel) {
+ mSmartspaceTarget = parcel.readParcelable(null);
+ mSmartspaceActionId = parcel.readString();
+ mEventType = parcel.readInt();
+ }
+
+ /**
+ * Get the {@link SmartspaceTarget} associated with this event.
+ */
+ @Nullable
+ public SmartspaceTarget getSmartspaceTarget() {
+ return mSmartspaceTarget;
+ }
+
+ /**
+ * Get the action id of the Smartspace Action associated with this event.
+ */
+ @Nullable
+ public String getSmartspaceActionId() {
+ return mSmartspaceActionId;
+ }
+
+ /**
+ * Get the {@link EventType} of this event.
+ */
+ @NonNull
+ @EventType
+ public int getEventType() {
+ return mEventType;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mSmartspaceTarget, flags);
+ dest.writeString(mSmartspaceActionId);
+ dest.writeInt(mEventType);
+ }
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"EVENT_"}, value = {
+ EVENT_TARGET_INTERACTION,
+ EVENT_TARGET_IN_VIEW,
+ EVENT_TARGET_OUT_OF_VIEW,
+ EVENT_TARGET_DISMISS,
+ EVENT_TARGET_BLOCK,
+ EVENT_UI_SURFACE_IN_VIEW,
+ EVENT_UI_SURFACE_OUT_OF_VIEW
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventType {
+ }
+
+ /**
+ * A builder for {@link SmartspaceTargetEvent}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
+ @EventType
+ private final int mEventType;
+ @Nullable
+ private SmartspaceTarget mSmartspaceTarget;
+ @Nullable
+ private String mSmartspaceActionId;
+
+ /**
+ * A builder for {@link SmartspaceTargetEvent}.
+ */
+ public Builder(@EventType int eventType) {
+ mEventType = eventType;
+ }
+
+ /**
+ * Sets the SmartspaceTarget for this event.
+ */
+ @NonNull
+ public Builder setSmartspaceTarget(@NonNull SmartspaceTarget smartspaceTarget) {
+ mSmartspaceTarget = smartspaceTarget;
+ return this;
+ }
+
+ /**
+ * Sets the Smartspace action id for this event.
+ */
+ @NonNull
+ public Builder setSmartspaceActionId(@NonNull String smartspaceActionId) {
+ mSmartspaceActionId = smartspaceActionId;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link SmartspaceTargetEvent} instance.
+ */
+ @NonNull
+ public SmartspaceTargetEvent build() {
+ return new SmartspaceTargetEvent(mSmartspaceTarget, mSmartspaceActionId, mEventType);
+ }
+ }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0c50446..c0e0635 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4615,6 +4615,18 @@
public static final String SEARCH_UI_SERVICE = "search_ui";
/**
+ * Used for getting the smartspace service.
+ *
+ * <p><b>NOTE: </b> this service is optional; callers of
+ * {@code Context.getSystemServiceName(SMARTSPACE_SERVICE)} should check for {@code null}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ @SystemApi
+ public static final String SMARTSPACE_SERVICE = "smartspace";
+
+ /**
* Use with {@link #getSystemService(String)} to access the
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
*
diff --git a/core/java/android/service/smartspace/ISmartspaceService.aidl b/core/java/android/service/smartspace/ISmartspaceService.aidl
new file mode 100644
index 0000000..c9c6807
--- /dev/null
+++ b/core/java/android/service/smartspace/ISmartspaceService.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.ISmartspaceCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * Interface from the system to Smartspace service.
+ *
+ * @hide
+ */
+oneway interface ISmartspaceService {
+
+ void onCreateSmartspaceSession(in SmartspaceConfig context, in SmartspaceSessionId sessionId);
+
+ void notifySmartspaceEvent(in SmartspaceSessionId sessionId, in SmartspaceTargetEvent event);
+
+ void requestSmartspaceUpdate(in SmartspaceSessionId sessionId);
+
+ void registerSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void unregisterSmartspaceUpdates(in SmartspaceSessionId sessionId,
+ in ISmartspaceCallback callback);
+
+ void onDestroySmartspaceSession(in SmartspaceSessionId sessionId);
+}
diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java
new file mode 100644
index 0000000..09b7310
--- /dev/null
+++ b/core/java/android/service/smartspace/SmartspaceService.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+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.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+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.smartspace.ISmartspaceService.Stub;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A service used to share the lifecycle of smartspace UI (open, close, interaction)
+ * and also to return smartspace result on a query.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SmartspaceService extends Service {
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ *
+ * <p>The service must also require the {@link android.permission#MANAGE_SMARTSPACE}
+ * permission.
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.service.smartspace.SmartspaceService";
+ private static final boolean DEBUG = false;
+ private static final String TAG = "SmartspaceService";
+ private final ArrayMap<SmartspaceSessionId, ArrayList<CallbackWrapper>> mSessionCallbacks =
+ new ArrayMap<>();
+ private Handler mHandler;
+
+ private final android.service.smartspace.ISmartspaceService mInterface = new Stub() {
+
+ @Override
+ public void onCreateSmartspaceSession(SmartspaceConfig smartspaceConfig,
+ SmartspaceSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doCreateSmartspaceSession,
+ SmartspaceService.this, smartspaceConfig, sessionId));
+ }
+
+ @Override
+ public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+ SmartspaceTargetEvent event) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::notifySmartspaceEvent,
+ SmartspaceService.this, sessionId, event));
+ }
+
+ @Override
+ public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doRequestPredictionUpdate,
+ SmartspaceService.this, sessionId));
+ }
+
+ @Override
+ public void registerSmartspaceUpdates(SmartspaceSessionId sessionId,
+ ISmartspaceCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doRegisterSmartspaceUpdates,
+ SmartspaceService.this, sessionId, callback));
+ }
+
+ @Override
+ public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+ ISmartspaceCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doUnregisterSmartspaceUpdates,
+ SmartspaceService.this, sessionId, callback));
+ }
+
+ @Override
+ public void onDestroySmartspaceSession(SmartspaceSessionId sessionId) {
+
+ mHandler.sendMessage(
+ obtainMessage(SmartspaceService::doDestroy,
+ SmartspaceService.this, sessionId));
+ }
+ };
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks);
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+
+ @Override
+ @NonNull
+ public final IBinder onBind(@NonNull Intent intent) {
+ Log.d(TAG, "onBind mSessionCallbacks: " + mSessionCallbacks);
+ 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;
+ }
+
+ private void doCreateSmartspaceSession(@NonNull SmartspaceConfig config,
+ @NonNull SmartspaceSessionId sessionId) {
+ Log.d(TAG, "doCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+ mSessionCallbacks.put(sessionId, new ArrayList<>());
+ onCreateSmartspaceSession(config, sessionId);
+ }
+
+ /**
+ * Gets called when the client calls <code> SmartspaceManager#createSmartspaceSession </code>.
+ */
+ public abstract void onCreateSmartspaceSession(@NonNull SmartspaceConfig config,
+ @NonNull SmartspaceSessionId sessionId);
+
+ /**
+ * Gets called when the client calls <code> SmartspaceSession#notifySmartspaceEvent </code>.
+ */
+ @MainThread
+ public abstract void notifySmartspaceEvent(@NonNull SmartspaceSessionId sessionId,
+ @NonNull SmartspaceTargetEvent event);
+
+ /**
+ * Gets called when the client calls <code> SmartspaceSession#requestSmartspaceUpdate </code>.
+ */
+ @MainThread
+ public abstract void onRequestSmartspaceUpdate(@NonNull SmartspaceSessionId sessionId);
+
+ private void doRegisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ Log.d(TAG, "doRegisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper == null) {
+ callbacks.add(new CallbackWrapper(callback,
+ callbackWrapper ->
+ mHandler.post(
+ () -> removeCallbackWrapper(callbacks, callbackWrapper))));
+ }
+ }
+
+ private void doUnregisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ Log.d(TAG, "doUnregisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks == null) {
+ Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
+ return;
+ }
+
+ final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+ if (wrapper != null) {
+ removeCallbackWrapper(callbacks, wrapper);
+ }
+ }
+
+ private void doRequestPredictionUpdate(@NonNull SmartspaceSessionId sessionId) {
+ Log.d(TAG, "doRequestPredictionUpdate mSessionCallbacks: " + mSessionCallbacks);
+ // Just an optimization, if there are no callbacks, then don't bother notifying the service
+ final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks != null && !callbacks.isEmpty()) {
+ onRequestSmartspaceUpdate(sessionId);
+ }
+ }
+
+ /**
+ * Finds the callback wrapper for the given callback.
+ */
+ private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks,
+ ISmartspaceCallback callback) {
+ for (int i = callbacks.size() - 1; i >= 0; i--) {
+ if (callbacks.get(i).isCallback(callback)) {
+ return callbacks.get(i);
+ }
+ }
+ return null;
+ }
+
+ private void removeCallbackWrapper(
+ ArrayList<CallbackWrapper> callbacks, CallbackWrapper wrapper) {
+ if (callbacks == null) {
+ return;
+ }
+ callbacks.remove(wrapper);
+ }
+
+ /**
+ * Gets called when the client calls <code> SmartspaceManager#destroy() </code>.
+ */
+ public abstract void onDestroySmartspaceSession(@NonNull SmartspaceSessionId sessionId);
+
+ private void doDestroy(@NonNull SmartspaceSessionId sessionId) {
+ Log.d(TAG, "doDestroy mSessionCallbacks: " + mSessionCallbacks);
+ super.onDestroy();
+ mSessionCallbacks.remove(sessionId);
+ onDestroySmartspaceSession(sessionId);
+ }
+
+ /**
+ * Used by the prediction factory to send back results the client app. The can be called
+ * in response to {@link #onRequestSmartspaceUpdate(SmartspaceSessionId)} or proactively as
+ * a result of changes in predictions.
+ */
+ public final void updateSmartspaceTargets(@NonNull SmartspaceSessionId sessionId,
+ @NonNull List<SmartspaceTarget> targets) {
+ Log.d(TAG, "updateSmartspaceTargets mSessionCallbacks: " + mSessionCallbacks);
+ List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+ if (callbacks != null) {
+ for (CallbackWrapper callback : callbacks) {
+ callback.accept(targets);
+ }
+ }
+ }
+
+ /**
+ * Destroys a smartspace session.
+ */
+ @MainThread
+ public abstract void onDestroy(@NonNull SmartspaceSessionId sessionId);
+
+ private static final class CallbackWrapper implements Consumer<List<SmartspaceTarget>>,
+ IBinder.DeathRecipient {
+
+ private final Consumer<CallbackWrapper> mOnBinderDied;
+ private ISmartspaceCallback mCallback;
+
+ CallbackWrapper(ISmartspaceCallback 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);
+ }
+ }
+
+ public boolean isCallback(@NonNull ISmartspaceCallback callback) {
+ if (mCallback == null) {
+ Slog.e(TAG, "Callback is null, likely the binder has died.");
+ return false;
+ }
+ return mCallback.equals(callback);
+ }
+
+ @Override
+ public void accept(List<SmartspaceTarget> smartspaceTargets) {
+ try {
+ if (mCallback != null) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "CallbackWrapper.accept smartspaceTargets=" + smartspaceTargets);
+ }
+ mCallback.onResult(new ParceledListSlice(smartspaceTargets));
+ }
+ } 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);
+ }
+ }
+ }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4a0a35d..c4309266 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5168,6 +5168,11 @@
<permission android:name="android.permission.MANAGE_SEARCH_UI"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the smartspace service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_SMARTSPACE"
+ 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> -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5e0cda6..01b8efa 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3800,6 +3800,15 @@
-->
<string name="config_defaultSearchUiService" translatable="false"></string>
+ <!-- The package name for the system's smartspace service.
+ This service returns smartspace results.
+
+ 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, smartspace will be disabled.
+ Example: "com.android.intelligence/.SmartspaceService"
+-->
+ <string name="config_defaultSmartspaceService" translatable="false"></string>
+
<!-- The package name for the system's speech recognition service.
This service must be trusted, as it can be activated without explicit consent of the user.
Example: "com.android.speech/.RecognitionService"
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index dfccdf4..815330f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3498,6 +3498,7 @@
<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_defaultSmartspaceService" />
<java-symbol type="string" name="config_defaultMusicRecognitionService" />
<java-symbol type="string" name="config_defaultAttentionService" />
<java-symbol type="string" name="config_defaultRotationResolverService" />
diff --git a/services/Android.bp b/services/Android.bp
index f6bb72a..f0cab1e 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -28,6 +28,7 @@
":services.profcollect-sources",
":services.restrictions-sources",
":services.searchui-sources",
+ ":services.smartspace-sources",
":services.speech-sources",
":services.startop.iorap-sources",
":services.systemcaptions-sources",
@@ -76,6 +77,7 @@
"services.profcollect",
"services.restrictions",
"services.searchui",
+ "services.smartspace",
"services.speech",
"services.startop",
"services.systemcaptions",
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d28c3cc..b8088c4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -341,6 +341,8 @@
"com.android.server.contentsuggestions.ContentSuggestionsManagerService";
private static final String SEARCH_UI_MANAGER_SERVICE_CLASS =
"com.android.server.searchui.SearchUiManagerService";
+ private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
+ "com.android.server.smartspace.SmartspaceManagerService";
private static final String DEVICE_IDLE_CONTROLLER_CLASS =
"com.android.server.DeviceIdleController";
private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
@@ -1670,6 +1672,12 @@
mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
t.traceEnd();
+ // Smartspace manager service
+ // TODO: add deviceHasConfigString(context, R.string.config_defaultSmartspaceService)
+ t.traceBegin("StartSmartspaceService");
+ mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+
t.traceBegin("InitConnectivityModuleConnector");
try {
ConnectivityModuleConnector.getInstance().init(context);
diff --git a/services/smartspace/Android.bp b/services/smartspace/Android.bp
new file mode 100644
index 0000000..fcf780d
--- /dev/null
+++ b/services/smartspace/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+ name: "services.smartspace-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.smartspace",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.smartspace-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
new file mode 100644
index 0000000..3b5a5a5
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.smartspace.ISmartspaceService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the {@link android.service.smartspace.SmartspaceService} implementation in another
+ * process.
+ */
+public class RemoteSmartspaceService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteSmartspaceService,
+ ISmartspaceService> {
+
+ private static final String TAG = "RemoteSmartspaceService";
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ private final RemoteSmartspaceServiceCallbacks mCallback;
+
+ public RemoteSmartspaceService(Context context, String serviceInterface,
+ ComponentName componentName, int userId,
+ RemoteSmartspaceServiceCallbacks 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 ISmartspaceService getServiceInterface(IBinder service) {
+ return ISmartspaceService.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<ISmartspaceService> request) {
+ scheduleAsyncRequest(request);
+ }
+
+ /**
+ * Execute async request on remote service immediately instead of sending it to Handler queue.
+ */
+ public void executeOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+ executeAsyncRequest(request);
+ }
+
+ /**
+ * Failure callback
+ */
+ public interface RemoteSmartspaceServiceCallbacks
+ extends VultureCallback<RemoteSmartspaceService> {
+
+ /**
+ * 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);
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
new file mode 100644
index 0000000..169b85e
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import static android.Manifest.permission.MANAGE_SMARTSPACE;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.content.Context.SMARTSPACE_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.ISmartspaceManager;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+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 smartspace targets given a query.
+ */
+public class SmartspaceManagerService extends
+ AbstractMasterSystemService<SmartspaceManagerService, SmartspacePerUserService> {
+
+ private static final String TAG = SmartspaceManagerService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ public SmartspaceManagerService(Context context) {
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultSmartspaceService), null,
+ PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @Override
+ protected SmartspacePerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
+ return new SmartspacePerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(SMARTSPACE_SERVICE, new SmartspaceManagerStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_SMARTSPACE, TAG);
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+ final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageUpdatedLocked();
+ }
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+ final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageRestartedLocked();
+ }
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private class SmartspaceManagerStub extends ISmartspaceManager.Stub {
+
+ @Override
+ public void createSmartspaceSession(@NonNull SmartspaceConfig smartspaceConfig,
+ @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+ runForUserLocked("createSmartspaceSession", sessionId, (service) ->
+ service.onCreateSmartspaceSessionLocked(smartspaceConfig, sessionId, token));
+ }
+
+ @Override
+ public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+ SmartspaceTargetEvent event) {
+ runForUserLocked("notifySmartspaceEvent", sessionId,
+ (service) -> service.notifySmartspaceEventLocked(sessionId, event));
+ }
+
+ @Override
+ public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+ runForUserLocked("requestSmartspaceUpdate", sessionId,
+ (service) -> service.requestSmartspaceUpdateLocked(sessionId));
+ }
+
+ @Override
+ public void registerSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ runForUserLocked("registerSmartspaceUpdates", sessionId,
+ (service) -> service.registerSmartspaceUpdatesLocked(sessionId, callback));
+ }
+
+ @Override
+ public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+ ISmartspaceCallback callback) {
+ runForUserLocked("unregisterSmartspaceUpdates", sessionId,
+ (service) -> service.unregisterSmartspaceUpdatesLocked(sessionId, callback));
+ }
+
+ @Override
+ public void destroySmartspaceSession(@NonNull SmartspaceSessionId sessionId) {
+ runForUserLocked("destroySmartspaceSession", 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 SmartspaceManagerServiceShellCommand(SmartspaceManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private void runForUserLocked(@NonNull final String func,
+ @NonNull final SmartspaceSessionId sessionId,
+ @NonNull final Consumer<SmartspacePerUserService> 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 SmartspacePerUserService service = getServiceForUserLocked(userId);
+ c.accept(service);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
new file mode 100644
index 0000000..4143418e
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the SmartspaceManagerService.
+ */
+public class SmartspaceManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ SmartspaceManagerServiceShellCommand.class.getSimpleName();
+
+ private final SmartspaceManagerService mService;
+
+ public SmartspaceManagerServiceShellCommand(@NonNull SmartspaceManagerService 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("SmartspaceService temporarily reset. ");
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("SmartspaceService 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("SmartspaceManagerService 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("");
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
new file mode 100644
index 0000000..db43468
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+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.smartspace.ISmartspaceService;
+import android.service.smartspace.SmartspaceService;
+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 SmartspaceManagerService}.
+ */
+public class SmartspacePerUserService extends
+ AbstractPerUserSystemService<SmartspacePerUserService, SmartspaceManagerService>
+ implements RemoteSmartspaceService.RemoteSmartspaceServiceCallbacks {
+
+ private static final String TAG = SmartspacePerUserService.class.getSimpleName();
+ @GuardedBy("mLock")
+ private final ArrayMap<SmartspaceSessionId, SmartspaceSessionInfo> mSessionInfos =
+ new ArrayMap<>();
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteSmartspaceService 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;
+
+ protected SmartspacePerUserService(SmartspaceManagerService 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/177858728): 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 smartspace session.
+ */
+ @GuardedBy("mLock")
+ public void onCreateSmartspaceSessionLocked(@NonNull SmartspaceConfig smartspaceConfig,
+ @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.onCreateSmartspaceSession(smartspaceConfig, sessionId));
+
+ if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
+ final SmartspaceSessionInfo sessionInfo = new SmartspaceSessionInfo(
+ sessionId, smartspaceConfig, 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 smartspace event to the service.
+ */
+ @GuardedBy("mLock")
+ public void notifySmartspaceEventLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull SmartspaceTargetEvent event) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId, s -> s.notifySmartspaceEvent(sessionId, event));
+ }
+
+ /**
+ * Requests the service to return smartspace results of an input query.
+ */
+ @GuardedBy("mLock")
+ public void requestSmartspaceUpdateLocked(@NonNull SmartspaceSessionId sessionId) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId,
+ s -> s.requestSmartspaceUpdate(sessionId));
+ }
+
+ /**
+ * Registers a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void registerSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.registerSmartspaceUpdates(sessionId, callback));
+ if (serviceExists) {
+ sessionInfo.addCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void unregisterSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.unregisterSmartspaceUpdates(sessionId, callback));
+ if (serviceExists) {
+ sessionInfo.removeCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Notifies the service of the end of an existing smartspace session.
+ */
+ @GuardedBy("mLock")
+ public void onDestroyLocked(@NonNull SmartspaceSessionId sessionId) {
+ if (isDebug()) {
+ Slog.d(TAG, "onDestroyLocked(): sessionId=" + sessionId);
+ }
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId, s -> s.onDestroySmartspaceSession(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 smartspace 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(RemoteSmartspaceService 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 (SmartspaceSessionInfo sessionInfo : mSessionInfos.values()) {
+ sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ protected boolean resolveService(
+ @NonNull final SmartspaceSessionId sessionId,
+ @NonNull final AbstractRemoteService.AsyncRequest<ISmartspaceService> cb) {
+
+ final RemoteSmartspaceService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.executeOnResolvedService(cb);
+ }
+ return service != null;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteSmartspaceService 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 RemoteSmartspaceService(getContext(),
+ SmartspaceService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
+ mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+
+ private static final class SmartspaceSessionInfo {
+ private static final boolean DEBUG = false; // Do not submit with true
+ @NonNull
+ final IBinder mToken;
+ @NonNull
+ final IBinder.DeathRecipient mDeathRecipient;
+ @NonNull
+ private final SmartspaceSessionId mSessionId;
+ @NonNull
+ private final SmartspaceConfig mSmartspaceConfig;
+ private final RemoteCallbackList<ISmartspaceCallback> mCallbacks =
+ new RemoteCallbackList<ISmartspaceCallback>() {
+ @Override
+ public void onCallbackDied(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Binder died for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ if (mCallbacks.getRegisteredCallbackCount() == 0) {
+ destroy();
+ }
+ }
+ };
+
+ SmartspaceSessionInfo(
+ @NonNull final SmartspaceSessionId id,
+ @NonNull final SmartspaceConfig context,
+ @NonNull final IBinder token,
+ @NonNull final IBinder.DeathRecipient deathRecipient) {
+ if (DEBUG) {
+ Slog.d(TAG, "Creating SmartspaceSessionInfo for session Id=" + id);
+ }
+ mSessionId = id;
+ mSmartspaceConfig = context;
+ mToken = token;
+ mDeathRecipient = deathRecipient;
+ }
+
+ void addCallbackLocked(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Storing callback for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ mCallbacks.register(callback);
+ }
+
+ void removeCallbackLocked(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing callback for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ mCallbacks.unregister(callback);
+ }
+
+ 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(SmartspacePerUserService 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.onCreateSmartspaceSessionLocked(mSmartspaceConfig, mSessionId, token);
+ }
+ }
+}