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