Merge lp:~ken-vandine/ubuntu-system-settings/apn_refactor into lp:ubuntu-system-settings

Proposed by Ken VanDine
Status: Merged
Approved by: Ken VanDine
Approved revision: 1482
Merged at revision: 1488
Proposed branch: lp:~ken-vandine/ubuntu-system-settings/apn_refactor
Merge into: lp:ubuntu-system-settings
Diff against target: 3487 lines (+2067/-1140)
16 files modified
plugins/cellular/CMakeLists.txt (+3/-4)
plugins/cellular/Components/CMakeLists.txt (+3/-0)
plugins/cellular/Components/KeyboardRectangle.qml (+74/-0)
plugins/cellular/Components/LabelTextField.qml (+54/-0)
plugins/cellular/Components/StandardAnimation.qml (+22/-0)
plugins/cellular/PageApnEditor.qml (+394/-346)
plugins/cellular/PageChooseApn.qml (+333/-535)
plugins/cellular/apn_editor.js (+192/-0)
plugins/cellular/apn_manager.js (+588/-60)
plugins/cellular/ofonoactivator.cpp (+0/-132)
plugins/cellular/ofonoactivator.h (+0/-42)
plugins/cellular/plugin.cpp (+0/-2)
tests/autopilot/ubuntu_system_settings/__init__.py (+147/-1)
tests/autopilot/ubuntu_system_settings/tests/__init__.py (+17/-18)
tests/autopilot/ubuntu_system_settings/tests/ofono.py (+99/-0)
tests/autopilot/ubuntu_system_settings/tests/test_cellular.py (+141/-0)
To merge this branch: bzr merge lp:~ken-vandine/ubuntu-system-settings/apn_refactor
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Ken VanDine Approve
Review via email: mp+265973@code.launchpad.net

Commit message

[cellular/apn] new APN panel made for the new 'Preferred' property, as well as the 'ResetContext' method.

Description of the change

New UI due to new NetworkManager, Nuntium and Ofono backend.

Silo: 33

* 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?
No, it's currently being written AND the NetworkManager backend change has not landed yet.
 * Has a 5 minute exploratory testing run been executed on N4?
N/A
 * If you changed the packaging (debian), did you subscribe a core-dev to this MP?
Yes, kenvandine
 * If you changed the UI, did you subscribe the design-reviewers to this MP?
mpt
 * What components might get impacted by your changes?
NetworkManager, Nuntium, Ofono
 * Have you requested review by the teams of these owning components?
Yes

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Ken VanDine (ken-vandine) wrote :

