Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e063827 authored by Robert Wu's avatar Robert Wu Committed by Android (Google) Code Review
Browse files

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

parents 2da476b3 32c75a0a
Loading
Loading
Loading
Loading
+132 −54
Original line number Diff line number Diff line
@@ -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
        public void onUserUnlocking(@NonNull TargetUser user) {
            if (user.getUserIdentifier()  == UserHandle.USER_SYSTEM) {
                mMidiService.onUnlockUser();
        @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) {
            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());
                }
            }
        }

        if (user.getUserIdentifier() == mUserManager.getMainUser().getIdentifier()) {
            PackageInfo info;
            try {
            info = mPackageManager.getPackageInfo(MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0);
                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;
        } else {
            mBluetoothServiceUid = -1;
            }
        }
    }

@@ -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());
    }
}