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