Merge lp:~cyphermox/ubuntu-system-settings/bluetooth-redesign into lp:ubuntu-system-settings

Proposed by Mathieu Trudel-Lapierre
Status: Merged
Approved by: Ken VanDine
Approved revision: 833
Merged at revision: 852
Proposed branch: lp:~cyphermox/ubuntu-system-settings/bluetooth-redesign
Merge into: lp:ubuntu-system-settings
Diff against target: 2028 lines (+1319/-115)
17 files modified
CMakeLists.txt (+2/-0)
debian/changelog (+19/-0)
debian/control (+2/-0)
plugins/bluetooth/PageComponent.qml (+147/-30)
plugins/bluetooth/bluetooth.cpp (+54/-27)
plugins/bluetooth/bluetooth.h (+15/-5)
plugins/bluetooth/device.cpp (+180/-26)
plugins/bluetooth/device.h (+22/-5)
plugins/bluetooth/devicemodel.cpp (+133/-19)
plugins/bluetooth/devicemodel.h (+15/-3)
tests/plugins/CMakeLists.txt (+1/-0)
tests/plugins/bluetooth/CMakeLists.txt (+61/-0)
tests/plugins/bluetooth/fakebluez.cpp (+118/-0)
tests/plugins/bluetooth/fakebluez.h (+84/-0)
tests/plugins/bluetooth/tst_bluetooth.cpp (+172/-0)
tests/plugins/bluetooth/tst_device.cpp (+197/-0)
tests/plugins/bluetooth/tst_devicemodel.cpp (+97/-0)
To merge this branch: bzr merge lp:~cyphermox/ubuntu-system-settings/bluetooth-redesign
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Manuel de la Peña (community) Approve
Sebastien Bacher (community) Needs Fixing
Brendan Donegan Pending
Review via email: mp+228327@code.launchpad.net

This proposal supersedes a proposal from 2014-07-18.

Commit message

Bluetooth UI redesign

Description of the change

* Bluetooth UI redesign:
  - Redesign the UI to follow the specification.
  - Create devices only when attempting to connect.
  - Bugfixes for displaying icons for devices.
  - Show new icons for common device types.
  - Support more device types.
  - Show Trusted (known) devices in a separate list from the devices detected
    by Discovery.
  - Allow for a way to forget (unpair) a known device. (LP: #1336197)
  - Follow the state of the Bluetooth adapter for whether Bluetooth
    is enabled. (LP: #1272317)
  - Miscellaneous bug fixes.

To post a comment you must log in.
Revision history for this message
Charles Kerr (charlesk) wrote : Posted in a previous version of this proposal

Mostly looks very good, I like the way interfaces are handled now.

It would be good for someone better at QML to look over the UI, but I've added a handful of comments below for the C++ code.

Revision history for this message
Mathieu Trudel-Lapierre (cyphermox) wrote : Posted in a previous version of this proposal

Thanks for the review!

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:809
http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-ci/1025/
Executed test runs:
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/2207
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/1841
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-amd64-ci/217
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-armhf-ci/217
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-armhf-ci/217/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-i386-ci/217
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/2417
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/3381
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/3381/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/10095
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/1545
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/2060
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/2060/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-system-settings-ci/1025/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Brendan Donegan (brendan-donegan) wrote : Posted in a previous version of this proposal

We should really have some tests for this - it's a pretty big update to this feature.

review: Needs Fixing
Revision history for this message
Mathieu Trudel-Lapierre (cyphermox) wrote : Posted in a previous version of this proposal

Tests are quite difficult to do for this, given that the system needs remote devices.

I'm however already reviewing/updating the manual testing doc.

Revision history for this message
Brendan Donegan (brendan-donegan) wrote : Posted in a previous version of this proposal

I understand comprehensive testing might be difficult, but is there anything you could achieve by mocking, or otherwise?

Revision history for this message
Iain Lane (laney) wrote : Posted in a previous version of this proposal

AIUI this is all going through the bluez D-Bus API — you should be able to mock this (dbusmock has a bluez 4 template that might be a good starting point for a bluez 4 one).

Will try to review this week assuming nobody beats me to it. In the meantime, please wrap all lines at 80 chars. :)

Revision history for this message
Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

Thanks, some comments from testing on my laptop config and looking to the UI side

* those are displayed on start
 WARNING - qrc:/qml/EntryComponent.qml:39:9: StatusIcon is not a type

* it hangs for 10 seconds before displaying the u.i, could be due to those
" WARNING - Could not initiate service discovery: "Host is down""

(the service not being available should not block the loading)

* that seems another valid issue
"bluetooth/PageComponent.qml:50:17 depends on non-NOTIFYable properties: Bluetooth::agent"

* looking to https://wiki.ubuntu.com/Bluetooth#phone

- there should be no separator following the discoverable/not discoverable entry
- when a device is connected there is a vertical separator on the left of the arrow on the right side, that's not on the device (and it should be a standard item that doesn't do that?)
- the "none detected" label oscillate between disabled/enable color, is that wanted?

* in the details device-settings subscreen
- the items have their text horizontally centered on the design
- they don't have separators
- the connect automatically should be a checkbox, not a switch
- connect automatically seems always checked when the devices are not auto connected

* is it supposed to be able to pair/ask for a pin password to connect to e.g a phone device?

review: Needs Fixing
Revision history for this message
Mathieu Trudel-Lapierre (cyphermox) wrote : Posted in a previous version of this proposal

I have filed bugs for the issues I will not be addressing right away; which are either already present or currently complicated to address as they would require updated UI components (or discussion with design):

 bug 1346492 [bluetooth] on Device details subscreen: there should be no separators between data items
 bug 1346488 [bluetooth] on Device details subscreen: text should be centered
 bug 1346486 [bluetooth] there should be no vertical separator next to the progression on Connected devices
 bug 1346484 [bluetooth] there should be no separator following the discoverable/not discoverable entry
 bug 1346483 [bluetooth] bluetooth/PageComponent.qml:50:17 depends on non-NOTIFYable properties: Bluetooth::agent

Revision history for this message
Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

Thanks for opening those bugs.

If we land the work with known issue it would be nice to have somebody commiting to work on those, though it feels like some are trivial changes (like having no separator is a property to set to false on the listitems and that it would be easier to add the line that to deal with bug reports)

