From 341d669e4a85bb0d009a70a9f7cb42d439ba037c Mon Sep 17 00:00:00 2001 From: Sergey Nikolaienkov Date: Wed, 20 Oct 2021 14:11:22 +0200 Subject: [PATCH] Introduce CDM AssociationRequestsProcessor Moving the code for handling incoming AssociationRequests from CompanionDeviceManagerServices into a separate class - AssociationRequestsProcessor. AssociationRequestsProcessor is reponsible for: - validating the incoming requests - making a decision whether user confirmation is required - launching and communicating with the UI (if needed) Bug: 197933995 Bug: 194100881 Test: make Change-Id: I8f0694c44b24697db2917a8ab06a6abceff315e3 --- .../AssociationRequestsProcessor.java | 320 +++++++++++++++++ .../CompanionDeviceManagerService.java | 330 +++--------------- 2 files changed, 366 insertions(+), 284 deletions(-) create mode 100644 services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java new file mode 100644 index 0000000000000..2a0adeb715592 --- /dev/null +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -0,0 +1,320 @@ +/* + * 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.companion; + +import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING; +import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import static com.android.internal.util.CollectionUtils.filter; +import static com.android.internal.util.FunctionalUtils.uncheckExceptions; +import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; +import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; +import static com.android.server.companion.CompanionDeviceManagerService.getCallingUserId; + +import static java.util.Collections.unmodifiableMap; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.companion.Association; +import android.companion.AssociationRequest; +import android.companion.CompanionDeviceManager; +import android.companion.ICompanionDeviceDiscoveryService; +import android.companion.IFindDeviceCallback; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.Signature; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.PackageUtils; +import android.util.Slog; + +import com.android.internal.infra.AndroidFuture; +import com.android.internal.infra.PerUser; +import com.android.internal.infra.ServiceConnector; +import com.android.internal.util.ArrayUtils; +import com.android.server.FgThread; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +class AssociationRequestsProcessor { + private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor"; + + private static final Map DEVICE_PROFILE_TO_PERMISSION; + static { + final Map map = new ArrayMap<>(); + map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH); + map.put(DEVICE_PROFILE_APP_STREAMING, + Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING); + + DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map); + } + + private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative( + CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, + ".CompanionDeviceDiscoveryService"); + + private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5; + private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min; + + private final Context mContext; + private final CompanionDeviceManagerService mService; + + private AssociationRequest mRequest; + private IFindDeviceCallback mFindDeviceCallback; + private String mCallingPackage; + private AndroidFuture mOngoingDeviceDiscovery; + + private PerUser> mServiceConnectors; + + AssociationRequestsProcessor(CompanionDeviceManagerService service) { + mContext = service.getContext(); + mService = service; + + final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO); + mServiceConnectors = new PerUser>() { + @Override + protected ServiceConnector create(int userId) { + return new ServiceConnector.Impl<>( + mContext, + serviceIntent, 0/* bindingFlags */, userId, + ICompanionDeviceDiscoveryService.Stub::asInterface); + } + }; + } + + void process(AssociationRequest request, IFindDeviceCallback callback, String callingPackage) + throws RemoteException { + if (DEBUG) { + Slog.d(TAG, "process(request=" + request + ", from=" + callingPackage + ")"); + } + + checkNotNull(request, "Request cannot be null"); + checkNotNull(callback, "Callback cannot be null"); + mService.checkCallerIsSystemOr(callingPackage); + int userId = getCallingUserId(); + mService.checkUsesFeature(callingPackage, userId); + final String deviceProfile = request.getDeviceProfile(); + validateDeviceProfileAndCheckPermission(deviceProfile); + + mFindDeviceCallback = callback; + mRequest = request; + mCallingPackage = callingPackage; + request.setCallingPackage(callingPackage); + + if (mayAssociateWithoutPrompt(callingPackage, userId)) { + Slog.i(TAG, "setSkipPrompt(true)"); + request.setSkipPrompt(true); + } + callback.asBinder().linkToDeath(mBinderDeathRecipient /* recipient */, 0); + + mOngoingDeviceDiscovery = getDeviceProfilePermissionDescription(deviceProfile) + .thenComposeAsync(description -> { + if (DEBUG) { + Slog.d(TAG, "fetchProfileDescription done: " + description); + } + + request.setDeviceProfilePrivilegesDescription(description); + + return mServiceConnectors.forUser(userId).postAsync(service -> { + if (DEBUG) { + Slog.d(TAG, "Connected to CDM service -> " + + "Starting discovery for " + request); + } + + AndroidFuture future = new AndroidFuture<>(); + service.startDiscovery(request, callingPackage, callback, future); + return future; + }).cancelTimeout(); + + }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> { + if (err == null) { + mService.createAssociationInternal( + userId, deviceAddress, callingPackage, deviceProfile); + } else { + Slog.e(TAG, "Failed to discover device(s)", err); + callback.onFailure("No devices found: " + err.getMessage()); + } + cleanup(); + })); + } + + void stopScan(AssociationRequest request, IFindDeviceCallback callback, String callingPackage) { + if (DEBUG) { + Slog.d(TAG, "stopScan(request = " + request + ")"); + } + if (Objects.equals(request, mRequest) + && Objects.equals(callback, mFindDeviceCallback) + && Objects.equals(callingPackage, mCallingPackage)) { + cleanup(); + } + } + + private void validateDeviceProfileAndCheckPermission(@Nullable String deviceProfile) { + // Device profile can be null. + if (deviceProfile == null) return; + + if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) { + // TODO: remove, when properly supporting this profile. + throw new UnsupportedOperationException( + "DEVICE_PROFILE_APP_STREAMING is not fully supported yet."); + } + + if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) { + throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile); + } + + final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile); + if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) { + throw new SecurityException("Application must hold " + permission + " to associate " + + "with a device with " + deviceProfile + " profile."); + } + } + + private void cleanup() { + if (DEBUG) { + Slog.d(TAG, "cleanup(); discovery = " + + mOngoingDeviceDiscovery + ", request = " + mRequest); + } + synchronized (mService.mLock) { + AndroidFuture ongoingDeviceDiscovery = mOngoingDeviceDiscovery; + if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) { + ongoingDeviceDiscovery.cancel(true); + } + if (mFindDeviceCallback != null) { + mFindDeviceCallback.asBinder().unlinkToDeath(mBinderDeathRecipient, 0); + mFindDeviceCallback = null; + } + mRequest = null; + mCallingPackage = null; + } + } + + private boolean mayAssociateWithoutPrompt(String packageName, int userId) { + String[] sameOemPackages = mContext.getResources() + .getStringArray(com.android.internal.R.array.config_companionDevicePackages); + if (!ArrayUtils.contains(sameOemPackages, packageName)) { + Slog.w(TAG, packageName + + " can not silently create associations due to no package found." + + " Packages from OEM: " + Arrays.toString(sameOemPackages) + ); + return false; + } + + // Throttle frequent associations + long now = System.currentTimeMillis(); + Set recentAssociations = filter( + mService.getAllAssociations(userId, packageName), + a -> now - a.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS); + + if (recentAssociations.size() >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) { + Slog.w(TAG, "Too many associations. " + packageName + + " already associated " + recentAssociations.size() + + " devices within the last " + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS + + "ms: " + recentAssociations); + return false; + } + String[] sameOemCerts = mContext.getResources() + .getStringArray(com.android.internal.R.array.config_companionDeviceCerts); + + Signature[] signatures = mService.mPackageManagerInternal + .getPackage(packageName).getSigningDetails().getSignatures(); + String[] apkCerts = PackageUtils.computeSignaturesSha256Digests(signatures); + + Set sameOemPackageCerts = + getSameOemPackageCerts(packageName, sameOemPackages, sameOemCerts); + + for (String cert : apkCerts) { + if (sameOemPackageCerts.contains(cert)) { + return true; + } + } + + Slog.w(TAG, packageName + + " can not silently create associations. " + packageName + + " has SHA256 certs from APK: " + Arrays.toString(apkCerts) + + " and from OEM: " + Arrays.toString(sameOemCerts) + ); + + return false; + } + + @NonNull + private AndroidFuture getDeviceProfilePermissionDescription( + @Nullable String deviceProfile) { + if (deviceProfile == null) { + return AndroidFuture.completedFuture(null); + } + + final AndroidFuture result = new AndroidFuture<>(); + mService.mPermissionControllerManager.getPrivilegesDescriptionStringForProfile( + deviceProfile, FgThread.getExecutor(), desc -> { + try { + result.complete(String.valueOf(desc)); + } catch (Exception e) { + result.completeExceptionally(e); + } + }); + return result; + } + + + void dump(@NonNull PrintWriter pw) { + pw.append("Discovery Service State:").append('\n'); + for (int i = 0, size = mServiceConnectors.size(); i < size; i++) { + int userId = mServiceConnectors.keyAt(i); + pw.append(" ") + .append("u").append(Integer.toString(userId)).append(": ") + .append(Objects.toString(mServiceConnectors.valueAt(i))) + .append('\n'); + } + } + + private final IBinder.DeathRecipient mBinderDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "binderDied()"); + } + mService.mMainHandler.post(AssociationRequestsProcessor.this::cleanup); + } + }; + + private static Set getSameOemPackageCerts( + String packageName, String[] oemPackages, String[] sameOemCerts) { + Set sameOemPackageCerts = new HashSet<>(); + + // Assume OEM may enter same package name in the parallel string array with + // multiple APK certs corresponding to it + for (int i = 0; i < oemPackages.length; i++) { + if (oemPackages[i].equals(packageName)) { + sameOemPackageCerts.add(sameOemCerts[i].replaceAll(":", "")); + } + } + + return sameOemPackageCerts; + } +} diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index a48172bff4df4..ad4c35c2e7558 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -19,8 +19,6 @@ package com.android.server.companion; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED; -import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING; -import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH; import static android.companion.DeviceId.TYPE_MAC_ADDRESS; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -31,7 +29,6 @@ import static com.android.internal.util.CollectionUtils.filter; import static com.android.internal.util.CollectionUtils.find; import static com.android.internal.util.CollectionUtils.forEach; import static com.android.internal.util.CollectionUtils.map; -import static com.android.internal.util.FunctionalUtils.uncheckExceptions; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkState; @@ -39,13 +36,10 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; import static java.util.Collections.emptySet; -import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MINUTES; -import android.Manifest; -import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -64,10 +58,8 @@ import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.companion.Association; import android.companion.AssociationRequest; -import android.companion.CompanionDeviceManager; import android.companion.DeviceId; import android.companion.DeviceNotAssociatedException; -import android.companion.ICompanionDeviceDiscoveryService; import android.companion.ICompanionDeviceManager; import android.companion.IFindDeviceCallback; import android.content.BroadcastReceiver; @@ -81,14 +73,11 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; -import android.content.pm.Signature; import android.content.pm.UserInfo; import android.net.NetworkPolicyManager; import android.os.Binder; import android.os.Environment; import android.os.Handler; -import android.os.IBinder; -import android.os.IInterface; import android.os.Parcel; import android.os.PowerWhitelistManager; import android.os.Process; @@ -103,7 +92,6 @@ import android.text.BidiFormatter; import android.util.ArrayMap; import android.util.ArraySet; import android.util.ExceptionUtils; -import android.util.PackageUtils; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -111,9 +99,6 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; import com.android.internal.content.PackageMonitor; -import com.android.internal.infra.AndroidFuture; -import com.android.internal.infra.PerUser; -import com.android.internal.infra.ServiceConnector; import com.android.internal.notification.NotificationAccessConfirmationActivityContract; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; @@ -146,27 +131,13 @@ import java.util.function.Predicate; /** @hide */ @SuppressLint("LongLogTag") -public class CompanionDeviceManagerService extends SystemService implements Binder.DeathRecipient { +public class CompanionDeviceManagerService extends SystemService { static final String LOG_TAG = "CompanionDeviceManagerService"; static final boolean DEBUG = false; - private static final Map DEVICE_PROFILE_TO_PERMISSION; - static { - final Map map = new ArrayMap<>(); - map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH); - map.put(DEVICE_PROFILE_APP_STREAMING, - Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING); - - DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map); - } - /** Range of Association IDs allocated for a user.*/ static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; - private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative( - CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, - ".CompanionDeviceDiscoveryService"); - private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000; private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000; @@ -177,9 +148,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; - private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5; - private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min; - private static DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); static { sDateFormat.setTimeZone(TimeZone.getDefault()); @@ -188,19 +156,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private final CompanionDeviceManagerImpl mImpl; // Persistent data store for all Associations. private final PersistentDataStore mPersistentDataStore; + private final AssociationRequestsProcessor mAssociationRequestsProcessor; private PowerWhitelistManager mPowerWhitelistManager; - private PerUser> mServiceConnectors; private IAppOpsService mAppOpsManager; private RoleManager mRoleManager; private BluetoothAdapter mBluetoothAdapter; private UserManager mUserManager; - private IFindDeviceCallback mFindDeviceCallback; private ScanCallback mBleScanCallback = new BleScanCallback(); - private AssociationRequest mRequest; - private String mCallingPackage; - private AndroidFuture mOngoingDeviceDiscovery; - private PermissionControllerManager mPermissionControllerManager; + PermissionControllerManager mPermissionControllerManager; private BluetoothDeviceConnectedListener mBluetoothDeviceConnectedListener = new BluetoothDeviceConnectedListener(); @@ -212,8 +176,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private ArrayMap mTriggerDeviceDisappearedRunnables = new ArrayMap<>(); - private final Object mLock = new Object(); - private final Handler mMainHandler = Handler.getMain(); + final Object mLock = new Object(); + final Handler mMainHandler = Handler.getMain(); private CompanionDevicePresenceController mCompanionDevicePresenceController; /** Maps a {@link UserIdInt} to a set of associations for the user. */ @@ -236,6 +200,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind super(context); mImpl = new CompanionDeviceManagerImpl(); mPersistentDataStore = new PersistentDataStore(); + mAssociationRequestsProcessor = new AssociationRequestsProcessor(this); mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); mRoleManager = context.getSystemService(RoleManager.class); @@ -249,17 +214,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind mUserManager = context.getSystemService(UserManager.class); mCompanionDevicePresenceController = new CompanionDevicePresenceController(); - Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO); - mServiceConnectors = new PerUser>() { - @Override - protected ServiceConnector create(int userId) { - return new ServiceConnector.Impl<>( - getContext(), - serviceIntent, 0/* bindingFlags */, userId, - ICompanionDeviceDiscoveryService.Stub::asInterface); - } - }; - registerPackageMonitor(); } @@ -357,39 +311,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } - @Override - public void binderDied() { - Slog.w(LOG_TAG, "binderDied()"); - mMainHandler.post(this::cleanup); - } - - private void cleanup() { - Slog.d(LOG_TAG, "cleanup(); discovery = " - + mOngoingDeviceDiscovery + ", request = " + mRequest); - synchronized (mLock) { - AndroidFuture ongoingDeviceDiscovery = mOngoingDeviceDiscovery; - if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) { - ongoingDeviceDiscovery.cancel(true); - } - mFindDeviceCallback = unlinkToDeath(mFindDeviceCallback, this, 0); - mRequest = null; - mCallingPackage = null; - } - } - - /** - * Usage: {@code a = unlinkToDeath(a, deathRecipient, flags); } - */ - @Nullable - @CheckResult - private static T unlinkToDeath(T iinterface, - IBinder.DeathRecipient deathRecipient, int flags) { - if (iinterface != null) { - iinterface.asBinder().unlinkToDeath(deathRecipient, flags); - } - return null; - } - class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { @Override @@ -410,61 +331,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind String callingPackage) throws RemoteException { Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback + ", callingPackage = " + callingPackage + ")"); - checkNotNull(request, "Request cannot be null"); - checkNotNull(callback, "Callback cannot be null"); - checkCallerIsSystemOr(callingPackage); - int userId = getCallingUserId(); - checkUsesFeature(callingPackage, userId); - final String deviceProfile = request.getDeviceProfile(); - validateDeviceProfileAndCheckPermission(deviceProfile); - - mFindDeviceCallback = callback; - mRequest = request; - mCallingPackage = callingPackage; - request.setCallingPackage(callingPackage); - - if (mayAssociateWithoutPrompt(callingPackage, userId)) { - Slog.i(LOG_TAG, "setSkipPrompt(true)"); - request.setSkipPrompt(true); - } - callback.asBinder().linkToDeath(CompanionDeviceManagerService.this /* recipient */, 0); - - mOngoingDeviceDiscovery = getDeviceProfilePermissionDescription(deviceProfile) - .thenComposeAsync(description -> { - Slog.d(LOG_TAG, "fetchProfileDescription done: " + description); - - request.setDeviceProfilePrivilegesDescription(description); - - return mServiceConnectors.forUser(userId).postAsync(service -> { - Slog.d(LOG_TAG, "Connected to CDM service; starting discovery for " + request); - - AndroidFuture future = new AndroidFuture<>(); - service.startDiscovery(request, callingPackage, callback, future); - return future; - }).cancelTimeout(); - - }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> { - if (err == null) { - createAssociationInternal( - userId, deviceAddress, callingPackage, deviceProfile); - } else { - Slog.e(LOG_TAG, "Failed to discover device(s)", err); - callback.onFailure("No devices found: " + err.getMessage()); - } - cleanup(); - })); + mAssociationRequestsProcessor.process(request, callback, callingPackage); } @Override public void stopScan(AssociationRequest request, IFindDeviceCallback callback, String callingPackage) { - Slog.d(LOG_TAG, "stopScan(request = " + request + ")"); - if (Objects.equals(request, mRequest) - && Objects.equals(callback, mFindDeviceCallback) - && Objects.equals(callingPackage, mCallingPackage)) { - cleanup(); - } + Slog.i(LOG_TAG, "stopScan(request = " + request + ")"); + mAssociationRequestsProcessor.stopScan(request, callback, callingPackage); } @Override @@ -505,44 +380,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind == PERMISSION_GRANTED; } - private void checkCallerIsSystemOr(String pkg) throws RemoteException { - checkCallerIsSystemOr(pkg, getCallingUserId()); - } - - private void checkCallerIsSystemOr(String pkg, int userId) throws RemoteException { - if (isCallerSystem()) { - return; - } - - checkArgument(getCallingUserId() == userId, - "Must be called by either same user or system"); - int callingUid = Binder.getCallingUid(); - if (mAppOpsManager.checkPackage(callingUid, pkg) != AppOpsManager.MODE_ALLOWED) { - throw new SecurityException(pkg + " doesn't belong to uid " + callingUid); - } - } - - private void validateDeviceProfileAndCheckPermission(@Nullable String deviceProfile) { - // Device profile can be null. - if (deviceProfile == null) return; - - if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) { - // TODO: remove, when properly supporting this profile. - throw new UnsupportedOperationException( - "DEVICE_PROFILE_APP_STREAMING is not fully supported yet."); - } - - if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) { - throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile); - } - - final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile); - if (getContext().checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) { - throw new SecurityException("Application must hold " + permission + " to associate " - + "with a device with " + deviceProfile + " profile."); - } - } - @Override public PendingIntent requestNotificationAccess(ComponentName component) throws RemoteException { @@ -674,23 +511,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind checkUsesFeature(callingPackage, userId); } - private void checkUsesFeature(String pkg, int userId) { - if (isCallerSystem()) { - // Drop the requirement for calls from system process - return; - } - - FeatureInfo[] reqFeatures = getPackageInfo(pkg, userId).reqFeatures; - String requiredFeature = PackageManager.FEATURE_COMPANION_DEVICE_SETUP; - int numFeatures = ArrayUtils.size(reqFeatures); - for (int i = 0; i < numFeatures; i++) { - if (requiredFeature.equals(reqFeatures[i].name)) return; - } - throw new IllegalStateException("Must declare uses-feature " - + requiredFeature - + " in manifest to use this API"); - } - @Override public boolean canPairWithoutPrompt( String packageName, String deviceMacAddress, int userId) { @@ -740,14 +560,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind .append(sDateFormat.format(time)).append('\n'); } - fout.append("Discovery Service State:").append('\n'); - for (int i = 0, size = mServiceConnectors.size(); i < size; i++) { - int userId = mServiceConnectors.keyAt(i); - fout.append(" ") - .append("u").append(Integer.toString(userId)).append(": ") - .append(Objects.toString(mServiceConnectors.valueAt(i))) - .append('\n'); - } + mAssociationRequestsProcessor.dump(fout); fout.append("Device Listener Services State:").append('\n'); for (int i = 0, size = mCompanionDevicePresenceController.mBoundServices.size(); @@ -762,7 +575,24 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } - private static int getCallingUserId() { + void checkCallerIsSystemOr(String pkg) throws RemoteException { + checkCallerIsSystemOr(pkg, getCallingUserId()); + } + + private void checkCallerIsSystemOr(String pkg, int userId) throws RemoteException { + if (isCallerSystem()) { + return; + } + + checkArgument(getCallingUserId() == userId, + "Must be called by either same user or system"); + int callingUid = Binder.getCallingUid(); + if (mAppOpsManager.checkPackage(callingUid, pkg) != AppOpsManager.MODE_ALLOWED) { + throw new SecurityException(pkg + " doesn't belong to uid " + callingUid); + } + } + + static int getCallingUserId() { return UserHandle.getUserId(Binder.getCallingUid()); } @@ -770,6 +600,23 @@ public class CompanionDeviceManagerService extends SystemService implements Bind return Binder.getCallingUid() == Process.SYSTEM_UID; } + void checkUsesFeature(String pkg, int userId) { + if (isCallerSystem()) { + // Drop the requirement for calls from system process + return; + } + + FeatureInfo[] reqFeatures = getPackageInfo(pkg, userId).reqFeatures; + String requiredFeature = PackageManager.FEATURE_COMPANION_DEVICE_SETUP; + int numFeatures = ArrayUtils.size(reqFeatures); + for (int i = 0; i < numFeatures; i++) { + if (requiredFeature.equals(reqFeatures[i].name)) return; + } + throw new IllegalStateException("Must declare uses-feature " + + requiredFeature + + " in manifest to use this API"); + } + void createAssociationInternal( int userId, String deviceMacAddress, String packageName, String deviceProfile) { final Association association = new Association( @@ -950,72 +797,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } - private Set getSameOemPackageCerts( - String packageName, String[] oemPackages, String[] sameOemCerts) { - Set sameOemPackageCerts = new HashSet<>(); - - // Assume OEM may enter same package name in the parallel string array with - // multiple APK certs corresponding to it - for (int i = 0; i < oemPackages.length; i++) { - if (oemPackages[i].equals(packageName)) { - sameOemPackageCerts.add(sameOemCerts[i].replaceAll(":", "")); - } - } - - return sameOemPackageCerts; - } - - boolean mayAssociateWithoutPrompt(String packageName, int userId) { - String[] sameOemPackages = getContext() - .getResources() - .getStringArray(com.android.internal.R.array.config_companionDevicePackages); - if (!ArrayUtils.contains(sameOemPackages, packageName)) { - Slog.w(LOG_TAG, packageName - + " can not silently create associations due to no package found." - + " Packages from OEM: " + Arrays.toString(sameOemPackages) - ); - return false; - } - - // Throttle frequent associations - long now = System.currentTimeMillis(); - Set recentAssociations = filter( - getAllAssociations(userId, packageName), - a -> now - a.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS); - - if (recentAssociations.size() >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) { - Slog.w(LOG_TAG, "Too many associations. " + packageName - + " already associated " + recentAssociations.size() - + " devices within the last " + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS - + "ms: " + recentAssociations); - return false; - } - String[] sameOemCerts = getContext() - .getResources() - .getStringArray(com.android.internal.R.array.config_companionDeviceCerts); - - Signature[] signatures = mPackageManagerInternal - .getPackage(packageName).getSigningDetails().getSignatures(); - String[] apkCerts = PackageUtils.computeSignaturesSha256Digests(signatures); - - Set sameOemPackageCerts = - getSameOemPackageCerts(packageName, sameOemPackages, sameOemCerts); - - for (String cert : apkCerts) { - if (sameOemPackageCerts.contains(cert)) { - return true; - } - } - - Slog.w(LOG_TAG, packageName - + " can not silently create associations. " + packageName - + " has SHA256 certs from APK: " + Arrays.toString(apkCerts) - + " and from OEM: " + Arrays.toString(sameOemCerts) - ); - - return false; - } - private static boolean containsEither(T[] array, T a, T b) { return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); } @@ -1120,7 +901,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } - private Set getAllAssociations(int userId, @Nullable String packageFilter) { + Set getAllAssociations(int userId, @Nullable String packageFilter) { return filter( getAllAssociations(userId), // Null filter == get all associations @@ -1444,25 +1225,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind return result; } - @NonNull - private AndroidFuture getDeviceProfilePermissionDescription( - @Nullable String deviceProfile) { - if (deviceProfile == null) { - return AndroidFuture.completedFuture(null); - } - - final AndroidFuture result = new AndroidFuture<>(); - mPermissionControllerManager.getPrivilegesDescriptionStringForProfile( - deviceProfile, FgThread.getExecutor(), desc -> { - try { - result.complete(String.valueOf(desc)); - } catch (Exception e) { - result.completeExceptionally(e); - } - }); - return result; - } - static int getFirstAssociationIdForUser(@UserIdInt int userId) { // We want the IDs to start from 1, not 0. return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;