1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.smartspace;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.AppGlobals;
22 import android.app.smartspace.ISmartspaceCallback;
23 import android.app.smartspace.SmartspaceConfig;
24 import android.app.smartspace.SmartspaceSessionId;
25 import android.app.smartspace.SmartspaceTargetEvent;
26 import android.content.ComponentName;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.pm.ServiceInfo;
30 import android.os.IBinder;
31 import android.os.RemoteCallbackList;
32 import android.os.RemoteException;
33 import android.service.smartspace.ISmartspaceService;
34 import android.service.smartspace.SmartspaceService;
35 import android.util.ArrayMap;
36 import android.util.Slog;
37 
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.infra.AbstractRemoteService;
40 import com.android.server.infra.AbstractPerUserSystemService;
41 
42 /**
43  * Per-user instance of {@link SmartspaceManagerService}.
44  */
45 public class SmartspacePerUserService extends
46         AbstractPerUserSystemService<SmartspacePerUserService, SmartspaceManagerService>
47         implements RemoteSmartspaceService.RemoteSmartspaceServiceCallbacks {
48 
49     private static final String TAG = SmartspacePerUserService.class.getSimpleName();
50     @GuardedBy("mLock")
51     private final ArrayMap<SmartspaceSessionId, SmartspaceSessionInfo> mSessionInfos =
52             new ArrayMap<>();
53     @Nullable
54     @GuardedBy("mLock")
55     private RemoteSmartspaceService mRemoteService;
56     /**
57      * When {@code true}, remote service died but service state is kept so it's restored after
58      * the system re-binds to it.
59      */
60     @GuardedBy("mLock")
61     private boolean mZombie;
62 
SmartspacePerUserService(SmartspaceManagerService master, Object lock, int userId)63     protected SmartspacePerUserService(SmartspaceManagerService master,
64             Object lock, int userId) {
65         super(master, lock, userId);
66     }
67 
68     @Override // from PerUserSystemService
newServiceInfoLocked(@onNull ComponentName serviceComponent)69     protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
70             throws NameNotFoundException {
71 
72         ServiceInfo si;
73         try {
74             si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
75                     PackageManager.GET_META_DATA, mUserId);
76         } catch (RemoteException e) {
77             throw new NameNotFoundException("Could not get service for " + serviceComponent);
78         }
79         // TODO(b/177858728): must check that either the service is from a system component,
80         // or it matches a service set by shell cmd (so it can be used on CTS tests and when
81         // OEMs are implementing the real service and also verify the proper permissions
82         return si;
83     }
84 
85     @GuardedBy("mLock")
86     @Override // from PerUserSystemService
updateLocked(boolean disabled)87     protected boolean updateLocked(boolean disabled) {
88         final boolean enabledChanged = super.updateLocked(disabled);
89         if (enabledChanged) {
90             if (isEnabledLocked()) {
91                 // Send the pending sessions over to the service
92                 resurrectSessionsLocked();
93             } else {
94                 // Clear the remote service for the next call
95                 updateRemoteServiceLocked();
96             }
97         }
98         return enabledChanged;
99     }
100 
101     /**
102      * Notifies the service of a new smartspace session.
103      */
104     @GuardedBy("mLock")
onCreateSmartspaceSessionLocked(@onNull SmartspaceConfig smartspaceConfig, @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token)105     public void onCreateSmartspaceSessionLocked(@NonNull SmartspaceConfig smartspaceConfig,
106             @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
107         final boolean serviceExists = resolveService(sessionId,
108                 s -> s.onCreateSmartspaceSession(smartspaceConfig, sessionId));
109 
110         if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
111             final SmartspaceSessionInfo sessionInfo = new SmartspaceSessionInfo(
112                     sessionId, smartspaceConfig, token, () -> {
113                 synchronized (mLock) {
114                     onDestroyLocked(sessionId);
115                 }
116             });
117             if (sessionInfo.linkToDeath()) {
118                 mSessionInfos.put(sessionId, sessionInfo);
119             } else {
120                 // destroy the session if calling process is already dead
121                 onDestroyLocked(sessionId);
122             }
123         }
124     }
125 
126     /**
127      * Records an smartspace event to the service.
128      */
129     @GuardedBy("mLock")
notifySmartspaceEventLocked(@onNull SmartspaceSessionId sessionId, @NonNull SmartspaceTargetEvent event)130     public void notifySmartspaceEventLocked(@NonNull SmartspaceSessionId sessionId,
131             @NonNull SmartspaceTargetEvent event) {
132         final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
133         if (sessionInfo == null) return;
134         resolveService(sessionId, s -> s.notifySmartspaceEvent(sessionId, event));
135     }
136 
137     /**
138      * Requests the service to return smartspace results of an input query.
139      */
140     @GuardedBy("mLock")
requestSmartspaceUpdateLocked(@onNull SmartspaceSessionId sessionId)141     public void requestSmartspaceUpdateLocked(@NonNull SmartspaceSessionId sessionId) {
142         final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
143         if (sessionInfo == null) return;
144         resolveService(sessionId,
145                 s -> s.requestSmartspaceUpdate(sessionId));
146     }
147 
148     /**
149      * Registers a callback for continuous updates of predicted apps or shortcuts.
150      */
151     @GuardedBy("mLock")
registerSmartspaceUpdatesLocked(@onNull SmartspaceSessionId sessionId, @NonNull ISmartspaceCallback callback)152     public void registerSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
153             @NonNull ISmartspaceCallback callback) {
154         final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
155         if (sessionInfo == null) return;
156         final boolean serviceExists = resolveService(sessionId,
157                 s -> s.registerSmartspaceUpdates(sessionId, callback));
158         if (serviceExists) {
159             sessionInfo.addCallbackLocked(callback);
160         }
161     }
162 
163     /**
164      * Unregisters a callback for continuous updates of predicted apps or shortcuts.
165      */
166     @GuardedBy("mLock")
unregisterSmartspaceUpdatesLocked(@onNull SmartspaceSessionId sessionId, @NonNull ISmartspaceCallback callback)167     public void unregisterSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
168             @NonNull ISmartspaceCallback callback) {
169         final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
170         if (sessionInfo == null) return;
171         final boolean serviceExists = resolveService(sessionId,
172                 s -> s.unregisterSmartspaceUpdates(sessionId, callback));
173         if (serviceExists) {
174             sessionInfo.removeCallbackLocked(callback);
175         }
176     }
177 
178     /**
179      * Notifies the service of the end of an existing smartspace session.
180      */
181     @GuardedBy("mLock")
onDestroyLocked(@onNull SmartspaceSessionId sessionId)182     public void onDestroyLocked(@NonNull SmartspaceSessionId sessionId) {
183         if (isDebug()) {
184             Slog.d(TAG, "onDestroyLocked(): sessionId=" + sessionId);
185         }
186         final SmartspaceSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
187         if (sessionInfo == null) return;
188         resolveService(sessionId, s -> s.onDestroySmartspaceSession(sessionId));
189         sessionInfo.destroy();
190     }
191 
192     @Override
onFailureOrTimeout(boolean timedOut)193     public void onFailureOrTimeout(boolean timedOut) {
194         if (isDebug()) {
195             Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
196         }
197         // Do nothing, we are just proxying to the smartspace ui service
198     }
199 
200     @Override
onConnectedStateChanged(boolean connected)201     public void onConnectedStateChanged(boolean connected) {
202         if (isDebug()) {
203             Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
204         }
205         if (connected) {
206             synchronized (mLock) {
207                 if (mZombie) {
208                     // Validation check - shouldn't happen
209                     if (mRemoteService == null) {
210                         Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
211                         return;
212                     }
213                     mZombie = false;
214                     resurrectSessionsLocked();
215                 }
216             }
217         }
218     }
219 
220     @Override
onServiceDied(RemoteSmartspaceService service)221     public void onServiceDied(RemoteSmartspaceService service) {
222         if (isDebug()) {
223             Slog.w(TAG, "onServiceDied(): service=" + service);
224         }
225         synchronized (mLock) {
226             mZombie = true;
227         }
228         updateRemoteServiceLocked();
229     }
230 
231     @GuardedBy("mLock")
updateRemoteServiceLocked()232     private void updateRemoteServiceLocked() {
233         if (mRemoteService != null) {
234             mRemoteService.destroy();
235             mRemoteService = null;
236         }
237     }
238 
onPackageUpdatedLocked()239     void onPackageUpdatedLocked() {
240         if (isDebug()) {
241             Slog.v(TAG, "onPackageUpdatedLocked()");
242         }
243         destroyAndRebindRemoteService();
244     }
245 
onPackageRestartedLocked()246     void onPackageRestartedLocked() {
247         if (isDebug()) {
248             Slog.v(TAG, "onPackageRestartedLocked()");
249         }
250         destroyAndRebindRemoteService();
251     }
252 
destroyAndRebindRemoteService()253     private void destroyAndRebindRemoteService() {
254         if (mRemoteService == null) {
255             return;
256         }
257 
258         if (isDebug()) {
259             Slog.d(TAG, "Destroying the old remote service.");
260         }
261         mRemoteService.destroy();
262         mRemoteService = null;
263 
264         synchronized (mLock) {
265             mZombie = true;
266         }
267         mRemoteService = getRemoteServiceLocked();
268         if (mRemoteService != null) {
269             if (isDebug()) {
270                 Slog.d(TAG, "Rebinding to the new remote service.");
271             }
272             mRemoteService.reconnect();
273         }
274     }
275 
276     /**
277      * Called after the remote service connected, it's used to restore state from a 'zombie'
278      * service (i.e., after it died).
279      */
resurrectSessionsLocked()280     private void resurrectSessionsLocked() {
281         final int numSessions = mSessionInfos.size();
282         if (isDebug()) {
283             Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
284                     + numSessions + " sessions.");
285         }
286 
287         for (SmartspaceSessionInfo sessionInfo : mSessionInfos.values()) {
288             sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
289         }
290     }
291 
292     @GuardedBy("mLock")
293     @Nullable
resolveService( @onNull final SmartspaceSessionId sessionId, @NonNull final AbstractRemoteService.AsyncRequest<ISmartspaceService> cb)294     protected boolean resolveService(
295             @NonNull final SmartspaceSessionId sessionId,
296             @NonNull final AbstractRemoteService.AsyncRequest<ISmartspaceService> cb) {
297 
298         final RemoteSmartspaceService service = getRemoteServiceLocked();
299         if (service != null) {
300             service.executeOnResolvedService(cb);
301         }
302         return service != null;
303     }
304 
305     @GuardedBy("mLock")
306     @Nullable
getRemoteServiceLocked()307     private RemoteSmartspaceService getRemoteServiceLocked() {
308         if (mRemoteService == null) {
309             final String serviceName = getComponentNameLocked();
310             if (serviceName == null) {
311                 if (mMaster.verbose) {
312                     Slog.v(TAG, "getRemoteServiceLocked(): not set");
313                 }
314                 return null;
315             }
316             ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
317 
318             mRemoteService = new RemoteSmartspaceService(getContext(),
319                     SmartspaceService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
320                     mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
321         }
322 
323         return mRemoteService;
324     }
325 
326     private static final class SmartspaceSessionInfo {
327         private static final boolean DEBUG = false;  // Do not submit with true
328         @NonNull
329         final IBinder mToken;
330         @NonNull
331         final IBinder.DeathRecipient mDeathRecipient;
332         @NonNull
333         private final SmartspaceSessionId mSessionId;
334         @NonNull
335         private final SmartspaceConfig mSmartspaceConfig;
336         private final RemoteCallbackList<ISmartspaceCallback> mCallbacks =
337                 new RemoteCallbackList<>();
338 
SmartspaceSessionInfo( @onNull final SmartspaceSessionId id, @NonNull final SmartspaceConfig context, @NonNull final IBinder token, @NonNull final IBinder.DeathRecipient deathRecipient)339         SmartspaceSessionInfo(
340                 @NonNull final SmartspaceSessionId id,
341                 @NonNull final SmartspaceConfig context,
342                 @NonNull final IBinder token,
343                 @NonNull final IBinder.DeathRecipient deathRecipient) {
344             if (DEBUG) {
345                 Slog.d(TAG, "Creating SmartspaceSessionInfo for session Id=" + id);
346             }
347             mSessionId = id;
348             mSmartspaceConfig = context;
349             mToken = token;
350             mDeathRecipient = deathRecipient;
351         }
352 
addCallbackLocked(ISmartspaceCallback callback)353         void addCallbackLocked(ISmartspaceCallback callback) {
354             if (DEBUG) {
355                 Slog.d(TAG, "Storing callback for session Id=" + mSessionId
356                         + " and callback=" + callback.asBinder());
357             }
358             mCallbacks.register(callback);
359         }
360 
removeCallbackLocked(ISmartspaceCallback callback)361         void removeCallbackLocked(ISmartspaceCallback callback) {
362             if (DEBUG) {
363                 Slog.d(TAG, "Removing callback for session Id=" + mSessionId
364                         + " and callback=" + callback.asBinder());
365             }
366             mCallbacks.unregister(callback);
367         }
368 
linkToDeath()369         boolean linkToDeath() {
370             try {
371                 mToken.linkToDeath(mDeathRecipient, 0);
372             } catch (RemoteException e) {
373                 if (DEBUG) {
374                     Slog.w(TAG, "Caller is dead before session can be started, sessionId: "
375                             + mSessionId);
376                 }
377                 return false;
378             }
379             return true;
380         }
381 
destroy()382         void destroy() {
383             if (DEBUG) {
384                 Slog.d(TAG, "Removing all callbacks for session Id=" + mSessionId
385                         + " and " + mCallbacks.getRegisteredCallbackCount() + " callbacks.");
386             }
387             if (mToken != null) {
388                 mToken.unlinkToDeath(mDeathRecipient, 0);
389             }
390             mCallbacks.kill();
391         }
392 
resurrectSessionLocked(SmartspacePerUserService service, IBinder token)393         void resurrectSessionLocked(SmartspacePerUserService service, IBinder token) {
394             int callbackCount = mCallbacks.getRegisteredCallbackCount();
395             if (DEBUG) {
396                 Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
397                         + ") for session Id=" + mSessionId + " and "
398                         + callbackCount + " callbacks.");
399             }
400             service.onCreateSmartspaceSessionLocked(mSmartspaceConfig, mSessionId, token);
401             mCallbacks.broadcast(
402                     callback -> service.registerSmartspaceUpdatesLocked(mSessionId, callback));
403         }
404     }
405 }
406