1 /*
2  * Copyright (C) 2021 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.ambientcontext;
18 
19 import android.annotation.NonNull;
20 import android.annotation.UserIdInt;
21 import android.app.ActivityManager;
22 import android.app.ActivityOptions;
23 import android.app.ActivityTaskManager;
24 import android.app.AppGlobals;
25 import android.app.BroadcastOptions;
26 import android.app.PendingIntent;
27 import android.app.ambientcontext.AmbientContextEvent;
28 import android.app.ambientcontext.AmbientContextEventRequest;
29 import android.app.ambientcontext.AmbientContextManager;
30 import android.app.ambientcontext.IAmbientContextObserver;
31 import android.content.ActivityNotFoundException;
32 import android.content.ComponentName;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.pm.PackageManager;
36 import android.content.pm.ParceledListSlice;
37 import android.content.pm.ServiceInfo;
38 import android.os.Binder;
39 import android.os.Bundle;
40 import android.os.RemoteCallback;
41 import android.os.RemoteException;
42 import android.service.ambientcontext.AmbientContextDetectionResult;
43 import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
44 import android.text.TextUtils;
45 import android.util.IndentingPrintWriter;
46 import android.util.Slog;
47 
48 import com.android.internal.annotations.GuardedBy;
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.server.infra.AbstractPerUserSystemService;
51 
52 import java.io.PrintWriter;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.List;
56 import java.util.function.Consumer;
57 
58 /**
59  * Base per-user manager service for {@link AmbientContextEvent}s.
60  */
61 abstract class AmbientContextManagerPerUserService extends
62         AbstractPerUserSystemService<AmbientContextManagerPerUserService,
63                 AmbientContextManagerService> {
64     private static final String TAG =
65             AmbientContextManagerPerUserService.class.getSimpleName();
66 
67     /**
68      * The type of service.
69      */
70     enum ServiceType {
71         DEFAULT,
72         WEARABLE
73     }
74 
AmbientContextManagerPerUserService( @onNull AmbientContextManagerService master, Object lock, @UserIdInt int userId)75     AmbientContextManagerPerUserService(
76             @NonNull AmbientContextManagerService master, Object lock, @UserIdInt int userId) {
77         super(master, lock, userId);
78     }
79 
80     /**
81      * Returns the current bound AmbientContextManagerPerUserService component for this user.
82      */
getComponentName()83     abstract ComponentName getComponentName();
84 
85     /**
86      * Sets the component name for the per user service.
87      */
setComponentName(ComponentName componentName)88     abstract void setComponentName(ComponentName componentName);
89 
90     /**
91      * Ensures that the remote service is initiated.
92      */
ensureRemoteServiceInitiated()93     abstract void ensureRemoteServiceInitiated();
94 
95     /**
96      * Returns the AmbientContextManagerPerUserService {@link ServiceType} for this user.
97      */
getServiceType()98     abstract ServiceType getServiceType();
99 
100     /**
101      * Returns the int config for the consent component for the
102      * specific AmbientContextManagerPerUserService type
103      */
getConsentComponentConfig()104     abstract int getConsentComponentConfig();
105 
106     /**
107      * Returns the int config for the intent extra key for the
108      * caller's package name while requesting ambient context consent.
109      */
getAmbientContextPackageNameExtraKeyConfig()110     abstract int getAmbientContextPackageNameExtraKeyConfig();
111 
112     /**
113      * Returns the int config for the Intent extra key for the event code int array while
114      * requesting ambient context consent.
115      */
getAmbientContextEventArrayExtraKeyConfig()116     abstract int getAmbientContextEventArrayExtraKeyConfig();
117 
118     /**
119      * Returns the permission that is required to bind to this service.
120      */
getProtectedBindPermission()121     abstract String getProtectedBindPermission();
122 
123     /**
124      * Returns the remote service implementation for this user.
125      */
getRemoteService()126     abstract RemoteAmbientDetectionService getRemoteService();
127 
128     /**
129      * Clears the remote service.
130      */
clearRemoteService()131     abstract void clearRemoteService();
132 
133     /**
134      * Called when there's an application with the callingPackage name is requesting for
135      * the AmbientContextDetection's service status.
136      *
137      * @param eventTypes the event types to query for
138      * @param callingPackage the package query for information
139      * @param statusCallback the callback to deliver the status on
140      */
onQueryServiceStatus(int[] eventTypes, String callingPackage, RemoteCallback statusCallback)141     public void onQueryServiceStatus(int[] eventTypes, String callingPackage,
142             RemoteCallback statusCallback) {
143         Slog.d(TAG, "Query event status of " + Arrays.toString(eventTypes)
144                 + " for " + callingPackage);
145         synchronized (mLock) {
146             if (!setUpServiceIfNeeded()) {
147                 Slog.w(TAG, "Detection service is not available at this moment.");
148                 sendStatusCallback(statusCallback,
149                         AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
150                 return;
151             }
152             ensureRemoteServiceInitiated();
153             getRemoteService().queryServiceStatus(
154                     eventTypes,
155                     callingPackage,
156                     getServerStatusCallback(
157                             statusCode -> sendStatusCallback(statusCallback, statusCode)));
158         }
159     }
160 
161     /**
162      * Unregisters the client from all previously registered events by removing from the
163      * mExistingRequests map, and unregister events from the service if those events are not
164      * requested by other apps.
165      */
onUnregisterObserver(String callingPackage)166     public void onUnregisterObserver(String callingPackage) {
167         synchronized (mLock) {
168             stopDetection(callingPackage);
169             mMaster.clientRemoved(mUserId, callingPackage);
170         }
171     }
172 
173     /**
174      * Starts the consent activity for the calling package and event types.
175      */
onStartConsentActivity(int[] eventTypes, String callingPackage)176     public void onStartConsentActivity(int[] eventTypes, String callingPackage) {
177         Slog.d(TAG, "Opening consent activity of " + Arrays.toString(eventTypes)
178                 + " for " + callingPackage);
179 
180         // Look up the recent task from the callingPackage
181         ActivityManager.RecentTaskInfo task;
182         ParceledListSlice<ActivityManager.RecentTaskInfo> recentTasks;
183         int userId = getUserId();
184         try {
185             recentTasks = ActivityTaskManager.getService().getRecentTasks(/*maxNum*/1,
186                     /*flags*/ 0, userId);
187         } catch (RemoteException e) {
188             Slog.e(TAG, "Failed to query recent tasks!");
189             return;
190         }
191 
192         if ((recentTasks == null) || recentTasks.getList().isEmpty()) {
193             Slog.e(TAG, "Recent task list is empty!");
194             return;
195         }
196 
197         task = recentTasks.getList().get(0);
198         if (!callingPackage.equals(task.topActivityInfo.packageName)) {
199             Slog.e(TAG, "Recent task package name: " + task.topActivityInfo.packageName
200                     + " doesn't match with client package name: " + callingPackage);
201             return;
202         }
203 
204         // Start activity as the same task from the callingPackage
205         ComponentName consentComponent = getConsentComponent();
206         if (consentComponent == null) {
207             Slog.e(TAG, "Consent component not found!");
208             return;
209         }
210 
211         Slog.d(TAG, "Starting consent activity for " + callingPackage);
212         Intent intent = new Intent();
213         final long identity = Binder.clearCallingIdentity();
214         try {
215             Context context = getContext();
216             String packageNameExtraKey = context.getResources().getString(
217                     getAmbientContextPackageNameExtraKeyConfig());
218             String eventArrayExtraKey = context.getResources().getString(
219                     getAmbientContextEventArrayExtraKeyConfig());
220 
221             // Create consent activity intent with the calling package name and requested events
222             intent.setComponent(consentComponent);
223             if (packageNameExtraKey != null) {
224                 intent.putExtra(packageNameExtraKey, callingPackage);
225             } else {
226                 Slog.d(TAG, "Missing packageNameExtraKey for consent activity");
227             }
228             if (eventArrayExtraKey != null) {
229                 intent.putExtra(eventArrayExtraKey, eventTypes);
230             } else {
231                 Slog.d(TAG, "Missing eventArrayExtraKey for consent activity");
232             }
233 
234             // Set parent to the calling app's task
235             ActivityOptions options = ActivityOptions.makeBasic();
236             options.setLaunchTaskId(task.taskId);
237             context.startActivityAsUser(intent, options.toBundle(), context.getUser());
238         } catch (ActivityNotFoundException e) {
239             Slog.e(TAG, "unable to start consent activity");
240         } finally {
241             Binder.restoreCallingIdentity(identity);
242         }
243     }
244 
245     /**
246      * Handles client registering as an observer. Only one registration is supported per app
247      * package. A new registration from the same package will overwrite the previous registration.
248      */
onRegisterObserver(AmbientContextEventRequest request, String packageName, IAmbientContextObserver observer)249     public void onRegisterObserver(AmbientContextEventRequest request,
250             String packageName, IAmbientContextObserver observer) {
251         synchronized (mLock) {
252             if (!setUpServiceIfNeeded()) {
253                 Slog.w(TAG, "Detection service is not available at this moment.");
254                 completeRegistration(observer, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
255                 return;
256             }
257 
258             // Register package and add to existing ClientRequests cache
259             startDetection(request, packageName, observer);
260             mMaster.newClientAdded(mUserId, request, packageName, observer);
261         }
262     }
263 
264     @Override
newServiceInfoLocked(@onNull ComponentName serviceComponent)265     protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
266             throws PackageManager.NameNotFoundException {
267         Slog.d(TAG, "newServiceInfoLocked with component name: "
268                 + serviceComponent.getClassName());
269 
270         if (getComponentName() == null
271                 || !serviceComponent.getClassName().equals(getComponentName().getClassName())) {
272             Slog.d(TAG, "service name does not match this per user, returning...");
273             return null;
274         }
275 
276         ServiceInfo serviceInfo;
277         try {
278             serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
279                     0, mUserId);
280             if (serviceInfo != null) {
281                 final String permission = serviceInfo.permission;
282                 if (!getProtectedBindPermission().equals(
283                         permission)) {
284                     throw new SecurityException(String.format(
285                             "Service %s requires %s permission. Found %s permission",
286                             serviceInfo.getComponentName(),
287                             getProtectedBindPermission(),
288                             serviceInfo.permission));
289                 }
290             }
291         } catch (RemoteException e) {
292             throw new PackageManager.NameNotFoundException(
293                     "Could not get service for " + serviceComponent);
294         }
295         return serviceInfo;
296     }
297 
298     /**
299      * Dumps the remote service.
300      */
dumpLocked(@onNull String prefix, @NonNull PrintWriter pw)301     protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
302         synchronized (super.mLock) {
303             super.dumpLocked(prefix, pw);
304         }
305         RemoteAmbientDetectionService remoteService = getRemoteService();
306         if (remoteService != null) {
307             remoteService.dump("", new IndentingPrintWriter(pw, "  "));
308         }
309     }
310 
311     /**
312      * Send request to the remote AmbientContextDetectionService impl to stop detecting the
313      * specified events. Intended for use by shell command for testing.
314      * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
315      */
316     @VisibleForTesting
stopDetection(String packageName)317     protected void stopDetection(String packageName) {
318         Slog.d(TAG, "Stop detection for " + packageName);
319         synchronized (mLock) {
320             if (getComponentName() != null) {
321                 ensureRemoteServiceInitiated();
322                 RemoteAmbientDetectionService remoteService = getRemoteService();
323                 remoteService.stopDetection(packageName);
324             }
325         }
326     }
327 
328     /**
329      * Destroys this service and unbinds from the remote service.
330      */
destroyLocked()331     protected void destroyLocked() {
332         Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed.");
333         RemoteAmbientDetectionService remoteService = getRemoteService();
334         if (remoteService != null) {
335             synchronized (mLock) {
336                 remoteService.unbind();
337                 clearRemoteService();
338             }
339         }
340     }
341 
342     /**
343      * Send request to the remote AmbientContextDetectionService impl to start detecting the
344      * specified events. Intended for use by shell command for testing.
345      * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
346      */
startDetection(AmbientContextEventRequest request, String callingPackage, IAmbientContextObserver observer)347     protected void startDetection(AmbientContextEventRequest request, String callingPackage,
348             IAmbientContextObserver observer) {
349         Slog.d(TAG, "Requested detection of " + request.getEventTypes());
350         synchronized (mLock) {
351             if (setUpServiceIfNeeded()) {
352                 ensureRemoteServiceInitiated();
353                 RemoteAmbientDetectionService remoteService = getRemoteService();
354                 remoteService.startDetection(request, callingPackage,
355                         createDetectionResultRemoteCallback(),
356                         getServerStatusCallback(
357                                 statusCode -> completeRegistration(observer, statusCode)));
358             } else {
359                 Slog.w(TAG, "No valid component found for AmbientContextDetectionService");
360                 completeRegistration(observer,
361                         AmbientContextManager.STATUS_NOT_SUPPORTED);
362             }
363         }
364     }
365 
366     /**
367      * Notifies the observer the status of the registration.
368      *
369      * @param observer the observer to notify
370      * @param statusCode the status to notify
371      */
completeRegistration(IAmbientContextObserver observer, int statusCode)372     protected void completeRegistration(IAmbientContextObserver observer, int statusCode) {
373         try {
374             observer.onRegistrationComplete(statusCode);
375         } catch (RemoteException e) {
376             Slog.w(TAG, "Failed to call IAmbientContextObserver.onRegistrationComplete: "
377                     + e.getMessage());
378         }
379     }
380 
381     /**
382      * Sends the status on the {@link RemoteCallback}.
383      *
384      * @param statusCallback the callback to send the status on
385      * @param statusCode the status to send
386      */
sendStatusCallback(RemoteCallback statusCallback, @AmbientContextManager.StatusCode int statusCode)387     protected void sendStatusCallback(RemoteCallback statusCallback,
388             @AmbientContextManager.StatusCode int statusCode) {
389         Bundle bundle = new Bundle();
390         bundle.putInt(
391                 AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
392                 statusCode);
393         statusCallback.sendResult(bundle);
394     }
395 
396     /**
397      * Sends out the Intent to the client after the event is detected.
398      *
399      * @param pendingIntent Client's PendingIntent for callback
400      * @param events detected events from the detection service
401      */
sendDetectionResultIntent(PendingIntent pendingIntent, List<AmbientContextEvent> events)402     protected void sendDetectionResultIntent(PendingIntent pendingIntent,
403             List<AmbientContextEvent> events) {
404         Intent intent = new Intent();
405         intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS,
406                 new ArrayList(events));
407         // Explicitly disallow the receiver from starting activities, to prevent apps from utilizing
408         // the PendingIntent as a backdoor to do this.
409         BroadcastOptions options = BroadcastOptions.makeBasic();
410         options.setPendingIntentBackgroundActivityLaunchAllowed(false);
411         try {
412             pendingIntent.send(getContext(), 0, intent, null,
413                     null, null, options.toBundle());
414             Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": "
415                     + events);
416         } catch (PendingIntent.CanceledException e) {
417             Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent);
418         }
419     }
420 
421     @NonNull
createDetectionResultRemoteCallback()422     protected RemoteCallback createDetectionResultRemoteCallback() {
423         return new RemoteCallback(result -> {
424             AmbientContextDetectionResult detectionResult =
425                     (AmbientContextDetectionResult) result.get(
426                             AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY);
427             String packageName = detectionResult.getPackageName();
428             IAmbientContextObserver observer = mMaster.getClientRequestObserver(
429                     mUserId, packageName);
430             if (observer == null) {
431                 return;
432             }
433 
434             final long token = Binder.clearCallingIdentity();
435             try {
436                 observer.onEvents(detectionResult.getEvents());
437                 Slog.i(TAG, "Got detection result of " + detectionResult.getEvents()
438                         + " for " + packageName);
439             } catch (RemoteException e) {
440                 Slog.w(TAG, "Failed to call IAmbientContextObserver.onEvents: " + e.getMessage());
441             } finally {
442                 Binder.restoreCallingIdentity(token);
443             }
444         });
445     }
446 
447     /**
448      * Resolves and sets up the service if it had not been done yet. Returns true if the service
449      * is available.
450      */
451     @GuardedBy("mLock")
452     @VisibleForTesting
setUpServiceIfNeeded()453     private boolean setUpServiceIfNeeded() {
454         if (getComponentName() == null) {
455             ComponentName[] componentNames = updateServiceInfoListLocked();
456             if (componentNames == null || componentNames.length != 2) {
457                 Slog.d(TAG, "updateServiceInfoListLocked returned incorrect componentNames");
458                 return false;
459             }
460 
461             switch (getServiceType()) {
462                 case DEFAULT:
463                     setComponentName(componentNames[0]);
464                     break;
465                 case WEARABLE:
466                     setComponentName(componentNames[1]);
467                     break;
468                 default:
469                     Slog.d(TAG, "updateServiceInfoListLocked returned unknown service types.");
470                     return false;
471             }
472         }
473 
474         if (getComponentName() == null) {
475             return false;
476         }
477 
478         ServiceInfo serviceInfo;
479         try {
480             serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
481                     getComponentName(), 0, mUserId);
482         } catch (RemoteException e) {
483             Slog.w(TAG, "RemoteException while setting up service");
484             return false;
485         }
486         return serviceInfo != null;
487     }
488 
489     /**
490      * Returns a RemoteCallback that handles the status from the detection service, and
491      * sends results to the client callback.
492      */
getServerStatusCallback(Consumer<Integer> statusConsumer)493     private RemoteCallback getServerStatusCallback(Consumer<Integer> statusConsumer) {
494         return new RemoteCallback(result -> {
495             AmbientContextDetectionServiceStatus serviceStatus =
496                     (AmbientContextDetectionServiceStatus) result.get(
497                             AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
498             final long token = Binder.clearCallingIdentity();
499             try {
500                 int statusCode = serviceStatus.getStatusCode();
501                 statusConsumer.accept(statusCode);
502                 Slog.i(TAG, "Got detection status of " + statusCode
503                         + " for " + serviceStatus.getPackageName());
504             } finally {
505                 Binder.restoreCallingIdentity(token);
506             }
507         });
508     }
509 
510     /**
511      * Returns the consent activity component from config lookup.
512      */
513     private ComponentName getConsentComponent() {
514         Context context = getContext();
515         String consentComponent = context.getResources().getString(getConsentComponentConfig());
516         if (TextUtils.isEmpty(consentComponent)) {
517             return null;
518         }
519         Slog.i(TAG, "Consent component name: " + consentComponent);
520         return ComponentName.unflattenFromString(consentComponent);
521     }
522 }
523