1 /* 2 * Copyright (C) 2019 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.people; 18 19 import android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.UserIdInt; 23 import android.app.ActivityManager; 24 import android.app.people.ConversationChannel; 25 import android.app.people.ConversationStatus; 26 import android.app.people.IConversationListener; 27 import android.app.people.IPeopleManager; 28 import android.app.prediction.AppPredictionContext; 29 import android.app.prediction.AppPredictionSessionId; 30 import android.app.prediction.AppTarget; 31 import android.app.prediction.AppTargetEvent; 32 import android.app.prediction.IPredictionCallback; 33 import android.content.Context; 34 import android.content.pm.PackageManager; 35 import android.content.pm.PackageManagerInternal; 36 import android.content.pm.ParceledListSlice; 37 import android.content.pm.ShortcutInfo; 38 import android.os.Binder; 39 import android.os.CancellationSignal; 40 import android.os.IBinder; 41 import android.os.IRemoteCallback; 42 import android.os.Process; 43 import android.os.RemoteCallbackList; 44 import android.os.RemoteException; 45 import android.os.UserHandle; 46 import android.util.ArrayMap; 47 import android.util.Slog; 48 49 import com.android.internal.annotations.VisibleForTesting; 50 import com.android.server.LocalServices; 51 import com.android.server.SystemService; 52 import com.android.server.people.data.DataManager; 53 54 import java.util.HashMap; 55 import java.util.List; 56 import java.util.Map; 57 import java.util.Objects; 58 import java.util.function.Consumer; 59 60 /** 61 * A service that manages the people and conversations provided by apps. 62 */ 63 public class PeopleService extends SystemService { 64 65 private static final String TAG = "PeopleService"; 66 67 private DataManager mLazyDataManager; 68 private ConversationListenerHelper mLazyConversationListenerHelper; 69 70 private PackageManagerInternal mPackageManagerInternal; 71 72 /** 73 * Initializes the system service. 74 * 75 * @param context The system server context. 76 */ PeopleService(Context context)77 public PeopleService(Context context) { 78 super(context); 79 } 80 81 @VisibleForTesting getConversationListenerHelper()82 ConversationListenerHelper getConversationListenerHelper() { 83 if (mLazyConversationListenerHelper == null) { 84 initLazyStuff(); 85 } 86 return mLazyConversationListenerHelper; 87 } 88 initLazyStuff()89 private synchronized void initLazyStuff() { 90 if (mLazyDataManager == null) { 91 mLazyDataManager = new DataManager(getContext()); 92 mLazyDataManager.initialize(); 93 mLazyConversationListenerHelper = new ConversationListenerHelper(); 94 mLazyDataManager.addConversationsListener(mLazyConversationListenerHelper); 95 } 96 } 97 getDataManager()98 private DataManager getDataManager() { 99 if (mLazyDataManager == null) { 100 initLazyStuff(); 101 } 102 return mLazyDataManager; 103 } 104 105 @Override onStart()106 public void onStart() { 107 onStart(/* isForTesting= */ false); 108 } 109 110 @VisibleForTesting onStart(boolean isForTesting)111 protected void onStart(boolean isForTesting) { 112 if (!isForTesting) { 113 publishBinderService(Context.PEOPLE_SERVICE, mService); 114 } 115 publishLocalService(PeopleServiceInternal.class, new LocalService()); 116 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); 117 } 118 119 @Override onUserUnlocked(@onNull TargetUser user)120 public void onUserUnlocked(@NonNull TargetUser user) { 121 getDataManager().onUserUnlocked(user.getUserIdentifier()); 122 } 123 124 @Override onUserStopping(@onNull TargetUser user)125 public void onUserStopping(@NonNull TargetUser user) { 126 getDataManager().onUserStopping(user.getUserIdentifier()); 127 } 128 129 /** 130 * Enforces that only the system or root UID can make certain calls. 131 * 132 * @param message used as message if SecurityException is thrown 133 * @throws SecurityException if the caller is not system or root 134 */ enforceSystemOrRoot(String message)135 private static void enforceSystemOrRoot(String message) { 136 if (!isSystemOrRoot()) { 137 throw new SecurityException("Only system may " + message); 138 } 139 } 140 isSystemOrRoot()141 private static boolean isSystemOrRoot() { 142 final int uid = Binder.getCallingUid(); 143 return UserHandle.isSameApp(uid, Process.SYSTEM_UID) || uid == Process.ROOT_UID; 144 } 145 handleIncomingUser(int userId)146 private int handleIncomingUser(int userId) { 147 try { 148 return ActivityManager.getService().handleIncomingUser( 149 Binder.getCallingPid(), Binder.getCallingUid(), userId, true, true, "", null); 150 } catch (RemoteException re) { 151 // Shouldn't happen, local. 152 } 153 return userId; 154 } 155 checkCallerIsSameApp(String pkg)156 private void checkCallerIsSameApp(String pkg) { 157 final int callingUid = Binder.getCallingUid(); 158 final int callingUserId = UserHandle.getUserId(callingUid); 159 160 if (mPackageManagerInternal.getPackageUid(pkg, /*flags=*/ 0, 161 callingUserId) != callingUid) { 162 throw new SecurityException("Calling uid " + callingUid + " cannot query events" 163 + "for package " + pkg); 164 } 165 } 166 167 /** 168 * Enforces that only the system, root UID or SystemUI can make certain calls. 169 * 170 * @param message used as message if SecurityException is thrown 171 * @throws SecurityException if the caller is not system or root 172 */ 173 @VisibleForTesting enforceSystemRootOrSystemUI(Context context, String message)174 protected void enforceSystemRootOrSystemUI(Context context, String message) { 175 if (isSystemOrRoot()) return; 176 context.enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE, 177 message); 178 } 179 180 @VisibleForTesting 181 final IBinder mService = new IPeopleManager.Stub() { 182 183 @Override 184 public ConversationChannel getConversation( 185 String packageName, int userId, String shortcutId) { 186 enforceSystemRootOrSystemUI(getContext(), "get conversation"); 187 return getDataManager().getConversation(packageName, userId, shortcutId); 188 } 189 190 @Override 191 public ParceledListSlice<ConversationChannel> getRecentConversations() { 192 enforceSystemRootOrSystemUI(getContext(), "get recent conversations"); 193 return new ParceledListSlice<>( 194 getDataManager().getRecentConversations( 195 Binder.getCallingUserHandle().getIdentifier())); 196 } 197 198 @Override 199 public void removeRecentConversation(String packageName, int userId, String shortcutId) { 200 enforceSystemOrRoot("remove a recent conversation"); 201 getDataManager().removeRecentConversation(packageName, userId, shortcutId, 202 Binder.getCallingUserHandle().getIdentifier()); 203 } 204 205 @Override 206 public void removeAllRecentConversations() { 207 enforceSystemOrRoot("remove all recent conversations"); 208 getDataManager().removeAllRecentConversations( 209 Binder.getCallingUserHandle().getIdentifier()); 210 } 211 212 @Override 213 public boolean isConversation(String packageName, int userId, String shortcutId) { 214 enforceHasReadPeopleDataPermission(); 215 handleIncomingUser(userId); 216 return getDataManager().isConversation(packageName, userId, shortcutId); 217 } 218 219 private void enforceHasReadPeopleDataPermission() throws SecurityException { 220 if (getContext().checkCallingPermission(Manifest.permission.READ_PEOPLE_DATA) 221 != PackageManager.PERMISSION_GRANTED) { 222 throw new SecurityException("Caller doesn't have READ_PEOPLE_DATA permission."); 223 } 224 } 225 226 @Override 227 public long getLastInteraction(String packageName, int userId, String shortcutId) { 228 enforceSystemRootOrSystemUI(getContext(), "get last interaction"); 229 return getDataManager().getLastInteraction(packageName, userId, shortcutId); 230 } 231 232 @Override 233 public void addOrUpdateStatus(String packageName, int userId, String conversationId, 234 ConversationStatus status) { 235 handleIncomingUser(userId); 236 checkCallerIsSameApp(packageName); 237 if (status.getStartTimeMillis() > System.currentTimeMillis()) { 238 throw new IllegalArgumentException("Start time must be in the past"); 239 } 240 getDataManager().addOrUpdateStatus(packageName, userId, conversationId, status); 241 } 242 243 @Override 244 public void clearStatus(String packageName, int userId, String conversationId, 245 String statusId) { 246 handleIncomingUser(userId); 247 checkCallerIsSameApp(packageName); 248 getDataManager().clearStatus(packageName, userId, conversationId, statusId); 249 } 250 251 @Override 252 public void clearStatuses(String packageName, int userId, String conversationId) { 253 handleIncomingUser(userId); 254 checkCallerIsSameApp(packageName); 255 getDataManager().clearStatuses(packageName, userId, conversationId); 256 } 257 258 @Override 259 public ParceledListSlice<ConversationStatus> getStatuses(String packageName, int userId, 260 String conversationId) { 261 handleIncomingUser(userId); 262 if (!isSystemOrRoot()) { 263 checkCallerIsSameApp(packageName); 264 } 265 return new ParceledListSlice<>( 266 getDataManager().getStatuses(packageName, userId, conversationId)); 267 } 268 269 @Override 270 public void registerConversationListener( 271 String packageName, int userId, String shortcutId, IConversationListener listener) { 272 enforceSystemRootOrSystemUI(getContext(), "register conversation listener"); 273 getConversationListenerHelper().addConversationListener( 274 new ListenerKey(packageName, userId, shortcutId), listener); 275 } 276 277 @Override 278 public void unregisterConversationListener(IConversationListener listener) { 279 enforceSystemRootOrSystemUI(getContext(), "unregister conversation listener"); 280 getConversationListenerHelper().removeConversationListener(listener); 281 } 282 }; 283 284 /** 285 * Listeners for conversation changes. 286 * 287 * @hide 288 */ 289 public interface ConversationsListener { 290 /** 291 * Triggers with the list of modified conversations from {@link DataManager} for dispatching 292 * relevant updates to clients. 293 * 294 * @param conversations The conversations with modified data 295 * @see IPeopleManager#registerConversationListener(String, int, String, 296 * android.app.people.ConversationListener) 297 */ onConversationsUpdate(@onNull List<ConversationChannel> conversations)298 default void onConversationsUpdate(@NonNull List<ConversationChannel> conversations) { 299 } 300 } 301 302 /** 303 * Implements {@code ConversationListenerHelper} to dispatch conversation updates to registered 304 * clients. 305 */ 306 public static class ConversationListenerHelper implements ConversationsListener { 307 ConversationListenerHelper()308 ConversationListenerHelper() { 309 } 310 311 @VisibleForTesting 312 final RemoteCallbackList<IConversationListener> mListeners = 313 new RemoteCallbackList<>(); 314 315 /** Adds {@code listener} with {@code key} associated. */ addConversationListener(ListenerKey key, IConversationListener listener)316 public synchronized void addConversationListener(ListenerKey key, 317 IConversationListener listener) { 318 mListeners.unregister(listener); 319 mListeners.register(listener, key); 320 } 321 322 /** Removes {@code listener}. */ removeConversationListener( IConversationListener listener)323 public synchronized void removeConversationListener( 324 IConversationListener listener) { 325 mListeners.unregister(listener); 326 } 327 328 @Override 329 /** Dispatches updates to {@code mListeners} with keys mapped to {@code conversations}. */ onConversationsUpdate(List<ConversationChannel> conversations)330 public void onConversationsUpdate(List<ConversationChannel> conversations) { 331 int count = mListeners.beginBroadcast(); 332 // Early opt-out if no listeners are registered. 333 if (count == 0) { 334 return; 335 } 336 Map<ListenerKey, ConversationChannel> keyedConversations = new HashMap<>(); 337 for (ConversationChannel conversation : conversations) { 338 keyedConversations.put(getListenerKey(conversation), conversation); 339 } 340 for (int i = 0; i < count; i++) { 341 final ListenerKey listenerKey = (ListenerKey) mListeners.getBroadcastCookie(i); 342 if (!keyedConversations.containsKey(listenerKey)) { 343 continue; 344 } 345 final IConversationListener listener = mListeners.getBroadcastItem(i); 346 try { 347 ConversationChannel channel = keyedConversations.get(listenerKey); 348 listener.onConversationUpdate(channel); 349 } catch (RemoteException e) { 350 // The RemoteCallbackList will take care of removing the dead object. 351 } 352 } 353 mListeners.finishBroadcast(); 354 } 355 getListenerKey(ConversationChannel conversation)356 private ListenerKey getListenerKey(ConversationChannel conversation) { 357 ShortcutInfo info = conversation.getShortcutInfo(); 358 return new ListenerKey(info.getPackage(), info.getUserId(), 359 info.getId()); 360 } 361 } 362 363 private static class ListenerKey { 364 private final String mPackageName; 365 private final Integer mUserId; 366 private final String mShortcutId; 367 ListenerKey(String packageName, Integer userId, String shortcutId)368 ListenerKey(String packageName, Integer userId, String shortcutId) { 369 this.mPackageName = packageName; 370 this.mUserId = userId; 371 this.mShortcutId = shortcutId; 372 } 373 getPackageName()374 public String getPackageName() { 375 return mPackageName; 376 } 377 getUserId()378 public Integer getUserId() { 379 return mUserId; 380 } 381 getShortcutId()382 public String getShortcutId() { 383 return mShortcutId; 384 } 385 386 @Override equals(Object o)387 public boolean equals(Object o) { 388 ListenerKey key = (ListenerKey) o; 389 return key.getPackageName().equals(mPackageName) 390 && Objects.equals(key.getUserId(), mUserId) 391 && key.getShortcutId().equals(mShortcutId); 392 } 393 394 @Override hashCode()395 public int hashCode() { 396 return mPackageName.hashCode() + mUserId.hashCode() + mShortcutId.hashCode(); 397 } 398 } 399 400 @VisibleForTesting 401 final class LocalService extends PeopleServiceInternal { 402 403 private Map<AppPredictionSessionId, SessionInfo> mSessions = new ArrayMap<>(); 404 405 @Override onCreatePredictionSession(AppPredictionContext appPredictionContext, AppPredictionSessionId sessionId)406 public void onCreatePredictionSession(AppPredictionContext appPredictionContext, 407 AppPredictionSessionId sessionId) { 408 mSessions.put(sessionId, 409 new SessionInfo(appPredictionContext, getDataManager(), sessionId.getUserId(), 410 getContext())); 411 } 412 413 @Override notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event)414 public void notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) { 415 runForSession(sessionId, 416 sessionInfo -> sessionInfo.getPredictor().onAppTargetEvent(event)); 417 } 418 419 @Override notifyLaunchLocationShown(AppPredictionSessionId sessionId, String launchLocation, ParceledListSlice targetIds)420 public void notifyLaunchLocationShown(AppPredictionSessionId sessionId, 421 String launchLocation, ParceledListSlice targetIds) { 422 runForSession(sessionId, 423 sessionInfo -> sessionInfo.getPredictor().onLaunchLocationShown( 424 launchLocation, targetIds.getList())); 425 } 426 427 @Override sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets, IPredictionCallback callback)428 public void sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets, 429 IPredictionCallback callback) { 430 runForSession(sessionId, 431 sessionInfo -> sessionInfo.getPredictor().onSortAppTargets( 432 targets.getList(), 433 targetList -> invokePredictionCallback(callback, targetList))); 434 } 435 436 @Override registerPredictionUpdates(AppPredictionSessionId sessionId, IPredictionCallback callback)437 public void registerPredictionUpdates(AppPredictionSessionId sessionId, 438 IPredictionCallback callback) { 439 runForSession(sessionId, sessionInfo -> sessionInfo.addCallback(callback)); 440 } 441 442 @Override unregisterPredictionUpdates(AppPredictionSessionId sessionId, IPredictionCallback callback)443 public void unregisterPredictionUpdates(AppPredictionSessionId sessionId, 444 IPredictionCallback callback) { 445 runForSession(sessionId, sessionInfo -> sessionInfo.removeCallback(callback)); 446 } 447 448 @Override requestPredictionUpdate(AppPredictionSessionId sessionId)449 public void requestPredictionUpdate(AppPredictionSessionId sessionId) { 450 runForSession(sessionId, 451 sessionInfo -> sessionInfo.getPredictor().onRequestPredictionUpdate()); 452 } 453 454 @Override onDestroyPredictionSession(AppPredictionSessionId sessionId)455 public void onDestroyPredictionSession(AppPredictionSessionId sessionId) { 456 runForSession(sessionId, sessionInfo -> { 457 sessionInfo.onDestroy(); 458 mSessions.remove(sessionId); 459 }); 460 } 461 462 @Override pruneDataForUser(@serIdInt int userId, @NonNull CancellationSignal signal)463 public void pruneDataForUser(@UserIdInt int userId, @NonNull CancellationSignal signal) { 464 getDataManager().pruneDataForUser(userId, signal); 465 } 466 467 @Nullable 468 @Override getBackupPayload(@serIdInt int userId)469 public byte[] getBackupPayload(@UserIdInt int userId) { 470 return getDataManager().getBackupPayload(userId); 471 } 472 473 @Override restore(@serIdInt int userId, @NonNull byte[] payload)474 public void restore(@UserIdInt int userId, @NonNull byte[] payload) { 475 getDataManager().restore(userId, payload); 476 } 477 478 @Override requestServiceFeatures(AppPredictionSessionId sessionId, IRemoteCallback callback)479 public void requestServiceFeatures(AppPredictionSessionId sessionId, 480 IRemoteCallback callback) {} 481 482 @VisibleForTesting getSessionInfo(AppPredictionSessionId sessionId)483 SessionInfo getSessionInfo(AppPredictionSessionId sessionId) { 484 return mSessions.get(sessionId); 485 } 486 runForSession(AppPredictionSessionId sessionId, Consumer<SessionInfo> method)487 private void runForSession(AppPredictionSessionId sessionId, Consumer<SessionInfo> method) { 488 SessionInfo sessionInfo = mSessions.get(sessionId); 489 if (sessionInfo == null) { 490 Slog.e(TAG, "Failed to find the session: " + sessionId); 491 return; 492 } 493 method.accept(sessionInfo); 494 } 495 invokePredictionCallback(IPredictionCallback callback, List<AppTarget> targets)496 private void invokePredictionCallback(IPredictionCallback callback, 497 List<AppTarget> targets) { 498 try { 499 callback.onResult(new ParceledListSlice<>(targets)); 500 } catch (RemoteException e) { 501 Slog.e(TAG, "Failed to calling callback" + e); 502 } 503 } 504 } 505 } 506