Merge lp:~jonas-drange/ubuntu-system-settings/apn-prototype into lp:ubuntu-system-settings

Proposed by Jonas G. Drange
Status: Merged
Merged at revision: 1488
Proposed branch: lp:~jonas-drange/ubuntu-system-settings/apn-prototype
Merge into: lp:ubuntu-system-settings
Diff against target: 3490 lines (+2067/-1143)
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/-21)
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:~jonas-drange/ubuntu-system-settings/apn-prototype
Reviewer Review Type Date Requested Status
Ken VanDine Approve
PS Jenkins bot continuous-integration Needs Fixing
Alfonso Sanchez-Beato mms/nuntium interactions Pending
Tony Espy sanity Pending
Matthew Paul Thomas design Pending
Review via email: mp+258992@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.
1409. By Jonas G. Drange

revert changelog

1410. By Jonas G. Drange

no auto in username, next field to be focused after pwd is name

1411. By Jonas G. Drange

merge trunk

1412. By Jonas G. Drange

fix changelog

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

ap proxy for labeltextfield, as well as some minor jiggling of the test

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

merge trunk

1415. By Jonas G. Drange

make contexts appear as active

1416. By Jonas G. Drange

refactor the test class, divide tests and create some new ones

1417. By Jonas G. Drange

some margin

1418. By Jonas G. Drange

fixing some review killers

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

fixing bad modem path

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

add ready signal and properly deal with active contexts when there are no preferred ones

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

remove ofonoactivator and sessions service deps

1422. By Jonas G. Drange

fix reset function

1423. By Jonas G. Drange

make the ui support changing type of context

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

address comments from design

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)
1425. By Jonas G. Drange

dropped Base in favor of Standard

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

move reset

1427. By Jonas G. Drange

logic for conflicting contexts, as well as the feedback to the user should it make a mess

1428. By Jonas G. Drange

note to translators about APN field label

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

Just a few inline comments on strings that we probably don't need translated, otherwise looks good.

review: Needs Information
Revision history for this message
Jonas G. Drange (jonas-drange) wrote :

Comments about translations. Thanks!

1429. By Jonas G. Drange

warning on de-prefer

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)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1430. By Jonas G. Drange

merge trunk

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

removing debugging

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

sync with trunk since this branch is old

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 :

I'm happy with this branch, wish we could see passing tests in CI but right now we are affected by an autopilot bug.

