xref: /haiku/src/preferences/bluetooth/InquiryPanel.cpp (revision 1deede7388b04dbeec5af85cae7164735ea9e70d)
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 
57 	PanelDiscoveryListener(InquiryPanel* iPanel)
58 		:
59 		DiscoveryListener(),
60 		fInquiryPanel(iPanel)
61 	{
62 
63 	}
64 
65 
66 	void
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
76 	InquiryCompleted(int discType)
77 	{
78 		BMessage* message = new BMessage(kMsgFinish);
79 		fInquiryPanel->PostMessage(message);
80 	}
81 
82 
83 	void
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 
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
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
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
330 InquiryPanel::QuitRequested(void)
331 {
332 
333 	return true;
334 }
335