SmartspaceManager initial implementation
Bug: 176851064
Test: Deployed on Phone,CTS tests
Change-Id: I3e03628d4d0e0aec0b6442f0ee8949ac378a1572
diff --git a/services/smartspace/Android.bp b/services/smartspace/Android.bp
new file mode 100644
index 0000000..fcf780d
--- /dev/null
+++ b/services/smartspace/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+ name: "services.smartspace-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.smartspace",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.smartspace-sources"],
+ libs: ["services.core"],
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
new file mode 100644
index 0000000..3b5a5a5
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.smartspace;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.smartspace.ISmartspaceService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the {@link android.service.smartspace.SmartspaceService} implementation in another
+ * process.
+ */
+public class RemoteSmartspaceService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteSmartspaceService,
+ ISmartspaceService> {
+
+ private static final String TAG = "RemoteSmartspaceService";
+
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+ private final RemoteSmartspaceServiceCallbacks mCallback;
+
+ public RemoteSmartspaceService(Context context, String serviceInterface,
+ ComponentName componentName, int userId,
+ RemoteSmartspaceServiceCallbacks callback, boolean bindInstantServiceAllowed,
+ boolean verbose) {
+ super(context, serviceInterface, componentName, userId, callback,
+ context.getMainThreadHandler(),
+ bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+ verbose, /* initialCapacity= */ 1);
+ mCallback = callback;
+ }
+
+ @Override
+ protected ISmartspaceService getServiceInterface(IBinder service) {
+ return ISmartspaceService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getTimeoutIdleBindMillis() {
+ return PERMANENT_BOUND_TIMEOUT_MS;
+ }
+
+ @Override
+ protected long getRemoteRequestMillis() {
+ return TIMEOUT_REMOTE_REQUEST_MILLIS;
+ }
+
+ /**
+ * Schedules a request to bind to the remote service.
+ */
+ public void reconnect() {
+ super.scheduleBind();
+ }
+
+ /**
+ * Schedule async request on remote service.
+ */
+ public void scheduleOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+ scheduleAsyncRequest(request);
+ }
+
+ /**
+ * Execute async request on remote service immediately instead of sending it to Handler queue.
+ */
+ public void executeOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+ executeAsyncRequest(request);
+ }
+
+ /**
+ * Failure callback
+ */
+ public interface RemoteSmartspaceServiceCallbacks
+ extends VultureCallback<RemoteSmartspaceService> {
+
+ /**
+ * Notifies a the failure or timeout of a remote call.
+ */
+ void onFailureOrTimeout(boolean timedOut);
+
+ /**
+ * Notifies change in connected state of the remote service.
+ */
+ void onConnectedStateChanged(boolean connected);
+ }
+
+ @Override // from AbstractRemoteService
+ protected void handleOnConnectedStateChanged(boolean connected) {
+ if (mCallback != null) {
+ mCallback.onConnectedStateChanged(connected);
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
new file mode 100644
index 0000000..169b85e
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.smartspace;
+
+import static android.Manifest.permission.MANAGE_SMARTSPACE;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.content.Context.SMARTSPACE_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.ISmartspaceManager;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to return smartspace targets given a query.
+ */
+public class SmartspaceManagerService extends
+ AbstractMasterSystemService<SmartspaceManagerService, SmartspacePerUserService> {
+
+ private static final String TAG = SmartspaceManagerService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+ private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+ public SmartspaceManagerService(Context context) {
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultSmartspaceService), null,
+ PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+ }
+
+ @Override
+ protected SmartspacePerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
+ return new SmartspacePerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(SMARTSPACE_SERVICE, new SmartspaceManagerStub());
+ }
+
+ @Override
+ protected void enforceCallingPermissionForManagement() {
+ getContext().enforceCallingPermission(MANAGE_SMARTSPACE, TAG);
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+ final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageUpdatedLocked();
+ }
+ }
+
+ @Override // from AbstractMasterSystemService
+ protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+ final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ service.onPackageRestartedLocked();
+ }
+ }
+
+ @Override
+ protected int getMaximumTemporaryServiceDurationMs() {
+ return MAX_TEMP_SERVICE_DURATION_MS;
+ }
+
+ private class SmartspaceManagerStub extends ISmartspaceManager.Stub {
+
+ @Override
+ public void createSmartspaceSession(@NonNull SmartspaceConfig smartspaceConfig,
+ @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+ runForUserLocked("createSmartspaceSession", sessionId, (service) ->
+ service.onCreateSmartspaceSessionLocked(smartspaceConfig, sessionId, token));
+ }
+
+ @Override
+ public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+ SmartspaceTargetEvent event) {
+ runForUserLocked("notifySmartspaceEvent", sessionId,
+ (service) -> service.notifySmartspaceEventLocked(sessionId, event));
+ }
+
+ @Override
+ public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+ runForUserLocked("requestSmartspaceUpdate", sessionId,
+ (service) -> service.requestSmartspaceUpdateLocked(sessionId));
+ }
+
+ @Override
+ public void registerSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ runForUserLocked("registerSmartspaceUpdates", sessionId,
+ (service) -> service.registerSmartspaceUpdatesLocked(sessionId, callback));
+ }
+
+ @Override
+ public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+ ISmartspaceCallback callback) {
+ runForUserLocked("unregisterSmartspaceUpdates", sessionId,
+ (service) -> service.unregisterSmartspaceUpdatesLocked(sessionId, callback));
+ }
+
+ @Override
+ public void destroySmartspaceSession(@NonNull SmartspaceSessionId sessionId) {
+ runForUserLocked("destroySmartspaceSession", sessionId,
+ (service) -> service.onDestroyLocked(sessionId));
+ }
+
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
+ new SmartspaceManagerServiceShellCommand(SmartspaceManagerService.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private void runForUserLocked(@NonNull final String func,
+ @NonNull final SmartspaceSessionId sessionId,
+ @NonNull final Consumer<SmartspacePerUserService> c) {
+ ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
+ final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ sessionId.getUserId(), false, ALLOW_NON_FULL, null, null);
+
+ if (DEBUG) {
+ Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ }
+ if (!(mServiceNameResolver.isTemporary(userId)
+ || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+
+ String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid();
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ final SmartspacePerUserService service = getServiceForUserLocked(userId);
+ c.accept(service);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
new file mode 100644
index 0000000..4143418e
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.smartspace;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the SmartspaceManagerService.
+ */
+public class SmartspaceManagerServiceShellCommand extends ShellCommand {
+
+ private static final String TAG =
+ SmartspaceManagerServiceShellCommand.class.getSimpleName();
+
+ private final SmartspaceManagerService mService;
+
+ public SmartspaceManagerServiceShellCommand(@NonNull SmartspaceManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "set": {
+ final String what = getNextArgRequired();
+ switch (what) {
+ case "temporary-service": {
+ final int userId = Integer.parseInt(getNextArgRequired());
+ String serviceName = getNextArg();
+ if (serviceName == null) {
+ mService.resetTemporaryService(userId);
+ pw.println("SmartspaceService temporarily reset. ");
+ return 0;
+ }
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryService(userId, serviceName, duration);
+ pw.println("SmartspaceService temporarily set to " + serviceName
+ + " for " + duration + "ms");
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter()) {
+ pw.println("SmartspaceManagerService commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implemtation.");
+ pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println("");
+ }
+ }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
new file mode 100644
index 0000000..db43468
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.service.smartspace.ISmartspaceService;
+import android.service.smartspace.SmartspaceService;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link SmartspaceManagerService}.
+ */
+public class SmartspacePerUserService extends
+ AbstractPerUserSystemService<SmartspacePerUserService, SmartspaceManagerService>
+ implements RemoteSmartspaceService.RemoteSmartspaceServiceCallbacks {
+
+ private static final String TAG = SmartspacePerUserService.class.getSimpleName();
+ @GuardedBy("mLock")
+ private final ArrayMap<SmartspaceSessionId, SmartspaceSessionInfo> mSessionInfos =
+ new ArrayMap<>();
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteSmartspaceService mRemoteService;
+ /**
+ * When {@code true}, remote service died but service state is kept so it's restored after
+ * the system re-binds to it.
+ */
+ @GuardedBy("mLock")
+ private boolean mZombie;
+
+ protected SmartspacePerUserService(SmartspaceManagerService master,
+ Object lock, int userId) {
+ super(master, lock, userId);
+ }
+
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws NameNotFoundException {
+
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new NameNotFoundException("Could not get service for " + serviceComponent);
+ }
+ // TODO(b/177858728): must check that either the service is from a system component,
+ // or it matches a service set by shell cmd (so it can be used on CTS tests and when
+ // OEMs are implementing the real service and also verify the proper permissions
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Override // from PerUserSystemService
+ protected boolean updateLocked(boolean disabled) {
+ final boolean enabledChanged = super.updateLocked(disabled);
+ if (enabledChanged) {
+ if (!isEnabledLocked()) {
+ // Clear the remote service for the next call
+ updateRemoteServiceLocked();
+ }
+ }
+ return enabledChanged;
+ }
+
+ /**
+ * Notifies the service of a new smartspace session.
+ */
+ @GuardedBy("mLock")
+ public void onCreateSmartspaceSessionLocked(@NonNull SmartspaceConfig smartspaceConfig,
+ @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.onCreateSmartspaceSession(smartspaceConfig, sessionId));
+
+ if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
+ final SmartspaceSessionInfo sessionInfo = new SmartspaceSessionInfo(
+ sessionId, smartspaceConfig, token, () -> {
+ synchronized (mLock) {
+ onDestroyLocked(sessionId);
+ }
+ });
+ if (sessionInfo.linkToDeath()) {
+ mSessionInfos.put(sessionId, sessionInfo);
+ } else {
+ // destroy the session if calling process is already dead
+ onDestroyLocked(sessionId);
+ }
+ }
+ }
+
+ /**
+ * Records an smartspace event to the service.
+ */
+ @GuardedBy("mLock")
+ public void notifySmartspaceEventLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull SmartspaceTargetEvent event) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId, s -> s.notifySmartspaceEvent(sessionId, event));
+ }
+
+ /**
+ * Requests the service to return smartspace results of an input query.
+ */
+ @GuardedBy("mLock")
+ public void requestSmartspaceUpdateLocked(@NonNull SmartspaceSessionId sessionId) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId,
+ s -> s.requestSmartspaceUpdate(sessionId));
+ }
+
+ /**
+ * Registers a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void registerSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.registerSmartspaceUpdates(sessionId, callback));
+ if (serviceExists) {
+ sessionInfo.addCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+ */
+ @GuardedBy("mLock")
+ public void unregisterSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+ @NonNull ISmartspaceCallback callback) {
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+ if (sessionInfo == null) return;
+ final boolean serviceExists = resolveService(sessionId,
+ s -> s.unregisterSmartspaceUpdates(sessionId, callback));
+ if (serviceExists) {
+ sessionInfo.removeCallbackLocked(callback);
+ }
+ }
+
+ /**
+ * Notifies the service of the end of an existing smartspace session.
+ */
+ @GuardedBy("mLock")
+ public void onDestroyLocked(@NonNull SmartspaceSessionId sessionId) {
+ if (isDebug()) {
+ Slog.d(TAG, "onDestroyLocked(): sessionId=" + sessionId);
+ }
+ final SmartspaceSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
+ if (sessionInfo == null) return;
+ resolveService(sessionId, s -> s.onDestroySmartspaceSession(sessionId));
+ sessionInfo.destroy();
+ }
+
+ @Override
+ public void onFailureOrTimeout(boolean timedOut) {
+ if (isDebug()) {
+ Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
+ }
+ // Do nothing, we are just proxying to the smartspace ui service
+ }
+
+ @Override
+ public void onConnectedStateChanged(boolean connected) {
+ if (isDebug()) {
+ Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
+ }
+ if (connected) {
+ synchronized (mLock) {
+ if (mZombie) {
+ // Validation check - shouldn't happen
+ if (mRemoteService == null) {
+ Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
+ return;
+ }
+ mZombie = false;
+ resurrectSessionsLocked();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDied(RemoteSmartspaceService service) {
+ if (isDebug()) {
+ Slog.w(TAG, "onServiceDied(): service=" + service);
+ }
+ synchronized (mLock) {
+ mZombie = true;
+ }
+ updateRemoteServiceLocked();
+ }
+
+ @GuardedBy("mLock")
+ private void updateRemoteServiceLocked() {
+ if (mRemoteService != null) {
+ mRemoteService.destroy();
+ mRemoteService = null;
+ }
+ }
+
+ void onPackageUpdatedLocked() {
+ if (isDebug()) {
+ Slog.v(TAG, "onPackageUpdatedLocked()");
+ }
+ destroyAndRebindRemoteService();
+ }
+
+ void onPackageRestartedLocked() {
+ if (isDebug()) {
+ Slog.v(TAG, "onPackageRestartedLocked()");
+ }
+ destroyAndRebindRemoteService();
+ }
+
+ private void destroyAndRebindRemoteService() {
+ if (mRemoteService == null) {
+ return;
+ }
+
+ if (isDebug()) {
+ Slog.d(TAG, "Destroying the old remote service.");
+ }
+ mRemoteService.destroy();
+ mRemoteService = null;
+
+ synchronized (mLock) {
+ mZombie = true;
+ }
+ mRemoteService = getRemoteServiceLocked();
+ if (mRemoteService != null) {
+ if (isDebug()) {
+ Slog.d(TAG, "Rebinding to the new remote service.");
+ }
+ mRemoteService.reconnect();
+ }
+ }
+
+ /**
+ * Called after the remote service connected, it's used to restore state from a 'zombie'
+ * service (i.e., after it died).
+ */
+ private void resurrectSessionsLocked() {
+ final int numSessions = mSessionInfos.size();
+ if (isDebug()) {
+ Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
+ + numSessions + " sessions.");
+ }
+
+ for (SmartspaceSessionInfo sessionInfo : mSessionInfos.values()) {
+ sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
+ }
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ protected boolean resolveService(
+ @NonNull final SmartspaceSessionId sessionId,
+ @NonNull final AbstractRemoteService.AsyncRequest<ISmartspaceService> cb) {
+
+ final RemoteSmartspaceService service = getRemoteServiceLocked();
+ if (service != null) {
+ service.executeOnResolvedService(cb);
+ }
+ return service != null;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteSmartspaceService getRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "getRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteSmartspaceService(getContext(),
+ SmartspaceService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
+ mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+
+ private static final class SmartspaceSessionInfo {
+ private static final boolean DEBUG = false; // Do not submit with true
+ @NonNull
+ final IBinder mToken;
+ @NonNull
+ final IBinder.DeathRecipient mDeathRecipient;
+ @NonNull
+ private final SmartspaceSessionId mSessionId;
+ @NonNull
+ private final SmartspaceConfig mSmartspaceConfig;
+ private final RemoteCallbackList<ISmartspaceCallback> mCallbacks =
+ new RemoteCallbackList<ISmartspaceCallback>() {
+ @Override
+ public void onCallbackDied(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Binder died for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ if (mCallbacks.getRegisteredCallbackCount() == 0) {
+ destroy();
+ }
+ }
+ };
+
+ SmartspaceSessionInfo(
+ @NonNull final SmartspaceSessionId id,
+ @NonNull final SmartspaceConfig context,
+ @NonNull final IBinder token,
+ @NonNull final IBinder.DeathRecipient deathRecipient) {
+ if (DEBUG) {
+ Slog.d(TAG, "Creating SmartspaceSessionInfo for session Id=" + id);
+ }
+ mSessionId = id;
+ mSmartspaceConfig = context;
+ mToken = token;
+ mDeathRecipient = deathRecipient;
+ }
+
+ void addCallbackLocked(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Storing callback for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ mCallbacks.register(callback);
+ }
+
+ void removeCallbackLocked(ISmartspaceCallback callback) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing callback for session Id=" + mSessionId
+ + " and callback=" + callback.asBinder());
+ }
+ mCallbacks.unregister(callback);
+ }
+
+ boolean linkToDeath() {
+ try {
+ mToken.linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Caller is dead before session can be started, sessionId: "
+ + mSessionId);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ void destroy() {
+ if (DEBUG) {
+ Slog.d(TAG, "Removing all callbacks for session Id=" + mSessionId
+ + " and " + mCallbacks.getRegisteredCallbackCount() + " callbacks.");
+ }
+ if (mToken != null) {
+ mToken.unlinkToDeath(mDeathRecipient, 0);
+ }
+ mCallbacks.kill();
+ }
+
+ void resurrectSessionLocked(SmartspacePerUserService service, IBinder token) {
+ int callbackCount = mCallbacks.getRegisteredCallbackCount();
+ if (DEBUG) {
+ Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
+ + ") for session Id=" + mSessionId + " and "
+ + callbackCount + " callbacks.");
+ }
+ service.onCreateSmartspaceSessionLocked(mSmartspaceConfig, mSessionId, token);
+ }
+ }
+}