Merge "Introduce CDM AssociationRequestsProcessor"

This commit is contained in:
Sergey Nikolaienkov
2021-10-26 07:36:17 +00:00
committed by Android (Google) Code Review
2 changed files with 366 additions and 284 deletions

View File

@@ -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<String, String> DEVICE_PROFILE_TO_PERMISSION;
static {
final Map<String, String> 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<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
AssociationRequestsProcessor(CompanionDeviceManagerService service) {
mContext = service.getContext();
mService = service;
final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
@Override
protected ServiceConnector<ICompanionDeviceDiscoveryService> 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<String> 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<Association> 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<String> 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<String> getDeviceProfilePermissionDescription(
@Nullable String deviceProfile) {
if (deviceProfile == null) {
return AndroidFuture.completedFuture(null);
}
final AndroidFuture<String> 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<String> getSameOemPackageCerts(
String packageName, String[] oemPackages, String[] sameOemCerts) {
Set<String> 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;
}
}

View File

@@ -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<String, String> DEVICE_PROFILE_TO_PERMISSION;
static {
final Map<String, String> 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<ServiceConnector<ICompanionDeviceDiscoveryService>> 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<String, TriggerDeviceDisappearedRunnable> 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<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
@Override
protected ServiceConnector<ICompanionDeviceDiscoveryService> 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 extends IInterface> 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<String> 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<String> getSameOemPackageCerts(
String packageName, String[] oemPackages, String[] sameOemCerts) {
Set<String> 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<Association> 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<String> 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 <T> 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<Association> getAllAssociations(int userId, @Nullable String packageFilter) {
Set<Association> 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<String> getDeviceProfilePermissionDescription(
@Nullable String deviceProfile) {
if (deviceProfile == null) {
return AndroidFuture.completedFuture(null);
}
final AndroidFuture<String> 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;