1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "device/bluetooth/bluetooth_discovery_manager_mac.h"
6
7#import <IOBluetooth/objc/IOBluetoothDevice.h>
8#import <IOBluetooth/objc/IOBluetoothDeviceInquiry.h>
9
10#include "base/mac/scoped_nsobject.h"
11#include "base/mac/sdk_forward_declarations.h"
12#include "base/logging.h"
13
14namespace device {
15
16class BluetoothDiscoveryManagerMacClassic;
17
18}  // namespace device
19
20// IOBluetoothDeviceInquiryDelegate implementation.
21@interface BluetoothDeviceInquiryDelegate
22    : NSObject<IOBluetoothDeviceInquiryDelegate> {
23 @private
24  device::BluetoothDiscoveryManagerMacClassic* manager_;  // weak
25}
26
27- (id)initWithManager:(device::BluetoothDiscoveryManagerMacClassic*)manager;
28
29@end
30
31namespace device {
32
33// ementation of BluetoothDiscoveryManagerMac for Bluetooth classic device
34// discovery, using the IOBluetooth framework.
35class BluetoothDiscoveryManagerMacClassic
36    : public BluetoothDiscoveryManagerMac {
37 public:
38  explicit BluetoothDiscoveryManagerMacClassic(Observer* observer)
39      : BluetoothDiscoveryManagerMac(observer),
40        should_do_discovery_(false),
41        inquiry_running_(false),
42        inquiry_delegate_(
43            [[BluetoothDeviceInquiryDelegate alloc] initWithManager:this]),
44        inquiry_([[IOBluetoothDeviceInquiry alloc]
45            initWithDelegate:inquiry_delegate_]) {}
46
47  virtual ~BluetoothDiscoveryManagerMacClassic() {}
48
49  // BluetoothDiscoveryManagerMac override.
50  virtual bool IsDiscovering() const OVERRIDE { return should_do_discovery_; }
51
52  // BluetoothDiscoveryManagerMac override.
53  virtual bool StartDiscovery() OVERRIDE {
54    DVLOG(1) << "Bluetooth Classic: StartDiscovery";
55    DCHECK(!should_do_discovery_);
56
57    DVLOG(1) << "Discovery requested";
58    should_do_discovery_ = true;
59
60    if (inquiry_running_) {
61      DVLOG(1) << "Device inquiry already running";
62      return true;
63    }
64
65    DVLOG(1) << "Requesting to start device inquiry";
66    if ([inquiry_ start] != kIOReturnSuccess) {
67      DVLOG(1) << "Failed to start device inquiry";
68
69      // Set |should_do_discovery_| to false here. Since we're reporting an
70      // error, we're indicating that the adapter call StartDiscovery again
71      // if needed.
72      should_do_discovery_ = false;
73      return false;
74    }
75
76    DVLOG(1) << "Device inquiry start was successful";
77    return true;
78  }
79
80  // BluetoothDiscoveryManagerMac override.
81  virtual bool StopDiscovery() OVERRIDE {
82    DVLOG(1) << "Bluetooth Classic: StopDiscovery";
83    DCHECK(should_do_discovery_);
84
85    should_do_discovery_ = false;
86
87    if (!inquiry_running_) {
88      DVLOG(1) << "No device inquiry running; discovery stopped";
89      return true;
90    }
91
92    DVLOG(1) << "Requesting to stop device inquiry";
93    IOReturn status = [inquiry_ stop];
94    if (status == kIOReturnSuccess) {
95      DVLOG(1) << "Device inquiry stop was successful";
96      return true;
97    }
98
99    if (status == kIOReturnNotPermitted) {
100      DVLOG(1) << "Device inquiry was already stopped";
101      return true;
102    }
103
104    LOG(WARNING) << "Failed to stop device inquiry";
105    return false;
106  }
107
108  // Called by BluetoothDeviceInquiryDelegate.
109  void DeviceInquiryStarted(IOBluetoothDeviceInquiry* inquiry) {
110    DCHECK(!inquiry_running_);
111
112    DVLOG(1) << "Device inquiry started!";
113
114    // If discovery was requested to stop in the mean time, stop the inquiry.
115    if (!should_do_discovery_) {
116      DVLOG(1) << "Discovery stop was requested earlier. Stopping inquiry";
117      [inquiry stop];
118      return;
119    }
120
121    inquiry_running_ = true;
122  }
123
124  void DeviceFound(IOBluetoothDeviceInquiry* inquiry,
125                   IOBluetoothDevice* device) {
126    DCHECK(observer_);
127    observer_->DeviceFound(device);
128  }
129
130  void DeviceInquiryComplete(IOBluetoothDeviceInquiry* inquiry,
131                             IOReturn error,
132                             bool aborted) {
133    DCHECK_EQ(inquiry_, inquiry);
134    DCHECK(observer_);
135    DVLOG(1) << "Device inquiry complete";
136    inquiry_running_ = false;
137
138    // If discovery is no longer desired, notify observers that discovery
139    // has stopped and return.
140    if (!should_do_discovery_) {
141      observer_->DiscoveryStopped(false /* unexpected */);
142      return;
143    }
144
145    // If discovery has stopped due to an unexpected reason, notify the
146    // observers and return.
147    if (error != kIOReturnSuccess) {
148      DVLOG(1) << "Inquiry has stopped with an error: " << error;
149      should_do_discovery_ = false;
150      observer_->DiscoveryStopped(true /* unexpected */);
151      return;
152    }
153
154    DVLOG(1) << "Restarting device inquiry";
155
156    if ([inquiry_ start] == kIOReturnSuccess) {
157      DVLOG(1) << "Device inquiry restart was successful";
158      return;
159    }
160
161    DVLOG(1) << "Failed to restart discovery";
162    should_do_discovery_ = false;
163    DCHECK(observer_);
164    observer_->DiscoveryStopped(true /* unexpected */);
165  }
166
167 private:
168  // The requested discovery state.
169  bool should_do_discovery_;
170
171  // The current inquiry state.
172  bool inquiry_running_;
173
174  // Objective-C objects for running and tracking device inquiry.
175  base::scoped_nsobject<BluetoothDeviceInquiryDelegate> inquiry_delegate_;
176  base::scoped_nsobject<IOBluetoothDeviceInquiry> inquiry_;
177
178  DISALLOW_COPY_AND_ASSIGN(BluetoothDiscoveryManagerMacClassic);
179};
180
181BluetoothDiscoveryManagerMac::BluetoothDiscoveryManagerMac(
182    Observer* observer) : observer_(observer) {
183  DCHECK(observer);
184}
185
186BluetoothDiscoveryManagerMac::~BluetoothDiscoveryManagerMac() {
187}
188
189// static
190BluetoothDiscoveryManagerMac* BluetoothDiscoveryManagerMac::CreateClassic(
191    Observer* observer) {
192  return new BluetoothDiscoveryManagerMacClassic(observer);
193}
194
195}  // namespace device
196
197@implementation BluetoothDeviceInquiryDelegate
198
199- (id)initWithManager:
200          (device::BluetoothDiscoveryManagerMacClassic*)manager {
201  if ((self = [super init]))
202    manager_ = manager;
203
204  return self;
205}
206
207- (void)deviceInquiryStarted:(IOBluetoothDeviceInquiry*)sender {
208  manager_->DeviceInquiryStarted(sender);
209}
210
211- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
212                          device:(IOBluetoothDevice*)device {
213  manager_->DeviceFound(sender, device);
214}
215
216- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
217                        error:(IOReturn)error
218                      aborted:(BOOL)aborted {
219  manager_->DeviceInquiryComplete(sender, error, aborted);
220}
221
222@end
223