Merge lp:~jonas-drange/ubuntu-system-settings/hotspots-binding into lp:ubuntu-system-settings

Proposed by Jonas G. Drange
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
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://bugs.launchpad.net/ubuntu/+source/lxc-android-config/+bug/1426923/comments/5

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://bazaar.launchpad.net/~mathieu-tl/+junk/touch-hotspot/view/head:/hotspot.py

To post a comment you must log in.
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
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Ken VanDine (ken-vandine) wrote : Posted in a previous version of this proposal

See a couple comments and questions inline.

review: Needs Information
Revision history for this message
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.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
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.

review: Needs Fixing
Revision history for this message
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.

review: Abstain
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
review: Needs Fixing (continuous-integration)
Revision history for this message
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

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
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.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
1377. By Jonas G. Drange

add comment to import

1378. By Jonas G. Drange

hiding completely on mako

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
1379. By Jonas G. Drange

unhide for testing

1380. By Jonas G. Drange

remove enabledshim

1381. By Jonas G. Drange

using usc serverpropertysynchroniser

1382. By Jonas G. Drange

foo

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
1383. By Jonas G. Drange

let the checkbox render before starting hotspot

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1384. By Jonas G. Drange

disable back button so as to ensure that the wifi is soft unblocked

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
1385. By Jonas G. Drange

sync trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1386. By Jonas G. Drange

sync with trunk

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1387. By Jonas G. Drange

sync with trunk

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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)

Subscribers

People subscribed via source and target branches