blob: c6b15b6dcd7aa97e79e7f32d35a40d58e0765ff8 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.ambientcontext;
import static android.provider.DeviceConfig.NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE;
import static android.provider.DeviceConfig.NAMESPACE_WEARABLE_SENSING;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEvent;
import android.app.ambientcontext.AmbientContextEventRequest;
import android.app.ambientcontext.AmbientContextManager;
import android.app.ambientcontext.IAmbientContextManager;
import android.app.ambientcontext.IAmbientContextObserver;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.ambientcontext.AmbientContextManagerPerUserService.ServiceType;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
import com.android.server.pm.KnownPackages;
import com.google.android.collect.Sets;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* System service for managing {@link AmbientContextEvent}s.
*/
public class AmbientContextManagerService extends
AbstractMasterSystemService<AmbientContextManagerService,
AmbientContextManagerPerUserService> {
private static final String TAG = AmbientContextManagerService.class.getSimpleName();
private static final String KEY_SERVICE_ENABLED = "service_enabled";
private static final Set<Integer> DEFAULT_EVENT_SET = Sets.newHashSet(
AmbientContextEvent.EVENT_COUGH,
AmbientContextEvent.EVENT_SNORE,
AmbientContextEvent.EVENT_BACK_DOUBLE_TAP);
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
public static final int MAX_TEMPORARY_SERVICE_DURATION_MS = 30000;
static class ClientRequest {
private final int mUserId;
private final AmbientContextEventRequest mRequest;
private final String mPackageName;
private final IAmbientContextObserver mObserver;
ClientRequest(int userId, AmbientContextEventRequest request,
String packageName, IAmbientContextObserver observer) {
this.mUserId = userId;
this.mRequest = request;
this.mPackageName = packageName;
this.mObserver = observer;
}
String getPackageName() {
return mPackageName;
}
AmbientContextEventRequest getRequest() {
return mRequest;
}
IAmbientContextObserver getObserver() {
return mObserver;
}
boolean hasUserId(int userId) {
return mUserId == userId;
}
boolean hasUserIdAndPackageName(int userId, String packageName) {
return (userId == mUserId) && packageName.equals(getPackageName());
}
}
private final Context mContext;
boolean mIsServiceEnabled;
boolean mIsWearableServiceEnabled;
private Set<ClientRequest> mExistingClientRequests;
public AmbientContextManagerService(Context context) {
super(context,
new FrameworkResourcesServiceNameResolver(
context,
R.array.config_defaultAmbientContextServices,
/*isMultiple=*/ true),
/*disallowProperty=*/null,
PACKAGE_UPDATE_POLICY_REFRESH_EAGER
| /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
mContext = context;
mExistingClientRequests = new ArraySet<>();
}
@Override
public void onStart() {
publishBinderService(Context.AMBIENT_CONTEXT_SERVICE, new AmbientContextManagerInternal());
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
DeviceConfig.addOnPropertiesChangedListener(
NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE,
getContext().getMainExecutor(),
(properties) -> onDeviceConfigChange(properties.getKeyset()));
mIsServiceEnabled = DeviceConfig.getBoolean(
NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE,
KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
mIsWearableServiceEnabled = DeviceConfig.getBoolean(
NAMESPACE_WEARABLE_SENSING,
KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
}
}
void newClientAdded(int userId, AmbientContextEventRequest request,
String callingPackage, IAmbientContextObserver observer) {
Slog.d(TAG, "New client added: " + callingPackage);
// Remove any existing ClientRequest for this user and package.
mExistingClientRequests.removeAll(
findExistingRequests(userId, callingPackage));
// Add to existing ClientRequests
mExistingClientRequests.add(
new ClientRequest(userId, request, callingPackage, observer));
}
void clientRemoved(int userId, String packageName) {
Slog.d(TAG, "Remove client: " + packageName);
mExistingClientRequests.removeAll(findExistingRequests(userId, packageName));
}
private Set<ClientRequest> findExistingRequests(int userId, String packageName) {
Set<ClientRequest> existingRequests = new ArraySet<>();
for (ClientRequest clientRequest : mExistingClientRequests) {
if (clientRequest.hasUserIdAndPackageName(userId, packageName)) {
existingRequests.add(clientRequest);
}
}
return existingRequests;
}
@Nullable
IAmbientContextObserver getClientRequestObserver(int userId, String packageName) {
for (ClientRequest clientRequest : mExistingClientRequests) {
if (clientRequest.hasUserIdAndPackageName(userId, packageName)) {
return clientRequest.getObserver();
}
}
return null;
}
private void onDeviceConfigChange(@NonNull Set<String> keys) {
if (keys.contains(KEY_SERVICE_ENABLED)) {
mIsServiceEnabled = DeviceConfig.getBoolean(
NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE,
KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
mIsWearableServiceEnabled = DeviceConfig.getBoolean(
NAMESPACE_WEARABLE_SENSING,
KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
}
}
@Override
protected AmbientContextManagerPerUserService newServiceLocked(int resolvedUserId,
boolean disabled) {
// This service uses newServiceListLocked, it is configured in multiple mode.
return null;
}
@Override // from AbstractMasterSystemService
protected List<AmbientContextManagerPerUserService> newServiceListLocked(int resolvedUserId,
boolean disabled, String[] serviceNames) {
if (serviceNames == null || serviceNames.length == 0) {
Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty");
return new ArrayList<>();
}
List<AmbientContextManagerPerUserService> serviceList =
new ArrayList<>(serviceNames.length);
if (serviceNames.length == 2) {
Slog.i(TAG, "Not using default services, "
+ "services provided for testing should be exactly two services.");
if (!isDefaultService(serviceNames[0]) && !isDefaultWearableService(serviceNames[1])) {
serviceList.add(new DefaultAmbientContextManagerPerUserService(
this, mLock, resolvedUserId,
AmbientContextManagerPerUserService.ServiceType.DEFAULT, serviceNames[0]));
serviceList.add(new WearableAmbientContextManagerPerUserService(
this, mLock, resolvedUserId,
AmbientContextManagerPerUserService.ServiceType.WEARABLE,
serviceNames[1]));
}
return serviceList;
} else {
Slog.i(TAG, "Incorrect number of services provided for testing.");
}
for (String serviceName : serviceNames) {
Slog.d(TAG, "newServicesListLocked with service name: " + serviceName);
if (getServiceType(serviceName)
== AmbientContextManagerPerUserService.ServiceType.WEARABLE) {
serviceList.add(new
WearableAmbientContextManagerPerUserService(
this, mLock, resolvedUserId,
AmbientContextManagerPerUserService.ServiceType.WEARABLE, serviceName));
} else {
serviceList.add(new DefaultAmbientContextManagerPerUserService(
this, mLock, resolvedUserId,
AmbientContextManagerPerUserService.ServiceType.DEFAULT, serviceName));
}
}
return serviceList;
}
@Override
protected void onServiceRemoved(
AmbientContextManagerPerUserService service, @UserIdInt int userId) {
Slog.d(TAG, "onServiceRemoved");
service.destroyLocked();
}
@Override
protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
Slog.d(TAG, "Restoring remote request. Reason: Service package restarted.");
restorePreviouslyEnabledClients(userId);
}
@Override
protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
Slog.d(TAG, "Restoring remote request. Reason: Service package updated.");
restorePreviouslyEnabledClients(userId);
}
@Override
protected void enforceCallingPermissionForManagement() {
getContext().enforceCallingPermission(
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
}
@Override
protected int getMaximumTemporaryServiceDurationMs() {
return MAX_TEMPORARY_SERVICE_DURATION_MS;
}
/** Returns {@code true} if the detection service is configured on this device. */
public static boolean isDetectionServiceConfigured() {
final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
final String[] packageNames = pmi.getKnownPackageNames(
KnownPackages.PACKAGE_AMBIENT_CONTEXT_DETECTION, UserHandle.USER_SYSTEM);
boolean isServiceConfigured = (packageNames.length != 0);
Slog.i(TAG, "Detection service configured: " + isServiceConfigured);
return isServiceConfigured;
}
/**
* Send request to the remote AmbientContextDetectionService impl to start detecting the
* specified events. Intended for use by shell command for testing.
* Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
*/
void startDetection(@UserIdInt int userId, AmbientContextEventRequest request,
String packageName, IAmbientContextObserver observer) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
synchronized (mLock) {
AmbientContextManagerPerUserService service =
getAmbientContextManagerPerUserServiceForEventTypes(
userId,
request.getEventTypes());
if (service != null) {
service.startDetection(request, packageName, observer);
} else {
Slog.i(TAG, "service not available for user_id: " + userId);
}
}
}
/**
* Send request to the remote AmbientContextDetectionService impl to stop detecting the
* specified events. Intended for use by shell command for testing.
* Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
*/
void stopAmbientContextEvent(@UserIdInt int userId, String packageName) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
synchronized (mLock) {
for (ClientRequest cr : mExistingClientRequests) {
Slog.i(TAG, "Looping through clients");
if (cr.hasUserIdAndPackageName(userId, packageName)) {
Slog.i(TAG, "we have an existing client");
AmbientContextManagerPerUserService service =
getAmbientContextManagerPerUserServiceForEventTypes(
userId, cr.getRequest().getEventTypes());
if (service != null) {
service.stopDetection(packageName);
} else {
Slog.i(TAG, "service not available for user_id: " + userId);
}
}
}
}
}
/**
* Send request to the remote AmbientContextDetectionService impl to query the status of the
* specified events. Intended for use by shell command for testing.
* Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
*/
void queryServiceStatus(@UserIdInt int userId, String packageName,
int[] eventTypes, RemoteCallback callback) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
synchronized (mLock) {
AmbientContextManagerPerUserService service =
getAmbientContextManagerPerUserServiceForEventTypes(
userId, intArrayToIntegerSet(eventTypes));
if (service != null) {
service.onQueryServiceStatus(eventTypes, packageName, callback);
} else {
Slog.i(TAG, "query service not available for user_id: " + userId);
}
}
}
private void restorePreviouslyEnabledClients(int userId) {
synchronized (mLock) {
final List<AmbientContextManagerPerUserService> services =
getServiceListForUserLocked(userId);
for (AmbientContextManagerPerUserService service : services) {
for (ClientRequest clientRequest : mExistingClientRequests) {
// Start detection for previously enabled clients
if (clientRequest.hasUserId(userId)) {
Slog.d(TAG, "Restoring detection for "
+ clientRequest.getPackageName());
service.startDetection(clientRequest.getRequest(),
clientRequest.getPackageName(),
clientRequest.getObserver());
}
}
}
}
}
/**
* Returns the AmbientContextManagerPerUserService component for this user.
*/
public ComponentName getComponentName(
@UserIdInt int userId,
AmbientContextManagerPerUserService.ServiceType serviceType) {
synchronized (mLock) {
final AmbientContextManagerPerUserService service =
getServiceForType(userId, serviceType);
if (service != null) {
return service.getComponentName();
}
}
return null;
}
private AmbientContextManagerPerUserService getAmbientContextManagerPerUserServiceForEventTypes(
@UserIdInt int userId, Set<Integer> eventTypes) {
if (isWearableEventTypesOnly(eventTypes)) {
return getServiceForType(userId,
AmbientContextManagerPerUserService.ServiceType.WEARABLE);
} else {
return getServiceForType(userId,
AmbientContextManagerPerUserService.ServiceType.DEFAULT);
}
}
private AmbientContextManagerPerUserService.ServiceType getServiceType(String serviceName) {
final String wearableService = mContext.getResources()
.getString(R.string.config_defaultWearableSensingService);
if (wearableService != null && wearableService.equals(serviceName)) {
return AmbientContextManagerPerUserService.ServiceType.WEARABLE;
}
return AmbientContextManagerPerUserService.ServiceType.DEFAULT;
}
private boolean isDefaultService(String serviceName) {
final String defaultService = mContext.getResources()
.getString(R.string.config_defaultAmbientContextDetectionService);
if (defaultService != null && defaultService.equals(serviceName)) {
return true;
}
return false;
}
private boolean isDefaultWearableService(String serviceName) {
final String wearableService = mContext.getResources()
.getString(R.string.config_defaultWearableSensingService);
if (wearableService != null && wearableService.equals(serviceName)) {
return true;
}
return false;
}
private AmbientContextManagerPerUserService getServiceForType(int userId,
AmbientContextManagerPerUserService.ServiceType serviceType) {
Slog.d(TAG, "getServiceForType with userid: "
+ userId + " service type: " + serviceType.name());
synchronized (mLock) {
final List<AmbientContextManagerPerUserService> services =
getServiceListForUserLocked(userId);
Slog.d(TAG, "Services that are available: "
+ (services == null ? "null services" : services.size()
+ " number of services"));
if (services == null) {
return null;
}
for (AmbientContextManagerPerUserService service : services) {
if (service.getServiceType() == serviceType) {
return service;
}
}
}
return null;
}
private boolean isWearableEventTypesOnly(Set<Integer> eventTypes) {
if (eventTypes.isEmpty()) {
Slog.d(TAG, "empty event types.");
return false;
}
for (Integer eventType : eventTypes) {
if (eventType < AmbientContextEvent.EVENT_VENDOR_WEARABLE_START) {
Slog.d(TAG, "Not all events types are wearable events.");
return false;
}
}
Slog.d(TAG, "only wearable events.");
return true;
}
private boolean isWearableEventTypesOnly(int[] eventTypes) {
Integer[] events = intArrayToIntegerArray(eventTypes);
return isWearableEventTypesOnly(new HashSet<>(Arrays.asList(events)));
}
private boolean containsMixedEvents(int[] eventTypes) {
if (isWearableEventTypesOnly(eventTypes)) {
return false;
}
// It's not only wearable events so check if it's only default events.
for (Integer event : eventTypes) {
if (!DEFAULT_EVENT_SET.contains(event)) {
// mixed events.
Slog.w(TAG, "Received mixed event types, this is not supported.");
return true;
}
}
// Only default events.
return false;
}
private static int[] integerSetToIntArray(@NonNull Set<Integer> integerSet) {
int[] intArray = new int[integerSet.size()];
int i = 0;
for (Integer type : integerSet) {
intArray[i++] = type;
}
return intArray;
}
private Set<Integer> intArrayToIntegerSet(int[] eventTypes) {
Set<Integer> types = new HashSet<>();
for (Integer i : eventTypes) {
types.add(i);
}
return types;
}
@NonNull
private static Integer[] intArrayToIntegerArray(@NonNull int[] integerSet) {
Integer[] intArray = new Integer[integerSet.length];
int i = 0;
for (Integer type : integerSet) {
intArray[i++] = type;
}
return intArray;
}
private final class AmbientContextManagerInternal extends IAmbientContextManager.Stub {
@Override
public void registerObserver(
AmbientContextEventRequest request, PendingIntent resultPendingIntent,
RemoteCallback statusCallback) {
Objects.requireNonNull(request);
Objects.requireNonNull(resultPendingIntent);
Objects.requireNonNull(statusCallback);
AmbientContextManagerPerUserService service =
getAmbientContextManagerPerUserServiceForEventTypes(
UserHandle.getCallingUserId(),
request.getEventTypes());
// Wrap the PendingIntent and statusCallback in a IAmbientContextObserver to make the
// code unified
IAmbientContextObserver observer = new IAmbientContextObserver.Stub() {
@Override
public void onEvents(List<AmbientContextEvent> events) throws RemoteException {
service.sendDetectionResultIntent(resultPendingIntent, events);
}
@Override
public void onRegistrationComplete(int statusCode) throws RemoteException {
service.sendStatusCallback(statusCallback,
statusCode);
}
};
registerObserverWithCallback(request, resultPendingIntent.getCreatorPackage(),
observer);
}
/**
* Register an observer for Ambient Context events.
*/
@Override
public void registerObserverWithCallback(AmbientContextEventRequest request,
String packageName,
IAmbientContextObserver observer) {
Slog.i(TAG, "AmbientContextManagerService registerObserverWithCallback.");
Objects.requireNonNull(request);
Objects.requireNonNull(packageName);
Objects.requireNonNull(observer);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
assertCalledByPackageOwner(packageName);
AmbientContextManagerPerUserService service =
getAmbientContextManagerPerUserServiceForEventTypes(
UserHandle.getCallingUserId(),
request.getEventTypes());
if (service == null) {
Slog.w(TAG, "onRegisterObserver unavailable user_id: "
+ UserHandle.getCallingUserId());
return;
}
int statusCode = checkStatusCode(
service, integerSetToIntArray(request.getEventTypes()));
if (statusCode == AmbientContextManager.STATUS_SUCCESS) {
service.onRegisterObserver(request, packageName, observer);
} else {
service.completeRegistration(observer, statusCode);
}
}
@Override
public void unregisterObserver(String callingPackage) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
assertCalledByPackageOwner(callingPackage);
synchronized (mLock) {
for (ClientRequest cr : mExistingClientRequests) {
if ((cr != null) && cr.getPackageName().equals(callingPackage)) {
AmbientContextManagerPerUserService service =
getAmbientContextManagerPerUserServiceForEventTypes(
UserHandle.getCallingUserId(),
cr.getRequest().getEventTypes());
if (service != null) {
service.onUnregisterObserver(callingPackage);
} else {
Slog.w(TAG, "onUnregisterObserver unavailable user_id: "
+ UserHandle.getCallingUserId());
}
}
}
}
}
@Override
public void queryServiceStatus(int[] eventTypes, String callingPackage,
RemoteCallback statusCallback) {
Objects.requireNonNull(eventTypes);
Objects.requireNonNull(callingPackage);
Objects.requireNonNull(statusCallback);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
assertCalledByPackageOwner(callingPackage);
synchronized (mLock) {
AmbientContextManagerPerUserService service =
getAmbientContextManagerPerUserServiceForEventTypes(
UserHandle.getCallingUserId(), intArrayToIntegerSet(eventTypes));
if (service == null) {
Slog.w(TAG, "queryServiceStatus unavailable user_id: "
+ UserHandle.getCallingUserId());
return;
}
int statusCode = checkStatusCode(service, eventTypes);
if (statusCode == AmbientContextManager.STATUS_SUCCESS) {
service.onQueryServiceStatus(eventTypes, callingPackage,
statusCallback);
} else {
service.sendStatusCallback(statusCallback, statusCode);
}
}
}
@Override
public void startConsentActivity(int[] eventTypes, String callingPackage) {
Objects.requireNonNull(eventTypes);
Objects.requireNonNull(callingPackage);
assertCalledByPackageOwner(callingPackage);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
if (containsMixedEvents(eventTypes)) {
Slog.d(TAG, "AmbientContextEventRequest contains mixed events,"
+ " this is not supported.");
return;
}
AmbientContextManagerPerUserService service =
getAmbientContextManagerPerUserServiceForEventTypes(
UserHandle.getCallingUserId(), intArrayToIntegerSet(eventTypes));
if (service != null) {
service.onStartConsentActivity(eventTypes, callingPackage);
} else {
Slog.w(TAG, "startConsentActivity unavailable user_id: "
+ UserHandle.getCallingUserId());
}
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
return;
}
synchronized (mLock) {
dumpLocked("", pw);
}
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
new AmbientContextShellCommand(AmbientContextManagerService.this).exec(
this, in, out, err, args, callback, resultReceiver);
}
private int checkStatusCode(AmbientContextManagerPerUserService service, int[] eventTypes) {
if (service.getServiceType() == ServiceType.DEFAULT && !mIsServiceEnabled) {
Slog.d(TAG, "Service not enabled.");
return AmbientContextManager.STATUS_SERVICE_UNAVAILABLE;
}
if (service.getServiceType() == ServiceType.WEARABLE && !mIsWearableServiceEnabled) {
Slog.d(TAG, "Wearable Service not available.");
return AmbientContextManager.STATUS_SERVICE_UNAVAILABLE;
}
if (containsMixedEvents(eventTypes)) {
Slog.d(TAG, "AmbientContextEventRequest contains mixed events,"
+ " this is not supported.");
return AmbientContextManager.STATUS_NOT_SUPPORTED;
}
return AmbientContextManager.STATUS_SUCCESS;
}
}
}