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