Loading services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +5 −3 Original line number Diff line number Diff line Loading @@ -23,7 +23,7 @@ import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVER import static android.content.ComponentName.createRelative; import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature; import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation; import static com.android.server.companion.RolesUtils.isRoleHolder; Loading @@ -31,6 +31,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.companion.AssociationInfo; Loading Loading @@ -102,8 +103,9 @@ import java.util.Set; * @see #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback, * ResultReceiver, MacAddress) */ @SuppressLint("LongLogTag") class AssociationRequestsProcessor { private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor"; private static final String TAG = "CompanionDevice_AssociationRequestsProcessor"; private static final ComponentName ASSOCIATION_REQUEST_APPROVAL_ACTIVITY = createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceActivity"); Loading Loading @@ -161,7 +163,7 @@ class AssociationRequestsProcessor { // 1. Enforce permissions and other requirements. enforcePermissionsForAssociation(mContext, request, packageUid); mService.checkUsesFeature(packageName, userId); enforceUsesCompanionDeviceFeature(mContext, userId, packageName); // 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER // to perform discovery NOR to collect user consent). Loading services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java 0 → 100644 +119 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.companion; import static android.content.Context.BIND_IMPORTANT; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.companion.AssociationInfo; import android.companion.CompanionDeviceService; import android.companion.ICompanionDeviceService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.util.Log; import com.android.internal.infra.ServiceConnector; /** * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the * application process. */ @SuppressLint("LongLogTag") class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> { private static final String TAG = "CompanionDevice_ServiceConnector"; private static final boolean DEBUG = false; private static final int BINDING_FLAGS = BIND_IMPORTANT; /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */ interface Listener { void onBindingDied(@UserIdInt int userId, @NonNull String packageName); } private final @UserIdInt int mUserId; private final @NonNull ComponentName mComponentName; private @Nullable Listener mListener; CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId, @NonNull ComponentName componentName) { super(context, buildIntent(componentName), BINDING_FLAGS, userId, null); mUserId = userId; mComponentName = componentName; } void setListener(@Nullable Listener listener) { mListener = listener; } void postOnDeviceAppeared(@NonNull AssociationInfo associationInfo) { post(companionService -> companionService.onDeviceAppeared(associationInfo)); } void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) { post(companionService -> companionService.onDeviceDisappeared(associationInfo)); } /** * Post "unbind" job, which will run *after* all previously posted jobs complete. * * IMPORTANT: use this method instead of invoking {@link ServiceConnector#unbind()} directly, * because the latter may cause previously posted callback, such as * {@link ICompanionDeviceService#onDeviceDisappeared(AssociationInfo)} to be dropped. */ void postUnbind() { post(it -> unbind()); } @Override protected void onServiceConnectionStatusChanged( @NonNull ICompanionDeviceService service, boolean isConnected) { if (DEBUG) { Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString() + " connected=" + isConnected); } } @Override public void onBindingDied(@NonNull ComponentName name) { // IMPORTANT: call super! super.onBindingDied(name); if (DEBUG) Log.d(TAG, "onBindingDied() " + mComponentName.toShortString()); mListener.onBindingDied(mUserId, mComponentName.getPackageName()); } @Override protected ICompanionDeviceService binderAsInterface(@NonNull IBinder service) { return ICompanionDeviceService.Stub.asInterface(service); } @Override protected long getAutoDisconnectTimeoutMs() { // Do NOT auto-disconnect. return -1; } private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) { return new Intent(CompanionDeviceService.SERVICE_INTERFACE) .setComponent(componentName); } } services/companion/java/com/android/server/companion/PackageUtils.java 0 → 100644 +126 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.companion; import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP; import static android.content.pm.PackageManager.GET_CONFIGURATIONS; import static android.content.pm.PackageManager.GET_META_DATA; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.companion.CompanionDeviceService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.PackageInfoFlags; import android.content.pm.PackageManager.ResolveInfoFlags; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Binder; import android.util.Slog; import com.android.internal.util.ArrayUtils; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Utility methods for working with {@link PackageInfo}-s. */ final class PackageUtils { private static final Intent COMPANION_SERVICE_INTENT = new Intent(CompanionDeviceService.SERVICE_INTERFACE); private static final String META_DATA_KEY_PRIMARY = "primary"; static @Nullable PackageInfo getPackageInfo(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName) { final PackageManager pm = context.getPackageManager(); final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS); return Binder.withCleanCallingIdentity(() -> pm.getPackageInfoAsUser(packageName, flags , userId)); } static void enforceUsesCompanionDeviceFeature(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName) { final boolean requested = ArrayUtils.contains( getPackageInfo(context, userId, packageName).reqFeatures, FEATURE_COMPANION_DEVICE_SETUP); if (requested) { throw new IllegalStateException("Must declare uses-feature " + FEATURE_COMPANION_DEVICE_SETUP + " in manifest to use this API"); } } /** * @return list of {@link CompanionDeviceService}-s per package for a given user. * Services marked as "primary" would always appear at the head of the lists, *before* * all non-primary services. */ static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser( @NonNull Context context, @UserIdInt int userId) { final PackageManager pm = context.getPackageManager(); final ResolveInfoFlags flags = ResolveInfoFlags.of(GET_META_DATA); final List<ResolveInfo> companionServices = pm.queryIntentServicesAsUser(COMPANION_SERVICE_INTENT, flags, userId); final Map<String, List<ComponentName>> packageNameToServiceInfoList = new HashMap<>(); for (ResolveInfo resolveInfo : companionServices) { final ServiceInfo service = resolveInfo.serviceInfo; final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE .equals(resolveInfo.serviceInfo.permission); if (!requiresPermission) { Slog.w(LOG_TAG, "CompanionDeviceService " + service.getComponentName().flattenToShortString() + " must require " + "android.permission.BIND_COMPANION_DEVICE_SERVICE"); continue; } // Use LinkedList, because we'll need to prepend "primary" services, while appending the // other (non-primary) services to the list. final LinkedList<ComponentName> services = (LinkedList<ComponentName>) packageNameToServiceInfoList.computeIfAbsent( service.packageName, it -> new LinkedList<>()); final ComponentName componentName = service.getComponentName(); if (isPrimaryCompanionDeviceService(service)) { // "Primary" service should be at the head of the list. services.addFirst(componentName); } else { services.addLast(componentName); } } return packageNameToServiceInfoList; } private static boolean isPrimaryCompanionDeviceService(ServiceInfo service) { return service.metaData != null && service.metaData.getBoolean(META_DATA_KEY_PRIMARY); } } Loading
services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +5 −3 Original line number Diff line number Diff line Loading @@ -23,7 +23,7 @@ import static android.companion.CompanionDeviceManager.COMPANION_DEVICE_DISCOVER import static android.content.ComponentName.createRelative; import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature; import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation; import static com.android.server.companion.RolesUtils.isRoleHolder; Loading @@ -31,6 +31,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.companion.AssociationInfo; Loading Loading @@ -102,8 +103,9 @@ import java.util.Set; * @see #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback, * ResultReceiver, MacAddress) */ @SuppressLint("LongLogTag") class AssociationRequestsProcessor { private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor"; private static final String TAG = "CompanionDevice_AssociationRequestsProcessor"; private static final ComponentName ASSOCIATION_REQUEST_APPROVAL_ACTIVITY = createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceActivity"); Loading Loading @@ -161,7 +163,7 @@ class AssociationRequestsProcessor { // 1. Enforce permissions and other requirements. enforcePermissionsForAssociation(mContext, request, packageUid); mService.checkUsesFeature(packageName, userId); enforceUsesCompanionDeviceFeature(mContext, userId, packageName); // 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER // to perform discovery NOR to collect user consent). Loading
services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java 0 → 100644 +119 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.companion; import static android.content.Context.BIND_IMPORTANT; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.companion.AssociationInfo; import android.companion.CompanionDeviceService; import android.companion.ICompanionDeviceService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.util.Log; import com.android.internal.infra.ServiceConnector; /** * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the * application process. */ @SuppressLint("LongLogTag") class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> { private static final String TAG = "CompanionDevice_ServiceConnector"; private static final boolean DEBUG = false; private static final int BINDING_FLAGS = BIND_IMPORTANT; /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */ interface Listener { void onBindingDied(@UserIdInt int userId, @NonNull String packageName); } private final @UserIdInt int mUserId; private final @NonNull ComponentName mComponentName; private @Nullable Listener mListener; CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId, @NonNull ComponentName componentName) { super(context, buildIntent(componentName), BINDING_FLAGS, userId, null); mUserId = userId; mComponentName = componentName; } void setListener(@Nullable Listener listener) { mListener = listener; } void postOnDeviceAppeared(@NonNull AssociationInfo associationInfo) { post(companionService -> companionService.onDeviceAppeared(associationInfo)); } void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) { post(companionService -> companionService.onDeviceDisappeared(associationInfo)); } /** * Post "unbind" job, which will run *after* all previously posted jobs complete. * * IMPORTANT: use this method instead of invoking {@link ServiceConnector#unbind()} directly, * because the latter may cause previously posted callback, such as * {@link ICompanionDeviceService#onDeviceDisappeared(AssociationInfo)} to be dropped. */ void postUnbind() { post(it -> unbind()); } @Override protected void onServiceConnectionStatusChanged( @NonNull ICompanionDeviceService service, boolean isConnected) { if (DEBUG) { Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString() + " connected=" + isConnected); } } @Override public void onBindingDied(@NonNull ComponentName name) { // IMPORTANT: call super! super.onBindingDied(name); if (DEBUG) Log.d(TAG, "onBindingDied() " + mComponentName.toShortString()); mListener.onBindingDied(mUserId, mComponentName.getPackageName()); } @Override protected ICompanionDeviceService binderAsInterface(@NonNull IBinder service) { return ICompanionDeviceService.Stub.asInterface(service); } @Override protected long getAutoDisconnectTimeoutMs() { // Do NOT auto-disconnect. return -1; } private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) { return new Intent(CompanionDeviceService.SERVICE_INTERFACE) .setComponent(componentName); } }
services/companion/java/com/android/server/companion/PackageUtils.java 0 → 100644 +126 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.companion; import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP; import static android.content.pm.PackageManager.GET_CONFIGURATIONS; import static android.content.pm.PackageManager.GET_META_DATA; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.companion.CompanionDeviceService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.PackageInfoFlags; import android.content.pm.PackageManager.ResolveInfoFlags; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Binder; import android.util.Slog; import com.android.internal.util.ArrayUtils; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Utility methods for working with {@link PackageInfo}-s. */ final class PackageUtils { private static final Intent COMPANION_SERVICE_INTENT = new Intent(CompanionDeviceService.SERVICE_INTERFACE); private static final String META_DATA_KEY_PRIMARY = "primary"; static @Nullable PackageInfo getPackageInfo(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName) { final PackageManager pm = context.getPackageManager(); final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS); return Binder.withCleanCallingIdentity(() -> pm.getPackageInfoAsUser(packageName, flags , userId)); } static void enforceUsesCompanionDeviceFeature(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName) { final boolean requested = ArrayUtils.contains( getPackageInfo(context, userId, packageName).reqFeatures, FEATURE_COMPANION_DEVICE_SETUP); if (requested) { throw new IllegalStateException("Must declare uses-feature " + FEATURE_COMPANION_DEVICE_SETUP + " in manifest to use this API"); } } /** * @return list of {@link CompanionDeviceService}-s per package for a given user. * Services marked as "primary" would always appear at the head of the lists, *before* * all non-primary services. */ static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser( @NonNull Context context, @UserIdInt int userId) { final PackageManager pm = context.getPackageManager(); final ResolveInfoFlags flags = ResolveInfoFlags.of(GET_META_DATA); final List<ResolveInfo> companionServices = pm.queryIntentServicesAsUser(COMPANION_SERVICE_INTENT, flags, userId); final Map<String, List<ComponentName>> packageNameToServiceInfoList = new HashMap<>(); for (ResolveInfo resolveInfo : companionServices) { final ServiceInfo service = resolveInfo.serviceInfo; final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE .equals(resolveInfo.serviceInfo.permission); if (!requiresPermission) { Slog.w(LOG_TAG, "CompanionDeviceService " + service.getComponentName().flattenToShortString() + " must require " + "android.permission.BIND_COMPANION_DEVICE_SERVICE"); continue; } // Use LinkedList, because we'll need to prepend "primary" services, while appending the // other (non-primary) services to the list. final LinkedList<ComponentName> services = (LinkedList<ComponentName>) packageNameToServiceInfoList.computeIfAbsent( service.packageName, it -> new LinkedList<>()); final ComponentName componentName = service.getComponentName(); if (isPrimaryCompanionDeviceService(service)) { // "Primary" service should be at the head of the list. services.addFirst(componentName); } else { services.addLast(componentName); } } return packageNameToServiceInfoList; } private static boolean isPrimaryCompanionDeviceService(ServiceInfo service) { return service.metaData != null && service.metaData.getBoolean(META_DATA_KEY_PRIMARY); } }