Merge lp:~jonas-drange/ubuntu-system-settings/hotspots-binding into lp:ubuntu-system-settings
- hotspots-binding
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1501 |
Proposed branch: | lp:~jonas-drange/ubuntu-system-settings/hotspots-binding |
Merge into: | lp:ubuntu-system-settings |
Prerequisite: | lp:~ken-vandine/ubuntu-system-settings/device_name |
Diff against target: |
2167 lines (+1568/-279) 12 files modified
plugins/cellular/CMakeLists.txt (+1/-0) plugins/cellular/Common.qml (+97/-0) plugins/cellular/Components/MultiSim.qml (+12/-3) plugins/cellular/Components/SingleSim.qml (+12/-4) plugins/cellular/Hotspot.qml (+63/-20) plugins/cellular/HotspotSetup.qml (+301/-70) plugins/cellular/PageComponent.qml (+2/-2) plugins/cellular/hotspotmanager.cpp (+617/-159) plugins/cellular/hotspotmanager.h (+147/-19) tests/autopilot/ubuntu_system_settings/__init__.py (+132/-0) tests/autopilot/ubuntu_system_settings/tests/__init__.py (+62/-1) tests/autopilot/ubuntu_system_settings/tests/test_cellular.py (+122/-1) |
To merge this branch: | bzr merge lp:~jonas-drange/ubuntu-system-settings/hotspots-binding |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Matthew Paul Thomas | design | Pending | |
Ken VanDine | Pending | ||
Mathieu Trudel-Lapierre | Pending | ||
Review via email: mp+259392@code.launchpad.net |
This proposal supersedes a proposal from 2015-03-09.
Commit message
[cellular] ap support in binding and rewriting the API to make it easier to interact with
Description of the change
Changes
This modifies the current hotspotmanager binding to do AP and adhoc hotspots, but AP by default. It also mimics [1] in a lot of ways.
So, roughly it
* adds means to talk to URFKill and wpa_supplicant, to respectively soft block/unblock Wi-Fi, and change the interface driver if necessary (which it is on all hybris devices),
* uses NetworkManager events to be less racy,
* and checks for failures and reports them to the user.
The UI has been changed to use the new binding, albeit still hidden due to bug 1426923 bug 1429314 bug 1421671 and bug 1427358.
How to test
If libhybris device (krillin):
https:/
If arale and the hotspot will not start:
1. mknod /dev/wmtWifi c 153 0
2. chmod 0660 /dev/wmtWifi
3. chown root:root /dev/wmtWifi
All devices:
set env variable USS_SHOW_ALL_UI=1
Checklist
* Is your branch in sync with latest trunk (e.g. bzr pull lp:trunk -> no changes)
Yes
* Did you build your software in a clean sbuild/pbuilder chroot or ppa?
Yes
* Did you build your software in a clean sbuild/pbuilder armhf chroot or ppa?
Yes
* Has your component "TestPlan” been executed successfully on emulator, N4?
Yes (Krillin, Arale), not N4 due to bug from above.
* Has a 5 minute exploratory testing run been executed on N4?
No, see above.
* If you changed the packaging (debian), did you subscribe a core-dev to this MP?
N/A
* If you changed the UI, did you subscribe the design-reviewers to this MP?
Yes (mpt)
* What components might get impacted by your changes?
Cellular for Single and Multi-SIM
* Have you requested review by the teams of these owning components?
N/A
Note: Prevent wake lock $ powerd-cli display on
[1] http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1339
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1354
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1359
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Ken VanDine (ken-vandine) wrote : Posted in a previous version of this proposal | # |
See a couple comments and questions inline.
Jonas G. Drange (jonas-drange) wrote : Posted in a previous version of this proposal | # |
Information given, but sat MP back to WIP to work on what Ken pointed out.
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1362
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1363
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Ken VanDine (ken-vandine) wrote : Posted in a previous version of this proposal | # |
In my testing I found that the ssidField which needs to be 8 or more characters only becomes valid when you enter the 9th character or the field loses focus. This MP is more about the backend it needs and the UI is still hidden. Not sure we want to block on that. However the hotspot I created on my mako appeared to work, but I couldn't make a successful connection to it from my laptop.
Mathieu Trudel-Lapierre (cyphermox) wrote : Posted in a previous version of this proposal | # |
I only reviewed the backend code but it looks fine to me; see one minor inline comment about wifi security.
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1364
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:1365
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Ken VanDine (ken-vandine) wrote : Posted in a previous version of this proposal | # |
Looks good, I pushed a fix to show the UI and disable predictive text in the password to another branch
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1368
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal | # |
PASSED: Continuous integration, rev:1370
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Ken VanDine (ken-vandine) wrote : Posted in a previous version of this proposal | # |
Please fix the indenting of the .cpp and .h files, you used 2 spaces instead of 4. Also see the comments inline in the diff.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1376
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 1377. By Jonas G. Drange
-
add comment to import
- 1378. By Jonas G. Drange
-
hiding completely on mako
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1377
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1378
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 1379. By Jonas G. Drange
-
unhide for testing
- 1380. By Jonas G. Drange
-
remove enabledshim
- 1381. By Jonas G. Drange
-
using usc serverpropertys
ynchroniser - 1382. By Jonas G. Drange
-
foo
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1382
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 1383. By Jonas G. Drange
-
let the checkbox render before starting hotspot
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1383
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 1384. By Jonas G. Drange
-
disable back button so as to ensure that the wifi is soft unblocked
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1384
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 1385. By Jonas G. Drange
-
sync trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1385
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 1386. By Jonas G. Drange
-
sync with trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1386
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 1387. By Jonas G. Drange
-
sync with trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1387
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'plugins/cellular/CMakeLists.txt' |
2 | --- plugins/cellular/CMakeLists.txt 2015-03-26 21:57:53 +0000 |
3 | +++ plugins/cellular/CMakeLists.txt 2015-07-31 14:56:39 +0000 |
4 | @@ -6,6 +6,7 @@ |
5 | set(QML_SOURCES |
6 | apn.js |
7 | carriers.js |
8 | + Common.qml |
9 | CustomApnEditor.qml |
10 | PageChooseApn.qml |
11 | PageChooseCarrier.qml |
12 | |
13 | === added file 'plugins/cellular/Common.qml' |
14 | --- plugins/cellular/Common.qml 1970-01-01 00:00:00 +0000 |
15 | +++ plugins/cellular/Common.qml 2015-07-31 14:56:39 +0000 |
16 | @@ -0,0 +1,97 @@ |
17 | +/* |
18 | + * Copyright (C) 2015 Canonical Ltd |
19 | + * |
20 | + * This program is free software: you can redistribute it and/or modify |
21 | + * it under the terms of the GNU General Public License version 3 as |
22 | + * published by the Free Software Foundation. |
23 | + * |
24 | + * This program is distributed in the hope that it will be useful, |
25 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
26 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
27 | + * GNU General Public License for more details. |
28 | + * |
29 | + * You should have received a copy of the GNU General Public License |
30 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
31 | + * |
32 | + * Authors: |
33 | + * Jonas G. Drange <jonas.drange@canonical.com> |
34 | + * |
35 | +*/ |
36 | +import QtQuick 2.0 |
37 | + |
38 | +Item { |
39 | + /* |
40 | + The mapping of code to string is taken from |
41 | + http://bazaar.launchpad.net/~vcs-imports/ |
42 | + network-manager/trunk/view/head:/cli/src/common.c |
43 | + |
44 | + NetworkManager documentation: https://developer.gnome.org/ |
45 | + NetworkManager/0.9/spec.html#type-NM_DEVICE_STATE_REASON |
46 | + */ |
47 | + function reasonToString (reason) { |
48 | + switch (reason) { |
49 | + case 0: |
50 | + return i18n.tr("Unknown error"); |
51 | + case 1: |
52 | + return i18n.tr("No reason given"); |
53 | + case 2: |
54 | + return i18n.tr("Device is now managed"); |
55 | + case 3: |
56 | + return i18n.tr("Device is now unmanaged"); |
57 | + case 4: |
58 | + return i18n.tr("The device could not be readied for configuration"); |
59 | + case 5: |
60 | + return i18n.tr("IP configuration could not be reserved (no available address, timeout, etc.)"); |
61 | + case 6: |
62 | + return i18n.tr("The IP configuration is no longer valid"); |
63 | + case 7: |
64 | + return i18n.tr("Your authentication details were incorrect"); |
65 | + case 8: |
66 | + return i18n.tr("802.1X supplicant disconnected"); |
67 | + case 9: |
68 | + return i18n.tr("802.1X supplicant configuration failed"); |
69 | + case 10: |
70 | + return i18n.tr("802.1X supplicant failed"); |
71 | + case 11: |
72 | + return i18n.tr("802.1X supplicant took too long to authenticate"); |
73 | + case 15: |
74 | + return i18n.tr("DHCP client failed to start"); |
75 | + case 16: |
76 | + return i18n.tr("DHCP client error"); |
77 | + case 17: |
78 | + return i18n.tr("DHCP client failed"); |
79 | + case 18: |
80 | + return i18n.tr("Shared connection service failed to start"); |
81 | + case 19: |
82 | + return i18n.tr("Shared connection service failed"); |
83 | + case 35: |
84 | + return i18n.tr("Necessary firmware for the device may be missing"); |
85 | + case 36: |
86 | + return i18n.tr("The device was removed"); |
87 | + case 37: |
88 | + return i18n.tr("NetworkManager went to sleep"); |
89 | + case 38: |
90 | + return i18n.tr("The device's active connection disappeared"); |
91 | + case 39: |
92 | + return i18n.tr("Device disconnected by user or client"); |
93 | + case 41: |
94 | + return i18n.tr("The device's existing connection was assumed"); |
95 | + case 42: |
96 | + return i18n.tr("The supplicant is now available"); |
97 | + case 43: |
98 | + return i18n.tr("The modem could not be found"); |
99 | + case 44: |
100 | + return i18n.tr("The Bluetooth connection failed or timed out"); |
101 | + case 50: |
102 | + return i18n.tr("A dependency of the connection failed"); |
103 | + case 52: |
104 | + return i18n.tr("ModemManager is unavailable"); |
105 | + case 53: |
106 | + return i18n.tr("The Wi-Fi network could not be found"); |
107 | + case 54: |
108 | + return i18n.tr("A secondary connection of the base connection failed"); |
109 | + default: |
110 | + return i18n.tr("Unknown"); |
111 | + } |
112 | + } |
113 | +} |
114 | |
115 | === modified file 'plugins/cellular/Components/MultiSim.qml' |
116 | --- plugins/cellular/Components/MultiSim.qml 2015-01-21 20:44:20 +0000 |
117 | +++ plugins/cellular/Components/MultiSim.qml 2015-07-31 14:56:39 +0000 |
118 | @@ -23,6 +23,11 @@ |
119 | import Ubuntu.Components 0.1 |
120 | import Ubuntu.Components.ListItems 0.1 as ListItem |
121 | |
122 | +/* This is a temporary solution to the issue of Hotspots failing on mako. If |
123 | +the device is mako, we hide the hotspot entry. Will be removed once lp:1434591 |
124 | +has been resolved. */ |
125 | +import Ubuntu.SystemSettings.Update 1.0 |
126 | + |
127 | Column { |
128 | objectName: "multiSim" |
129 | |
130 | @@ -50,7 +55,8 @@ |
131 | |
132 | ListItem.SingleValue { |
133 | text : i18n.tr("Hotspot disabled because Wi-Fi is off.") |
134 | - visible: showAllUI && !hotspotItem.visible |
135 | + visible: !hotspotItem.visible && |
136 | + UpdateManager.deviceName !== "mako" |
137 | } |
138 | |
139 | ListItem.SingleValue { |
140 | @@ -58,9 +64,12 @@ |
141 | text: i18n.tr("Wi-Fi hotspot") |
142 | progression: true |
143 | onClicked: { |
144 | - pageStack.push(Qt.resolvedUrl("Hotspot.qml")) |
145 | + pageStack.push(Qt.resolvedUrl("../Hotspot.qml")) |
146 | } |
147 | - visible: showAllUI && (actionGroup.actionObject.valid ? actionGroup.actionObject.state : false) |
148 | + visible: (actionGroup.actionObject.valid ? |
149 | + actionGroup.actionObject.state : false) && |
150 | + UpdateManager.deviceName !== "mako" |
151 | + |
152 | } |
153 | |
154 | ListItem.Standard { |
155 | |
156 | === modified file 'plugins/cellular/Components/SingleSim.qml' |
157 | --- plugins/cellular/Components/SingleSim.qml 2015-01-20 12:23:34 +0000 |
158 | +++ plugins/cellular/Components/SingleSim.qml 2015-07-31 14:56:39 +0000 |
159 | @@ -22,6 +22,11 @@ |
160 | import Ubuntu.Components 0.1 |
161 | import Ubuntu.Components.ListItems 0.1 as ListItem |
162 | |
163 | +/* This is a temporary solution to the issue of Hotspots failing on mako. If |
164 | +the device is mako, we hide the hotspot entry. Will be removed once lp:1434591 |
165 | +has been resolved. */ |
166 | +import Ubuntu.SystemSettings.Update 1.0 |
167 | + |
168 | Column { |
169 | |
170 | objectName: "singleSim" |
171 | @@ -49,7 +54,6 @@ |
172 | id: dataRoamingItem |
173 | text: i18n.tr("Data roaming") |
174 | enabled: sim.connMan.powered |
175 | - showDivider: false |
176 | control: Switch { |
177 | id: dataRoamingControl |
178 | objectName: "roaming" |
179 | @@ -62,17 +66,21 @@ |
180 | |
181 | ListItem.SingleValue { |
182 | text : i18n.tr("Hotspot disabled because Wi-Fi is off.") |
183 | - visible: showAllUI && !hotspotItem.visible |
184 | + visible: !hotspotItem.visible && |
185 | + UpdateManager.deviceName !== "mako" |
186 | } |
187 | |
188 | ListItem.SingleValue { |
189 | id: hotspotItem |
190 | + objectName: "hotspotEntry" |
191 | text: i18n.tr("Wi-Fi hotspot") |
192 | progression: true |
193 | onClicked: { |
194 | - pageStack.push(Qt.resolvedUrl("Hotspot.qml")) |
195 | + pageStack.push(Qt.resolvedUrl("../Hotspot.qml")) |
196 | } |
197 | - visible: showAllUI && (actionGroup.actionObject.valid ? actionGroup.actionObject.state : false) |
198 | + visible: (actionGroup.actionObject.valid ? |
199 | + actionGroup.actionObject.state : false) && |
200 | + UpdateManager.deviceName !== "mako" |
201 | } |
202 | |
203 | ListItem.Standard { |
204 | |
205 | === modified file 'plugins/cellular/Hotspot.qml' |
206 | --- plugins/cellular/Hotspot.qml 2014-07-29 15:39:08 +0000 |
207 | +++ plugins/cellular/Hotspot.qml 2015-07-31 14:56:39 +0000 |
208 | @@ -20,57 +20,100 @@ |
209 | import SystemSettings 1.0 |
210 | import Ubuntu.Components 0.1 |
211 | import Ubuntu.Components.ListItems 0.1 as ListItem |
212 | +import Ubuntu.Components.Popups 0.1 |
213 | +import Ubuntu.Settings.Components 0.1 as USC |
214 | import Ubuntu.SystemSettings.Cellular 1.0 |
215 | |
216 | ItemPage { |
217 | |
218 | id: hotspot |
219 | + objectName: "hotspotPage" |
220 | |
221 | title: i18n.tr("Wi-Fi hotspot") |
222 | |
223 | + // We disable the back action while a hotspot is in the process of either |
224 | + // being enabled or disabled. |
225 | + head.backAction: Action { |
226 | + iconName: "back" |
227 | + enabled: hotspotSwitch.enabled |
228 | + onTriggered: { |
229 | + pageStack.pop(); |
230 | + } |
231 | + } |
232 | + |
233 | HotspotManager { |
234 | id: hotspotManager |
235 | } |
236 | |
237 | + Loader { |
238 | + id: setup |
239 | + asynchronous: false |
240 | + } |
241 | + |
242 | Column { |
243 | |
244 | anchors.fill: parent |
245 | + spacing: units.gu(2) |
246 | |
247 | ListItem.Standard { |
248 | text: i18n.tr("Hotspot") |
249 | + enabled: hotspotManager.stored |
250 | control: Switch { |
251 | id: hotspotSwitch |
252 | - checked: hotspotManager.isHotspotActive() |
253 | - onTriggered: { |
254 | - if(checked) { |
255 | - hotspotManager.enableHotspot() |
256 | - } else { |
257 | - hotspotManager.disableHotspot() |
258 | + objectName: "hotspotSwitch" |
259 | + enabled: !switchSync.syncWaiting |
260 | + |
261 | + USC.ServerPropertySynchroniser { |
262 | + id: switchSync |
263 | + userTarget: hotspotSwitch |
264 | + userProperty: "checked" |
265 | + serverTarget: hotspotManager |
266 | + serverProperty: "enabled" |
267 | + useWaitBuffer: true |
268 | + |
269 | + // Since this blocks the UI thread, we wait until |
270 | + // the UI has completed the checkbox animation before we |
271 | + // ask the server to uipdate. |
272 | + onSyncTriggered: { |
273 | + triggerTimer.value = value; |
274 | + triggerTimer.start(); |
275 | } |
276 | } |
277 | + |
278 | + Timer { |
279 | + id: triggerTimer |
280 | + property bool value |
281 | + interval: 250; repeat: false |
282 | + onTriggered: hotspotManager.enabled = value |
283 | + } |
284 | } |
285 | } |
286 | |
287 | - Label { |
288 | - width: parent.width |
289 | - wrapMode: Text.WordWrap |
290 | - anchors.leftMargin: units.gu(2) |
291 | - anchors.rightMargin: units.gu(2) |
292 | - text : hotspotSwitch.enabled ? |
293 | + ListItem.Caption { |
294 | + anchors { |
295 | + left: parent.left |
296 | + right: parent.right |
297 | + leftMargin: units.gu(2) |
298 | + rightMargin: units.gu(2) |
299 | + } |
300 | + text : hotspotSwitch.stored ? |
301 | i18n.tr("When hotspot is on, other devices can use your cellular data connection over Wi-Fi. Normal data charges apply.") |
302 | - : i18n.tr("Other devices can use your cellular data connection over the Wi-Fi network. Normal data charges apply.") |
303 | + : i18n.tr("Other devices can use your cellular data connection over the Wi-Fi network. Normal data charges apply.") |
304 | } |
305 | |
306 | Button { |
307 | - text: i18n.tr("Set up hotspot") |
308 | - anchors.left: parent.left |
309 | - anchors.right: parent.right |
310 | - anchors.leftMargin: units.gu(2) |
311 | - anchors.rightMargin: units.gu(2) |
312 | + objectName: "hotspotSetupEntry" |
313 | + anchors.horizontalCenter: parent.horizontalCenter |
314 | + width: parent.width - units.gu(4) |
315 | + text: hotspotManager.stored ? |
316 | + i18n.tr("Change password/setup…") : i18n.tr("Set up hotspot…") |
317 | + |
318 | onClicked: { |
319 | - pageStack.push(Qt.resolvedUrl("HotspotSetup.qml"), {hotspotManager: hotspotManager}) |
320 | + setup.setSource(Qt.resolvedUrl("HotspotSetup.qml")); |
321 | + PopupUtils.open(setup.item, hotspot, { |
322 | + hotspotManager: hotspotManager |
323 | + }); |
324 | } |
325 | } |
326 | - |
327 | } |
328 | } |
329 | |
330 | === modified file 'plugins/cellular/HotspotSetup.qml' |
331 | --- plugins/cellular/HotspotSetup.qml 2014-07-04 11:32:33 +0000 |
332 | +++ plugins/cellular/HotspotSetup.qml 2015-07-31 14:56:39 +0000 |
333 | @@ -22,82 +22,313 @@ |
334 | import Ubuntu.Components 0.1 |
335 | import Ubuntu.Components.ListItems 0.1 as ListItem |
336 | import Ubuntu.SystemSettings.Cellular 1.0 |
337 | - |
338 | -ItemPage { |
339 | - |
340 | +import Ubuntu.Components.Popups 0.1 |
341 | + |
342 | +Component { |
343 | id: hotspotSetup |
344 | - title: i18n.tr("Change hotspot setup") |
345 | - |
346 | - property var hotspotManager: null |
347 | - |
348 | - |
349 | - Column { |
350 | - |
351 | - anchors.fill: parent |
352 | - |
353 | - ListItem.Standard { |
354 | - id: ssidLabel |
355 | - text: i18n.tr("Hotspot name") |
356 | - } |
357 | - |
358 | - TextField { |
359 | - id: ssidField |
360 | - text: hotspotManager.getHotspotName() |
361 | - anchors.left: parent.left |
362 | - anchors.right: parent.right |
363 | - anchors.leftMargin: units.gu(2) |
364 | - anchors.rightMargin: units.gu(2) |
365 | - } |
366 | - |
367 | - ListItem.Standard { |
368 | - id: passwordLabel |
369 | - text: i18n.tr("Key (must be 8 characters or longer)") |
370 | - } |
371 | - |
372 | - TextField { |
373 | - id: passwordField |
374 | - text: hotspotManager.getHotspotPassword() |
375 | - echoMode: passwordVisibleSwitch.checked ? TextInput.Normal : TextInput.Password |
376 | - anchors.left: parent.left |
377 | - anchors.right: parent.right |
378 | - anchors.leftMargin: units.gu(2) |
379 | - anchors.rightMargin: units.gu(2) |
380 | - } |
381 | - |
382 | - ListItem.Standard { |
383 | - text: i18n.tr("Show key") |
384 | - id: passwordVisible |
385 | - control: Switch { |
386 | - id: passwordVisibleSwitch |
387 | + |
388 | + Dialog { |
389 | + id: hotspotSetupDialog |
390 | + property var hotspotManager: null |
391 | + |
392 | + /* hotspotManager.stored changes as soon as the user has added a |
393 | + hotspot, and we use this value when we choose between e.g. "Change" and |
394 | + "Setup". We'd like the narrative to be consistent, so we stick with |
395 | + what the stored value was at component completion. |
396 | + */ |
397 | + property bool stored: false |
398 | + Component.onCompleted: stored = hotspotManager.stored; |
399 | + |
400 | + objectName: "hotspotSetup" |
401 | + anchorToKeyboard: true |
402 | + |
403 | + function settingsValid() { |
404 | + return ssidField.text != "" && passwordField.length >= 8; |
405 | + } |
406 | + |
407 | + title: stored ? |
408 | + i18n.tr("Change hotspot setup") : i18n.tr("Setup hotspot") |
409 | + text: feedback.enabled ? feedback.text : ""; |
410 | + |
411 | + Common { |
412 | + id: common |
413 | + } |
414 | + |
415 | + states: [ |
416 | + State { |
417 | + name: "STARTING" |
418 | + PropertyChanges { |
419 | + target: workingIndicator |
420 | + running: true |
421 | + } |
422 | + PropertyChanges { |
423 | + target: ssidLabel |
424 | + opacity: 0.5 |
425 | + } |
426 | + PropertyChanges { |
427 | + target: ssidField |
428 | + enabled: false |
429 | + } |
430 | + PropertyChanges { |
431 | + target: passwordLabel |
432 | + opacity: 0.5 |
433 | + } |
434 | + PropertyChanges { |
435 | + target: passwordField |
436 | + enabled: false |
437 | + } |
438 | + PropertyChanges { |
439 | + target: feedback |
440 | + enabled: true |
441 | + } |
442 | + PropertyChanges { |
443 | + target: confirmButton |
444 | + enabled: false |
445 | + } |
446 | + }, |
447 | + |
448 | + State { |
449 | + name: "FAILED" |
450 | + PropertyChanges { |
451 | + target: feedback |
452 | + enabled: true |
453 | + } |
454 | + PropertyChanges { |
455 | + target: ssidField |
456 | + errorHighlight: true |
457 | + } |
458 | + StateChangeScript { |
459 | + script: ssidField.forceActiveFocus() |
460 | + } |
461 | + }, |
462 | + |
463 | + State { |
464 | + name: "SUCCEEDED" |
465 | + PropertyChanges { |
466 | + target: successIcon |
467 | + visible: true |
468 | + } |
469 | + PropertyChanges { |
470 | + target: successIndicator |
471 | + running: true |
472 | + } |
473 | + PropertyChanges { |
474 | + target: ssidLabel |
475 | + opacity: 0.5 |
476 | + } |
477 | + PropertyChanges { |
478 | + target: ssidField |
479 | + enabled: false |
480 | + } |
481 | + PropertyChanges { |
482 | + target: passwordLabel |
483 | + opacity: 0.5 |
484 | + } |
485 | + PropertyChanges { |
486 | + target: passwordField |
487 | + enabled: false |
488 | + } |
489 | + PropertyChanges { |
490 | + target: confirmButton |
491 | + enabled: false |
492 | + } |
493 | } |
494 | - } |
495 | + ] |
496 | |
497 | - RowLayout { |
498 | + Column { |
499 | anchors { |
500 | left: parent.left |
501 | right: parent.right |
502 | - margins: units.gu(2) |
503 | - } |
504 | - |
505 | - Button { |
506 | - id: cancelButton |
507 | - Layout.fillWidth: true |
508 | - text: i18n.tr("Cancel") |
509 | - onClicked: { |
510 | - pageStack.pop() |
511 | - } |
512 | - } |
513 | - |
514 | - Button { |
515 | - id: connectButton |
516 | - Layout.fillWidth: true |
517 | - text: i18n.tr("Change") |
518 | - enabled: ssidField.text != "" && passwordField.length >= 8 |
519 | - onClicked: { |
520 | - hotspotManager.setupHotspot(ssidField.text, passwordField.text) |
521 | - pageStack.pop() |
522 | - } |
523 | - } |
524 | + } |
525 | + spacing: units.gu(1) |
526 | + |
527 | + Label { |
528 | + property bool enabled: false |
529 | + id: feedback |
530 | + horizontalAlignment: Text.AlignHCenter |
531 | + height: contentHeight |
532 | + wrapMode: Text.WordWrap |
533 | + visible: false |
534 | + } |
535 | + |
536 | + Label { |
537 | + id: ssidLabel |
538 | + text: i18n.tr("Hotspot name") |
539 | + fontSize: "medium" |
540 | + font.bold: true |
541 | + color: Theme.palette.selected.backgroundText |
542 | + elide: Text.ElideRight |
543 | + } |
544 | + |
545 | + TextField { |
546 | + id: ssidField |
547 | + objectName: "ssidField" |
548 | + text: hotspotManager.ssid |
549 | + inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText |
550 | + Component.onCompleted: forceActiveFocus() |
551 | + width: parent.width |
552 | + } |
553 | + |
554 | + Label { |
555 | + id: passwordLabel |
556 | + text: i18n.tr("Key (must be 8 characters or longer)") |
557 | + fontSize: "medium" |
558 | + font.bold: true |
559 | + color: Theme.palette.selected.backgroundText |
560 | + wrapMode: Text.WordWrap |
561 | + width: parent.width |
562 | + } |
563 | + |
564 | + TextField { |
565 | + id: passwordField |
566 | + objectName: "passwordField" |
567 | + text: hotspotManager.password |
568 | + echoMode: passwordVisibleSwitch.checked ? |
569 | + TextInput.Normal : TextInput.Password |
570 | + inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText |
571 | + width: parent.width |
572 | + } |
573 | + |
574 | + ListItem.Standard { |
575 | + text: i18n.tr("Show key") |
576 | + id: passwordVisible |
577 | + onClicked: passwordVisibleSwitch.trigger() |
578 | + control: Switch { |
579 | + id: passwordVisibleSwitch |
580 | + activeFocusOnPress: false |
581 | + } |
582 | + } |
583 | + |
584 | + Row { |
585 | + |
586 | + anchors { |
587 | + left: parent.left |
588 | + right: parent.right |
589 | + } |
590 | + width: parent.width |
591 | + spacing: units.gu(2) |
592 | + |
593 | + Button { |
594 | + id: cancelButton |
595 | + width: (parent.width / 2) - units.gu(1) |
596 | + text: i18n.tr("Cancel") |
597 | + activeFocusOnPress: false |
598 | + onClicked: PopupUtils.close(hotspotSetupDialog) |
599 | + } |
600 | + |
601 | + Button { |
602 | + id: confirmButton |
603 | + objectName: "confirmButton" |
604 | + width: (parent.width / 2) - units.gu(1) |
605 | + text: hotspotSetupDialog.stored ? i18n.tr("Change") : |
606 | + i18n.tr("Enable") |
607 | + enabled: settingsValid() |
608 | + activeFocusOnPress: false |
609 | + onClicked: { |
610 | + if (hotspotSetupDialog.stored) { |
611 | + changeAction.trigger() |
612 | + } else { |
613 | + enableAction.trigger(); |
614 | + } |
615 | + } |
616 | + |
617 | + Icon { |
618 | + id: successIcon |
619 | + anchors.centerIn: parent |
620 | + height: parent.height - units.gu(1.5) |
621 | + width: parent.height - units.gu(1.5) |
622 | + name: "tick" |
623 | + color: "green" |
624 | + visible: false |
625 | + } |
626 | + |
627 | + ActivityIndicator { |
628 | + id: workingIndicator |
629 | + anchors.centerIn: parent |
630 | + running: false |
631 | + visible: running |
632 | + height: parent.height - units.gu(1.5) |
633 | + } |
634 | + } |
635 | + } |
636 | + } |
637 | + |
638 | + Action { |
639 | + id: enableAction |
640 | + enabled: settingsValid() |
641 | + onTriggered: { |
642 | + hotspotSetupDialog.state = "STARTING"; |
643 | + |
644 | + function hotspotEnabledHandler (enabled) { |
645 | + if (enabled) { |
646 | + hotspotSetupDialog.state = "SUCCEEDED"; |
647 | + hotspotManager.enabledChanged.disconnect( |
648 | + hotspotEnabledHandler); |
649 | + } |
650 | + } |
651 | + |
652 | + hotspotManager.ssid = ssidField.text; |
653 | + hotspotManager.password = passwordField.text; |
654 | + hotspotManager.enabledChanged.connect(hotspotEnabledHandler); |
655 | + hotspotManager.enabled = true; |
656 | + } |
657 | + } |
658 | + |
659 | + Action { |
660 | + id: changeAction |
661 | + enabled: settingsValid() |
662 | + onTriggered: { |
663 | + |
664 | + function hotspotEnabledHandler (enabled) { |
665 | + if (enabled) { |
666 | + hotspotSetupDialog.state = "SUCCEEDED"; |
667 | + hotspotManager.enabledChanged.disconnect( |
668 | + hotspotEnabledHandler); |
669 | + } |
670 | + } |
671 | + |
672 | + function hotspotDisabledHandler (enabled) { |
673 | + if (!enabled) { |
674 | + hotspotManager.enabledChanged.connect(hotspotEnabledHandler); |
675 | + hotspotManager.enabled = true; |
676 | + hotspotManager.enabledChanged.disconnect( |
677 | + hotspotDisabledHandler); |
678 | + } |
679 | + } |
680 | + |
681 | + hotspotManager.ssid = ssidField.text; |
682 | + hotspotManager.password = passwordField.text; |
683 | + |
684 | + if (hotspotManager.enabled) { |
685 | + hotspotSetupDialog.state = "STARTING"; |
686 | + hotspotManager.enabledChanged.connect( |
687 | + hotspotDisabledHandler); |
688 | + hotspotManager.enabled = false; |
689 | + } else { |
690 | + PopupUtils.close(hotspotSetupDialog); |
691 | + } |
692 | + } |
693 | + } |
694 | + |
695 | + /* Timer that shows a tick in the connect button once we have |
696 | + successfully changed/started a hotspot. */ |
697 | + Timer { |
698 | + id: successIndicator |
699 | + interval: 2000 |
700 | + running: false |
701 | + repeat: false |
702 | + onTriggered: PopupUtils.close(hotspotSetupDialog) |
703 | + } |
704 | + |
705 | + Connections { |
706 | + target: hotspotManager |
707 | + |
708 | + onReportError: { |
709 | + if (hotspotSetupDialog.state === "STARTING") { |
710 | + hotspotSetupDialog.state = "FAILED"; |
711 | + feedback.text = common.reasonToString(reason); |
712 | + } |
713 | + } |
714 | } |
715 | } |
716 | } |
717 | |
718 | === modified file 'plugins/cellular/PageComponent.qml' |
719 | --- plugins/cellular/PageComponent.qml 2015-01-30 08:23:57 +0000 |
720 | +++ plugins/cellular/PageComponent.qml 2015-07-31 14:56:39 +0000 |
721 | @@ -121,7 +121,7 @@ |
722 | |
723 | Behavior on opacity { |
724 | PropertyAnimation { |
725 | - duration: UbuntuAnimation.SleepyDuration |
726 | + duration: UbuntuAnimation.SlowDuration |
727 | } |
728 | } |
729 | } |
730 | @@ -143,7 +143,7 @@ |
731 | |
732 | Behavior on opacity { |
733 | PropertyAnimation { |
734 | - duration: UbuntuAnimation.SleepyDuration |
735 | + duration: UbuntuAnimation.SlowDuration |
736 | } |
737 | } |
738 | |
739 | |
740 | === modified file 'plugins/cellular/hotspotmanager.cpp' |
741 | --- plugins/cellular/hotspotmanager.cpp 2014-07-23 13:44:31 +0000 |
742 | +++ plugins/cellular/hotspotmanager.cpp 2015-07-31 14:56:39 +0000 |
743 | @@ -1,8 +1,9 @@ |
744 | /* |
745 | - * Copyright (C) 2014 Canonical, Ltd. |
746 | + * Copyright (C) 2014, 2015 Canonical, Ltd. |
747 | * |
748 | * Authors: |
749 | * Jussi Pakkanen <jussi.pakkanen@canonical.com> |
750 | + * Jonas G. Drange <jonas.drange@canonical.com> |
751 | * |
752 | * This program is free software: you can redistribute it and/or modify it |
753 | * under the terms of the GNU General Public License version 3, as published |
754 | @@ -15,13 +16,9 @@ |
755 | * |
756 | * You should have received a copy of the GNU General Public License |
757 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
758 | - */ |
759 | +*/ |
760 | |
761 | #include "hotspotmanager.h" |
762 | - |
763 | -#include "nm_manager_proxy.h" |
764 | -#include "nm_settings_proxy.h" |
765 | -#include "nm_settings_connection_proxy.h" |
766 | #include <QStringList> |
767 | #include <QDBusReply> |
768 | #include <QtDebug> |
769 | @@ -31,31 +28,156 @@ |
770 | typedef QMap<QString, QVariantMap> nmConnectionArg; |
771 | Q_DECLARE_METATYPE(nmConnectionArg) |
772 | |
773 | -namespace { |
774 | +namespace { // wpa_supplicant interaction |
775 | + |
776 | +const QString wpa_supplicant_service("fi.w1.wpa_supplicant1"); |
777 | +const QString wpa_supplicant_interface("fi.w1.wpa_supplicant1"); |
778 | +const QString wpa_supplicant_path("/fi/w1/wpa_supplicant1"); |
779 | + |
780 | +bool isHybrisWlan() { |
781 | + QString program("getprop"); |
782 | + QStringList arguments; |
783 | + arguments << "urfkill.hybris.wlan"; |
784 | + |
785 | + QProcess *getprop = new QProcess(); |
786 | + getprop->start(program, arguments); |
787 | + |
788 | + if (!getprop->waitForFinished()) { |
789 | + qCritical() << "getprop process failed:" << getprop->errorString(); |
790 | + delete getprop; |
791 | + return false; |
792 | + } |
793 | + |
794 | + int index = getprop->readAllStandardOutput().indexOf("1"); |
795 | + delete getprop; |
796 | + |
797 | + // A non-negative integer means getprop returned 1 |
798 | + return index >= 0; |
799 | +} |
800 | + |
801 | +// True if changed successfully, or there was no need. Otherwise false. |
802 | +// Supported modes are 'p2p', 'sta' and 'ap'. |
803 | +bool changeInterfaceFirmware(const QString interface, const QString mode) { |
804 | + // Not supported. |
805 | + if (mode == "adhoc") { |
806 | + return true; |
807 | + } |
808 | + |
809 | + if (isHybrisWlan()) { |
810 | + QDBusInterface wpasIface ( |
811 | + wpa_supplicant_service, wpa_supplicant_path, wpa_supplicant_interface, |
812 | + QDBusConnection::systemBus()); |
813 | + |
814 | + const QDBusObjectPath interface_path(interface); |
815 | + |
816 | + // TODO(jgdx): We need to guard against calling this |
817 | + // when the interface is not soft blocked. |
818 | + auto set_interface = wpasIface.call("SetInterfaceFirmware", |
819 | + QVariant::fromValue(interface_path), QVariant(mode)); |
820 | + |
821 | + if (set_interface.type() == QDBusMessage::ErrorMessage) { |
822 | + qCritical() << "Failed to change interface firmware:" |
823 | + << set_interface.errorMessage(); |
824 | + return false; |
825 | + } else { |
826 | + return true; |
827 | + } |
828 | + } |
829 | + |
830 | + // We had no need to change the firmware. |
831 | + return true; |
832 | +} |
833 | +} // wpa_supplicant interaction |
834 | + |
835 | +namespace { // UrfKill interaction |
836 | + |
837 | +const QString urfkill_service("org.freedesktop.URfkill"); |
838 | +const QString urfkill_interface("org.freedesktop.URfkill"); |
839 | +const QString urfkill_path("/org/freedesktop/URfkill"); |
840 | + |
841 | +// True if call went through and returned true. |
842 | +bool setWifiBlock(bool block) { |
843 | + QDBusInterface urfkill_dbus_interface(urfkill_service, urfkill_path, |
844 | + urfkill_interface, QDBusConnection::systemBus()); |
845 | + |
846 | + const unsigned int device_type = 1; /* wifi type */ |
847 | + auto reply = urfkill_dbus_interface.call("Block", device_type, block); |
848 | + |
849 | + if (reply.type() == QDBusMessage::ErrorMessage) { |
850 | + qCritical() << "Failed to block wifi" << reply.errorMessage(); |
851 | + return false; |
852 | + } |
853 | + |
854 | + // We got exactly one argument back, and it's not an error. |
855 | + if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().count() == 1) { |
856 | + // returned false from call |
857 | + if (!qdbus_cast<bool>(reply.arguments().at(0))) { |
858 | + qCritical() << "URfkill Block call did not succeed"; |
859 | + return false; |
860 | + } else { |
861 | + return true; |
862 | + } |
863 | + } |
864 | + // We did not get what we wanted from dbus, so we treat that as failure. |
865 | + return false; |
866 | +} |
867 | +} // UrfKill interaction |
868 | + |
869 | +namespace { // NetworkManager interaction |
870 | |
871 | const QString nm_service("org.freedesktop.NetworkManager"); |
872 | +const QString nm_object("/org/freedesktop/NetworkManager"); |
873 | const QString nm_settings_object("/org/freedesktop/NetworkManager/Settings"); |
874 | const QString nm_settings_interface("org.freedesktop.NetworkManager.Settings"); |
875 | const QString nm_connection_interface("org.freedesktop.NetworkManager.Settings.Connection"); |
876 | -const QString nm_dbus_path("/org/freedesktop/NetworkManager"); |
877 | +const QString nm_connection_active_interface("org.freedesktop.NetworkManager.Connection.Active"); |
878 | const QString nm_device_interface("org.freedesktop.NetworkManager.Device"); |
879 | - |
880 | -#define NM_METHOD_NAME "AddAndActivateConnection" |
881 | - |
882 | -void startAdhoc(const QByteArray &ssid, const QString &password, const QDBusObjectPath &devicePath) { |
883 | +const QString dbus_properties_interface("org.freedesktop.DBus.Properties"); |
884 | +const QString connection_property("Connection"); |
885 | + |
886 | +// NetworkManager dbus interface proxy we will use to query |
887 | +// against NetworkManager. See |
888 | +// https://developer.gnome.org/NetworkManager/0.9/spec.html |
889 | +// #org.freedesktop.NetworkManager |
890 | +OrgFreedesktopNetworkManagerInterface nm_manager( |
891 | + nm_service, nm_object, QDBusConnection::systemBus()); |
892 | + |
893 | +// NetworkManager Settings interface proxy we use to get |
894 | +// the list of connections, as well as adding connections. |
895 | +// See https://developer.gnome.org/NetworkManager/0.9/spec.html |
896 | +// #org.freedesktop.NetworkManager.Settings |
897 | +OrgFreedesktopNetworkManagerSettingsInterface nm_settings( |
898 | + nm_service, nm_settings_object, QDBusConnection::systemBus()); |
899 | + |
900 | +// Helper that maps QStrings to other QVariantMaps, i.e. |
901 | +// QMap<QString, QVariantMap>. QVariantMap is an alias for |
902 | +// QMap<QString, QVariant>. |
903 | +// See http://doc.qt.io/qt-5/qvariant.html#QVariantMap-typedef and |
904 | +// https://developer.gnome.org/NetworkManager/0.9/spec.html |
905 | +// #type-String_String_Variant_Map_Map |
906 | +nmConnectionArg createConnectionSettings( |
907 | + const QByteArray &ssid, const QString &password, |
908 | + const QDBusObjectPath &devicePath, QString mode, bool autoConnect = true) { |
909 | + Q_UNUSED(devicePath); |
910 | nmConnectionArg connection; |
911 | |
912 | - QDBusObjectPath specific("/"); |
913 | + QString s_ssid = QString::fromLatin1(ssid); |
914 | + QString s_uuid = QUuid().createUuid().toString(); |
915 | + // Remove {} from the generated uuid. |
916 | + s_uuid.remove(0, 1); |
917 | + s_uuid.remove(s_uuid.size() - 1, 1); |
918 | |
919 | QVariantMap wireless; |
920 | wireless[QStringLiteral("security")] = QVariant(QStringLiteral("802-11-wireless-security")); |
921 | wireless[QStringLiteral("ssid")] = QVariant(ssid); |
922 | - wireless[QStringLiteral("mode")] = QVariant(QStringLiteral("adhoc")); |
923 | + wireless[QStringLiteral("mode")] = QVariant(mode); |
924 | + |
925 | connection["802-11-wireless"] = wireless; |
926 | |
927 | QVariantMap connsettings; |
928 | - connsettings[QStringLiteral("autoconnect")] = QVariant(false); |
929 | - connsettings[QStringLiteral("uuid")] = QVariant(QStringLiteral("aab22b5d-7342-48dc-8920-1b7da31d6829")); |
930 | + connsettings[QStringLiteral("autoconnect")] = QVariant(autoConnect); |
931 | + connsettings[QStringLiteral("id")] = QVariant(s_ssid); |
932 | + connsettings[QStringLiteral("uuid")] = QVariant(s_uuid); |
933 | connsettings[QStringLiteral("type")] = QVariant(QStringLiteral("802-11-wireless")); |
934 | connection["connection"] = connsettings; |
935 | |
936 | @@ -66,6 +188,10 @@ |
937 | ipv4[QStringLiteral("routes")] = QVariant(QStringList()); |
938 | connection["ipv4"] = ipv4; |
939 | |
940 | + QVariantMap ipv6; |
941 | + ipv6[QStringLiteral("method")] = QVariant(QStringLiteral("ignore")); |
942 | + connection["ipv6"] = ipv6; |
943 | + |
944 | QVariantMap security; |
945 | security[QStringLiteral("proto")] = QVariant(QStringList{"rsn"}); |
946 | security[QStringLiteral("pairwise")] = QVariant(QStringList{"ccmp"}); |
947 | @@ -74,176 +200,508 @@ |
948 | security[QStringLiteral("psk")] = QVariant(password); |
949 | connection["802-11-wireless-security"] = security; |
950 | |
951 | - OrgFreedesktopNetworkManagerInterface mgr(nm_service, |
952 | - nm_dbus_path, |
953 | - QDBusConnection::systemBus()); |
954 | - auto reply = mgr.AddAndActivateConnection(connection, devicePath, specific); |
955 | - reply.waitForFinished(); |
956 | - if(!reply.isValid()) { |
957 | - qWarning() << "Failed to start adhoc network: " << reply.error().message() << "\n"; |
958 | - } |
959 | -} |
960 | - |
961 | -bool detectAdhoc(QString &dbusPath, QByteArray &ssid, QString &password, bool &isActive) { |
962 | - static const QString activeIface("org.freedesktop.NetworkManager.Connection.Active"); |
963 | - static const QString connProp("Connection"); |
964 | - OrgFreedesktopNetworkManagerInterface mgr(nm_service, |
965 | - nm_dbus_path, |
966 | - QDBusConnection::systemBus()); |
967 | - auto activeConnections = mgr.activeConnections(); |
968 | - OrgFreedesktopNetworkManagerSettingsInterface settings(nm_service, nm_settings_object, |
969 | - QDBusConnection::systemBus()); |
970 | - QSet<QDBusObjectPath> actives; |
971 | - auto r = settings.ListConnections(); |
972 | - r.waitForFinished(); |
973 | - for(const auto &conn : activeConnections) { |
974 | - QDBusInterface iface(nm_service, conn.path(), "org.freedesktop.DBus.Properties", |
975 | - QDBusConnection::systemBus()); |
976 | - QDBusReply<QVariant> conname = iface.call("Get", activeIface, connProp); |
977 | - if(!conname.isValid()) { |
978 | - qWarning() << "Error getting connamd: " << conname.error().message() << "\n"; |
979 | + return connection; |
980 | +} |
981 | + |
982 | +// Helper that returns a QMap<QString, QVariantMap> given a QDBusObjectPath. |
983 | +// See https://developer.gnome.org/NetworkManager/0.9/spec.html |
984 | +// #org.freedesktop.NetworkManager.Settings.Connection.GetSettings |
985 | +nmConnectionArg getConnectionSettings (QDBusObjectPath connection) { |
986 | + OrgFreedesktopNetworkManagerSettingsConnectionInterface conn( |
987 | + nm_service, connection.path(), QDBusConnection::systemBus()); |
988 | + |
989 | + auto connection_settings = conn.GetSettings(); |
990 | + connection_settings.waitForFinished(); |
991 | + return connection_settings.value(); |
992 | +} |
993 | + |
994 | + |
995 | +// Helper that returns a QMap<QString, QVariantMap> given a QDBusObjectPath. |
996 | +// See https://developer.gnome.org/NetworkManager/0.9/spec.html |
997 | +// #org.freedesktop.NetworkManager.Settings.Connection.GetSettings |
998 | +nmConnectionArg getConnectionSecrets (QDBusObjectPath connection, |
999 | + const QString key) { |
1000 | + OrgFreedesktopNetworkManagerSettingsConnectionInterface conn( |
1001 | + nm_service, connection.path(), QDBusConnection::systemBus()); |
1002 | + auto connection_secrets = conn.GetSecrets(key); |
1003 | + connection_secrets.waitForFinished(); |
1004 | + return connection_secrets.value(); |
1005 | +} |
1006 | + |
1007 | +// Helper that adds a connection and returns the QDBusObjectPath |
1008 | +// of the newly created connection. |
1009 | +// See https://developer.gnome.org/NetworkManager/0.9/spec.html |
1010 | +// #org.freedesktop.NetworkManager.Settings.AddConnection |
1011 | +QDBusObjectPath addConnection( |
1012 | + const QByteArray &ssid, const QString &password, |
1013 | + const QDBusObjectPath &devicePath, QString mode) { |
1014 | + |
1015 | + nmConnectionArg connection = createConnectionSettings(ssid, password, |
1016 | + devicePath, mode); |
1017 | + |
1018 | + auto add_connection_reply = nm_settings.AddConnection(connection); |
1019 | + add_connection_reply.waitForFinished(); |
1020 | + |
1021 | + if(!add_connection_reply.isValid()) { |
1022 | + qCritical() << "Failed to add connection: " |
1023 | + << add_connection_reply.error().message(); |
1024 | + return QDBusObjectPath(); |
1025 | + } |
1026 | + return add_connection_reply.argumentAt<0>(); |
1027 | +} |
1028 | + |
1029 | +// Returns a QDBusObjectPath of a hotspot given a mode. |
1030 | +// Valid modes are 'p2p', 'ap' and 'adhoc'. |
1031 | +QDBusObjectPath getHotspot(QString mode) { |
1032 | + const char wifi_key[] = "802-11-wireless"; |
1033 | + |
1034 | + auto listed_connections = nm_settings.ListConnections(); |
1035 | + listed_connections.waitForFinished(); |
1036 | + |
1037 | + for(const auto &connection : listed_connections.value()) { |
1038 | + OrgFreedesktopNetworkManagerSettingsConnectionInterface conn( |
1039 | + nm_service, connection.path(), QDBusConnection::systemBus()); |
1040 | + |
1041 | + auto connection_settings = getConnectionSettings(connection); |
1042 | + |
1043 | + if(connection_settings.find(wifi_key) != connection_settings.end()) { |
1044 | + auto wifi_setup = connection_settings[wifi_key]; |
1045 | + QString wifi_mode = wifi_setup["mode"].toString(); |
1046 | + |
1047 | + if(wifi_mode == mode) { |
1048 | + return connection; |
1049 | + } |
1050 | + } |
1051 | + } |
1052 | + return QDBusObjectPath(); |
1053 | +} |
1054 | + |
1055 | +// Returns a QDBusObjectPath of a wireless device. For now |
1056 | +// it returns the first device. |
1057 | +QDBusObjectPath getWirelessDevice () { |
1058 | + // find the first wlan adapter for now |
1059 | + auto reply1 = nm_manager.GetDevices(); |
1060 | + reply1.waitForFinished(); |
1061 | + |
1062 | + if(!reply1.isValid()) { |
1063 | + qCritical() << "Could not get network device: " |
1064 | + << reply1.error().message(); |
1065 | + return QDBusObjectPath(); |
1066 | + } |
1067 | + auto devices = reply1.value(); |
1068 | + |
1069 | + QDBusObjectPath dev; |
1070 | + for (const auto &d : devices) { |
1071 | + QDBusInterface iface(nm_service, d.path(), nm_device_interface, |
1072 | + QDBusConnection::systemBus()); |
1073 | + auto type_v = iface.property("DeviceType"); |
1074 | + |
1075 | + if (type_v.toUInt() == 2 /* NM_DEVICE_TYPE_WIFI */) { |
1076 | + return d; |
1077 | + } |
1078 | + } |
1079 | + qCritical() << "Wireless device not found, hotspot functionality is inoperative."; |
1080 | + return dev; |
1081 | +} |
1082 | + |
1083 | +// Helper to check if the hotspot on a given QDBusObjectPath is active |
1084 | +// or not. It checks if the Connection.Active [1] for the given |
1085 | +// path is in NetworkManager's ActiveConnections property [2]. |
1086 | +// [1] https://developer.gnome.org/NetworkManager/0.9/spec.html |
1087 | +// #org.freedesktop.NetworkManager.Connection.Active |
1088 | +// [2] https://developer.gnome.org/NetworkManager/0.9/spec.html |
1089 | +// #org.freedesktop.NetworkManager |
1090 | +bool isHotspotActive (QDBusObjectPath path) { |
1091 | + QSet<QDBusObjectPath> active_relevant_connections; |
1092 | + auto active_connections = nm_manager.activeConnections(); |
1093 | + for(const auto &active_connection : active_connections) { |
1094 | + |
1095 | + // Active connection interface proxy. It might have a connection |
1096 | + // property we can use to deduce if this active connection represents |
1097 | + // our active hotspot. |
1098 | + QDBusInterface active_connection_dbus_interface( |
1099 | + nm_service, active_connection.path(), dbus_properties_interface, |
1100 | + QDBusConnection::systemBus()); |
1101 | + |
1102 | + // Get the Connection property, if any. |
1103 | + QDBusReply<QVariant> connection_property = |
1104 | + active_connection_dbus_interface.call("Get", |
1105 | + nm_connection_active_interface, "Connection"); |
1106 | + |
1107 | + // The Connection property did not exist, so ignore this |
1108 | + // active connection. |
1109 | + if(!connection_property.isValid()) { |
1110 | continue; |
1111 | } |
1112 | - QDBusObjectPath mainConnection = qvariant_cast<QDBusObjectPath>(conname.value()); |
1113 | - actives.insert(mainConnection); |
1114 | - } |
1115 | - const char wifiKey[] = "802-11-wireless"; |
1116 | - for(const auto &i : r.value()) { |
1117 | - OrgFreedesktopNetworkManagerSettingsConnectionInterface conn(nm_service, |
1118 | - i.path(), |
1119 | - QDBusConnection::systemBus()); |
1120 | - auto reply = conn.GetSettings(); |
1121 | - reply.waitForFinished(); |
1122 | - auto s = reply.value(); |
1123 | - if(s.find(wifiKey) != s.end()) { |
1124 | - auto wsetup = s[wifiKey]; |
1125 | - if(wsetup["mode"] == "adhoc") { |
1126 | - dbusPath = i.path(); |
1127 | - ssid = wsetup["ssid"].toByteArray(); |
1128 | - auto pwdReply = conn.GetSecrets("802-11-wireless-security"); |
1129 | - pwdReply.waitForFinished(); |
1130 | - password = pwdReply.value()["802-11-wireless-security"]["psk"].toString(); |
1131 | - isActive = false; |
1132 | - for(const auto &ac : actives) { |
1133 | - if(i == ac) { |
1134 | - isActive = true; |
1135 | - break; |
1136 | - } |
1137 | - } |
1138 | - return true; |
1139 | - } |
1140 | + |
1141 | + // It does exist, cast it to a object path. |
1142 | + QDBusObjectPath connection_path = qvariant_cast<QDBusObjectPath>( |
1143 | + connection_property.value()); |
1144 | + |
1145 | + // The object path is the same as the given hotspot path. |
1146 | + if (path == connection_path) { |
1147 | + return true; |
1148 | } |
1149 | } |
1150 | + |
1151 | + // No active connection had a Connection property equal to the |
1152 | + // given path, so return false. |
1153 | return false; |
1154 | } |
1155 | |
1156 | -QDBusObjectPath detectWirelessDevice() { |
1157 | - OrgFreedesktopNetworkManagerInterface mgr(nm_service, |
1158 | - nm_dbus_path, |
1159 | - QDBusConnection::systemBus()); |
1160 | - auto devices = mgr.GetDevices(); |
1161 | - devices.waitForFinished(); |
1162 | - for(const auto &dpath : devices.value()) { |
1163 | - QDBusInterface iface(nm_service, dpath.path(), "org.freedesktop.DBus.Properties", |
1164 | - QDBusConnection::systemBus()); |
1165 | - QDBusReply<QVariant> typeReply = iface.call("Get", "org.freedesktop.NetworkManager.Device", "DeviceType"); |
1166 | - auto typeInt = qvariant_cast<int>(typeReply.value()); |
1167 | - if(typeInt == 2) { |
1168 | - return dpath; // Assumptions are that there is only one wifi device and it is not hotpluggable. |
1169 | - } |
1170 | - } |
1171 | - qWarning() << "Wireless device not found, hotspot functionality is inoperative.\n"; |
1172 | - return QDBusObjectPath(); |
1173 | -} |
1174 | - |
1175 | std::string generate_password() { |
1176 | static const std::string items("abcdefghijklmnopqrstuvwxyz01234567890"); |
1177 | - const int passwordLength = 8; |
1178 | + const int password_length = 8; |
1179 | std::string result; |
1180 | - for(int i=0; i<passwordLength; i++) { |
1181 | + |
1182 | + for(int i=0; i<password_length; i++) { |
1183 | result.push_back(items[std::rand() % items.length()]); |
1184 | } |
1185 | return result; |
1186 | } |
1187 | - |
1188 | -} |
1189 | - |
1190 | -HotspotManager::HotspotManager(QObject *parent) : QObject(parent), |
1191 | - m_devicePath(detectWirelessDevice()) { |
1192 | +} // NetworkManager interaction |
1193 | + |
1194 | +HotspotManager::HotspotManager(QObject *parent) : |
1195 | + QObject(parent), m_mode("ap"), m_enabled(false), m_stored(false), |
1196 | + m_password(generate_password().c_str()), m_ssid(QByteArray("Ubuntu")), |
1197 | + m_device_path(getWirelessDevice()) { |
1198 | static bool isRegistered = false; |
1199 | + |
1200 | if(!isRegistered) { |
1201 | qDBusRegisterMetaType<nmConnectionArg>(); |
1202 | isRegistered = true; |
1203 | } |
1204 | - if(!detectAdhoc(m_settingsPath, m_ssid, m_password, m_isActive)) { |
1205 | - m_settingsPath = ""; |
1206 | - m_ssid = "Ubuntu hotspot"; |
1207 | - m_password = generate_password().c_str(); |
1208 | - m_isActive = false; |
1209 | - } |
1210 | -} |
1211 | - |
1212 | -QByteArray HotspotManager::getHotspotName() { |
1213 | + |
1214 | + // Stored is false if hotspot path is empty. |
1215 | + m_hotspot_path = getHotspot(m_mode); |
1216 | + setStored(!m_hotspot_path.path().isEmpty()); |
1217 | + |
1218 | + if (m_stored) { |
1219 | + updateSettingsFromDbus(m_hotspot_path); |
1220 | + } |
1221 | + |
1222 | + // Watches for new connections added to NetworkManager's Settings |
1223 | + // interface. |
1224 | + nm_settings.connection().connect( |
1225 | + nm_settings.service(), nm_settings_object, nm_settings_interface, |
1226 | + "NewConnection", this, SLOT(onNewConnection(QDBusObjectPath))); |
1227 | + |
1228 | + // Watches changes in NetworkManager. |
1229 | + nm_manager.connection().connect( |
1230 | + nm_manager.service(), nm_object, nm_service, "PropertiesChanged", this, |
1231 | + SLOT(onPropertiesChanged(QMap<QString, QVariant>))); |
1232 | +} |
1233 | + |
1234 | +void HotspotManager::setEnabled(bool value) { |
1235 | + bool blocked = setWifiBlock(true); |
1236 | + |
1237 | + // Failed to soft block, here we revert the enabled setting. |
1238 | + if (!blocked) { |
1239 | + // "The device could not be readied for configuration" |
1240 | + Q_EMIT reportError(5); |
1241 | + setEnable(false); |
1242 | + return; |
1243 | + } |
1244 | + |
1245 | + // We are enabling a hotspot |
1246 | + if (value) { |
1247 | + // If the SSID is empty, we report an error. |
1248 | + if (m_ssid.isEmpty()) { |
1249 | + Q_EMIT reportError(1); |
1250 | + setEnable(false); |
1251 | + return; |
1252 | + } |
1253 | + |
1254 | + bool changed = changeInterfaceFirmware("/", m_mode); |
1255 | + if (!changed) { |
1256 | + // Necessary firmware for the device may be missing |
1257 | + Q_EMIT reportError(35); |
1258 | + setEnable(false); |
1259 | + setWifiBlock(false); |
1260 | + return; |
1261 | + } |
1262 | + |
1263 | + if (m_stored) { |
1264 | + // we defer enabling until old hotspot is deleted |
1265 | + // if we can delete the old one |
1266 | + // If not, unset stored flag and call this method. |
1267 | + if (!destroy(m_hotspot_path)) { |
1268 | + setStored(false); |
1269 | + setEnabled(true); |
1270 | + setEnable(true); |
1271 | + } |
1272 | + } else { |
1273 | + // we defer enabling until new hotspot is created |
1274 | + m_hotspot_path = addConnection(m_ssid, m_password, m_device_path, m_mode); |
1275 | + if (m_hotspot_path.path().isEmpty()) { |
1276 | + // Emit "Unknown Error". |
1277 | + Q_EMIT reportError(0); |
1278 | + setEnable(false); |
1279 | + setWifiBlock(false); |
1280 | + } |
1281 | + } |
1282 | + |
1283 | + } else { |
1284 | + // Disabling the hotspot. |
1285 | + disable(); |
1286 | + setEnable(false); |
1287 | + |
1288 | + bool unblocked = setWifiBlock(false); |
1289 | + if (!unblocked) { |
1290 | + // "The device could not be readied for configuration" |
1291 | + Q_EMIT reportError(5); |
1292 | + } |
1293 | + } |
1294 | +} |
1295 | + |
1296 | +// Disables a hotspot. |
1297 | +void HotspotManager::disable() { |
1298 | + QDBusObjectPath hotspot = getHotspot(m_mode); |
1299 | + if (hotspot.path().isEmpty()) { |
1300 | + qWarning() << "Could not find a hotspot setup to disable.\n"; |
1301 | + return; |
1302 | + } |
1303 | + |
1304 | + // Create Connection.Settings proxy for hotspot |
1305 | + OrgFreedesktopNetworkManagerSettingsConnectionInterface conn( |
1306 | + nm_service, hotspot.path(), QDBusConnection::systemBus()); |
1307 | + |
1308 | + // Get new settings |
1309 | + nmConnectionArg new_settings = createConnectionSettings(m_ssid, m_password, |
1310 | + m_device_path, m_mode, false); |
1311 | + auto updating = conn.Update(new_settings); |
1312 | + updating.waitForFinished(); |
1313 | + if(!updating.isValid()) { |
1314 | + qCritical() << "Could not update connection with autoconnect=false: " |
1315 | + << updating.error().message(); |
1316 | + } |
1317 | + |
1318 | + auto active_connections = nm_manager.activeConnections(); |
1319 | + for(const auto &active_connection : active_connections) { |
1320 | + // DBus Properties interface proxy of the active connection. We use |
1321 | + // this proxy to get to the Connection property. |
1322 | + QDBusInterface iface( |
1323 | + nm_service, active_connection.path(), |
1324 | + "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); |
1325 | + |
1326 | + // Call to get the Connection property. |
1327 | + QDBusReply<QVariant> conname = iface.call( |
1328 | + "Get", nm_connection_active_interface, "Connection"); |
1329 | + |
1330 | + // Cast the property to a object path. |
1331 | + QDBusObjectPath backingConnection = qvariant_cast<QDBusObjectPath>( |
1332 | + conname.value()); |
1333 | + |
1334 | + // This active connection's Connection property was our hotspot, |
1335 | + // so we will deactivate it. Note that we do not remove the hotspot, |
1336 | + // as we are storing the ssid, password and mode on the connection. |
1337 | + if(backingConnection == m_hotspot_path) { |
1338 | + // Deactivate the connection. |
1339 | + auto deactivation = nm_manager.DeactivateConnection(active_connection); |
1340 | + deactivation.waitForFinished(); |
1341 | + if(!deactivation.isValid()) { |
1342 | + qCritical() << "Could not get deactivate connection: " |
1343 | + << deactivation.error().message(); |
1344 | + } |
1345 | + return; |
1346 | + } |
1347 | + } |
1348 | + return; |
1349 | +} |
1350 | + |
1351 | +bool HotspotManager::enabled() const { |
1352 | + return m_enabled; |
1353 | +} |
1354 | + |
1355 | +void HotspotManager::setEnable(bool value) { |
1356 | + if (m_enabled != value) { |
1357 | + m_enabled = value; |
1358 | + Q_EMIT enabledChanged(value); |
1359 | + } |
1360 | +} |
1361 | + |
1362 | +bool HotspotManager::stored() const { |
1363 | + return m_stored; |
1364 | +} |
1365 | + |
1366 | +void HotspotManager::setStored(bool value) { |
1367 | + if (m_stored != value) { |
1368 | + m_stored = value; |
1369 | + Q_EMIT storedChanged(value); |
1370 | + } |
1371 | +} |
1372 | + |
1373 | +QByteArray HotspotManager::ssid() const { |
1374 | return m_ssid; |
1375 | } |
1376 | |
1377 | -QString HotspotManager::getHotspotPassword() { |
1378 | +void HotspotManager::setSsid(QByteArray value) { |
1379 | + if (m_ssid != value) { |
1380 | + m_ssid = value; |
1381 | + Q_EMIT ssidChanged(value); |
1382 | + } |
1383 | +} |
1384 | + |
1385 | +QString HotspotManager::password() const { |
1386 | return m_password; |
1387 | } |
1388 | |
1389 | -void HotspotManager::setupHotspot(QByteArray ssid_, QString password_) { |
1390 | - m_ssid = ssid_; |
1391 | - m_password = password_; |
1392 | -} |
1393 | - |
1394 | -void HotspotManager::enableHotspot() { |
1395 | - if(!m_settingsPath.isEmpty()) { |
1396 | - // Prints a warning message if the connection has disappeared already. |
1397 | - destroyHotspot(); |
1398 | - // NM returns from the dbus call immediately but only destroys the |
1399 | - // connection some time later. There is no callback for when this happens. |
1400 | - // So this is the best we can do with reasonable effort. |
1401 | - QThread::sleep(1); |
1402 | - } |
1403 | - startAdhoc(m_ssid, m_password, m_devicePath); |
1404 | - detectAdhoc(m_settingsPath, m_ssid, m_password, m_isActive); |
1405 | -} |
1406 | - |
1407 | -bool HotspotManager::isHotspotActive() { |
1408 | - return m_isActive; |
1409 | -} |
1410 | - |
1411 | -void HotspotManager::disableHotspot() { |
1412 | - static const QString activeIface("org.freedesktop.NetworkManager.Connection.Active"); |
1413 | - static const QString connProp("Connection"); |
1414 | - OrgFreedesktopNetworkManagerInterface mgr(nm_service, |
1415 | - nm_dbus_path, |
1416 | - QDBusConnection::systemBus()); |
1417 | - auto activeConnections = mgr.activeConnections(); |
1418 | - for(const auto &aConn : activeConnections) { |
1419 | - QDBusInterface iface(nm_service, aConn.path(), "org.freedesktop.DBus.Properties", |
1420 | - QDBusConnection::systemBus()); |
1421 | - QDBusReply<QVariant> conname = iface.call("Get", activeIface, connProp); |
1422 | - QDBusObjectPath backingConnection = qvariant_cast<QDBusObjectPath>(conname.value()); |
1423 | - if(backingConnection.path() == m_settingsPath) { |
1424 | - mgr.DeactivateConnection(aConn); |
1425 | - return; |
1426 | +void HotspotManager::setPassword(QString value) { |
1427 | + if (m_password != value) { |
1428 | + m_password = value; |
1429 | + Q_EMIT passwordChanged(value); |
1430 | + } |
1431 | +} |
1432 | + |
1433 | +QString HotspotManager::mode() const { |
1434 | + return m_mode; |
1435 | +} |
1436 | + |
1437 | +void HotspotManager::setMode(QString value) { |
1438 | + if (m_mode != value) { |
1439 | + m_mode = value; |
1440 | + Q_EMIT modeChanged(value); |
1441 | + } |
1442 | +} |
1443 | + |
1444 | +void HotspotManager::onNewConnection(QDBusObjectPath path) { |
1445 | + // The new connection is the same as the stored hotspot path. |
1446 | + if (path == m_hotspot_path) { |
1447 | + // If a new hotspot was added, it is also given that |
1448 | + // Wi-Fi has been soft blocked. We can now unblock Wi-Fi |
1449 | + // and let NetworkManager pick up the newly created |
1450 | + // connection. |
1451 | + bool unblocked = setWifiBlock(false); |
1452 | + if (!unblocked) { |
1453 | + // "The device could not be readied for configuration" |
1454 | + Q_EMIT reportError(5); |
1455 | + } else { |
1456 | + // We successfully unblocked the Wi-Fi, so set m_enable to true. |
1457 | + setEnable(true); |
1458 | } |
1459 | - } |
1460 | - qWarning() << "Could not find a hotspot setup to disable.\n"; |
1461 | -} |
1462 | - |
1463 | -void HotspotManager::destroyHotspot() { |
1464 | - if(m_settingsPath.isEmpty()) { |
1465 | - qWarning() << "Tried to destroy nonexisting hotspot.\n"; |
1466 | + |
1467 | + // This also mean we have successfully created a hotspot connection |
1468 | + // object in NetworkManager, so m_stored should now be true. |
1469 | + setStored(true); |
1470 | + } |
1471 | +} |
1472 | + |
1473 | + |
1474 | +bool HotspotManager::destroy(QDBusObjectPath path) { |
1475 | + if(path.path().isEmpty()) { |
1476 | + return false; |
1477 | + } |
1478 | + |
1479 | + // Connection Settings interface proxy for the connection |
1480 | + // we are about to delete. |
1481 | + OrgFreedesktopNetworkManagerSettingsConnectionInterface conn( |
1482 | + nm_service, path.path(), QDBusConnection::systemBus()); |
1483 | + |
1484 | + // Subscribe to the connection proxy's Removed signal. |
1485 | + conn.connection().connect(conn.service(), path.path(), conn.interface(), |
1486 | + "Removed", this, SLOT(onRemoved())); |
1487 | + |
1488 | + auto del = conn.Delete(); |
1489 | + del.waitForFinished(); |
1490 | + return del.isValid(); |
1491 | +} |
1492 | + |
1493 | +void HotspotManager::onRemoved() { |
1494 | + // The UI does not support direct deletion of a hotspot, and given how |
1495 | + // hotspots currently work, every time we want to re-use a hotspot, we |
1496 | + // delete it an add a new one. |
1497 | + // Thus, if a hotspot was deleted, we now create a new one. |
1498 | + m_hotspot_path = addConnection(m_ssid, m_password, m_device_path, m_mode); |
1499 | + |
1500 | + // We could not add a connection, so report, disable and unblock. |
1501 | + if (m_hotspot_path.path().isEmpty()) { |
1502 | + // Emit "Unknown Error". |
1503 | + Q_EMIT reportError(0); |
1504 | + setEnable(false); |
1505 | + setWifiBlock(false); |
1506 | + } |
1507 | +} |
1508 | + |
1509 | +void HotspotManager::onPropertiesChanged(QVariantMap properties) { |
1510 | + // If we have no hotspot path, ignore changes in NetworkManager. |
1511 | + if (m_hotspot_path.path().isEmpty()) { |
1512 | return; |
1513 | } |
1514 | - QDBusInterface control(nm_service, m_settingsPath, nm_connection_interface, |
1515 | - QDBusConnection::systemBus()); |
1516 | - QDBusReply<void> reply = control.call("Delete"); |
1517 | - if(!reply.isValid()) { |
1518 | - qWarning() << "Could not disconnect adhoc network: " << reply.error().message() << "\n"; |
1519 | - } else { |
1520 | - m_isActive = false; |
1521 | + |
1522 | + // Set flag so we know that ActiveConnections changed. |
1523 | + bool active_connection_changed = false; |
1524 | + |
1525 | + for(QVariantMap::const_iterator iter = properties.begin(); iter != properties.end(); ++iter) { |
1526 | + if (iter.key() == "ActiveConnections") { |
1527 | + active_connection_changed = true; |
1528 | + |
1529 | + const QDBusArgument args = qvariant_cast<QDBusArgument>(iter.value()); |
1530 | + if (args.currentType() == QDBusArgument::ArrayType) { |
1531 | + args.beginArray(); |
1532 | + |
1533 | + while (!args.atEnd()) { |
1534 | + QDBusObjectPath path = qdbus_cast<QDBusObjectPath>(args); |
1535 | + |
1536 | + QDBusInterface active_connection_dbus_interface( |
1537 | + nm_service, path.path(), dbus_properties_interface, |
1538 | + QDBusConnection::systemBus()); |
1539 | + |
1540 | + QDBusReply<QVariant> connection_property = active_connection_dbus_interface.call( |
1541 | + "Get", nm_connection_active_interface, "Connection"); |
1542 | + |
1543 | + // Active connection did not have a connection property, |
1544 | + // so ignore it. |
1545 | + if(!connection_property.isValid()) { |
1546 | + continue; |
1547 | + } |
1548 | + |
1549 | + QDBusObjectPath connection_path = qvariant_cast<QDBusObjectPath>( |
1550 | + connection_property.value()); |
1551 | + |
1552 | + // We see our connection as being active, so we emit that is |
1553 | + // enabled and return. |
1554 | + if (connection_path == m_hotspot_path) { |
1555 | + setEnable(true); |
1556 | + return; |
1557 | + } |
1558 | + } |
1559 | + args.endArray(); |
1560 | + } |
1561 | + } |
1562 | + } |
1563 | + |
1564 | + // At this point ActiveConnections changed, but |
1565 | + // our hotspot was not in that list. |
1566 | + if (active_connection_changed) { |
1567 | + setEnable(false); |
1568 | + } |
1569 | +} |
1570 | + |
1571 | +void HotspotManager::updateSettingsFromDbus(QDBusObjectPath path) { |
1572 | + setEnable(isHotspotActive(m_hotspot_path)); |
1573 | + |
1574 | + nmConnectionArg settings = getConnectionSettings(path); |
1575 | + const char wifi_key[] = "802-11-wireless"; |
1576 | + const char security_key[] = "802-11-wireless-security"; |
1577 | + |
1578 | + if (settings.find(wifi_key) != settings.end()) { |
1579 | + QByteArray ssid = settings[wifi_key]["ssid"].toByteArray(); |
1580 | + if (!ssid.isEmpty()) { |
1581 | + setSsid(ssid); |
1582 | + } |
1583 | + |
1584 | + QString mode = settings[wifi_key]["mode"].toString(); |
1585 | + if (!mode.isEmpty()) { |
1586 | + setMode(mode); |
1587 | + } |
1588 | + } |
1589 | + |
1590 | + nmConnectionArg secrets = getConnectionSecrets(path, security_key); |
1591 | + |
1592 | + if (secrets.find(security_key) != secrets.end()) { |
1593 | + QString pwd = secrets[security_key]["psk"].toString(); |
1594 | + if (!pwd.isEmpty()) { |
1595 | + setPassword(pwd); |
1596 | + } |
1597 | } |
1598 | } |
1599 | |
1600 | === modified file 'plugins/cellular/hotspotmanager.h' |
1601 | --- plugins/cellular/hotspotmanager.h 2014-07-23 13:44:31 +0000 |
1602 | +++ plugins/cellular/hotspotmanager.h 2015-07-31 14:56:39 +0000 |
1603 | @@ -1,8 +1,9 @@ |
1604 | /* |
1605 | - * Copyright (C) 2014 Canonical, Ltd. |
1606 | + * Copyright (C) 2014, 2015 Canonical, Ltd. |
1607 | * |
1608 | * Authors: |
1609 | * Jussi Pakkanen <jussi.pakkanen@canonical.com> |
1610 | + * Jonas G. Drange <jonas.drange@canonical.com> |
1611 | * |
1612 | * This program is free software: you can redistribute it and/or modify it |
1613 | * under the terms of the GNU General Public License version 3, as published |
1614 | @@ -15,39 +16,166 @@ |
1615 | * |
1616 | * You should have received a copy of the GNU General Public License |
1617 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1618 | - */ |
1619 | + * |
1620 | + * |
1621 | + * HotspotManager API |
1622 | + * ============================== |
1623 | + * |
1624 | + * Methods |
1625 | + * |
1626 | + * Signals |
1627 | + * enabledChanged(bool enabled) |
1628 | + * Signal that gets emitted when the hotspot is disabled or enabled. |
1629 | + * |
1630 | + * storedChanged(bool stored) |
1631 | + * Signal that gets emitted when a hotspot was stored. |
1632 | + * |
1633 | + * ssidChanged(QByteArray ssid) |
1634 | + * Signal that gets emitted when the ssid of the hotspot was changed. |
1635 | + * |
1636 | + * passwordChanged(QString password) |
1637 | + * Signal that gets emitted when the password of the hotspot was changed. |
1638 | + * |
1639 | + * modeChanged(QString mode) |
1640 | + * Signal that gets emitted when the mode was changed. |
1641 | + * |
1642 | + * authChanged(QString auth) |
1643 | + * |
1644 | + * Note that none of these signal will be emitted if a change to the hotspot |
1645 | + * was made by anyone else than the HotspotManager. Right now, the canonical |
1646 | + * way to edit a hotspot, is through the hotspot manager. |
1647 | + * |
1648 | + * |
1649 | + * reportError(int reason) |
1650 | + * The reasons correspond to https://developer.gnome.org/ |
1651 | + * NetworkManager/0.9/spec.html#type-NM_DEVICE_STATE_REASON |
1652 | + * |
1653 | + * Properties |
1654 | + * bool enabled [readwrite] |
1655 | + * Whether or not the hotspot is enabled. |
1656 | + * |
1657 | + * bool stored [readonly] |
1658 | + * Whether or not a hotspot is known to the hotspotmanager. |
1659 | + * |
1660 | + * QByteArray ssid [readwrite] |
1661 | + * The current SSID of the hotspot. |
1662 | + * |
1663 | + * QString auth [readwrite] |
1664 | + * The current authentication of the hotspot. The default for this property |
1665 | + * is "wpa-psk" and is currently the only supported scheme. WEP is unsupported |
1666 | + * by design, as is no scheme at all. |
1667 | + * |
1668 | + * TODO: Check/add support for wpa-eap |
1669 | + * |
1670 | + * QString password [readwrite] |
1671 | + * The current Pre-Shared-Key for the hotspot. If the key is 64-characters |
1672 | + * long, it must contain only hexadecimal characters and is interpreted as a |
1673 | + * hexadecimal WPA key. Otherwise, the key must be between 8 and 63 ASCII |
1674 | + * characters and is interpreted as a WPA passphrase. |
1675 | + * |
1676 | + * QString mode [readwrite, optional] |
1677 | + * The current hotspot mode. The default of this value is "ap", but can be |
1678 | + * set to "p2p" or "adhoc". "p2p" and "adhoc" is currently not fully supported. |
1679 | + * |
1680 | + * TODO: Complete support for adhoc and p2p modes. |
1681 | +*/ |
1682 | |
1683 | -#ifndef CELLULAR_DBUS_HELPER |
1684 | -#define CELLULAR_DBUS_HELPER |
1685 | +#ifndef HOTSPOTMANAGER_H |
1686 | +#define HOTSPOTMANAGER_H |
1687 | |
1688 | #include <QObject> |
1689 | #include <QDBusObjectPath> |
1690 | -/** |
1691 | - * For exposing dbus data to Qml. |
1692 | - */ |
1693 | +#include <QUuid> |
1694 | + |
1695 | +#include "nm_manager_proxy.h" |
1696 | +#include "nm_settings_proxy.h" |
1697 | +#include "nm_settings_connection_proxy.h" |
1698 | |
1699 | class HotspotManager : public QObject { |
1700 | Q_OBJECT |
1701 | + Q_PROPERTY( bool enabled |
1702 | + READ enabled |
1703 | + WRITE setEnabled |
1704 | + NOTIFY enabledChanged) |
1705 | + Q_PROPERTY( QByteArray ssid |
1706 | + READ ssid |
1707 | + WRITE setSsid |
1708 | + NOTIFY ssidChanged) |
1709 | + Q_PROPERTY( QString auth |
1710 | + READ auth |
1711 | + WRITE setAuth |
1712 | + NOTIFY authChanged) |
1713 | + Q_PROPERTY( QString password |
1714 | + READ password |
1715 | + WRITE setPassword |
1716 | + NOTIFY passwordChanged) |
1717 | + Q_PROPERTY( QString mode |
1718 | + READ mode |
1719 | + WRITE setMode |
1720 | + NOTIFY modeChanged) |
1721 | + Q_PROPERTY( bool stored |
1722 | + READ stored |
1723 | + NOTIFY storedChanged) |
1724 | |
1725 | public: |
1726 | explicit HotspotManager(QObject *parent = nullptr); |
1727 | ~HotspotManager() {}; |
1728 | |
1729 | - Q_INVOKABLE QByteArray getHotspotName(); |
1730 | - Q_INVOKABLE QString getHotspotPassword(); |
1731 | - Q_INVOKABLE void setupHotspot(QByteArray ssid, QString password); |
1732 | - Q_INVOKABLE bool isHotspotActive(); |
1733 | - Q_INVOKABLE void enableHotspot(); |
1734 | - Q_INVOKABLE void disableHotspot(); |
1735 | - void destroyHotspot(); |
1736 | + bool enabled() const; |
1737 | + void setEnabled(bool); |
1738 | + bool stored() const; |
1739 | + |
1740 | + QByteArray ssid() const; |
1741 | + void setSsid(QByteArray); |
1742 | + |
1743 | + QString password() const; |
1744 | + void setPassword(QString); |
1745 | + |
1746 | + QString mode() const; |
1747 | + void setMode(QString); |
1748 | + |
1749 | + QString auth() const; |
1750 | + void setAuth(QString); |
1751 | + |
1752 | +Q_SIGNALS: |
1753 | + void enabledChanged(bool enabled); |
1754 | + void storedChanged(bool stored); |
1755 | + void ssidChanged(const QByteArray ssid); |
1756 | + void passwordChanged(const QString password); |
1757 | + void modeChanged(const QString mode); |
1758 | + void authChanged(const QString auth); |
1759 | + |
1760 | + /* |
1761 | + The mapping of code to string is taken from |
1762 | + http://bazaar.launchpad.net/~vcs-imports/ |
1763 | + network-manager/trunk/view/head:/cli/src/common.c |
1764 | + |
1765 | + NetworkManager documentation: https://developer.gnome.org/ |
1766 | + NetworkManager/0.9/spec.html#type-NM_DEVICE_STATE_REASON |
1767 | + */ |
1768 | + void reportError(const int &reason); |
1769 | + |
1770 | +public Q_SLOTS: |
1771 | + void onNewConnection(const QDBusObjectPath); |
1772 | + void onRemoved(); |
1773 | + void onPropertiesChanged(const QVariantMap); |
1774 | |
1775 | private: |
1776 | + QString m_mode; |
1777 | + bool m_enabled; |
1778 | + bool m_stored; |
1779 | + QString m_password; |
1780 | QByteArray m_ssid; |
1781 | - QString m_password; |
1782 | - QString m_settingsPath; |
1783 | - QDBusObjectPath m_devicePath; |
1784 | - bool m_isActive; |
1785 | + QDBusObjectPath m_device_path; |
1786 | + QDBusObjectPath m_hotspot_path; |
1787 | + |
1788 | + void disable(); |
1789 | + |
1790 | + bool destroy(QDBusObjectPath); |
1791 | + |
1792 | + void setStored(bool); |
1793 | + void setEnable(bool); |
1794 | + void updateSettingsFromDbus(QDBusObjectPath); |
1795 | }; |
1796 | |
1797 | - |
1798 | #endif |
1799 | |
1800 | === modified file 'tests/autopilot/ubuntu_system_settings/__init__.py' |
1801 | --- tests/autopilot/ubuntu_system_settings/__init__.py 2015-07-29 18:06:14 +0000 |
1802 | +++ tests/autopilot/ubuntu_system_settings/__init__.py 2015-07-31 14:56:39 +0000 |
1803 | @@ -309,6 +309,138 @@ |
1804 | field.write(name) |
1805 | self.pointing_device.click_object(ok) |
1806 | |
1807 | + """ |
1808 | + :returns: Whether or not hotspot can be used. |
1809 | + """ |
1810 | + @autopilot.logging.log_action(logger.debug) |
1811 | + def have_hotspot(self): |
1812 | + return self.wait_select_single(objectName='hotspotEntry').visible |
1813 | + |
1814 | + """ |
1815 | + :param: Configuration with keys ssid and password. |
1816 | + :returns: Hotspot page. |
1817 | + """ |
1818 | + @autopilot.logging.log_action(logger.debug) |
1819 | + def setup_hotspot(self, config=None): |
1820 | + hotspot_page = self._enter_hotspot() |
1821 | + hotspot_page.setup_hotspot(config) |
1822 | + return hotspot_page |
1823 | + |
1824 | + """ |
1825 | + Enables hotspot. |
1826 | + :returns: Hotspot page. |
1827 | + """ |
1828 | + @autopilot.logging.log_action(logger.debug) |
1829 | + def enable_hotspot(self): |
1830 | + hotspot_page = self._enter_hotspot() |
1831 | + hotspot_page.enable_hotspot() |
1832 | + return hotspot_page |
1833 | + |
1834 | + """ |
1835 | + Disables hotspot. |
1836 | + :returns: Hotspot page. |
1837 | + """ |
1838 | + @autopilot.logging.log_action(logger.debug) |
1839 | + def disable_hotspot(self): |
1840 | + hotspot_page = self._enter_hotspot() |
1841 | + hotspot_page.disable_hotspot() |
1842 | + return hotspot_page |
1843 | + |
1844 | + @autopilot.logging.log_action(logger.debug) |
1845 | + def _enter_hotspot(self): |
1846 | + obj = self.wait_select_single(objectName="hotspotEntry") |
1847 | + self.pointing_device.click_object(obj) |
1848 | + return self.get_root_instance().wait_select_single( |
1849 | + objectName='hotspotPage') |
1850 | + |
1851 | + |
1852 | +class Hotspot(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase): |
1853 | + |
1854 | + """Autopilot helper for Hotspot page.""" |
1855 | + |
1856 | + @classmethod |
1857 | + def validate_dbus_object(cls, path, state): |
1858 | + name = introspection.get_classname_from_path(path) |
1859 | + if name == b'ItemPage': |
1860 | + if state['objectName'][1] == 'hotspotPage': |
1861 | + return True |
1862 | + return False |
1863 | + |
1864 | + @property |
1865 | + def _switch(self): |
1866 | + return self.wait_select_single( |
1867 | + ubuntuuitoolkit.CheckBox, |
1868 | + objectName='hotspotSwitch') |
1869 | + |
1870 | + @autopilot.logging.log_action(logger.debug) |
1871 | + def enable_hotspot(self): |
1872 | + self._switch.check() |
1873 | + |
1874 | + @autopilot.logging.log_action(logger.debug) |
1875 | + def disable_hotspot(self): |
1876 | + self._switch.uncheck() |
1877 | + |
1878 | + @autopilot.logging.log_action(logger.debug) |
1879 | + def setup_hotspot(self, config): |
1880 | + obj = self.select_single(objectName='hotspotSetupEntry') |
1881 | + self.pointing_device.click_object(obj) |
1882 | + setup = self.get_root_instance().wait_select_single( |
1883 | + objectName='hotspotSetup') |
1884 | + if config: |
1885 | + if 'ssid' in config: |
1886 | + setup.set_ssid(config['ssid']) |
1887 | + if 'password' in config: |
1888 | + setup.set_password(config['password']) |
1889 | + setup.enable() |
1890 | + if setup: |
1891 | + setup.wait_until_destroyed() |
1892 | + |
1893 | + @autopilot.logging.log_action(logger.debug) |
1894 | + def get_hotspot_status(self): |
1895 | + return self._switch.checked |
1896 | + |
1897 | + |
1898 | +class HotspotSetup(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase): |
1899 | + |
1900 | + """Autopilot helper for Hotspot setup.""" |
1901 | + |
1902 | + @classmethod |
1903 | + def validate_dbus_object(cls, path, state): |
1904 | + name = introspection.get_classname_from_path(path) |
1905 | + if name == b'Dialog': |
1906 | + if state['objectName'][1] == 'hotspotSetup': |
1907 | + return True |
1908 | + return False |
1909 | + |
1910 | + @property |
1911 | + def _ssid_field(self): |
1912 | + return self.wait_select_single( |
1913 | + ubuntuuitoolkit.TextField, |
1914 | + objectName='ssidField') |
1915 | + |
1916 | + @property |
1917 | + def _password_field(self): |
1918 | + return self.wait_select_single( |
1919 | + ubuntuuitoolkit.TextField, |
1920 | + objectName='passwordField') |
1921 | + |
1922 | + @property |
1923 | + def _enable_button(self): |
1924 | + return self.wait_select_single( |
1925 | + 'Button', objectName='confirmButton') |
1926 | + |
1927 | + @autopilot.logging.log_action(logger.debug) |
1928 | + def set_ssid(self, ssid): |
1929 | + self._ssid_field.write(ssid) |
1930 | + |
1931 | + @autopilot.logging.log_action(logger.debug) |
1932 | + def set_password(self, password): |
1933 | + self._password_field.write(password) |
1934 | + |
1935 | + @autopilot.logging.log_action(logger.debug) |
1936 | + def enable(self): |
1937 | + self.pointing_device.click_object(self._enable_button) |
1938 | + |
1939 | |
1940 | class BluetoothPage(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase): |
1941 | |
1942 | |
1943 | === modified file 'tests/autopilot/ubuntu_system_settings/tests/__init__.py' |
1944 | --- tests/autopilot/ubuntu_system_settings/tests/__init__.py 2015-07-25 01:23:51 +0000 |
1945 | +++ tests/autopilot/ubuntu_system_settings/tests/__init__.py 2015-07-31 14:56:39 +0000 |
1946 | @@ -34,7 +34,6 @@ |
1947 | from gi.repository import UPowerGlib |
1948 | from testtools.matchers import Equals, NotEquals, GreaterThan |
1949 | |
1950 | - |
1951 | ACCOUNTS_IFACE = 'org.freedesktop.Accounts' |
1952 | ACCOUNTS_USER_IFACE = 'org.freedesktop.Accounts.User' |
1953 | ACCOUNTS_OBJ = '/org/freedesktop/Accounts' |
1954 | @@ -389,6 +388,68 @@ |
1955 | self.cellular_page = self.main_view.go_to_cellular_page() |
1956 | |
1957 | |
1958 | +class HotspotBaseTestCase(CellularBaseTestCase): |
1959 | + |
1960 | + @classmethod |
1961 | + def setUpClass(cls): |
1962 | + super(HotspotBaseTestCase, cls).setUpClass() |
1963 | + nm_tmpl = os.path.join(os.path.dirname(__file__), 'networkmanager.py') |
1964 | + (cls.n_mock, cls.obj_nm) = cls.spawn_server_template( |
1965 | + nm_tmpl, stdout=subprocess.PIPE) |
1966 | + (cls.u_mock, cls.obj_urf) = cls.spawn_server_template( |
1967 | + 'urfkill', stdout=subprocess.PIPE) |
1968 | + |
1969 | + @classmethod |
1970 | + def tearDownClass(cls): |
1971 | + cls.n_mock.terminate() |
1972 | + cls.n_mock.wait() |
1973 | + cls.u_mock.terminate() |
1974 | + cls.u_mock.wait() |
1975 | + super(HotspotBaseTestCase, cls).tearDownClass() |
1976 | + |
1977 | + def tearDown(self): |
1978 | + self.obj_nm.Reset() |
1979 | + self.urfkill_mock.ClearCalls() |
1980 | + super(HotspotBaseTestCase, self).tearDown() |
1981 | + |
1982 | + def setUp(self): |
1983 | + self.patch_environment("USS_SHOW_ALL_UI", "1") |
1984 | + self.nm_mock = dbus.Interface(self.obj_nm, dbusmock.MOCK_IFACE) |
1985 | + self.device_path = self.obj_nm.AddWiFiDevice('test0', 'Barbaz', 1) |
1986 | + self.device_mock = dbus.Interface(self.dbus_con.get_object( |
1987 | + NM_SERVICE, self.device_path), |
1988 | + 'org.freedesktop.DBus.Properties') |
1989 | + self.urfkill_mock = dbus.Interface(self.obj_urf, dbusmock.MOCK_IFACE) |
1990 | + super(HotspotBaseTestCase, self).setUp() |
1991 | + |
1992 | + def add_hotspot(self, name, password, secured=True, enabled=False): |
1993 | + settings = { |
1994 | + 'connection': { |
1995 | + 'id': dbus.String('Test AP', variant_level=1), |
1996 | + 'type': dbus.String('802-11-wireless', variant_level=1), }, |
1997 | + '802-11-wireless': { |
1998 | + 'mode': dbus.String('ap', variant_level=1), |
1999 | + 'ssid': dbus.String(name, variant_level=1), |
2000 | + } |
2001 | + } |
2002 | + |
2003 | + if secured: |
2004 | + settings['802-11-wireless']['security'] = dbus.String( |
2005 | + '802-11-wireless-security', variant_level=1) |
2006 | + settings['802-11-wireless-security'] = { |
2007 | + 'auth-alg': dbus.String('shared', variant_level=1), |
2008 | + 'key-mgmt': dbus.String('wpa-psk', variant_level=1), |
2009 | + 'psk': dbus.String(password, variant_level=1), |
2010 | + } |
2011 | + |
2012 | + if enabled: |
2013 | + settings['connection']['autoconnect'] = True |
2014 | + |
2015 | + connection_path = self.obj_nm.SettingsAddConnection(settings) |
2016 | + |
2017 | + return connection_path |
2018 | + |
2019 | + |
2020 | class BluetoothBaseTestCase(UbuntuSystemSettingsTestCase): |
2021 | |
2022 | def setUp(self): |
2023 | |
2024 | === modified file 'tests/autopilot/ubuntu_system_settings/tests/test_cellular.py' |
2025 | --- tests/autopilot/ubuntu_system_settings/tests/test_cellular.py 2014-10-29 15:51:17 +0000 |
2026 | +++ tests/autopilot/ubuntu_system_settings/tests/test_cellular.py 2015-07-31 14:56:39 +0000 |
2027 | @@ -5,6 +5,7 @@ |
2028 | # under the terms of the GNU General Public License version 3, as published |
2029 | # by the Free Software Foundation. |
2030 | |
2031 | +import dbus |
2032 | from gi.repository import Gio, GLib |
2033 | from time import sleep |
2034 | |
2035 | @@ -13,7 +14,14 @@ |
2036 | from testtools.matchers import Equals, raises, StartsWith |
2037 | |
2038 | from ubuntu_system_settings.tests import ( |
2039 | - CellularBaseTestCase, CONNMAN_IFACE, RDO_IFACE, NETREG_IFACE) |
2040 | + CellularBaseTestCase, HotspotBaseTestCase, CONNMAN_IFACE, RDO_IFACE, |
2041 | + NETREG_IFACE) |
2042 | + |
2043 | +from ubuntu_system_settings.tests.networkmanager import ( |
2044 | + CSETTINGS_IFACE, MAIN_OBJ as NM_PATH, MAIN_IFACE as NM_IFACE, |
2045 | +) |
2046 | + |
2047 | +DEV_IFACE = 'org.freedesktop.NetworkManager.Device' |
2048 | |
2049 | |
2050 | class CellularTestCase(CellularBaseTestCase): |
2051 | @@ -288,3 +296,116 @@ |
2052 | lambda: |
2053 | gsettings.get_value('default-sim-for-messages').get_string(), |
2054 | Eventually(Equals('/ril_1'))) |
2055 | + |
2056 | + |
2057 | +class HotspotTestCase(HotspotBaseTestCase): |
2058 | + |
2059 | + def test_setup(self): |
2060 | + if not self.cellular_page.have_hotspot(): |
2061 | + self.skipTest('Cannot test hotspot since wifi is disabled.') |
2062 | + |
2063 | + ssid = 'Ubuntu' |
2064 | + password = 'abcdefgh' |
2065 | + config = {'password': password} |
2066 | + active_con_path = NM_PATH + '/ActiveConnection/0' |
2067 | + con_path = NM_PATH + '/Settings/0' |
2068 | + |
2069 | + hotspot_page = self.cellular_page.setup_hotspot(config) |
2070 | + |
2071 | + # Assert that the switch is on. |
2072 | + self.assertTrue(hotspot_page.get_hotspot_status()) |
2073 | + |
2074 | + # Assert that Block on Urfkill is called twice. |
2075 | + self.assertThat( |
2076 | + lambda: len(self.urfkill_mock.GetMethodCalls('Block')), |
2077 | + Eventually(Equals(2)) |
2078 | + ) |
2079 | + |
2080 | + # Assert that we get one active connection. |
2081 | + self.assertThat( |
2082 | + lambda: len(self.obj_nm.GetAll(NM_IFACE)['ActiveConnections']), |
2083 | + Eventually(Equals(1)) |
2084 | + ) |
2085 | + |
2086 | + # Assert that the active connection has a certain path. |
2087 | + self.assertThat( |
2088 | + lambda: self.obj_nm.GetAll(NM_IFACE)['ActiveConnections'][0], |
2089 | + Eventually(Equals(active_con_path)) |
2090 | + ) |
2091 | + |
2092 | + # Assert the device's active connection |
2093 | + self.assertThat( |
2094 | + lambda: self.device_mock.Get(DEV_IFACE, 'ActiveConnection'), |
2095 | + Eventually(Equals(active_con_path)) |
2096 | + ) |
2097 | + |
2098 | + connection_mock = dbus.Interface(self.dbus_con.get_object( |
2099 | + NM_IFACE, con_path), CSETTINGS_IFACE) |
2100 | + |
2101 | + settings = connection_mock.GetSettings() |
2102 | + |
2103 | + # Assert that autoconnect is true, that ssid and password is what we |
2104 | + # expect them to be. |
2105 | + self.assertTrue(settings['connection']['autoconnect']) |
2106 | + |
2107 | + s_ssid = bytearray(settings['802-11-wireless']['ssid']).decode('utf-8') |
2108 | + self.assertEqual(s_ssid, ssid) |
2109 | + self.assertEqual(settings['802-11-wireless-security']['psk'], password) |
2110 | + |
2111 | + def test_enabling(self): |
2112 | + if not self.cellular_page.have_hotspot(): |
2113 | + self.skipTest('Cannot test hotspot since wifi is disabled.') |
2114 | + |
2115 | + self.add_hotspot('foo', 'abcdefgh', enabled=False) |
2116 | + |
2117 | + self.assertThat( |
2118 | + lambda: len(self.obj_nm.GetAll(NM_IFACE)['ActiveConnections']), |
2119 | + Eventually(Equals(0)) |
2120 | + ) |
2121 | + |
2122 | + self.cellular_page.enable_hotspot() |
2123 | + |
2124 | + self.assertThat( |
2125 | + lambda: len(self.obj_nm.GetAll(NM_IFACE)['ActiveConnections']), |
2126 | + Eventually(Equals(1)) |
2127 | + ) |
2128 | + |
2129 | + def test_disabling(self): |
2130 | + if not self.cellular_page.have_hotspot(): |
2131 | + self.skipTest('Cannot test hotspot since wifi is disabled.') |
2132 | + |
2133 | + self.add_hotspot('foo', 'abcdefgh', enabled=True) |
2134 | + |
2135 | + self.assertThat( |
2136 | + lambda: len(self.obj_nm.GetAll(NM_IFACE)['ActiveConnections']), |
2137 | + Eventually(Equals(1)) |
2138 | + ) |
2139 | + |
2140 | + self.cellular_page.disable_hotspot() |
2141 | + |
2142 | + self.assertThat( |
2143 | + lambda: len(self.obj_nm.GetAll(NM_IFACE)['ActiveConnections']), |
2144 | + Eventually(Equals(0)) |
2145 | + ) |
2146 | + |
2147 | + def test_changing(self): |
2148 | + if not self.cellular_page.have_hotspot(): |
2149 | + self.skipTest('Cannot test hotspot since wifi is disabled.') |
2150 | + |
2151 | + con_path = self.add_hotspot('foo', 'abcdefgh', enabled=True) |
2152 | + |
2153 | + ssid = 'bar' |
2154 | + password = 'zomgzomg' |
2155 | + config = {'ssid': ssid, 'password': password} |
2156 | + self.cellular_page.setup_hotspot(config) |
2157 | + |
2158 | + con_path = NM_PATH + '/Settings/0' |
2159 | + |
2160 | + con_mock = dbus.Interface(self.dbus_con.get_object( |
2161 | + NM_IFACE, con_path), CSETTINGS_IFACE) |
2162 | + |
2163 | + settings = con_mock.GetSettings() |
2164 | + |
2165 | + s_ssid = bytearray(settings['802-11-wireless']['ssid']).decode('utf-8') |
2166 | + self.assertEqual(s_ssid, ssid) |
2167 | + self.assertEqual(settings['802-11-wireless-security']['psk'], password) |
FAILED: Continuous integration, rev:1337 jenkins. qa.ubuntu. com/job/ ubuntu- system- settings- ci/2013/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 1695 jenkins. qa.ubuntu. com/job/ ubuntu- system- settings- vivid-i386- ci/283 jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- vivid-mako/ 1503 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1693 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 1693/artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 18664
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- system- settings- ci/2013/ rebuild
http://