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