review: Approve

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-22 13:07:42 +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-22 13:07:42 +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-22 13:07:42 +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-22 13:07:42 +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-22 13:07:42 +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-22 13:07:42 +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-22 13:07:42 +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-22 13:07:42 +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-22 13:07:42 +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-22 13:07:42 +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-16 13:30:34 +0000
2953+++ tests/autopilot/ubuntu_system_settings/__init__.py 2015-07-22 13:07:42 +0000
2954@@ -186,6 +186,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@@ -261,6 +267,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@@ -350,11 +374,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@@ -377,6 +412,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@@ -395,6 +431,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-06 13:48:32 +0000
3144+++ tests/autopilot/ubuntu_system_settings/tests/__init__.py 2015-07-22 13:07:42 +0000
3145@@ -180,25 +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- interfaces = modem.GetProperties()['Interfaces']
3165- interfaces.append(CONNMAN_IFACE)
3166- modem.SetProperty('Interfaces', interfaces)
3167-
3168 def mock_carriers(self, name):
3169 self.dbusmock.AddObject(
3170 '/%s/operator/op2' % name,
3171@@ -297,7 +278,6 @@
3172
3173 self.mock_carriers('ril_0')
3174 self.mock_radio_settings(self.modem_0)
3175- self.mock_connection_manager(self.modem_0)
3176 self.mock_call_forwarding(self.modem_0)
3177 self.mock_call_settings(self.modem_0)
3178
3179@@ -308,6 +288,9 @@
3180 ('GetOperators', '', 'a(oa{sv})', self.get_all_operators('ril_0')),
3181 ('Scan', '', 'a(oa{sv})', self.get_all_operators('ril_0')),
3182 ])
3183+ self.modem_0.connMan = dbus.Interface(self.dbus_con.get_object(
3184+ 'org.ofono', '/ril_0'),
3185+ 'org.ofono.ConnectionManager')
3186
3187 def add_sim2(self):
3188 '''Mock two modems/sims for the dual sim story'''
3189@@ -328,7 +311,6 @@
3190 ])
3191 self.mock_carriers(second_modem)
3192 self.mock_radio_settings(self.modem_1, technologies=['gsm'])
3193- self.mock_connection_manager(self.modem_1)
3194 self.mock_call_forwarding(self.modem_1)
3195 self.mock_call_settings(self.modem_1)
3196
3197@@ -337,6 +319,10 @@
3198 'SubscriberNumbers', ['08123', '938762783']
3199 )
3200
3201+ self.modem_1.connMan = dbus.Interface(self.dbus_con.get_object(
3202+ 'org.ofono', '/' + second_modem),
3203+ 'org.ofono.ConnectionManager')
3204+
3205 @classmethod
3206 def setUpClass(cls):
3207 cls.start_system_bus()
3208@@ -391,6 +377,16 @@
3209 super(CellularBaseTestCase, self).setUp()
3210 self.cellular_page = self.main_view.go_to_cellular_page()
3211
3212+ def add_connection_context(self, modem, **kwargs):
3213+ iface = 'org.ofono.ConnectionContext'
3214+ path = modem.connMan.AddContext(kwargs.get('Type', 'internet'))
3215+ context = dbus.Interface(self.dbus_con.get_object(
3216+ 'org.ofono', path),
3217+ iface)
3218+
3219+ for key, value in kwargs.items():
3220+ context.SetProperty(key, value)
3221+
3222
3223 class BluetoothBaseTestCase(UbuntuSystemSettingsTestCase):
3224
3225
3226=== modified file 'tests/autopilot/ubuntu_system_settings/tests/ofono.py'
3227--- tests/autopilot/ubuntu_system_settings/tests/ofono.py 2015-07-14 09:32:38 +0000
3228+++ tests/autopilot/ubuntu_system_settings/tests/ofono.py 2015-07-22 13:07:42 +0000
3229@@ -83,6 +83,7 @@
3230 obj.name = name
3231 obj.sim_pin = "2468"
3232 add_simmanager_api(obj)
3233+ add_connectionmanager_api(obj)
3234 add_voice_call_api(obj)
3235 add_netreg_api(obj)
3236 self.modems.append(path)
3237@@ -122,6 +123,104 @@
3238 ])
3239
3240
3241+def add_connectionmanager_api(mock):
3242+ '''Add org.ofono.ConnectionManager API to a mock'''
3243+
3244+ iface = 'org.ofono.ConnectionManager'
3245+ mock.contexts = []
3246+ mock.AddProperties(iface, {
3247+ 'Attached': _parameters.get('Attached', True),
3248+ 'Bearer': _parameters.get('Bearer', 'gprs'),
3249+ 'RoamingAllowed': _parameters.get('RoamingAllowed', False),
3250+ 'Powered': _parameters.get('ConnectionPowered', True),
3251+ })
3252+ mock.AddMethods(iface, [
3253+ ('GetProperties', '', 'a{sv}', 'ret = self.GetAll("%s")' % iface),
3254+ ('SetProperty', 'sv', '', 'self.Set("%(i)s", args[0], args[1]); '
3255+ 'self.EmitSignal("%(i)s", "PropertyChanged", "sv", ['
3256+ 'args[0], args[1]])' % {'i': iface}),
3257+ ('AddContext', 's', 'o', 'ret = self.AddConnectionContext(args[0])'),
3258+ ('RemoveContext', 'o', '', 'self.RemoveConnectionContext(args[0])'),
3259+ ('DeactivateAll', '', '', ''),
3260+ ('GetContexts', '', 'a(oa{sv})', 'ret = self.GetConnectionContexts()'),
3261+ ])
3262+
3263+ interfaces = mock.GetProperties()['Interfaces']
3264+ interfaces.append(iface)
3265+ mock.SetProperty('Interfaces', interfaces)
3266+
3267+
3268+@dbus.service.method('org.ofono.ConnectionManager',
3269+ in_signature='', out_signature='a(oa{sv})')
3270+def GetConnectionContexts(self):
3271+ contexts = dbus.Array([], signature='a(oa{sv})')
3272+ for ctx in self.contexts:
3273+ contexts.append(dbus.Struct(
3274+ (ctx.__dbus_object_path__, ctx.GetProperties())))
3275+ return contexts
3276+
3277+
3278+@dbus.service.method('org.ofono.ConnectionManager',
3279+ in_signature='s', out_signature='o')
3280+def AddConnectionContext(self, type):
3281+ name = 'context%s' % str(len(self.contexts))
3282+ path = '%s/%s' % (self.__dbus_object_path__, name)
3283+ iface = 'org.ofono.ConnectionContext'
3284+
3285+ # We give the context a name, just like ofono does.
3286+ # See https://github.com/rilmodem/ofono/blob/master/src/gprs.c#L148
3287+ ofono_default_accesspointname = {
3288+ 'internet': 'Internet',
3289+ 'mms': 'MMS',
3290+ 'ai': 'AI',
3291+ 'ims': 'IMS',
3292+ 'wap': 'WAP'
3293+ }
3294+
3295+ self.AddObject(
3296+ path,
3297+ iface,
3298+ {
3299+ 'Active': False,
3300+ 'AccessPointName': '',
3301+ 'Type': type,
3302+ 'Username': '',
3303+ 'Password': '',
3304+ 'Protocol': 'ip',
3305+ 'Name': ofono_default_accesspointname[type],
3306+ 'Preferred': False,
3307+ 'Settings': dbus.Dictionary({}, signature='sv'),
3308+ 'IPv6.Settings': dbus.Dictionary({}, signature='sv'),
3309+ 'MessageProxy': '',
3310+ 'MessageCenter': '',
3311+ },
3312+ [
3313+ ('GetProperties', '', 'a{sv}',
3314+ 'ret = self.GetAll("org.ofono.ConnectionContext")'),
3315+ (
3316+ 'SetProperty', 'sv', '',
3317+ 'self.Set("%s", args[0], args[1]); '
3318+ 'self.EmitSignal("%s", "PropertyChanged",'
3319+ '"sv", [args[0], args[1]])' % (iface, iface)),
3320+ ])
3321+ ctx_obj = dbusmock.get_object(path)
3322+ self.contexts.append(ctx_obj)
3323+ self.EmitSignal('org.ofono.ConnectionManager',
3324+ 'ContextAdded', 'oa{sv}', [path, ctx_obj.GetProperties()])
3325+
3326+ return path
3327+
3328+
3329+@dbus.service.method('org.ofono.ConnectionManager',
3330+ in_signature='o', out_signature='')
3331+def RemoveConnectionContext(self, path):
3332+ ctx_obj = dbusmock.get_object(path)
3333+ self.contexts.remove(ctx_obj)
3334+ self.RemoveObject(path)
3335+ self.EmitSignal('org.ofono.ConnectionManager',
3336+ 'ContextRemoved', 'o', [path])
3337+
3338+
3339 @dbus.service.method('org.ofono.SimManager',
3340 in_signature='ss', out_signature='')
3341 def LockPin(self, pin_type, pin):
3342
3343=== modified file 'tests/autopilot/ubuntu_system_settings/tests/test_cellular.py'
3344--- tests/autopilot/ubuntu_system_settings/tests/test_cellular.py 2014-10-29 15:51:17 +0000
3345+++ tests/autopilot/ubuntu_system_settings/tests/test_cellular.py 2015-07-22 13:07:42 +0000
3346@@ -288,3 +288,144 @@
3347 lambda:
3348 gsettings.get_value('default-sim-for-messages').get_string(),
3349 Eventually(Equals('/ril_1')))
3350+
3351+
3352+class ApnTestCase(CellularBaseTestCase):
3353+
3354+ def test_remove_apn(self):
3355+ self.add_connection_context(self.modem_0, Type='mms', Name='Failed')
3356+ contexts = self.modem_0.connMan.GetContexts()
3357+
3358+ # Assert there's a Failed mms context
3359+ self.assertEqual(1, len(contexts))
3360+ self.assertEqual('/ril_0/context0', contexts[0][0])
3361+ self.assertEqual('Failed', contexts[0][1]['Name'])
3362+
3363+ # We delete the failed context
3364+ self.cellular_page.delete_apn('Failed')
3365+
3366+ def test_create_internet_apn(self):
3367+ editor = self.cellular_page.open_apn_editor(None)
3368+ editor.set_type('internet')
3369+ editor.set_name('Ubuntu')
3370+ editor.set_access_point_name('ubuntu.ap')
3371+ editor.set_username('user')
3372+ editor.set_password('pass')
3373+ editor.save()
3374+
3375+ # Wait for our new context to appear.
3376+ self.assertThat(
3377+ lambda: self.modem_0.connMan.GetContexts()[0][0],
3378+ Eventually(Equals('/ril_0/context0'))
3379+ )
3380+ # Wait for our context to be renamed from the default ofono name.
3381+ self.assertThat(
3382+ lambda: self.modem_0.connMan.GetContexts()[0][1]['Name'],
3383+ Eventually(Equals('Ubuntu'))
3384+ )
3385+ contexts = self.modem_0.connMan.GetContexts()
3386+ new_ctx = contexts[0][1]
3387+ self.assertEqual(1, len(contexts))
3388+ self.assertEqual('internet', new_ctx['Type'])
3389+ self.assertEqual('user', new_ctx['Username'])
3390+ self.assertEqual('pass', new_ctx['Password'])
3391+
3392+ def test_create_mms_apn(self):
3393+ editor = self.cellular_page.open_apn_editor(None)
3394+ editor.set_type('mms')
3395+ editor.set_name('Ubuntu')
3396+ editor.set_access_point_name('ubuntu.ap')
3397+ editor.set_message_center('ubuntu.com')
3398+ editor.set_message_proxy('ubuntu:8080')
3399+ editor.set_username('user')
3400+ editor.set_password('pass')
3401+ editor.save()
3402+
3403+ # Wait for our new context to appear.
3404+ self.assertThat(
3405+ lambda: self.modem_0.connMan.GetContexts()[0][0],
3406+ Eventually(Equals('/ril_0/context0'))
3407+ )
3408+ # Wait for our context to be renamed from the default ofono name.
3409+ self.assertThat(
3410+ lambda: self.modem_0.connMan.GetContexts()[0][1]['Name'],
3411+ Eventually(Equals('Ubuntu'))
3412+ )
3413+ contexts = self.modem_0.connMan.GetContexts()
3414+ new_ctx = contexts[0][1]
3415+ self.assertEqual(1, len(contexts))
3416+ self.assertEqual('mms', new_ctx['Type'])
3417+ self.assertEqual('ubuntu.ap', new_ctx['AccessPointName'])
3418+ self.assertEqual('http://ubuntu.com', new_ctx['MessageCenter'])
3419+ self.assertEqual('ubuntu:8080', new_ctx['MessageProxy'])
3420+ self.assertEqual('user', new_ctx['Username'])
3421+ self.assertEqual('pass', new_ctx['Password'])
3422+
3423+ def test_create_mms_and_internet_apn(self):
3424+ editor = self.cellular_page.open_apn_editor(None)
3425+ editor.set_type('internet+mms')
3426+ editor.set_name('Ubuntu')
3427+ editor.set_access_point_name('ubuntu.ap')
3428+ editor.set_message_center('ubuntu.com')
3429+ editor.set_message_proxy('ubuntu:8080')
3430+ editor.set_username('user')
3431+ editor.set_password('pass')
3432+ editor.save()
3433+
3434+ # Wait for our new context to appear.
3435+ self.assertThat(
3436+ lambda: self.modem_0.connMan.GetContexts()[0][0],
3437+ Eventually(Equals('/ril_0/context0'))
3438+ )
3439+ # Wait for our context to be renamed from the default ofono name.
3440+ self.assertThat(
3441+ lambda: self.modem_0.connMan.GetContexts()[0][1]['Name'],
3442+ Eventually(Equals('Ubuntu'))
3443+ )
3444+ contexts = self.modem_0.connMan.GetContexts()
3445+ new_ctx = contexts[0][1]
3446+ self.assertEqual(1, len(contexts))
3447+ self.assertEqual('internet', new_ctx['Type'])
3448+ self.assertEqual('ubuntu.ap', new_ctx['AccessPointName'])
3449+ self.assertEqual('http://ubuntu.com', new_ctx['MessageCenter'])
3450+ self.assertEqual('ubuntu:8080', new_ctx['MessageProxy'])
3451+ self.assertEqual('user', new_ctx['Username'])
3452+ self.assertEqual('pass', new_ctx['Password'])
3453+
3454+ def create_lte_apn(self):
3455+ editor = self.cellular_page.open_apn_editor(None)
3456+ editor.set_type('ia')
3457+ editor.set_name('Ubuntu')
3458+ editor.set_access_point_name('ubuntu.ap')
3459+ editor.set_username('user')
3460+ editor.set_password('pass')
3461+ editor.save()
3462+
3463+ # Wait for our new context to appear.
3464+ self.assertThat(
3465+ lambda: self.modem_0.connMan.GetContexts()[0][0],
3466+ Eventually(Equals('/ril_0/context0'))
3467+ )
3468+ # Wait for our context to be renamed from the default ofono name.
3469+ self.assertThat(
3470+ lambda: self.modem_0.connMan.GetContexts()[0][1]['Name'],
3471+ Eventually(Equals('Ubuntu'))
3472+ )
3473+ contexts = self.modem_0.connMan.GetContexts()
3474+ new_ctx = contexts[0][1]
3475+ self.assertEqual(1, len(contexts))
3476+ self.assertEqual('ia', new_ctx['Type'])
3477+ self.assertEqual('user', new_ctx['Username'])
3478+ self.assertEqual('pass', new_ctx['Password'])
3479+
3480+ def select_apn(self):
3481+ self.add_connection_context(self.modem_0,
3482+ Type='internet', Name='Provisioned')
3483+
3484+ self.cellular_page.prefer_apn('Provisioned')
3485+
3486+ # Assert that Preferred becomes true.
3487+ self.assertThat(
3488+ lambda: self.modem_0.connMan.GetContexts()[0][1]['Preferred'],
3489+ Eventually(Equals(True))
3490+ )

Subscribers

People subscribed via source and target branches