| /* |
| * 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 { |
| if (mDeviceInfo == null) { |
| mDeviceInfo = server.getDeviceInfo(); |
| } |
| 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(); |
| } |
| } |