This is just fixing a merge conflict from jonas' branch, which is approved.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
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-27 14:21:15 +0000
4@@ -4,9 +4,10 @@
5 install(FILES settings-cellular.svg DESTINATION ${PLUGIN_MANIFEST_DIR}/icons)
6
7 set(QML_SOURCES
8- apn.js
9+ apn_manager.js
10+ apn_editor.js
11 carriers.js
12- CustomApnEditor.qml
13+ PageApnEditor.qml
14 PageChooseApn.qml
15 PageChooseCarrier.qml
16 PageCarrierAndApn.qml
17@@ -23,8 +24,6 @@
18 plugin.h
19 hotspotmanager.cpp
20 hotspotmanager.h
21- ofonoactivator.cpp
22- ofonoactivator.h
23 connectivity.cpp
24 connectivity.h
25 nm_manager_proxy.h
26
27=== modified file 'plugins/cellular/Components/CMakeLists.txt'
28--- plugins/cellular/Components/CMakeLists.txt 2014-11-12 12:46:51 +0000
29+++ plugins/cellular/Components/CMakeLists.txt 2015-07-27 14:21:15 +0000
30@@ -1,12 +1,15 @@
31 set(QML_SOURCES
32 DataMultiSim.qml
33 DefaultSim.qml
34+ KeyboardRectangle.qml
35+ LabelTextField.qml
36 MultiSim.qml
37 NoSim.qml
38 RadioSingleSim.qml
39 Sim.qml
40 SimEditor.qml
41 SingleSim.qml
42+ StandardAnimation.qml
43 )
44 install(FILES ${QML_SOURCES} DESTINATION ${PLUGIN_QML_DIR}/cellular/Components)
45
46
47=== added file 'plugins/cellular/Components/KeyboardRectangle.qml'
48--- plugins/cellular/Components/KeyboardRectangle.qml 1970-01-01 00:00:00 +0000
49+++ plugins/cellular/Components/KeyboardRectangle.qml 2015-07-27 14:21:15 +0000
50@@ -0,0 +1,74 @@
51+/*
52+ * Copyright (C) 2015 Canonical, Ltd.
53+ *
54+ * This program is free software; you can redistribute it and/or modify
55+ * it under the terms of the GNU General Public License as published by
56+ * the Free Software Foundation; version 3.
57+ *
58+ * This program is distributed in the hope that it will be useful,
59+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
60+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
61+ * GNU General Public License for more details.
62+ *
63+ * You should have received a copy of the GNU General Public License
64+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
65+ */
66+
67+import QtQuick 2.2
68+
69+Item {
70+ id: keyboardRect
71+ anchors.left: parent.left
72+ anchors.right: parent.right
73+ anchors.bottom: parent.bottom
74+ height: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0
75+
76+ Behavior on height {
77+ StandardAnimation { }
78+ }
79+
80+ states: [
81+ State {
82+ name: "hidden"
83+ when: keyboardRect.height == 0
84+ },
85+ State {
86+ name: "shown"
87+ when: keyboardRect.height == Qt.inputMethod.keyboardRectangle.height
88+ }
89+ ]
90+
91+ function recursiveFindFocusedItem(parent) {
92+ if (parent.activeFocus) {
93+ return parent;
94+ }
95+
96+ for (var i in parent.children) {
97+ var child = parent.children[i];
98+ if (child.activeFocus) {
99+ return child;
100+ }
101+
102+ var item = recursiveFindFocusedItem(child);
103+
104+ if (item != null) {
105+ return item;
106+ }
107+ }
108+
109+ return null;
110+ }
111+
112+ Connections {
113+ target: Qt.inputMethod
114+
115+ onVisibleChanged: {
116+ if (!Qt.inputMethod.visible) {
117+ var focusedItem = recursiveFindFocusedItem(keyboardRect.parent);
118+ if (focusedItem != null) {
119+ focusedItem.focus = false;
120+ }
121+ }
122+ }
123+ }
124+}
125
126=== added file 'plugins/cellular/Components/LabelTextField.qml'
127--- plugins/cellular/Components/LabelTextField.qml 1970-01-01 00:00:00 +0000
128+++ plugins/cellular/Components/LabelTextField.qml 2015-07-27 14:21:15 +0000
129@@ -0,0 +1,54 @@
130+/*
131+ * This file is part of system-settings
132+ *
133+ * Copyright (C) 2015 Canonical Ltd.
134+ *
135+ * This program is free software: you can redistribute it and/or modify it
136+ * under the terms of the GNU General Public License version 3, as published
137+ * by the Free Software Foundation.
138+ *
139+ * This program is distributed in the hope that it will be useful, but
140+ * WITHOUT ANY WARRANTY; without even the implied warranties of
141+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
142+ * PURPOSE. See the GNU General Public License for more details.
143+ *
144+ * You should have received a copy of the GNU General Public License along
145+ * with this program. If not, see <http://www.gnu.org/licenses/>.
146+ */
147+
148+import QtQuick 2.0
149+import Ubuntu.Components 1.1
150+import Ubuntu.Components.Themes.Ambiance 0.1
151+import Ubuntu.Keyboard 0.1
152+import SystemSettings 1.0
153+
154+TextField {
155+ id: field
156+ property var next
157+ anchors {
158+ left: parent.left
159+ right: parent.right
160+ }
161+ height: implicitHeight + units.gu(2)
162+ style: TextFieldStyle {
163+ overlaySpacing: units.gu(1)
164+ frameSpacing: units.gu(1)
165+ background: Rectangle {
166+ property bool error: (field.hasOwnProperty("errorHighlight") &&
167+ field.errorHighlight &&
168+ !field.acceptableInput)
169+ onErrorChanged: error ? UbuntuColors.orange : color
170+ color: Theme.palette.selected.background
171+ anchors.fill: parent
172+ visible: field.activeFocus
173+ }
174+ color: UbuntuColors.lightAubergine
175+ }
176+
177+ // Ubuntu.Keyboard
178+ // TRANSLATORS: This is the text that will be used on the "return" key for the virtual keyboard,
179+ // this word must be less than 5 characters
180+ InputMethod.extensions: { "enterKeyText": i18n.tr("Next") }
181+ KeyNavigation.tab: next
182+ Keys.onReturnPressed: next.forceActiveFocus()
183+}
184
185=== added file 'plugins/cellular/Components/StandardAnimation.qml'
186--- plugins/cellular/Components/StandardAnimation.qml 1970-01-01 00:00:00 +0000
187+++ plugins/cellular/Components/StandardAnimation.qml 2015-07-27 14:21:15 +0000
188@@ -0,0 +1,22 @@
189+/*
190+ * Copyright (C) 2015 Canonical, Ltd.
191+ *
192+ * This program is free software; you can redistribute it and/or modify
193+ * it under the terms of the GNU General Public License as published by
194+ * the Free Software Foundation; version 3.
195+ *
196+ * This program is distributed in the hope that it will be useful,
197+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
198+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
199+ * GNU General Public License for more details.
200+ *
201+ * You should have received a copy of the GNU General Public License
202+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
203+ */
204+
205+import QtQuick 2.2
206+
207+NumberAnimation {
208+ duration: 300
209+ easing.type: Easing.InOutQuad
210+}
211
212=== renamed file 'plugins/cellular/CustomApnEditor.qml' => 'plugins/cellular/PageApnEditor.qml'
213--- plugins/cellular/CustomApnEditor.qml 2015-03-27 15:01:01 +0000
214+++ plugins/cellular/PageApnEditor.qml 2015-07-27 14:21:15 +0000
215@@ -3,7 +3,8 @@
216 *
217 * Copyright (C) 2014 Canonical Ltd.
218 *
219- * Contact: Pat McGowan <pat.mcgowan@canonical.com>
220+ * Contact: Pat McGowan <pat.mcgowan@canonical.com>,
221+ * Jonas G. Drange <jonas.drange@canonical.com>
222 *
223 * This program is free software: you can redistribute it and/or modify it
224 * under the terms of the GNU General Public License version 3, as published
225@@ -19,367 +20,414 @@
226 */
227
228 import QtQuick 2.0
229-import QtQuick.Layouts 1.1
230 import SystemSettings 1.0
231 import Ubuntu.Components 1.1
232+import Ubuntu.Components.Popups 1.0
233 import Ubuntu.Components.ListItems 1.0 as ListItem
234-import "apn.js" as APN
235+import MeeGo.QOfono 0.2
236+import "Components" as LocalComponents
237+import "apn_editor.js" as Editor
238
239 ItemPage {
240- objectName: "customapnPage"
241 id: root
242-
243- // "internet" or "mms"
244- property var type
245-
246- // dict of "type" : ctx
247- property var contexts;
248-
249- /// work around LP(#1361919)
250- property var activateCb;
251-
252- // Sim qml object
253- property var sim
254-
255- QtObject {
256- id: d
257- property var typeText : type === "internet" ? i18n.tr("Internet") : i18n.tr("MMS")
258- property bool isMms : type === "mms"
259-
260- property bool isValid : false
261-
262- function validateFields() {
263- if (apnName.text === "") {
264- isValid = false;
265- return
266- }
267- if (isMms) {
268- if (mmsc.text === "") {
269- isValid = false;
270- return;
271- }
272- /// @todo validate proxy
273- /// @todo force port to be integer and validate it's value
274- }
275-
276- // @todo the rest
277- isValid = true;
278- }
279- }
280-
281- //TRANSLATORS: %1 is either i18n.tr("Internet") or i18n.tr("MMS")
282- title: i18n.tr("Custom %1 APN").arg(d.typeText)
283-
284- // workaround of getting the following error on startup:
285- // WARNING - ... : QML Page: Binding loop detected for property "flickable"
286+ objectName: "apnEditor"
287+
288+ // Property that holds the APN manager (apn_manager.js) module.
289+ property var manager
290+
291+ property OfonoContextConnection contextQML
292+
293+ // All models.
294+ property ListModel mmsModel
295+ property ListModel internetModel
296+ property ListModel iaModel
297+
298+ // Flags that indicate what context we are editing.
299+ property bool isCombo: Editor.indexToType(typeSel.selectedIndex) === 'internet+mms'
300+ property bool isInternet: Editor.indexToType(typeSel.selectedIndex) === 'internet'
301+ property bool isMms: Editor.indexToType(typeSel.selectedIndex) === 'mms'
302+ property bool isIa: Editor.indexToType(typeSel.selectedIndex) === 'ia'
303+
304+ property bool isValid: Editor.isValid()
305+
306+ // priv
307+ property bool _edgeReady: false
308+
309+ property QtObject activeItem: null
310+
311+ // When a user has requested saving a context.
312+ signal saving ()
313+
314+ // When user has saved a context.
315+ signal saved ()
316+
317+ // Signal for new contexts.
318+ signal newContext (OfonoContextConnection context)
319+
320+ // When user cancels.
321+ signal canceled ()
322+
323+ title: contextQML ? i18n.tr("Edit") : i18n.tr("New APN")
324+
325+ state: "default"
326+ states: [
327+ PageHeadState {
328+ name: "default"
329+ head: root.head
330+ actions: [
331+ Action {
332+ objectName: "saveApn"
333+ iconName: "ok"
334+ enabled: isValid
335+ onTriggered: Editor.saving()
336+ }
337+ ]
338+ },
339+ PageHeadState {
340+ name: "busy"
341+ head: root.head
342+ actions: [
343+ Action {
344+ iconName: "ok"
345+ enabled: false
346+ }
347+ ]
348+ },
349+ State {
350+ name: "busy"
351+ PropertyChanges { target: name; enabled: false; }
352+ PropertyChanges { target: accessPointName; enabled: false; }
353+ PropertyChanges { target: username; enabled: false; }
354+ PropertyChanges { target: password; enabled: false; }
355+ PropertyChanges { target: messageCenter; enabled: false; }
356+ PropertyChanges { target: messageProxy; enabled: false; }
357+ PropertyChanges { target: port; enabled: false; }
358+ }
359+ ]
360+
361+ onSaving: state = "busy"
362+ onSaved: pageStack.pop();
363+ onCanceled: pageStack.pop();
364+ onNewContext: Editor.newContext(context);
365+
366+ Component.onCompleted: {
367+ if (contextQML) {
368+ Editor.populate(contextQML);
369+ }
370+ Editor.ready();
371+ }
372+
373+ // We need to disable keyboard anchoring because we implement the
374+ // KeyboardRectangle pattern
375+ Binding {
376+ target: main
377+ property: "anchorToKeyboard"
378+ value: false
379+ }
380+
381 flickable: null
382- Component.onCompleted: {
383- flickable: scrollWidget
384-
385- var ctx;
386- if (d.isMms) {
387- ctx = contexts["mms"];
388- if (ctx === undefined) {
389- // @bug LP(:#1362795)
390- return;
391- }
392- } else {
393- ctx = contexts["internet"]
394- }
395-
396- // We do not have any context yet, we will create one, but not now.
397- // We create it once the user presses 'Activate'.
398- if (!ctx) {
399- return;
400- }
401-
402- apnName.text = ctx.accessPointName;
403- userName.text = ctx.username;
404- pword.text = ctx.password;
405- mmsc.text = ctx.messageCenter;
406- var proxyText = ctx.messageProxy.split(":");
407- proxy.text = proxyText[0] !== undefined ? proxyText[0] : "";
408- port.text = proxyText[1] !== undefined ? proxyText[1] : "";
409- /// @todo protocol values
410-
411- if (d.isMms) {
412- /// @todo disabled for now
413- doBoth.checked = false;
414- return;
415- var internetApn = contexts["internet"]
416- if (ctx.accessPointName === internetApn.accessPointName &&
417- ctx.username == internetApn.username &&
418- ctx.password == internetApn.password
419- /* auth + procol */) {
420- doBoth.checked = true;
421- }
422- else
423- doBoth.checked = false;
424- }
425- }
426-
427 Flickable {
428- id: scrollWidget
429+ id: scrollArea
430+ objectName: "scrollArea"
431+
432+ // this is necessary to avoid the page to appear below the header
433+ clip: true
434+ flickableDirection: Flickable.VerticalFlick
435 anchors {
436- top: parent.top
437- left: parent.left
438- right: parent.right
439- bottom: parent.bottom
440- margins: units.gu(2)
441+ fill: parent
442+ bottomMargin: keyboard.height
443 }
444+ contentHeight: contents.height + units.gu(2)
445 contentWidth: parent.width
446- clip: true
447- contentHeight: theContents.height
448- boundsBehavior: (contentHeight > height) ? Flickable.DragAndOvershootBounds : Flickable.StopAtBounds
449- flickableDirection: Flickable.VerticalFlick
450-
451- ColumnLayout {
452- id: theContents
453- anchors {
454- left: parent.left
455- right: parent.right
456- }
457- spacing: units.gu(2)
458-
459- ListItem.Standard {
460- id: sameSwitch
461- anchors {
462- left: parent.left
463- right: parent.right
464+
465+ // after add a new field we need to wait for the contentHeight to
466+ // change to scroll to the correct position
467+ onContentHeightChanged: Editor.makeMeVisible(root.activeItem)
468+
469+ Column {
470+ id: contents
471+ anchors { left: parent.left; right: parent.right; }
472+
473+ // Type controls
474+ Column {
475+ anchors { left: parent.left; right: parent.right; }
476+
477+ SettingsItemTitle {
478+ text: i18n.tr("Used for:")
479 }
480- /// @todo disable for now
481- //visible: d.isMms
482- visible: false
483- text: i18n.tr("Same APN as for Internet")
484- control: Switch {
485- id: doBoth
486- checked: false
487- anchors.verticalCenter: parent.verticalCenter
488- onClicked: {
489- if (checked) {
490- var internetApn = contexts["internet"]
491- apnName.text = internetApn.accessPointName;
492- userName.text = internetApn.username;
493- pword.text = internetApn.password;
494- }
495+
496+ ListItem.ItemSelector {
497+ model: [i18n.tr('Internet and MMS'),
498+ i18n.tr('Internet'),
499+ i18n.tr('MMS'),
500+ i18n.tr('LTE'), ]
501+ id: typeSel
502+ objectName: "typeSelector"
503+ delegate: OptionSelectorDelegate {
504+ showDivider: false
505+ objectName: "type_" + Editor.indexToType(index)
506 }
507- }
508- }
509-
510- GridLayout {
511- id: theGrid
512- columns: 2
513- columnSpacing: units.gu(1)
514- rowSpacing: units.gu(1)
515- anchors{
516- right: parent.right
517- left:parent.left
518- }
519-
520- Label {
521- //TRANSLATORS: %1 is either i18n.tr("Internet") or i18n.tr("MMS")
522- text: i18n.tr("%1 APN").arg(d.typeText)
523- }
524- TextField {
525- id: apnName
526- enabled: !doBoth.checked
527- onTextChanged: d.validateFields()
528- inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
529- }
530-
531- Label {
532+ width: parent.width
533+ KeyNavigation.tab: name
534+ }
535+ }
536+
537+ // Name controls
538+ Column {
539+ anchors { left: parent.left; right: parent.right; }
540+
541+ SettingsItemTitle {
542+ anchors { left: parent.left; right: parent.right }
543+ text: i18n.tr("Name")
544+ }
545+
546+ LocalComponents.LabelTextField {
547+ id: name
548+ objectName: "name"
549+ inputMethodHints: Qt.ImhNoAutoUppercase |
550+ Qt.ImhNoPredictiveText
551+ placeholderText: i18n.tr("Enter a name describing the APN")
552+ next: accessPointName
553+ Component.onCompleted: forceActiveFocus()
554+ onActiveFocusChanged: if (activeFocus) Editor.makeMeVisible(
555+ name)
556+ }
557+ }
558+
559+ // APN controls
560+ Column {
561+ anchors { left: parent.left; right: parent.right; }
562+
563+ SettingsItemTitle {
564+ anchors { left: parent.left; right: parent.right }
565+ /* TRANSLATORS: This string is a description of a text
566+ field and should thus be concise. */
567+ text: i18n.tr("APN")
568+ }
569+
570+ LocalComponents.LabelTextField {
571+ id: accessPointName
572+ objectName: "accessPointName"
573+ inputMethodHints: Qt.ImhUrlCharactersOnly |
574+ Qt.ImhNoAutoUppercase |
575+ Qt.ImhNoPredictiveText
576+ placeholderText: i18n.tr("Enter the name of the access point")
577+ next: isMms || isCombo ? messageCenter : username
578+ onActiveFocusChanged: if (activeFocus) Editor.makeMeVisible(
579+ accessPointName)
580+ }
581+ }
582+
583+ // MMSC controls
584+ Column {
585+ anchors { left: parent.left; right: parent.right }
586+ visible: isMms || isCombo
587+
588+ SettingsItemTitle {
589+ anchors { left: parent.left; right: parent.right }
590 text: i18n.tr("MMSC")
591- visible: d.isMms
592- }
593- TextField {
594- id: mmsc
595- visible: d.isMms
596- onTextChanged: d.validateFields()
597- inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
598- }
599- Label {
600+ }
601+
602+ LocalComponents.LabelTextField {
603+ id: messageCenter
604+ objectName: "messageCenter"
605+ inputMethodHints: Qt.ImhUrlCharactersOnly |
606+ Qt.ImhNoAutoUppercase |
607+ Qt.ImhNoPredictiveText
608+ placeholderText: i18n.tr("Enter message center")
609+ next: messageProxy
610+ onFocusChanged: {
611+ if (!focus && text.length > 0) {
612+ text = Editor.setHttp(text);
613+ }
614+ if (activeFocus) Editor.makeMeVisible(messageCenter);
615+ }
616+ }
617+ }
618+
619+ // Proxy controls
620+ Column {
621+ anchors { left: parent.left; right: parent.right }
622+ visible: isMms || isCombo
623+
624+ SettingsItemTitle {
625+ anchors { left: parent.left; right: parent.right }
626 text: i18n.tr("Proxy")
627- visible: d.isMms
628- }
629- TextField {
630- id: proxy
631- visible: d.isMms
632- onTextChanged: d.validateFields()
633- inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
634- }
635- Label {
636- text: i18n.tr("Port")
637- visible: d.isMms
638- }
639- TextField {
640+ }
641+
642+ LocalComponents.LabelTextField {
643+ id: messageProxy
644+ objectName: "messageProxy"
645+ inputMethodHints: Qt.ImhUrlCharactersOnly |
646+ Qt.ImhNoAutoUppercase |
647+ Qt.ImhNoPredictiveText
648+ placeholderText: i18n.tr("Enter message proxy")
649+ next: port
650+ onTextChanged: {
651+ movePortDelay.running = false;
652+ if (text.search(/\:\d+$/) >= 0) {
653+ movePortDelay.running = true;
654+ }
655+ }
656+ onActiveFocusChanged: if (activeFocus) Editor.makeMeVisible(
657+ messageProxy)
658+ }
659+
660+ Timer {
661+ id: movePortDelay
662+ interval: 1000
663+ onTriggered: {
664+
665+ function getPort(s) {
666+ var match = s.match(/\:(\d+)/);
667+ if (match === null) {
668+ return null;
669+ } else {
670+ return match[1];
671+ }
672+ }
673+
674+ var prt = getPort(messageProxy.text);
675+ var portIndex = messageProxy.text.indexOf(prt);
676+ var textSansPort = messageProxy.text.slice(0, portIndex - 1);
677+
678+ if (prt) {
679+ messageProxy.text = textSansPort;
680+ port.text = prt;
681+ }
682+ }
683+ }
684+ }
685+
686+ // Proxy port controls
687+ Column {
688+ anchors { left: parent.left; right: parent.right }
689+ visible: isMms || isCombo
690+
691+ SettingsItemTitle {
692+ id: portLabel
693+ text: i18n.tr("Proxy port")
694+ }
695+
696+ LocalComponents.LabelTextField {
697 id: port
698- visible: d.isMms
699- maximumLength: 4
700- onTextChanged: d.validateFields()
701- inputMethodHints: Qt.ImhDigitsOnly | Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
702- }
703-
704- Label {
705- text: i18n.tr("Username")
706- }
707- TextField {
708- id: userName
709- enabled: !doBoth.checked
710- inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
711- }
712-
713- Label {
714+ objectName: "port"
715+ maximumLength: 5
716+ inputMethodHints: Qt.ImhNoAutoUppercase |
717+ Qt.ImhNoPredictiveText
718+ validator: portValidator
719+ placeholderText: i18n.tr("Enter message proxy port")
720+ next: username
721+ onActiveFocusChanged: if (activeFocus) Editor.makeMeVisible(
722+ port)
723+ }
724+
725+ RegExpValidator {
726+ id: portValidator
727+ regExp: /([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])/
728+ }
729+ }
730+
731+ // Username controls
732+ Column {
733+ anchors { left: parent.left; right: parent.right }
734+
735+ SettingsItemTitle {
736+ width: parent.width
737+ text: i18n.tr("User name")
738+ }
739+
740+ LocalComponents.LabelTextField {
741+ id: username
742+ objectName: "username"
743+ inputMethodHints: Qt.ImhNoAutoUppercase |
744+ Qt.ImhNoPredictiveText |
745+ Qt.ImhSensitiveData
746+ placeholderText: i18n.tr("Enter username")
747+ next: password
748+ onActiveFocusChanged: if (activeFocus) Editor.makeMeVisible(
749+ username)
750+ }
751+ }
752+
753+ // Password controls
754+ Column {
755+ anchors { left: parent.left; right: parent.right }
756+
757+ SettingsItemTitle {
758+ width: parent.width
759 text: i18n.tr("Password")
760 }
761- TextField {
762- id: pword
763- enabled: !doBoth.checked
764- inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
765- }
766- /// @todo support for ipv6 will be added after RTM
767- }
768-
769- Item {
770- id: buttonRectangle
771-
772- height: cancelButton.height + units.gu(2)
773-
774- anchors {
775- left: parent.left
776- right: parent.right
777- }
778-
779- Button {
780- id: cancelButton
781-
782- text: i18n.tr("Cancel")
783-
784- anchors {
785- left: parent.left
786- right: parent.horizontalCenter
787- bottom: parent.bottom
788- topMargin: units.gu(1)
789- rightMargin: units.gu(1)
790- bottomMargin: units.gu(1)
791- }
792-
793- onClicked: {
794- pageStack.pop()
795- }
796- }
797-
798- Button {
799- id: confirmButton
800-
801- text: d.isMms ? i18n.tr("Save") : i18n.tr("Activate")
802-
803- anchors {
804- left: parent.horizontalCenter
805- right: parent.right
806- bottom: parent.bottom
807- topMargin: units.gu(1)
808- leftMargin: units.gu(1)
809- rightMargin: units.gu(4)
810- bottomMargin: units.gu(1)
811- }
812-
813- enabled: d.isValid;
814-
815- onClicked: {
816- var ctx;
817- var values;
818-
819- if (d.isMms)
820- ctx = contexts["mms"];
821- else
822- ctx = contexts["internet"];
823-
824- /// @bug LP(:#1362795)
825- if (d.isMms && ctx === undefined) {
826- var mmsData = ({})
827- mmsData["accessPointName"] = apnName.text;
828- mmsData["username"] = userName.text;
829- mmsData["password"] = pword.text;
830- mmsData["messageCenter"] = mmsc.text
831- var proxyValue = "";
832- if (proxy.text !== "") {
833- proxyValue = proxy.text;
834- if (port.text !== "")
835- proxyValue = proxyValue + ":" + port.text;
836- }
837- mmsData["messageProxy"] = proxyValue;
838- activateCb("mms", undefined, mmsData);
839- pageStack.pop();
840- return;
841- }
842-
843- values = {
844- 'accessPointName': apnName.text,
845- 'username': userName.text,
846- 'password': pword.text
847- };
848-
849- if (d.isMms) {
850- values['messageCenter'] = mmsc.text;
851- var proxyValue = "";
852- if (proxy.text !== "") {
853- proxyValue = proxy.text;
854- if (port.text !== "")
855- proxyValue = proxyValue + ":" + port.text;
856- }
857- values['messageProxy'] = proxyValue;
858- }
859-
860- // If we are editing an existing context, update
861- // its values, activate and exit.
862- if (ctx) {
863- APN.updateContext(ctx, values);
864- activateCb(ctx.type, ctx.contextPath);
865- pageStack.pop();
866- } else {
867- // If we do not have a context, create one and defer
868- // editing it to when it has been created—and we
869- // will also wait until Ofono has given it a name.
870- // This is all very async, so we will use connect()
871- // and dynamic QML.
872-
873- function addedCustomContext (path) {
874- // This is a handler for when we add a context
875- // in the custom apn editor.
876- // We create a temporary QML object for the
877- // context like this, until we get the APN
878- // editor refactored.
879- // TODO(jgdx): create some kind of libqofono
880- // objects framework in apn.js
881- var newCtx = Qt.createQmlObject(
882- 'import MeeGo.QOfono 0.2;'+
883- 'OfonoContextConnection {'+
884- 'contextPath: "'+path+'" }',
885- root, "apn.js");
886-
887- // Ofono sets a default name (Internet or MMS).
888- // We are going to change it to our default name.
889- newCtx.nameChanged.connect(function (name) {
890- if (name === "Internet") {
891- newCtx.name = APN.CUSTOM_INTERNET_CONTEXT_NAME();
892- APN.updateContext(newCtx, values);
893- } else if (name === "MMS") {
894- newCtx.name = APN.CUSTOM_MMS_CONTEXT_NAME();
895- APN.updateContext(newCtx, values);
896- } else {
897- newCtx.destroy(100);
898- activateCb(newCtx.type, newCtx.contextPath);
899- pageStack.pop();
900- }
901- });
902- }
903-
904- sim.connMan.addContext(root.type);
905- sim.connMan.contextAdded.connect(addedCustomContext);
906- }
907- }
908- }
909- } // item for buttons
910- } // the contents
911- } // the flickable
912+
913+ LocalComponents.LabelTextField {
914+ id: password
915+ objectName: "password"
916+ width: parent.width
917+ echoMode: TextInput.Normal
918+ inputMethodHints: Qt.ImhNoAutoUppercase |
919+ Qt.ImhNoPredictiveText |
920+ Qt.ImhSensitiveData
921+ placeholderText: i18n.tr("Enter password")
922+ next: name
923+ onActiveFocusChanged: if (activeFocus) Editor.makeMeVisible(
924+ password)
925+ }
926+ }
927+
928+ // Authentication controls
929+ Column {
930+ anchors { left: parent.left; right: parent.right }
931+ visible: showAllUI
932+
933+ SettingsItemTitle {
934+ width: parent.width
935+ text: i18n.tr("Authentication")
936+ }
937+
938+ ListItem.ItemSelector {
939+ model: [i18n.tr("None"),
940+ i18n.tr("PAP or CHAP"),
941+ i18n.tr("PAP only"),
942+ i18n.tr("CHAP only")]
943+ }
944+ }
945+
946+ // Protocol controls
947+ Column {
948+ anchors { left: parent.left; right: parent.right }
949+ visible: showAllUI
950+
951+ SettingsItemTitle {
952+ width: parent.width
953+ text: i18n.tr("Protocol")
954+ }
955+
956+ ListItem.ItemSelector {
957+ model: [i18n.tr("IPv4"),
958+ i18n.tr("IPv6"),
959+ i18n.tr("IPv4v6")]
960+ }
961+ }
962+ }
963+ } // main column holding all controls and buttons
964+
965+ Timer {
966+ id: updateContext
967+ property OfonoContextConnection ctx
968+ interval: 1500
969+ onTriggered: {
970+ Editor.updateContextQML(ctx);
971+ root.saved();
972+ }
973+ }
974+
975+ LocalComponents.KeyboardRectangle {
976+ id: keyboard
977+ anchors.bottom: parent.bottom
978+ onHeightChanged: {
979+ if (root.activeItem) {
980+ Editor.makeMeVisible(root.activeItem);
981+ }
982+ }
983+ }
984 }
985
986=== modified file 'plugins/cellular/PageChooseApn.qml'
987--- plugins/cellular/PageChooseApn.qml 2015-03-27 15:01:01 +0000
988+++ plugins/cellular/PageChooseApn.qml 2015-07-27 14:21:15 +0000
989@@ -3,7 +3,8 @@
990 *
991 * Copyright (C) 2014 Canonical Ltd.
992 *
993- * Contact: Pat McGowan <pat.mcgowan@canonical.com>
994+ * Contact: Pat McGowan <pat.mcgowan@canonical.com>,
995+ * Jonas G. Drange <jonas.drange@canonical.com>
996 *
997 * This program is free software: you can redistribute it and/or modify it
998 * under the terms of the GNU General Public License version 3, as published
999@@ -16,15 +17,20 @@
1000 *
1001 * You should have received a copy of the GNU General Public License along
1002 * with this program. If not, see <http://www.gnu.org/licenses/>.
1003+ *
1004+ * Note: Everything user facing refers to APN and e.g. LTE, but in the
1005+ * code an APN configuration is a 'context' and LTE is 'ia'.
1006+ *
1007 */
1008
1009 import QtQuick 2.0
1010 import SystemSettings 1.0
1011 import Ubuntu.Components 1.1
1012-import Ubuntu.Components.Popups 0.1
1013-import Ubuntu.Components.ListItems 0.1 as ListItem
1014+import Ubuntu.Components.Popups 1.0
1015+import Ubuntu.Components.ListItems 1.0 as ListItem
1016 import MeeGo.QOfono 0.2
1017 import Ubuntu.SystemSettings.Cellular 1.0
1018+import "apn_manager.js" as Manager
1019
1020 ItemPage {
1021 id: root
1022@@ -32,548 +38,340 @@
1023 objectName: "apnPage"
1024
1025 property var sim
1026-
1027- QtObject {
1028- id: d
1029-
1030- // map of contextPath : connCtx
1031- property var mContexts: ({})
1032-
1033- readonly property string mCustomContextNameInternet: "___ubuntu_custom_apn_internet"
1034- readonly property string mCustomContextNameMms: "___ubuntu_custom_apn_mms"
1035- property var mCustomContextInternet : undefined
1036- property var mCustomContextMms : undefined
1037-
1038- // LP(:#1362795)
1039- property var pendingCustomMmsData : ({})
1040-
1041- // suppress any actions that we don't want to take
1042- // when updating selectedIndexes, etc
1043- property bool __suppressActivation : true;
1044- property bool __haveCustomContexts : false;
1045-
1046- function updateContexts(contexts)
1047- {
1048- var tmp = contexts || sim.connMan.contexts.slice(0);
1049- var added = tmp.filter(function(i) {
1050- return mContexts[i] === undefined;
1051- });
1052- var removed = Object.keys(mContexts).filter(function(i) {
1053- return tmp.indexOf(i) === -1;
1054- })
1055-
1056- removed.forEach(function(currentValue, index, array) {
1057- // just asserting to verify the logic
1058- // remove once proven functional
1059- // the exception gives a very nice log error message from QmlEngine and does not
1060- // have any side effects
1061- if (mContexts[currentValue] === undefined) {
1062- throw "updateContexts: removed is broken";
1063- }
1064-
1065- if (mContexts[currentValue].name === mCustomContextNameInternet)
1066- mCustomContextInternet = undefined
1067- else if (mContexts[currentValue].name === mCustomContextNameMms)
1068- mCustomContextMms = undefined
1069-
1070- mContexts[currentValue].destroy();
1071- delete mContexts[currentValue];
1072- });
1073-
1074- added.forEach(function(currentValue, index, array) {
1075- // just asserting to verify the logic
1076- // remove once proven functional
1077- // the exception gives a very nice log error message from QmlEngine and does not
1078- // have any side effects
1079- if (mContexts[currentValue] !== undefined) {
1080- throw "updateContexts: added is broken";
1081- }
1082- var ctx = connCtx.createObject(parent,
1083- {
1084- "contextPath": currentValue
1085- });
1086- mContexts[currentValue] = ctx;
1087- });
1088-
1089- // just asserting to verify the logic
1090- // remove once proven functional
1091- // the exception gives a very nice log error message from QmlEngine and does not
1092- // have any side effects
1093- if (Object.keys(mContexts).length !== tmp.length) {
1094- throw "Object.keys(contexts).length !== tmp.length";
1095- }
1096- tmp.forEach(function(currentValue, index, array) {
1097- if (mContexts[currentValue] === undefined)
1098- throw "contexts[currentValue] === undefined";
1099- });
1100-
1101- buildLists();
1102- }
1103-
1104- // expects updateContexts() to have ran before executing.
1105- function checkAndCreateCustomContexts() {
1106-
1107- // When a context is added, we assume it is a custom one.
1108- function addedCustomContext (path) {
1109-
1110- // We do not have a QML object representing this context,
1111- // so we ask updateContexts to create one.
1112- if (!d.mContexts[path]) {
1113- d.updateContexts();
1114- }
1115- d.mContexts[path].name = mCustomContextNameInternet;
1116- sim.connMan.contextAdded.disconnect(addedCustomContext);
1117- }
1118-
1119- var customInternet = Object.keys(mContexts).filter(function (i) {
1120- var ctx = mContexts[i];
1121- return ctx.name === mCustomContextNameInternet;
1122- });
1123- var customMms = Object.keys(mContexts).filter(function (i) {
1124- var ctx = mContexts[i];
1125- return ctx.name === mCustomContextNameMms;
1126- });
1127-
1128- // make sure there is only one context per type
1129- if (customInternet.length > 1) {
1130- customInternet.forEach(function(currentValue, index, array) {
1131- if (index === 0)
1132- return;
1133- sim.connMan.removeContext(currentValue);
1134- });
1135- }
1136- if (customMms.length > 1) {
1137- customMms.forEach(function(currentValue, index, array) {
1138- if (index === 0)
1139- return;
1140- sim.connMan.removeContext(currentValue);
1141- });
1142- }
1143-
1144- if (customInternet.length !== 0) {
1145- d.__haveCustomContexts = true;
1146- }
1147-
1148- // @bug don't create the custom MMS context
1149- // LP(:#1362795)
1150- // if (customMms.length === 0) {
1151- // sim.connMan.addContext("mms");
1152- // }
1153-
1154- buildLists();
1155- }
1156-
1157- property var mInternetApns : [];
1158- property var mMmsApns : [];
1159- function buildLists()
1160- {
1161- d.__suppressActivation = true;
1162-
1163- var internet = [];
1164- var mms = [];
1165-
1166- internet = Object.keys(mContexts).filter(function(i) {
1167- var ctx = mContexts[i];
1168- if (ctx.type === "internet") {
1169- if (ctx.name === mCustomContextNameInternet) {
1170- mCustomContextInternet = ctx
1171- // don't add yet
1172- return false;
1173- }
1174- return true;
1175- }
1176- return false;
1177- });
1178- mms = Object.keys(mContexts).filter(function(i) {
1179- var ctx = mContexts[i];
1180- if ( ctx.type === "mms") {
1181- if (ctx.name === mCustomContextNameMms) {
1182- mCustomContextMms = ctx;
1183- // don't add yet
1184- return false;
1185- }
1186- return true;
1187- }
1188- return false;
1189- });
1190-
1191- // make sure customized are the last on the lists
1192- if (mCustomContextInternet !== undefined)
1193- internet = internet.concat([mCustomContextInternet.contextPath])
1194- if (mCustomContextMms !== undefined)
1195- mms = mms.concat([mCustomContextMms.contextPath])
1196- else {
1197- /// @bug LP(#1361864)
1198- // add anyway a "dummy" Custom so we can show at least the one provisioned
1199- // MMS context as long as the user does not hit "Custom" in the MMS list.
1200- mms = mms.concat(["dummycustom"])
1201- }
1202-
1203- // add "Same APN as for Internet" to be the first on the MMS list
1204- mms = ["/same/as/internet"].concat(mms);
1205-
1206- mInternetApns = internet;
1207- mMmsApns = mms;
1208-
1209- internetApnSelector.updateSelectedIndex();
1210-
1211- d.__suppressActivation = false;
1212- }
1213-
1214- function openApnEditor(type) {
1215- var ctx;
1216- if (type === "internet") {
1217- ctx = mCustomContextInternet;
1218- } else if (type == "mms") {
1219- ctx = mCustomContextMms;
1220- }
1221- /// can't modify active context
1222- if (ctx !== undefined && ctx.active)
1223- ctx.active = false;
1224-
1225- pageStack.push(Qt.resolvedUrl("CustomApnEditor.qml"),
1226- {
1227- type: type,
1228- contexts: {"internet": mCustomContextInternet,
1229- "mms": mCustomContextMms},
1230- activateCb: activateHelper,
1231- sim: sim
1232- });
1233- }
1234-
1235- function activateHelper(type, contextPath, customMmsData) {
1236- if (type === "internet") {
1237- activator.activate(contextPath, sim.simMng.subscriberIdentity, sim.simMng.modemPath)
1238- }
1239- else if (type === "mms") {
1240- if (contextPath === undefined) {
1241- // LP(:#1362795)
1242- pendingCustomMmsData = customMmsData
1243- /// remove any provisioned ones..
1244- var remove = []
1245- Object.keys(mContexts).forEach(function(currentValue, index, array) {
1246- var ctx = mContexts[currentValue];
1247- if (ctx.type === "mms")
1248- remove = remove.concat([ctx.contextPath])
1249- });
1250- remove.forEach(function(currentValue, index, array) {
1251- sim.connMan.removeContext(currentValue);
1252- });
1253- sim.connMan.addContext("mms")
1254- }
1255- } else {
1256- // somebody is calling the function wrong
1257- // the exception gives a very nice log error message from QmlEngine and does not
1258- // have any side effects
1259- throw "activateHelper: bad data: type: " + type + ", contextPath: " + contextPath + ", customMmsData: " + customMmsData;
1260- }
1261- }
1262- }
1263-
1264- OfonoActivator {
1265- id:activator
1266+ property var editor
1267+
1268+ // Signal that indicates that we have all our contexts.
1269+ signal ready()
1270+ Component.onCompleted: root.ready.connect(Manager.ready)
1271+
1272+ /**
1273+ * We have three ListModels: one for Internet contexts, one for MMS
1274+ * contexts and one for ia contexts. We use OfonoContextConnection qml
1275+ * objects to represents the contexts.
1276+ *
1277+ * The model will have helpful properties:
1278+ * title: Title that goes in the editor.
1279+ * type: A code that tells us what context type this model will have.
1280+ *
1281+ * Model objects will have the following roles:
1282+ * path: the path of the context
1283+ * qml: the QML of the context
1284+ */
1285+
1286+ ListModel {
1287+ id: mmsContexts
1288+ property string title: i18n.tr("MMS APN")
1289+ property string type: 'mms'
1290+ }
1291+
1292+ ListModel {
1293+ id: internetContexts
1294+ property string title: i18n.tr("Internet APN")
1295+ property string type: 'internet'
1296+ }
1297+
1298+ ListModel {
1299+ id: iaContexts
1300+ property string title: i18n.tr("LTE APN")
1301+ property string type: 'ia'
1302 }
1303
1304 Component {
1305- id: connCtx
1306+ id: contextComponent
1307 OfonoContextConnection {
1308-
1309- // add helper property to detect dual internet/MMS contexts
1310- property bool dual : false
1311- onTypeChanged:{
1312- if (type == "internet")
1313- if (messageCenter !== "")
1314- dual = true
1315-
1316- d.buildLists();
1317- }
1318-
1319- onActiveChanged: if (type === "internet") internetApnSelector.updateSelectedIndex()
1320- onNameChanged: {
1321-
1322- if (name === d.mCustomContextNameInternet) {
1323- d.__haveCustomContexts = true;
1324- }
1325-
1326- if (name === "MMS") {
1327- this.name = d.mCustomContextNameMms;
1328- this.accessPointName = d.pendingCustomMmsData["accessPointName"];
1329- this.username = d.pendingCustomMmsData["username"];
1330- this.password = d.pendingCustomMmsData["password"];
1331- this.messageCenter = d.pendingCustomMmsData["messageCenter"];
1332- this.messageProxy = d.pendingCustomMmsData["messageProxy"];
1333- d.pendingCustomMmsData = ({});
1334- }
1335- d.buildLists();
1336- }
1337- onAccessPointNameChanged: d.buildLists()
1338- onReportError: console.error("Context error on " + contextPath + ": " + errorString)
1339- }
1340- }
1341-
1342- Connections {
1343- target: sim.connMan
1344- onContextsChanged: d.updateContexts(contexts)
1345- Component.onCompleted: {
1346- /// @todo workaround the work around that the UI currently only supports max. 1 MMS context
1347- /// remove once nuntium stuff is implemented
1348- // remove all but one MMS context
1349- var mms = Object.keys(d.mContexts).filter(function(i) {
1350- var ctx = d.mContexts[i]
1351- if (ctx.type === "mms")
1352- return true;
1353- return false;
1354- });
1355- mms = mms.slice(1);
1356- mms.forEach(function(currentValue, index, array) {
1357- sim.connMan.removeContext(currentValue);
1358- });
1359-
1360- d.updateContexts();
1361- }
1362- }
1363+ property bool isCombined: type === 'internet' && messageCenter
1364+ property string typeString: {
1365+ if (isCombined) {
1366+ return i18n.tr("Internet and MMS");
1367+ } else if (type === 'internet' && !messageCenter) {
1368+ return i18n.tr("Internet");
1369+ } else if (type === 'ia') {
1370+ return i18n.tr("LTE");
1371+ } else if (type === 'mms') {
1372+ return i18n.tr("MMS");
1373+ } else {
1374+ return type;
1375+ }
1376+ }
1377+ }
1378+ }
1379+
1380+ state: "default"
1381+ states: [
1382+ PageHeadState {
1383+ name: "default"
1384+ head: root.head
1385+ actions: [
1386+ Action {
1387+ iconName: "add"
1388+ objectName: "newApn"
1389+ onTriggered: {
1390+ editor = pageStack.push(Qt.resolvedUrl("PageApnEditor.qml"), {
1391+ manager: Manager,
1392+ mmsModel: mmsContexts,
1393+ internetModel: internetContexts,
1394+ iaModel: iaContexts
1395+ });
1396+ }
1397+ }
1398+ ]
1399+ }
1400+ ]
1401
1402 Flickable {
1403 id: scrollWidget
1404 anchors.fill: parent
1405+ contentWidth: parent.width
1406 contentHeight: contentItem.childrenRect.height
1407- boundsBehavior: (contentHeight > root.height) ? Flickable.DragAndOvershootBounds : Flickable.StopAtBounds
1408- flickableDirection: Flickable.VerticalFlick
1409-
1410+ boundsBehavior: (contentHeight > root.height) ?
1411+ Flickable.DragAndOvershootBounds : Flickable.StopAtBounds
1412
1413 Column {
1414- anchors { left: parent.left; right: parent.right }
1415-
1416- spacing: units.gu(2)
1417-
1418- ListItem.ItemSelector {
1419- id: internetApnSelector
1420- width: parent.width
1421- model: d.mInternetApns
1422- expanded: true
1423- selectedIndex: -1
1424- text: i18n.tr("Internet APN:")
1425- onModelChanged: updateSelectedIndex()
1426-
1427- function updateSelectedIndex()
1428- {
1429- var tmp = d.__suppressActivation
1430- d.__suppressActivation = true;
1431- var idx = -1;
1432- if (model) {
1433- model.forEach(function(currentValue, index, array) {
1434- if (d.mContexts[currentValue].active)
1435- idx = index;
1436- });
1437- }
1438- selectedIndex = idx;
1439- d.__suppressActivation = tmp;
1440- }
1441-
1442- delegate: OptionSelectorDelegate {
1443- showDivider: false
1444- text: {
1445- var ctx = d.mContexts[modelData];
1446- if (ctx.name !== "") {
1447- if (ctx.name !== d.mCustomContextNameInternet) {
1448- return ctx.name
1449- } else {
1450- //: user visible name of the custom Internet APN
1451- return i18n.tr("Custom");
1452- }
1453- } else {
1454- return ctx.accessPointName
1455- }
1456- }
1457- }
1458- onSelectedIndexChanged: {
1459- if (selectedIndex === -1) {
1460- if (mmsApnSelector && mmsApnSelector.model[mmsApnSelector.selectedIndex] === "/same/as/internet")
1461- mmsApnSelector.selectedIndex = -1;
1462- return;
1463- }
1464-
1465- var ctx = d.mContexts[model[selectedIndex]];
1466- if(ctx.dual) {
1467- if (!d.mCustomContextMms)
1468- mmsApnSelector.selectedIndex = mmsApnSelector.model.indexOf("/same/as/internet");
1469- }
1470- else if (mmsApnSelector.model[mmsApnSelector.selectedIndex] === "/same/as/internet")
1471- mmsApnSelector.selectedIndex = -1;
1472-
1473- if (d.__suppressActivation)
1474- return;
1475-
1476- if (d.mCustomContextInternet && model[selectedIndex] === d.mCustomContextInternet.contextPath) {
1477- if (d.mCustomContextInternet.accessPointName === "") {
1478- d.openApnEditor("internet");
1479- updateSelectedIndex();
1480- return;
1481- }
1482- }
1483-
1484- d.activateHelper("internet", ctx.contextPath);
1485- }
1486- }
1487-
1488-
1489- Button {
1490- anchors { horizontalCenter: parent.horizontalCenter }
1491- objectName: "customApnEdit"
1492- text: i18n.tr("Custom Internet APN…")
1493- width: parent.width - units.gu(4)
1494- onClicked: d.openApnEditor("internet")
1495- }
1496-
1497- ListItem.ItemSelector {
1498- id: mmsApnSelector
1499- width: parent.width
1500- model: d.mMmsApns
1501- expanded: true
1502- selectedIndex: -1
1503- text: i18n.tr("MMS APN:")
1504- delegate: OptionSelectorDelegate {
1505- showDivider: modelData === "/same/as/internet"
1506- enabled: {
1507- if (modelData !== "/same/as/internet")
1508- return true;
1509- else {
1510- var tmp = d.mContexts[internetApnSelector.model[internetApnSelector.selectedIndex]]
1511- return tmp === undefined ? false : tmp.dual
1512- }
1513- }
1514- // work around OptionSelectorDelegate not having a visual change depending on being disabled
1515- opacity: enabled ? 1.0 : 0.5
1516- text: {
1517- if (modelData === "/same/as/internet") {
1518- return i18n.tr("Same APN as for Internet");
1519- }
1520- if (modelData === "dummycustom") {
1521- return i18n.tr("Custom");
1522- }
1523- var ctx = d.mContexts[modelData];
1524- if (ctx.name !== "") {
1525- if (ctx.name !== d.mCustomContextNameMms) {
1526- return ctx.name
1527- } else {
1528- //: user visible name of the custom MMS APN
1529- return i18n.tr("Custom");
1530- }
1531- } else {
1532- return ctx.accessPointName
1533- }
1534- }
1535- }
1536- onModelChanged: updateSelectedIndex();
1537- function updateSelectedIndex()
1538- {
1539- // if we have custom MMS context, it must be active.
1540- // @bug LP(#1361864)
1541- var tmp = d.__suppressActivation;
1542- d.__suppressActivation = true;
1543- if (d.mCustomContextMms) {
1544- selectedIndex = model.indexOf(d.mCustomContextMms.contextPath);
1545- } else if (model.length === 3) {
1546- /* meaning we have:
1547- * 0 - /same/as/internet
1548- * 1 - some provisioned one
1549- * 2 - dummycustom
1550- */
1551- selectedIndex = 1;
1552- } else if (internetApnSelector.model && internetApnSelector.selectedIndex !== -1) {
1553- if (d.mContexts[internetApnSelector.model[internetApnSelector.selectedIndex]].dual) {
1554- selectedIndex = model.indexOf("/same/as/internet");
1555- } else
1556- selectedIndex = -1;
1557- } else {
1558- selectedIndex = -1;
1559- }
1560- d.__suppressActivation = tmp;
1561- }
1562-
1563- onSelectedIndexChanged: {
1564- if (selectedIndex === -1 || d.__suppressActivation)
1565- return;
1566-
1567- if (model[selectedIndex] === "/same/as/internet") {
1568- // @bug delete _any_ actual MMS context
1569- // LP:(#1362795)
1570- var remove = [];
1571- Object.keys(d.mContexts).forEach(function(currentValue, index, array) {
1572- var ctx = d.mContexts[currentValue];
1573- if (ctx.type === "mms")
1574- remove = remove.concat([ctx.contextPath]);
1575- });
1576- remove.forEach(function(currentValue, index, array) {
1577- sim.connMan.removeContext(currentValue);
1578- });
1579- return;
1580- }
1581-
1582- if (model[selectedIndex] === "dummycustom") {
1583- d.openApnEditor("mms");
1584- updateSelectedIndex()
1585- return;
1586- }
1587-
1588- // no need to "activate" anything.
1589- // just fall through return here.
1590- // once we actually are able to suppport multiple MMS contexts
1591- // on the system, add some code here to set one of them active
1592- }
1593- }
1594-
1595- Button {
1596- anchors { horizontalCenter: parent.horizontalCenter }
1597- objectName: "customApnEdit"
1598- text: i18n.tr("Custom MMS APN…")
1599- width: parent.width - units.gu(4)
1600- onClicked: {
1601- if (!d.__haveCustomContexts) {
1602- d.checkAndCreateCustomContexts();
1603- d.__haveCustomContexts = true;
1604- }
1605- d.openApnEditor("mms");
1606- }
1607- }
1608-
1609- // @todo: no means of doing any meaningful reset right now.
1610- // LP(#1338758)
1611- // ListItem.ThinDivider {}
1612- // ListItem.SingleControl {
1613- // control: Button {
1614- // objectName: "resetButton"
1615- // text: i18n.tr("Reset APN Settings")
1616- // width: parent.width - units.gu(4)
1617- // onClicked: {
1618- // PopupUtils.open(resetDialog)
1619- // }
1620- // }
1621- // }
1622- }
1623- }
1624-
1625- Component {
1626- id: resetDialog
1627- Dialog {
1628- id: dialogue
1629- title: i18n.tr("Reset APN Settings")
1630- text: i18n.tr("Are you sure that you want to Reset APN Settings?")
1631- Button {
1632- text: i18n.tr("Cancel")
1633- onClicked: PopupUtils.close(dialogue)
1634- }
1635- Button {
1636- text: i18n.tr("Reset")
1637- color: UbuntuColors.orange
1638- onClicked: {
1639- // delete all APNs
1640- // kick ofono per
1641- // https://bugs.launchpad.net/ubuntu/+source/ofono/+bug/1338758
1642-
1643- }
1644- }
1645-
1646- }
1647+ id: apnsCol
1648+ anchors { left: parent.left; right: parent.right }
1649+
1650+ Repeater {
1651+ id: apns
1652+ anchors { left: parent.left; right: parent.right }
1653+ height: childrenRect.height
1654+ model: [internetContexts, mmsContexts, iaContexts]
1655+ delegate: apnsDelegate
1656+ }
1657+ }
1658+
1659+ Button {
1660+ anchors {
1661+ top: apnsCol.bottom
1662+ right: parent.right
1663+ left: parent.left
1664+ margins: units.gu(2)
1665+ }
1666+ text: i18n.tr("Reset All APN Settings…")
1667+ onClicked: PopupUtils.open(resetDialog)
1668+ }
1669+ }
1670+
1671+ Component {
1672+ id: resetDialog
1673+ Dialog {
1674+ id: dialogue
1675+ title: i18n.tr("Reset APN Settings")
1676+ text: i18n.tr("Are you sure that you want to Reset APN Settings?")
1677+
1678+ Button {
1679+ text: i18n.tr("Reset")
1680+ color: UbuntuColors.orange
1681+ onClicked: {
1682+ Manager.reset();
1683+ PopupUtils.close(dialogue);
1684+ }
1685+ }
1686+
1687+ Button {
1688+ text: i18n.tr("Cancel")
1689+ onClicked: PopupUtils.close(dialogue)
1690+ }
1691+ }
1692+ }
1693+
1694+ Component {
1695+ id: disablesInternetWarning
1696+ Dialog {
1697+ id: dialogue
1698+ property OfonoContextConnection mms
1699+ property OfonoContextConnection combined
1700+ /* TRANSLATORS: %1 is the MMS APN that the user has chosen to be
1701+ “preferred”. */
1702+ title: i18n.tr("Prefer %1").arg(mms.name)
1703+ /* TRANSLATORS: %1 is the MMS APN that the user has chosen to be
1704+ “preferred”, i.e. used to retrieve MMS messages. %2 is the Internet
1705+ APN that will be “de-preferred” as a result of this action. */
1706+ text: i18n.tr("You have chosen %1 as your preferred MMS APN. " +
1707+ "This disconnects an Internet connection provided " +
1708+ "by %2.").arg(mms.name).arg(combined.name)
1709+
1710+ Button {
1711+ text: i18n.tr("Disconnect")
1712+ onClicked: {
1713+ Manager.setPreferred(mms, true, true);
1714+ PopupUtils.close(dialogue);
1715+ }
1716+ }
1717+
1718+ Button {
1719+ text: i18n.tr("Cancel")
1720+ onClicked: PopupUtils.close(dialogue)
1721+ }
1722+ }
1723+ }
1724+
1725+ Component {
1726+ id: disablesMMSWarning
1727+ Dialog {
1728+ id: dialogue
1729+ property OfonoContextConnection internet
1730+ property OfonoContextConnection combined
1731+ /* TRANSLATORS: %1 is the Internet APN that the user has chosen to
1732+ be “preferred”. */
1733+ title: i18n.tr("Prefer %1").arg(internet.name)
1734+ /* TRANSLATORS: %1 is the Internet APN that the user has chosen to
1735+ be “preferred”, i.e. used to connect to the Internet. %2 is the MMS
1736+ APN that will be “de-preferred” as a result of this action. */
1737+ text: i18n.tr("You have chosen %1 as your preferred Internet APN. " +
1738+ "This disables the MMS configuration provided " +
1739+ "by %2.").arg(internet.name).arg(combined.name)
1740+
1741+ Button {
1742+ text: i18n.tr("Disable")
1743+ onClicked: {
1744+ Manager.setPreferred(internet, true, true);
1745+ PopupUtils.close(dialogue);
1746+ }
1747+ }
1748+
1749+ Button {
1750+ text: i18n.tr("Cancel")
1751+ onClicked: PopupUtils.close(dialogue)
1752+ }
1753+ }
1754+ }
1755+
1756+ Component {
1757+ id: disableContextWarning
1758+ Dialog {
1759+ id: dialogue
1760+ property OfonoContextConnection context
1761+ /* TRANSLATORS: %1 is the APN that the user has disconnected or
1762+ disabled. */
1763+ title: context.type === 'internet' ?
1764+ i18n.tr("Disconnect %1").arg(context.name) :
1765+ i18n.tr("Disable %1").arg(context.name)
1766+
1767+ /* TRANSLATORS: %1 is the APN that the user has disconnected or
1768+ disabled. */
1769+ text: context.type === 'internet' ?
1770+ i18n.tr("This disconnects %1.").arg(context.name) :
1771+ i18n.tr("This disables %1.").arg(context.name)
1772+
1773+ Button {
1774+ text: context.type === 'mms' ?
1775+ i18n.tr("Disable") :
1776+ i18n.tr("Disconnect")
1777+ onClicked: {
1778+ Manager.setPreferred(context, false, true);
1779+ PopupUtils.close(dialogue);
1780+ }
1781+ }
1782+
1783+ Button {
1784+ text: i18n.tr("Cancel")
1785+ onClicked: PopupUtils.close(dialogue)
1786+ }
1787+ }
1788+ }
1789+
1790+ Component {
1791+ id: apnsDelegate
1792+
1793+ Repeater {
1794+ anchors { left: parent.left; right: parent.right }
1795+ model: modelData
1796+ delegate: apnDelegate
1797+ }
1798+ }
1799+
1800+ Component {
1801+ id: apnDelegate
1802+
1803+ ListItem.Standard {
1804+ id: apnListItem
1805+ property alias text: apnItemName.text
1806+ objectName: "edit_" + qml.name
1807+ height: units.gu(6)
1808+ removable: true
1809+ confirmRemoval: true
1810+ progression: true
1811+
1812+ onItemRemoved: Manager.removeContext(path);
1813+ onClicked: {
1814+ editor = pageStack.push(Qt.resolvedUrl("PageApnEditor.qml"), {
1815+ manager: Manager,
1816+ contextQML: qml,
1817+ mmsModel: mmsContexts,
1818+ internetModel: internetContexts,
1819+ iaModel: iaContexts
1820+ });
1821+ }
1822+
1823+ control: CheckBox {
1824+ id: check
1825+ objectName: qml.name + "_preferred"
1826+ property bool serverChecked: qml && qml.preferred
1827+ onServerCheckedChanged: checked = serverChecked
1828+ Component.onCompleted: checked = serverChecked
1829+ onTriggered: Manager.setPreferred.call(this, qml, checked)
1830+ }
1831+
1832+ Item {
1833+ anchors {
1834+ top: parent.top
1835+ bottom: parent.bottom
1836+ left: parent.left
1837+ leftMargin: units.gu(2)
1838+ right: parent.right
1839+ }
1840+
1841+ Label {
1842+ id: apnItemName
1843+ anchors {
1844+ topMargin: units.gu(1)
1845+ top: parent.top
1846+ left: parent.left
1847+ right: parent.right
1848+ }
1849+
1850+ text: qml.name
1851+ elide: Text.ElideRight
1852+ opacity: apnListItem.enabled ? 1.0 : 0.5
1853+ }
1854+
1855+ Label {
1856+ id: apnItemType
1857+ anchors {
1858+ left: parent.left
1859+ right: parent.right
1860+ top: apnItemName.bottom
1861+ }
1862+
1863+ text: qml.typeString
1864+ color: Theme.palette.normal.backgroundText
1865+ fontSize: "small"
1866+ wrapMode: Text.Wrap
1867+ maximumLineCount: 5
1868+ }
1869+ }
1870+ }
1871+ }
1872+
1873+ Connections {
1874+ target: sim.connMan
1875+
1876+ onContextAdded: Manager.contextAdded(path)
1877+ onContextRemoved: Manager.contextRemoved(path)
1878+ onContextsChanged: Manager.contextsChanged(contexts)
1879+ onReportError: Manager.reportError(message)
1880+ Component.onCompleted: Manager.contextsChanged(sim.connMan.contexts)
1881+ }
1882+
1883+ // We set the target to be ConnMan before we want to call 'ResetContexts' on
1884+ // ConnMan. When ConnMan powers down, the connManPoweredChanged handler is
1885+ // called and call 'ResetContexts'. This is because we can't make this call
1886+ // while ConnMan is 'Powered'. After the 'ResetContexts' call is done,
1887+ // the target is reset to null.
1888+ Connections {
1889+ id: restorePowered
1890+ target: null
1891+ ignoreUnknownSignals: true
1892+ onPoweredChanged: Manager.connManPoweredChanged(powered)
1893 }
1894 }
1895
1896=== added file 'plugins/cellular/apn_editor.js'
1897--- plugins/cellular/apn_editor.js 1970-01-01 00:00:00 +0000
1898+++ plugins/cellular/apn_editor.js 2015-07-27 14:21:15 +0000
1899@@ -0,0 +1,192 @@
1900+/*
1901+ * This file is part of system-settings
1902+ *
1903+ * Copyright (C) 2015 Canonical Ltd.
1904+ *
1905+ * Contact: Jonas G. Drange <jonas.drange@canonical.com>
1906+ *
1907+ * This program is free software: you can redistribute it and/or modify it
1908+ * under the terms of the GNU General Public License version 3, as published
1909+ * by the Free Software Foundation.
1910+ *
1911+ * This program is distributed in the hope that it will be useful, but
1912+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1913+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1914+ * PURPOSE. See the GNU General Public License for more details.
1915+ *
1916+ * You should have received a copy of the GNU General Public License along
1917+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1918+ *
1919+ * This is a collection of functions to help the custom apn editor.
1920+ */
1921+
1922+/**
1923+ * Updates the given OfonoContextConnection by using values from the editor.
1924+ *
1925+ * @param {OfonoContextConnection} qml to be updated
1926+*/
1927+function updateContextQML (ctx) {
1928+ var toType = indexToType(typeSel.selectedIndex);
1929+ toType = toType === 'internet+mms' ? 'internet' : toType;
1930+ ctx.disconnect();
1931+ ctx.name = name.text;
1932+ ctx.type = toType;
1933+ ctx.accessPointName = accessPointName.text;
1934+ ctx.username = username.text;
1935+ ctx.password = password.text;
1936+ ctx.messageCenter = messageCenter.visible ? messageCenter.text : '';
1937+ ctx.messageProxy = messageProxy.visible ? messageProxy.text + (port.text ? ':' + port.text : '') : '';
1938+}
1939+
1940+/**
1941+ * Populates editor with values from a OfonoContextConnection.
1942+ *
1943+ * @param {OfonoContextConnection} qml to use as reference
1944+*/
1945+function populate (ctx) {
1946+ name.text = ctx.name;
1947+ accessPointName.text = ctx.accessPointName;
1948+ username.text = ctx.username;
1949+ password.text = ctx.password;
1950+ messageCenter.text = ctx.messageCenter;
1951+ messageProxy.text = ctx.messageProxy;
1952+
1953+ if (ctx.isCombined) {
1954+ typeSel.selectedIndex = typeToIndex('internet+mms');
1955+ } else {
1956+ typeSel.selectedIndex = typeToIndex(ctx.type);
1957+ }
1958+}
1959+
1960+/**
1961+ * Handler for when a user saves a context.
1962+*/
1963+function saving () {
1964+ var model;
1965+ var type;
1966+ root.saving();
1967+
1968+ // Edit or new? Create a context if it does not exist.
1969+ if (contextQML) {
1970+ updateContextQML(contextQML);
1971+ root.saved();
1972+ } else {
1973+ type = indexToType();
1974+ if (type === 'internet+mms') type = 'internet';
1975+ manager.createContext(type);
1976+ }
1977+}
1978+
1979+/**
1980+ * Handler for new contexts.
1981+ *
1982+ * @param {OfonoContextConnection} new context
1983+*/
1984+function newContext (context) {
1985+ // Start a timer that will update the context.
1986+ // Ofono and libqofono seems to be very unreliable
1987+ // when it comes to how a context is created,
1988+ // so we just wait a couple of seconds until we're
1989+ // sure the context exists and can be edited.
1990+ updateContext.ctx = context;
1991+ updateContext.start();
1992+}
1993+
1994+/**
1995+ * Checks whether or not a link has a http protocol prefix.
1996+ *
1997+ * @param {String} link to check
1998+*/
1999+function hasProtocol (link) {
2000+ return link.search(/^http[s]?\:\/\//) == -1;
2001+}
2002+
2003+/**
2004+ * Prepend http:// to a link if there isn't one.
2005+ *
2006+ * @param {String} link to add http:// to
2007+ * @return {String} changed link
2008+*/
2009+function setHttp (link) {
2010+ if (hasProtocol(link)) {
2011+ link = 'http://' + link;
2012+ }
2013+ return link;
2014+}
2015+
2016+/**
2017+ * Asks whether or not the values in the editor is valid or not.
2018+ *
2019+ * @return {Boolean} whether or not the editor is valid
2020+*/
2021+function isValid () {
2022+ if (isMms || isCombo) {
2023+ return name.text &&
2024+ accessPointName.text &&
2025+ messageCenter.text;
2026+ } else {
2027+ return name.text &&
2028+ accessPointName.text;
2029+ }
2030+}
2031+
2032+/**
2033+ * Given a type, this asks what index of the type selector
2034+ * it corresponds to.
2035+ *
2036+ * @param {String} type to check
2037+ * @return {Number} of index
2038+*/
2039+function typeToIndex (type) {
2040+ if (type === 'internet+mms') return 0;
2041+ if (type === 'internet') return 1;
2042+ if (type === 'mms') return 2;
2043+ if (type === 'ia') return 3;
2044+}
2045+
2046+/**
2047+ * Given an index, we ask what type this index corresponds to.
2048+ *
2049+ * @param {Number} [optional] index to check
2050+ * @return {String} type it corresponds to
2051+*/
2052+function indexToType (index) {
2053+ if (typeof index === 'undefined') {
2054+ index = typeSel.selectedIndex;
2055+ }
2056+ if (index === 0) return 'internet+mms';
2057+ if (index === 1) return 'internet';
2058+ if (index === 2) return 'mms';
2059+ if (index === 3) return 'ia';
2060+}
2061+
2062+function ready () {
2063+ _edgeReady = true;
2064+}
2065+
2066+function makeMeVisible(item) {
2067+ if (!_edgeReady || !item) {
2068+ return;
2069+ }
2070+
2071+ root.activeItem = item;
2072+
2073+ var position = scrollArea.contentItem.mapFromItem(item, 0, root.activeItem.y);
2074+
2075+ // check if the item is already visible
2076+ var bottomY = scrollArea.contentY + scrollArea.height;
2077+ var itemBottom = position.y + item.height; // extra margin
2078+ if (position.y >= scrollArea.contentY && itemBottom <= bottomY) {
2079+ return;
2080+ }
2081+
2082+ // if it is not, try to scroll and make it visible
2083+ var targetY = itemBottom - scrollArea.height;
2084+ if (targetY >= 0 && position.y) {
2085+ scrollArea.contentY = targetY;
2086+ } else if (position.y < scrollArea.contentY) {
2087+ // if it is hidden at the top, also show it
2088+ scrollArea.contentY = position.y;
2089+ }
2090+ scrollArea.returnToBounds();
2091+}
2092
2093=== renamed file 'plugins/cellular/apn.js' => 'plugins/cellular/apn_manager.js'
2094--- plugins/cellular/apn.js 2015-03-26 21:57:53 +0000
2095+++ plugins/cellular/apn_manager.js 2015-07-27 14:21:15 +0000
2096@@ -1,61 +1,589 @@
2097-var _CUSTOM_INTERNET_CONTEXT_NAME = '___ubuntu_custom_apn_internet';
2098-var _CUSTOM_MMS_CONTEXT_NAME = '___ubuntu_custom_apn_mms';
2099-
2100-/*
2101-Updates a context with new values.
2102-
2103-@param {QOfonoConnectionContext} context
2104-@param {Object} values dict with new values
2105-*/
2106-function updateContext (context, values) {
2107- var messageProxy;
2108- console.warn('updateContext', values.accessPointName, values.messageCenter,
2109- values.messageProxy, values.port, values.useraccessPointName,
2110- values.password, values.type);
2111- if (typeof values.accessPointName !== 'undefined') {
2112- context.accessPointName = values.accessPointName;
2113- }
2114- if (typeof values.messageCenter !== 'undefined') {
2115- context.messageCenter = values.messageCenter;
2116- }
2117- if (typeof values.messageProxy !== 'undefined') {
2118- messageProxy = values.messageProxy;
2119- if (messageProxy !== '') {
2120- messageProxy = messageProxy + ':' + values.port;
2121- }
2122- context.messageProxy = messageProxy;
2123- }
2124- if (typeof values.username !== 'undefined') {
2125- context.username = values.username;
2126- }
2127- if (typeof values.password !== 'undefined') {
2128- context.password = values.password;
2129- }
2130- if (typeof values.type !== 'undefined') {
2131- context.type = values.type;
2132- }
2133-
2134- if (context.type === 'internet') {
2135- context.name = CUSTOM_INTERNET_CONTEXT_NAME();
2136- } else if (context.type === 'mms') {
2137- context.name = CUSTOM_MMS_CONTEXT_NAME();
2138- }
2139-}
2140-
2141-/*
2142-Exposes the custom internet context name to the world.
2143-
2144-@return {String} custom internet context name
2145-*/
2146-function CUSTOM_INTERNET_CONTEXT_NAME () {
2147- return _CUSTOM_INTERNET_CONTEXT_NAME;
2148-}
2149-
2150-/*
2151-Exposes the custom mms context name to the world.
2152-
2153-@return {String} custom mms context name
2154-*/
2155-function CUSTOM_MMS_CONTEXT_NAME () {
2156- return _CUSTOM_MMS_CONTEXT_NAME;
2157+/*
2158+ * This file is part of system-settings
2159+ *
2160+ * Copyright (C) 2015 Canonical Ltd.
2161+ *
2162+ * Contact: Jonas G. Drange <jonas.drange@canonical.com>
2163+ *
2164+ * This program is free software: you can redistribute it and/or modify it
2165+ * under the terms of the GNU General Public License version 3, as published
2166+ * by the Free Software Foundation.
2167+ *
2168+ * This program is distributed in the hope that it will be useful, but
2169+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2170+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2171+ * PURPOSE. See the GNU General Public License for more details.
2172+ *
2173+ * You should have received a copy of the GNU General Public License along
2174+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2175+ *
2176+ * This is a collection of functions to help dynamic creation and deletion
2177+ * of ofono contexts.
2178+ */
2179+
2180+// Map of path to QOfonoContextConnection objects
2181+var _pathToQml = {};
2182+var _totalContext = 0;
2183+var _validContexts = 0;
2184+
2185+/**
2186+ * Get the list model corresponding to a given type.
2187+ *
2188+ * @throws {Error} if the type was not mms|internet|ia
2189+ * @param {String} type of model to get
2190+ * @return {ListModel} model that matches type
2191+*/
2192+function getModelFromType (type) {
2193+ var model;
2194+ switch (type) {
2195+ case 'mms':
2196+ model = mmsContexts;
2197+ break;
2198+ case 'internet':
2199+ case 'internet+mms':
2200+ model = internetContexts;
2201+ break;
2202+ case 'ia':
2203+ model = iaContexts;
2204+ break;
2205+ default:
2206+ throw new Error('Unknown context type ' + type);
2207+ }
2208+ return model;
2209+}
2210+
2211+/**
2212+ * Get QML for a context path.
2213+ *
2214+ * @param {String} path of context
2215+ * @return {QOfonoContextConnection|null} qml from path or null if none found
2216+*/
2217+function getContextQML (path) {
2218+ if (!_pathToQml.hasOwnProperty(path)) {
2219+ return null;
2220+ } else {
2221+ return _pathToQml[path];
2222+ }
2223+}
2224+
2225+/**
2226+ * Given an array of paths, it will create and associate
2227+ * an QOfonoContextConnection QML object for each new path.
2228+ *
2229+ * It will also delete any QML that is not in given list of paths.
2230+ *
2231+ * @param {Array} paths, array of operator paths
2232+ */
2233+function updateQML (paths) {
2234+ _garbageCollect(paths);
2235+ _createQml(paths);
2236+}
2237+
2238+/**
2239+ * Destroys QML and makes sure to remove from the
2240+ * appropriate model.
2241+ *
2242+ * @param {String} path of object to delete
2243+ * @return {Boolean} deletion successful
2244+ */
2245+function deleteQML (path) {
2246+ var ctx;
2247+ var i;
2248+ if (!_pathToQml.hasOwnProperty(path)) {
2249+ return false;
2250+ } else {
2251+ ctx = _pathToQml[path];
2252+
2253+ [mmsContexts, internetContexts, iaContexts].forEach(function (model) {
2254+ for (i = 0; i < model.count; i++) {
2255+ if (ctx.contextPath == model.get(i).path) {
2256+ model.remove(i);
2257+ break;
2258+ }
2259+ }
2260+ });
2261+
2262+ _pathToQml[path].destroy();
2263+ delete _pathToQml[path];
2264+ return true;
2265+ }
2266+}
2267+
2268+/**
2269+ * Removes QML that no longer exists in list of paths.
2270+ *
2271+ * @param {Array:String} paths we use as reference.
2272+ */
2273+function _garbageCollect (paths) {
2274+ var path;
2275+ for (path in _pathToQml) {
2276+ if (_pathToQml.hasOwnProperty(path)) {
2277+ if (paths.indexOf(path) === -1) {
2278+ deleteQML(path);
2279+ _totalContext--;
2280+ }
2281+ }
2282+ }
2283+}
2284+
2285+/**
2286+ * Creates QML for list in paths.
2287+ *
2288+ * @param {Array:String} list of paths
2289+ * @param {String} path to the modem
2290+ */
2291+function _createQml (paths) {
2292+ var ctx;
2293+ paths.forEach(function (path, i) {
2294+ if (!_pathToQml.hasOwnProperty(path)) {
2295+
2296+ ctx = createContextQml(path);
2297+
2298+ // Some contexts have a name, others do not. Normalize this.
2299+ if (!ctx.name) {
2300+ ctx.nameChanged.connect(contextNameChanged.bind(ctx));
2301+ } else {
2302+ contextNameChanged.bind(ctx)(ctx.name);
2303+ }
2304+
2305+ // Some context come with a type, others not. Normalize this.
2306+ if (!ctx.type) {
2307+ ctx.typeChanged.connect(contextTypeChanged.bind(ctx));
2308+ } else {
2309+ addContextToModel(ctx);
2310+ }
2311+
2312+ ctx.validChanged.connect(contextValidChanged.bind(ctx));
2313+ ctx.activeChanged.connect(contextActiveChanged.bind(ctx));
2314+
2315+ _pathToQml[path] = ctx;
2316+ _totalContext++;
2317+ }
2318+ });
2319+}
2320+
2321+/**
2322+ * Creates a OfonoContextConnection qml object from a given path.
2323+ * Since the components are all local, this will always return an object.
2324+ *
2325+ * @param {String} path of context
2326+ * @return {OfonoContextConnection} qml that was created
2327+*/
2328+function createContextQml (path) {
2329+ if (!_pathToQml.hasOwnProperty(path)) {
2330+ return contextComponent.createObject(root, {
2331+ 'contextPath': path,
2332+ 'modemPath': sim.path
2333+ });
2334+ } else {
2335+ return _pathToQml[path];
2336+ }
2337+}
2338+
2339+/**
2340+ * Creates a context of a certain type.
2341+ *
2342+ * @param {String} type of context to be created.
2343+*/
2344+function createContext (type) {
2345+ sim.connMan.addContext(type);
2346+}
2347+
2348+/**
2349+ * Removes a context. We don't remove any QML until we receive signal from
2350+ * ofono that the context was removed, but we disconnect it if active.
2351+ *
2352+ * @param {String} path of context to be removed
2353+*/
2354+function removeContext (path) {
2355+ var ctx = getContextQML(path);
2356+
2357+ if (ctx && ctx.active) {
2358+ ctx.disconnect();
2359+ }
2360+
2361+ sim.connMan.removeContext(path);
2362+}
2363+
2364+/**
2365+ * Adds a context to the appropriate model. If the context to be added is found
2366+ * in another model, which will happen if the user changes type of the context,
2367+ * we remove it from the old model and add it to the new.
2368+ *
2369+ * @param {OfonoContextConnection} ctx to be added
2370+ * @param {String} [optional] type of context
2371+*/
2372+function addContextToModel (ctx, type) {
2373+ var data = {
2374+ path: ctx.contextPath,
2375+ qml: ctx
2376+ };
2377+ var model;
2378+ var oldModel;
2379+ var haveContext;
2380+
2381+ // We will move a model if it already exist.
2382+ [mmsContexts, internetContexts, iaContexts].forEach(function (m) {
2383+ var i;
2384+ for (i = 0; i < m.count && !haveContext; i++) {
2385+ if (ctx.contextPath == m.get(i).path) {
2386+ haveContext = m.get(i);
2387+ oldModel = m;
2388+ break;
2389+ }
2390+ }
2391+ });
2392+
2393+ if (typeof type === 'undefined') {
2394+ type = ctx.type;
2395+ }
2396+
2397+ if (haveContext && oldModel) {
2398+ oldModel.remove(haveContext);
2399+ }
2400+
2401+ model = getModelFromType(type);
2402+ model.append(data);
2403+}
2404+
2405+/**
2406+ * Removes a context from the appropriate model.
2407+ *
2408+ * @param {OfonoContextConnection} ctx to be removed
2409+ * @param {String} [optional] type of context
2410+*/
2411+function removeContextFromModel (ctx, type) {
2412+ var model = getModelFromType(type);
2413+ var i;
2414+
2415+ if (typeof type === 'undefined') {
2416+ type = ctx.type;
2417+ }
2418+
2419+ for (i = 0; i < model.count; i++) {
2420+ if (model.get(i).path === ctx.contextPath) {
2421+ model.remove(i);
2422+ return;
2423+ }
2424+ }
2425+}
2426+
2427+/**
2428+ * Handler for removed contexts.
2429+ *
2430+ * @param {String} path that was removed
2431+*/
2432+function contextRemoved (path) {
2433+ var paths = sim.connMan.contexts.slice(0);
2434+ var updatedPaths = paths.filter(function (val) {
2435+ return val !== path;
2436+ });
2437+ _garbageCollect(paths);
2438+}
2439+
2440+/**
2441+ * Handler for when a type has been determined. If a contex changes type,
2442+ * we need to move it to the correct model.
2443+ * Note that “this' refers to the context on which type changed.
2444+ *
2445+ * @param {String} type
2446+ */
2447+function contextTypeChanged (type) {
2448+ addContextToModel(this, type);
2449+}
2450+
2451+/**
2452+ * Handler for when validity of context changes.
2453+ * Note that “this' refers to the context on which valid changed.
2454+ *
2455+ * @param {Boolean} valid
2456+ */
2457+function contextValidChanged (valid) {
2458+ if (valid) {
2459+ _validContexts++;
2460+ } else {
2461+ _validContexts--;
2462+ }
2463+
2464+ if (_validContexts === _totalContext) {
2465+ root.ready();
2466+ }
2467+}
2468+
2469+/**
2470+ * Handler for when active changes.
2471+ * Note that “this' refers to the context on which active changed.
2472+ *
2473+ * @param {Boolean} active
2474+ */
2475+function contextActiveChanged (active) {
2476+ if (active) {
2477+ checkPreferred();
2478+ }
2479+}
2480+
2481+/**
2482+ * This is code that is supposed to identify new contexts that user creates.
2483+ * If we think the context is new, and the editor page is open, we notify it.
2484+ *
2485+ * Note that “this' refers to the context on which name changed.
2486+ *
2487+ * @param {String} name's new value
2488+*/
2489+function contextNameChanged (name) {
2490+ switch (name) {
2491+ case 'Internet':
2492+ case 'IA':
2493+ case 'MMS':
2494+ if (editor) {
2495+ editor.newContext(this);
2496+ }
2497+ break;
2498+ }
2499+ this.nameChanged.disconnect(contextNameChanged);
2500+}
2501+
2502+/**
2503+ * Handler for added contexts.
2504+ *
2505+ * @param {String} path which was added
2506+ */
2507+function contextAdded (path) {
2508+ _createQml([path]);
2509+}
2510+
2511+/**
2512+ * Handler for when contexts change.
2513+ *
2514+ * @param {Array:String} paths after change
2515+ */
2516+function contextsChanged (paths) {
2517+ updateQML(paths);
2518+ checkPreferred();
2519+}
2520+
2521+/**
2522+ * Handler for when errors are reported from ofono.
2523+ *
2524+ * @param {String} message from libqofono
2525+ */
2526+function reportError (message) {
2527+ console.error(message);
2528+}
2529+
2530+/**
2531+ * Set Preferred on a context. A side effect of this, if value is true, is
2532+ * that all other contexts of the same type will get de-preferred. If an
2533+ * MMS context is preferred, all other MMS contexts are de-preferred.
2534+ *
2535+ * There is also a case where if you prefer an MMS context when there already
2536+ * is a preferred Internet+MMS (combined) context. In this case we prompt the
2537+ * user what to do.
2538+ *
2539+ * Note: If triggered by a CheckBox, the “this” argument will be the CheckBox.
2540+ *
2541+ * @param {OfonoContextConnection} context to prefer
2542+ * @param {Boolean} new preferred value
2543+ * @param {Boolean} whether or not to force this action, even though it may
2544+ * decrease the level of connectivity of the user.
2545+*/
2546+function setPreferred (context, value, force) {
2547+ var conflictingContexts = getConflictingContexts(context);
2548+ var mmsPreferralCausesCombinedDePreferral;
2549+ var internetPreferralCausesCombinedDePreferral;
2550+
2551+ // The context is about to be de-preferred.
2552+ if (!value) {
2553+ if (force) {
2554+ context.preferred = false;
2555+ } else {
2556+ PopupUtils.open(disableContextWarning, root, {
2557+ context: context
2558+ });
2559+ this.checked = true;
2560+ }
2561+ return;
2562+ }
2563+
2564+ // If user is preferring standalone Internet or standalone MMS context,
2565+ // over a combined context, we will give a warning, if not forced.
2566+ conflictingContexts.forEach(function (ctxC) {
2567+ if (ctxC.isCombined) {
2568+ if (context.type === 'mms') {
2569+ mmsPreferralCausesCombinedDePreferral = ctxC;
2570+ }
2571+
2572+ if (context.type === 'internet') {
2573+ internetPreferralCausesCombinedDePreferral = ctxC;
2574+ }
2575+ }
2576+ });
2577+
2578+ if (mmsPreferralCausesCombinedDePreferral && !force) {
2579+ PopupUtils.open(disablesInternetWarning, root, {
2580+ combined: mmsPreferralCausesCombinedDePreferral,
2581+ mms: context
2582+ });
2583+ this.checked = false;
2584+ return;
2585+ } else if (internetPreferralCausesCombinedDePreferral && !force) {
2586+ PopupUtils.open(disablesMMSWarning, root, {
2587+ combined: internetPreferralCausesCombinedDePreferral,
2588+ internet: context
2589+ });
2590+ this.checked = false;
2591+ return;
2592+ }
2593+
2594+ conflictingContexts.forEach(function (ctx) {
2595+ ctx.preferred = false;
2596+ });
2597+
2598+ context.preferred = true;
2599+}
2600+
2601+/**
2602+ * Reset apn configuration.
2603+ */
2604+function reset () {
2605+ // If cellular data is on, we need to turn it off. The reset itself,
2606+ // as well as turning cellular data back on, is done by the use of a
2607+ // Connection component and connManPoweredChanged.
2608+ if (sim.connMan.powered) {
2609+ restorePowered.target = sim.connMan;
2610+ sim.connMan.powered = false;
2611+ } else {
2612+ connManPoweredChanged(sim.connMan.powered);
2613+ }
2614+}
2615+
2616+
2617+/**
2618+ * Handler for when powered changed. This handler is attached to a signal by
2619+ * a Connections component in PageChooseApn.qml.
2620+ */
2621+function connManPoweredChanged (powered) {
2622+ if (!powered) {
2623+
2624+ // We want to fire the ready signal again, once we've reset, but
2625+ // the reset contexts won't necessarily fire 'validChanged' signals,
2626+ // so we manually set valid contexts to 0.
2627+ _validContexts = 0;
2628+ root.ready.connect(ready);
2629+
2630+ sim.connMan.resetContexts();
2631+
2632+ // If restorePowered had a target, we know to turn cellular
2633+ // data back on.
2634+ if (restorePowered.target) {
2635+ sim.connMan.powered = true;
2636+ }
2637+ }
2638+ restorePowered.target = null;
2639+}
2640+
2641+/**
2642+ * Checks if there are preferred contexts. If there are none,
2643+ * we prefer the active one.
2644+ */
2645+function checkPreferred () {
2646+ var models = [internetContexts, iaContexts, mmsContexts];
2647+
2648+ models.forEach(function (model) {
2649+ var i;
2650+ var ctx;
2651+ var activeCtx;
2652+
2653+ // Find active contexts in model.
2654+ for (i = 0; i < model.count; i++) {
2655+ ctx = model.get(i).qml;
2656+
2657+ if (ctx.active) {
2658+ activeCtx = ctx;
2659+ }
2660+ }
2661+
2662+ if (activeCtx && getConflictingContexts(activeCtx).length === 0) {
2663+ activeCtx.preferred = true;
2664+ }
2665+ });
2666+}
2667+
2668+/**
2669+ * Gives a list of conflicting contexts, i.e. contexts that are preferred
2670+ * and will create problems† if preferred at the same time as the given
2671+ * context.
2672+ *
2673+ * † Multiple preferred contexts of the same type will cause undefined
2674+ * Nuntium and NetworkManager behaviour.
2675+ *
2676+ * @param {OfonoContextConnection|String} context to be preferred and to check
2677+ conflicts against, or type as string
2678+ * @return {Array:OfonoContextConnection} list of OfonoContextConnection that
2679+ * are in conflict, excluding itself
2680+ *
2681+ */
2682+function getConflictingContexts (context) {
2683+ var type;
2684+ var conflicts = [];
2685+ var i;
2686+ var ctxI;
2687+ var typeModel;
2688+
2689+ if (typeof context === 'string') {
2690+ type = context;
2691+ } else {
2692+ type = context.isCombined ? 'internet+mms' : context.type;
2693+ }
2694+
2695+ switch (type) {
2696+ // A combined context will conflict with internet contexts and MMS
2697+ // contexts.
2698+ case 'internet+mms':
2699+ [internetContexts, mmsContexts].forEach(function (model) {
2700+ var i;
2701+ for (i = 0; i < model.count; i++) {
2702+ ctxI = model.get(i).qml;
2703+ if (ctxI.preferred) {
2704+ conflicts.push(ctxI);
2705+ }
2706+ }
2707+ });
2708+ break;
2709+ // An MMS context will conflict with other MMS contexts, as well as
2710+ // combined contexts.
2711+ case 'mms':
2712+ for (i = 0; i < mmsContexts.count; i++) {
2713+ ctxI = mmsContexts.get(i).qml;
2714+ if (ctxI.preferred) {
2715+ conflicts.push(ctxI);
2716+ }
2717+ }
2718+ for (i = 0; i < internetContexts.count; i++) {
2719+ ctxI = internetContexts.get(i).qml;
2720+ if (ctxI.isCombined && ctxI.preferred) {
2721+ conflicts.push(ctxI);
2722+ }
2723+ }
2724+ break;
2725+ case 'internet':
2726+ case 'ia':
2727+ typeModel = getModelFromType(type);
2728+ for (i = 0; i < typeModel.count; i++) {
2729+ ctxI = typeModel.get(i).qml;
2730+ if (ctxI.preferred) {
2731+ conflicts.push(ctxI);
2732+ }
2733+ }
2734+ break;
2735+ default:
2736+ throw new Error('Can\'t resolve conflicts for type' + type);
2737+ }
2738+
2739+ return conflicts;
2740+}
2741+
2742+function ready () {
2743+ checkPreferred();
2744+ root.ready.disconnect(ready);
2745 }
2746
2747=== removed file 'plugins/cellular/ofonoactivator.cpp'
2748--- plugins/cellular/ofonoactivator.cpp 2014-10-09 10:50:50 +0000
2749+++ plugins/cellular/ofonoactivator.cpp 1970-01-01 00:00:00 +0000
2750@@ -1,132 +0,0 @@
2751-/*
2752- * Copyright (C) 2014 Canonical, Ltd.
2753- *
2754- * Authors:
2755- * Jussi Pakkanen <jussi.pakkanen@canonical.com>
2756- *
2757- * This program is free software: you can redistribute it and/or modify it
2758- * under the terms of the GNU General Public License version 3, as published
2759- * by the Free Software Foundation.
2760- *
2761- * This library is distributed in the hope that it will be useful, but WITHOUT
2762- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
2763- * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
2764- * details.
2765- *
2766- * You should have received a copy of the GNU General Public License
2767- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2768- */
2769-
2770-#include "ofonoactivator.h"
2771-
2772-#include"nm_manager_proxy.h"
2773-#include"nm_settings_proxy.h"
2774-#include"nm_settings_connection_proxy.h"
2775-#include<QCoreApplication>
2776-
2777-typedef QMap<QString,QVariantMap> Vardict;
2778-Q_DECLARE_METATYPE(Vardict)
2779-
2780-namespace {
2781-
2782-QString nmService("org.freedesktop.NetworkManager");
2783-QString nmSettingsPath("/org/freedesktop/NetworkManager/Settings");
2784-QString nmPath("/org/freedesktop/NetworkManager");
2785-
2786-QDBusObjectPath detectConnection(const QString &ofonoContext, const QString imsi) {
2787- auto ofonoContextBase = ofonoContext.split('/').back();
2788- auto target = "/" + imsi + "/" + ofonoContextBase;
2789-
2790- OrgFreedesktopNetworkManagerSettingsInterface settings(nmService, nmSettingsPath,
2791- QDBusConnection::systemBus());
2792- auto reply = settings.ListConnections();
2793- reply.waitForFinished();
2794- if(!reply.isValid()) {
2795- qWarning() << "Error getting connection list: " << reply.error().message() << "\n";
2796- }
2797- auto connections = reply.value(); // Empty list if failed.
2798-
2799- for(const auto &c : connections) {
2800- OrgFreedesktopNetworkManagerSettingsConnectionInterface connProxy(nmService,
2801- c.path(), QDBusConnection::systemBus());
2802- auto reply2 = connProxy.GetSettings();
2803- reply2.waitForFinished();
2804- if(!reply2.isValid()) {
2805- qWarning() << "Error getting property: " << reply2.error().message() << "\n";
2806- continue;
2807- }
2808- auto settings = reply2.value();
2809- auto id = settings["connection"]["id"].toString();
2810- if(id == target) {
2811- return c;
2812- }
2813- }
2814- return QDBusObjectPath("");
2815-}
2816-
2817-QDBusObjectPath detectDevice(const QString &modemPath) {
2818- OrgFreedesktopNetworkManagerInterface nm(nmService, nmPath, QDBusConnection::systemBus());
2819- auto reply = nm.GetDevices();
2820- reply.waitForFinished();
2821- auto devices = reply.value();
2822-
2823- for(const auto &device : devices) {
2824- QDBusInterface iface(nmService, device.path(), "org.freedesktop.DBus.Properties",
2825- QDBusConnection::systemBus());
2826- QDBusReply<QDBusVariant> ifaceReply = iface.call("Get",
2827- "org.freedesktop.NetworkManager.Device", "Interface");
2828- if(!ifaceReply.isValid()) {
2829- qWarning() << "Error getting property: " << ifaceReply.error().message() << "\n";
2830- continue;
2831- }
2832- auto devIface = ifaceReply.value().variant().toString();
2833- if(devIface == modemPath) {
2834- return device;
2835- }
2836- }
2837- return QDBusObjectPath("");
2838-}
2839-}
2840-
2841-void activateOfono(QDBusObjectPath connection, QDBusObjectPath device)
2842-{
2843- OrgFreedesktopNetworkManagerInterface nm(nmService, nmPath, QDBusConnection::systemBus());
2844-
2845-
2846- /// @bug https://bugs.launchpad.net/ubuntu/+source/network-manager/+bug/1378102
2847- /// This is a big and fat workaround for the above bug
2848- // this is probably racy as well as we are not tracking the ActiveConnection to know
2849- // when the Device has actually disconnected.
2850- QDBusInterface dev_iface("org.freedesktop.NetworkManager",
2851- device.path(),
2852- "org.freedesktop.NetworkManager.Device",
2853- nm.connection());
2854- dev_iface.call("Disconnect");
2855-
2856-
2857- nm.ActivateConnection(connection, device, QDBusObjectPath("/"));
2858-}
2859-
2860-OfonoActivator::OfonoActivator(QObject *parent) : QObject(parent) {
2861- static bool isRegistered = false;
2862- if(!isRegistered) {
2863- qDBusRegisterMetaType<Vardict>();
2864- isRegistered = true;
2865- }
2866-}
2867-
2868-Q_INVOKABLE bool OfonoActivator::activate(const QString ofonoContext, const QString imsi, const QString modemPath)
2869-{
2870- auto dev = detectDevice(modemPath);
2871- if(dev.path() == "") {
2872- qWarning() << "Could not detect device object to use for Ofono activation.\n";
2873- return false;
2874- }
2875- auto conn = detectConnection(ofonoContext, imsi);
2876- if(conn.path() == "") {
2877- qWarning() << "Could not detect connection object to use for Ofono activation.\n";
2878- return false;
2879- }
2880- activateOfono(conn, dev);
2881- return true;
2882-}
2883
2884=== removed file 'plugins/cellular/ofonoactivator.h'
2885--- plugins/cellular/ofonoactivator.h 2014-08-27 09:11:52 +0000
2886+++ plugins/cellular/ofonoactivator.h 1970-01-01 00:00:00 +0000
2887@@ -1,42 +0,0 @@
2888-/*
2889- * Copyright (C) 2014 Canonical, Ltd.
2890- *
2891- * Authors:
2892- * Jussi Pakkanen <jussi.pakkanen@canonical.com>
2893- *
2894- * This program is free software: you can redistribute it and/or modify it
2895- * under the terms of the GNU General Public License version 3, as published
2896- * by the Free Software Foundation.
2897- *
2898- * This library is distributed in the hope that it will be useful, but WITHOUT
2899- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
2900- * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
2901- * details.
2902- *
2903- * You should have received a copy of the GNU General Public License
2904- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2905- */
2906-
2907-#ifndef OFONO_ACTIVATOR
2908-#define OFONO_ACTIVATOR
2909-
2910-#include <QObject>
2911-
2912-/**
2913- * For exposing ofono controls to qml.
2914- */
2915-
2916-class OfonoActivator : public QObject {
2917- Q_OBJECT
2918-
2919-public:
2920- OfonoActivator(QObject *parent = nullptr);
2921- ~OfonoActivator() {};
2922-
2923- Q_INVOKABLE bool activate(const QString ofonoContext, const QString imsi, const QString modemPath);
2924-
2925-private:
2926-};
2927-
2928-
2929-#endif
2930
2931=== modified file 'plugins/cellular/plugin.cpp'
2932--- plugins/cellular/plugin.cpp 2015-01-21 20:44:20 +0000
2933+++ plugins/cellular/plugin.cpp 2015-07-27 14:21:15 +0000
2934@@ -21,7 +21,6 @@
2935 #include <QtQml/QQmlContext>
2936 #include "connectivity.h"
2937 #include "hotspotmanager.h"
2938-#include "ofonoactivator.h"
2939
2940 static QObject *connectivitySingeltonProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
2941 {
2942@@ -37,7 +36,6 @@
2943 Q_ASSERT(uri == QLatin1String("Ubuntu.SystemSettings.Cellular"));
2944 qmlRegisterSingletonType<Connectivity>(uri, 1, 0, "Connectivity", connectivitySingeltonProvider);
2945 qmlRegisterType<HotspotManager>(uri, 1, 0, "HotspotManager");
2946- qmlRegisterType<OfonoActivator>(uri, 1, 0, "OfonoActivator");
2947 }
2948
2949 void BackendPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
2950
2951=== modified file 'tests/autopilot/ubuntu_system_settings/__init__.py'
2952--- tests/autopilot/ubuntu_system_settings/__init__.py 2015-07-22 12:54:52 +0000
2953+++ tests/autopilot/ubuntu_system_settings/__init__.py 2015-07-27 14:21:15 +0000
2954@@ -187,6 +187,12 @@
2955 return False
2956
2957
2958+class LabelTextField(ubuntuuitoolkit.TextField):
2959+ """LabelTextField is a component local to the APN Editor in the cellular
2960+ plugin."""
2961+ pass
2962+
2963+
2964 class CellularPage(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
2965
2966 """Autopilot helper for the Cellular page."""
2967@@ -262,6 +268,24 @@
2968 chooseCarrierPage.set_carrier(carrier)
2969
2970 @autopilot.logging.log_action(logger.debug)
2971+ def open_apn_editor(self, name, sim=None):
2972+ carrierApnPage = self._click_carrier_apn()
2973+ chooseApnPage = carrierApnPage.open_apn(sim)
2974+ return chooseApnPage.open(name)
2975+
2976+ @autopilot.logging.log_action(logger.debug)
2977+ def delete_apn(self, name, sim=None):
2978+ carrierApnPage = self._click_carrier_apn()
2979+ chooseApnPage = carrierApnPage.open_apn(sim)
2980+ return chooseApnPage.delete(name)
2981+
2982+ @autopilot.logging.log_action(logger.debug)
2983+ def prefer_apn(self, name, sim=None):
2984+ carrierApnPage = self._click_carrier_apn()
2985+ chooseApnPage = carrierApnPage.open_apn(sim)
2986+ return chooseApnPage.check(name)
2987+
2988+ @autopilot.logging.log_action(logger.debug)
2989 def _click_carrier_apn(self):
2990 item = self.select_single(objectName='carrierApnEntry')
2991 self.pointing_device.click_object(item)
2992@@ -351,11 +375,22 @@
2993 return self.get_root_instance().wait_select_single(
2994 objectName='chooseCarrierPage')
2995
2996+ @autopilot.logging.log_action(logger.debug)
2997+ def open_apn(self, sim):
2998+ return self._click_apn(sim)
2999+
3000+ @autopilot.logging.log_action(logger.debug)
3001+ def _click_apn(self, sim):
3002+ obj = self.select_single(
3003+ objectName='apn')
3004+ self.pointing_device.click_object(obj)
3005+ return self.get_root_instance().wait_select_single(
3006+ objectName='apnPage')
3007+
3008
3009 class PageCarriersAndApns(
3010 ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
3011 """Autopilot helper for carrier/apn entry page (multisim)."""
3012- """Autopilot helper for carrier/apn entry page (singlesim)."""
3013 @autopilot.logging.log_action(logger.debug)
3014 def open_carrier(self, sim):
3015 return self._click_carrier(sim)
3016@@ -378,6 +413,7 @@
3017 item = self.select_single(text='Automatically')
3018 self.pointing_device.click_object(item)
3019
3020+ @autopilot.logging.log_action(logger.debug)
3021 def set_carrier(self, carrier):
3022 # wait for animation, since page.animationRunning.wait_for(False)
3023 # does not work?
3024@@ -396,6 +432,116 @@
3025 self.pointing_device.click_object(item)
3026
3027
3028+class PageChooseApn(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
3029+
3030+ """Autopilot helper for apn editor page"""
3031+
3032+ @autopilot.logging.log_action(logger.debug)
3033+ def open(self, name):
3034+ return self._open_editor(name)
3035+
3036+ @autopilot.logging.log_action(logger.debug)
3037+ def delete(self, name):
3038+ self._delete(name)
3039+
3040+ @autopilot.logging.log_action(logger.debug)
3041+ def _delete(self, name):
3042+ item = self.wait_select_single('Standard', objectName='edit_%s' % name)
3043+ item.swipe_to_delete()
3044+ item.confirm_removal()
3045+
3046+ @autopilot.logging.log_action(logger.debug)
3047+ def check(self, name):
3048+ self._check(name)
3049+
3050+ @autopilot.logging.log_action(logger.debug)
3051+ def _check(self, name):
3052+ item = self.wait_select_single(
3053+ 'CheckBox', objectName='%s_preferred' % name
3054+ )
3055+ item.check()
3056+
3057+ @autopilot.logging.log_action(logger.debug)
3058+ def _open_editor(self, name):
3059+ if name:
3060+ item = self.select_single(objectName='edit_%s' % name)
3061+ self.pointing_device.click_object(item)
3062+ else:
3063+ main_view = self.get_root_instance().select_single(
3064+ objectName='systemSettingsMainView')
3065+ header = main_view.select_single('AppHeader')
3066+ header.click_action_button('newApn')
3067+ return self.get_root_instance().wait_select_single(
3068+ objectName='apnEditor')
3069+
3070+
3071+class PageApnEditor(ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
3072+
3073+ """Autopilot helper for apn editor page"""
3074+
3075+ flickable = None
3076+
3077+ def __init__(self, *args):
3078+ super().__init__(*args)
3079+ self.flickable = self.select_single(objectName='scrollArea')
3080+
3081+ @autopilot.logging.log_action(logger.debug)
3082+ def set_type(self, t):
3083+ selector = self.select_single(
3084+ 'ItemSelector', objectName='typeSelector')
3085+ self.pointing_device.click_object(selector)
3086+ selector.currentlyExpanded.wait_for(True)
3087+ item = self.select_single(objectName='type_%s' % t)
3088+
3089+ # A bit dirty
3090+ while selector.currentlyExpanded:
3091+ self.pointing_device.click_object(item)
3092+
3093+ @autopilot.logging.log_action(logger.debug)
3094+ def set_name(self, new_name):
3095+ self._populate_field('name', new_name)
3096+
3097+ @autopilot.logging.log_action(logger.debug)
3098+ def set_access_point_name(self, new_name):
3099+ self._populate_field('accessPointName', new_name)
3100+
3101+ @autopilot.logging.log_action(logger.debug)
3102+ def set_message_center(self, new_message_center):
3103+ self._populate_field('messageCenter', new_message_center)
3104+
3105+ @autopilot.logging.log_action(logger.debug)
3106+ def set_message_proxy(self, new_message_proxy):
3107+ self._populate_field('messageProxy', new_message_proxy)
3108+
3109+ # Sleep for the duration of the timer that will copy any
3110+ # port into the port field
3111+ sleep(1.5)
3112+
3113+ @autopilot.logging.log_action(logger.debug)
3114+ def set_port(self, new_port):
3115+ self._populate_field('port', new_port)
3116+
3117+ @autopilot.logging.log_action(logger.debug)
3118+ def set_username(self, new_username):
3119+ self._populate_field('username', new_username)
3120+
3121+ @autopilot.logging.log_action(logger.debug)
3122+ def set_password(self, new_password):
3123+ self._populate_field('password', new_password)
3124+
3125+ def _populate_field(self, field, text):
3126+ f = self.select_single(LabelTextField, objectName=field)
3127+ self.flickable.swipe_child_into_view(f)
3128+ f.write(text)
3129+
3130+ @autopilot.logging.log_action(logger.debug)
3131+ def save(self):
3132+ main_view = self.get_root_instance().select_single(
3133+ objectName='systemSettingsMainView')
3134+ header = main_view.select_single('AppHeader')
3135+ header.click_action_button('saveApn')
3136+
3137+
3138 class SecurityPage(ubuntuuitoolkit.QQuickFlickable):
3139
3140 """Autopilot helper for the Security page."""
3141
3142=== modified file 'tests/autopilot/ubuntu_system_settings/tests/__init__.py'
3143--- tests/autopilot/ubuntu_system_settings/tests/__init__.py 2015-07-17 15:06:35 +0000
3144+++ tests/autopilot/ubuntu_system_settings/tests/__init__.py 2015-07-27 14:21:15 +0000
3145@@ -180,22 +180,6 @@
3146 return 'ret = [(m, objects[m].GetAll("org.ofono.NetworkOperator")) ' \
3147 'for m in objects if "%s/operator/" in m]' % name
3148
3149- def mock_connection_manager(self, modem):
3150- modem.AddProperty(CONNMAN_IFACE, 'Powered', dbus.Boolean(1))
3151- modem.AddProperty(CONNMAN_IFACE, 'RoamingAllowed', dbus.Boolean(0))
3152- modem.AddMethods(
3153- CONNMAN_IFACE,
3154- [
3155- (
3156- 'GetProperties', '', 'a{sv}',
3157- 'ret = self.GetAll("%s")' % CONNMAN_IFACE),
3158- (
3159- 'SetProperty', 'sv', '',
3160- 'self.Set("IFACE", args[0], args[1]); '
3161- 'self.EmitSignal("IFACE", "PropertyChanged", "sv",\
3162- [args[0], args[1]])'.replace("IFACE", CONNMAN_IFACE)),
3163- ])
3164-
3165 def mock_carriers(self, name):
3166 self.dbusmock.AddObject(
3167 '/%s/operator/op2' % name,
3168@@ -284,7 +268,6 @@
3169
3170 self.mock_carriers('ril_0')
3171 self.mock_radio_settings(self.modem_0)
3172- self.mock_connection_manager(self.modem_0)
3173 self.mock_call_forwarding(self.modem_0)
3174 self.mock_call_settings(self.modem_0)
3175
3176@@ -295,6 +278,9 @@
3177 ('GetOperators', '', 'a(oa{sv})', self.get_all_operators('ril_0')),
3178 ('Scan', '', 'a(oa{sv})', self.get_all_operators('ril_0')),
3179 ])
3180+ self.modem_0.connMan = dbus.Interface(self.dbus_con.get_object(
3181+ 'org.ofono', '/ril_0'),
3182+ 'org.ofono.ConnectionManager')
3183
3184 def add_sim2(self):
3185 '''Mock two modems/sims for the dual sim story'''
3186@@ -315,7 +301,6 @@
3187 ])
3188 self.mock_carriers(second_modem)
3189 self.mock_radio_settings(self.modem_1, technologies=['gsm'])
3190- self.mock_connection_manager(self.modem_1)
3191 self.mock_call_forwarding(self.modem_1)
3192 self.mock_call_settings(self.modem_1)
3193
3194@@ -324,6 +309,10 @@
3195 'SubscriberNumbers', ['08123', '938762783']
3196 )
3197
3198+ self.modem_1.connMan = dbus.Interface(self.dbus_con.get_object(
3199+ 'org.ofono', '/' + second_modem),
3200+ 'org.ofono.ConnectionManager')
3201+
3202 @classmethod
3203 def setUpClass(cls):
3204 cls.start_system_bus()
3205@@ -378,6 +367,16 @@
3206 super(CellularBaseTestCase, self).setUp()
3207 self.cellular_page = self.main_view.go_to_cellular_page()
3208
3209+ def add_connection_context(self, modem, **kwargs):
3210+ iface = 'org.ofono.ConnectionContext'
3211+ path = modem.connMan.AddContext(kwargs.get('Type', 'internet'))
3212+ context = dbus.Interface(self.dbus_con.get_object(
3213+ 'org.ofono', path),
3214+ iface)
3215+
3216+ for key, value in kwargs.items():
3217+ context.SetProperty(key, value)
3218+
3219
3220 class BluetoothBaseTestCase(UbuntuSystemSettingsTestCase):
3221
3222
3223=== modified file 'tests/autopilot/ubuntu_system_settings/tests/ofono.py'
3224--- tests/autopilot/ubuntu_system_settings/tests/ofono.py 2015-07-22 12:54:57 +0000
3225+++ tests/autopilot/ubuntu_system_settings/tests/ofono.py 2015-07-27 14:21:15 +0000
3226@@ -88,6 +88,7 @@
3227 obj.name = name
3228 obj.sim_pin = "2468"
3229 add_simmanager_api(obj)
3230+ add_connectionmanager_api(obj)
3231 add_voice_call_api(obj)
3232 add_netreg_api(obj)
3233 self.modems.append(path)
3234@@ -127,6 +128,104 @@
3235 ])
3236
3237
3238+def add_connectionmanager_api(mock):
3239+ '''Add org.ofono.ConnectionManager API to a mock'''
3240+
3241+ iface = 'org.ofono.ConnectionManager'
3242+ mock.contexts = []
3243+ mock.AddProperties(iface, {
3244+ 'Attached': _parameters.get('Attached', True),
3245+ 'Bearer': _parameters.get('Bearer', 'gprs'),
3246+ 'RoamingAllowed': _parameters.get('RoamingAllowed', False),
3247+ 'Powered': _parameters.get('ConnectionPowered', True),
3248+ })
3249+ mock.AddMethods(iface, [
3250+ ('GetProperties', '', 'a{sv}', 'ret = self.GetAll("%s")' % iface),
3251+ ('SetProperty', 'sv', '', 'self.Set("%(i)s", args[0], args[1]); '
3252+ 'self.EmitSignal("%(i)s", "PropertyChanged", "sv", ['
3253+ 'args[0], args[1]])' % {'i': iface}),
3254+ ('AddContext', 's', 'o', 'ret = self.AddConnectionContext(args[0])'),
3255+ ('RemoveContext', 'o', '', 'self.RemoveConnectionContext(args[0])'),
3256+ ('DeactivateAll', '', '', ''),
3257+ ('GetContexts', '', 'a(oa{sv})', 'ret = self.GetConnectionContexts()'),
3258+ ])
3259+
3260+ interfaces = mock.GetProperties()['Interfaces']
3261+ interfaces.append(iface)
3262+ mock.SetProperty('Interfaces', interfaces)
3263+
3264+
3265+@dbus.service.method('org.ofono.ConnectionManager',
3266+ in_signature='', out_signature='a(oa{sv})')
3267+def GetConnectionContexts(self):
3268+ contexts = dbus.Array([], signature='a(oa{sv})')
3269+ for ctx in self.contexts:
3270+ contexts.append(dbus.Struct(
3271+ (ctx.__dbus_object_path__, ctx.GetProperties())))
3272+ return contexts
3273+
3274+
3275+@dbus.service.method('org.ofono.ConnectionManager',
3276+ in_signature='s', out_signature='o')
3277+def AddConnectionContext(self, type):
3278+ name = 'context%s' % str(len(self.contexts))
3279+ path = '%s/%s' % (self.__dbus_object_path__, name)
3280+ iface = 'org.ofono.ConnectionContext'
3281+
3282+ # We give the context a name, just like ofono does.
3283+ # See https://github.com/rilmodem/ofono/blob/master/src/gprs.c#L148
3284+ ofono_default_accesspointname = {
3285+ 'internet': 'Internet',
3286+ 'mms': 'MMS',
3287+ 'ai': 'AI',
3288+ 'ims': 'IMS',
3289+ 'wap': 'WAP'
3290+ }
3291+
3292+ self.AddObject(
3293+ path,
3294+ iface,
3295+ {
3296+ 'Active': False,
3297+ 'AccessPointName': '',
3298+ 'Type': type,
3299+ 'Username': '',
3300+ 'Password': '',
3301+ 'Protocol': 'ip',
3302+ 'Name': ofono_default_accesspointname[type],
3303+ 'Preferred': False,
3304+ 'Settings': dbus.Dictionary({}, signature='sv'),
3305+ 'IPv6.Settings': dbus.Dictionary({}, signature='sv'),
3306+ 'MessageProxy': '',
3307+ 'MessageCenter': '',
3308+ },
3309+ [
3310+ ('GetProperties', '', 'a{sv}',
3311+ 'ret = self.GetAll("org.ofono.ConnectionContext")'),
3312+ (
3313+ 'SetProperty', 'sv', '',
3314+ 'self.Set("%s", args[0], args[1]); '
3315+ 'self.EmitSignal("%s", "PropertyChanged",'
3316+ '"sv", [args[0], args[1]])' % (iface, iface)),
3317+ ])
3318+ ctx_obj = dbusmock.get_object(path)
3319+ self.contexts.append(ctx_obj)
3320+ self.EmitSignal('org.ofono.ConnectionManager',
3321+ 'ContextAdded', 'oa{sv}', [path, ctx_obj.GetProperties()])
3322+
3323+ return path
3324+
3325+
3326+@dbus.service.method('org.ofono.ConnectionManager',
3327+ in_signature='o', out_signature='')
3328+def RemoveConnectionContext(self, path):
3329+ ctx_obj = dbusmock.get_object(path)
3330+ self.contexts.remove(ctx_obj)
3331+ self.RemoveObject(path)
3332+ self.EmitSignal('org.ofono.ConnectionManager',
3333+ 'ContextRemoved', 'o', [path])
3334+
3335+
3336 @dbus.service.method('org.ofono.SimManager',
3337 in_signature='ss', out_signature='')
3338 def LockPin(self, pin_type, pin):
3339
3340=== modified file 'tests/autopilot/ubuntu_system_settings/tests/test_cellular.py'
3341--- tests/autopilot/ubuntu_system_settings/tests/test_cellular.py 2014-10-29 15:51:17 +0000
3342+++ tests/autopilot/ubuntu_system_settings/tests/test_cellular.py 2015-07-27 14:21:15 +0000
3343@@ -288,3 +288,144 @@
3344 lambda:
3345 gsettings.get_value('default-sim-for-messages').get_string(),
3346 Eventually(Equals('/ril_1')))
3347+
3348+
3349+class ApnTestCase(CellularBaseTestCase):
3350+
3351+ def test_remove_apn(self):
3352+ self.add_connection_context(self.modem_0, Type='mms', Name='Failed')
3353+ contexts = self.modem_0.connMan.GetContexts()
3354+
3355+ # Assert there's a Failed mms context
3356+ self.assertEqual(1, len(contexts))
3357+ self.assertEqual('/ril_0/context0', contexts[0][0])
3358+ self.assertEqual('Failed', contexts[0][1]['Name'])
3359+
3360+ # We delete the failed context
3361+ self.cellular_page.delete_apn('Failed')
3362+
3363+ def test_create_internet_apn(self):
3364+ editor = self.cellular_page.open_apn_editor(None)
3365+ editor.set_type('internet')
3366+ editor.set_name('Ubuntu')
3367+ editor.set_access_point_name('ubuntu.ap')
3368+ editor.set_username('user')
3369+ editor.set_password('pass')
3370+ editor.save()
3371+
3372+ # Wait for our new context to appear.
3373+ self.assertThat(
3374+ lambda: self.modem_0.connMan.GetContexts()[0][0],
3375+ Eventually(Equals('/ril_0/context0'))
3376+ )
3377+ # Wait for our context to be renamed from the default ofono name.
3378+ self.assertThat(
3379+ lambda: self.modem_0.connMan.GetContexts()[0][1]['Name'],
3380+ Eventually(Equals('Ubuntu'))
3381+ )
3382+ contexts = self.modem_0.connMan.GetContexts()
3383+ new_ctx = contexts[0][1]
3384+ self.assertEqual(1, len(contexts))
3385+ self.assertEqual('internet', new_ctx['Type'])
3386+ self.assertEqual('user', new_ctx['Username'])
3387+ self.assertEqual('pass', new_ctx['Password'])
3388+
3389+ def test_create_mms_apn(self):
3390+ editor = self.cellular_page.open_apn_editor(None)
3391+ editor.set_type('mms')
3392+ editor.set_name('Ubuntu')
3393+ editor.set_access_point_name('ubuntu.ap')
3394+ editor.set_message_center('ubuntu.com')
3395+ editor.set_message_proxy('ubuntu:8080')
3396+ editor.set_username('user')
3397+ editor.set_password('pass')
3398+ editor.save()
3399+
3400+ # Wait for our new context to appear.
3401+ self.assertThat(
3402+ lambda: self.modem_0.connMan.GetContexts()[0][0],
3403+ Eventually(Equals('/ril_0/context0'))
3404+ )
3405+ # Wait for our context to be renamed from the default ofono name.
3406+ self.assertThat(
3407+ lambda: self.modem_0.connMan.GetContexts()[0][1]['Name'],
3408+ Eventually(Equals('Ubuntu'))
3409+ )
3410+ contexts = self.modem_0.connMan.GetContexts()
3411+ new_ctx = contexts[0][1]
3412+ self.assertEqual(1, len(contexts))
3413+ self.assertEqual('mms', new_ctx['Type'])
3414+ self.assertEqual('ubuntu.ap', new_ctx['AccessPointName'])
3415+ self.assertEqual('http://ubuntu.com', new_ctx['MessageCenter'])
3416+ self.assertEqual('ubuntu:8080', new_ctx['MessageProxy'])
3417+ self.assertEqual('user', new_ctx['Username'])
3418+ self.assertEqual('pass', new_ctx['Password'])
3419+
3420+ def test_create_mms_and_internet_apn(self):
3421+ editor = self.cellular_page.open_apn_editor(None)
3422+ editor.set_type('internet+mms')
3423+ editor.set_name('Ubuntu')
3424+ editor.set_access_point_name('ubuntu.ap')
3425+ editor.set_message_center('ubuntu.com')
3426+ editor.set_message_proxy('ubuntu:8080')
3427+ editor.set_username('user')
3428+ editor.set_password('pass')
3429+ editor.save()
3430+
3431+ # Wait for our new context to appear.
3432+ self.assertThat(
3433+ lambda: self.modem_0.connMan.GetContexts()[0][0],
3434+ Eventually(Equals('/ril_0/context0'))
3435+ )
3436+ # Wait for our context to be renamed from the default ofono name.
3437+ self.assertThat(
3438+ lambda: self.modem_0.connMan.GetContexts()[0][1]['Name'],
3439+ Eventually(Equals('Ubuntu'))
3440+ )
3441+ contexts = self.modem_0.connMan.GetContexts()
3442+ new_ctx = contexts[0][1]
3443+ self.assertEqual(1, len(contexts))
3444+ self.assertEqual('internet', new_ctx['Type'])
3445+ self.assertEqual('ubuntu.ap', new_ctx['AccessPointName'])
3446+ self.assertEqual('http://ubuntu.com', new_ctx['MessageCenter'])
3447+ self.assertEqual('ubuntu:8080', new_ctx['MessageProxy'])
3448+ self.assertEqual('user', new_ctx['Username'])
3449+ self.assertEqual('pass', new_ctx['Password'])
3450+
3451+ def create_lte_apn(self):
3452+ editor = self.cellular_page.open_apn_editor(None)
3453+ editor.set_type('ia')
3454+ editor.set_name('Ubuntu')
3455+ editor.set_access_point_name('ubuntu.ap')
3456+ editor.set_username('user')
3457+ editor.set_password('pass')
3458+ editor.save()
3459+
3460+ # Wait for our new context to appear.
3461+ self.assertThat(
3462+ lambda: self.modem_0.connMan.GetContexts()[0][0],
3463+ Eventually(Equals('/ril_0/context0'))
3464+ )
3465+ # Wait for our context to be renamed from the default ofono name.
3466+ self.assertThat(
3467+ lambda: self.modem_0.connMan.GetContexts()[0][1]['Name'],
3468+ Eventually(Equals('Ubuntu'))
3469+ )
3470+ contexts = self.modem_0.connMan.GetContexts()
3471+ new_ctx = contexts[0][1]
3472+ self.assertEqual(1, len(contexts))
3473+ self.assertEqual('ia', new_ctx['Type'])
3474+ self.assertEqual('user', new_ctx['Username'])
3475+ self.assertEqual('pass', new_ctx['Password'])
3476+
3477+ def select_apn(self):
3478+ self.add_connection_context(self.modem_0,
3479+ Type='internet', Name='Provisioned')
3480+
3481+ self.cellular_page.prefer_apn('Provisioned')
3482+
3483+ # Assert that Preferred becomes true.
3484+ self.assertThat(
3485+ lambda: self.modem_0.connMan.GetContexts()[0][1]['Preferred'],
3486+ Eventually(Equals(True))
3487+ )

Subscribers

People subscribed via source and target branches