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