Some of the other issues still need to be addressed though (the 10 seconds delay at opening, the type warnings, the wrong widget, the fact that devices are marked as autoconnected by default when they don't do that)

Revision history for this message
Brendan Donegan (brendan-donegan) wrote : Posted in a previous version of this proposal

Mathieu, what is the plan for testing this then? Will you be able to have a look at the dbus-mock template Laney mentioned?

Revision history for this message
Mathieu Trudel-Lapierre (cyphermox) wrote : Posted in a previous version of this proposal

What is the property for the separators? I have looked and have been unable to find this in documentation.

As for the delay and some of the warnings; I have addressed those in the last few commits.

Do note that the warning about the agent property was already present and is cosmetic; it doesn't seem to affect the behavior of the program in any meaningful way; and the EntryComponent part does not appear to be in the bluetooth panel; at least it's not referenced anywhere in the bluetooth code, as such, it should be covered elsewhere than in this merge proposal.

Brendan; I've spent the past two days writing the mocking for bluez 4; I'll add tests next.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:815
http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-ci/1038/
Executed test runs:
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/2357
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/1932
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-amd64-ci/230
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-armhf-ci/230
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-armhf-ci/230/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-i386-ci/230
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/2537
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/3571
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/3571/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/10262
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/1620
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/2176
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/2176/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-system-settings-ci/1038/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

> What is the property for the separators? I have looked and have been unable to find this in documentation.

http://developer.ubuntu.com/api/devel/ubuntu-13.04/qml/ui-toolkit/qml-ubuntu-components-listitems0-empty.html#showDivider-prop

Revision history for this message
Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

There is still a several hangs on start (trying on a desktop config if that makes a difference), even it's closer from 3 seconds than the 10 seconds it was before

Revision history for this message
Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

the icons also look wrong, do you know if design is working on providing better ones?

Revision history for this message
Mathieu Trudel-Lapierre (cyphermox) wrote : Posted in a previous version of this proposal

Icons are provided in suru-icon-theme,I'd expect that's probably not the same on desktop.

Revision history for this message
Mathieu Trudel-Lapierre (cyphermox) wrote : Posted in a previous version of this proposal
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
820. By Mathieu Trudel-Lapierre

Hide divider below Discoverable/Not discoverable entry.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Manuel de la Peña (mandel) :
review: Needs Fixing
821. By Mathieu Trudel-Lapierre

Fix leak in slotServiceDiscoveryDone

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
822. By Mathieu Trudel-Lapierre

Change getStatusString as switch statement rather than a long if

823. By Mathieu Trudel-Lapierre

Merge from parent

824. By Mathieu Trudel-Lapierre

Chain constructors rather than calling an init function.

825. By Mathieu Trudel-Lapierre

Make path variable in removeDevice() have a shorter scope.

826. By Mathieu Trudel-Lapierre

Use a watcher to warn if setting the Trusted property on a device fails.

827. By Mathieu Trudel-Lapierre

Make bluezDevice QDBusInterface a QScopedPointer in slotDeviceCreated()

828. By Mathieu Trudel-Lapierre

Make it explicit that we're dealing with a QSharedPointer from getDeviceFromAddress().

829. By Mathieu Trudel-Lapierre

Cleanup CMakeLists for bluetooth plugin tests.

830. By Mathieu Trudel-Lapierre

Clean up m_bluezMock variable in FakeBluez in destructor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Manuel de la Peña (mandel) :
review: Needs Fixing
831. By Mathieu Trudel-Lapierre

Pass parent through the Bluetooth constructors

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
832. By Mathieu Trudel-Lapierre

Make discoverServices() get called from a timer in a separate thread rather than blocking UI while we wait for the device to settle

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Sebastien Bacher (seb128) wrote :

There is some merge conflicts there, needs a rebase on current trunk.

Otherwise looks fine to me to land, it's not perfect but we can fix small issues later. We need the libqtdbusmock changes to land first still though

review: Needs Fixing
833. By Mathieu Trudel-Lapierre

Merge from parent

Revision history for this message
Manuel de la Peña (mandel) :
review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:833
http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-ci/1118/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/2872
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/2282
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-amd64-ci/310
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-armhf-ci/310
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-armhf-ci/310/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-i386-ci/310
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/2942
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/4115
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/4115/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/10813
    UNSTABLE: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/1887
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/2546
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/2546/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-system-settings-ci/1118/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:833
http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-ci/1128/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-utopic-touch/2928
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-utopic/2317
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-amd64-ci/320
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-armhf-ci/320
        deb: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-armhf-ci/320/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-system-settings-utopic-i386-ci/320
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-deb-autopilot-runner-mako/2990
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/4171
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-armhf/4171/artifact/work/output/*zip*/output.zip
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/10875
    SUCCESS: http://jenkins.qa.ubuntu.com/job/autopilot-testrunner-otto-utopic/1917
    SUCCESS: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/2582
        deb: http://jenkins.qa.ubuntu.com/job/generic-mediumtests-builder-utopic-amd64/2582/artifact/work/output/*zip*/output.zip

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-system-settings-ci/1128/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-07-14 00:05:30 +0000
3+++ CMakeLists.txt 2014-07-31 14:30:31 +0000
4@@ -41,6 +41,8 @@
5 pkg_search_module(TIMEZONEMAP REQUIRED timezonemap)
6 pkg_search_module(ICU REQUIRED icu-i18n)
7 pkg_search_module(ANDR_PROP libandroid-properties)
8+pkg_check_modules(QTDBUSMOCK REQUIRED libqtdbusmock-1 REQUIRED)
9+pkg_check_modules(QTDBUSTEST REQUIRED libqtdbustest-1 REQUIRED)
10 pkg_search_module(POLKIT_AGENT polkit-agent-1)
11
12 find_program(XGETTEXT_BIN xgettext)
13
14=== modified file 'debian/changelog'
15--- debian/changelog 2014-07-30 19:48:03 +0000
16+++ debian/changelog 2014-07-31 14:30:31 +0000
17@@ -1,3 +1,22 @@
18+ubuntu-system-settings (0.3+14.10.20140730-0ubuntu2) UNRELEASED; urgency=medium
19+
20+ * Bluetooth UI redesign:
21+ - Redesign the UI to follow the specification.
22+ - Create devices only when attempting to connect.
23+ - Bugfixes for displaying icons for devices.
24+ - Show new icons for common device types.
25+ - Support more device types.
26+ - Show Trusted (known) devices in a separate list from the devices detected
27+ by Discovery.
28+ - Allow for a way to forget (unpair) a known device. (LP: #1336197)
29+ - Follow the state of the Bluetooth adapter for whether Bluetooth
30+ is enabled. (LP: #1272317)
31+ - Miscellaneous bug fixes.
32+ * debian/control: add Build-Depends on libqtdbusmock1-dev and
33+ libqtdbustest1-dev for Bluetooth testing.
34+
35+ -- Mathieu Trudel-Lapierre <mathieu-tl@ubuntu.com> Thu, 17 Jul 2014 14:46:42 -0400
36+
37 ubuntu-system-settings (0.3+14.10.20140730-0ubuntu1) utopic; urgency=low
38
39 [ CI bot ]
40
41=== modified file 'debian/control'
42--- debian/control 2014-07-30 19:47:49 +0000
43+++ debian/control 2014-07-31 14:30:31 +0000
44@@ -28,6 +28,8 @@
45 libubuntuoneauth-2.0-dev,
46 libubuntu-download-manager-client-dev,
47 libubuntu-download-manager-common-dev,
48+ libqtdbusmock1-dev (>= 0.2+14.04.20140724),
49+ libqtdbustest1-dev,
50 xvfb,
51 cmake,
52 pep8,
53
54=== modified file 'plugins/bluetooth/PageComponent.qml'
55--- plugins/bluetooth/PageComponent.qml 2014-06-20 21:56:15 +0000
56+++ plugins/bluetooth/PageComponent.qml 2014-07-31 14:30:31 +0000
57@@ -65,17 +65,23 @@
58 }
59 }
60
61- function getDisplayName(connection, displayName) {
62- if (connection == Device.Connecting)
63- // TRANSLATORS: %1 is the display name of the device that is connecting
64- return i18n.tr("%1 (Connecting…)").arg(displayName);
65- else if (connection == Device.Disconnecting)
66- // TRANSLATORS: %1 is the display name of the device that is disconnecting
67- return i18n.tr("%1 (Disconnecting…)").arg(displayName);
68- else
69+ function getDisplayName(type, displayName) {
70+ /* TODO: If the device requires a PIN, show ellipsis.
71+ * Right now we don't have a way to check this, just return the name
72+ */
73 return displayName;
74 }
75
76+ function getStatusString(connection) {
77+ switch (connection) {
78+ case Device.Connected: return i18n.tr("Connected");
79+ case Device.Connecting: return i18n.tr("Connecting…");
80+ case Device.Disconnecting: return i18n.tr("Disconnecting…");
81+ case Device.Disconnected: return i18n.tr("Disconnected");
82+ default: return i18n.tr("Unknown");
83+ }
84+ }
85+
86 function getTypeString(type) {
87 switch (type) {
88 case Device.Computer: return i18n.tr("Computer");
89@@ -132,6 +138,7 @@
90 id: btSwitch
91 // Cannot use onCheckedChanged as this triggers a loop
92 onClicked: bluetoothActionGroup.enabled.activate()
93+ checked: backend.powered
94 }
95 Component.onCompleted: clicked.connect(btSwitch.clicked)
96 }
97@@ -143,13 +150,16 @@
98 }
99
100 // Discoverability
101- ListItem.Subtitled {
102+ ListItem.Standard {
103 enabled: bluetoothActionGroup.enabled
104+ showDivider: false
105
106 Rectangle {
107 color: "transparent"
108 anchors.fill: parent
109 anchors.topMargin: units.gu(1)
110+ anchors.leftMargin: units.gu(2)
111+ anchors.rightMargin: units.gu(2)
112
113 Label {
114 anchors {
115@@ -180,8 +190,8 @@
116 right: parent.right
117 topMargin: units.gu(1)
118 }
119- visible: !backend.discoverable
120- running: !backend.discoverable
121+ visible: backend.powered && !backend.discoverable
122+ running: true
123 }
124 }
125 }
126@@ -204,12 +214,18 @@
127
128 model: backend.connectedDevices
129 delegate: ListItem.Standard {
130- iconName: iconName
131- text: getDisplayName(connection, displayName)
132+ iconSource: iconPath
133+ iconFrame: false
134+ text: getDisplayName(type, displayName)
135+ control: ActivityIndicator {
136+ visible: connection == Device.Connecting
137+ running: true
138+ }
139 onClicked: {
140 backend.setSelectedDevice(addressName);
141 pageStack.push(connectedDevicePage);
142 }
143+ progression: true
144 }
145 }
146
147@@ -217,10 +233,10 @@
148
149 ListItem.Standard {
150 id: disconnectedHeader
151- text: connectedList.visible ? i18n.tr("Connect a different device:") : i18n.tr("Connect another device:")
152+ text: connectedList.visible ? i18n.tr("Connect another device:") : i18n.tr("Connect a device:")
153 enabled: bluetoothActionGroup.enabled
154 control: ActivityIndicator {
155- visible: backend.discovering
156+ visible: backend.powered && backend.discovering
157 running: true
158 }
159 }
160@@ -234,19 +250,49 @@
161
162 model: backend.disconnectedDevices
163 delegate: ListItem.Standard {
164- iconName: iconName
165- text: getDisplayName(connection, displayName)
166+ iconSource: iconPath
167+ iconFrame: false
168+ text: getDisplayName(type, displayName)
169+ enabled: backend.isSupportedType(type)
170 onClicked: {
171- backend.stopDiscovery();
172- backend.connectDevice(addressName);
173+ backend.setSelectedDevice(addressName);
174+ pageStack.push(connectedDevicePage);
175 }
176+ progression: true
177 }
178 }
179 ListItem.Standard {
180 id: disconnectedNone
181 text: i18n.tr("None detected")
182 visible: !disconnectedList.visible
183- enabled: !backend.discovering
184+ enabled: false
185+ }
186+
187+ // Devices that connect automatically
188+ ListItem.Standard {
189+ id: autoconnectHeader
190+ text: i18n.tr("Connect automatically when detected:")
191+ visible: autoconnectList.visible
192+ enabled: bluetoothActionGroup.enabled
193+ }
194+ ListView {
195+ id: autoconnectList
196+ width: parent.width
197+ height: autoconnectHeader.height * count
198+
199+ visible: bluetoothActionGroup.enabled && (count > 0)
200+
201+ model: backend.autoconnectDevices
202+ delegate: ListItem.Standard {
203+ iconSource: iconPath
204+ iconFrame: false
205+ text: getDisplayName(type, displayName)
206+ onClicked: {
207+ backend.setSelectedDevice(addressName);
208+ pageStack.push(connectedDevicePage);
209+ }
210+ progression: true
211+ }
212 }
213 }
214 }
215@@ -263,22 +309,93 @@
216 text: i18n.tr("Name")
217 value: backend.selectedDevice ? backend.selectedDevice.name : i18n.tr("None")
218 }
219+ ListItem.Standard {
220+ Rectangle {
221+ color: "transparent"
222+ anchors.fill: parent
223+ anchors.topMargin: units.gu(1)
224+ anchors.leftMargin: units.gu(2)
225+ anchors.rightMargin: units.gu(2)
226+
227+ Label {
228+ anchors {
229+ top: parent.top
230+ left: parent.left
231+ topMargin: units.gu(1)
232+ }
233+ height: units.gu(3)
234+ text: i18n.tr("Type")
235+ }
236+ Image {
237+ anchors {
238+ right: deviceType.left
239+ rightMargin: units.gu(1)
240+ }
241+ height: units.gu(4)
242+ width: units.gu(4)
243+ source: backend.selectedDevice ? backend.selectedDevice.iconName : ""
244+ }
245+ Label {
246+ id: deviceType
247+ anchors {
248+ top: parent.top
249+ right: parent.right
250+ topMargin: units.gu(1)
251+ }
252+ height: units.gu(3)
253+ text: getTypeString(backend.selectedDevice ? backend.selectedDevice.type : Device.OTHER)
254+ }
255+ }
256+ }
257 ListItem.SingleValue {
258- text: i18n.tr("Type")
259- value: getTypeString(backend.selectedDevice ? backend.selectedDevice.type : Device.OTHER)
260+ text: i18n.tr("Status")
261+ value: getStatusString(backend.selectedDevice ? backend.selectedDevice.connection : Device.Disconnected)
262 }
263 ListItem.SingleValue {
264 text: i18n.tr("Signal Strength")
265 value: getSignalString(backend.selectedDevice ? backend.selectedDevice.strength : Device.None)
266 }
267- ListItem.SingleControl {
268- control: Button {
269- text: i18n.tr("Disconnect")
270- width: parent.width - units.gu(8)
271- onClicked: {
272- backend.disconnectDevice();
273- pageStack.pop();
274- }
275+ ListItem.Standard {
276+ id: trustedCheck
277+ text: i18n.tr("Connect automatically when detected:")
278+ control: CheckBox {
279+ onClicked: {
280+ if (backend.selectedDevice) {
281+ backend.selectedDevice.trusted = !backend.selectedDevice.trusted
282+ }
283+ }
284+ checked: backend.selectedDevice ? backend.selectedDevice.trusted : false
285+ }
286+ Component.onCompleted:
287+ clicked.connect(trustedCheck.clicked)
288+ }
289+ ListItem.SingleControl {
290+ control: Button {
291+ text: backend.selectedDevice && (backend.selectedDevice.connection == Device.Connected || backend.selectedDevice.connection == Device.Connecting) ? i18n.tr("Disconnect") : i18n.tr("Connect")
292+ width: parent.width - units.gu(8)
293+ onClicked: {
294+ if (backend.selectedDevice
295+ && (backend.selectedDevice.connection == Device.Connected
296+ || backend.selectedDevice.connection == Device.Connecting)) {
297+ backend.disconnectDevice();
298+ } else {
299+ backend.stopDiscovery()
300+ backend.connectDevice(backend.selectedDevice.address);
301+ }
302+ pageStack.pop();
303+ }
304+ visible: backend.selectedDevice ? true : false
305+ }
306+ }
307+ ListItem.SingleControl {
308+ control: Button {
309+ text: i18n.tr("Forget this device")
310+ width: parent.width - units.gu(8)
311+ onClicked: {
312+ backend.removeDevice();
313+ pageStack.pop();
314+ }
315+ enabled: backend.selectedDevice && backend.selectedDevice.path.length > 0 ? true : false
316 }
317 }
318 }
319
320=== modified file 'plugins/bluetooth/bluetooth.cpp'
321--- plugins/bluetooth/bluetooth.cpp 2014-06-20 21:56:15 +0000
322+++ plugins/bluetooth/bluetooth.cpp 2014-07-31 14:30:31 +0000
323@@ -26,8 +26,13 @@
324 #include "dbus-shared.h"
325
326 Bluetooth::Bluetooth(QObject *parent):
327+ Bluetooth(QDBusConnection::systemBus(), parent)
328+{
329+}
330+
331+Bluetooth::Bluetooth(const QDBusConnection &dbus, QObject *parent):
332 QObject(parent),
333- m_dbus(QDBusConnection::systemBus()),
334+ m_dbus(dbus),
335 m_devices(m_dbus),
336 m_agent(m_dbus, m_devices)
337 {
338@@ -36,28 +41,27 @@
339 if(!m_dbus.registerObject(DBUS_AGENT_PATH, &m_agent))
340 qFatal("Couldn't register agent at " DBUS_AGENT_PATH);
341
342- QVector<Device::Type> types;
343- types.append(Device::Type::Headset);
344- types.append(Device::Type::Headphones);
345- types.append(Device::Type::OtherAudio);
346- m_connectedDevices.filterOnType(types);
347 m_connectedDevices.filterOnConnections(Device::Connection::Connected |
348+ Device::Connection::Connecting |
349 Device::Connection::Disconnecting);
350 m_connectedDevices.setSourceModel(&m_devices);
351
352- m_disconnectedDevices.filterOnType(types);
353- m_disconnectedDevices.filterOnConnections(Device::Connection::Connecting |
354- Device::Connection::Disconnected);
355+ m_disconnectedDevices.filterOnConnections(Device::Connection::Disconnected);
356+ m_disconnectedDevices.filterOnTrusted(false);
357 m_disconnectedDevices.setSourceModel(&m_devices);
358
359+ m_autoconnectDevices.filterOnConnections(Device::Connection::Disconnected);
360+ m_autoconnectDevices.filterOnTrusted(true);
361+ m_autoconnectDevices.setSourceModel(&m_devices);
362+
363+ QObject::connect(&m_devices, SIGNAL(poweredChanged(bool)),
364+ this, SIGNAL(poweredChanged(bool)));
365+
366 QObject::connect(&m_devices, SIGNAL(discoveringChanged(bool)),
367 this, SIGNAL(discoveringChanged(bool)));
368
369 QObject::connect(&m_devices, SIGNAL(discoverableChanged(bool)),
370 this, SIGNAL(discoverableChanged(bool)));
371-
372- QObject::connect(&m_agent, SIGNAL(onPairingDone()),
373- this, SLOT(onPairingDone()));
374 }
375
376 void Bluetooth::setSelectedDevice(const QString &address)
377@@ -83,6 +87,20 @@
378 m_devices.stopDiscovery();
379 }
380
381+bool Bluetooth::isSupportedType(const int type)
382+{
383+ switch((Device::Type)type) {
384+
385+ case Device::Type::Headset:
386+ case Device::Type::Headphones:
387+ case Device::Type::OtherAudio:
388+ return true;
389+
390+ default:
391+ return false;
392+ }
393+}
394+
395 /***
396 ****
397 ***/
398@@ -119,6 +137,13 @@
399 return ret;
400 }
401
402+QAbstractItemModel * Bluetooth::getAutoconnectDevices()
403+{
404+ auto ret = &m_autoconnectDevices;
405+ QQmlEngine::setObjectOwnership(ret, QQmlEngine::CppOwnership);
406+ return ret;
407+}
408+
409
410 /***
411 ****
412@@ -128,7 +153,7 @@
413 {
414 Device::Type type;
415
416- if (m_selectedDevice)
417+ if (m_selectedDevice) {
418 type = m_selectedDevice->getType();
419 if (type == Device::Type::Headset)
420 m_selectedDevice->disconnect(Device::ConnectionMode::Audio);
421@@ -136,6 +161,9 @@
422 m_selectedDevice->disconnect(Device::ConnectionMode::Audio);
423 else if (type == Device::Type::OtherAudio)
424 m_selectedDevice->disconnect(Device::ConnectionMode::Audio);
425+ } else {
426+ qWarning() << "No selected device to disconnect";
427+ }
428 }
429
430 void Bluetooth::connectDevice(const QString &address)
431@@ -144,8 +172,10 @@
432 auto device = m_devices.getDeviceFromAddress(address);
433 Device::Type type;
434
435- if (!device)
436+ if (!device) {
437+ qWarning() << "No device to connect.";
438 return;
439+ }
440
441 type = device->getType();
442 if (type == Device::Type::Headset)
443@@ -155,24 +185,21 @@
444 else if (type == Device::Type::OtherAudio)
445 connMode = Device::ConnectionMode::Audio;
446
447- if (device->isPaired()) {
448+ if (device->isTrusted()) {
449 device->connect(connMode);
450 } else {
451- m_connectAfterPairing[address] = connMode;
452- m_devices.pairDevice(address);
453+ m_devices.addConnectAfterPairing(address, connMode);
454+ m_devices.createDevice(address);
455 }
456 }
457
458-void Bluetooth::onPairingDone()
459+void Bluetooth::removeDevice()
460 {
461- QMapIterator<QString,Device::ConnectionMode> it(m_connectAfterPairing);
462- while (it.hasNext()) {
463- it.next();
464- const QString &address = it.key();
465- auto device = m_devices.getDeviceFromAddress(address);
466- if (device)
467- device->connect(it.value());
468+ if (m_selectedDevice) {
469+ QString path = m_selectedDevice->getPath();
470+ m_devices.removeDevice(path);
471+ } else {
472+ qWarning() << "No selected device to remove.";
473 }
474+}
475
476- m_connectAfterPairing.clear();
477-}
478
479=== modified file 'plugins/bluetooth/bluetooth.h'
480--- plugins/bluetooth/bluetooth.h 2014-06-20 21:56:15 +0000
481+++ plugins/bluetooth/bluetooth.h 2014-07-31 14:30:31 +0000
482@@ -39,6 +39,10 @@
483 READ getDisconnectedDevices
484 CONSTANT)
485
486+ Q_PROPERTY (QAbstractItemModel* autoconnectDevices
487+ READ getAutoconnectDevices
488+ CONSTANT)
489+
490 Q_PROPERTY (QObject * selectedDevice
491 READ getSelectedDevice
492 NOTIFY selectedDeviceChanged);
493@@ -46,6 +50,10 @@
494 Q_PROPERTY (QObject * agent
495 READ getAgent);
496
497+ Q_PROPERTY (bool powered
498+ READ isPowered
499+ NOTIFY poweredChanged);
500+
501 Q_PROPERTY (bool discovering
502 READ isDiscovering
503 NOTIFY discoveringChanged);
504@@ -56,30 +64,33 @@
505
506 Q_SIGNALS:
507 void selectedDeviceChanged();
508+ void poweredChanged(bool powered);
509 void discoveringChanged(bool isActive);
510 void discoverableChanged(bool isActive);
511
512-private Q_SLOTS:
513- void onPairingDone();
514-
515 public:
516 Bluetooth(QObject *parent = 0);
517+ Bluetooth(const QDBusConnection &dbus, QObject *parent = 0);
518 ~Bluetooth() {}
519
520 Q_INVOKABLE QString adapterName() const { return m_devices.adapterName(); }
521 Q_INVOKABLE void setSelectedDevice(const QString &address);
522 Q_INVOKABLE void connectDevice(const QString &address);
523 Q_INVOKABLE void disconnectDevice();
524+ Q_INVOKABLE void removeDevice();
525 Q_INVOKABLE void toggleDiscovery();
526 Q_INVOKABLE void startDiscovery();
527 Q_INVOKABLE void stopDiscovery();
528+ Q_INVOKABLE static bool isSupportedType(const int type);
529
530 public:
531 Agent * getAgent();
532 Device * getSelectedDevice();
533 QAbstractItemModel * getConnectedDevices();
534 QAbstractItemModel * getDisconnectedDevices();
535+ QAbstractItemModel * getAutoconnectDevices();
536
537+ bool isPowered() const { return m_devices.isPowered(); }
538 bool isDiscovering() const { return m_devices.isDiscovering(); }
539 bool isDiscoverable() const { return m_devices.isDiscoverable(); }
540
541@@ -88,11 +99,10 @@
542 DeviceModel m_devices;
543 DeviceFilter m_connectedDevices;
544 DeviceFilter m_disconnectedDevices;
545+ DeviceFilter m_autoconnectDevices;
546 QSharedPointer<Device> m_selectedDevice;
547
548 Agent m_agent;
549-
550- QMap<QString,Device::ConnectionMode> m_connectAfterPairing;
551 };
552
553 #endif // BLUETOOTH_H
554
555=== modified file 'plugins/bluetooth/device.cpp'
556--- plugins/bluetooth/device.cpp 2014-06-20 20:59:31 +0000
557+++ plugins/bluetooth/device.cpp 2014-07-31 14:30:31 +0000
558@@ -19,6 +19,8 @@
559
560 #include <QDBusReply>
561 #include <QDebug> // qWarning()
562+#include <QThread>
563+#include <QTimer>
564
565 #include "dbus-shared.h"
566 #include "device.h"
567@@ -27,8 +29,18 @@
568 ****
569 ***/
570
571+Device::Device(const QMap<QString,QVariant> &properties)
572+{
573+ setProperties(properties);
574+}
575+
576 Device::Device(const QString &path, QDBusConnection &bus)
577 {
578+ initDevice(path, bus);
579+}
580+
581+void Device::initDevice(const QString &path, QDBusConnection &bus)
582+{
583 /* whenever any of the properties changes,
584 trigger the catch-all deviceChanged() signal */
585 QObject::connect(this, SIGNAL(nameChanged()), this, SIGNAL(deviceChanged()));
586@@ -46,6 +58,8 @@
587 initInterface(m_audioSourceInterface, path, "org.bluez.AudioSource", bus);
588 initInterface(m_audioSinkInterface, path, "org.bluez.AudioSink", bus);
589 initInterface(m_headsetInterface, path, "org.bluez.Headset", bus);
590+
591+ Q_EMIT(pathChanged());
592 }
593
594 /***
595@@ -94,38 +108,142 @@
596 }
597 }
598
599+void Device::connectPending()
600+{
601+ if (m_paired && !m_trusted) {
602+ /* Give the device a bit of time to settle.
603+ * Once service discovery is done, it will call connect() on the
604+ * pending interfaces.
605+ */
606+ QTimer::singleShot(1, this, SLOT(discoverServices()));
607+ }
608+}
609+
610 /***
611 ****
612 ***/
613
614+void Device::addConnectAfterPairing(ConnectionMode mode)
615+{
616+ m_connectAfterPairing.append(mode);
617+}
618+
619+void Device::slotServiceDiscoveryDone(QDBusPendingCallWatcher *call)
620+{
621+ QDBusPendingReply<void> reply = *call;
622+
623+ if (!reply.isError()) {
624+ while (!m_connectAfterPairing.isEmpty()) {
625+ ConnectionMode mode = m_connectAfterPairing.takeFirst();
626+ connect(mode);
627+ }
628+ } else {
629+ qWarning() << "Could not initiate service discovery:"
630+ << reply.error().message();
631+ }
632+ call->deleteLater();
633+}
634+
635+void Device::discoverServices()
636+{
637+ if (m_deviceInterface) {
638+ QDBusPendingCall pcall
639+ = m_deviceInterface->asyncCall("DiscoverServices",
640+ QLatin1String(""));
641+
642+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall,
643+ this);
644+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
645+ this,
646+ SLOT(slotServiceDiscoveryDone(QDBusPendingCallWatcher*)));
647+ } else {
648+ qWarning() << "Can't do service discovery: the device interface is not ready.";
649+ }
650+}
651+
652+void Device::callInterface(const QSharedPointer<QDBusInterface> &interface, const QString &method)
653+{
654+ QDBusReply<void> reply;
655+ constexpr int maxTries = 4;
656+ int retryCount = 0;
657+
658+ while (retryCount < maxTries) {
659+ reply = interface->call(method);
660+ if (reply.isValid())
661+ break;
662+ QThread::msleep(500);
663+ retryCount++;
664+ }
665+
666+ if (retryCount >= maxTries && !reply.isValid()) {
667+ qWarning() << "Could not" << method << "the interface" << interface->interface()
668+ << ":" << reply.error().message();
669+ }
670+}
671+
672 void Device::disconnect(ConnectionMode mode)
673 {
674+ QSharedPointer<QDBusInterface> interface;
675+
676 if (m_headsetInterface && (mode == HeadsetMode))
677- m_headsetInterface->asyncCall("Disconnect");
678+ interface = m_headsetInterface;
679 else if (m_audioInterface && (mode == Audio))
680- m_audioInterface->asyncCall("Disconnect");
681- else
682+ interface = m_audioInterface;
683+ else {
684 qWarning() << "Unhandled connection mode" << mode;
685+ return;
686+ }
687+
688+ callInterface(interface, "Disconnect");
689 }
690
691 void Device::connect(ConnectionMode mode)
692 {
693+ QSharedPointer<QDBusInterface> interface;
694+
695 if (m_headsetInterface && (mode == HeadsetMode))
696- m_headsetInterface->asyncCall("Connect");
697+ interface = m_headsetInterface;
698 else if (m_audioInterface && (mode == Audio))
699- m_audioInterface->asyncCall("Connect");
700- else
701+ interface = m_audioInterface;
702+ else {
703 qWarning() << "Unhandled connection mode" << mode;
704-}
705-
706-void Device::makeTrusted()
707-{
708- QVariant value;
709- QDBusVariant trusted(true);
710-
711- value.setValue(trusted);
712-
713- m_deviceInterface->asyncCall("SetProperty", "Trusted", value);
714+ return;
715+ }
716+
717+ callInterface(interface, "Connect");
718+}
719+
720+void Device::slotMakeTrustedDone(QDBusPendingCallWatcher *call)
721+{
722+ QDBusPendingReply<void> reply = *call;
723+
724+ if (reply.isError()) {
725+ qWarning() << "Could not set device as trusted:"
726+ << reply.error().message();
727+ }
728+ call->deleteLater();
729+}
730+
731+void Device::makeTrusted(bool trusted)
732+{
733+ QVariant value;
734+ QDBusVariant variant(trusted);
735+
736+ value.setValue(variant);
737+
738+ if (m_deviceInterface) {
739+ QDBusPendingCall pcall
740+ = m_deviceInterface->asyncCall("SetProperty", "Trusted", value);
741+
742+ QDBusPendingCallWatcher *watcher
743+ = new QDBusPendingCallWatcher(pcall, this);
744+ QObject::connect(watcher,
745+ SIGNAL(finished(QDBusPendingCallWatcher*)),
746+ this,
747+ SLOT(slotServiceDiscoveryDone(QDBusPendingCallWatcher*)));
748+ } else {
749+ qWarning() << "Can't set device trusted before it is added in BlueZ";
750+ }
751 }
752
753 /***
754@@ -198,12 +316,41 @@
755
756 const auto type = getType();
757
758- if (type == Type::Headset || type == Type::Headphones || type == Type::OtherAudio)
759- setIconName("image://theme/audio-headset");
760- else if (type == Type::Phone)
761- setIconName("image://theme/phone");
762- else if (!m_fallbackIconName.isEmpty())
763+ switch (type) {
764+ case Type::Headset:
765+ setIconName("image://theme/audio-headset-symbolic");
766+ break;
767+ case Type::Headphones:
768+ setIconName("image://theme/audio-headphones-symbolic");
769+ break;
770+ case Type::Carkit:
771+ setIconName("image://theme/audio-carkit-symbolic");
772+ break;
773+ case Type::Speakers:
774+ case Type::OtherAudio:
775+ setIconName("image://theme/audio-speakers-symbolic");
776+ break;
777+ case Type::Mouse:
778+ setIconName("image://theme/input-mouse-symbolic");
779+ break;
780+ case Type::Keyboard:
781+ setIconName("image://theme/input-keyboard-symbolic");
782+ break;
783+ case Type::Cellular:
784+ setIconName("image://theme/phone-cellular-symbolic");
785+ break;
786+ case Type::Smartphone:
787+ setIconName("image://theme/phone-smartphone-symbolic");
788+ break;
789+ case Type::Phone:
790+ setIconName("image://theme/phone-uncategorized-symbolic");
791+ break;
792+ case Type::Computer:
793+ setIconName("image://theme/computer-symbolic");
794+ break;
795+ default:
796 setIconName(QString("image://theme/%1").arg(m_fallbackIconName));
797+ }
798 }
799
800 void Device::updateConnection()
801@@ -224,7 +371,7 @@
802 c = m_isConnected ? Connection::Connected : Connection::Disconnected;
803
804 if (m_isConnected && m_paired && !m_trusted)
805- makeTrusted();
806+ makeTrusted(true);
807
808 setConnection(c);
809 }
810@@ -245,6 +392,7 @@
811 setType(getTypeFromClass(value.toUInt()));
812 } else if (key == "Paired") { // org.bluez.Device
813 setPaired(value.toBool());
814+ connectPending();
815 updateConnection();
816 } else if (key == "Trusted") { // org.bluez.Device
817 setTrusted(value.toBool());
818@@ -269,13 +417,13 @@
819 case 0x02:
820 switch ((c & 0xfc) >> 2) {
821 case 0x01:
822- case 0x02:
823+ return Type::Cellular;
824 case 0x03:
825- case 0x05:
826- return Type::Phone;
827-
828+ return Type::Smartphone;
829 case 0x04:
830 return Type::Modem;
831+ default:
832+ return Type::Phone;
833 }
834 break;
835
836@@ -288,9 +436,15 @@
837 case 0x02:
838 return Type::Headset;
839
840+ case 0x05:
841+ return Type::Speakers;
842+
843 case 0x06:
844 return Type::Headphones;
845
846+ case 0x08:
847+ return Type::Carkit;
848+
849 case 0x0b: // vcr
850 case 0x0c: // video camera
851 case 0x0d: // camcorder
852
853=== modified file 'plugins/bluetooth/device.h'
854--- plugins/bluetooth/device.h 2014-06-19 15:10:43 +0000
855+++ plugins/bluetooth/device.h 2014-07-31 14:30:31 +0000
856@@ -29,6 +29,10 @@
857 {
858 Q_OBJECT
859
860+ Q_PROPERTY(QString path
861+ READ getPath
862+ NOTIFY pathChanged)
863+
864 Q_PROPERTY(QString name
865 READ getName
866 NOTIFY nameChanged)
867@@ -51,6 +55,7 @@
868
869 Q_PROPERTY(bool trusted
870 READ isTrusted
871+ WRITE makeTrusted
872 NOTIFY trustedChanged)
873
874 Q_PROPERTY(Connection connection
875@@ -63,9 +68,9 @@
876
877 public:
878
879- enum Type { Other, Computer, Phone, Modem, Network, Headset,
880- Headphones, Video, OtherAudio, Joypad, Keypad,
881- Keyboard, Tablet, Mouse, Printer, Camera };
882+ enum Type { Other, Computer, Cellular, Smartphone, Phone, Modem, Network,
883+ Headset, Speakers, Headphones, Video, OtherAudio, Joypad,
884+ Keypad, Keyboard, Tablet, Mouse, Printer, Camera, Carkit };
885
886 enum Strength { None, Poor, Fair, Good, Excellent };
887
888@@ -80,6 +85,7 @@
889 Q_DECLARE_FLAGS(Connections, Connection)
890
891 Q_SIGNALS:
892+ void pathChanged();
893 void nameChanged();
894 void iconNameChanged();
895 void addressChanged();
896@@ -99,7 +105,7 @@
897 bool isTrusted() const { return m_trusted; }
898 Connection getConnection() const { return m_connection; }
899 Strength getStrength() const { return m_strength; }
900- QString getPath() const { return m_deviceInterface->path(); }
901+ QString getPath() const { return m_deviceInterface ? m_deviceInterface->path() : QString(); }
902
903 private:
904 QString m_name;
905@@ -118,6 +124,7 @@
906 QSharedPointer<QDBusInterface> m_audioSourceInterface;
907 QSharedPointer<QDBusInterface> m_audioSinkInterface;
908 QSharedPointer<QDBusInterface> m_headsetInterface;
909+ QList<ConnectionMode> m_connectAfterPairing;
910
911 protected:
912 void setName(const QString &name);
913@@ -135,14 +142,24 @@
914 Device() {}
915 ~Device() {}
916 Device(const QString &path, QDBusConnection &bus);
917+ Device(const QMap<QString,QVariant> &properties);
918+ void initDevice(const QString &path, QDBusConnection &bus);
919 bool isValid() const { return getType() != Type::Other; }
920+ void callInterface(const QSharedPointer<QDBusInterface> &interface, const QString &method);
921 void connect(ConnectionMode);
922- void makeTrusted();
923+ void connectPending();
924+ void makeTrusted(bool trusted);
925 void disconnect(ConnectionMode);
926 void setProperties(const QMap<QString,QVariant> &properties);
927+ void addConnectAfterPairing(const ConnectionMode mode);
928+
929+ public Q_SLOTS:
930+ void discoverServices();
931
932 private Q_SLOTS:
933 void slotPropertyChanged(const QString &key, const QDBusVariant &value);
934+ void slotServiceDiscoveryDone(QDBusPendingCallWatcher *call);
935+ void slotMakeTrustedDone(QDBusPendingCallWatcher *call);
936
937 private:
938 void updateProperties(QSharedPointer<QDBusInterface>);
939
940=== modified file 'plugins/bluetooth/devicemodel.cpp'
941--- plugins/bluetooth/devicemodel.cpp 2014-06-20 21:56:15 +0000
942+++ plugins/bluetooth/devicemodel.cpp 2014-07-31 14:30:31 +0000
943@@ -100,7 +100,7 @@
944
945 void DeviceModel::startDiscovery()
946 {
947- if (m_bluezAdapter && !m_isDiscovering) {
948+ if (m_bluezAdapter && m_isPowered && !m_isDiscovering) {
949 m_bluezAdapter->asyncCall("StartDiscovery");
950 m_isDiscovering = true;
951 Q_EMIT(discoveringChanged(m_isDiscovering));
952@@ -236,6 +236,8 @@
953 m_isPairable = value.toBool();
954 } else if (key == "Discoverable") {
955 setDiscoverable(value.toBool());
956+ } else if (key == "Powered") {
957+ setPowered(value.toBool());
958 }
959 }
960
961@@ -247,6 +249,14 @@
962 }
963 }
964
965+void DeviceModel::setPowered(bool powered)
966+{
967+ if (m_isPowered != powered) {
968+ m_isPowered = powered;
969+ Q_EMIT(poweredChanged(m_isPowered));
970+ }
971+}
972+
973 void DeviceModel::slotEnableDiscoverable()
974 {
975 trySetDiscoverable(true);
976@@ -325,7 +335,36 @@
977
978 void DeviceModel::slotDeviceCreated(const QDBusObjectPath &path)
979 {
980- addDevice(path.path());
981+ const QString service = "org.bluez";
982+ const QString interface = "org.bluez.Device";
983+ QScopedPointer<QDBusInterface> bluezDevice(
984+ new QDBusInterface(service, path.path(), interface, m_dbus));
985+
986+ // A device was created. Now, we likely already have it, so let's find it
987+ // again and finish the initialization.
988+ QDBusReply<QMap<QString,QVariant> > properties
989+ = bluezDevice->call("GetProperties");
990+ if (properties.isValid()) {
991+ QMapIterator<QString,QVariant> it(properties);
992+ while (it.hasNext()) {
993+ it.next();
994+ if (it.key() == "Address") {
995+ QSharedPointer<Device> device = getDeviceFromAddress(it.value().toString());
996+
997+ if (device) {
998+ device->initDevice(path.path(), m_dbus);
999+ QObject::connect(device.data(), SIGNAL(deviceChanged()),
1000+ this, SLOT(slotDeviceChanged()));
1001+ addDevice(device);
1002+ } else {
1003+ addDevice(path.path());
1004+ }
1005+ break;
1006+ }
1007+ }
1008+ } else {
1009+ qWarning() << "Invalid device properties for" << path.path();
1010+ }
1011 }
1012
1013 void DeviceModel::slotDeviceFound(const QString &address,
1014@@ -333,18 +372,26 @@
1015 {
1016 Q_UNUSED(properties);
1017
1018- auto device = getDeviceFromAddress(address);
1019- if (!device) // hey, we haven't seen this one before
1020- m_bluezAdapter->asyncCall(QLatin1String("CreateDevice"), address);
1021+ QSharedPointer<Device> device = getDeviceFromAddress(address);
1022+
1023+ if (!device) {
1024+ QSharedPointer<Device> device(new Device(properties));
1025+ if (device->isValid()) {
1026+ addDevice(device);
1027+ }
1028+ }
1029 }
1030
1031 void DeviceModel::slotDeviceRemoved(const QDBusObjectPath &path)
1032 {
1033- Q_UNUSED(path);
1034-
1035- /* This is a no-op because we want to list both paired & unpaired devices.
1036- So, keep it in m_devices until a call to slotDeviceDisappeared()
1037- indicates the device has disappeared altogether */
1038+ /* Remove the device immediately, it will be listed again
1039+ once discovery results are returned. */
1040+
1041+ auto device = getDeviceFromPath(path.path());
1042+
1043+ const int row = findRowFromAddress(device->getAddress());
1044+ if ((row >= 0))
1045+ removeRow(row);
1046 }
1047
1048 void DeviceModel::slotDeviceDisappeared(const QString &address)
1049@@ -389,13 +436,63 @@
1050 return QSharedPointer<Device>();
1051 }
1052
1053-void DeviceModel::pairDevice (const QString &address)
1054-{
1055- if (m_bluezAdapter) {
1056- m_bluezAdapter->asyncCall("CreatePairedDevice",
1057- address,
1058- qVariantFromValue(QDBusObjectPath(DBUS_AGENT_PATH)),
1059- QString(DBUS_AGENT_CAPABILITY));
1060+void DeviceModel::addConnectAfterPairing(const QString &address, Device::ConnectionMode mode)
1061+{
1062+ QSharedPointer<Device> device = getDeviceFromAddress(address);
1063+ if (device) {
1064+ device->addConnectAfterPairing(mode);
1065+ } else {
1066+ qWarning() << "Device could not be found, can't add an operation";
1067+ }
1068+}
1069+
1070+void DeviceModel::slotCreateFinished(QDBusPendingCallWatcher *call)
1071+{
1072+ QDBusPendingReply<QDBusObjectPath> reply = *call;
1073+
1074+ if (reply.isError()) {
1075+ qWarning() << "Could not create device:" << reply.error().message();
1076+ }
1077+
1078+ call->deleteLater();
1079+}
1080+
1081+void DeviceModel::createDevice (const QString &address)
1082+{
1083+ if (m_bluezAdapter) {
1084+ QDBusPendingCall pcall = m_bluezAdapter->asyncCall("CreatePairedDevice",
1085+ address,
1086+ qVariantFromValue(QDBusObjectPath(DBUS_AGENT_PATH)),
1087+ QString(DBUS_AGENT_CAPABILITY));
1088+
1089+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
1090+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
1091+ this, SLOT(slotCreateFinished(QDBusPendingCallWatcher*)));
1092+ } else {
1093+ qWarning() << "Default adapter is not available for device creation";
1094+ }
1095+}
1096+
1097+void DeviceModel::slotRemoveFinished(QDBusPendingCallWatcher *call)
1098+{
1099+ QDBusPendingReply<void> reply = *call;
1100+
1101+ if (reply.isError()) {
1102+ qWarning() << "Could not remove device:" << reply.error().message();
1103+ }
1104+ call->deleteLater();
1105+}
1106+
1107+void DeviceModel::removeDevice (const QString &path)
1108+{
1109+ if (m_bluezAdapter) {
1110+ QDBusPendingCall pcall = m_bluezAdapter->asyncCall("RemoveDevice", qVariantFromValue(QDBusObjectPath(path)));
1111+
1112+ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
1113+ QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
1114+ this, SLOT(slotRemoveFinished(QDBusPendingCallWatcher*)));
1115+ } else {
1116+ qWarning() << "Default adapter is not available for device removal";
1117 }
1118 }
1119
1120@@ -417,11 +514,12 @@
1121
1122 if (Q_UNLIKELY(names.empty())) {
1123 names[Qt::DisplayRole] = "displayName";
1124- names[Qt::DecorationRole] = "iconName";
1125+ names[IconRole] = "iconPath";
1126 names[TypeRole] = "type";
1127 names[StrengthRole] = "strength";
1128 names[ConnectionRole] = "connection";
1129 names[AddressRole] = "addressName";
1130+ names[TrustedRole] = "trusted";
1131 }
1132
1133 return names;
1134@@ -440,7 +538,7 @@
1135 ret = device->isPaired() ? device->getName() : device->getName() + "…";
1136 break;
1137
1138- case Qt::DecorationRole:
1139+ case IconRole:
1140 ret = device->getIconName();
1141 break;
1142
1143@@ -459,6 +557,10 @@
1144 case AddressRole:
1145 ret = device->getAddress();
1146 break;
1147+
1148+ case TrustedRole:
1149+ ret = device->isTrusted();
1150+ break;
1151 }
1152 }
1153
1154@@ -486,6 +588,13 @@
1155 invalidateFilter();
1156 }
1157
1158+void DeviceFilter::filterOnTrusted(bool trusted)
1159+{
1160+ m_trustedEnabled = true;
1161+ m_trustedFilter = trusted;
1162+ invalidateFilter();
1163+}
1164+
1165 bool DeviceFilter::filterAcceptsRow(int sourceRow,
1166 const QModelIndex &sourceParent) const
1167 {
1168@@ -502,6 +611,11 @@
1169 accepts = (m_connections & connection) != 0;
1170 }
1171
1172+ if (accepts && m_trustedEnabled) {
1173+ const bool trusted = childIndex.model()->data(childIndex, DeviceModel::TrustedRole).value<bool>();
1174+ accepts = trusted == m_trustedFilter;
1175+ }
1176+
1177 return accepts;
1178 }
1179
1180
1181=== modified file 'plugins/bluetooth/devicemodel.h'
1182--- plugins/bluetooth/devicemodel.h 2014-06-20 21:56:15 +0000
1183+++ plugins/bluetooth/devicemodel.h 2014-07-31 14:30:31 +0000
1184@@ -46,12 +46,13 @@
1185 enum Roles
1186 {
1187 // Qt::DisplayRole holds device name
1188- // Qt::DecorationRole has icon
1189 TypeRole = Qt::UserRole,
1190+ IconRole,
1191 StrengthRole,
1192 ConnectionRole,
1193 AddressRole,
1194- LastRole = AddressRole
1195+ TrustedRole,
1196+ LastRole = TrustedRole
1197 };
1198
1199 // implemented virtual methods from QAbstractTableModel
1200@@ -64,14 +65,18 @@
1201 QString adapterName() const { return m_adapterName; }
1202
1203 public:
1204+ bool isPowered() const { return m_isPowered; }
1205 bool isDiscovering() const { return m_isDiscovering; }
1206 bool isDiscoverable() const { return m_isDiscoverable; }
1207- void pairDevice(const QString &address);
1208+ void addConnectAfterPairing(const QString &address, Device::ConnectionMode mode);
1209+ void createDevice(const QString &address);
1210+ void removeDevice(const QString &path);
1211 void stopDiscovery();
1212 void startDiscovery();
1213 void toggleDiscovery();
1214
1215 Q_SIGNALS:
1216+ void poweredChanged(bool powered);
1217 void discoveringChanged(bool isDiscovering);
1218 void discoverableChanged(bool isDiscoverable);
1219
1220@@ -84,6 +89,7 @@
1221
1222 QString m_adapterName;
1223 QString m_adapterAddress;
1224+ bool m_isPowered = false;
1225 bool m_isPairable = false;
1226 bool m_isDiscovering = false;
1227 bool m_isDiscoverable = false;
1228@@ -92,6 +98,7 @@
1229 void restartTimer();
1230 void trySetDiscoverable(bool discoverable);
1231 void setDiscoverable(bool discoverable);
1232+ void setPowered(bool powered);
1233
1234 QScopedPointer<QDBusInterface> m_bluezAdapter;
1235 void clearAdapter();
1236@@ -106,6 +113,8 @@
1237 void emitRowChanged(int row);
1238
1239 private Q_SLOTS:
1240+ void slotCreateFinished(QDBusPendingCallWatcher *call);
1241+ void slotRemoveFinished(QDBusPendingCallWatcher *call);
1242 void slotPropertyChanged(const QString &key, const QDBusVariant &value);
1243 void slotTimeout();
1244 void slotEnableDiscoverable();
1245@@ -127,6 +136,7 @@
1246 virtual ~DeviceFilter() {}
1247 void filterOnType(const QVector<Device::Type>);
1248 void filterOnConnections(Device::Connections);
1249+ void filterOnTrusted(bool trusted);
1250
1251 protected:
1252 virtual bool filterAcceptsRow(int, const QModelIndex&) const;
1253@@ -137,6 +147,8 @@
1254 bool m_typeEnabled = false;
1255 Device::Connections m_connections = Device::Connection::Connected;
1256 bool m_connectionsEnabled = false;
1257+ bool m_trustedEnabled = false;
1258+ bool m_trustedFilter = false;
1259 };
1260
1261 #endif // BLUETOOTH_DEVICE_MODEL_H
1262
1263=== modified file 'tests/plugins/CMakeLists.txt'
1264--- tests/plugins/CMakeLists.txt 2014-07-13 23:57:50 +0000
1265+++ tests/plugins/CMakeLists.txt 2014-07-31 14:30:31 +0000
1266@@ -1,1 +1,2 @@
1267 add_subdirectory(system-update)
1268+add_subdirectory(bluetooth)
1269
1270=== added directory 'tests/plugins/bluetooth'
1271=== added file 'tests/plugins/bluetooth/CMakeLists.txt'
1272--- tests/plugins/bluetooth/CMakeLists.txt 1970-01-01 00:00:00 +0000
1273+++ tests/plugins/bluetooth/CMakeLists.txt 2014-07-31 14:30:31 +0000
1274@@ -0,0 +1,61 @@
1275+set(XVFB_CMD xvfb-run -a -s "-screen 0 640x480x24")
1276+include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/plugins/bluetooth)
1277+include_directories(${QTDBUSMOCK_INCLUDE_DIRS})
1278+include_directories(${QTDBUSTEST_INCLUDE_DIRS})
1279+add_definitions(-DTESTS)
1280+
1281+add_executable(tst-bluetooth
1282+ tst_bluetooth.cpp
1283+ fakebluez.cpp
1284+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/bluetooth.cpp
1285+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/devicemodel.cpp
1286+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/device.cpp
1287+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/agent.cpp
1288+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/agentadaptor.cpp
1289+)
1290+
1291+target_link_libraries(tst-bluetooth
1292+ ${QTDBUSMOCK_LIBRARIES}
1293+ ${QTDBUSTEST_LIBRARIES}
1294+)
1295+
1296+add_executable(tst-bluetooth-devicemodel
1297+ tst_devicemodel.cpp
1298+ fakebluez.cpp
1299+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/bluetooth.cpp
1300+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/devicemodel.cpp
1301+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/device.cpp
1302+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/agent.cpp
1303+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/agentadaptor.cpp
1304+)
1305+
1306+target_link_libraries(tst-bluetooth-devicemodel
1307+ ${QTDBUSMOCK_LIBRARIES}
1308+ ${QTDBUSTEST_LIBRARIES}
1309+)
1310+
1311+add_executable(tst-bluetooth-device
1312+ tst_devicemodel.cpp
1313+ fakebluez.cpp
1314+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/bluetooth.cpp
1315+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/devicemodel.cpp
1316+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/device.cpp
1317+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/agent.cpp
1318+ ${CMAKE_SOURCE_DIR}/plugins/bluetooth/agentadaptor.cpp
1319+)
1320+
1321+target_link_libraries(tst-bluetooth-device
1322+ ${QTDBUSMOCK_LIBRARIES}
1323+ ${QTDBUSTEST_LIBRARIES}
1324+)
1325+
1326+qt5_use_modules(tst-bluetooth Qml Quick Core DBus Test)
1327+qt5_use_modules(tst-bluetooth-devicemodel Qml Quick Core DBus Test)
1328+qt5_use_modules(tst-bluetooth-device Qml Quick Core DBus Test)
1329+
1330+add_test(NAME tst-bluetooth
1331+ COMMAND ${XVFB_CMD} ${CMAKE_CURRENT_BINARY_DIR}/tst-bluetooth)
1332+add_test(NAME tst-bluetooth-devicemodel
1333+ COMMAND ${XVFB_CMD} ${CMAKE_CURRENT_BINARY_DIR}/tst-bluetooth-devicemodel)
1334+add_test(NAME tst-bluetooth-device
1335+ COMMAND ${XVFB_CMD} ${CMAKE_CURRENT_BINARY_DIR}/tst-bluetooth-device)
1336
1337=== added file 'tests/plugins/bluetooth/fakebluez.cpp'
1338--- tests/plugins/bluetooth/fakebluez.cpp 1970-01-01 00:00:00 +0000
1339+++ tests/plugins/bluetooth/fakebluez.cpp 2014-07-31 14:30:31 +0000
1340@@ -0,0 +1,118 @@
1341+/*
1342+ * Copyright 2013 Canonical Ltd.
1343+ *
1344+ * This library is free software; you can redistribute it and/or
1345+ * modify it under the terms of version 3 of the GNU Lesser General Public
1346+ * License as published by the Free Software Foundation.
1347+ *
1348+ * This program is distributed in the hope that it will be useful,
1349+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1350+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1351+ * General Public License for more details.
1352+ *
1353+ * You should have received a copy of the GNU Lesser General Public
1354+ * License along with this library; if not, write to the
1355+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
1356+ * Boston, MA 02110-1301, USA.
1357+ */
1358+
1359+#include <QDBusReply>
1360+#include <QDebug>
1361+
1362+#include "fakebluez.h"
1363+
1364+namespace Bluez {
1365+
1366+FakeBluez::FakeBluez(QObject *parent) :
1367+ QObject(parent),
1368+ m_dbusMock(m_dbusTestRunner)
1369+{
1370+ DBusMock::registerMetaTypes();
1371+
1372+ m_dbusMock.registerTemplate(BLUEZ_SERVICE, "bluez4",
1373+ QDBusConnection::SystemBus);
1374+ m_dbusTestRunner.startServices();
1375+
1376+ m_bluezMock = new QDBusInterface(BLUEZ_SERVICE,
1377+ BLUEZ_MAIN_OBJECT,
1378+ BLUEZ_MOCK_IFACE,
1379+ m_dbusTestRunner.systemConnection());
1380+}
1381+
1382+FakeBluez::~FakeBluez()
1383+{
1384+ delete m_bluezMock;
1385+}
1386+
1387+QString
1388+FakeBluez::addAdapter(const QString &name, const QString &system_name)
1389+{
1390+ QDBusReply<QString> reply = m_bluezMock->call("AddAdapter",
1391+ name, system_name);
1392+
1393+ if (reply.isValid()) {
1394+ m_currentAdapter = reply.value().replace("/org/bluez/", "");
1395+ } else {
1396+ qWarning() << "Failed to add mock adapter:" << reply.error().message();
1397+ }
1398+
1399+ return reply.isValid() ? reply.value() : QString();
1400+}
1401+
1402+QString
1403+FakeBluez::addDevice(const QString& name, const QString &address)
1404+{
1405+ QDBusReply<QString> reply = m_bluezMock->call("AddDevice",
1406+ m_currentAdapter,
1407+ address, name);
1408+
1409+ if (reply.isValid()) {
1410+ m_devices.append(reply.value());
1411+ } else {
1412+ qWarning() << "Failed to add mock device:" << reply.error().message();
1413+ }
1414+
1415+ return reply.isValid() ? reply.value() : QString();
1416+}
1417+
1418+QVariant
1419+FakeBluez::getProperty(const QString &path,
1420+ const QString &interface,
1421+ const QString &property)
1422+{
1423+ QDBusInterface iface(BLUEZ_SERVICE, path,
1424+ "org.freedesktop.DBus.Properties",
1425+ m_dbusTestRunner.systemConnection());
1426+
1427+ QDBusReply<QVariant> reply = iface.call("Get", interface, property);
1428+
1429+ if (reply.isValid()) {
1430+ return reply.value();
1431+ } else {
1432+ qWarning() << "Error getting property from mock:"
1433+ << reply.error().message();
1434+ }
1435+
1436+ return reply.isValid() ? reply.value() : QVariant();
1437+}
1438+
1439+void
1440+FakeBluez::setProperty(const QString &path,
1441+ const QString &interface,
1442+ const QString &property,
1443+ const QVariant &value)
1444+{
1445+ QDBusInterface iface(BLUEZ_SERVICE, path,
1446+ interface,
1447+ m_dbusTestRunner.systemConnection());
1448+
1449+ QDBusReply<void> reply = iface.call("SetProperty",
1450+ property, value);
1451+
1452+ if (!reply.isValid()) {
1453+ qWarning() << "Error setting property on mock:"
1454+ << reply.error().message();
1455+ }
1456+}
1457+
1458+}
1459
1460=== added file 'tests/plugins/bluetooth/fakebluez.h'
1461--- tests/plugins/bluetooth/fakebluez.h 1970-01-01 00:00:00 +0000
1462+++ tests/plugins/bluetooth/fakebluez.h 2014-07-31 14:30:31 +0000
1463@@ -0,0 +1,84 @@
1464+/*
1465+ * Copyright 2013 Canonical Ltd.
1466+ *
1467+ * This library is free software; you can redistribute it and/or
1468+ * modify it under the terms of version 3 of the GNU Lesser General Public
1469+ * License as published by the Free Software Foundation.
1470+ *
1471+ * This program is distributed in the hope that it will be useful,
1472+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1473+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1474+ * General Public License for more details.
1475+ *
1476+ * You should have received a copy of the GNU Lesser General Public
1477+ * License along with this library; if not, write to the
1478+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
1479+ * Boston, MA 02110-1301, USA.
1480+ */
1481+
1482+#ifndef FAKEBLUEZ_H
1483+#define FAKEBLUEZ_H
1484+
1485+#include <QObject>
1486+#include <QVariant>
1487+#include <QString>
1488+#include <QDBusConnection>
1489+#include <QDBusInterface>
1490+
1491+#include <libqtdbusmock/DBusMock.h>
1492+#include <libqtdbustest/DBusTestRunner.h>
1493+
1494+#define BLUEZ_SERVICE "org.bluez"
1495+#define BLUEZ_MAIN_OBJECT "/"
1496+#define BLUEZ_MOCK_IFACE "org.bluez.Mock"
1497+
1498+#define BLUEZ_MANAGER_IFACE "org.bluez.Manager"
1499+#define BLUEZ_ADAPTER_IFACE "org.bluez.Adapter"
1500+#define BLUEZ_DEVICE_IFACE "org.bluez.Device"
1501+#define BLUEZ_AUDIO_IFACE "org.bluez.Audio"
1502+
1503+using namespace QtDBusTest;
1504+using namespace QtDBusMock;
1505+
1506+namespace Bluez {
1507+
1508+class FakeBluez : public QObject
1509+{
1510+ Q_OBJECT
1511+
1512+private:
1513+ DBusMock m_dbusMock;
1514+ DBusTestRunner m_dbusTestRunner;
1515+
1516+ QDBusInterface *m_bluezMock;
1517+ QString m_currentAdapter;
1518+ QList<QString> m_devices;
1519+
1520+ QDBusInterface getInterface(const QString &path, const QString &interface);
1521+
1522+public:
1523+ explicit FakeBluez(QObject *parent = 0);
1524+
1525+ ~FakeBluez();
1526+
1527+ const QString currentAdapter() { return m_currentAdapter; }
1528+ const QList<QString> devices() { return m_devices; }
1529+ const QDBusConnection & dbus() { return m_dbusTestRunner.systemConnection(); }
1530+
1531+ QString addAdapter(const QString &name, const QString &system_name);
1532+ QString addDevice(const QString &name, const QString &address);
1533+
1534+ QVariant getProperty(const QString &path,
1535+ const QString &interface,
1536+ const QString &property);
1537+
1538+ void setProperty(const QString &path,
1539+ const QString &interface,
1540+ const QString &property,
1541+ const QVariant &value);
1542+
1543+};
1544+
1545+}
1546+
1547+#endif // FAKEBLUEZ_H
1548
1549=== added file 'tests/plugins/bluetooth/tst_bluetooth.cpp'
1550--- tests/plugins/bluetooth/tst_bluetooth.cpp 1970-01-01 00:00:00 +0000
1551+++ tests/plugins/bluetooth/tst_bluetooth.cpp 2014-07-31 14:30:31 +0000
1552@@ -0,0 +1,172 @@
1553+/*
1554+ * Copyright 2013 Canonical Ltd.
1555+ *
1556+ * This library is free software; you can redistribute it and/or
1557+ * modify it under the terms of version 3 of the GNU Lesser General Public
1558+ * License as published by the Free Software Foundation.
1559+ *
1560+ * This program is distributed in the hope that it will be useful,
1561+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1562+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1563+ * General Public License for more details.
1564+ *
1565+ * You should have received a copy of the GNU Lesser General Public
1566+ * License along with this library; if not, write to the
1567+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
1568+ * Boston, MA 02110-1301, USA.
1569+ */
1570+
1571+#include <QTest>
1572+#include <QSignalSpy>
1573+#include <QThread>
1574+
1575+#include "bluetooth.h"
1576+#include "device.h"
1577+#include "agent.h"
1578+#include "fakebluez.h"
1579+
1580+using namespace Bluez;
1581+
1582+class BluetoothTest: public QObject
1583+{
1584+ Q_OBJECT
1585+
1586+private:
1587+ FakeBluez *m_bluezMock;
1588+ Bluetooth *m_bluetooth;
1589+ QDBusConnection *m_dbus;
1590+
1591+private Q_SLOTS:
1592+ void init();
1593+ void testGotAdapter();
1594+ void testStartDiscovery();
1595+ void testStopDiscovery();
1596+ void testToggleDiscovery();
1597+ void testIsSupportedType();
1598+ void testIsDiscovering();
1599+ void cleanup();
1600+
1601+};
1602+
1603+void BluetoothTest::init()
1604+{
1605+ m_bluezMock = new FakeBluez();
1606+ m_bluezMock->addAdapter("new0", "bluetoothTest");
1607+ m_dbus = new QDBusConnection(m_bluezMock->dbus());
1608+ m_bluetooth = new Bluetooth(*m_dbus);
1609+}
1610+
1611+void BluetoothTest::cleanup()
1612+{
1613+ delete m_bluezMock;
1614+ delete m_bluetooth;
1615+}
1616+
1617+void BluetoothTest::testGotAdapter()
1618+{
1619+ QString expected = "bluetoothTest";
1620+ QString result;
1621+
1622+ result = m_bluetooth->adapterName();
1623+
1624+ QCOMPARE(result, expected);
1625+}
1626+
1627+void BluetoothTest::testStartDiscovery()
1628+{
1629+ bool expected = true;
1630+ QVariant result;
1631+
1632+ QSKIP("Fails due to a bug in bluez4 dbusmock template", SkipAll);
1633+
1634+ result = m_bluezMock->getProperty(m_bluezMock->currentAdapter(),
1635+ "org.bluez.Adapter",
1636+ "Discovering");
1637+ qWarning() << result;
1638+ QCOMPARE(result.toBool(), !expected);
1639+
1640+ m_bluetooth->startDiscovery();
1641+
1642+ result = m_bluezMock->getProperty(m_bluezMock->currentAdapter(),
1643+ "org.bluez.Adapter",
1644+ "Discovering");
1645+ qWarning() << result;
1646+ QCOMPARE(result.toBool(), expected);
1647+}
1648+
1649+void BluetoothTest::testStopDiscovery()
1650+{
1651+ bool expected = false;
1652+ QVariant result;
1653+
1654+ QSKIP("Fails due to a bug in bluez4 dbusmock template", SkipAll);
1655+
1656+ result = m_bluezMock->getProperty(m_bluezMock->currentAdapter(),
1657+ "org.bluez.Adapter",
1658+ "Discovering");
1659+ QCOMPARE(result.toBool(), !expected);
1660+
1661+ m_bluetooth->stopDiscovery();
1662+
1663+ result = m_bluezMock->getProperty(m_bluezMock->currentAdapter(),
1664+ "org.bluez.Adapter",
1665+ "Discovering");
1666+ QCOMPARE(result.toBool(), expected);
1667+}
1668+
1669+void BluetoothTest::testToggleDiscovery()
1670+{
1671+ QVariant result;
1672+
1673+ QSKIP("Fails due to a bug in bluez4 dbusmock template", SkipAll);
1674+
1675+ m_bluetooth->stopDiscovery();
1676+
1677+ result = m_bluezMock->getProperty(m_bluezMock->currentAdapter(),
1678+ "org.bluez.Adapter",
1679+ "Discovering");
1680+ QCOMPARE(result.toBool(), false);
1681+
1682+ m_bluetooth->toggleDiscovery();
1683+
1684+ result = m_bluezMock->getProperty(m_bluezMock->currentAdapter(),
1685+ "org.bluez.Adapter",
1686+ "Discovering");
1687+ QCOMPARE(result.toBool(), true);
1688+
1689+ m_bluetooth->toggleDiscovery();
1690+
1691+ result = m_bluezMock->getProperty(m_bluezMock->currentAdapter(),
1692+ "org.bluez.Adapter",
1693+ "Discovering");
1694+ QCOMPARE(result.toBool(), false);
1695+}
1696+
1697+void BluetoothTest::testIsSupportedType()
1698+{
1699+ QCOMPARE(Bluetooth::isSupportedType(Device::Type::Headset), true);
1700+ QCOMPARE(Bluetooth::isSupportedType(Device::Type::Mouse), false);
1701+ QCOMPARE(Bluetooth::isSupportedType(Device::Type::Tablet), false);
1702+}
1703+
1704+void BluetoothTest::testIsDiscovering()
1705+{
1706+ m_bluetooth->stopDiscovery();
1707+
1708+ QCOMPARE(m_bluetooth->isDiscovering(), false);
1709+
1710+ m_bluetooth->startDiscovery();
1711+
1712+ QCOMPARE(m_bluetooth->isDiscovering(), true);
1713+
1714+ m_bluetooth->toggleDiscovery();
1715+
1716+ QCOMPARE(m_bluetooth->isDiscovering(), false);
1717+
1718+ m_bluetooth->toggleDiscovery();
1719+
1720+ QCOMPARE(m_bluetooth->isDiscovering(), true);
1721+}
1722+
1723+QTEST_MAIN(BluetoothTest);
1724+#include "tst_bluetooth.moc"
1725
1726=== added file 'tests/plugins/bluetooth/tst_device.cpp'
1727--- tests/plugins/bluetooth/tst_device.cpp 1970-01-01 00:00:00 +0000
1728+++ tests/plugins/bluetooth/tst_device.cpp 2014-07-31 14:30:31 +0000
1729@@ -0,0 +1,197 @@
1730+/*
1731+ * Copyright 2013 Canonical Ltd.
1732+ *
1733+ * This library is free software; you can redistribute it and/or
1734+ * modify it under the terms of version 3 of the GNU Lesser General Public
1735+ * License as published by the Free Software Foundation.
1736+ *
1737+ * This program is distributed in the hope that it will be useful,
1738+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1739+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1740+ * General Public License for more details.
1741+ *
1742+ * You should have received a copy of the GNU Lesser General Public
1743+ * License along with this library; if not, write to the
1744+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
1745+ * Boston, MA 02110-1301, USA.
1746+ */
1747+
1748+#include <QTest>
1749+#include <QSignalSpy>
1750+#include <QThread>
1751+
1752+#include "bluetooth.h"
1753+#include "device.h"
1754+#include "fakebluez.h"
1755+
1756+using namespace Bluez;
1757+
1758+class DeviceTest: public QObject
1759+{
1760+ Q_OBJECT
1761+
1762+private:
1763+ FakeBluez *m_bluezMock;
1764+ Device *m_device;
1765+ QDBusConnection *m_dbus;
1766+
1767+private:
1768+ void checkAudioState(const QString &expected);
1769+
1770+private Q_SLOTS:
1771+ void init();
1772+ void testGetName();
1773+ void testGetAddress();
1774+ void testGetIconName();
1775+ void testGetType();
1776+ void testIsPaired();
1777+ void testIsTrusted();
1778+ void testGetConnection();
1779+ void testGetStrength();
1780+ void testGetPath();
1781+ void testMakeTrusted();
1782+ void testDiscoverServices();
1783+
1784+ void cleanup();
1785+
1786+};
1787+
1788+void DeviceTest::init()
1789+{
1790+ m_bluezMock = new FakeBluez();
1791+ m_bluezMock->addAdapter("new0", "bluetoothTest");
1792+ m_bluezMock->addDevice("My Phone", "00:00:de:ad:be:ef");
1793+ m_dbus = new QDBusConnection(m_bluezMock->dbus());
1794+
1795+ QList<QString> devices = m_bluezMock->devices();
1796+ if (devices.isEmpty())
1797+ QFAIL("No devices in mock to be tested.");
1798+
1799+ m_device = new Device(devices.first(), *m_dbus);
1800+}
1801+
1802+void DeviceTest::cleanup()
1803+{
1804+ delete m_bluezMock;
1805+ delete m_device;
1806+}
1807+
1808+void DeviceTest::testGetName()
1809+{
1810+ QCOMPARE(m_device->getName(), "My Phone");
1811+}
1812+
1813+void DeviceTest::testAddress()
1814+{
1815+ QCOMPARE(m_device->getAddress(), "00:00:de:ad:be:ef");
1816+}
1817+
1818+void DeviceTest::testIconName()
1819+{
1820+ QCOMPARE(m_device->getIconName(), "image://theme/audio-headset");
1821+}
1822+
1823+void DeviceTest::testGetType()
1824+{
1825+ QCOMPARE(m_device->getType(), Device::Type::Headset);
1826+}
1827+
1828+void DeviceTest::testIsPaired()
1829+{
1830+ QCOMPARE(m_device->isPaired(), false);
1831+
1832+ m_bluezMock->setProperty("org.bluez.Device", "Paired", QVariant(true));
1833+
1834+ QCOMPARE(m_device->isPaired(), true);
1835+}
1836+
1837+void DeviceTest::testIsTrusted()
1838+{
1839+ QCOMPARE(m_device->isTrusted(), false);
1840+
1841+ m_bluezMock->setProperty("org.bluez.Device", "Trusted", QVariant(true));
1842+
1843+ QCOMPARE(m_device->isTrusted(), true);
1844+}
1845+
1846+void DeviceTest::testGetConnection()
1847+{
1848+ QCOMPARE(m_device.getConnection(), Device::Connection::Disconnected);
1849+}
1850+
1851+void DeviceTest::testGetStrength()
1852+{
1853+ QCOMPARE(m_device.getStrength(), Device::Strength::Good);
1854+}
1855+
1856+void DeviceTest::testGetPath()
1857+{
1858+ QCOMPARE(m_device.getPath(),
1859+ "/org/bluez/" + m_bluezMock->currentAdapter() + "/"
1860+ + "dev_00_00_de_ad_be_ef");
1861+}
1862+
1863+void DeviceTest::testMakeTrusted()
1864+{
1865+ QCOMPARE(m_device->isTrusted(), false);
1866+
1867+ m_device->makeTrusted(true);
1868+
1869+ QCOMPARE(m_device->isTrusted(), true);
1870+
1871+ m_device->makeTrusted(false);
1872+
1873+ QCOMPARE(m_device->isTrusted(), false);
1874+}
1875+
1876+void DeviceTest::checkAudioState(const QString &expected)
1877+{
1878+ QVariant state = m_bluezMock->getProperty("org.bluez.Audio", "State");
1879+
1880+ QVERIFY(state.type() == QMetaType::QString);
1881+ QCOMPARE(state.toString(), expected);
1882+}
1883+
1884+void DeviceTest::testDiscoverServices()
1885+{
1886+ m_device->discoverServices();
1887+
1888+ QVariant state = m_bluezMock->getProperty("org.bluez.Audio", "State");
1889+
1890+ checkAudioState("disconnected");
1891+}
1892+
1893+void DeviceTest::testConnect()
1894+{
1895+ testDiscoverServices();
1896+
1897+ m_device->connect(Device::ConnectionMode::Audio);
1898+
1899+ checkAudioState("connected");
1900+}
1901+
1902+void DeviceTest::testDisconnect()
1903+{
1904+ testConnect();
1905+
1906+ m_device->disconnect(Device::ConnectionMode::Audio);
1907+
1908+ checkAudioState("disconnected");
1909+}
1910+
1911+void DeviceTest::testConnectPending()
1912+{
1913+ testIsPaired();
1914+ testIsTrusted();
1915+
1916+ m_device->addConnectAfterPairing(Device::ConnectionMode::Audio);
1917+
1918+ checkAudioState("disconnected");
1919+
1920+ m_device->connectPending();
1921+
1922+ checkAudioState("connected");
1923+}
1924+
1925+QTEST_MAIN(DeviceTest);
1926+#include "tst_device.moc"
1927
1928=== added file 'tests/plugins/bluetooth/tst_devicemodel.cpp'
1929--- tests/plugins/bluetooth/tst_devicemodel.cpp 1970-01-01 00:00:00 +0000
1930+++ tests/plugins/bluetooth/tst_devicemodel.cpp 2014-07-31 14:30:31 +0000
1931@@ -0,0 +1,97 @@
1932+/*
1933+ * Copyright 2013 Canonical Ltd.
1934+ *
1935+ * This library is free software; you can redistribute it and/or
1936+ * modify it under the terms of version 3 of the GNU Lesser General Public
1937+ * License as published by the Free Software Foundation.
1938+ *
1939+ * This program is distributed in the hope that it will be useful,
1940+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1941+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1942+ * General Public License for more details.
1943+ *
1944+ * You should have received a copy of the GNU Lesser General Public
1945+ * License along with this library; if not, write to the
1946+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
1947+ * Boston, MA 02110-1301, USA.
1948+ */
1949+
1950+#include <QTest>
1951+#include <QSignalSpy>
1952+#include <QThread>
1953+
1954+#include "bluetooth.h"
1955+#include "devicemodel.h"
1956+#include "fakebluez.h"
1957+
1958+using namespace Bluez;
1959+
1960+class DeviceModelTest: public QObject
1961+{
1962+ Q_OBJECT
1963+
1964+private:
1965+ FakeBluez *m_bluezMock;
1966+ DeviceModel *m_devicemodel;
1967+ QDBusConnection *m_dbus;
1968+
1969+private Q_SLOTS:
1970+ void init();
1971+ void testDeviceFoundOnStart();
1972+ void testDeviceFound();
1973+ void testGetDeviceFromAddress();
1974+ void testGetDeviceFromPath();
1975+ void cleanup();
1976+
1977+};
1978+
1979+void DeviceModelTest::init()
1980+{
1981+ m_bluezMock = new FakeBluez();
1982+
1983+ m_bluezMock->addAdapter("new0", "bluetoothTest");
1984+ m_bluezMock->addDevice("My Phone", "00:00:de:ad:be:ef");
1985+
1986+ m_dbus = new QDBusConnection(m_bluezMock->dbus());
1987+ m_devicemodel = new DeviceModel(*m_dbus);
1988+}
1989+
1990+void DeviceModelTest::cleanup()
1991+{
1992+ delete m_bluezMock;
1993+ delete m_devicemodel;
1994+}
1995+
1996+void DeviceModelTest::testDeviceFoundOnStart()
1997+{
1998+ QCOMPARE(m_devicemodel->rowCount(), 1);
1999+}
2000+
2001+void DeviceModelTest::testDeviceFound()
2002+{
2003+ QSKIP("TODO: Finish me.", SkipAll);
2004+
2005+ m_bluezMock->addDevice("My New Phone", "00:0b:ad:c0:ff:ee");
2006+ QCOMPARE(m_devicemodel->rowCount(), 2);
2007+}
2008+
2009+void DeviceModelTest::testGetDeviceFromAddress()
2010+{
2011+ auto device = m_devicemodel->getDeviceFromAddress("00:00:de:ad:be:ef");
2012+
2013+ QVERIFY(device);
2014+ QVERIFY(!device->getPath().isEmpty());
2015+}
2016+
2017+void DeviceModelTest::testGetDeviceFromPath()
2018+{
2019+ QList<QString> devices = m_bluezMock->devices();
2020+
2021+ auto device = m_devicemodel->getDeviceFromPath(devices.at(0));
2022+
2023+ QVERIFY(device);
2024+ QVERIFY(!device->getPath().isEmpty());
2025+}
2026+
2027+QTEST_MAIN(DeviceModelTest);
2028+#include "tst_devicemodel.moc"

Subscribers

People subscribed via source and target branches