| /* | |
| * Copyright (C) 2014 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 an | |
| * limitations under the License. | |
| */ | |
| package com.android.server.midi; | |
| import android.bluetooth.BluetoothDevice; | |
| import android.content.ComponentName; | |
| import android.content.Context; | |
| import android.content.Intent; | |
| import android.content.ServiceConnection; | |
| import android.content.pm.ApplicationInfo; | |
| import android.content.pm.PackageInfo; | |
| import android.content.pm.PackageManager; | |
| import android.content.pm.ResolveInfo; | |
| import android.content.pm.ServiceInfo; | |
| import android.content.res.XmlResourceParser; | |
| import android.media.midi.IBluetoothMidiService; | |
| import android.media.midi.IMidiDeviceListener; | |
| import android.media.midi.IMidiDeviceOpenCallback; | |
| import android.media.midi.IMidiDeviceServer; | |
| import android.media.midi.IMidiManager; | |
| import android.media.midi.MidiDeviceInfo; | |
| import android.media.midi.MidiDeviceService; | |
| import android.media.midi.MidiDeviceStatus; | |
| import android.media.midi.MidiManager; | |
| import android.os.Binder; | |
| import android.os.Bundle; | |
| import android.os.IBinder; | |
| import android.os.Process; | |
| import android.os.RemoteException; | |
| import android.os.UserHandle; | |
| import android.util.Log; | |
| import com.android.internal.content.PackageMonitor; | |
| import com.android.internal.util.DumpUtils; | |
| import com.android.internal.util.IndentingPrintWriter; | |
| import com.android.internal.util.XmlUtils; | |
| import com.android.server.SystemService; | |
| import org.xmlpull.v1.XmlPullParser; | |
| import java.io.FileDescriptor; | |
| import java.io.PrintWriter; | |
| import java.util.ArrayList; | |
| import java.util.HashMap; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| public class MidiService extends IMidiManager.Stub { | |
| public static class Lifecycle extends SystemService { | |
| private MidiService mMidiService; | |
| public Lifecycle(Context context) { | |
| super(context); | |
| } | |
| @Override | |
| public void onStart() { | |
| mMidiService = new MidiService(getContext()); | |
| publishBinderService(Context.MIDI_SERVICE, mMidiService); | |
| } | |
| @Override | |
| public void onUnlockUser(int userHandle) { | |
| if (userHandle == UserHandle.USER_SYSTEM) { | |
| mMidiService.onUnlockUser(); | |
| } | |
| } | |
| } | |
| private static final String TAG = "MidiService"; | |
| private final Context mContext; | |
| // list of all our clients, keyed by Binder token | |
| private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>(); | |
| // list of all devices, keyed by MidiDeviceInfo | |
| private final HashMap<MidiDeviceInfo, Device> mDevicesByInfo | |
| = new HashMap<MidiDeviceInfo, Device>(); | |
| // list of all Bluetooth devices, keyed by BluetoothDevice | |
| private final HashMap<BluetoothDevice, Device> mBluetoothDevices | |
| = new HashMap<BluetoothDevice, Device>(); | |
| // list of all devices, keyed by IMidiDeviceServer | |
| private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>(); | |
| // used for assigning IDs to MIDI devices | |
| private int mNextDeviceId = 1; | |
| private final PackageManager mPackageManager; | |
| // UID of BluetoothMidiService | |
| private int mBluetoothServiceUid; | |
| // PackageMonitor for listening to package changes | |
| private final PackageMonitor mPackageMonitor = new PackageMonitor() { | |
| @Override | |
| public void onPackageAdded(String packageName, int uid) { | |
| addPackageDeviceServers(packageName); | |
| } | |
| @Override | |
| public void onPackageModified(String packageName) { | |
| removePackageDeviceServers(packageName); | |
| addPackageDeviceServers(packageName); | |
| } | |
| @Override | |
| public void onPackageRemoved(String packageName, int uid) { | |
| removePackageDeviceServers(packageName); | |
| } | |
| }; | |
| private final class Client implements IBinder.DeathRecipient { | |
| // Binder token for this client | |
| private final IBinder mToken; | |
| // This client's UID | |
| private final int mUid; | |
| // This client's PID | |
| private final int mPid; | |
| // List of all receivers for this client | |
| private final HashMap<IBinder, IMidiDeviceListener> mListeners | |
| = new HashMap<IBinder, IMidiDeviceListener>(); | |
| // List of all device connections for this client | |
| private final HashMap<IBinder, DeviceConnection> mDeviceConnections | |
| = new HashMap<IBinder, DeviceConnection>(); | |
| public Client(IBinder token) { | |
| mToken = token; | |
| mUid = Binder.getCallingUid(); | |
| mPid = Binder.getCallingPid(); | |
| } | |
| public int getUid() { | |
| return mUid; | |
| } | |
| public void addListener(IMidiDeviceListener listener) { | |
| // Use asBinder() so that we can match it in removeListener(). | |
| // The listener proxy objects themselves do not match. | |
| mListeners.put(listener.asBinder(), listener); | |
| } | |
| public void removeListener(IMidiDeviceListener listener) { | |
| mListeners.remove(listener.asBinder()); | |
| if (mListeners.size() == 0 && mDeviceConnections.size() == 0) { | |
| close(); | |
| } | |
| } | |
| public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback) { | |
| DeviceConnection connection = new DeviceConnection(device, this, callback); | |
| mDeviceConnections.put(connection.getToken(), connection); | |
| device.addDeviceConnection(connection); | |
| } | |
| // called from MidiService.closeDevice() | |
| public void removeDeviceConnection(IBinder token) { | |
| DeviceConnection connection = mDeviceConnections.remove(token); | |
| if (connection != null) { | |
| connection.getDevice().removeDeviceConnection(connection); | |
| } | |
| if (mListeners.size() == 0 && mDeviceConnections.size() == 0) { | |
| close(); | |
| } | |
| } | |
| // called from Device.close() | |
| public void removeDeviceConnection(DeviceConnection connection) { | |
| mDeviceConnections.remove(connection.getToken()); | |
| if (mListeners.size() == 0 && mDeviceConnections.size() == 0) { | |
| close(); | |
| } | |
| } | |
| public void deviceAdded(Device device) { | |
| // ignore private devices that our client cannot access | |
| if (!device.isUidAllowed(mUid)) return; | |
| MidiDeviceInfo deviceInfo = device.getDeviceInfo(); | |
| try { | |
| for (IMidiDeviceListener listener : mListeners.values()) { | |
| listener.onDeviceAdded(deviceInfo); | |
| } | |
| } catch (RemoteException e) { | |
| Log.e(TAG, "remote exception", e); | |
| } | |
| } | |
| public void deviceRemoved(Device device) { | |
| // ignore private devices that our client cannot access | |
| if (!device.isUidAllowed(mUid)) return; | |
| MidiDeviceInfo deviceInfo = device.getDeviceInfo(); | |
| try { | |
| for (IMidiDeviceListener listener : mListeners.values()) { | |
| listener.onDeviceRemoved(deviceInfo); | |
| } | |
| } catch (RemoteException e) { | |
| Log.e(TAG, "remote exception", e); | |
| } | |
| } | |
| public void deviceStatusChanged(Device device, MidiDeviceStatus status) { | |
| // ignore private devices that our client cannot access | |
| if (!device.isUidAllowed(mUid)) return; | |
| try { | |
| for (IMidiDeviceListener listener : mListeners.values()) { | |
| listener.onDeviceStatusChanged(status); | |
| } | |
| } catch (RemoteException e) { | |
| Log.e(TAG, "remote exception", e); | |
| } | |
| } | |
| private void close() { | |
| synchronized (mClients) { | |
| mClients.remove(mToken); | |
| mToken.unlinkToDeath(this, 0); | |
| } | |
| for (DeviceConnection connection : mDeviceConnections.values()) { | |
| connection.getDevice().removeDeviceConnection(connection); | |
| } | |
| } | |
| @Override | |
| public void binderDied() { | |
| Log.d(TAG, "Client died: " + this); | |
| close(); | |
| } | |
| @Override | |
| public String toString() { | |
| StringBuilder sb = new StringBuilder("Client: UID: "); | |
| sb.append(mUid); | |
| sb.append(" PID: "); | |
| sb.append(mPid); | |
| sb.append(" listener count: "); | |
| sb.append(mListeners.size()); | |
| sb.append(" Device Connections:"); | |
| for (DeviceConnection connection : mDeviceConnections.values()) { | |
| sb.append(" <device "); | |
| sb.append(connection.getDevice().getDeviceInfo().getId()); | |
| sb.append(">"); | |
| } | |
| return sb.toString(); | |
| } | |
| } | |
| private Client getClient(IBinder token) { | |
| synchronized (mClients) { | |
| Client client = mClients.get(token); | |
| if (client == null) { | |
| client = new Client(token); | |
| try { | |
| token.linkToDeath(client, 0); | |
| } catch (RemoteException e) { | |
| return null; | |
| } | |
| mClients.put(token, client); | |
| } | |
| return client; | |
| } | |
| } | |
| private final class Device implements IBinder.DeathRecipient { | |
| private IMidiDeviceServer mServer; | |
| private MidiDeviceInfo mDeviceInfo; | |
| private final BluetoothDevice mBluetoothDevice; | |
| private MidiDeviceStatus mDeviceStatus; | |
| // ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only) | |
| private final ServiceInfo mServiceInfo; | |
| // UID of device implementation | |
| private final int mUid; | |
| // ServiceConnection for implementing Service (virtual devices only) | |
| // mServiceConnection is non-null when connected or attempting to connect to the service | |
| private ServiceConnection mServiceConnection; | |
| // List of all device connections for this device | |
| private final ArrayList<DeviceConnection> mDeviceConnections | |
| = new ArrayList<DeviceConnection>(); | |
| public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo, | |
| ServiceInfo serviceInfo, int uid) { | |
| mDeviceInfo = deviceInfo; | |
| mServiceInfo = serviceInfo; | |
| mUid = uid; | |
| mBluetoothDevice = (BluetoothDevice)deviceInfo.getProperties().getParcelable( | |
| MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE);; | |
| setDeviceServer(server); | |
| } | |
| public Device(BluetoothDevice bluetoothDevice) { | |
| mBluetoothDevice = bluetoothDevice; | |
| mServiceInfo = null; | |
| mUid = mBluetoothServiceUid; | |
| } | |
| private void setDeviceServer(IMidiDeviceServer server) { | |
| if (server != null) { | |
| if (mServer != null) { | |
| Log.e(TAG, "mServer already set in setDeviceServer"); | |
| return; | |
| } | |
| IBinder binder = server.asBinder(); | |
| try { | |
| binder.linkToDeath(this, 0); | |
| mServer = server; | |
| } catch (RemoteException e) { | |
| mServer = null; | |
| return; | |
| } | |
| mDevicesByServer.put(binder, this); | |
| } else if (mServer != null) { | |
| server = mServer; | |
| mServer = null; | |
| IBinder binder = server.asBinder(); | |
| mDevicesByServer.remove(binder); | |
| try { | |
| server.closeDevice(); | |
| binder.unlinkToDeath(this, 0); | |
| } catch (RemoteException e) { | |
| // nothing to do here | |
| } | |
| } | |
| if (mDeviceConnections != null) { | |
| for (DeviceConnection connection : mDeviceConnections) { | |
| connection.notifyClient(server); | |
| } | |
| } | |
| } | |
| public MidiDeviceInfo getDeviceInfo() { | |
| return mDeviceInfo; | |
| } | |
| // only used for bluetooth devices, which are created before we have a MidiDeviceInfo | |
| public void setDeviceInfo(MidiDeviceInfo deviceInfo) { | |
| mDeviceInfo = deviceInfo; | |
| } | |
| public MidiDeviceStatus getDeviceStatus() { | |
| return mDeviceStatus; | |
| } | |
| public void setDeviceStatus(MidiDeviceStatus status) { | |
| mDeviceStatus = status; | |
| } | |
| public IMidiDeviceServer getDeviceServer() { | |
| return mServer; | |
| } | |
| public ServiceInfo getServiceInfo() { | |
| return mServiceInfo; | |
| } | |
| public String getPackageName() { | |
| return (mServiceInfo == null ? null : mServiceInfo.packageName); | |
| } | |
| public int getUid() { | |
| return mUid; | |
| } | |
| public boolean isUidAllowed(int uid) { | |
| return (!mDeviceInfo.isPrivate() || mUid == uid); | |
| } | |
| public void addDeviceConnection(DeviceConnection connection) { | |
| synchronized (mDeviceConnections) { | |
| if (mServer != null) { | |
| mDeviceConnections.add(connection); | |
| connection.notifyClient(mServer); | |
| } else if (mServiceConnection == null && | |
| (mServiceInfo != null || mBluetoothDevice != null)) { | |
| mDeviceConnections.add(connection); | |
| mServiceConnection = new ServiceConnection() { | |
| @Override | |
| public void onServiceConnected(ComponentName name, IBinder service) { | |
| IMidiDeviceServer server = null; | |
| if (mBluetoothDevice != null) { | |
| IBluetoothMidiService mBluetoothMidiService = IBluetoothMidiService.Stub.asInterface(service); | |
| try { | |
| // We need to explicitly add the device in a separate method | |
| // because onBind() is only called once. | |
| IBinder deviceBinder = mBluetoothMidiService.addBluetoothDevice(mBluetoothDevice); | |
| server = IMidiDeviceServer.Stub.asInterface(deviceBinder); | |
| } catch(RemoteException e) { | |
| Log.e(TAG, "Could not call addBluetoothDevice()", e); | |
| } | |
| } else { | |
| server = IMidiDeviceServer.Stub.asInterface(service); | |
| } | |
| setDeviceServer(server); | |
| } | |
| @Override | |
| public void onServiceDisconnected(ComponentName name) { | |
| setDeviceServer(null); | |
| mServiceConnection = null; | |
| } | |
| }; | |
| Intent intent; | |
| if (mBluetoothDevice != null) { | |
| intent = new Intent(MidiManager.BLUETOOTH_MIDI_SERVICE_INTENT); | |
| intent.setComponent(new ComponentName( | |
| MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, | |
| MidiManager.BLUETOOTH_MIDI_SERVICE_CLASS)); | |
| } else { | |
| intent = new Intent(MidiDeviceService.SERVICE_INTERFACE); | |
| intent.setComponent( | |
| new ComponentName(mServiceInfo.packageName, mServiceInfo.name)); | |
| } | |
| if (!mContext.bindService(intent, mServiceConnection, | |
| Context.BIND_AUTO_CREATE)) { | |
| Log.e(TAG, "Unable to bind service: " + intent); | |
| setDeviceServer(null); | |
| mServiceConnection = null; | |
| } | |
| } else { | |
| Log.e(TAG, "No way to connect to device in addDeviceConnection"); | |
| connection.notifyClient(null); | |
| } | |
| } | |
| } | |
| public void removeDeviceConnection(DeviceConnection connection) { | |
| synchronized (mDeviceConnections) { | |
| mDeviceConnections.remove(connection); | |
| if (mDeviceConnections.size() == 0 && mServiceConnection != null) { | |
| mContext.unbindService(mServiceConnection); | |
| mServiceConnection = null; | |
| if (mBluetoothDevice != null) { | |
| // Bluetooth devices are ephemeral - remove when no clients exist | |
| synchronized (mDevicesByInfo) { | |
| closeLocked(); | |
| } | |
| } else { | |
| setDeviceServer(null); | |
| } | |
| } | |
| } | |
| } | |
| // synchronize on mDevicesByInfo | |
| public void closeLocked() { | |
| synchronized (mDeviceConnections) { | |
| for (DeviceConnection connection : mDeviceConnections) { | |
| connection.getClient().removeDeviceConnection(connection); | |
| } | |
| mDeviceConnections.clear(); | |
| } | |
| setDeviceServer(null); | |
| // closed virtual devices should not be removed from mDevicesByInfo | |
| // since they can be restarted on demand | |
| if (mServiceInfo == null) { | |
| removeDeviceLocked(this); | |
| } else { | |
| mDeviceStatus = new MidiDeviceStatus(mDeviceInfo); | |
| } | |
| if (mBluetoothDevice != null) { | |
| mBluetoothDevices.remove(mBluetoothDevice); | |
| } | |
| } | |
| @Override | |
| public void binderDied() { | |
| Log.d(TAG, "Device died: " + this); | |
| synchronized (mDevicesByInfo) { | |
| closeLocked(); | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| StringBuilder sb = new StringBuilder("Device Info: "); | |
| sb.append(mDeviceInfo); | |
| sb.append(" Status: "); | |
| sb.append(mDeviceStatus); | |
| sb.append(" UID: "); | |
| sb.append(mUid); | |
| sb.append(" DeviceConnection count: "); | |
| sb.append(mDeviceConnections.size()); | |
| sb.append(" mServiceConnection: "); | |
| sb.append(mServiceConnection); | |
| return sb.toString(); | |
| } | |
| } | |
| // Represents a connection between a client and a device | |
| private final class DeviceConnection { | |
| private final IBinder mToken = new Binder(); | |
| private final Device mDevice; | |
| private final Client mClient; | |
| private IMidiDeviceOpenCallback mCallback; | |
| public DeviceConnection(Device device, Client client, IMidiDeviceOpenCallback callback) { | |
| mDevice = device; | |
| mClient = client; | |
| mCallback = callback; | |
| } | |
| public Device getDevice() { | |
| return mDevice; | |
| } | |
| public Client getClient() { | |
| return mClient; | |
| } | |
| public IBinder getToken() { | |
| return mToken; | |
| } | |
| public void notifyClient(IMidiDeviceServer deviceServer) { | |
| if (mCallback != null) { | |
| try { | |
| mCallback.onDeviceOpened(deviceServer, (deviceServer == null ? null : mToken)); | |
| } catch (RemoteException e) { | |
| // Client binderDied() method will do necessary cleanup, so nothing to do here | |
| } | |
| mCallback = null; | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| return "DeviceConnection Device ID: " + mDevice.getDeviceInfo().getId(); | |
| } | |
| } | |
| public MidiService(Context context) { | |
| mContext = context; | |
| mPackageManager = context.getPackageManager(); | |
| mBluetoothServiceUid = -1; | |
| } | |
| private void onUnlockUser() { | |
| mPackageMonitor.register(mContext, null, true); | |
| Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE); | |
| List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(intent, | |
| PackageManager.GET_META_DATA); | |
| 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); | |
| } | |
| } | |
| } | |
| 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; | |
| } | |
| } | |
| @Override | |
| public void registerListener(IBinder token, IMidiDeviceListener listener) { | |
| Client client = getClient(token); | |
| if (client == null) return; | |
| client.addListener(listener); | |
| // Let listener know whether any ports are already busy. | |
| updateStickyDeviceStatus(client.mUid, listener); | |
| } | |
| @Override | |
| public void unregisterListener(IBinder token, IMidiDeviceListener listener) { | |
| Client client = getClient(token); | |
| if (client == null) return; | |
| client.removeListener(listener); | |
| } | |
| // Inform listener of the status of all known devices. | |
| private void updateStickyDeviceStatus(int uid, IMidiDeviceListener listener) { | |
| synchronized (mDevicesByInfo) { | |
| for (Device device : mDevicesByInfo.values()) { | |
| // ignore private devices that our client cannot access | |
| if (device.isUidAllowed(uid)) { | |
| try { | |
| MidiDeviceStatus status = device.getDeviceStatus(); | |
| if (status != null) { | |
| listener.onDeviceStatusChanged(status); | |
| } | |
| } catch (RemoteException e) { | |
| Log.e(TAG, "remote exception", e); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0]; | |
| public MidiDeviceInfo[] getDevices() { | |
| ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>(); | |
| int uid = Binder.getCallingUid(); | |
| synchronized (mDevicesByInfo) { | |
| for (Device device : mDevicesByInfo.values()) { | |
| if (device.isUidAllowed(uid)) { | |
| deviceInfos.add(device.getDeviceInfo()); | |
| } | |
| } | |
| } | |
| return deviceInfos.toArray(EMPTY_DEVICE_INFO_ARRAY); | |
| } | |
| @Override | |
| public void openDevice(IBinder token, MidiDeviceInfo deviceInfo, | |
| IMidiDeviceOpenCallback callback) { | |
| Client client = getClient(token); | |
| if (client == null) return; | |
| Device device; | |
| synchronized (mDevicesByInfo) { | |
| device = mDevicesByInfo.get(deviceInfo); | |
| if (device == null) { | |
| throw new IllegalArgumentException("device does not exist: " + deviceInfo); | |
| } | |
| if (!device.isUidAllowed(Binder.getCallingUid())) { | |
| throw new SecurityException("Attempt to open private device with wrong UID"); | |
| } | |
| } | |
| // clear calling identity so bindService does not fail | |
| long identity = Binder.clearCallingIdentity(); | |
| try { | |
| client.addDeviceConnection(device, callback); | |
| } finally { | |
| Binder.restoreCallingIdentity(identity); | |
| } | |
| } | |
| @Override | |
| public void openBluetoothDevice(IBinder token, BluetoothDevice bluetoothDevice, | |
| IMidiDeviceOpenCallback callback) { | |
| Client client = getClient(token); | |
| if (client == null) return; | |
| // Bluetooth devices are created on demand | |
| Device device; | |
| synchronized (mDevicesByInfo) { | |
| device = mBluetoothDevices.get(bluetoothDevice); | |
| if (device == null) { | |
| device = new Device(bluetoothDevice); | |
| mBluetoothDevices.put(bluetoothDevice, device); | |
| } | |
| } | |
| // clear calling identity so bindService does not fail | |
| long identity = Binder.clearCallingIdentity(); | |
| try { | |
| client.addDeviceConnection(device, callback); | |
| } finally { | |
| Binder.restoreCallingIdentity(identity); | |
| } | |
| } | |
| @Override | |
| public void closeDevice(IBinder clientToken, IBinder deviceToken) { | |
| Client client = getClient(clientToken); | |
| if (client == null) return; | |
| client.removeDeviceConnection(deviceToken); | |
| } | |
| @Override | |
| public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts, | |
| int numOutputPorts, String[] inputPortNames, String[] outputPortNames, | |
| Bundle properties, int type) { | |
| int uid = Binder.getCallingUid(); | |
| 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) { | |
| throw new SecurityException("only MidiBluetoothService can create Bluetooth devices"); | |
| } | |
| synchronized (mDevicesByInfo) { | |
| return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames, | |
| outputPortNames, properties, server, null, false, uid); | |
| } | |
| } | |
| @Override | |
| public void unregisterDeviceServer(IMidiDeviceServer server) { | |
| synchronized (mDevicesByInfo) { | |
| Device device = mDevicesByServer.get(server.asBinder()); | |
| if (device != null) { | |
| device.closeLocked(); | |
| } | |
| } | |
| } | |
| @Override | |
| public MidiDeviceInfo getServiceDeviceInfo(String packageName, String className) { | |
| synchronized (mDevicesByInfo) { | |
| for (Device device : mDevicesByInfo.values()) { | |
| ServiceInfo serviceInfo = device.getServiceInfo(); | |
| if (serviceInfo != null && | |
| packageName.equals(serviceInfo.packageName) && | |
| className.equals(serviceInfo.name)) { | |
| return device.getDeviceInfo(); | |
| } | |
| } | |
| return null; | |
| } | |
| } | |
| @Override | |
| public MidiDeviceStatus getDeviceStatus(MidiDeviceInfo deviceInfo) { | |
| Device device = mDevicesByInfo.get(deviceInfo); | |
| if (device == null) { | |
| throw new IllegalArgumentException("no such device for " + deviceInfo); | |
| } | |
| return device.getDeviceStatus(); | |
| } | |
| @Override | |
| public void setDeviceStatus(IMidiDeviceServer server, MidiDeviceStatus status) { | |
| Device device = mDevicesByServer.get(server.asBinder()); | |
| if (device != null) { | |
| if (Binder.getCallingUid() != device.getUid()) { | |
| throw new SecurityException("setDeviceStatus() caller UID " + Binder.getCallingUid() | |
| + " does not match device's UID " + device.getUid()); | |
| } | |
| device.setDeviceStatus(status); | |
| notifyDeviceStatusChanged(device, status); | |
| } | |
| } | |
| private void notifyDeviceStatusChanged(Device device, MidiDeviceStatus status) { | |
| synchronized (mClients) { | |
| for (Client c : mClients.values()) { | |
| c.deviceStatusChanged(device, status); | |
| } | |
| } | |
| } | |
| // synchronize on mDevicesByInfo | |
| private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts, | |
| String[] inputPortNames, String[] outputPortNames, Bundle properties, | |
| IMidiDeviceServer server, ServiceInfo serviceInfo, | |
| boolean isPrivate, int uid) { | |
| int id = mNextDeviceId++; | |
| MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts, | |
| inputPortNames, outputPortNames, properties, isPrivate); | |
| if (server != null) { | |
| try { | |
| server.setDeviceInfo(deviceInfo); | |
| } catch (RemoteException e) { | |
| Log.e(TAG, "RemoteException in setDeviceInfo()"); | |
| return null; | |
| } | |
| } | |
| Device device = null; | |
| BluetoothDevice bluetoothDevice = null; | |
| if (type == MidiDeviceInfo.TYPE_BLUETOOTH) { | |
| bluetoothDevice = (BluetoothDevice)properties.getParcelable( | |
| MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE); | |
| device = mBluetoothDevices.get(bluetoothDevice); | |
| if (device != null) { | |
| device.setDeviceInfo(deviceInfo); | |
| } | |
| } | |
| if (device == null) { | |
| device = new Device(server, deviceInfo, serviceInfo, uid); | |
| } | |
| mDevicesByInfo.put(deviceInfo, device); | |
| if (bluetoothDevice != null) { | |
| mBluetoothDevices.put(bluetoothDevice, device); | |
| } | |
| synchronized (mClients) { | |
| for (Client c : mClients.values()) { | |
| c.deviceAdded(device); | |
| } | |
| } | |
| return deviceInfo; | |
| } | |
| // synchronize on mDevicesByInfo | |
| private void removeDeviceLocked(Device device) { | |
| IMidiDeviceServer server = device.getDeviceServer(); | |
| if (server != null) { | |
| mDevicesByServer.remove(server.asBinder()); | |
| } | |
| mDevicesByInfo.remove(device.getDeviceInfo()); | |
| synchronized (mClients) { | |
| for (Client c : mClients.values()) { | |
| c.deviceRemoved(device); | |
| } | |
| } | |
| } | |
| private void addPackageDeviceServers(String packageName) { | |
| PackageInfo info; | |
| try { | |
| info = mPackageManager.getPackageInfo(packageName, | |
| PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); | |
| } catch (PackageManager.NameNotFoundException e) { | |
| Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e); | |
| return; | |
| } | |
| ServiceInfo[] services = info.services; | |
| if (services == null) return; | |
| for (int i = 0; i < services.length; i++) { | |
| addPackageDeviceServer(services[i]); | |
| } | |
| } | |
| private static final String[] EMPTY_STRING_ARRAY = new String[0]; | |
| private void addPackageDeviceServer(ServiceInfo serviceInfo) { | |
| XmlResourceParser parser = null; | |
| try { | |
| parser = serviceInfo.loadXmlMetaData(mPackageManager, | |
| MidiDeviceService.SERVICE_INTERFACE); | |
| if (parser == null) return; | |
| // ignore virtual device servers that do not require the correct permission | |
| if (!android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE.equals( | |
| serviceInfo.permission)) { | |
| Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName | |
| + ": it does not require the permission " | |
| + android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE); | |
| return; | |
| } | |
| Bundle properties = null; | |
| int numInputPorts = 0; | |
| int numOutputPorts = 0; | |
| boolean isPrivate = false; | |
| ArrayList<String> inputPortNames = new ArrayList<String>(); | |
| ArrayList<String> outputPortNames = new ArrayList<String>(); | |
| while (true) { | |
| int eventType = parser.next(); | |
| if (eventType == XmlPullParser.END_DOCUMENT) { | |
| break; | |
| } else if (eventType == XmlPullParser.START_TAG) { | |
| String tagName = parser.getName(); | |
| if ("device".equals(tagName)) { | |
| if (properties != null) { | |
| Log.w(TAG, "nested <device> elements in metadata for " | |
| + serviceInfo.packageName); | |
| continue; | |
| } | |
| properties = new Bundle(); | |
| properties.putParcelable(MidiDeviceInfo.PROPERTY_SERVICE_INFO, serviceInfo); | |
| numInputPorts = 0; | |
| numOutputPorts = 0; | |
| isPrivate = false; | |
| int count = parser.getAttributeCount(); | |
| for (int i = 0; i < count; i++) { | |
| String name = parser.getAttributeName(i); | |
| String value = parser.getAttributeValue(i); | |
| if ("private".equals(name)) { | |
| isPrivate = "true".equals(value); | |
| } else { | |
| properties.putString(name, value); | |
| } | |
| } | |
| } else if ("input-port".equals(tagName)) { | |
| if (properties == null) { | |
| Log.w(TAG, "<input-port> outside of <device> in metadata for " | |
| + serviceInfo.packageName); | |
| continue; | |
| } | |
| numInputPorts++; | |
| String portName = null; | |
| int count = parser.getAttributeCount(); | |
| for (int i = 0; i < count; i++) { | |
| String name = parser.getAttributeName(i); | |
| String value = parser.getAttributeValue(i); | |
| if ("name".equals(name)) { | |
| portName = value; | |
| break; | |
| } | |
| } | |
| inputPortNames.add(portName); | |
| } else if ("output-port".equals(tagName)) { | |
| if (properties == null) { | |
| Log.w(TAG, "<output-port> outside of <device> in metadata for " | |
| + serviceInfo.packageName); | |
| continue; | |
| } | |
| numOutputPorts++; | |
| String portName = null; | |
| int count = parser.getAttributeCount(); | |
| for (int i = 0; i < count; i++) { | |
| String name = parser.getAttributeName(i); | |
| String value = parser.getAttributeValue(i); | |
| if ("name".equals(name)) { | |
| portName = value; | |
| break; | |
| } | |
| } | |
| outputPortNames.add(portName); | |
| } | |
| } else if (eventType == XmlPullParser.END_TAG) { | |
| String tagName = parser.getName(); | |
| if ("device".equals(tagName)) { | |
| if (properties != null) { | |
| if (numInputPorts == 0 && numOutputPorts == 0) { | |
| Log.w(TAG, "<device> with no ports in metadata for " | |
| + serviceInfo.packageName); | |
| continue; | |
| } | |
| int uid; | |
| try { | |
| ApplicationInfo appInfo = mPackageManager.getApplicationInfo( | |
| serviceInfo.packageName, 0); | |
| uid = appInfo.uid; | |
| } catch (PackageManager.NameNotFoundException e) { | |
| Log.e(TAG, "could not fetch ApplicationInfo for " | |
| + serviceInfo.packageName); | |
| continue; | |
| } | |
| synchronized (mDevicesByInfo) { | |
| addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL, | |
| numInputPorts, numOutputPorts, | |
| inputPortNames.toArray(EMPTY_STRING_ARRAY), | |
| outputPortNames.toArray(EMPTY_STRING_ARRAY), | |
| properties, null, serviceInfo, isPrivate, uid); | |
| } | |
| // setting properties to null signals that we are no longer | |
| // processing a <device> | |
| properties = null; | |
| inputPortNames.clear(); | |
| outputPortNames.clear(); | |
| } | |
| } | |
| } | |
| } | |
| } catch (Exception e) { | |
| Log.w(TAG, "Unable to load component info " + serviceInfo.toString(), e); | |
| } finally { | |
| if (parser != null) parser.close(); | |
| } | |
| } | |
| private void removePackageDeviceServers(String packageName) { | |
| synchronized (mDevicesByInfo) { | |
| Iterator<Device> iterator = mDevicesByInfo.values().iterator(); | |
| while (iterator.hasNext()) { | |
| Device device = iterator.next(); | |
| if (packageName.equals(device.getPackageName())) { | |
| iterator.remove(); | |
| removeDeviceLocked(device); | |
| } | |
| } | |
| } | |
| } | |
| @Override | |
| public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { | |
| if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; | |
| final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); | |
| pw.println("MIDI Manager State:"); | |
| pw.increaseIndent(); | |
| pw.println("Devices:"); | |
| pw.increaseIndent(); | |
| synchronized (mDevicesByInfo) { | |
| for (Device device : mDevicesByInfo.values()) { | |
| pw.println(device.toString()); | |
| } | |
| } | |
| pw.decreaseIndent(); | |
| pw.println("Clients:"); | |
| pw.increaseIndent(); | |
| synchronized (mClients) { | |
| for (Client client : mClients.values()) { | |
| pw.println(client.toString()); | |
| } | |
| } | |
| pw.decreaseIndent(); | |
| } | |
| } |