Merge "Introduce CDM AssociationRequestsProcessor"
This commit is contained in:
committed by
Android (Google) Code Review
commit
9c502487fb
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user