Merge "MIDI: Add virtual devices per user" into udc-qpr-dev

This commit is contained in:
Robert Wu
2023-07-12 18:41:40 +00:00
committed by Android (Google) Code Review

View File

@@ -16,7 +16,9 @@
package com.android.server.midi;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
import android.content.BroadcastReceiver;
@@ -48,6 +50,7 @@ import android.os.ParcelUuid;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.EventLog;
import android.util.Log;
@@ -81,6 +84,11 @@ import java.util.concurrent.atomic.AtomicLong;
// 2. synchronized (mDeviceConnections)
//TODO Introduce a single lock object to lock the whole state and avoid the requirement above.
// All users should be able to connect to USB and Bluetooth MIDI devices.
// All users can create can install an app that provides, a Virtual MIDI Device Service.
// Users can not open virtual MIDI devices created by other users.
// getDevices() surfaces devices that can be opened by that user.
// openDevice() rejects devices that are cannot be opened by that user.
public class MidiService extends IMidiManager.Stub {
public static class Lifecycle extends SystemService {
@@ -97,10 +105,21 @@ public class MidiService extends IMidiManager.Stub {
}
@Override
@RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
anyOf = {Manifest.permission.QUERY_USERS,
Manifest.permission.CREATE_USERS,
Manifest.permission.MANAGE_USERS})
public void onUserStarting(@NonNull TargetUser user) {
mMidiService.onStartOrUnlockUser(user, false /* matchDirectBootUnaware */);
}
@Override
@RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
anyOf = {Manifest.permission.QUERY_USERS,
Manifest.permission.CREATE_USERS,
Manifest.permission.MANAGE_USERS})
public void onUserUnlocking(@NonNull TargetUser user) {
if (user.getUserIdentifier() == UserHandle.USER_SYSTEM) {
mMidiService.onUnlockUser();
}
mMidiService.onStartOrUnlockUser(user, true /* matchDirectBootUnaware */);
}
}
@@ -134,6 +153,7 @@ public class MidiService extends IMidiManager.Stub {
private int mNextDeviceId = 1;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
private static final String MIDI_LEGACY_STRING = "MIDI 1.0";
private static final String MIDI_UNIVERSAL_STRING = "MIDI 2.0";
@@ -159,21 +179,24 @@ public class MidiService extends IMidiManager.Stub {
private final HashSet<ParcelUuid> mNonMidiUUIDs = new HashSet<ParcelUuid>();
// PackageMonitor for listening to package changes
// uid is the uid of the package so use getChangingUserId() to fetch the userId.
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
public void onPackageAdded(String packageName, int uid) {
addPackageDeviceServers(packageName);
addPackageDeviceServers(packageName, getChangingUserId());
}
@Override
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
public void onPackageModified(String packageName) {
removePackageDeviceServers(packageName);
addPackageDeviceServers(packageName);
removePackageDeviceServers(packageName, getChangingUserId());
addPackageDeviceServers(packageName, getChangingUserId());
}
@Override
public void onPackageRemoved(String packageName, int uid) {
removePackageDeviceServers(packageName);
removePackageDeviceServers(packageName, getChangingUserId());
}
};
@@ -202,6 +225,10 @@ public class MidiService extends IMidiManager.Stub {
return mUid;
}
private int getUserId() {
return UserHandle.getUserId(mUid);
}
public void addListener(IMidiDeviceListener listener) {
if (mListeners.size() >= MAX_LISTENERS_PER_CLIENT) {
throw new SecurityException(
@@ -219,8 +246,12 @@ public class MidiService extends IMidiManager.Stub {
}
}
public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback) {
Log.d(TAG, "addDeviceConnection() device:" + device);
@RequiresPermission(anyOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
Manifest.permission.INTERACT_ACROSS_USERS,
Manifest.permission.INTERACT_ACROSS_PROFILES})
public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback,
int userId) {
Log.d(TAG, "addDeviceConnection() device:" + device + " userId:" + userId);
if (mDeviceConnections.size() >= MAX_CONNECTIONS_PER_CLIENT) {
Log.i(TAG, "too many MIDI connections for UID = " + mUid);
throw new SecurityException(
@@ -228,7 +259,7 @@ public class MidiService extends IMidiManager.Stub {
}
DeviceConnection connection = new DeviceConnection(device, this, callback);
mDeviceConnections.put(connection.getToken(), connection);
device.addDeviceConnection(connection);
device.addDeviceConnection(connection, userId);
}
// called from MidiService.closeDevice()
@@ -251,8 +282,8 @@ public class MidiService extends IMidiManager.Stub {
}
public void deviceAdded(Device device) {
// ignore private devices that our client cannot access
if (!device.isUidAllowed(mUid)) return;
// ignore devices that our client cannot access
if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
@@ -265,8 +296,8 @@ public class MidiService extends IMidiManager.Stub {
}
public void deviceRemoved(Device device) {
// ignore private devices that our client cannot access
if (!device.isUidAllowed(mUid)) return;
// ignore devices that our client cannot access
if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
MidiDeviceInfo deviceInfo = device.getDeviceInfo();
try {
@@ -279,8 +310,8 @@ public class MidiService extends IMidiManager.Stub {
}
public void deviceStatusChanged(Device device, MidiDeviceStatus status) {
// ignore private devices that our client cannot access
if (!device.isUidAllowed(mUid)) return;
// ignore devices that our client cannot access
if (!device.isUidAllowed(mUid) || !device.isUserIdAllowed(getUserId())) return;
try {
for (IMidiDeviceListener listener : mListeners.values()) {
@@ -354,6 +385,8 @@ public class MidiService extends IMidiManager.Stub {
private final ServiceInfo mServiceInfo;
// UID of device implementation
private final int mUid;
// User Id of the app. Only used for virtual devices
private final int mUserId;
// ServiceConnection for implementing Service (virtual devices only)
// mServiceConnection is non-null when connected or attempting to connect to the service
@@ -375,19 +408,24 @@ public class MidiService extends IMidiManager.Stub {
private AtomicInteger mTotalOutputBytes = new AtomicInteger();
public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo,
ServiceInfo serviceInfo, int uid) {
ServiceInfo serviceInfo, int uid, int userId) {
mDeviceInfo = deviceInfo;
mServiceInfo = serviceInfo;
mUid = uid;
mUserId = userId;
mBluetoothDevice = (BluetoothDevice)deviceInfo.getProperties().getParcelable(
MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE, android.bluetooth.BluetoothDevice.class);;
setDeviceServer(server);
}
@RequiresPermission(anyOf = {Manifest.permission.QUERY_USERS,
Manifest.permission.CREATE_USERS,
Manifest.permission.MANAGE_USERS})
public Device(BluetoothDevice bluetoothDevice) {
mBluetoothDevice = bluetoothDevice;
mServiceInfo = null;
mUid = mBluetoothServiceUid;
mUserId = mUserManager.getMainUser().getIdentifier();
}
private void setDeviceServer(IMidiDeviceServer server) {
@@ -468,11 +506,22 @@ public class MidiService extends IMidiManager.Stub {
return mUid;
}
public int getUserId() {
return mUserId;
}
public boolean isUidAllowed(int uid) {
return (!mDeviceInfo.isPrivate() || mUid == uid);
}
public void addDeviceConnection(DeviceConnection connection) {
public boolean isUserIdAllowed(int userId) {
return (mDeviceInfo.getType() != MidiDeviceInfo.TYPE_VIRTUAL || mUserId == userId);
}
@RequiresPermission(anyOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
Manifest.permission.INTERACT_ACROSS_USERS,
Manifest.permission.INTERACT_ACROSS_PROFILES})
public void addDeviceConnection(DeviceConnection connection, int userId) {
Log.d(TAG, "addDeviceConnection() [A] connection:" + connection);
synchronized (mDeviceConnections) {
mDeviceConnectionsAdded.incrementAndGet();
@@ -537,8 +586,8 @@ public class MidiService extends IMidiManager.Stub {
new ComponentName(mServiceInfo.packageName, mServiceInfo.name));
}
if (!mContext.bindService(intent, mServiceConnection,
Context.BIND_AUTO_CREATE)) {
if (!mContext.bindServiceAsUser(intent, mServiceConnection,
Context.BIND_AUTO_CREATE, UserHandle.of(mUserId))) {
Log.e(TAG, "Unable to bind service: " + intent);
setDeviceServer(null);
mServiceConnection = null;
@@ -886,6 +935,8 @@ public class MidiService extends IMidiManager.Stub {
public MidiService(Context context) {
mContext = context;
mPackageManager = context.getPackageManager();
mUserManager = mContext.getSystemService(UserManager.class);
mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
// TEMPORARY - Disable BTL-MIDI
//FIXME - b/25689266
@@ -913,32 +964,41 @@ public class MidiService extends IMidiManager.Stub {
// mNonMidiUUIDs.add(BluetoothUuid.BATTERY);
}
private void onUnlockUser() {
mPackageMonitor.register(mContext, null, true);
@RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS},
anyOf = {Manifest.permission.QUERY_USERS,
Manifest.permission.CREATE_USERS,
Manifest.permission.MANAGE_USERS})
private void onStartOrUnlockUser(TargetUser user, boolean matchDirectBootUnaware) {
Log.d(TAG, "onStartOrUnlockUser " + user.getUserIdentifier() + " matchDirectBootUnaware: "
+ matchDirectBootUnaware);
Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(intent,
PackageManager.GET_META_DATA);
int resolveFlags = PackageManager.GET_META_DATA;
if (matchDirectBootUnaware) {
resolveFlags |= PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
}
List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServicesAsUser(intent,
resolveFlags, user.getUserIdentifier());
if (resolveInfos != null) {
int count = resolveInfos.size();
for (int i = 0; i < count; i++) {
ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
if (serviceInfo != null) {
addPackageDeviceServer(serviceInfo);
addPackageDeviceServer(serviceInfo, user.getUserIdentifier());
}
}
}
PackageInfo info;
try {
info = mPackageManager.getPackageInfo(MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0);
} catch (PackageManager.NameNotFoundException e) {
info = null;
}
if (info != null && info.applicationInfo != null) {
mBluetoothServiceUid = info.applicationInfo.uid;
} else {
mBluetoothServiceUid = -1;
if (user.getUserIdentifier() == mUserManager.getMainUser().getIdentifier()) {
PackageInfo info;
try {
info = mPackageManager.getPackageInfoAsUser(
MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0, user.getUserIdentifier());
} catch (PackageManager.NameNotFoundException e) {
info = null;
}
if (info != null && info.applicationInfo != null) {
mBluetoothServiceUid = info.applicationInfo.uid;
}
}
}
@@ -960,10 +1020,11 @@ public class MidiService extends IMidiManager.Stub {
// Inform listener of the status of all known devices.
private void updateStickyDeviceStatus(int uid, IMidiDeviceListener listener) {
int userId = UserHandle.getUserId(uid);
synchronized (mDevicesByInfo) {
for (Device device : mDevicesByInfo.values()) {
// ignore private devices that our client cannot access
if (device.isUidAllowed(uid)) {
// ignore devices that our client cannot access
if (device.isUidAllowed(uid) && device.isUserIdAllowed(userId)) {
try {
MidiDeviceStatus status = device.getDeviceStatus();
if (status != null) {
@@ -989,10 +1050,11 @@ public class MidiService extends IMidiManager.Stub {
public MidiDeviceInfo[] getDevicesForTransport(int transport) {
ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
int uid = Binder.getCallingUid();
int userId = getCallingUserId();
synchronized (mDevicesByInfo) {
for (Device device : mDevicesByInfo.values()) {
if (device.isUidAllowed(uid)) {
if (device.isUidAllowed(uid) && device.isUserIdAllowed(userId)) {
// UMP devices have protocols that are not PROTOCOL_UNKNOWN
if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
if (device.getDeviceInfo().getDefaultProtocol()
@@ -1029,6 +1091,9 @@ public class MidiService extends IMidiManager.Stub {
if (!device.isUidAllowed(Binder.getCallingUid())) {
throw new SecurityException("Attempt to open private device with wrong UID");
}
if (!device.isUserIdAllowed(getCallingUserId())) {
throw new SecurityException("Attempt to open virtual device with wrong user id");
}
}
if (deviceInfo.getType() == MidiDeviceInfo.TYPE_USB) {
@@ -1044,7 +1109,7 @@ public class MidiService extends IMidiManager.Stub {
final long identity = Binder.clearCallingIdentity();
try {
Log.i(TAG, "addDeviceConnection() [B] device:" + device);
client.addDeviceConnection(device, callback);
client.addDeviceConnection(device, callback, getCallingUserId());
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1106,7 +1171,7 @@ public class MidiService extends IMidiManager.Stub {
final long identity = Binder.clearCallingIdentity();
try {
Log.i(TAG, "addDeviceConnection() [C] device:" + device);
client.addDeviceConnection(device, callback);
client.addDeviceConnection(device, callback, getCallingUserId());
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1124,6 +1189,7 @@ public class MidiService extends IMidiManager.Stub {
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
Bundle properties, int type, int defaultProtocol) {
int uid = Binder.getCallingUid();
int userId = getCallingUserId();
if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
throw new SecurityException("only system can create USB devices");
} else if (type == MidiDeviceInfo.TYPE_BLUETOOTH && uid != mBluetoothServiceUid) {
@@ -1133,7 +1199,7 @@ public class MidiService extends IMidiManager.Stub {
synchronized (mDevicesByInfo) {
return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames,
outputPortNames, properties, server, null, false, uid,
defaultProtocol);
defaultProtocol, userId);
}
}
@@ -1210,7 +1276,8 @@ public class MidiService extends IMidiManager.Stub {
private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
String[] inputPortNames, String[] outputPortNames, Bundle properties,
IMidiDeviceServer server, ServiceInfo serviceInfo,
boolean isPrivate, int uid, int defaultProtocol) {
boolean isPrivate, int uid, int defaultProtocol, int userId) {
Log.d(TAG, "addDeviceLocked()" + uid + " type:" + type);
// Limit the number of devices per app.
int deviceCountForApp = 0;
@@ -1250,7 +1317,7 @@ public class MidiService extends IMidiManager.Stub {
}
}
if (device == null) {
device = new Device(server, deviceInfo, serviceInfo, uid);
device = new Device(server, deviceInfo, serviceInfo, uid, userId);
}
mDevicesByInfo.put(deviceInfo, device);
if (bluetoothDevice != null) {
@@ -1281,12 +1348,14 @@ public class MidiService extends IMidiManager.Stub {
}
}
private void addPackageDeviceServers(String packageName) {
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
private void addPackageDeviceServers(String packageName, int userId) {
PackageInfo info;
try {
info = mPackageManager.getPackageInfo(packageName,
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
info = mPackageManager.getPackageInfoAsUser(packageName,
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
return;
@@ -1295,13 +1364,14 @@ public class MidiService extends IMidiManager.Stub {
ServiceInfo[] services = info.services;
if (services == null) return;
for (int i = 0; i < services.length; i++) {
addPackageDeviceServer(services[i]);
addPackageDeviceServer(services[i], userId);
}
}
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private void addPackageDeviceServer(ServiceInfo serviceInfo) {
private void addPackageDeviceServer(ServiceInfo serviceInfo, int userId) {
Log.d(TAG, "addPackageDeviceServer()" + userId);
XmlResourceParser parser = null;
try {
@@ -1404,8 +1474,8 @@ public class MidiService extends IMidiManager.Stub {
int uid;
try {
ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
serviceInfo.packageName, 0);
ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
serviceInfo.packageName, 0, userId);
uid = appInfo.uid;
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "could not fetch ApplicationInfo for "
@@ -1419,7 +1489,7 @@ public class MidiService extends IMidiManager.Stub {
inputPortNames.toArray(EMPTY_STRING_ARRAY),
outputPortNames.toArray(EMPTY_STRING_ARRAY),
properties, null, serviceInfo, isPrivate, uid,
MidiDeviceInfo.PROTOCOL_UNKNOWN);
MidiDeviceInfo.PROTOCOL_UNKNOWN, userId);
}
// setting properties to null signals that we are no longer
// processing a <device>
@@ -1437,12 +1507,13 @@ public class MidiService extends IMidiManager.Stub {
}
}
private void removePackageDeviceServers(String packageName) {
private void removePackageDeviceServers(String packageName, int userId) {
synchronized (mDevicesByInfo) {
Iterator<Device> iterator = mDevicesByInfo.values().iterator();
while (iterator.hasNext()) {
Device device = iterator.next();
if (packageName.equals(device.getPackageName())) {
if (packageName.equals(device.getPackageName())
&& (device.getUserId() == userId)) {
iterator.remove();
removeDeviceLocked(device);
}
@@ -1571,4 +1642,11 @@ public class MidiService extends IMidiManager.Stub {
String extractUsbDeviceTag(String propertyName) {
return propertyName.substring(propertyName.length() - MIDI_LEGACY_STRING.length());
}
/**
* @return the user id of the calling user.
*/
private int getCallingUserId() {
return UserHandle.getUserId(Binder.getCallingUid());
}
}