diff options
| -rw-r--r-- | services/midi/java/com/android/server/midi/MidiService.java | 186 |
1 files changed, 132 insertions, 54 deletions
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 4aba30a661ad..f660b42457db 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -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()); + } } |