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