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
17package com.android.server.midi;
18
19import android.bluetooth.BluetoothDevice;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.PackageInfo;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.content.pm.ServiceInfo;
29import android.content.res.XmlResourceParser;
30import android.media.midi.IBluetoothMidiService;
31import android.media.midi.IMidiDeviceListener;
32import android.media.midi.IMidiDeviceOpenCallback;
33import android.media.midi.IMidiDeviceServer;
34import android.media.midi.IMidiManager;
35import android.media.midi.MidiDeviceInfo;
36import android.media.midi.MidiDeviceService;
37import android.media.midi.MidiDeviceStatus;
38import android.media.midi.MidiManager;
39import android.os.Binder;
40import android.os.Bundle;
41import android.os.IBinder;
42import android.os.Process;
43import android.os.RemoteException;
44import android.os.UserHandle;
45import android.util.Log;
46
47import com.android.internal.content.PackageMonitor;
48import com.android.internal.util.IndentingPrintWriter;
49import com.android.internal.util.XmlUtils;
50import com.android.server.SystemService;
51
52import org.xmlpull.v1.XmlPullParser;
53
54import java.io.FileDescriptor;
55import java.io.PrintWriter;
56import java.util.ArrayList;
57import java.util.HashMap;
58import java.util.Iterator;
59import java.util.List;
60
61public 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