diff options
author | 2021-01-29 13:02:19 -0800 | |
---|---|---|
committer | 2021-02-01 21:39:07 +0000 | |
commit | d33e8c387a586031d197335cc774317c77c01418 (patch) | |
tree | 08ceb297f540f79f2902aa98a5991a1dc24c516f | |
parent | 00e8383cebb0b33a0274372bf2644e8642d57874 (diff) |
SmartspaceManager initial implementation
Bug: 176851064
Test: Deployed on Phone,CTS tests
Change-Id: I3e03628d4d0e0aec0b6442f0ee8949ac378a1572
29 files changed, 3433 insertions, 4 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0a0f77e33ed4..fbfd3540beb5 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -138,6 +138,7 @@ package android { 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.search { } +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 @@ package android.content { 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.settings.suggestions { } +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 8b3cee403bca..58f6c52d2b05 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -6,9 +6,11 @@ ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions(): 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 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize(): 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 @@ MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId( +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.AudioRecordingMonitor#registerAudioRecordingCallb 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 @@ UserHandleName: android.app.search.SearchAction.Builder#setUserHandle(android.os 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 d5f01499793c..518feee2a125 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -34,6 +34,7 @@ import android.app.prediction.AppPredictionManager; 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 @@ public final class SystemServiceRegistry { } }); + 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 000000000000..df105f9db7c1 --- /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 000000000000..e7ec8891ea83 --- /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 000000000000..033cda1ba845 --- /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 000000000000..136b6f412b54 --- /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 000000000000..1f9cbb5ca33b --- /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 000000000000..ff5eb109dfe0 --- /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 000000000000..16def61239cf --- /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 000000000000..a864412edd65 --- /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 000000000000..5220c35d7064 --- /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 000000000000..3442cf294cbb --- /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 000000000000..ce5040eb0a3e --- /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 000000000000..e797a9b7e42c --- /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 000000000000..1e0653d67ace --- /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 0c50446e0a4e..c0e06352ab2e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4615,6 +4615,18 @@ public abstract class Context { 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 000000000000..c9c6807058af --- /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 000000000000..09b731091bfd --- /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 4a0a35de7c17..c4309266b249 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 5e0cda69911b..01b8efafa2fd 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 dfccdf4bd9a5..815330ff1851 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 f6bb72a46b87..f0cab1ea008f 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -28,6 +28,7 @@ filegroup { ":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 @@ java_library { "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 d28c3cc8e2b8..b8088c4697f0 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -341,6 +341,8 @@ public final class SystemServer implements Dumpable { "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 @@ public final class SystemServer implements Dumpable { 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 000000000000..fcf780d4d927 --- /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 000000000000..3b5a5a51536e --- /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 000000000000..169b85eb7e06 --- /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 000000000000..4143418e112e --- /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 000000000000..db4346830efb --- /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); + } + } +} |