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