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