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
36using Bluetooth::DeviceListItem;
37
38// private funcionaility provided by kit
39extern uint8 GetInquiryTime();
40
41static const uint32 kMsgStart = 'InSt';
42static const uint32 kMsgFinish = 'InFn';
43static const uint32 kMsgShowDebug = 'ShDG';
44
45static const uint32 kMsgInquiry = 'iQbt';
46static const uint32 kMsgAddListDevice = 'aDdv';
47
48static const uint32 kMsgSelected = 'isLt';
49static const uint32 kMsgSecond = 'sCMs';
50static const uint32 kMsgRetrieve = 'IrEt';
51
52
53class PanelDiscoveryListener : public DiscoveryListener {
54
55public:
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
90private:
91	InquiryPanel*	fInquiryPanel;
92
93};
94
95
96InquiryPanel::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
168void
169InquiryPanel::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
319void
320InquiryPanel::UpdateListStatus(void)
321{
322	if (fRemoteList->CurrentSelection() < 0 || fScanning || fRetrieving)
323		fAddButton->SetEnabled(false);
324	else
325		fAddButton->SetEnabled(true);
326}
327
328
329bool
330InquiryPanel::QuitRequested(void)
331{
332
333	return true;
334}
335