on xenial help sign in
Merge from trunk
dddfa87Michael Terry
CMakeLists.txt+3
@@ -87,6 +87,9 @@ endif()
87# "signals" and "slots"
87# "signals" and "slots"
88add_definitions(-DQT_NO_KEYWORDS)
88add_definitions(-DQT_NO_KEYWORDS)
89
89
90+# Save a few container detach and conversions
91+add_definitions(-DQT_STRICT_ITERATORS)
92+
90# Tests
93# Tests
91include(CTest)
94include(CTest)
92enable_testing()
95enable_testing()
debian/changelog+42
@@ -1,3 +1,45 @@
1+unity8 (8.00+14.10.20141009.4-0ubuntu1) utopic; urgency=low
2+
3+ [ Michał Sawicz ]
4+ * Rename datetime indicator in test
5+
6+ [ Albert Astals ]
7+ * Close overview temp scope on show dash (LP: #1373819)
8+ * Enable QT_STRICT_ITERATORS
9+ * Make Base not clickable, since we don't use it in anywhere clickable
10+ (we only use it for Category delegates in the LVWPH) (LP: #1300709)
11+ * Hide preview if it was visible when going to a scope (LP: #1374548)
12+ * Remove contentScale variable
13+
14+ [ Andrea Cimitan ]
15+ * Fix card implicitHeight when summary is declared, but its text is
16+ empty. Also fixes vertical journal height and clipping (LP:
17+ #1362160)
18+ * Redesign for pinned apps. Remove thindivider on top of ubuntu dash
19+ button (not needed anymore) (LP: #1377100)
20+
21+ [ Ying-Chun Liu ]
22+ * Fix run_on_device.sh to let it run again.
23+
24+ [ Nick Dedekind ]
25+ * Fixed indicator menu bindings to server toggle value broken by user
26+ interaction. (LP: #1336715)
27+ * Force rendering so we don't get stuck in "waitForRendering" loop in
28+ tests.
29+
30+ [ Daniel d'Andrada ]
31+ * PhoneStage: focus the new topmost app after the current one closes
32+ itself (LP: #1375267)
33+
34+ [ Michael Terry ]
35+ * Fix some security issues with the tablet greeter, which allowed the
36+ lockscreen to be bypassed. (LP: #1367715) (LP: #1367715)
37+
38+ [ Michael Zanetti ]
39+ * scale down errorText label if necessary (LP: #1378848)
40+
41+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 09 Oct 2014 14:05:54 +0000
42+
1unity8 (8.00+14.10.20141008-0ubuntu1) utopic; urgency=low
43unity8 (8.00+14.10.20141008-0ubuntu1) utopic; urgency=low
2
44
3 [ Michael Zanetti ]
45 [ Michael Zanetti ]
plugins/Dash/CardCreator.js+1−1
@@ -624,7 +624,7 @@ function cardString(template, components) {
624
624
625 var implicitHeight = 'implicitHeight: ';
625 var implicitHeight = 'implicitHeight: ';
626 if (hasSummary) {
626 if (hasSummary) {
627− implicitHeight += 'summary.y + summary.height + (summary.text ? units.gu(1) : 0);\n';
627+ implicitHeight += 'summary.y + summary.height + units.gu(1);\n';
628 } else if (hasHeaderRow) {
628 } else if (hasHeaderRow) {
629 implicitHeight += 'row.y + row.height + units.gu(1);\n';
629 implicitHeight += 'row.y + row.height + units.gu(1);\n';
630 } else if (hasMascot) {
630 } else if (hasMascot) {
qml/Components/ListItems/Base.qml+2−11
@@ -17,7 +17,7 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19
19
20−AbstractButton {
20+Item {
21 id: emptyListItem
21 id: emptyListItem
22 width: parent ? parent.width : units.gu(31)
22 width: parent ? parent.width : units.gu(31)
23 height: body.height + bottomDividerLine.height
23 height: body.height + bottomDividerLine.height
@@ -28,15 +28,6 @@ AbstractButton {
28 */
28 */
29 property bool selected: false
29 property bool selected: false
30
30
31− /*!
32− \preliminary
33− Highlight the list item when it is pressed.
34− This is used to disable the highlighting of the full list item
35− when custom highlighting needs to be implemented (for example in
36− ListItem.Standard which can have a split).
37− */
38− property bool highlightWhenPressed: true
39−
40 /*!
31 /*!
41 \preliminary
32 \preliminary
42 Set to show or hide the thin bottom divider line (drawn by the \l ThinDivider component).
33 Set to show or hide the thin bottom divider line (drawn by the \l ThinDivider component).
@@ -119,7 +110,7 @@ AbstractButton {
119 right: parent.right
110 right: parent.right
120 bottom: bottomDividerLine.top
111 bottom: bottomDividerLine.top
121 }
112 }
122− pressed: (emptyListItem.selected || (emptyListItem.highlightWhenPressed && emptyListItem.pressed)) ? "pressed" : ""
113+ pressed: emptyListItem.selected
123 }
114 }
124 }
115 }
125}
116}
qml/Components/PinLockscreen.qml+1
@@ -114,6 +114,7 @@ Column {
114 horizontalAlignment: Text.AlignHCenter
114 horizontalAlignment: Text.AlignHCenter
115 text: root.errorText
115 text: root.errorText
116 visible: pinentryField.incorrectOverride
116 visible: pinentryField.incorrectOverride
117+ scale: Math.min(1, parent.width / width)
117 }
118 }
118
119
119 AbstractButton {
120 AbstractButton {
qml/Components/ResponsiveVerticalJournal.qml+3−2
@@ -47,7 +47,9 @@ Item {
47 property alias delegate: verticalJournalView.delegate
47 property alias delegate: verticalJournalView.delegate
48 property alias displayMarginBeginning: verticalJournalView.displayMarginBeginning
48 property alias displayMarginBeginning: verticalJournalView.displayMarginBeginning
49 property alias displayMarginEnd: verticalJournalView.displayMarginEnd
49 property alias displayMarginEnd: verticalJournalView.displayMarginEnd
50+
50− implicitHeight: verticalJournalView.implicitHeight
51+ implicitHeight: verticalJournalView.implicitHeight + rowSpacing
52+ clip: height < implicitHeight
51
53
52 VerticalJournal {
54 VerticalJournal {
53 id: verticalJournalView
55 id: verticalJournalView
@@ -59,7 +61,6 @@ Item {
59 topMargin: rowSpacing / 2
61 topMargin: rowSpacing / 2
60 bottomMargin: rowSpacing / 2
62 bottomMargin: rowSpacing / 2
61 }
63 }
62− clip: parent.height != implicitHeight
63
64
64 function px2gu(pixels) {
65 function px2gu(pixels) {
65 return Math.floor(pixels / units.gu(1))
66 return Math.floor(pixels / units.gu(1))
qml/Dash/Dash.qml+3−4
@@ -29,7 +29,6 @@ Showable {
29 visible: shown
29 visible: shown
30
30
31 property string showScopeOnLoaded: "clickscope"
31 property string showScopeOnLoaded: "clickscope"
32− property real contentScale: 1.0
33
32
34 DashCommunicatorService {
33 DashCommunicatorService {
35 objectName: "dashCommunicatorService"
34 objectName: "dashCommunicatorService"
@@ -41,8 +40,9 @@ Showable {
41 if (window.active) {
40 if (window.active) {
42 dashContentCache.scheduleUpdate();
41 dashContentCache.scheduleUpdate();
43 }
42 }
44− overviewController.enableAnimation = window.active;
43+ overviewController.enableAnimation = window.active && !scopesOverview.showingNonFavoriteScope;
45 overviewController.progress = 0;
44 overviewController.progress = 0;
45+ scopesOverview.closeTempScope();
46 }
46 }
47 }
47 }
48 }
48 }
@@ -190,7 +190,6 @@ Showable {
190 dash.showScopeOnLoaded = ""
190 dash.showScopeOnLoaded = ""
191 }
191 }
192 }
192 }
193− scale: dash.contentScale
194 clip: scale != 1.0 || scopeItem.visible || overviewController.progress != 0
193 clip: scale != 1.0 || scopeItem.visible || overviewController.progress != 0
195 Behavior on x {
194 Behavior on x {
196 UbuntuNumberAnimation {
195 UbuntuNumberAnimation {
@@ -245,7 +244,7 @@ Showable {
245 y: overviewController.progress == 0 ? dashContent.y : overviewProgressY
244 y: overviewController.progress == 0 ? dashContent.y : overviewProgressY
246 width: parent.width
245 width: parent.width
247 height: parent.height
246 height: parent.height
248− scale: dash.contentScale * overviewProgressScale
247+ scale: overviewProgressScale
249 enabled: opacity == 1
248 enabled: opacity == 1
250 opacity: 1 - overviewController.progress
249 opacity: 1 - overviewController.progress
251 clip: scale != 1.0
250 clip: scale != 1.0
qml/Dash/DashRenderer.qml+2−2
@@ -17,9 +17,9 @@
17import QtQuick 2.0
17import QtQuick 2.0
18
18
19Item {
19Item {
20− property int collapsedHeight
20+ property real collapsedHeight
21
21
22− property int expandedHeight
22+ property real expandedHeight
23
23
24 property int collapsedItemCount: -1
24 property int collapsedItemCount: -1
25
25
qml/Dash/GenericScopeView.qml−1
@@ -213,7 +213,6 @@ FocusScope {
213 delegate: ListItems.Base {
213 delegate: ListItems.Base {
214 id: baseItem
214 id: baseItem
215 objectName: "dashCategory" + category
215 objectName: "dashCategory" + category
216− highlightWhenPressed: false
217 showDivider: false
216 showDivider: false
218
217
219 property Item seeAllButton: seeAll
218 property Item seeAllButton: seeAll
qml/Dash/ScopesOverview.qml+9−1
@@ -89,6 +89,7 @@ Item {
89 } else {
89 } else {
90 root.allFavoriteSelected(scopeId);
90 root.allFavoriteSelected(scopeId);
91 }
91 }
92+ previewListView.open = false;
92 }
93 }
93 }
94 }
94
95
@@ -98,6 +99,14 @@ Item {
98 value: progress === 1
99 value: progress === 1
99 }
100 }
100
101
102+ function closeTempScope() {
103+ if (tempScopeItem.scope) {
104+ root.scope.closeScope(tempScopeItem.scope);
105+ tempScopeItem.scope = null;
106+ tempScopeItem.backClicked()
107+ }
108+ }
109+
101 function animateDashFromAll(scopeId) {
110 function animateDashFromAll(scopeId) {
102 var currentScopePos = allScopeCardPosition(scopeId);
111 var currentScopePos = allScopeCardPosition(scopeId);
103 if (currentScopePos) {
112 if (currentScopePos) {
@@ -531,7 +540,6 @@ Item {
531
540
532 width: parent.width
541 width: parent.width
533 height: parent.height
542 height: parent.height
534− scale: dash.contentScale
535 clip: scale != 1.0
543 clip: scale != 1.0
536 visible: scope != null
544 visible: scope != null
537 hasBackAction: true
545 hasBackAction: true
qml/Greeter/Greeter.qml+13−1
@@ -36,7 +36,7 @@ Showable {
36
36
37 property alias dragHandleWidth: dragHandle.width
37 property alias dragHandleWidth: dragHandle.width
38 property alias model: greeterContentLoader.model
38 property alias model: greeterContentLoader.model
39− property bool locked: shown && !LightDM.Greeter.promptless
39+ property bool locked: true
40
40
41 readonly property bool narrowMode: !multiUser && height > width
41 readonly property bool narrowMode: !multiUser && height > width
42 readonly property bool multiUser: LightDM.Users.count > 1
42 readonly property bool multiUser: LightDM.Users.count > 1
@@ -57,6 +57,18 @@ Showable {
57 }
57 }
58 }
58 }
59
59
60+ function tryToUnlock() {
61+ if (created) {
62+ greeterContentLoader.item.tryToUnlock()
63+ }
64+ }
65+
66+ function reset() {
67+ if (created) {
68+ greeterContentLoader.item.reset()
69+ }
70+ }
71+
60 onRequiredChanged: {
72 onRequiredChanged: {
61 // Reset hide animation to default once we're finished with it
73 // Reset hide animation to default once we're finished with it
62 if (required) {
74 if (required) {
qml/Greeter/GreeterContent.qml+12
@@ -29,6 +29,18 @@ Item {
29 signal selected(int uid)
29 signal selected(int uid)
30 signal unlocked(int uid)
30 signal unlocked(int uid)
31
31
32+ function tryToUnlock() {
33+ if (loginLoader.item) {
34+ loginLoader.item.tryToUnlock()
35+ }
36+ }
37+
38+ function reset() {
39+ if (loginLoader.item) {
40+ loginLoader.item.reset()
41+ }
42+ }
43+
32 Rectangle {
44 Rectangle {
33 // In case background fails to load
45 // In case background fails to load
34 id: backgroundBackup
46 id: backgroundBackup
qml/Greeter/LoginList.qml+19−6
@@ -35,6 +35,22 @@ Item {
35 signal selected(int uid)
35 signal selected(int uid)
36 signal unlocked(int uid)
36 signal unlocked(int uid)
37
37
38+ function tryToUnlock() {
39+ if (LightDM.Greeter.promptless) {
40+ if (LightDM.Greeter.authenticated) {
41+ root.unlocked(userList.currentIndex)
42+ } else {
43+ root.resetAuthentication()
44+ }
45+ } else {
46+ passwordInput.forceActiveFocus()
47+ }
48+ }
49+
50+ function reset() {
51+ root.resetAuthentication()
52+ }
53+
38 Keys.onEscapePressed: root.resetAuthentication()
54 Keys.onEscapePressed: root.resetAuthentication()
39
55
40 Rectangle {
56 Rectangle {
@@ -229,14 +245,11 @@ Item {
229 }
245 }
230
246
231 MouseArea {
247 MouseArea {
248+ id: passwordMouseArea
249+ objectName: "passwordMouseArea"
232 anchors.fill: passwordInput
250 anchors.fill: passwordInput
233 enabled: LightDM.Greeter.promptless
251 enabled: LightDM.Greeter.promptless
234− onClicked: {
252+ onClicked: root.tryToUnlock()
235− if (LightDM.Greeter.authenticated)
236− root.unlocked(userList.currentIndex);
237− else
238− root.resetAuthentication();
239− }
240 }
253 }
241
254
242 function resetAuthentication() {
255 function resetAuthentication() {
qml/Launcher/LauncherDelegate.qml+20−33
@@ -27,7 +27,7 @@ Item {
27 property bool itemFocused: false
27 property bool itemFocused: false
28 property real maxAngle: 0
28 property real maxAngle: 0
29 property bool inverted: false
29 property bool inverted: false
30− property bool clipCorner: false
30+ property alias pinned: pin.visible
31
31
32 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
32 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
33 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
33 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
@@ -71,14 +71,31 @@ Item {
71 height: root.itemHeight - units.gu(1)
71 height: root.itemHeight - units.gu(1)
72 }
72 }
73
73
74+ Rectangle {
75+ id: pin
76+ anchors {
77+ left: iconShape.left
78+ top: iconShape.top
79+ topMargin: -units.dp(2)
80+ }
81+ width: units.gu(1)
82+ height: width
83+ radius: width / 2
84+ color: "white"
85+ }
86+
74 UbuntuShape {
87 UbuntuShape {
88+ id: countEmblem
75 objectName: "countEmblem"
89 objectName: "countEmblem"
90+
91+ readonly property real pinMargin: pin.visible ? pin.width + units.gu(1) - anchors.leftMargin : 0
92+
76 anchors {
93 anchors {
77 right: parent.right
94 right: parent.right
78 top: parent.top
95 top: parent.top
79 margins: units.dp(3)
96 margins: units.dp(3)
80 }
97 }
81− width: Math.min(root.itemWidth, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
98+ width: Math.min(root.itemWidth - pinMargin, Math.max(units.gu(2), countLabel.implicitWidth + units.gu(1)))
82 height: units.gu(2)
99 height: units.gu(2)
83 color: UbuntuColors.orange
100 color: UbuntuColors.orange
84 visible: root.countVisible
101 visible: root.countVisible
@@ -92,7 +109,7 @@ Item {
92 // FIXME: verticalCenter seems to be off wee bit and QML doesn't have a centerLine
109 // FIXME: verticalCenter seems to be off wee bit and QML doesn't have a centerLine
93 // property for Text: https://bugreports.qt-project.org/browse/QTBUG-40479
110 // property for Text: https://bugreports.qt-project.org/browse/QTBUG-40479
94 anchors.verticalCenterOffset: -units.dp(.5)
111 anchors.verticalCenterOffset: -units.dp(.5)
95− width: root.itemWidth - units.gu(1)
112+ width: root.itemWidth - units.gu(1) - countEmblem.pinMargin
96 horizontalAlignment: Text.AlignHCenter
113 horizontalAlignment: Text.AlignHCenter
97 elide: Text.ElideRight
114 elide: Text.ElideRight
98 color: "white"
115 color: "white"
@@ -149,24 +166,6 @@ Item {
149 }
166 }
150 }
167 }
151
168
152− Item {
153− id: clipper
154− anchors.centerIn: parent
155− width: iconItem.width
156− height: iconItem.height
157− Rectangle {
158− anchors {
159− fill: parent
160− topMargin: -units.gu(2)
161− leftMargin: -units.gu(2)
162− rightMargin: -units.gu(2)
163− bottomMargin: units.gu(1.2)
164− }
165− color: "red"
166− rotation: root.rotation + 45
167− }
168− }
169−
170 ShaderEffect {
169 ShaderEffect {
171 id: transformEffect
170 id: transformEffect
172 anchors.centerIn: parent
171 anchors.centerIn: parent
@@ -176,7 +175,6 @@ Item {
176 property real itemOpacity: root.itemOpacity
175 property real itemOpacity: root.itemOpacity
177 property real brightness: Math.max(-1, root.brightness)
176 property real brightness: Math.max(-1, root.brightness)
178 property real angle: root.angle
177 property real angle: root.angle
179− property bool clipCorner: root.clipCorner
180 rotation: root.inverted ? 180 : 0
178 rotation: root.inverted ? 180 : 0
181
179
182 property variant source: ShaderEffectSource {
180 property variant source: ShaderEffectSource {
@@ -185,11 +183,6 @@ Item {
185 hideSource: true
183 hideSource: true
186 }
184 }
187
185
188− property var mask: ShaderEffectSource {
189− sourceItem: clipper
190− hideSource: true
191− }
192−
193 transform: [
186 transform: [
194 // Rotating 3 times at top/bottom because that increases the perspective.
187 // Rotating 3 times at top/bottom because that increases the perspective.
195 // This is a hack, but as QML does not support real 3D coordinates
188 // This is a hack, but as QML does not support real 3D coordinates
@@ -224,19 +217,13 @@ Item {
224 fragmentShader: "
217 fragmentShader: "
225 varying highp vec2 qt_TexCoord0;
218 varying highp vec2 qt_TexCoord0;
226 uniform sampler2D source;
219 uniform sampler2D source;
227− uniform sampler2D mask;
228 uniform lowp float brightness;
220 uniform lowp float brightness;
229 uniform lowp float itemOpacity;
221 uniform lowp float itemOpacity;
230− uniform bool clipCorner;
231 void main(void)
222 void main(void)
232 {
223 {
233 highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
224 highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
234− highp vec4 maskColor = texture2D(mask, qt_TexCoord0);
235 sourceColor.rgb = mix(sourceColor.rgb, vec3(step(0.0, brightness)), abs(brightness));
225 sourceColor.rgb = mix(sourceColor.rgb, vec3(step(0.0, brightness)), abs(brightness));
236 sourceColor *= itemOpacity;
226 sourceColor *= itemOpacity;
237− if (clipCorner) {
238− sourceColor *= maskColor.a;
239− }
240 gl_FragColor = sourceColor;
227 gl_FragColor = sourceColor;
241 }"
228 }"
242 }
229 }
qml/Launcher/LauncherPanel.qml+5−14
@@ -65,15 +65,6 @@ Rectangle {
65 }
65 }
66 }
66 }
67
67
68− ThinDivider {
69− anchors {
70− left: parent.left
71− right: parent.right
72− margins: -mainColumn.anchors.leftMargin
73− }
74− rotation: root.rotation
75− }
76−
77 Item {
68 Item {
78 anchors.left: parent.left
69 anchors.left: parent.left
79 anchors.right: parent.right
70 anchors.right: parent.right
@@ -158,7 +149,7 @@ Rectangle {
158 count: model.count
149 count: model.count
159 countVisible: model.countVisible
150 countVisible: model.countVisible
160 progress: model.progress
151 progress: model.progress
161− clipCorner: model.pinned
152+ pinned: model.pinned
162 itemFocused: model.focused
153 itemFocused: model.focused
163 inverted: root.inverted
154 inverted: root.inverted
164 z: -Math.abs(offset)
155 z: -Math.abs(offset)
@@ -469,10 +460,10 @@ Rectangle {
469 width: itemWidth
460 width: itemWidth
470 rotation: root.rotation
461 rotation: root.rotation
471 itemOpacity: 0.9
462 itemOpacity: 0.9
472− clipCorner: dndArea.draggedIndex > -1 &&
463+ pinned: dndArea.draggedIndex > -1 &&
473− LauncherModel.get(dndArea.draggedIndex).pinned &&
464+ LauncherModel.get(dndArea.draggedIndex).pinned &&
474− !dndArea.preDragging &&
465+ !dndArea.preDragging &&
475− !dndArea.dragging
466+ !dndArea.dragging
476
467
477 function flatten() {
468 function flatten() {
478 fakeDragItemAnimation.start();
469 fakeDragItemAnimation.start();
qml/Panel/Indicators/MenuItemFactory.qml+18−3
@@ -283,12 +283,17 @@ Item {
283 objectName: "checkableMenu"
283 objectName: "checkableMenu"
284 property QtObject menuData: null
284 property QtObject menuData: null
285 property int menuIndex: -1
285 property int menuIndex: -1
286+ property bool serverChecked: menuData && menuData.isToggled || false
286
287
287 text: menuData && menuData.label || ""
288 text: menuData && menuData.label || ""
288 enabled: menuData && menuData.sensitive || false
289 enabled: menuData && menuData.sensitive || false
289− checked: menuData && menuData.isToggled || false
290+ checked: serverChecked
290 highlightWhenPressed: false
291 highlightWhenPressed: false
291
292
293+ onServerCheckedChanged: {
294+ // value can be changed by menu, so a binding won't work.
295+ checked = serverChecked;
296+ }
292 onTriggered: {
297 onTriggered: {
293 menuModel.activate(menuIndex);
298 menuModel.activate(menuIndex);
294 }
299 }
@@ -302,13 +307,18 @@ Item {
302 objectName: "switchMenu"
307 objectName: "switchMenu"
303 property QtObject menuData: null
308 property QtObject menuData: null
304 property int menuIndex: -1
309 property int menuIndex: -1
310+ property bool serverChecked: menuData && menuData.isToggled || false
305
311
306 text: menuData && menuData.label || ""
312 text: menuData && menuData.label || ""
307 iconSource: menuData && menuData.icon || ""
313 iconSource: menuData && menuData.icon || ""
308 enabled: menuData && menuData.sensitive || false
314 enabled: menuData && menuData.sensitive || false
309− checked: menuData && menuData.isToggled || false
315+ checked: serverChecked
310 highlightWhenPressed: false
316 highlightWhenPressed: false
311
317
318+ onServerCheckedChanged: {
319+ // value can be changed by menu, so a binding won't work.
320+ checked = serverChecked;
321+ }
312 onTriggered: {
322 onTriggered: {
313 menuModel.activate(menuIndex);
323 menuModel.activate(menuIndex);
314 }
324 }
@@ -431,6 +441,7 @@ Item {
431 property var menuModel: menuFactory.menuModel
441 property var menuModel: menuFactory.menuModel
432 property int menuIndex: -1
442 property int menuIndex: -1
433 property var extendedData: menuData && menuData.ext || undefined
443 property var extendedData: menuData && menuData.ext || undefined
444+ property bool serverChecked: menuData && menuData.isToggled || false
434
445
435 property var strengthAction: UnityMenuAction {
446 property var strengthAction: UnityMenuAction {
436 model: menuModel
447 model: menuModel
@@ -440,12 +451,16 @@ Item {
440
451
441 text: menuData && menuData.label || ""
452 text: menuData && menuData.label || ""
442 enabled: menuData && menuData.sensitive || false
453 enabled: menuData && menuData.sensitive || false
443− active: menuData && menuData.isToggled || false
454+ active: serverChecked
444 secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
455 secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
445 adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
456 adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
446 signalStrength: strengthAction.valid ? strengthAction.state : 0
457 signalStrength: strengthAction.valid ? strengthAction.state : 0
447 highlightWhenPressed: false
458 highlightWhenPressed: false
448
459
460+ onServerCheckedChanged: {
461+ // value can be changed by menu, so a binding won't work.
462+ active = serverChecked;
463+ }
449 onMenuModelChanged: {
464 onMenuModelChanged: {
450 loadAttributes();
465 loadAttributes();
451 }
466 }
qml/Shell.qml+74−40
@@ -87,9 +87,9 @@ Item {
87 }
87 }
88 }
88 }
89
89
90− function setFakeActiveForApp(app) {
90+ function setLockedApp(app) {
91 if (shell.locked) {
91 if (shell.locked) {
92− greeter.fakeActiveForApp = app
92+ greeter.lockedApp = app
93 lockscreen.hide()
93 lockscreen.hide()
94 }
94 }
95 }
95 }
@@ -169,31 +169,37 @@ Item {
169 Connections {
169 Connections {
170 target: ApplicationManager
170 target: ApplicationManager
171 onFocusRequested: {
171 onFocusRequested: {
172+ if (greeter.narrowMode) {
172− if (appId === "dialer-app") {
173+ if (appId === "dialer-app") {
173− // Always let the dialer-app through. Either user asked
174+ // Always let the dialer-app through. Either user asked
174− // for an emergency call or accepted an incoming call.
175+ // for an emergency call or accepted an incoming call.
175− setFakeActiveForApp(appId)
176+ setLockedApp(appId)
176− } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
177+ } else if (greeter.hasLockedApp && greeter.lockedApp !== appId) {
177− lockscreen.show();
178+ greeter.startUnlock()
179+ }
180+ greeter.hide();
181+ } else {
182+ if (LightDM.Greeter.active) {
183+ greeter.startUnlock()
184+ }
178 }
185 }
179− greeter.hide();
180 }
186 }
181
187
182 onFocusedApplicationIdChanged: {
188 onFocusedApplicationIdChanged: {
183− if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
189+ if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
184− lockscreen.show();
190+ greeter.startUnlock()
185 }
191 }
186 panel.indicators.hide();
192 panel.indicators.hide();
187 }
193 }
188
194
189 onApplicationAdded: {
195 onApplicationAdded: {
190 if (greeter.shown && appId != "unity8-dash") {
196 if (greeter.shown && appId != "unity8-dash") {
191− greeter.hide();
197+ greeter.startUnlock()
192 }
198 }
193− if (appId === "dialer-app") {
199+ if (greeter.narrowMode && appId === "dialer-app") {
194 // Always let the dialer-app through. Either user asked
200 // Always let the dialer-app through. Either user asked
195 // for an emergency call or accepted an incoming call.
201 // for an emergency call or accepted an incoming call.
196− setFakeActiveForApp(appId)
202+ setLockedApp(appId)
197 }
203 }
198 launcher.hide();
204 launcher.hide();
199 }
205 }
@@ -201,9 +207,19 @@ Item {
201
207
202 Loader {
208 Loader {
203 id: applicationsDisplayLoader
209 id: applicationsDisplayLoader
210+ objectName: "applicationsDisplayLoader"
204 anchors.fill: parent
211 anchors.fill: parent
205
212
213+ // When we have a locked app, we only want to show that one app.
214+ // FIXME: do this in a less traumatic way. We currently only allow
215+ // locked apps in phone mode (see FIXME in Lockscreen component in
216+ // this same file). When that changes, we need to do something
217+ // nicer here. But this code is currently just to prevent a
218+ // theoretical attack where user enters lockedApp mode, then makes
219+ // the screen larger (maybe connects to monitor) and tries to enter
220+ // tablet mode.
221+ property bool tabletMode: shell.sideStageEnabled && !greeter.hasLockedApp
206− source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
222+ source: tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
207
223
208 Binding {
224 Binding {
209 target: applicationsDisplayLoader.item
225 target: applicationsDisplayLoader.item
@@ -229,7 +245,7 @@ Item {
229 Binding {
245 Binding {
230 target: applicationsDisplayLoader.item
246 target: applicationsDisplayLoader.item
231 property: "spreadEnabled"
247 property: "spreadEnabled"
232− value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
248+ value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
233 }
249 }
234 Binding {
250 Binding {
235 target: applicationsDisplayLoader.item
251 target: applicationsDisplayLoader.item
@@ -304,14 +320,14 @@ Item {
304 // and wider screens are tablets which don't. When we do allow this
320 // and wider screens are tablets which don't. When we do allow this
305 // on devices with a side stage and a SIM, work should be done to
321 // on devices with a side stage and a SIM, work should be done to
306 // ensure that the main stage is disabled while the dialer is present
322 // ensure that the main stage is disabled while the dialer is present
307− // in the side stage.
323+ // in the side stage. See the FIXME in the stage loader in this file.
308 showEmergencyCallButton: !shell.sideStageEnabled
324 showEmergencyCallButton: !shell.sideStageEnabled
309
325
310 onEntered: LightDM.Greeter.respond(passphrase);
326 onEntered: LightDM.Greeter.respond(passphrase);
311 onCancel: greeter.show()
327 onCancel: greeter.show()
312− onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter fake-active mode
328+ onEmergencyCall: shell.activateApplication("dialer-app") // will automatically enter locked-app mode
313
329
314− onShownChanged: if (shown) greeter.fakeActiveForApp = ""
330+ onShownChanged: if (shown) greeter.lockedApp = ""
315
331
316 Timer {
332 Timer {
317 id: forcedDelayTimer
333 id: forcedDelayTimer
@@ -367,11 +383,13 @@ Item {
367 if (!LightDM.Greeter.active) {
383 if (!LightDM.Greeter.active) {
368 return; // could happen if hideGreeter() comes in before we prompt
384 return; // could happen if hideGreeter() comes in before we prompt
369 }
385 }
386+ if (greeter.narrowMode) {
370− if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
387+ if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
371− lockscreen.hide()
388+ lockscreen.hide()
372− } else {
389+ } else {
373− lockscreen.reset();
390+ lockscreen.reset();
374− lockscreen.show();
391+ lockscreen.show();
392+ }
375 }
393 }
376 }
394 }
377
395
@@ -422,7 +440,7 @@ Item {
422 Binding {
440 Binding {
423 target: LightDM.Greeter
441 target: LightDM.Greeter
424 property: "active"
442 property: "active"
425− value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
443+ value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
426 }
444 }
427
445
428 Rectangle {
446 Rectangle {
@@ -434,7 +452,8 @@ Item {
434 Item {
452 Item {
435 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
453 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
436 id: greeterWrapper
454 id: greeterWrapper
455+ objectName: "greeterWrapper"
437− x: launcher.progress
456+ x: greeter.narrowMode ? launcher.progress : 0
438 y: panel.panelHeight
457 y: panel.panelHeight
439 width: parent.width
458 width: parent.width
440 height: parent.height - panel.panelHeight
459 height: parent.height - panel.panelHeight
@@ -447,7 +466,7 @@ Item {
447 property bool fullyShown: showProgress === 1.0
466 property bool fullyShown: showProgress === 1.0
448 onFullyShownChanged: {
467 onFullyShownChanged: {
449 // Wait until the greeter is completely covering lockscreen before resetting it.
468 // Wait until the greeter is completely covering lockscreen before resetting it.
450− if (fullyShown && !LightDM.Greeter.authenticated) {
469+ if (greeter.narrowMode && fullyShown && !LightDM.Greeter.authenticated) {
451 lockscreen.reset();
470 lockscreen.reset();
452 lockscreen.show();
471 lockscreen.show();
453 }
472 }
@@ -456,7 +475,7 @@ Item {
456 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
475 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
457 onShowProgressChanged: {
476 onShowProgressChanged: {
458 if (showProgress === 0) {
477 if (showProgress === 0) {
459− if (LightDM.Greeter.authenticated) {
478+ if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
460 greeter.login()
479 greeter.login()
461 } else if (greeter.narrowMode) {
480 } else if (greeter.narrowMode) {
462 lockscreen.clear(false) // to reset focus if necessary
481 lockscreen.clear(false) // to reset focus if necessary
@@ -470,13 +489,16 @@ Item {
470
489
471 signal sessionStarted() // helpful for tests
490 signal sessionStarted() // helpful for tests
472
491
473− property string fakeActiveForApp: ""
492+ property string lockedApp: ""
493+ property bool hasLockedApp: lockedApp !== ""
474
494
475 available: true
495 available: true
476 hides: [launcher, panel.indicators]
496 hides: [launcher, panel.indicators]
477 shown: true
497 shown: true
478 loadContent: required || lockscreen.required // keeps content in memory for quick show()
498 loadContent: required || lockscreen.required // keeps content in memory for quick show()
479
499
500+ locked: shell.locked
501+
480 defaultBackground: shell.background
502 defaultBackground: shell.background
481
503
482 width: parent.width
504 width: parent.width
@@ -484,6 +506,18 @@ Item {
484
506
485 dragHandleWidth: shell.edgeSize
507 dragHandleWidth: shell.edgeSize
486
508
509+ function startUnlock() {
510+ if (narrowMode) {
511+ if (!LightDM.Greeter.authenticated) {
512+ lockscreen.show()
513+ }
514+ hide()
515+ } else {
516+ show()
517+ tryToUnlock()
518+ }
519+ }
520+
487 function login() {
521 function login() {
488 enabled = false;
522 enabled = false;
489 if (LightDM.Greeter.startSessionSync()) {
523 if (LightDM.Greeter.startSessionSync()) {
@@ -504,8 +538,10 @@ Item {
504
538
505 if (greeter.narrowMode) {
539 if (greeter.narrowMode) {
506 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
540 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
541+ } else {
542+ reset()
507 }
543 }
508− greeter.fakeActiveForApp = "";
544+ greeter.lockedApp = "";
509 greeter.forceActiveFocus();
545 greeter.forceActiveFocus();
510 }
546 }
511 }
547 }
@@ -550,10 +586,7 @@ Item {
550 }
586 }
551
587
552 if (LightDM.Greeter.active) {
588 if (LightDM.Greeter.active) {
553− if (!LightDM.Greeter.authenticated) {
554− lockscreen.show()
555− }
556− greeter.hide()
589+ greeter.startUnlock()
557 }
590 }
558
591
559 var animate = !LightDM.Greeter.active && !stages.shown
592 var animate = !LightDM.Greeter.active && !stages.shown
@@ -562,7 +595,8 @@ Item {
562 }
595 }
563
596
564 function showDash() {
597 function showDash() {
565− if (greeter.fakeActiveForApp !== "") { // just in case user gets here
598+ if (greeter.hasLockedApp || // just in case user gets here
599+ (!greeter.narrowMode && shell.locked)) {
566 return
600 return
567 }
601 }
568
602
@@ -589,7 +623,7 @@ Item {
589 anchors.fill: parent //because this draws indicator menus
623 anchors.fill: parent //because this draws indicator menus
590 indicators {
624 indicators {
591 hides: [launcher]
625 hides: [launcher]
592− available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
626+ available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
593 contentEnabled: edgeDemo.panelContentEnabled
627 contentEnabled: edgeDemo.panelContentEnabled
594 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
628 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
595 panelHeight: units.gu(3)
629 panelHeight: units.gu(3)
@@ -600,7 +634,7 @@ Item {
600 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
634 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
601
635
602 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
636 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
603− || greeter.fakeActiveForApp !== ""
637+ || greeter.hasLockedApp
604 }
638 }
605
639
606 Launcher {
640 Launcher {
@@ -613,7 +647,7 @@ Item {
613 anchors.bottom: parent.bottom
647 anchors.bottom: parent.bottom
614 width: parent.width
648 width: parent.width
615 dragAreaWidth: shell.edgeSize
649 dragAreaWidth: shell.edgeSize
616− available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
650+ available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
617
651
618 onShowDashHome: showHome()
652 onShowDashHome: showHome()
619 onDash: showDash()
653 onDash: showDash()
@@ -623,8 +657,8 @@ Item {
623 }
657 }
624 }
658 }
625 onLauncherApplicationSelected: {
659 onLauncherApplicationSelected: {
626− if (greeter.fakeActiveForApp !== "") {
660+ if (greeter.hasLockedApp) {
627− lockscreen.show()
661+ greeter.startUnlock()
628 }
662 }
629 if (!edgeDemo.running)
663 if (!edgeDemo.running)
630 shell.activateApplication(appId)
664 shell.activateApplication(appId)
qml/Stages/PhoneStage.qml+8
@@ -85,6 +85,14 @@ Rectangle {
85 if (spreadView.closingIndex == -1) {
85 if (spreadView.closingIndex == -1) {
86 spreadView.phase = 0;
86 spreadView.phase = 0;
87 spreadView.contentX = -spreadView.shift;
87 spreadView.contentX = -spreadView.shift;
88+ focusTopMostApp();
89+ }
90+ }
91+
92+ function focusTopMostApp() {
93+ if (ApplicationManager.count > 0) {
94+ var topmostApp = ApplicationManager.get(0);
95+ ApplicationManager.focusApplication(topmostApp.appId);
88 }
96 }
89 }
97 }
90 }
98 }
qml/Stages/TabletStage.qml+7
@@ -139,9 +139,16 @@ Rectangle {
139 if (priv.sideStageAppId == appId) {
139 if (priv.sideStageAppId == appId) {
140 priv.sideStageAppId = "";
140 priv.sideStageAppId = "";
141 }
141 }
142+
142 if (ApplicationManager.count == 0) {
143 if (ApplicationManager.count == 0) {
143 spreadView.phase = 0;
144 spreadView.phase = 0;
144 spreadView.contentX = -spreadView.shift;
145 spreadView.contentX = -spreadView.shift;
146+ } else if (spreadView.closingIndex == -1) {
147+ // Unless we're closing the app ourselves in the spread,
148+ // lets make sure the spread doesn't mess up by the changing app list.
149+ spreadView.phase = 0;
150+ spreadView.contentX = -spreadView.shift;
151+ ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
145 }
152 }
146 }
153 }
147 }
154 }
run_on_device.sh+4−5
@@ -130,11 +130,10 @@ if [ -z "${PASSWORD}" ]; then
130 usage
130 usage
131fi
131fi
132
132
133−PASSFILE=$( adb shell "FILE=\`mktemp\`
133+PASSFILE=$(adb shell 'mktemp' | tr -d '\r')
134− echo \"#!/bin/sh\" > \$FILE
134+adb shell "echo '#!/bin/sh' > ${PASSFILE}"
135− echo \"echo \\\"${PASSWORD}\\\"\" >> \$FILE
135+adb shell "echo \"echo \\\"${PASSWORD}\\\"\" >> ${PASSFILE}"
136− chmod +x \$FILE
136+adb shell "chmod +x ${PASSFILE}"
137− echo \$FILE" | tr -d '\r' )
138
137
139setup_adb_forwarding
138setup_adb_forwarding
140
139
tests/autopilot/unity8/indicators/tests/test_indicators.py+2−2
@@ -72,11 +72,11 @@ class IndicatorPageTitleMatchesWidgetTestCase(IndicatorTestCase):
72 ('Bluetooth', dict(indicator_name='indicator-bluetooth',
72 ('Bluetooth', dict(indicator_name='indicator-bluetooth',
73 title='Bluetooth')),
73 title='Bluetooth')),
74 ('Datetime', dict(indicator_name='indicator-datetime',
74 ('Datetime', dict(indicator_name='indicator-datetime',
75− title='Upcoming events')),
75+ title='Time & Date')),
76 ('Location', dict(indicator_name='indicator-location',
76 ('Location', dict(indicator_name='indicator-location',
77 title='Location')),
77 title='Location')),
78 ('Messaging', dict(indicator_name='indicator-messages',
78 ('Messaging', dict(indicator_name='indicator-messages',
79− title='Notification center')),
79+ title='Notifications')),
80 ('Network', dict(indicator_name='indicator-network',
80 ('Network', dict(indicator_name='indicator-network',
81 title='Network')),
81 title='Network')),
82 ('Power', dict(indicator_name='indicator-power',
82 ('Power', dict(indicator_name='indicator-power',
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+1
@@ -54,6 +54,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
54 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
54 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
55 item->setCount(1234567890);
55 item->setCount(1234567890);
56 item->setCountVisible(true);
56 item->setCountVisible(true);
57+ item->setPinned(true);
57 m_list.append(item);
58 m_list.append(item);
58 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
59 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
59 item->setProgress(50);
60 item->setProgress(50);
tests/mocks/libusermetrics/UserMetrics.cpp+2−2
@@ -238,7 +238,7 @@ void UserMetricsPrivate::setUsername(const QString &username)
238 m_username = username;
238 m_username = username;
239
239
240 m_dataIndex = m_fakeData.constFind(m_username);
240 m_dataIndex = m_fakeData.constFind(m_username);
241− if (m_dataIndex == m_fakeData.end())
241+ if (m_dataIndex == m_fakeData.constEnd())
242 {
242 {
243 m_dataIndex = m_fakeData.constFind("");
243 m_dataIndex = m_fakeData.constFind("");
244 }
244 }
@@ -383,7 +383,7 @@ void UserMetricsPrivate::finishSetFakeData()
383void UserMetricsPrivate::nextFakeData()
383void UserMetricsPrivate::nextFakeData()
384{
384{
385 ++m_dataIndex;
385 ++m_dataIndex;
386− if (m_dataIndex == m_fakeData.end() || m_dataIndex.key() != m_username)
386+ if (m_dataIndex == m_fakeData.constEnd() || m_dataIndex.key() != m_username)
387 {
387 {
388 m_dataIndex = m_fakeData.constFind(m_username);
388 m_dataIndex = m_fakeData.constFind(m_username);
389 }
389 }
tests/qmltests/CMakeLists.txt+1
@@ -21,6 +21,7 @@ set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
21
21
22add_qml_test(. Shell ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
22add_qml_test(. Shell ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
23add_qml_test(. ShellWithPin ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single-pin")
23add_qml_test(. ShellWithPin ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single-pin")
24+add_qml_test(. TabletShell ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/full")
24add_qml_test(Components Background)
25add_qml_test(Components Background)
25add_qml_test(Components Carousel)
26add_qml_test(Components Carousel)
26add_qml_test(Components DraggingArea)
27add_qml_test(Components DraggingArea)
tests/qmltests/Greeter/tst_MultiGreeter.qml+2−2
@@ -29,6 +29,7 @@ Item {
29 Greeter {
29 Greeter {
30 id: greeter
30 id: greeter
31 anchors.fill: parent
31 anchors.fill: parent
32+ locked: !LightDM.Greeter.authenticated
32 }
33 }
33
34
34 Component {
35 Component {
@@ -115,8 +116,7 @@ Item {
115 var waitForSignal = data.uid != 0 && userList.currentIndex != data.uid
116 var waitForSignal = data.uid != 0 && userList.currentIndex != data.uid
116 select_index(data.uid)
117 select_index(data.uid)
117 tryCompare(userList, "currentIndex", data.uid)
118 tryCompare(userList, "currentIndex", data.uid)
118− tryCompare(greeter, "locked", data.tag !== "no-password" &&
119+ tryCompare(greeter, "locked", data.tag !== "no-password")
119− data.tag !== "auth-error")
120 if (waitForSignal) {
120 if (waitForSignal) {
121 selectionSpy.wait()
121 selectionSpy.wait()
122 tryCompare(selectionSpy, "count", 1)
122 tryCompare(selectionSpy, "count", 1)
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+24
@@ -37,6 +37,11 @@ Item {
37 property int modelIndex: 0
37 property int modelIndex: 0
38 property var data
38 property var data
39
39
40+ anchors {
41+ left: parent.left
42+ right: parent.right
43+ }
44+
40 onLoaded: {
45 onLoaded: {
41 if (item.hasOwnProperty("menuData")) {
46 if (item.hasOwnProperty("menuData")) {
42 item.menuData = data;
47 item.menuData = data;
@@ -734,5 +739,24 @@ Item {
734 compare(loader.item.canGoPrevious, false, "CanGoPrevious should be false");
739 compare(loader.item.canGoPrevious, false, "CanGoPrevious should be false");
735 compare(loader.item.enabled, data.enabled, "Enabled does not match data");
740 compare(loader.item.enabled, data.enabled, "Enabled does not match data");
736 }
741 }
742+
743+ function test_lp1336715_broken_checkbox_bindings() {
744+ menuData.type = "com.canonical.indicator.switch";
745+ menuData.sensitive = true;
746+ menuData.isToggled = false;
747+
748+ loader.data = menuData;
749+ loader.sourceComponent = factory.load(menuData);
750+
751+ compare(loader.item.checked, false, "Loader did not load check state");
752+ mouseClick(loader.item,
753+ loader.item.width / 2, loader.item.height / 2);
754+ compare(loader.item.checked, true, "Clicking switch menu should toggle check");
755+
756+ menuData.isToggled = true; // toggled will not update in mock
757+ menuData.isToggled = false;
758+
759+ compare(loader.item.checked, false, "Server updates no longer working");
760+ }
737 }
761 }
738}
762}
tests/qmltests/Stages/tst_PhoneStage.qml+17
@@ -322,5 +322,22 @@ Item {
322 }
322 }
323 phoneStage.orientation = Qt.PortraitOrientation;
323 phoneStage.orientation = Qt.PortraitOrientation;
324 }
324 }
325+
326+ function test_focusNewTopMostAppAfterFocusedOneClosesItself() {
327+ addApps(2);
328+
329+ var secondApp = ApplicationManager.get(0);
330+ tryCompare(secondApp, "state", ApplicationInfoInterface.Running);
331+ tryCompare(secondApp, "focused", true);
332+
333+ var firstApp = ApplicationManager.get(1);
334+ tryCompare(firstApp, "state", ApplicationInfoInterface.Suspended);
335+ tryCompare(firstApp, "focused", false);
336+
337+ ApplicationManager.stopApplication(secondApp.appId);
338+
339+ tryCompare(firstApp, "state", ApplicationInfoInterface.Running);
340+ tryCompare(firstApp, "focused", true);
341+ }
325 }
342 }
326}
343}
tests/qmltests/tst_Shell.qml+1
@@ -484,6 +484,7 @@ Row {
484 tryCompare(greeter, "showProgress", 0)
484 tryCompare(greeter, "showProgress", 0)
485 waitForRendering(greeter);
485 waitForRendering(greeter);
486 LightDM.Greeter.showGreeter()
486 LightDM.Greeter.showGreeter()
487+ waitForRendering(greeter)
487 tryCompare(greeter, "showProgress", 1)
488 tryCompare(greeter, "showProgress", 1)
488 LightDM.Greeter.hideGreeter()
489 LightDM.Greeter.hideGreeter()
489 tryCompare(greeter, "showProgress", 0)
490 tryCompare(greeter, "showProgress", 0)
tests/qmltests/tst_ShellWithPin.qml+106−25
@@ -27,10 +27,9 @@ import Powerd 0.1
27
27
28import "../../qml"
28import "../../qml"
29
29
30−Item {
30+Row {
31 id: root
31 id: root
32− width: shell.width + units.gu(20)
32+ spacing: 0
33− height: shell.height
34
33
35 QtObject {
34 QtObject {
36 id: applicationArguments
35 id: applicationArguments
@@ -48,21 +47,55 @@ Item {
48 }
47 }
49 }
48 }
50
49
51− Shell {
50+ Loader {
52− id: shell
51+ id: shellLoader
53− maxFailedLogins: maxRetriesTextField.text
52+
54− }
55− Column {
56− anchors { top: parent.top; right: parent.right; bottom: parent.bottom; margins:units.gu(1) }
57− width: units.gu(18)
53+ width: units.gu(40)
54+ height: units.gu(71)
58
55
59− Label {
56+ property bool itemDestroyed: false
60− text: "Max retries:"
57+ sourceComponent: Component {
61− color: "black"
58+ Shell {
59+ Component.onDestruction: {
60+ shellLoader.itemDestroyed = true
61+ }
62+ maxFailedLogins: maxRetriesTextField.text
63+ }
62 }
64 }
65+ }
66+
67+ Rectangle {
68+ color: "white"
69+ width: units.gu(30)
70+ height: shellLoader.height
71+
72+ Column {
73+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
74+ spacing: units.gu(1)
75+ Row {
76+ anchors { left: parent.left; right: parent.right }
77+ Button {
78+ text: "Show Greeter"
79+ onClicked: {
80+ if (shellLoader.status !== Loader.Ready)
81+ return
82+
83+ var greeter = testCase.findChild(shellLoader.item, "greeter")
84+ if (!greeter.shown) {
85+ greeter.show()
86+ }
87+ }
88+ }
89+ }
90+
91+ Label {
92+ text: "Max retries:"
93+ color: "black"
94+ }
63− TextField {
95+ TextField {
64− id: maxRetriesTextField
96+ id: maxRetriesTextField
65− text: "-1"
97+ text: "-1"
98+ }
66 }
99 }
67 }
100 }
68
101
@@ -81,12 +114,10 @@ Item {
81 name: "ShellWithPin"
114 name: "ShellWithPin"
82 when: windowShown
115 when: windowShown
83
116
84− function initTestCase() {
117+ property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
85−
86− sessionSpy.target = findChild(shell, "greeter")
87− }
88
118
89 function init() {
119 function init() {
120+ sessionSpy.target = findChild(shell, "greeter")
90 swipeAwayGreeter()
121 swipeAwayGreeter()
91 shell.failedLoginsDelayAttempts = -1
122 shell.failedLoginsDelayAttempts = -1
92 maxRetriesTextField.text = "-1"
123 maxRetriesTextField.text = "-1"
@@ -95,12 +126,28 @@ Item {
95 }
126 }
96
127
97 function cleanup() {
128 function cleanup() {
98− LightDM.Greeter.showGreeter()
129+ shellLoader.itemDestroyed = false
99− var greeter = findChild(shell, "greeter")
130+
100− tryCompare(greeter, "showProgress", 1)
131+ shellLoader.active = false
132+
133+ tryCompare(shellLoader, "status", Loader.Null)
134+ tryCompare(shellLoader, "item", null)
135+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
136+ // item might still be alive. So if we set Loader.active back to true
137+ // again right now we will get the very same Shell instance back. So no reload
138+ // actually took place. Likely because Loader waits until the next event loop
139+ // iteration to do its work. So to ensure the reload, we will wait until the
140+ // Shell instance gets destroyed.
141+ tryCompare(shellLoader, "itemDestroyed", true)
101
142
102 // kill all (fake) running apps
143 // kill all (fake) running apps
103 killApps()
144 killApps()
145+
146+ // reload our test subject to get it in a fresh state once again
147+ shellLoader.active = true
148+
149+ tryCompare(shellLoader, "status", Loader.Ready)
150+ removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item)
104 }
151 }
105
152
106 function killApps() {
153 function killApps() {
@@ -113,6 +160,7 @@ Item {
113
160
114 function swipeAwayGreeter() {
161 function swipeAwayGreeter() {
115 var greeter = findChild(shell, "greeter");
162 var greeter = findChild(shell, "greeter");
163+ waitForRendering(greeter)
116 tryCompare(greeter, "showProgress", 1);
164 tryCompare(greeter, "showProgress", 1);
117
165
118 var touchX = shell.width - (shell.edgeSize / 2);
166 var touchX = shell.width - (shell.edgeSize / 2);
@@ -168,7 +216,8 @@ Item {
168
216
169 mouseClick(emergencyButton, units.gu(1), units.gu(1))
217 mouseClick(emergencyButton, units.gu(1), units.gu(1))
170
218
171− tryCompare(greeter, "fakeActiveForApp", "dialer-app")
219+ tryCompare(greeter, "lockedApp", "dialer-app")
220+ tryCompare(greeter, "hasLockedApp", true)
172 tryCompare(lockscreen, "shown", false)
221 tryCompare(lockscreen, "shown", false)
173 tryCompare(panel, "fullscreenMode", true)
222 tryCompare(panel, "fullscreenMode", true)
174 tryCompare(indicators, "available", false)
223 tryCompare(indicators, "available", false)
@@ -180,7 +229,8 @@ Item {
180 LightDM.Greeter.showGreeter()
229 LightDM.Greeter.showGreeter()
181
230
182 tryCompare(greeter, "shown", true)
231 tryCompare(greeter, "shown", true)
183− tryCompare(greeter, "fakeActiveForApp", "")
232+ tryCompare(greeter, "lockedApp", "")
233+ tryCompare(greeter, "hasLockedApp", false)
184 tryCompare(lockscreen, "shown", true)
234 tryCompare(lockscreen, "shown", true)
185 tryCompare(panel, "fullscreenMode", false)
235 tryCompare(panel, "fullscreenMode", false)
186 tryCompare(indicators, "available", true)
236 tryCompare(indicators, "available", true)
@@ -253,5 +303,36 @@ Item {
253 enterPin("1111")
303 enterPin("1111")
254 tryCompare(resetSpy, "count", 1)
304 tryCompare(resetSpy, "count", 1)
255 }
305 }
306+
307+ function test_emergencyDialerLockOut() {
308+ // This is a theoretical attack on the lockscreen: Enter emergency
309+ // dialer mode on a phone, then plug into a larger screen,
310+ // switching to a tablet interface. This would in theory move the
311+ // dialer to a side stage and give access to other apps. So just
312+ // confirm that such an attack doesn't work.
313+
314+ var applicationsDisplayLoader = findChild(shell, "applicationsDisplayLoader")
315+
316+ // We start in phone mode
317+ tryCompare(shell, "sideStageEnabled", false)
318+ tryCompare(applicationsDisplayLoader, "tabletMode", false)
319+
320+ var app = ApplicationManager.startApplication("dialer-app")
321+
322+ var greeter = findChild(shell, "greeter")
323+ tryCompare(greeter, "showProgress", 0)
324+ tryCompare(greeter, "hasLockedApp", true)
325+
326+ // OK, we're in. Now try (but fail) to switch to tablet mode
327+ shell.tablet = true
328+ tryCompare(shell, "sideStageEnabled", true)
329+ tryCompare(applicationsDisplayLoader, "tabletMode", false)
330+
331+ // And when we kill the app, we go back to locked tablet mode
332+ killApps()
333+ tryCompare(greeter, "showProgress", 1)
334+ tryCompare(shell, "sideStageEnabled", true)
335+ tryCompare(applicationsDisplayLoader, "tabletMode", true)
336+ }
256 }
337 }
257}
338}
tests/qmltests/tst_TabletShell.qmlnew file+289
@@ -0,0 +1,289 @@
1+/*
2+ * Copyright (C) 2013,2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import GSettings 1.0
20+import LightDM 0.1 as LightDM
21+import Ubuntu.Components 1.1
22+import Ubuntu.Telephony 0.1 as Telephony
23+import Unity.Application 0.1
24+import Unity.Connectivity 0.1
25+import Unity.Test 0.1 as UT
26+import Powerd 0.1
27+
28+import "../../qml"
29+
30+Row {
31+ id: root
32+ spacing: 0
33+
34+ QtObject {
35+ id: applicationArguments
36+
37+ function hasGeometry() {
38+ return false
39+ }
40+
41+ function width() {
42+ return 0
43+ }
44+
45+ function height() {
46+ return 0
47+ }
48+ }
49+
50+ Loader {
51+ id: shellLoader
52+
53+ width: units.gu(100)
54+ height: units.gu(80)
55+
56+ property bool itemDestroyed: false
57+ sourceComponent: Component {
58+ Shell {
59+ Component.onDestruction: {
60+ shellLoader.itemDestroyed = true
61+ }
62+ }
63+ }
64+ }
65+
66+ Rectangle {
67+ color: "white"
68+ width: units.gu(20)
69+ height: shellLoader.height
70+
71+ Column {
72+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
73+ spacing: units.gu(1)
74+ Row {
75+ anchors { left: parent.left; right: parent.right }
76+ Button {
77+ text: "Show Greeter"
78+ onClicked: {
79+ if (shellLoader.status !== Loader.Ready)
80+ return
81+
82+ var greeter = testCase.findChild(shellLoader.item, "greeter")
83+ if (!greeter.shown) {
84+ greeter.show()
85+ }
86+ }
87+ }
88+ }
89+ }
90+ }
91+
92+ SignalSpy {
93+ id: sessionSpy
94+ signalName: "sessionStarted"
95+ }
96+
97+ SignalSpy {
98+ id: dashCommunicatorSpy
99+ signalName: "setCurrentScopeCalled"
100+ }
101+
102+ SignalSpy {
103+ id: unlockAllModemsSpy
104+ target: Connectivity
105+ signalName: "unlockingAllModems"
106+ }
107+
108+ Telephony.CallEntry {
109+ id: phoneCall
110+ phoneNumber: "+447812221111"
111+ }
112+
113+ UT.UnityTestCase {
114+ id: testCase
115+ name: "TabletShell"
116+ when: windowShown
117+
118+ property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
119+
120+ function init() {
121+ sessionSpy.clear()
122+ sessionSpy.target = findChild(shell, "greeter")
123+ dashCommunicatorSpy.target = findInvisibleChild(shell, "dashCommunicator")
124+ }
125+
126+ function cleanup() {
127+ shellLoader.itemDestroyed = false
128+
129+ shellLoader.active = false
130+
131+ tryCompare(shellLoader, "status", Loader.Null)
132+ tryCompare(shellLoader, "item", null)
133+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
134+ // item might still be alive. So if we set Loader.active back to true
135+ // again right now we will get the very same Shell instance back. So no reload
136+ // actually took place. Likely because Loader waits until the next event loop
137+ // iteration to do its work. So to ensure the reload, we will wait until the
138+ // Shell instance gets destroyed.
139+ tryCompare(shellLoader, "itemDestroyed", true)
140+
141+ // kill all (fake) running apps
142+ killApps()
143+
144+ unlockAllModemsSpy.clear()
145+
146+ // reload our test subject to get it in a fresh state once again
147+ shellLoader.active = true
148+
149+ tryCompare(shellLoader, "status", Loader.Ready)
150+ removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item)
151+ }
152+
153+ function killApps() {
154+ while (ApplicationManager.count > 1) {
155+ var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
156+ ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId)
157+ }
158+ compare(ApplicationManager.count, 1)
159+ }
160+
161+ function selectIndex(i) {
162+ // We could be anywhere in list; find target index to know which direction
163+ var greeter = findChild(shell, "greeter")
164+ var userlist = findChild(greeter, "userList")
165+ if (userlist.currentIndex == i)
166+ keyClick(Qt.Key_Escape) // Reset state if we're not moving
167+ while (userlist.currentIndex != i) {
168+ var next = userlist.currentIndex + 1
169+ if (userlist.currentIndex > i) {
170+ next = userlist.currentIndex - 1
171+ }
172+ var account = findChild(greeter, "username"+next)
173+ mouseClick(account, 1, 1)
174+ tryCompare(userlist, "currentIndex", next)
175+ tryCompare(userlist, "movingInternally", false)
176+ }
177+ }
178+
179+ function selectUser(name) {
180+ // Find index of user with the right name
181+ var greeter = findChild(shell, "greeter")
182+ for (var i = 0; i < greeter.model.count; i++) {
183+ if (greeter.model.data(i, LightDM.UserRoles.NameRole) == name) {
184+ break
185+ }
186+ }
187+ if (i == greeter.model.count) {
188+ fail("Didn't find name")
189+ return -1
190+ }
191+ selectIndex(i)
192+ return i
193+ }
194+
195+ function clickPasswordInput(isButton) {
196+ var greeter = findChild(shell, "greeter")
197+ tryCompare(greeter, "showProgress", 1)
198+
199+ var passwordMouseArea = findChild(shell, "passwordMouseArea")
200+ tryCompare(passwordMouseArea, "enabled", isButton)
201+
202+ var passwordInput = findChild(shell, "passwordInput")
203+ mouseClick(passwordInput, passwordInput.width / 2, passwordInput.height / 2)
204+ }
205+
206+ function confirmLoggedIn(loggedIn) {
207+ var greeterWrapper = findChild(shell, "greeterWrapper")
208+ tryCompare(greeterWrapper, "showProgress", loggedIn ? 0 : 1)
209+ tryCompare(sessionSpy, "count", loggedIn ? 1 : 0)
210+ }
211+
212+ function swipeFromLeftEdge(swipeLength) {
213+ var touchStartX = 2
214+ var touchStartY = shell.height / 2
215+ touchFlick(shell, touchStartX, touchStartY, swipeLength, touchStartY)
216+ }
217+
218+ function test_noLockscreen() {
219+ selectUser("has-password")
220+ var lockscreen = findChild(shell, "lockscreen")
221+ tryCompare(lockscreen, "shown", false)
222+ }
223+
224+ function test_showAndHideGreeterDBusCalls() {
225+ var greeter = findChild(shell, "greeter")
226+ LightDM.Greeter.hideGreeter()
227+ tryCompare(greeter, "showProgress", 0)
228+ LightDM.Greeter.showGreeter()
229+ tryCompare(greeter, "showProgress", 1)
230+ }
231+
232+ function test_login_data() {
233+ return [
234+ {tag: "auth error", user: "auth-error", loggedIn: false, password: ""},
235+ {tag: "with password", user: "has-password", loggedIn: true, password: "password"},
236+ {tag: "without password", user: "no-password", loggedIn: true, password: ""},
237+ ]
238+ }
239+
240+ function test_login(data) {
241+ selectUser(data.user)
242+
243+ clickPasswordInput(data.password === "")
244+
245+ if (data.password !== "") {
246+ typeString(data.password)
247+ keyClick(Qt.Key_Enter)
248+ }
249+
250+ confirmLoggedIn(data.loggedIn)
251+ }
252+
253+ function test_appLaunchDuringGreeter_data() {
254+ return [
255+ {tag: "auth error", user: "auth-error", loggedIn: false, passwordFocus: false},
256+ {tag: "without password", user: "no-password", loggedIn: true, passwordFocus: false},
257+ {tag: "with password", user: "has-password", loggedIn: false, passwordFocus: true},
258+ ]
259+ }
260+
261+ function test_appLaunchDuringGreeter(data) {
262+ selectUser(data.user)
263+
264+ var greeter = findChild(shell, "greeter")
265+ var app = ApplicationManager.startApplication("dialer-app")
266+
267+ confirmLoggedIn(data.loggedIn)
268+
269+ if (data.passwordFocus) {
270+ var passwordInput = findChild(greeter, "passwordInput")
271+ tryCompare(passwordInput, "focus", true)
272+ }
273+ }
274+
275+ function test_leftEdgeDrag_data() {
276+ return [
277+ {tag: "without password", user: "no-password", loggedIn: true},
278+ {tag: "with password", user: "has-password", loggedIn: false},
279+ ]
280+ }
281+
282+ function test_leftEdgeDrag(data) {
283+ selectUser(data.user)
284+ swipeFromLeftEdge(shell.width * 0.75)
285+ wait(500) // to give time to handle dash() signal from Launcher
286+ confirmLoggedIn(data.loggedIn)
287+ }
288+ }
289+}
tests/utils/modules/Unity/Test/UnityTestCase.qml+9
@@ -20,8 +20,17 @@ import Ubuntu.Components 0.1
20import Unity.Test 0.1 as UT
20import Unity.Test 0.1 as UT
21
21
22TestCase {
22TestCase {
23+ id: testCase
23 TestUtil {id:util}
24 TestUtil {id:util}
24
25
26+ ActivityIndicator {
27+ visible: testCase.running
28+ anchors.centerIn: parent
29+ Component.onCompleted: parent = testCase.parent
30+ z: 100
31+ running: visible
32+ }
33+
25 // Fake implementation to be provided to items under test
34 // Fake implementation to be provided to items under test
26 property var fakeDateTime: new function() {
35 property var fakeDateTime: new function() {
27 this.currentTimeMs = 0
36 this.currentTimeMs = 0