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