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