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