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