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 
Lifecycle(Context context)67         public Lifecycle(Context context) {
68             super(context);
69         }
70 
71         @Override
onStart()72         public void onStart() {
73             mMidiService = new MidiService(getContext());
74             publishBinderService(Context.MIDI_SERVICE, mMidiService);
75         }
76 
77         @Override
onUnlockUser(int userHandle)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 
Client(IBinder token)144         public Client(IBinder token) {
145             mToken = token;
146             mUid = Binder.getCallingUid();
147             mPid = Binder.getCallingPid();
148         }
149 
getUid()150         public int getUid() {
151             return mUid;
152         }
153 
addListener(IMidiDeviceListener listener)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 
removeListener(IMidiDeviceListener listener)160         public void removeListener(IMidiDeviceListener listener) {
161             mListeners.remove(listener.asBinder());
162             if (mListeners.size() == 0 && mDeviceConnections.size() == 0) {
163                 close();
164             }
165         }
166 
addDeviceConnection(Device device, IMidiDeviceOpenCallback callback)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()
removeDeviceConnection(IBinder token)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()
removeDeviceConnection(DeviceConnection connection)185         public void removeDeviceConnection(DeviceConnection connection) {
186             mDeviceConnections.remove(connection.getToken());
187             if (mListeners.size() == 0 && mDeviceConnections.size() == 0) {
188                 close();
189             }
190         }
191 
deviceAdded(Device device)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 
deviceRemoved(Device device)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 
deviceStatusChanged(Device device, MidiDeviceStatus status)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 
close()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
binderDied()245         public void binderDied() {
246             Log.d(TAG, "Client died: " + this);
247             close();
248         }
249 
250         @Override
toString()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 
getClient(IBinder token)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 
Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo, ServiceInfo serviceInfo, int uid)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 
Device(BluetoothDevice bluetoothDevice)314         public Device(BluetoothDevice bluetoothDevice) {
315             mBluetoothDevice = bluetoothDevice;
316             mServiceInfo = null;
317             mUid = mBluetoothServiceUid;
318         }
319 
setDeviceServer(IMidiDeviceServer server)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 
getDeviceInfo()357         public MidiDeviceInfo getDeviceInfo() {
358             return mDeviceInfo;
359         }
360 
361         // only used for bluetooth devices, which are created before we have a MidiDeviceInfo
setDeviceInfo(MidiDeviceInfo deviceInfo)362         public void setDeviceInfo(MidiDeviceInfo deviceInfo) {
363             mDeviceInfo = deviceInfo;
364         }
365 
getDeviceStatus()366         public MidiDeviceStatus getDeviceStatus() {
367             return mDeviceStatus;
368         }
369 
setDeviceStatus(MidiDeviceStatus status)370         public void setDeviceStatus(MidiDeviceStatus status) {
371             mDeviceStatus = status;
372         }
373 
getDeviceServer()374         public IMidiDeviceServer getDeviceServer() {
375             return mServer;
376         }
377 
getServiceInfo()378         public ServiceInfo getServiceInfo() {
379             return mServiceInfo;
380         }
381 
getPackageName()382         public String getPackageName() {
383             return (mServiceInfo == null ? null : mServiceInfo.packageName);
384         }
385 
getUid()386         public int getUid() {
387             return mUid;
388         }
389 
isUidAllowed(int uid)390         public boolean isUidAllowed(int uid) {
391             return (!mDeviceInfo.isPrivate() || mUid == uid);
392         }
393 
addDeviceConnection(DeviceConnection connection)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 
removeDeviceConnection(DeviceConnection connection)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
closeLocked()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
binderDied()498         public void binderDied() {
499             Log.d(TAG, "Device died: " + this);
500             synchronized (mDevicesByInfo) {
501                 closeLocked();
502             }
503         }
504 
505         @Override
toString()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 
DeviceConnection(Device device, Client client, IMidiDeviceOpenCallback callback)528         public DeviceConnection(Device device, Client client, IMidiDeviceOpenCallback callback) {
529             mDevice = device;
530             mClient = client;
531             mCallback = callback;
532         }
533 
getDevice()534         public Device getDevice() {
535             return mDevice;
536         }
537 
getClient()538         public Client getClient() {
539             return mClient;
540         }
541 
getToken()542         public IBinder getToken() {
543             return mToken;
544         }
545 
notifyClient(IMidiDeviceServer deviceServer)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
toString()558         public String toString() {
559             return "DeviceConnection Device ID: " + mDevice.getDeviceInfo().getId();
560         }
561     }
562 
MidiService(Context context)563     public MidiService(Context context) {
564         mContext = context;
565         mPackageManager = context.getPackageManager();
566 
567         mBluetoothServiceUid = -1;
568     }
569 
onUnlockUser()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
registerListener(IBinder token, IMidiDeviceListener listener)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
unregisterListener(IBinder token, IMidiDeviceListener listener)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.
updateStickyDeviceStatus(int uid, IMidiDeviceListener listener)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 
getDevices()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
openDevice(IBinder token, MidiDeviceInfo deviceInfo, IMidiDeviceOpenCallback callback)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
openBluetoothDevice(IBinder token, BluetoothDevice bluetoothDevice, IMidiDeviceOpenCallback callback)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
closeDevice(IBinder clientToken, IBinder deviceToken)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
registerDeviceServer(IMidiDeviceServer server, int numInputPorts, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, Bundle properties, int type)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
unregisterDeviceServer(IMidiDeviceServer server)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
getServiceDeviceInfo(String packageName, String className)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
getDeviceStatus(MidiDeviceInfo deviceInfo)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
setDeviceStatus(IMidiDeviceServer server, MidiDeviceStatus status)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 
notifyDeviceStatusChanged(Device device, MidiDeviceStatus status)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
addDeviceLocked(int type, int numInputPorts, int numOutputPorts, String[] inputPortNames, String[] outputPortNames, Bundle properties, IMidiDeviceServer server, ServiceInfo serviceInfo, boolean isPrivate, int uid)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
removeDeviceLocked(Device device)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 
addPackageDeviceServers(String packageName)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 
addPackageDeviceServer(ServiceInfo serviceInfo)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 
removePackageDeviceServers(String packageName)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
dump(FileDescriptor fd, PrintWriter writer, String[] args)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