1 /*
2 * Copyright 2008-2009, Oliver Ruiz Dorantes, <oliver.ruiz.dorantes@gmail.com>
3 * Copyright 2021, Haiku, Inc.
4 * Distributed under the terms of the MIT License.
5 *
6 * Authors:
7 * Fredrik Modéen <fredrik_at_modeen.se>
8 */
9
10 #include <Alert.h>
11 #include <Button.h>
12 #include <Catalog.h>
13 #include <LayoutBuilder.h>
14 #include <ListView.h>
15 #include <ListItem.h>
16 #include <MessageRunner.h>
17 #include <ScrollView.h>
18 #include <StatusBar.h>
19 #include <SpaceLayoutItem.h>
20 #include <TextView.h>
21 #include <TabView.h>
22
23 #include <bluetooth/bdaddrUtils.h>
24 #include <bluetooth/DiscoveryAgent.h>
25 #include <bluetooth/DiscoveryListener.h>
26 #include <bluetooth/LocalDevice.h>
27
28 #include "defs.h"
29 #include "DeviceListItem.h"
30 #include "InquiryPanel.h"
31
32
33 #undef B_TRANSLATION_CONTEXT
34 #define B_TRANSLATION_CONTEXT "Inquiry panel"
35
36 using Bluetooth::DeviceListItem;
37
38 // private funcionaility provided by kit
39 extern uint8 GetInquiryTime();
40
41 static const uint32 kMsgStart = 'InSt';
42 static const uint32 kMsgFinish = 'InFn';
43 static const uint32 kMsgShowDebug = 'ShDG';
44
45 static const uint32 kMsgInquiry = 'iQbt';
46 static const uint32 kMsgAddListDevice = 'aDdv';
47
48 static const uint32 kMsgSelected = 'isLt';
49 static const uint32 kMsgSecond = 'sCMs';
50 static const uint32 kMsgRetrieve = 'IrEt';
51
52
53 class PanelDiscoveryListener : public DiscoveryListener {
54
55 public:
56
PanelDiscoveryListener(InquiryPanel * iPanel)57 PanelDiscoveryListener(InquiryPanel* iPanel)
58 :
59 DiscoveryListener(),
60 fInquiryPanel(iPanel)
61 {
62
63 }
64
65
66 void
DeviceDiscovered(RemoteDevice * btDevice,DeviceClass cod)67 DeviceDiscovered(RemoteDevice* btDevice, DeviceClass cod)
68 {
69 BMessage* message = new BMessage(kMsgAddListDevice);
70 message->AddPointer("remoteItem", new DeviceListItem(btDevice));
71 fInquiryPanel->PostMessage(message);
72 }
73
74
75 void
InquiryCompleted(int discType)76 InquiryCompleted(int discType)
77 {
78 BMessage* message = new BMessage(kMsgFinish);
79 fInquiryPanel->PostMessage(message);
80 }
81
82
83 void
InquiryStarted(status_t status)84 InquiryStarted(status_t status)
85 {
86 BMessage* message = new BMessage(kMsgStart);
87 fInquiryPanel->PostMessage(message);
88 }
89
90 private:
91 InquiryPanel* fInquiryPanel;
92
93 };
94
95
InquiryPanel(BRect frame,LocalDevice * lDevice)96 InquiryPanel::InquiryPanel(BRect frame, LocalDevice* lDevice)
97 :
98 BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Bluetooth"), B_FLOATING_WINDOW,
99 B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS, B_ALL_WORKSPACES ),
100 fMessenger(this),
101 fScanning(false),
102 fRetrieving(false),
103 fLocalDevice(lDevice)
104
105 {
106 fScanProgress = new BStatusBar("status",
107 B_TRANSLATE("Scanning progress"), "");
108 activeColor = fScanProgress->BarColor();
109
110 if (fLocalDevice == NULL)
111 fLocalDevice = LocalDevice::GetLocalDevice();
112
113 fMessage = new BTextView("description", B_WILL_DRAW);
114 fMessage->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
115 fMessage->SetLowColor(fMessage->ViewColor());
116 fMessage->MakeEditable(false);
117 fMessage->MakeSelectable(false);
118
119 fInquiryButton = new BButton("Inquiry", B_TRANSLATE("Inquiry"),
120 new BMessage(kMsgInquiry), B_WILL_DRAW);
121
122 fAddButton = new BButton("add", B_TRANSLATE("Add device to list"),
123 new BMessage(kMsgAddToRemoteList), B_WILL_DRAW);
124 fAddButton->SetEnabled(false);
125
126 fRemoteList = new BListView("AttributeList", B_SINGLE_SELECTION_LIST);
127 fRemoteList->SetSelectionMessage(new BMessage(kMsgSelected));
128
129 fScrollView = new BScrollView("ScrollView", fRemoteList, 0, false, true);
130 fScrollView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
131
132 if (fLocalDevice != NULL) {
133 fMessage->SetText(B_TRANSLATE(
134 "Check that the Bluetooth capabilities of your"
135 " remote device are activated. Press 'Inquiry' to start scanning."
136 " The needed time for the retrieval of the names is unknown, "
137 "although should not take more than 3 seconds per device. "
138 "Afterwards you will be able to add them to your main list,"
139 " where you will be able to pair with them."));
140 fInquiryButton->SetEnabled(true);
141 fDiscoveryAgent = fLocalDevice->GetDiscoveryAgent();
142 fDiscoveryListener = new PanelDiscoveryListener(this);
143
144 SetTitle((const char*)(fLocalDevice->GetFriendlyName().String()));
145 } else {
146 fMessage->SetText(B_TRANSLATE("There isn't any Bluetooth LocalDevice "
147 "registered on the system."));
148 fInquiryButton->SetEnabled(false);
149 }
150
151 fRetrieveMessage = new BMessage(kMsgRetrieve);
152 fSecondsMessage = new BMessage(kMsgSecond);
153
154 BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
155 .SetInsets(B_USE_SMALL_SPACING)
156 .Add(fMessage, 0)
157 .Add(fScanProgress, 10)
158 .Add(fScrollView, 20)
159 .AddGroup(B_HORIZONTAL, 10)
160 .Add(fAddButton)
161 .AddGlue()
162 .Add(fInquiryButton)
163 .End()
164 .End();
165 }
166
167
168 void
MessageReceived(BMessage * message)169 InquiryPanel::MessageReceived(BMessage* message)
170 {
171 static float timer = 0; // expected time of the inquiry process
172 static float scanningTime = 0;
173 static int32 retrievalIndex = 0;
174 static bool labelPlaced = false;
175
176 switch (message->what) {
177 case kMsgInquiry:
178
179 fDiscoveryAgent->StartInquiry(BT_GIAC, fDiscoveryListener, GetInquiryTime());
180
181 timer = BT_BASE_INQUIRY_TIME * GetInquiryTime() + 1;
182 // does it works as expected?
183 fScanProgress->SetMaxValue(timer);
184
185 break;
186
187 case kMsgAddListDevice:
188 {
189 DeviceListItem* listItem;
190
191 message->FindPointer("remoteItem", (void **)&listItem);
192
193 fRemoteList->AddItem(listItem);
194 }
195 break;
196
197 case kMsgAddToRemoteList:
198 {
199 message->PrintToStream();
200 int32 index = fRemoteList->CurrentSelection(0);
201 DeviceListItem* item = (DeviceListItem*) fRemoteList->RemoveItem(index);;
202
203 BMessage message(kMsgAddToRemoteList);
204 message.AddPointer("device", item);
205
206 be_app->PostMessage(&message);
207 // TODO: all others listitems can be deleted
208 }
209 break;
210
211 case kMsgSelected:
212 UpdateListStatus();
213 break;
214
215 case kMsgStart:
216 fRemoteList->MakeEmpty();
217 fScanProgress->Reset();
218 fScanProgress->SetTo(1);
219 fScanProgress->SetTrailingText(B_TRANSLATE("Starting scan"
220 B_UTF8_ELLIPSIS));
221 fScanProgress->SetBarColor(activeColor);
222
223 fAddButton->SetEnabled(false);
224 fInquiryButton->SetEnabled(false);
225
226 BMessageRunner::StartSending(fMessenger, fSecondsMessage, 1000000, timer);
227
228 scanningTime = 1;
229 fScanning = true;
230
231 break;
232
233 case kMsgFinish:
234
235 retrievalIndex = 0;
236 fScanning = false;
237 fRetrieving = true;
238 labelPlaced = false;
239 fScanProgress->SetTo(100);
240 fScanProgress->SetTrailingText(B_TRANSLATE("Retrieving names"
241 B_UTF8_ELLIPSIS));
242 BMessageRunner::StartSending(fMessenger, fRetrieveMessage, 1000000, 1);
243
244 break;
245
246 case kMsgSecond:
247 if (fScanning && scanningTime < timer) {
248 // TODO time formatting could use Locale Kit
249
250 // TODO should not be needed if SetMaxValue works...
251 fScanProgress->SetTo(scanningTime * 100 / timer);
252 BString elapsedTime = B_TRANSLATE("Remaining %1 seconds");
253
254 BString seconds("");
255 seconds << (int)(timer - scanningTime);
256
257 elapsedTime.ReplaceFirst("%1", seconds.String());
258 fScanProgress->SetTrailingText(elapsedTime.String());
259
260 scanningTime = scanningTime + 1;
261 }
262 break;
263
264 case kMsgRetrieve:
265
266 if (fRetrieving) {
267
268 if (retrievalIndex < fDiscoveryAgent->RetrieveDevices(0).CountItems()) {
269
270 if (!labelPlaced) {
271
272 labelPlaced = true;
273 BString progressText(B_TRANSLATE("Retrieving name of %1"));
274
275 BString namestr;
276 namestr << bdaddrUtils::ToString(fDiscoveryAgent
277 ->RetrieveDevices(0).ItemAt(retrievalIndex)
278 ->GetBluetoothAddress());
279 progressText.ReplaceFirst("%1", namestr.String());
280 fScanProgress->SetTrailingText(progressText.String());
281
282 } else {
283 // Really erally expensive operation should be done in a separate thread
284 // once Haiku gets a BarberPole in API replacing the progress bar
285 ((DeviceListItem*)fRemoteList->ItemAt(retrievalIndex))
286 ->SetDevice((BluetoothDevice*) fDiscoveryAgent
287 ->RetrieveDevices(0).ItemAt(retrievalIndex));
288 fRemoteList->InvalidateItem(retrievalIndex);
289
290 retrievalIndex++;
291 labelPlaced = false;
292 }
293
294 BMessageRunner::StartSending(fMessenger, fRetrieveMessage, 500000, 1);
295
296 } else {
297
298 fRetrieving = false;
299 retrievalIndex = 0;
300
301 fScanProgress->SetBarColor(
302 ui_color(B_PANEL_BACKGROUND_COLOR));
303 fScanProgress->SetTrailingText(
304 B_TRANSLATE("Scanning completed."));
305 fInquiryButton->SetEnabled(true);
306 UpdateListStatus();
307 }
308 }
309
310 break;
311
312 default:
313 BWindow::MessageReceived(message);
314 break;
315 }
316 }
317
318
319 void
UpdateListStatus(void)320 InquiryPanel::UpdateListStatus(void)
321 {
322 if (fRemoteList->CurrentSelection() < 0 || fScanning || fRetrieving)
323 fAddButton->SetEnabled(false);
324 else
325 fAddButton->SetEnabled(true);
326 }
327
328
329 bool
QuitRequested(void)330 InquiryPanel::QuitRequested(void)
331 {
332
333 return true;
334 }
335