Home | History | Annotate | Download | only in midi
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions an
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.midi;
     18 
     19 import android.bluetooth.BluetoothDevice;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.ServiceConnection;
     24 import android.content.pm.ApplicationInfo;
     25 import android.content.pm.PackageInfo;
     26 import android.content.pm.PackageManager;
     27 import android.content.pm.ResolveInfo;
     28 import android.content.pm.ServiceInfo;
     29 import android.content.res.XmlResourceParser;
     30 import android.media.midi.IBluetoothMidiService;
     31 import android.media.midi.IMidiDeviceListener;
     32 import android.media.midi.IMidiDeviceOpenCallback;
     33 import android.media.midi.IMidiDeviceServer;
     34 import android.media.midi.IMidiManager;
     35 import android.media.midi.MidiDeviceInfo;
     36 import android.media.midi.MidiDeviceService;
     37 import android.media.midi.MidiDeviceStatus;
     38 import android.media.midi.MidiManager;
     39 import android.os.Binder;
     40 import android.os.Bundle;
     41 import android.os.IBinder;
     42 import android.os.Process;
     43 import android.os.RemoteException;
     44 import android.os.UserHandle;
     45 import android.util.Log;
     46 
     47 import com.android.internal.content.PackageMonitor;
     48 import com.android.internal.util.DumpUtils;
     49 import com.android.internal.util.IndentingPrintWriter;
     50 import com.android.internal.util.XmlUtils;
     51 import com.android.server.SystemService;
     52 
     53 import org.xmlpull.v1.XmlPullParser;
     54 
     55 import java.io.FileDescriptor;
     56 import java.io.PrintWriter;
     57 import java.util.ArrayList;
     58 import java.util.HashMap;
     59 import java.util.Iterator;
     60 import java.util.List;
     61 
     62 public class MidiService extends IMidiManager.Stub {
     63 
     64     public static class Lifecycle extends SystemService {
     65         private MidiService mMidiService;
     66 
     67         public Lifecycle(Context context) {
     68             super(context);
     69         }
     70 
     71         @Override
     72         public void onStart() {
     73             mMidiService = new MidiService(getContext());
     74             publishBinderService(Context.MIDI_SERVICE, mMidiService);
     75         }
     76 
     77         @Override
     78         public void onUnlockUser(int userHandle) {
     79             if (userHandle == UserHandle.USER_SYSTEM) {
     80                 mMidiService.onUnlockUser();
     81             }
     82         }
     83     }
     84 
     85     private static final String TAG = "MidiService";
     86 
     87     private final Context mContext;
     88 
     89     // list of all our clients, keyed by Binder token
     90     private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
     91 
     92     // list of all devices, keyed by MidiDeviceInfo
     93     private final HashMap<MidiDeviceInfo, Device> mDevicesByInfo
     94             = new HashMap<MidiDeviceInfo, Device>();
     95 
     96     // list of all Bluetooth devices, keyed by BluetoothDevice
     97      private final HashMap<BluetoothDevice, Device> mBluetoothDevices
     98             = new HashMap<BluetoothDevice, Device>();
     99 
    100     // list of all devices, keyed by IMidiDeviceServer
    101     private final HashMap<IBinder, Device> mDevicesByServer = new HashMap<IBinder, Device>();
    102 
    103     // used for assigning IDs to MIDI devices
    104     private int mNextDeviceId = 1;
    105 
    106     private final PackageManager mPackageManager;
    107 
    108     // UID of BluetoothMidiService
    109     private int mBluetoothServiceUid;
    110 
    111     // PackageMonitor for listening to package changes
    112     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
    113         @Override
    114         public void onPackageAdded(String packageName, int uid) {
    115             addPackageDeviceServers(packageName);
    116         }
    117 
    118         @Override
    119         public void onPackageModified(String packageName) {
    120             removePackageDeviceServers(packageName);
    121             addPackageDeviceServers(packageName);
    122         }
    123 
    124         @Override
    125         public void onPackageRemoved(String packageName, int uid) {
    126             removePackageDeviceServers(packageName);
    127         }
    128     };
    129 
    130     private final class Client implements IBinder.DeathRecipient {
    131         // Binder token for this client
    132         private final IBinder mToken;
    133         // This client's UID
    134         private final int mUid;
    135         // This client's PID
    136         private final int mPid;
    137         // List of all receivers for this client
    138         private final HashMap<IBinder, IMidiDeviceListener> mListeners
    139                 = new HashMap<IBinder, IMidiDeviceListener>();
    140         // List of all device connections for this client
    141         private final HashMap<IBinder, DeviceConnection> mDeviceConnections
    142                 = new HashMap<IBinder, DeviceConnection>();
    143 
    144         public Client(IBinder token) {
    145             mToken = token;
    146             mUid = Binder.getCallingUid();
    147             mPid = Binder.getCallingPid();
    148         }
    149 
    150         public int getUid() {
    151             return mUid;
    152         }
    153 
    154         public void addListener(IMidiDeviceListener listener) {
    155             // Use asBinder() so that we can match it in removeListener().
    156             // The listener proxy objects themselves do not match.
    157             mListeners.put(listener.asBinder(), listener);
    158         }
    159 
    160         public void removeListener(IMidiDeviceListener listener) {
    161             mListeners.remove(listener.asBinder());
    162             if (mListeners.size() == 0 && mDeviceConnections.size() == 0) {
    163                 close();
    164             }
    165         }
    166 
    167         public void addDeviceConnection(Device device, IMidiDeviceOpenCallback callback) {
    168             DeviceConnection connection = new DeviceConnection(device, this, callback);
    169             mDeviceConnections.put(connection.getToken(), connection);
    170             device.addDeviceConnection(connection);
    171         }
    172 
    173         // called from MidiService.closeDevice()
    174         public void removeDeviceConnection(IBinder token) {
    175             DeviceConnection connection = mDeviceConnections.remove(token);
    176             if (connection != null) {
    177                 connection.getDevice().removeDeviceConnection(connection);
    178             }
    179             if (mListeners.size() == 0 && mDeviceConnections.size() == 0) {
    180                 close();
    181             }
    182         }
    183 
    184         // called from Device.close()
    185         public void removeDeviceConnection(DeviceConnection connection) {
    186             mDeviceConnections.remove(connection.getToken());
    187             if (mListeners.size() == 0 && mDeviceConnections.size() == 0) {
    188                 close();
    189             }
    190         }
    191 
    192         public void deviceAdded(Device device) {
    193             // ignore private devices that our client cannot access
    194             if (!device.isUidAllowed(mUid)) return;
    195 
    196             MidiDeviceInfo deviceInfo = device.getDeviceInfo();
    197             try {
    198                 for (IMidiDeviceListener listener : mListeners.values()) {
    199                     listener.onDeviceAdded(deviceInfo);
    200                 }
    201             } catch (RemoteException e) {
    202                 Log.e(TAG, "remote exception", e);
    203             }
    204         }
    205 
    206         public void deviceRemoved(Device device) {
    207             // ignore private devices that our client cannot access
    208             if (!device.isUidAllowed(mUid)) return;
    209 
    210             MidiDeviceInfo deviceInfo = device.getDeviceInfo();
    211             try {
    212                 for (IMidiDeviceListener listener : mListeners.values()) {
    213                     listener.onDeviceRemoved(deviceInfo);
    214                 }
    215             } catch (RemoteException e) {
    216                 Log.e(TAG, "remote exception", e);
    217             }
    218         }
    219 
    220         public void deviceStatusChanged(Device device, MidiDeviceStatus status) {
    221             // ignore private devices that our client cannot access
    222             if (!device.isUidAllowed(mUid)) return;
    223 
    224             try {
    225                 for (IMidiDeviceListener listener : mListeners.values()) {
    226                     listener.onDeviceStatusChanged(status);
    227                 }
    228             } catch (RemoteException e) {
    229                 Log.e(TAG, "remote exception", e);
    230             }
    231         }
    232 
    233         private void close() {
    234             synchronized (mClients) {
    235                 mClients.remove(mToken);
    236                 mToken.unlinkToDeath(this, 0);
    237             }
    238 
    239             for (DeviceConnection connection : mDeviceConnections.values()) {
    240                 connection.getDevice().removeDeviceConnection(connection);
    241             }
    242         }
    243 
    244         @Override
    245         public void binderDied() {
    246             Log.d(TAG, "Client died: " + this);
    247             close();
    248         }
    249 
    250         @Override
    251         public String toString() {
    252             StringBuilder sb = new StringBuilder("Client: UID: ");
    253             sb.append(mUid);
    254             sb.append(" PID: ");
    255             sb.append(mPid);
    256             sb.append(" listener count: ");
    257             sb.append(mListeners.size());
    258             sb.append(" Device Connections:");
    259             for (DeviceConnection connection : mDeviceConnections.values()) {
    260                 sb.append(" <device ");
    261                 sb.append(connection.getDevice().getDeviceInfo().getId());
    262                 sb.append(">");
    263             }
    264             return sb.toString();
    265         }
    266     }
    267 
    268     private Client getClient(IBinder token) {
    269         synchronized (mClients) {
    270             Client client = mClients.get(token);
    271             if (client == null) {
    272                 client = new Client(token);
    273 
    274                 try {
    275                     token.linkToDeath(client, 0);
    276                 } catch (RemoteException e) {
    277                     return null;
    278                 }
    279                 mClients.put(token, client);
    280             }
    281             return client;
    282         }
    283     }
    284 
    285     private final class Device implements IBinder.DeathRecipient {
    286         private IMidiDeviceServer mServer;
    287         private MidiDeviceInfo mDeviceInfo;
    288         private final BluetoothDevice mBluetoothDevice;
    289         private MidiDeviceStatus mDeviceStatus;
    290 
    291         // ServiceInfo for the device's MidiDeviceServer implementation (virtual devices only)
    292         private final ServiceInfo mServiceInfo;
    293         // UID of device implementation
    294         private final int mUid;
    295 
    296         // ServiceConnection for implementing Service (virtual devices only)
    297         // mServiceConnection is non-null when connected or attempting to connect to the service
    298         private ServiceConnection mServiceConnection;
    299 
    300         // List of all device connections for this device
    301         private final ArrayList<DeviceConnection> mDeviceConnections
    302                 = new ArrayList<DeviceConnection>();
    303 
    304         public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo,
    305                 ServiceInfo serviceInfo, int uid) {
    306             mDeviceInfo = deviceInfo;
    307             mServiceInfo = serviceInfo;
    308             mUid = uid;
    309             mBluetoothDevice = (BluetoothDevice)deviceInfo.getProperties().getParcelable(
    310                     MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE);;
    311             setDeviceServer(server);
    312         }
    313 
    314         public Device(BluetoothDevice bluetoothDevice) {
    315             mBluetoothDevice = bluetoothDevice;
    316             mServiceInfo = null;
    317             mUid = mBluetoothServiceUid;
    318         }
    319 
    320         private void setDeviceServer(IMidiDeviceServer server) {
    321             if (server != null) {
    322                 if (mServer != null) {
    323                     Log.e(TAG, "mServer already set in setDeviceServer");
    324                     return;
    325                 }
    326                 IBinder binder = server.asBinder();
    327                 try {
    328                     binder.linkToDeath(this, 0);
    329                     mServer = server;
    330                 } catch (RemoteException e) {
    331                     mServer = null;
    332                     return;
    333                 }
    334                 mDevicesByServer.put(binder, this);
    335             } else if (mServer != null) {
    336                 server = mServer;
    337                 mServer = null;
    338 
    339                 IBinder binder = server.asBinder();
    340                 mDevicesByServer.remove(binder);
    341 
    342                 try {
    343                     server.closeDevice();
    344                     binder.unlinkToDeath(this, 0);
    345                 } catch (RemoteException e) {
    346                     // nothing to do here
    347                 }
    348             }
    349 
    350             if (mDeviceConnections != null) {
    351                 for (DeviceConnection connection : mDeviceConnections) {
    352                     connection.notifyClient(server);
    353                 }
    354             }
    355         }
    356 
    357         public MidiDeviceInfo getDeviceInfo() {
    358             return mDeviceInfo;
    359         }
    360 
    361         // only used for bluetooth devices, which are created before we have a MidiDeviceInfo
    362         public void setDeviceInfo(MidiDeviceInfo deviceInfo) {
    363             mDeviceInfo = deviceInfo;
    364         }
    365 
    366         public MidiDeviceStatus getDeviceStatus() {
    367             return mDeviceStatus;
    368         }
    369 
    370         public void setDeviceStatus(MidiDeviceStatus status) {
    371             mDeviceStatus = status;
    372         }
    373 
    374         public IMidiDeviceServer getDeviceServer() {
    375             return mServer;
    376         }
    377 
    378         public ServiceInfo getServiceInfo() {
    379             return mServiceInfo;
    380         }
    381 
    382         public String getPackageName() {
    383             return (mServiceInfo == null ? null : mServiceInfo.packageName);
    384         }
    385 
    386         public int getUid() {
    387             return mUid;
    388         }
    389 
    390         public boolean isUidAllowed(int uid) {
    391             return (!mDeviceInfo.isPrivate() || mUid == uid);
    392         }
    393 
    394         public void addDeviceConnection(DeviceConnection connection) {
    395             synchronized (mDeviceConnections) {
    396                 if (mServer != null) {
    397                     mDeviceConnections.add(connection);
    398                     connection.notifyClient(mServer);
    399                 } else if (mServiceConnection == null &&
    400                     (mServiceInfo != null || mBluetoothDevice != null)) {
    401                     mDeviceConnections.add(connection);
    402 
    403                     mServiceConnection = new ServiceConnection() {
    404                         @Override
    405                         public void onServiceConnected(ComponentName name, IBinder service) {
    406                             IMidiDeviceServer server = null;
    407                             if (mBluetoothDevice != null) {
    408                                 IBluetoothMidiService mBluetoothMidiService = IBluetoothMidiService.Stub.asInterface(service);
    409                                 try {
    410                                     // We need to explicitly add the device in a separate method
    411                                     // because onBind() is only called once.
    412                                     IBinder deviceBinder = mBluetoothMidiService.addBluetoothDevice(mBluetoothDevice);
    413                                     server = IMidiDeviceServer.Stub.asInterface(deviceBinder);
    414                                 } catch(RemoteException e) {
    415                                     Log.e(TAG, "Could not call addBluetoothDevice()", e);
    416                                 }
    417                             } else {
    418                                 server = IMidiDeviceServer.Stub.asInterface(service);
    419                             }
    420                             setDeviceServer(server);
    421                         }
    422 
    423                         @Override
    424                         public void onServiceDisconnected(ComponentName name) {
    425                             setDeviceServer(null);
    426                             mServiceConnection = null;
    427                         }
    428                     };
    429 
    430                     Intent intent;
    431                     if (mBluetoothDevice != null) {
    432                         intent = new Intent(MidiManager.BLUETOOTH_MIDI_SERVICE_INTENT);
    433                         intent.setComponent(new ComponentName(
    434                                 MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE,
    435                                 MidiManager.BLUETOOTH_MIDI_SERVICE_CLASS));
    436                     } else {
    437                         intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
    438                         intent.setComponent(
    439                                 new ComponentName(mServiceInfo.packageName, mServiceInfo.name));
    440                     }
    441 
    442                     if (!mContext.bindService(intent, mServiceConnection,
    443                             Context.BIND_AUTO_CREATE)) {
    444                         Log.e(TAG, "Unable to bind service: " + intent);
    445                         setDeviceServer(null);
    446                         mServiceConnection = null;
    447                     }
    448                 } else {
    449                     Log.e(TAG, "No way to connect to device in addDeviceConnection");
    450                     connection.notifyClient(null);
    451                 }
    452             }
    453         }
    454 
    455         public void removeDeviceConnection(DeviceConnection connection) {
    456             synchronized (mDeviceConnections) {
    457                 mDeviceConnections.remove(connection);
    458 
    459                 if (mDeviceConnections.size() == 0 && mServiceConnection != null) {
    460                     mContext.unbindService(mServiceConnection);
    461                     mServiceConnection = null;
    462                     if (mBluetoothDevice != null) {
    463                         // Bluetooth devices are ephemeral - remove when no clients exist
    464                         synchronized (mDevicesByInfo) {
    465                             closeLocked();
    466                         }
    467                     } else {
    468                         setDeviceServer(null);
    469                     }
    470                 }
    471             }
    472         }
    473 
    474         // synchronize on mDevicesByInfo
    475         public void closeLocked() {
    476             synchronized (mDeviceConnections) {
    477                 for (DeviceConnection connection : mDeviceConnections) {
    478                     connection.getClient().removeDeviceConnection(connection);
    479                 }
    480                 mDeviceConnections.clear();
    481             }
    482             setDeviceServer(null);
    483 
    484             // closed virtual devices should not be removed from mDevicesByInfo
    485             // since they can be restarted on demand
    486             if (mServiceInfo == null) {
    487                 removeDeviceLocked(this);
    488             } else {
    489                 mDeviceStatus = new MidiDeviceStatus(mDeviceInfo);
    490             }
    491 
    492             if (mBluetoothDevice != null) {
    493                 mBluetoothDevices.remove(mBluetoothDevice);
    494             }
    495         }
    496 
    497         @Override
    498         public void binderDied() {
    499             Log.d(TAG, "Device died: " + this);
    500             synchronized (mDevicesByInfo) {
    501                 closeLocked();
    502             }
    503         }
    504 
    505         @Override
    506         public String toString() {
    507             StringBuilder sb = new StringBuilder("Device Info: ");
    508             sb.append(mDeviceInfo);
    509             sb.append(" Status: ");
    510             sb.append(mDeviceStatus);
    511             sb.append(" UID: ");
    512             sb.append(mUid);
    513             sb.append(" DeviceConnection count: ");
    514             sb.append(mDeviceConnections.size());
    515             sb.append(" mServiceConnection: ");
    516             sb.append(mServiceConnection);
    517             return sb.toString();
    518         }
    519     }
    520 
    521     // Represents a connection between a client and a device
    522     private final class DeviceConnection {
    523         private final IBinder mToken = new Binder();
    524         private final Device mDevice;
    525         private final Client mClient;
    526         private IMidiDeviceOpenCallback mCallback;
    527 
    528         public DeviceConnection(Device device, Client client, IMidiDeviceOpenCallback callback) {
    529             mDevice = device;
    530             mClient = client;
    531             mCallback = callback;
    532         }
    533 
    534         public Device getDevice() {
    535             return mDevice;
    536         }
    537 
    538         public Client getClient() {
    539             return mClient;
    540         }
    541 
    542         public IBinder getToken() {
    543             return mToken;
    544         }
    545 
    546         public void notifyClient(IMidiDeviceServer deviceServer) {
    547             if (mCallback != null) {
    548                 try {
    549                     mCallback.onDeviceOpened(deviceServer, (deviceServer == null ? null : mToken));
    550                 } catch (RemoteException e) {
    551                     // Client binderDied() method will do necessary cleanup, so nothing to do here
    552                 }
    553                 mCallback = null;
    554             }
    555         }
    556 
    557         @Override
    558         public String toString() {
    559             return "DeviceConnection Device ID: " + mDevice.getDeviceInfo().getId();
    560         }
    561     }
    562 
    563     public MidiService(Context context) {
    564         mContext = context;
    565         mPackageManager = context.getPackageManager();
    566 
    567         mBluetoothServiceUid = -1;
    568     }
    569 
    570     private void onUnlockUser() {
    571         mPackageMonitor.register(mContext, null, true);
    572 
    573         Intent intent = new Intent(MidiDeviceService.SERVICE_INTERFACE);
    574         List<ResolveInfo> resolveInfos = mPackageManager.queryIntentServices(intent,
    575                 PackageManager.GET_META_DATA);
    576         if (resolveInfos != null) {
    577             int count = resolveInfos.size();
    578             for (int i = 0; i < count; i++) {
    579                 ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
    580                 if (serviceInfo != null) {
    581                     addPackageDeviceServer(serviceInfo);
    582                 }
    583             }
    584         }
    585 
    586         PackageInfo info;
    587         try {
    588             info = mPackageManager.getPackageInfo(MidiManager.BLUETOOTH_MIDI_SERVICE_PACKAGE, 0);
    589         } catch (PackageManager.NameNotFoundException e) {
    590             info = null;
    591         }
    592         if (info != null && info.applicationInfo != null) {
    593             mBluetoothServiceUid = info.applicationInfo.uid;
    594         } else {
    595             mBluetoothServiceUid = -1;
    596         }
    597     }
    598 
    599     @Override
    600     public void registerListener(IBinder token, IMidiDeviceListener listener) {
    601         Client client = getClient(token);
    602         if (client == null) return;
    603         client.addListener(listener);
    604         // Let listener know whether any ports are already busy.
    605         updateStickyDeviceStatus(client.mUid, listener);
    606     }
    607 
    608     @Override
    609     public void unregisterListener(IBinder token, IMidiDeviceListener listener) {
    610         Client client = getClient(token);
    611         if (client == null) return;
    612         client.removeListener(listener);
    613     }
    614 
    615     // Inform listener of the status of all known devices.
    616     private void updateStickyDeviceStatus(int uid, IMidiDeviceListener listener) {
    617         synchronized (mDevicesByInfo) {
    618             for (Device device : mDevicesByInfo.values()) {
    619                 // ignore private devices that our client cannot access
    620                 if (device.isUidAllowed(uid)) {
    621                     try {
    622                         MidiDeviceStatus status = device.getDeviceStatus();
    623                         if (status != null) {
    624                             listener.onDeviceStatusChanged(status);
    625                         }
    626                     } catch (RemoteException e) {
    627                         Log.e(TAG, "remote exception", e);
    628                     }
    629                 }
    630             }
    631         }
    632     }
    633 
    634     private static final MidiDeviceInfo[] EMPTY_DEVICE_INFO_ARRAY = new MidiDeviceInfo[0];
    635 
    636     public MidiDeviceInfo[] getDevices() {
    637         ArrayList<MidiDeviceInfo> deviceInfos = new ArrayList<MidiDeviceInfo>();
    638         int uid = Binder.getCallingUid();
    639 
    640         synchronized (mDevicesByInfo) {
    641             for (Device device : mDevicesByInfo.values()) {
    642                 if (device.isUidAllowed(uid)) {
    643                     deviceInfos.add(device.getDeviceInfo());
    644                 }
    645             }
    646         }
    647 
    648         return deviceInfos.toArray(EMPTY_DEVICE_INFO_ARRAY);
    649     }
    650 
    651     @Override
    652     public void openDevice(IBinder token, MidiDeviceInfo deviceInfo,
    653             IMidiDeviceOpenCallback callback) {
    654         Client client = getClient(token);
    655         if (client == null) return;
    656 
    657         Device device;
    658         synchronized (mDevicesByInfo) {
    659             device = mDevicesByInfo.get(deviceInfo);
    660             if (device == null) {
    661                 throw new IllegalArgumentException("device does not exist: " + deviceInfo);
    662             }
    663             if (!device.isUidAllowed(Binder.getCallingUid())) {
    664                 throw new SecurityException("Attempt to open private device with wrong UID");
    665             }
    666         }
    667 
    668         // clear calling identity so bindService does not fail
    669         long identity = Binder.clearCallingIdentity();
    670         try {
    671             client.addDeviceConnection(device, callback);
    672         } finally {
    673             Binder.restoreCallingIdentity(identity);
    674         }
    675     }
    676 
    677     @Override
    678     public void openBluetoothDevice(IBinder token, BluetoothDevice bluetoothDevice,
    679             IMidiDeviceOpenCallback callback) {
    680         Client client = getClient(token);
    681         if (client == null) return;
    682 
    683         // Bluetooth devices are created on demand
    684         Device device;
    685         synchronized (mDevicesByInfo) {
    686             device = mBluetoothDevices.get(bluetoothDevice);
    687             if (device == null) {
    688                 device = new Device(bluetoothDevice);
    689                 mBluetoothDevices.put(bluetoothDevice, device);
    690             }
    691         }
    692 
    693         // clear calling identity so bindService does not fail
    694         long identity = Binder.clearCallingIdentity();
    695         try {
    696             client.addDeviceConnection(device, callback);
    697         } finally {
    698             Binder.restoreCallingIdentity(identity);
    699         }
    700     }
    701 
    702     @Override
    703     public void closeDevice(IBinder clientToken, IBinder deviceToken) {
    704         Client client = getClient(clientToken);
    705         if (client == null) return;
    706         client.removeDeviceConnection(deviceToken);
    707     }
    708 
    709     @Override
    710     public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
    711             int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
    712             Bundle properties, int type) {
    713         int uid = Binder.getCallingUid();
    714         if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
    715             throw new SecurityException("only system can create USB devices");
    716         } else if (type == MidiDeviceInfo.TYPE_BLUETOOTH && uid != mBluetoothServiceUid) {
    717             throw new SecurityException("only MidiBluetoothService can create Bluetooth devices");
    718         }
    719 
    720         synchronized (mDevicesByInfo) {
    721             return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames,
    722                     outputPortNames, properties, server, null, false, uid);
    723         }
    724     }
    725 
    726     @Override
    727     public void unregisterDeviceServer(IMidiDeviceServer server) {
    728         synchronized (mDevicesByInfo) {
    729             Device device = mDevicesByServer.get(server.asBinder());
    730             if (device != null) {
    731                 device.closeLocked();
    732             }
    733         }
    734     }
    735 
    736     @Override
    737     public MidiDeviceInfo getServiceDeviceInfo(String packageName, String className) {
    738         synchronized (mDevicesByInfo) {
    739             for (Device device : mDevicesByInfo.values()) {
    740                  ServiceInfo serviceInfo = device.getServiceInfo();
    741                  if (serviceInfo != null &&
    742                         packageName.equals(serviceInfo.packageName) &&
    743                         className.equals(serviceInfo.name)) {
    744                     return device.getDeviceInfo();
    745                 }
    746             }
    747             return null;
    748         }
    749     }
    750 
    751     @Override
    752     public MidiDeviceStatus getDeviceStatus(MidiDeviceInfo deviceInfo) {
    753         Device device = mDevicesByInfo.get(deviceInfo);
    754         if (device == null) {
    755             throw new IllegalArgumentException("no such device for " + deviceInfo);
    756         }
    757         return device.getDeviceStatus();
    758     }
    759 
    760     @Override
    761     public void setDeviceStatus(IMidiDeviceServer server, MidiDeviceStatus status) {
    762         Device device = mDevicesByServer.get(server.asBinder());
    763         if (device != null) {
    764             if (Binder.getCallingUid() != device.getUid()) {
    765                 throw new SecurityException("setDeviceStatus() caller UID " + Binder.getCallingUid()
    766                         + " does not match device's UID " + device.getUid());
    767             }
    768             device.setDeviceStatus(status);
    769             notifyDeviceStatusChanged(device, status);
    770         }
    771     }
    772 
    773     private void notifyDeviceStatusChanged(Device device, MidiDeviceStatus status) {
    774         synchronized (mClients) {
    775             for (Client c : mClients.values()) {
    776                 c.deviceStatusChanged(device, status);
    777             }
    778         }
    779     }
    780 
    781     // synchronize on mDevicesByInfo
    782     private MidiDeviceInfo addDeviceLocked(int type, int numInputPorts, int numOutputPorts,
    783             String[] inputPortNames, String[] outputPortNames, Bundle properties,
    784             IMidiDeviceServer server, ServiceInfo serviceInfo,
    785             boolean isPrivate, int uid) {
    786 
    787         int id = mNextDeviceId++;
    788         MidiDeviceInfo deviceInfo = new MidiDeviceInfo(type, id, numInputPorts, numOutputPorts,
    789                 inputPortNames, outputPortNames, properties, isPrivate);
    790 
    791         if (server != null) {
    792             try {
    793                 server.setDeviceInfo(deviceInfo);
    794             } catch (RemoteException e) {
    795                 Log.e(TAG, "RemoteException in setDeviceInfo()");
    796                 return null;
    797             }
    798         }
    799 
    800         Device device = null;
    801         BluetoothDevice bluetoothDevice = null;
    802         if (type == MidiDeviceInfo.TYPE_BLUETOOTH) {
    803             bluetoothDevice = (BluetoothDevice)properties.getParcelable(
    804                     MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE);
    805             device = mBluetoothDevices.get(bluetoothDevice);
    806             if (device != null) {
    807                 device.setDeviceInfo(deviceInfo);
    808             }
    809         }
    810         if (device == null) {
    811             device = new Device(server, deviceInfo, serviceInfo, uid);
    812         }
    813         mDevicesByInfo.put(deviceInfo, device);
    814         if (bluetoothDevice != null) {
    815             mBluetoothDevices.put(bluetoothDevice, device);
    816         }
    817 
    818         synchronized (mClients) {
    819             for (Client c : mClients.values()) {
    820                 c.deviceAdded(device);
    821             }
    822         }
    823 
    824         return deviceInfo;
    825     }
    826 
    827     // synchronize on mDevicesByInfo
    828     private void removeDeviceLocked(Device device) {
    829         IMidiDeviceServer server = device.getDeviceServer();
    830         if (server != null) {
    831             mDevicesByServer.remove(server.asBinder());
    832         }
    833         mDevicesByInfo.remove(device.getDeviceInfo());
    834 
    835         synchronized (mClients) {
    836             for (Client c : mClients.values()) {
    837                 c.deviceRemoved(device);
    838             }
    839         }
    840     }
    841 
    842     private void addPackageDeviceServers(String packageName) {
    843         PackageInfo info;
    844 
    845         try {
    846             info = mPackageManager.getPackageInfo(packageName,
    847                     PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
    848         } catch (PackageManager.NameNotFoundException e) {
    849             Log.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
    850             return;
    851         }
    852 
    853         ServiceInfo[] services = info.services;
    854         if (services == null) return;
    855         for (int i = 0; i < services.length; i++) {
    856             addPackageDeviceServer(services[i]);
    857         }
    858     }
    859 
    860     private static final String[] EMPTY_STRING_ARRAY = new String[0];
    861 
    862     private void addPackageDeviceServer(ServiceInfo serviceInfo) {
    863         XmlResourceParser parser = null;
    864 
    865         try {
    866             parser = serviceInfo.loadXmlMetaData(mPackageManager,
    867                     MidiDeviceService.SERVICE_INTERFACE);
    868             if (parser == null) return;
    869 
    870             // ignore virtual device servers that do not require the correct permission
    871             if (!android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE.equals(
    872                     serviceInfo.permission)) {
    873                 Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName
    874                         + ": it does not require the permission "
    875                         + android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE);
    876                 return;
    877             }
    878 
    879             Bundle properties = null;
    880             int numInputPorts = 0;
    881             int numOutputPorts = 0;
    882             boolean isPrivate = false;
    883             ArrayList<String> inputPortNames = new ArrayList<String>();
    884             ArrayList<String> outputPortNames = new ArrayList<String>();
    885 
    886             while (true) {
    887                 int eventType = parser.next();
    888                 if (eventType == XmlPullParser.END_DOCUMENT) {
    889                     break;
    890                 } else if (eventType == XmlPullParser.START_TAG) {
    891                     String tagName = parser.getName();
    892                     if ("device".equals(tagName)) {
    893                         if (properties != null) {
    894                             Log.w(TAG, "nested <device> elements in metadata for "
    895                                 + serviceInfo.packageName);
    896                             continue;
    897                         }
    898                         properties = new Bundle();
    899                         properties.putParcelable(MidiDeviceInfo.PROPERTY_SERVICE_INFO, serviceInfo);
    900                         numInputPorts = 0;
    901                         numOutputPorts = 0;
    902                         isPrivate = false;
    903 
    904                         int count = parser.getAttributeCount();
    905                         for (int i = 0; i < count; i++) {
    906                             String name = parser.getAttributeName(i);
    907                             String value = parser.getAttributeValue(i);
    908                             if ("private".equals(name)) {
    909                                 isPrivate = "true".equals(value);
    910                             } else {
    911                                 properties.putString(name, value);
    912                             }
    913                         }
    914                     } else if ("input-port".equals(tagName)) {
    915                         if (properties == null) {
    916                             Log.w(TAG, "<input-port> outside of <device> in metadata for "
    917                                 + serviceInfo.packageName);
    918                             continue;
    919                         }
    920                         numInputPorts++;
    921 
    922                         String portName = null;
    923                         int count = parser.getAttributeCount();
    924                         for (int i = 0; i < count; i++) {
    925                             String name = parser.getAttributeName(i);
    926                             String value = parser.getAttributeValue(i);
    927                             if ("name".equals(name)) {
    928                                 portName = value;
    929                                 break;
    930                             }
    931                         }
    932                         inputPortNames.add(portName);
    933                     } else if ("output-port".equals(tagName)) {
    934                         if (properties == null) {
    935                             Log.w(TAG, "<output-port> outside of <device> in metadata for "
    936                                 + serviceInfo.packageName);
    937                             continue;
    938                         }
    939                         numOutputPorts++;
    940 
    941                         String portName = null;
    942                         int count = parser.getAttributeCount();
    943                         for (int i = 0; i < count; i++) {
    944                             String name = parser.getAttributeName(i);
    945                             String value = parser.getAttributeValue(i);
    946                             if ("name".equals(name)) {
    947                                 portName = value;
    948                                 break;
    949                             }
    950                         }
    951                         outputPortNames.add(portName);
    952                     }
    953                 } else if (eventType == XmlPullParser.END_TAG) {
    954                     String tagName = parser.getName();
    955                     if ("device".equals(tagName)) {
    956                         if (properties != null) {
    957                             if (numInputPorts == 0 && numOutputPorts == 0) {
    958                                 Log.w(TAG, "<device> with no ports in metadata for "
    959                                     + serviceInfo.packageName);
    960                                 continue;
    961                             }
    962 
    963                             int uid;
    964                             try {
    965                                 ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
    966                                         serviceInfo.packageName, 0);
    967                                 uid = appInfo.uid;
    968                             } catch (PackageManager.NameNotFoundException e) {
    969                                 Log.e(TAG, "could not fetch ApplicationInfo for "
    970                                         + serviceInfo.packageName);
    971                                 continue;
    972                             }
    973 
    974                             synchronized (mDevicesByInfo) {
    975                                 addDeviceLocked(MidiDeviceInfo.TYPE_VIRTUAL,
    976                                     numInputPorts, numOutputPorts,
    977                                     inputPortNames.toArray(EMPTY_STRING_ARRAY),
    978                                     outputPortNames.toArray(EMPTY_STRING_ARRAY),
    979                                     properties, null, serviceInfo, isPrivate, uid);
    980                             }
    981                             // setting properties to null signals that we are no longer
    982                             // processing a <device>
    983                             properties = null;
    984                             inputPortNames.clear();
    985                             outputPortNames.clear();
    986                         }
    987                     }
    988                 }
    989             }
    990         } catch (Exception e) {
    991             Log.w(TAG, "Unable to load component info " + serviceInfo.toString(), e);
    992         } finally {
    993             if (parser != null) parser.close();
    994         }
    995     }
    996 
    997     private void removePackageDeviceServers(String packageName) {
    998         synchronized (mDevicesByInfo) {
    999             Iterator<Device> iterator = mDevicesByInfo.values().iterator();
   1000             while (iterator.hasNext()) {
   1001                 Device device = iterator.next();
   1002                 if (packageName.equals(device.getPackageName())) {
   1003                     iterator.remove();
   1004                     removeDeviceLocked(device);
   1005                 }
   1006             }
   1007         }
   1008     }
   1009 
   1010     @Override
   1011     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
   1012         if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
   1013         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
   1014 
   1015         pw.println("MIDI Manager State:");
   1016         pw.increaseIndent();
   1017 
   1018         pw.println("Devices:");
   1019         pw.increaseIndent();
   1020         synchronized (mDevicesByInfo) {
   1021             for (Device device : mDevicesByInfo.values()) {
   1022                 pw.println(device.toString());
   1023             }
   1024         }
   1025         pw.decreaseIndent();
   1026 
   1027         pw.println("Clients:");
   1028         pw.increaseIndent();
   1029         synchronized (mClients) {
   1030             for (Client client : mClients.values()) {
   1031                 pw.println(client.toString());
   1032             }
   1033         }
   1034         pw.decreaseIndent();
   1035     }
   1036 }
   1037