xref: /aosp_15_r20/external/walt/ios/WALT/MIDIClient.m (revision bf47c6829f95be9dd55f4c5bbc44a71c90aad403)
1*bf47c682SAndroid Build Coastguard Worker/*
2*bf47c682SAndroid Build Coastguard Worker * Copyright (C) 2016 The Android Open Source Project
3*bf47c682SAndroid Build Coastguard Worker *
4*bf47c682SAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*bf47c682SAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*bf47c682SAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*bf47c682SAndroid Build Coastguard Worker *
8*bf47c682SAndroid Build Coastguard Worker *      http://www.apache.org/licenses/LICENSE-2.0
9*bf47c682SAndroid Build Coastguard Worker *
10*bf47c682SAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*bf47c682SAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*bf47c682SAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*bf47c682SAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*bf47c682SAndroid Build Coastguard Worker * limitations under the License.
15*bf47c682SAndroid Build Coastguard Worker */
16*bf47c682SAndroid Build Coastguard Worker
17*bf47c682SAndroid Build Coastguard Worker#import "MIDIClient.h"
18*bf47c682SAndroid Build Coastguard Worker
19*bf47c682SAndroid Build Coastguard Worker#include <CoreMIDI/CoreMIDI.h>
20*bf47c682SAndroid Build Coastguard Worker
21*bf47c682SAndroid Build Coastguard Worker#import "MIDIEndpoint.h"
22*bf47c682SAndroid Build Coastguard Worker#import "MIDIMessage.h"
23*bf47c682SAndroid Build Coastguard Worker
24*bf47c682SAndroid Build Coastguard WorkerNSString * const MIDIClientErrorDomain = @"MIDIClientErrorDomain";
25*bf47c682SAndroid Build Coastguard Worker
26*bf47c682SAndroid Build Coastguard Worker@interface MIDIClient ()
27*bf47c682SAndroid Build Coastguard Worker@property (readwrite, nonatomic) MIDISource *source;
28*bf47c682SAndroid Build Coastguard Worker@property (readwrite, nonatomic) MIDIDestination *destination;
29*bf47c682SAndroid Build Coastguard Worker// Used by midiRead() for SysEx messages spanning multiple packets.
30*bf47c682SAndroid Build Coastguard Worker@property (readwrite, nonatomic) NSMutableData *sysExBuffer;
31*bf47c682SAndroid Build Coastguard Worker
32*bf47c682SAndroid Build Coastguard Worker/** Returns whether the client's source or destination is attached to a particular device. */
33*bf47c682SAndroid Build Coastguard Worker- (BOOL)attachedToDevice:(MIDIDeviceRef)device;
34*bf47c682SAndroid Build Coastguard Worker@end
35*bf47c682SAndroid Build Coastguard Worker
36*bf47c682SAndroid Build Coastguard Worker// Note: These functions (midiStateChanged and midiRead) are not called on the main thread!
37*bf47c682SAndroid Build Coastguard Workerstatic void midiStateChanged(const MIDINotification *message, void *context) {
38*bf47c682SAndroid Build Coastguard Worker  MIDIClient *client = (__bridge MIDIClient *)context;
39*bf47c682SAndroid Build Coastguard Worker
40*bf47c682SAndroid Build Coastguard Worker  switch (message->messageID) {
41*bf47c682SAndroid Build Coastguard Worker    case kMIDIMsgObjectAdded: {
42*bf47c682SAndroid Build Coastguard Worker      const MIDIObjectAddRemoveNotification *notification =
43*bf47c682SAndroid Build Coastguard Worker          (const MIDIObjectAddRemoveNotification *)message;
44*bf47c682SAndroid Build Coastguard Worker
45*bf47c682SAndroid Build Coastguard Worker      @autoreleasepool {
46*bf47c682SAndroid Build Coastguard Worker        if ((notification->childType & (kMIDIObjectType_Source|kMIDIObjectType_Destination)) != 0 &&
47*bf47c682SAndroid Build Coastguard Worker            [client.delegate respondsToSelector:@selector(MIDIClientEndpointAdded:)]) {
48*bf47c682SAndroid Build Coastguard Worker          [client.delegate MIDIClientEndpointAdded:client];
49*bf47c682SAndroid Build Coastguard Worker        }
50*bf47c682SAndroid Build Coastguard Worker      }
51*bf47c682SAndroid Build Coastguard Worker      break;
52*bf47c682SAndroid Build Coastguard Worker    }
53*bf47c682SAndroid Build Coastguard Worker
54*bf47c682SAndroid Build Coastguard Worker    case kMIDIMsgObjectRemoved: {
55*bf47c682SAndroid Build Coastguard Worker      const MIDIObjectAddRemoveNotification *notification =
56*bf47c682SAndroid Build Coastguard Worker          (const MIDIObjectAddRemoveNotification *)message;
57*bf47c682SAndroid Build Coastguard Worker
58*bf47c682SAndroid Build Coastguard Worker      @autoreleasepool {
59*bf47c682SAndroid Build Coastguard Worker        if ((notification->childType & (kMIDIObjectType_Source|kMIDIObjectType_Destination)) != 0 &&
60*bf47c682SAndroid Build Coastguard Worker            [client.delegate respondsToSelector:@selector(MIDIClientEndpointRemoved:)]) {
61*bf47c682SAndroid Build Coastguard Worker          [client.delegate MIDIClientEndpointRemoved:client];
62*bf47c682SAndroid Build Coastguard Worker        }
63*bf47c682SAndroid Build Coastguard Worker      }
64*bf47c682SAndroid Build Coastguard Worker      break;
65*bf47c682SAndroid Build Coastguard Worker    }
66*bf47c682SAndroid Build Coastguard Worker
67*bf47c682SAndroid Build Coastguard Worker    case kMIDIMsgSetupChanged:
68*bf47c682SAndroid Build Coastguard Worker    case kMIDIMsgPropertyChanged:
69*bf47c682SAndroid Build Coastguard Worker    case kMIDIMsgSerialPortOwnerChanged:
70*bf47c682SAndroid Build Coastguard Worker    case kMIDIMsgThruConnectionsChanged: {
71*bf47c682SAndroid Build Coastguard Worker      @autoreleasepool {
72*bf47c682SAndroid Build Coastguard Worker        if ([client.delegate respondsToSelector:@selector(MIDIClientConfigurationChanged:)]) {
73*bf47c682SAndroid Build Coastguard Worker          [client.delegate MIDIClientConfigurationChanged:client];
74*bf47c682SAndroid Build Coastguard Worker        }
75*bf47c682SAndroid Build Coastguard Worker      }
76*bf47c682SAndroid Build Coastguard Worker      break;
77*bf47c682SAndroid Build Coastguard Worker    }
78*bf47c682SAndroid Build Coastguard Worker
79*bf47c682SAndroid Build Coastguard Worker    case kMIDIMsgIOError: {
80*bf47c682SAndroid Build Coastguard Worker      const MIDIIOErrorNotification *notification = (const MIDIIOErrorNotification *)message;
81*bf47c682SAndroid Build Coastguard Worker
82*bf47c682SAndroid Build Coastguard Worker      if ([client attachedToDevice:notification->driverDevice]) {
83*bf47c682SAndroid Build Coastguard Worker        @autoreleasepool {
84*bf47c682SAndroid Build Coastguard Worker          NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain
85*bf47c682SAndroid Build Coastguard Worker                                               code:notification->errorCode
86*bf47c682SAndroid Build Coastguard Worker                                           userInfo:nil];
87*bf47c682SAndroid Build Coastguard Worker          if ([client.delegate respondsToSelector:@selector(MIDIClient:receivedError:)]) {
88*bf47c682SAndroid Build Coastguard Worker            [client.delegate MIDIClient:client receivedError:error];
89*bf47c682SAndroid Build Coastguard Worker          }
90*bf47c682SAndroid Build Coastguard Worker        }
91*bf47c682SAndroid Build Coastguard Worker      }
92*bf47c682SAndroid Build Coastguard Worker      break;
93*bf47c682SAndroid Build Coastguard Worker    }
94*bf47c682SAndroid Build Coastguard Worker
95*bf47c682SAndroid Build Coastguard Worker    default: {
96*bf47c682SAndroid Build Coastguard Worker      NSLog(@"Unhandled MIDI state change: %d", (int)message->messageID);
97*bf47c682SAndroid Build Coastguard Worker    }
98*bf47c682SAndroid Build Coastguard Worker  }
99*bf47c682SAndroid Build Coastguard Worker}
100*bf47c682SAndroid Build Coastguard Worker
101*bf47c682SAndroid Build Coastguard Workerstatic void midiRead(const MIDIPacketList *packets, void *portContext, void *sourceContext) {
102*bf47c682SAndroid Build Coastguard Worker  MIDIClient *client = (__bridge MIDIClient *)portContext;
103*bf47c682SAndroid Build Coastguard Worker
104*bf47c682SAndroid Build Coastguard Worker  // Read the data out of each packet and forward it to the client's delegate.
105*bf47c682SAndroid Build Coastguard Worker  // Each MIDIPacket will contain either some MIDI commands, or the start/continuation of a SysEx
106*bf47c682SAndroid Build Coastguard Worker  // command. The start of a command is detected with a byte greater than or equal to 0x80 (all data
107*bf47c682SAndroid Build Coastguard Worker  // must be 7-bit friendly). The end of a SysEx command is marked with 0x7F.
108*bf47c682SAndroid Build Coastguard Worker
109*bf47c682SAndroid Build Coastguard Worker  // TODO(pquinn): Should something be done with the timestamp data?
110*bf47c682SAndroid Build Coastguard Worker
111*bf47c682SAndroid Build Coastguard Worker  UInt32 packetCount = packets->numPackets;
112*bf47c682SAndroid Build Coastguard Worker  const MIDIPacket *packet = &packets->packet[0];
113*bf47c682SAndroid Build Coastguard Worker  @autoreleasepool {
114*bf47c682SAndroid Build Coastguard Worker    while (packetCount--) {
115*bf47c682SAndroid Build Coastguard Worker      if (packet->length == 0) {
116*bf47c682SAndroid Build Coastguard Worker        continue;
117*bf47c682SAndroid Build Coastguard Worker      }
118*bf47c682SAndroid Build Coastguard Worker
119*bf47c682SAndroid Build Coastguard Worker      const Byte firstByte = packet->data[0];
120*bf47c682SAndroid Build Coastguard Worker      const Byte lastByte = packet->data[packet->length - 1];
121*bf47c682SAndroid Build Coastguard Worker
122*bf47c682SAndroid Build Coastguard Worker      if (firstByte >= 0x80 && firstByte != MIDIMessageSysEx && firstByte != MIDIMessageSysExEnd) {
123*bf47c682SAndroid Build Coastguard Worker        // Packet describes non-SysEx MIDI messages.
124*bf47c682SAndroid Build Coastguard Worker        NSMutableData *data = nil;
125*bf47c682SAndroid Build Coastguard Worker        for (UInt16 i = 0; i < packet->length; ++i) {
126*bf47c682SAndroid Build Coastguard Worker          // Packets can contain multiple MIDI messages.
127*bf47c682SAndroid Build Coastguard Worker          if (packet->data[i] >= 0x80) {
128*bf47c682SAndroid Build Coastguard Worker            if (data.length > 0) {  // Tell the delegate about the last extracted command.
129*bf47c682SAndroid Build Coastguard Worker              [client.delegate MIDIClient:client receivedData:data];
130*bf47c682SAndroid Build Coastguard Worker            }
131*bf47c682SAndroid Build Coastguard Worker            data = [[NSMutableData alloc] init];
132*bf47c682SAndroid Build Coastguard Worker          }
133*bf47c682SAndroid Build Coastguard Worker          [data appendBytes:&packet->data[i] length:1];
134*bf47c682SAndroid Build Coastguard Worker        }
135*bf47c682SAndroid Build Coastguard Worker
136*bf47c682SAndroid Build Coastguard Worker        if (data.length > 0) {
137*bf47c682SAndroid Build Coastguard Worker          [client.delegate MIDIClient:client receivedData:data];
138*bf47c682SAndroid Build Coastguard Worker        }
139*bf47c682SAndroid Build Coastguard Worker      }
140*bf47c682SAndroid Build Coastguard Worker
141*bf47c682SAndroid Build Coastguard Worker      if (firstByte == MIDIMessageSysEx) {
142*bf47c682SAndroid Build Coastguard Worker        // The start of a SysEx message; collect data into sysExBuffer.
143*bf47c682SAndroid Build Coastguard Worker        client.sysExBuffer = [[NSMutableData alloc] initWithBytes:packet->data
144*bf47c682SAndroid Build Coastguard Worker                                                           length:packet->length];
145*bf47c682SAndroid Build Coastguard Worker      } else if (firstByte < 0x80 || firstByte == MIDIMessageSysExEnd) {
146*bf47c682SAndroid Build Coastguard Worker        // Continuation or end of a SysEx message.
147*bf47c682SAndroid Build Coastguard Worker        [client.sysExBuffer appendBytes:packet->data length:packet->length];
148*bf47c682SAndroid Build Coastguard Worker      }
149*bf47c682SAndroid Build Coastguard Worker
150*bf47c682SAndroid Build Coastguard Worker      if (lastByte == MIDIMessageSysExEnd) {
151*bf47c682SAndroid Build Coastguard Worker        // End of a SysEx message.
152*bf47c682SAndroid Build Coastguard Worker        [client.delegate MIDIClient:client receivedData:client.sysExBuffer];
153*bf47c682SAndroid Build Coastguard Worker        client.sysExBuffer = nil;
154*bf47c682SAndroid Build Coastguard Worker      }
155*bf47c682SAndroid Build Coastguard Worker
156*bf47c682SAndroid Build Coastguard Worker      packet = MIDIPacketNext(packet);
157*bf47c682SAndroid Build Coastguard Worker    }
158*bf47c682SAndroid Build Coastguard Worker  }
159*bf47c682SAndroid Build Coastguard Worker}
160*bf47c682SAndroid Build Coastguard Worker
161*bf47c682SAndroid Build Coastguard Worker@implementation MIDIClient {
162*bf47c682SAndroid Build Coastguard Worker  NSString *_name;
163*bf47c682SAndroid Build Coastguard Worker  MIDIClientRef _client;
164*bf47c682SAndroid Build Coastguard Worker  MIDIPortRef _input;
165*bf47c682SAndroid Build Coastguard Worker  MIDIPortRef _output;
166*bf47c682SAndroid Build Coastguard Worker}
167*bf47c682SAndroid Build Coastguard Worker
168*bf47c682SAndroid Build Coastguard Worker- (instancetype)initWithName:(NSString *)name error:(NSError **)error {
169*bf47c682SAndroid Build Coastguard Worker  if ((self = [super init])) {
170*bf47c682SAndroid Build Coastguard Worker    _name = name;  // Hold onto the name because MIDIClientCreate() doesn't retain it.
171*bf47c682SAndroid Build Coastguard Worker    OSStatus result = MIDIClientCreate((__bridge CFStringRef)name,
172*bf47c682SAndroid Build Coastguard Worker                                       midiStateChanged,
173*bf47c682SAndroid Build Coastguard Worker                                       (__bridge void *)self,
174*bf47c682SAndroid Build Coastguard Worker                                       &_client);
175*bf47c682SAndroid Build Coastguard Worker    if (result != noErr) {
176*bf47c682SAndroid Build Coastguard Worker      if (error) {
177*bf47c682SAndroid Build Coastguard Worker        *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
178*bf47c682SAndroid Build Coastguard Worker      }
179*bf47c682SAndroid Build Coastguard Worker      self = nil;
180*bf47c682SAndroid Build Coastguard Worker    }
181*bf47c682SAndroid Build Coastguard Worker  }
182*bf47c682SAndroid Build Coastguard Worker  return self;
183*bf47c682SAndroid Build Coastguard Worker}
184*bf47c682SAndroid Build Coastguard Worker
185*bf47c682SAndroid Build Coastguard Worker- (void)dealloc {
186*bf47c682SAndroid Build Coastguard Worker  MIDIClientDispose(_client);  // Automatically disposes of the ports too.
187*bf47c682SAndroid Build Coastguard Worker}
188*bf47c682SAndroid Build Coastguard Worker
189*bf47c682SAndroid Build Coastguard Worker- (BOOL)connectToSource:(MIDISource *)source error:(NSError **)error {
190*bf47c682SAndroid Build Coastguard Worker  OSStatus result = noErr;
191*bf47c682SAndroid Build Coastguard Worker  if (!_input) {  // Lazily create the input port.
192*bf47c682SAndroid Build Coastguard Worker    result = MIDIInputPortCreate(_client,
193*bf47c682SAndroid Build Coastguard Worker                                 (__bridge CFStringRef)_name,
194*bf47c682SAndroid Build Coastguard Worker                                 midiRead,
195*bf47c682SAndroid Build Coastguard Worker                                 (__bridge void *)self,
196*bf47c682SAndroid Build Coastguard Worker                                 &_input);
197*bf47c682SAndroid Build Coastguard Worker    if (result != noErr) {
198*bf47c682SAndroid Build Coastguard Worker      if (error) {
199*bf47c682SAndroid Build Coastguard Worker        *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
200*bf47c682SAndroid Build Coastguard Worker      }
201*bf47c682SAndroid Build Coastguard Worker      return NO;
202*bf47c682SAndroid Build Coastguard Worker    }
203*bf47c682SAndroid Build Coastguard Worker  }
204*bf47c682SAndroid Build Coastguard Worker
205*bf47c682SAndroid Build Coastguard Worker  // Connect the source to the port.
206*bf47c682SAndroid Build Coastguard Worker  result = MIDIPortConnectSource(_input, source.endpoint, (__bridge void *)self);
207*bf47c682SAndroid Build Coastguard Worker  if (result != noErr) {
208*bf47c682SAndroid Build Coastguard Worker    if (error) {
209*bf47c682SAndroid Build Coastguard Worker      *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
210*bf47c682SAndroid Build Coastguard Worker    }
211*bf47c682SAndroid Build Coastguard Worker    return NO;
212*bf47c682SAndroid Build Coastguard Worker  }
213*bf47c682SAndroid Build Coastguard Worker
214*bf47c682SAndroid Build Coastguard Worker  self.source = source;
215*bf47c682SAndroid Build Coastguard Worker  return YES;
216*bf47c682SAndroid Build Coastguard Worker}
217*bf47c682SAndroid Build Coastguard Worker
218*bf47c682SAndroid Build Coastguard Worker- (BOOL)connectToDestination:(MIDIDestination *)destination error:(NSError **)error {
219*bf47c682SAndroid Build Coastguard Worker  if (!_output) {  // Lazily create the output port.
220*bf47c682SAndroid Build Coastguard Worker    OSStatus result = MIDIOutputPortCreate(_client,
221*bf47c682SAndroid Build Coastguard Worker                                           (__bridge CFStringRef)_name,
222*bf47c682SAndroid Build Coastguard Worker                                           &_output);
223*bf47c682SAndroid Build Coastguard Worker    if (result != noErr) {
224*bf47c682SAndroid Build Coastguard Worker      if (error) {
225*bf47c682SAndroid Build Coastguard Worker        *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
226*bf47c682SAndroid Build Coastguard Worker      }
227*bf47c682SAndroid Build Coastguard Worker      return NO;
228*bf47c682SAndroid Build Coastguard Worker    }
229*bf47c682SAndroid Build Coastguard Worker  }
230*bf47c682SAndroid Build Coastguard Worker
231*bf47c682SAndroid Build Coastguard Worker  self.destination = destination;
232*bf47c682SAndroid Build Coastguard Worker  return YES;
233*bf47c682SAndroid Build Coastguard Worker}
234*bf47c682SAndroid Build Coastguard Worker
235*bf47c682SAndroid Build Coastguard Worker- (BOOL)sendData:(NSData *)data error:(NSError **)error {
236*bf47c682SAndroid Build Coastguard Worker  if (data.length > sizeof(((MIDIPacket *)0)->data)) {
237*bf47c682SAndroid Build Coastguard Worker    // TODO(pquinn): Dynamically allocate a buffer.
238*bf47c682SAndroid Build Coastguard Worker    if (error) {
239*bf47c682SAndroid Build Coastguard Worker      *error = [NSError errorWithDomain:MIDIClientErrorDomain
240*bf47c682SAndroid Build Coastguard Worker                                   code:0
241*bf47c682SAndroid Build Coastguard Worker                               userInfo:@{NSLocalizedDescriptionKey:
242*bf47c682SAndroid Build Coastguard Worker                                            @"Too much data for a basic MIDIPacket."}];
243*bf47c682SAndroid Build Coastguard Worker    }
244*bf47c682SAndroid Build Coastguard Worker    return NO;
245*bf47c682SAndroid Build Coastguard Worker  }
246*bf47c682SAndroid Build Coastguard Worker
247*bf47c682SAndroid Build Coastguard Worker  MIDIPacketList packetList;
248*bf47c682SAndroid Build Coastguard Worker  MIDIPacket *packet = MIDIPacketListInit(&packetList);
249*bf47c682SAndroid Build Coastguard Worker  packet = MIDIPacketListAdd(&packetList, sizeof(packetList), packet, 0, data.length, data.bytes);
250*bf47c682SAndroid Build Coastguard Worker  if (!packet) {
251*bf47c682SAndroid Build Coastguard Worker    if (error) {
252*bf47c682SAndroid Build Coastguard Worker      *error = [NSError errorWithDomain:MIDIClientErrorDomain
253*bf47c682SAndroid Build Coastguard Worker                                   code:0
254*bf47c682SAndroid Build Coastguard Worker                               userInfo:@{NSLocalizedDescriptionKey:
255*bf47c682SAndroid Build Coastguard Worker                                            @"Packet too large for buffer."}];
256*bf47c682SAndroid Build Coastguard Worker    }
257*bf47c682SAndroid Build Coastguard Worker    return NO;
258*bf47c682SAndroid Build Coastguard Worker  }
259*bf47c682SAndroid Build Coastguard Worker
260*bf47c682SAndroid Build Coastguard Worker  OSStatus result = MIDISend(_output, self.destination.endpoint, &packetList);
261*bf47c682SAndroid Build Coastguard Worker  if (result != noErr) {
262*bf47c682SAndroid Build Coastguard Worker    if (error) {
263*bf47c682SAndroid Build Coastguard Worker      *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
264*bf47c682SAndroid Build Coastguard Worker    }
265*bf47c682SAndroid Build Coastguard Worker    return NO;
266*bf47c682SAndroid Build Coastguard Worker  }
267*bf47c682SAndroid Build Coastguard Worker  return YES;
268*bf47c682SAndroid Build Coastguard Worker}
269*bf47c682SAndroid Build Coastguard Worker
270*bf47c682SAndroid Build Coastguard Worker- (BOOL)attachedToDevice:(MIDIDeviceRef)device {
271*bf47c682SAndroid Build Coastguard Worker  MIDIDeviceRef sourceDevice = 0, destinationDevice = 0;
272*bf47c682SAndroid Build Coastguard Worker  MIDIEntityGetDevice(self.source.endpoint, &sourceDevice);
273*bf47c682SAndroid Build Coastguard Worker  MIDIEntityGetDevice(self.destination.endpoint, &destinationDevice);
274*bf47c682SAndroid Build Coastguard Worker
275*bf47c682SAndroid Build Coastguard Worker  SInt32 sourceID = 0, destinationID = 0, deviceID = 0;
276*bf47c682SAndroid Build Coastguard Worker  MIDIObjectGetIntegerProperty(sourceDevice, kMIDIPropertyUniqueID, &sourceID);
277*bf47c682SAndroid Build Coastguard Worker  MIDIObjectGetIntegerProperty(destinationDevice, kMIDIPropertyUniqueID, &destinationID);
278*bf47c682SAndroid Build Coastguard Worker  MIDIObjectGetIntegerProperty(device, kMIDIPropertyUniqueID, &deviceID);
279*bf47c682SAndroid Build Coastguard Worker
280*bf47c682SAndroid Build Coastguard Worker  return (deviceID == sourceID || deviceID == destinationID);
281*bf47c682SAndroid Build Coastguard Worker}
282*bf47c682SAndroid Build Coastguard Worker@end
283