Merge lp:~jonas-drange/ubuntu-system-settings/rtm-apneditor-libqofono-compat into lp:ubuntu-system-settings/rtm-14.09
- rtm-apneditor-libqofono-compat
- Merge into rtm-14.09
Proposed by
Jonas G. Drange
Status: | Merged |
---|---|
Approved by: | Ken VanDine |
Approved revision: | 989 |
Merged at revision: | 990 |
Proposed branch: | lp:~jonas-drange/ubuntu-system-settings/rtm-apneditor-libqofono-compat |
Merge into: | lp:ubuntu-system-settings/rtm-14.09 |
Diff against target: |
540 lines (+208/-222) 2 files modified
plugins/cellular/CustomApnEditor.qml (+1/-2) plugins/cellular/PageChooseApn.qml (+207/-220) |
To merge this branch: | bzr merge lp:~jonas-drange/ubuntu-system-settings/rtm-apneditor-libqofono-compat |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ken VanDine | Approve | ||
PS Jenkins bot | continuous-integration | Needs Fixing | |
Review via email: mp+249823@code.launchpad.net |
Commit message
[cellular] makes the apn editor compatible to libqofono 0.7
Description of the change
[cellular] makes the apn editor compatible to libqofono 0.7
To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : | # |
review:
Needs Fixing
(continuous-integration)
Revision history for this message
Ken VanDine (ken-vandine) wrote : | # |
I've verified it isn't creating the duplicate contexts with libqofono 0.70, so great! I can't create a working context with my SIM, which isn't new. The code looks fine, thanks!
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'plugins/cellular/CustomApnEditor.qml' |
2 | --- plugins/cellular/CustomApnEditor.qml 2014-10-06 14:28:46 +0000 |
3 | +++ plugins/cellular/CustomApnEditor.qml 2015-02-16 14:04:43 +0000 |
4 | @@ -139,7 +139,7 @@ |
5 | } |
6 | /// @todo disable for now |
7 | //visible: d.isMms |
8 | - visible: false |
9 | + visible: false |
10 | text: i18n.tr("Same APN as for Internet") |
11 | control: Switch { |
12 | id: doBoth |
13 | @@ -300,7 +300,6 @@ |
14 | pageStack.pop(); |
15 | return; |
16 | } |
17 | - |
18 | ctx.accessPointName = apnName.text; |
19 | ctx.username = userName.text; |
20 | ctx.password = pword.text; |
21 | |
22 | === modified file 'plugins/cellular/PageChooseApn.qml' |
23 | --- plugins/cellular/PageChooseApn.qml 2014-10-06 19:36:13 +0000 |
24 | +++ plugins/cellular/PageChooseApn.qml 2015-02-16 14:04:43 +0000 |
25 | @@ -50,6 +50,7 @@ |
26 | // suppress any actions that we don't want to take |
27 | // when updating selectedIndexes, etc |
28 | property bool __suppressActivation : true; |
29 | + property bool __haveCustomContexts : false; |
30 | |
31 | function isEmptyCustom (type, ctx) |
32 | { |
33 | @@ -76,9 +77,9 @@ |
34 | return false; |
35 | } |
36 | |
37 | - function updateContexts() |
38 | + function updateContexts(contexts) |
39 | { |
40 | - var tmp = sim.connMan.contexts.slice(0); |
41 | + var tmp = contexts || sim.connMan.contexts.slice(0); |
42 | var added = tmp.filter(function(i) { |
43 | return mContexts[i] === undefined; |
44 | }); |
45 | @@ -118,26 +119,6 @@ |
46 | "contextPath": currentValue |
47 | }); |
48 | mContexts[currentValue] = ctx; |
49 | - |
50 | - if (isEmptyCustom("internet", ctx)) |
51 | - { |
52 | - ctx.name = mCustomContextNameInternet; |
53 | - // name updates async, so return here and |
54 | - // have the buildLists() called from Context::onNameChanged |
55 | - return; |
56 | - } else if (isEmptyCustom("mms", ctx)) |
57 | - { |
58 | - ctx.name = mCustomContextNameMms; |
59 | - ctx.accessPointName = pendingCustomMmsData["accessPointName"]; |
60 | - ctx.username = pendingCustomMmsData["username"]; |
61 | - ctx.password = pendingCustomMmsData["password"]; |
62 | - ctx.messageCenter = pendingCustomMmsData["messageCenter"]; |
63 | - ctx.messageProxy = pendingCustomMmsData["messageProxy"]; |
64 | - pendingCustomMmsData = ({}); |
65 | - // values update async, so return here and |
66 | - // have the buildLists() called from Context::onNameChanged |
67 | - return; |
68 | - } |
69 | }); |
70 | |
71 | // just asserting to verify the logic |
72 | @@ -156,8 +137,7 @@ |
73 | } |
74 | |
75 | // expects updateContexts() to have ran before executing. |
76 | - function checkAndCreateCustomContexts() |
77 | - { |
78 | + function checkAndCreateCustomContexts() { |
79 | var customInternet = Object.keys(mContexts).filter(function (i) { |
80 | var ctx = mContexts[i]; |
81 | return ctx.name === mCustomContextNameInternet || |
82 | @@ -250,6 +230,8 @@ |
83 | mInternetApns = internet; |
84 | mMmsApns = mms; |
85 | |
86 | + internetApnSelector.updateSelectedIndex(); |
87 | + |
88 | d.__suppressActivation = false; |
89 | } |
90 | |
91 | @@ -312,14 +294,29 @@ |
92 | |
93 | // add helper property to detect dual internet/MMS contexts |
94 | property bool dual : false |
95 | - Component.onCompleted:{ |
96 | + onTypeChanged:{ |
97 | if (type == "internet") |
98 | if (messageCenter !== "") |
99 | dual = true |
100 | + |
101 | + d.buildLists(); |
102 | } |
103 | |
104 | onActiveChanged: if (type === "internet") internetApnSelector.updateSelectedIndex() |
105 | - onNameChanged: d.buildLists() |
106 | + onNameChanged: { |
107 | + if (name === "Internet") { |
108 | + this.name = d.mCustomContextNameInternet; |
109 | + } else if (name === "MMS") { |
110 | + this.name = d.mCustomContextNameMms; |
111 | + this.accessPointName = d.pendingCustomMmsData["accessPointName"]; |
112 | + this.username = d.pendingCustomMmsData["username"]; |
113 | + this.password = d.pendingCustomMmsData["password"]; |
114 | + this.messageCenter = d.pendingCustomMmsData["messageCenter"]; |
115 | + this.messageProxy = d.pendingCustomMmsData["messageProxy"]; |
116 | + d.pendingCustomMmsData = ({}); |
117 | + } |
118 | + d.buildLists(); |
119 | + } |
120 | onAccessPointNameChanged: d.buildLists() |
121 | onReportError: console.error("Context error on " + contextPath + ": " + errorString) |
122 | } |
123 | @@ -327,12 +324,8 @@ |
124 | |
125 | Connections { |
126 | target: sim.connMan |
127 | - onContextsChanged: d.updateContexts() |
128 | - |
129 | + onContextsChanged: d.updateContexts(contexts) |
130 | Component.onCompleted: { |
131 | - d.updateContexts(); |
132 | - |
133 | - |
134 | /// @todo workaround the work around that the UI currently only supports max. 1 MMS context |
135 | /// remove once nuntium stuff is implemented |
136 | // remove all but one MMS context |
137 | @@ -347,9 +340,7 @@ |
138 | sim.connMan.removeContext(currentValue); |
139 | }); |
140 | |
141 | - |
142 | - // do this once. |
143 | - d.checkAndCreateCustomContexts(); |
144 | + d.updateContexts(); |
145 | } |
146 | } |
147 | |
148 | @@ -360,211 +351,207 @@ |
149 | boundsBehavior: (contentHeight > root.height) ? Flickable.DragAndOvershootBounds : Flickable.StopAtBounds |
150 | flickableDirection: Flickable.VerticalFlick |
151 | |
152 | + |
153 | Column { |
154 | - anchors { |
155 | - left: parent.left |
156 | - right: parent.right |
157 | - } |
158 | - |
159 | - ListItem.Standard { |
160 | - id: heading1 |
161 | - objectName: "internetapn" |
162 | + anchors { left: parent.left; right: parent.right } |
163 | + |
164 | + spacing: units.gu(2) |
165 | + |
166 | + ListItem.ItemSelector { |
167 | + id: internetApnSelector |
168 | + width: parent.width |
169 | + model: d.mInternetApns |
170 | + expanded: true |
171 | + selectedIndex: -1 |
172 | text: i18n.tr("Internet APN:") |
173 | - progression: false |
174 | - } |
175 | - ListItem.ThinDivider {} |
176 | - ListItem.SingleControl { |
177 | - control: ListItem.ItemSelector { |
178 | - id: internetApnSelector |
179 | - width: parent.width - units.gu(4) |
180 | - model: d.mInternetApns |
181 | - expanded: true |
182 | - selectedIndex: -1 |
183 | - onModelChanged: updateSelectedIndex() |
184 | + onModelChanged: updateSelectedIndex() |
185 | |
186 | - function updateSelectedIndex() |
187 | - { |
188 | - var tmp = d.__suppressActivation |
189 | - d.__suppressActivation = true; |
190 | - var idx = -1; |
191 | - if (model) { |
192 | - model.forEach(function(currentValue, index, array) { |
193 | - if (d.mContexts[currentValue].active) |
194 | - idx = index; |
195 | - }); |
196 | - } |
197 | - selectedIndex = idx; |
198 | - d.__suppressActivation = tmp; |
199 | + function updateSelectedIndex() |
200 | + { |
201 | + var tmp = d.__suppressActivation |
202 | + d.__suppressActivation = true; |
203 | + var idx = -1; |
204 | + if (model) { |
205 | + model.forEach(function(currentValue, index, array) { |
206 | + if (d.mContexts[currentValue].active) |
207 | + idx = index; |
208 | + }); |
209 | } |
210 | + selectedIndex = idx; |
211 | + d.__suppressActivation = tmp; |
212 | + } |
213 | |
214 | - delegate: OptionSelectorDelegate { |
215 | - showDivider: false |
216 | - text: { |
217 | - var ctx = d.mContexts[modelData]; |
218 | - if (ctx.name !== "") { |
219 | - if (ctx.name !== d.mCustomContextNameInternet) { |
220 | - return ctx.name |
221 | - } else { |
222 | - //: user visible name of the custom Internet APN |
223 | - return i18n.tr("Custom"); |
224 | - } |
225 | + delegate: OptionSelectorDelegate { |
226 | + showDivider: false |
227 | + text: { |
228 | + var ctx = d.mContexts[modelData]; |
229 | + if (ctx.name !== "") { |
230 | + if (ctx.name !== d.mCustomContextNameInternet) { |
231 | + return ctx.name |
232 | } else { |
233 | - return ctx.accessPointName |
234 | + //: user visible name of the custom Internet APN |
235 | + return i18n.tr("Custom"); |
236 | } |
237 | + } else { |
238 | + return ctx.accessPointName |
239 | } |
240 | } |
241 | - onSelectedIndexChanged: { |
242 | - if (selectedIndex === -1) { |
243 | - if (mmsApnSelector && mmsApnSelector.model[mmsApnSelector.selectedIndex] === "/same/as/internet") |
244 | - mmsApnSelector.selectedIndex = -1; |
245 | - return; |
246 | - } |
247 | - |
248 | - var ctx = d.mContexts[model[selectedIndex]]; |
249 | - if(ctx.dual) { |
250 | - if (!d.mCustomContextMms) |
251 | - mmsApnSelector.selectedIndex = mmsApnSelector.model.indexOf("/same/as/internet"); |
252 | - } |
253 | - else if (mmsApnSelector.model[mmsApnSelector.selectedIndex] === "/same/as/internet") |
254 | + } |
255 | + onSelectedIndexChanged: { |
256 | + if (selectedIndex === -1) { |
257 | + if (mmsApnSelector && mmsApnSelector.model[mmsApnSelector.selectedIndex] === "/same/as/internet") |
258 | mmsApnSelector.selectedIndex = -1; |
259 | - |
260 | - if (d.__suppressActivation) |
261 | + return; |
262 | + } |
263 | + |
264 | + var ctx = d.mContexts[model[selectedIndex]]; |
265 | + if(ctx.dual) { |
266 | + if (!d.mCustomContextMms) |
267 | + mmsApnSelector.selectedIndex = mmsApnSelector.model.indexOf("/same/as/internet"); |
268 | + } |
269 | + else if (mmsApnSelector.model[mmsApnSelector.selectedIndex] === "/same/as/internet") |
270 | + mmsApnSelector.selectedIndex = -1; |
271 | + |
272 | + if (d.__suppressActivation) |
273 | + return; |
274 | + |
275 | + if (d.mCustomContextInternet && model[selectedIndex] === d.mCustomContextInternet.contextPath) { |
276 | + if (d.mCustomContextInternet.accessPointName === "") { |
277 | + d.openApnEditor("internet"); |
278 | + updateSelectedIndex(); |
279 | return; |
280 | - |
281 | - if (d.mCustomContextInternet && model[selectedIndex] === d.mCustomContextInternet.contextPath) { |
282 | - if (d.mCustomContextInternet.accessPointName === "") { |
283 | - d.openApnEditor("internet"); |
284 | - updateSelectedIndex(); |
285 | - return; |
286 | - } |
287 | } |
288 | - |
289 | - d.activateHelper("internet", ctx.contextPath); |
290 | - } |
291 | - } |
292 | - } |
293 | - ListItem.SingleControl { |
294 | - control: Button { |
295 | - objectName: "customApnEdit" |
296 | - text: i18n.tr("Custom Internet APN…") |
297 | - width: parent.width - units.gu(4) |
298 | - onClicked: d.openApnEditor("internet") |
299 | - } |
300 | - } |
301 | - |
302 | - ListItem.Divider {} |
303 | - |
304 | - ListItem.Standard { |
305 | - id: heading2 |
306 | - objectName: "mmsapn" |
307 | + } |
308 | + |
309 | + d.activateHelper("internet", ctx.contextPath); |
310 | + } |
311 | + } |
312 | + |
313 | + |
314 | + Button { |
315 | + anchors { horizontalCenter: parent.horizontalCenter } |
316 | + objectName: "customApnEdit" |
317 | + text: i18n.tr("Custom Internet APN…") |
318 | + width: parent.width - units.gu(4) |
319 | + onClicked: { |
320 | + if (!d.__haveCustomContexts) { |
321 | + d.checkAndCreateCustomContexts(); |
322 | + d.__haveCustomContexts = true; |
323 | + } |
324 | + |
325 | + d.openApnEditor("internet") |
326 | + } |
327 | + } |
328 | + |
329 | + ListItem.ItemSelector { |
330 | + id: mmsApnSelector |
331 | + width: parent.width |
332 | + model: d.mMmsApns |
333 | + expanded: true |
334 | + selectedIndex: -1 |
335 | text: i18n.tr("MMS APN:") |
336 | - progression: false |
337 | - } |
338 | - ListItem.ThinDivider {} |
339 | - ListItem.SingleControl { |
340 | - control: ListItem.ItemSelector { |
341 | - id: mmsApnSelector |
342 | - width: parent.width - units.gu(4) |
343 | - model: d.mMmsApns |
344 | - expanded: true |
345 | - selectedIndex: -1 |
346 | - delegate: OptionSelectorDelegate { |
347 | - showDivider: modelData === "/same/as/internet" |
348 | - enabled: { |
349 | - if (modelData !== "/same/as/internet") |
350 | - return true; |
351 | - else { |
352 | - var tmp = d.mContexts[internetApnSelector.model[internetApnSelector.selectedIndex]] |
353 | - return tmp === undefined ? false : tmp.dual |
354 | - } |
355 | - } |
356 | - // work around OptionSelectorDelegate not having a visual change depending on being disabled |
357 | - opacity: enabled ? 1.0 : 0.5 |
358 | - text: { |
359 | - if (modelData === "/same/as/internet") { |
360 | - return i18n.tr("Same APN as for Internet"); |
361 | - } |
362 | - if (modelData === "dummycustom") { |
363 | + delegate: OptionSelectorDelegate { |
364 | + showDivider: modelData === "/same/as/internet" |
365 | + enabled: { |
366 | + if (modelData !== "/same/as/internet") |
367 | + return true; |
368 | + else { |
369 | + var tmp = d.mContexts[internetApnSelector.model[internetApnSelector.selectedIndex]] |
370 | + return tmp === undefined ? false : tmp.dual |
371 | + } |
372 | + } |
373 | + // work around OptionSelectorDelegate not having a visual change depending on being disabled |
374 | + opacity: enabled ? 1.0 : 0.5 |
375 | + text: { |
376 | + if (modelData === "/same/as/internet") { |
377 | + return i18n.tr("Same APN as for Internet"); |
378 | + } |
379 | + if (modelData === "dummycustom") { |
380 | + return i18n.tr("Custom"); |
381 | + } |
382 | + var ctx = d.mContexts[modelData]; |
383 | + if (ctx.name !== "") { |
384 | + if (ctx.name !== d.mCustomContextNameMms) { |
385 | + return ctx.name |
386 | + } else { |
387 | + //: user visible name of the custom MMS APN |
388 | return i18n.tr("Custom"); |
389 | } |
390 | - var ctx = d.mContexts[modelData]; |
391 | - if (ctx.name !== "") { |
392 | - if (ctx.name !== d.mCustomContextNameMms) { |
393 | - return ctx.name |
394 | - } else { |
395 | - //: user visible name of the custom MMS APN |
396 | - return i18n.tr("Custom"); |
397 | - } |
398 | - } else { |
399 | - return ctx.accessPointName |
400 | - } |
401 | - } |
402 | - } |
403 | - onModelChanged: updateSelectedIndex(); |
404 | - function updateSelectedIndex() |
405 | - { |
406 | - // if we have custom MMS context, it must be active. |
407 | - // @bug LP(#1361864) |
408 | - var tmp = d.__suppressActivation; |
409 | - d.__suppressActivation = true; |
410 | - if (d.mCustomContextMms) { |
411 | - selectedIndex = model.indexOf(d.mCustomContextMms.contextPath); |
412 | - } else if (model.length === 3) { |
413 | - /* meaning we have: |
414 | - * 0 - /same/as/internet |
415 | - * 1 - some provisioned one |
416 | - * 2 - dummycustom |
417 | - */ |
418 | - selectedIndex = 1; |
419 | - } else if (internetApnSelector.model && internetApnSelector.selectedIndex !== -1) { |
420 | - if (d.mContexts[internetApnSelector.model[internetApnSelector.selectedIndex]].dual) { |
421 | - selectedIndex = model.indexOf("/same/as/internet"); |
422 | - } else |
423 | - selectedIndex = -1; |
424 | } else { |
425 | + return ctx.accessPointName |
426 | + } |
427 | + } |
428 | + } |
429 | + onModelChanged: updateSelectedIndex(); |
430 | + function updateSelectedIndex() |
431 | + { |
432 | + // if we have custom MMS context, it must be active. |
433 | + // @bug LP(#1361864) |
434 | + var tmp = d.__suppressActivation; |
435 | + d.__suppressActivation = true; |
436 | + if (d.mCustomContextMms) { |
437 | + selectedIndex = model.indexOf(d.mCustomContextMms.contextPath); |
438 | + } else if (model.length === 3) { |
439 | + /* meaning we have: |
440 | + * 0 - /same/as/internet |
441 | + * 1 - some provisioned one |
442 | + * 2 - dummycustom |
443 | + */ |
444 | + selectedIndex = 1; |
445 | + } else if (internetApnSelector.model && internetApnSelector.selectedIndex !== -1) { |
446 | + if (d.mContexts[internetApnSelector.model[internetApnSelector.selectedIndex]].dual) { |
447 | + selectedIndex = model.indexOf("/same/as/internet"); |
448 | + } else |
449 | selectedIndex = -1; |
450 | - } |
451 | - d.__suppressActivation = tmp; |
452 | - } |
453 | - |
454 | - onSelectedIndexChanged: { |
455 | - if (selectedIndex === -1 || d.__suppressActivation) |
456 | - return; |
457 | - |
458 | - if (model[selectedIndex] === "/same/as/internet") { |
459 | - // @bug delete _any_ actual MMS context |
460 | - // LP:(#1362795) |
461 | - var remove = []; |
462 | - Object.keys(d.mContexts).forEach(function(currentValue, index, array) { |
463 | - var ctx = d.mContexts[currentValue]; |
464 | - if (ctx.type === "mms") |
465 | - remove = remove.concat([ctx.contextPath]); |
466 | - }); |
467 | - remove.forEach(function(currentValue, index, array) { |
468 | - sim.connMan.removeContext(currentValue); |
469 | - }); |
470 | - return; |
471 | - } |
472 | - |
473 | - if (model[selectedIndex] === "dummycustom") { |
474 | - d.openApnEditor("mms"); |
475 | - updateSelectedIndex() |
476 | - return; |
477 | - } |
478 | - |
479 | - // no need to "activate" anything. |
480 | - // just fall through return here. |
481 | - // once we actually are able to suppport multiple MMS contexts |
482 | - // on the system, add some code here to set one of them active |
483 | - } |
484 | + } else { |
485 | + selectedIndex = -1; |
486 | + } |
487 | + d.__suppressActivation = tmp; |
488 | + } |
489 | + |
490 | + onSelectedIndexChanged: { |
491 | + if (selectedIndex === -1 || d.__suppressActivation) |
492 | + return; |
493 | + |
494 | + if (model[selectedIndex] === "/same/as/internet") { |
495 | + // @bug delete _any_ actual MMS context |
496 | + // LP:(#1362795) |
497 | + var remove = []; |
498 | + Object.keys(d.mContexts).forEach(function(currentValue, index, array) { |
499 | + var ctx = d.mContexts[currentValue]; |
500 | + if (ctx.type === "mms") |
501 | + remove = remove.concat([ctx.contextPath]); |
502 | + }); |
503 | + remove.forEach(function(currentValue, index, array) { |
504 | + sim.connMan.removeContext(currentValue); |
505 | + }); |
506 | + return; |
507 | + } |
508 | + |
509 | + if (model[selectedIndex] === "dummycustom") { |
510 | + d.openApnEditor("mms"); |
511 | + updateSelectedIndex() |
512 | + return; |
513 | + } |
514 | + |
515 | + // no need to "activate" anything. |
516 | + // just fall through return here. |
517 | + // once we actually are able to suppport multiple MMS contexts |
518 | + // on the system, add some code here to set one of them active |
519 | } |
520 | } |
521 | - ListItem.SingleControl { |
522 | - control: Button { |
523 | - objectName: "customApnEdit" |
524 | - text: i18n.tr("Custom MMS APN…") |
525 | - width: parent.width - units.gu(4) |
526 | - onClicked: d.openApnEditor("mms") |
527 | + |
528 | + Button { |
529 | + anchors { horizontalCenter: parent.horizontalCenter } |
530 | + objectName: "customApnEdit" |
531 | + text: i18n.tr("Custom MMS APN…") |
532 | + width: parent.width - units.gu(4) |
533 | + onClicked: { |
534 | + if (!d.__haveCustomContexts) { |
535 | + d.checkAndCreateCustomContexts(); |
536 | + d.__haveCustomContexts = true; |
537 | + } |
538 | + d.openApnEditor("mms"); |
539 | } |
540 | } |
541 |
FAILED: Continuous integration, rev:989 jenkins. qa.ubuntu. com/job/ ubuntu- system- settings- rtm-14. 09-ci/61/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- 14.09-touch/ 40 jenkins. qa.ubuntu. com/job/ ubuntu- system- settings- rtm-14. 09-14.09- armhf-ci/ 62 jenkins. qa.ubuntu. com/job/ ubuntu- system- settings- rtm-14. 09-14.09- armhf-ci/ 62/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- 14.09-mako/ 41 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- 14.09-armhf/ 40 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- 14.09-armhf/ 40/artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 18089
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- system- settings- rtm-14. 09-ci/61/ rebuild
http://