SmartspaceManager initial implementation

Bug: 176851064
Test: Deployed on Phone,CTS tests
Change-Id: I3e03628d4d0e0aec0b6442f0ee8949ac378a1572
This commit is contained in:
Shashwat Razdan
2021-01-29 13:02:19 -08:00
parent 00e8383ceb
commit d33e8c387a
29 changed files with 3433 additions and 4 deletions

View File

@@ -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 {

View File

@@ -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`

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;

View File

@@ -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];
}
};
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -4614,6 +4614,18 @@ public abstract class Context {
@SystemApi
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}.

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}

View File

@@ -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> -->

View File

@@ -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"

View File

@@ -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" />

View File

@@ -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",

View File

@@ -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);

View File

@@ -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"],
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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("");
}
}
}

View File

@@ -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);
}
}
}