on xenial help sign in
Merge from trunk
commitdddfa87e08c62c414682a15854bee1299f2192e4 authorMichael Terry <michael.terry@canonical.com> dateThu 9 Oct 2014, 12:09 (12 years ago) parent341a832d548b1d
31 files changed+700−155
CMakeLists.txt+3 debian/changelog+42 plugins/Dash/CardCreator.js+1−1 qml/Components/ListItems/Base.qml+2−11 qml/Components/PinLockscreen.qml+1 qml/Components/ResponsiveVerticalJournal.qml+3−2 qml/Dash/Dash.qml+3−4 qml/Dash/DashRenderer.qml+2−2 qml/Dash/GenericScopeView.qml−1 qml/Dash/ScopesOverview.qml+9−1 qml/Greeter/Greeter.qml+13−1 qml/Greeter/GreeterContent.qml+12 qml/Greeter/LoginList.qml+19−6 qml/Launcher/LauncherDelegate.qml+20−33 qml/Launcher/LauncherPanel.qml+5−14 qml/Panel/Indicators/MenuItemFactory.qml+18−3 qml/Shell.qml+74−40 qml/Stages/PhoneStage.qml+8 qml/Stages/TabletStage.qml+7 run_on_device.sh+4−5 tests/autopilot/unity8/indicators/tests/test_indicators.py+2−2 tests/mocks/Unity/Launcher/MockLauncherModel.cpp+1 tests/mocks/libusermetrics/UserMetrics.cpp+2−2 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Greeter/tst_MultiGreeter.qml+2−2 tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+24 tests/qmltests/Stages/tst_PhoneStage.qml+17 tests/qmltests/tst_Shell.qml+1 tests/qmltests/tst_ShellWithPin.qml+106−25 tests/qmltests/tst_TabletShell.qml+289 tests/utils/modules/Unity/Test/UnityTestCase.qml+9
CMakeLists.txt+3
@@ -87,6 +87,9 @@ endif()
8787# "signals" and "slots"
8888add_definitions(-DQT_NO_KEYWORDS)
8989
90+# Save a few container detach and conversions
91+add_definitions(-DQT_STRICT_ITERATORS)
92+
9093# Tests
9194include(CTest)
9295enable_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+
143unity8 (8.00+14.10.20141008-0ubuntu1) utopic; urgency=low
244
345 [ Michael Zanetti ]
plugins/Dash/CardCreator.js+1−1
@@ -624,7 +624,7 @@ function cardString(template, components) {
624624
625625 var implicitHeight = 'implicitHeight: ';
626626 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';
628628 } else if (hasHeaderRow) {
629629 implicitHeight += 'row.y + row.height + units.gu(1);\n';
630630 } else if (hasMascot) {
qml/Components/ListItems/Base.qml+2−11
@@ -17,7 +17,7 @@
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
1919
20−AbstractButton {
20+Item {
2121 id: emptyListItem
2222 width: parent ? parent.width : units.gu(31)
2323 height: body.height + bottomDividerLine.height
@@ -28,15 +28,6 @@ AbstractButton {
2828 */
2929 property bool selected: false
3030
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−
4031 /*!
4132 \preliminary
4233 Set to show or hide the thin bottom divider line (drawn by the \l ThinDivider component).
@@ -119,7 +110,7 @@ AbstractButton {
119110 right: parent.right
120111 bottom: bottomDividerLine.top
121112 }
122− pressed: (emptyListItem.selected || (emptyListItem.highlightWhenPressed && emptyListItem.pressed)) ? "pressed" : ""
113+ pressed: emptyListItem.selected
123114 }
124115 }
125116}
qml/Components/PinLockscreen.qml+1
@@ -114,6 +114,7 @@ Column {
114114 horizontalAlignment: Text.AlignHCenter
115115 text: root.errorText
116116 visible: pinentryField.incorrectOverride
117+ scale: Math.min(1, parent.width / width)
117118 }
118119
119120 AbstractButton {
qml/Components/ResponsiveVerticalJournal.qml+3−2
@@ -47,7 +47,9 @@ Item {
4747 property alias delegate: verticalJournalView.delegate
4848 property alias displayMarginBeginning: verticalJournalView.displayMarginBeginning
4949 property alias displayMarginEnd: verticalJournalView.displayMarginEnd
50− implicitHeight: verticalJournalView.implicitHeight
50+
51+ implicitHeight: verticalJournalView.implicitHeight + rowSpacing
52+ clip: height < implicitHeight
5153
5254 VerticalJournal {
5355 id: verticalJournalView
@@ -59,7 +61,6 @@ Item {
5961 topMargin: rowSpacing / 2
6062 bottomMargin: rowSpacing / 2
6163 }
62− clip: parent.height != implicitHeight
6364
6465 function px2gu(pixels) {
6566 return Math.floor(pixels / units.gu(1))
qml/Dash/Dash.qml+3−4
@@ -29,7 +29,6 @@ Showable {
2929 visible: shown
3030
3131 property string showScopeOnLoaded: "clickscope"
32− property real contentScale: 1.0
3332
3433 DashCommunicatorService {
3534 objectName: "dashCommunicatorService"
@@ -41,8 +40,9 @@ Showable {
4140 if (window.active) {
4241 dashContentCache.scheduleUpdate();
4342 }
44− overviewController.enableAnimation = window.active;
43+ overviewController.enableAnimation = window.active && !scopesOverview.showingNonFavoriteScope;
4544 overviewController.progress = 0;
45+ scopesOverview.closeTempScope();
4646 }
4747 }
4848 }
@@ -190,7 +190,6 @@ Showable {
190190 dash.showScopeOnLoaded = ""
191191 }
192192 }
193− scale: dash.contentScale
194193 clip: scale != 1.0 || scopeItem.visible || overviewController.progress != 0
195194 Behavior on x {
196195 UbuntuNumberAnimation {
@@ -245,7 +244,7 @@ Showable {
245244 y: overviewController.progress == 0 ? dashContent.y : overviewProgressY
246245 width: parent.width
247246 height: parent.height
248− scale: dash.contentScale * overviewProgressScale
247+ scale: overviewProgressScale
249248 enabled: opacity == 1
250249 opacity: 1 - overviewController.progress
251250 clip: scale != 1.0
qml/Dash/DashRenderer.qml+2−2
@@ -17,9 +17,9 @@
1717import QtQuick 2.0
1818
1919Item {
20− property int collapsedHeight
20+ property real collapsedHeight
2121
22− property int expandedHeight
22+ property real expandedHeight
2323
2424 property int collapsedItemCount: -1
2525
qml/Dash/GenericScopeView.qml−1
@@ -213,7 +213,6 @@ FocusScope {
213213 delegate: ListItems.Base {
214214 id: baseItem
215215 objectName: "dashCategory" + category
216− highlightWhenPressed: false
217216 showDivider: false
218217
219218 property Item seeAllButton: seeAll
qml/Dash/ScopesOverview.qml+9−1
@@ -89,6 +89,7 @@ Item {
8989 } else {
9090 root.allFavoriteSelected(scopeId);
9191 }
92+ previewListView.open = false;
9293 }
9394 }
9495
@@ -98,6 +99,14 @@ Item {
9899 value: progress === 1
99100 }
100101
102+ function closeTempScope() {
103+ if (tempScopeItem.scope) {
104+ root.scope.closeScope(tempScopeItem.scope);
105+ tempScopeItem.scope = null;
106+ tempScopeItem.backClicked()
107+ }
108+ }
109+
101110 function animateDashFromAll(scopeId) {
102111 var currentScopePos = allScopeCardPosition(scopeId);
103112 if (currentScopePos) {
@@ -531,7 +540,6 @@ Item {
531540
532541 width: parent.width
533542 height: parent.height
534− scale: dash.contentScale
535543 clip: scale != 1.0
536544 visible: scope != null
537545 hasBackAction: true
qml/Greeter/Greeter.qml+13−1
@@ -36,7 +36,7 @@ Showable {
3636
3737 property alias dragHandleWidth: dragHandle.width
3838 property alias model: greeterContentLoader.model
39− property bool locked: shown && !LightDM.Greeter.promptless
39+ property bool locked: true
4040
4141 readonly property bool narrowMode: !multiUser && height > width
4242 readonly property bool multiUser: LightDM.Users.count > 1
@@ -57,6 +57,18 @@ Showable {
5757 }
5858 }
5959
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+
6072 onRequiredChanged: {
6173 // Reset hide animation to default once we're finished with it
6274 if (required) {
qml/Greeter/GreeterContent.qml+12
@@ -29,6 +29,18 @@ Item {
2929 signal selected(int uid)
3030 signal unlocked(int uid)
3131
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+
3244 Rectangle {
3345 // In case background fails to load
3446 id: backgroundBackup
qml/Greeter/LoginList.qml+19−6
@@ -35,6 +35,22 @@ Item {
3535 signal selected(int uid)
3636 signal unlocked(int uid)
3737
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+
3854 Keys.onEscapePressed: root.resetAuthentication()
3955
4056 Rectangle {
@@ -229,14 +245,11 @@ Item {
229245 }
230246
231247 MouseArea {
248+ id: passwordMouseArea
249+ objectName: "passwordMouseArea"
232250 anchors.fill: passwordInput
233251 enabled: LightDM.Greeter.promptless
234− onClicked: {
235− if (LightDM.Greeter.authenticated)
236− root.unlocked(userList.currentIndex);
237− else
238− root.resetAuthentication();
239− }
252+ onClicked: root.tryToUnlock()
240253 }
241254
242255 function resetAuthentication() {
qml/Launcher/LauncherDelegate.qml+20−33
@@ -27,7 +27,7 @@ Item {
2727 property bool itemFocused: false
2828 property real maxAngle: 0
2929 property bool inverted: false
30− property bool clipCorner: false
30+ property alias pinned: pin.visible
3131
3232 readonly property int effectiveHeight: Math.cos(angle * Math.PI / 180) * itemHeight
3333 readonly property real foldedHeight: Math.cos(maxAngle * Math.PI / 180) * itemHeight
@@ -71,14 +71,31 @@ Item {
7171 height: root.itemHeight - units.gu(1)
7272 }
7373
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+
7487 UbuntuShape {
88+ id: countEmblem
7589 objectName: "countEmblem"
90+
91+ readonly property real pinMargin: pin.visible ? pin.width + units.gu(1) - anchors.leftMargin : 0
92+
7693 anchors {
7794 right: parent.right
7895 top: parent.top
7996 margins: units.dp(3)
8097 }
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)))
8299 height: units.gu(2)
83100 color: UbuntuColors.orange
84101 visible: root.countVisible
@@ -92,7 +109,7 @@ Item {
92109 // FIXME: verticalCenter seems to be off wee bit and QML doesn't have a centerLine
93110 // property for Text: https://bugreports.qt-project.org/browse/QTBUG-40479
94111 anchors.verticalCenterOffset: -units.dp(.5)
95− width: root.itemWidth - units.gu(1)
112+ width: root.itemWidth - units.gu(1) - countEmblem.pinMargin
96113 horizontalAlignment: Text.AlignHCenter
97114 elide: Text.ElideRight
98115 color: "white"
@@ -149,24 +166,6 @@ Item {
149166 }
150167 }
151168
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−
170169 ShaderEffect {
171170 id: transformEffect
172171 anchors.centerIn: parent
@@ -176,7 +175,6 @@ Item {
176175 property real itemOpacity: root.itemOpacity
177176 property real brightness: Math.max(-1, root.brightness)
178177 property real angle: root.angle
179− property bool clipCorner: root.clipCorner
180178 rotation: root.inverted ? 180 : 0
181179
182180 property variant source: ShaderEffectSource {
@@ -185,11 +183,6 @@ Item {
185183 hideSource: true
186184 }
187185
188− property var mask: ShaderEffectSource {
189− sourceItem: clipper
190− hideSource: true
191− }
192−
193186 transform: [
194187 // Rotating 3 times at top/bottom because that increases the perspective.
195188 // This is a hack, but as QML does not support real 3D coordinates
@@ -224,19 +217,13 @@ Item {
224217 fragmentShader: "
225218 varying highp vec2 qt_TexCoord0;
226219 uniform sampler2D source;
227− uniform sampler2D mask;
228220 uniform lowp float brightness;
229221 uniform lowp float itemOpacity;
230− uniform bool clipCorner;
231222 void main(void)
232223 {
233224 highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
234− highp vec4 maskColor = texture2D(mask, qt_TexCoord0);
235225 sourceColor.rgb = mix(sourceColor.rgb, vec3(step(0.0, brightness)), abs(brightness));
236226 sourceColor *= itemOpacity;
237− if (clipCorner) {
238− sourceColor *= maskColor.a;
239− }
240227 gl_FragColor = sourceColor;
241228 }"
242229 }
qml/Launcher/LauncherPanel.qml+5−14
@@ -65,15 +65,6 @@ Rectangle {
6565 }
6666 }
6767
68− ThinDivider {
69− anchors {
70− left: parent.left
71− right: parent.right
72− margins: -mainColumn.anchors.leftMargin
73− }
74− rotation: root.rotation
75− }
76−
7768 Item {
7869 anchors.left: parent.left
7970 anchors.right: parent.right
@@ -158,7 +149,7 @@ Rectangle {
158149 count: model.count
159150 countVisible: model.countVisible
160151 progress: model.progress
161− clipCorner: model.pinned
152+ pinned: model.pinned
162153 itemFocused: model.focused
163154 inverted: root.inverted
164155 z: -Math.abs(offset)
@@ -469,10 +460,10 @@ Rectangle {
469460 width: itemWidth
470461 rotation: root.rotation
471462 itemOpacity: 0.9
472− clipCorner: dndArea.draggedIndex > -1 &&
473− LauncherModel.get(dndArea.draggedIndex).pinned &&
474− !dndArea.preDragging &&
475− !dndArea.dragging
463+ pinned: dndArea.draggedIndex > -1 &&
464+ LauncherModel.get(dndArea.draggedIndex).pinned &&
465+ !dndArea.preDragging &&
466+ !dndArea.dragging
476467
477468 function flatten() {
478469 fakeDragItemAnimation.start();
qml/Panel/Indicators/MenuItemFactory.qml+18−3
@@ -283,12 +283,17 @@ Item {
283283 objectName: "checkableMenu"
284284 property QtObject menuData: null
285285 property int menuIndex: -1
286+ property bool serverChecked: menuData && menuData.isToggled || false
286287
287288 text: menuData && menuData.label || ""
288289 enabled: menuData && menuData.sensitive || false
289− checked: menuData && menuData.isToggled || false
290+ checked: serverChecked
290291 highlightWhenPressed: false
291292
293+ onServerCheckedChanged: {
294+ // value can be changed by menu, so a binding won't work.
295+ checked = serverChecked;
296+ }
292297 onTriggered: {
293298 menuModel.activate(menuIndex);
294299 }
@@ -302,13 +307,18 @@ Item {
302307 objectName: "switchMenu"
303308 property QtObject menuData: null
304309 property int menuIndex: -1
310+ property bool serverChecked: menuData && menuData.isToggled || false
305311
306312 text: menuData && menuData.label || ""
307313 iconSource: menuData && menuData.icon || ""
308314 enabled: menuData && menuData.sensitive || false
309− checked: menuData && menuData.isToggled || false
315+ checked: serverChecked
310316 highlightWhenPressed: false
311317
318+ onServerCheckedChanged: {
319+ // value can be changed by menu, so a binding won't work.
320+ checked = serverChecked;
321+ }
312322 onTriggered: {
313323 menuModel.activate(menuIndex);
314324 }
@@ -431,6 +441,7 @@ Item {
431441 property var menuModel: menuFactory.menuModel
432442 property int menuIndex: -1
433443 property var extendedData: menuData && menuData.ext || undefined
444+ property bool serverChecked: menuData && menuData.isToggled || false
434445
435446 property var strengthAction: UnityMenuAction {
436447 model: menuModel
@@ -440,12 +451,16 @@ Item {
440451
441452 text: menuData && menuData.label || ""
442453 enabled: menuData && menuData.sensitive || false
443− active: menuData && menuData.isToggled || false
454+ active: serverChecked
444455 secure: getExtendedProperty(extendedData, "xCanonicalWifiApIsSecure", false)
445456 adHoc: getExtendedProperty(extendedData, "xCanonicalWifiApIsAdhoc", false)
446457 signalStrength: strengthAction.valid ? strengthAction.state : 0
447458 highlightWhenPressed: false
448459
460+ onServerCheckedChanged: {
461+ // value can be changed by menu, so a binding won't work.
462+ active = serverChecked;
463+ }
449464 onMenuModelChanged: {
450465 loadAttributes();
451466 }
qml/Shell.qml+74−40
@@ -87,9 +87,9 @@ Item {
8787 }
8888 }
8989
90− function setFakeActiveForApp(app) {
90+ function setLockedApp(app) {
9191 if (shell.locked) {
92− greeter.fakeActiveForApp = app
92+ greeter.lockedApp = app
9393 lockscreen.hide()
9494 }
9595 }
@@ -169,31 +169,37 @@ Item {
169169 Connections {
170170 target: ApplicationManager
171171 onFocusRequested: {
172− if (appId === "dialer-app") {
173− // Always let the dialer-app through. Either user asked
174− // for an emergency call or accepted an incoming call.
175− setFakeActiveForApp(appId)
176− } else if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== appId) {
177− lockscreen.show();
172+ if (greeter.narrowMode) {
173+ if (appId === "dialer-app") {
174+ // Always let the dialer-app through. Either user asked
175+ // for an emergency call or accepted an incoming call.
176+ setLockedApp(appId)
177+ } else if (greeter.hasLockedApp && greeter.lockedApp !== appId) {
178+ greeter.startUnlock()
179+ }
180+ greeter.hide();
181+ } else {
182+ if (LightDM.Greeter.active) {
183+ greeter.startUnlock()
184+ }
178185 }
179− greeter.hide();
180186 }
181187
182188 onFocusedApplicationIdChanged: {
183− if (greeter.fakeActiveForApp !== "" && greeter.fakeActiveForApp !== ApplicationManager.focusedApplicationId) {
184− lockscreen.show();
189+ if (greeter.hasLockedApp && greeter.lockedApp !== ApplicationManager.focusedApplicationId) {
190+ greeter.startUnlock()
185191 }
186192 panel.indicators.hide();
187193 }
188194
189195 onApplicationAdded: {
190196 if (greeter.shown && appId != "unity8-dash") {
191− greeter.hide();
197+ greeter.startUnlock()
192198 }
193− if (appId === "dialer-app") {
199+ if (greeter.narrowMode && appId === "dialer-app") {
194200 // Always let the dialer-app through. Either user asked
195201 // for an emergency call or accepted an incoming call.
196− setFakeActiveForApp(appId)
202+ setLockedApp(appId)
197203 }
198204 launcher.hide();
199205 }
@@ -201,9 +207,19 @@ Item {
201207
202208 Loader {
203209 id: applicationsDisplayLoader
210+ objectName: "applicationsDisplayLoader"
204211 anchors.fill: parent
205212
206− source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
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
222+ source: tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
207223
208224 Binding {
209225 target: applicationsDisplayLoader.item
@@ -229,7 +245,7 @@ Item {
229245 Binding {
230246 target: applicationsDisplayLoader.item
231247 property: "spreadEnabled"
232− value: edgeDemo.stagesEnabled && greeter.fakeActiveForApp === "" // to support emergency dialer hack
248+ value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
233249 }
234250 Binding {
235251 target: applicationsDisplayLoader.item
@@ -304,14 +320,14 @@ Item {
304320 // and wider screens are tablets which don't. When we do allow this
305321 // on devices with a side stage and a SIM, work should be done to
306322 // 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.
308324 showEmergencyCallButton: !shell.sideStageEnabled
309325
310326 onEntered: LightDM.Greeter.respond(passphrase);
311327 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
313329
314− onShownChanged: if (shown) greeter.fakeActiveForApp = ""
330+ onShownChanged: if (shown) greeter.lockedApp = ""
315331
316332 Timer {
317333 id: forcedDelayTimer
@@ -367,11 +383,13 @@ Item {
367383 if (!LightDM.Greeter.active) {
368384 return; // could happen if hideGreeter() comes in before we prompt
369385 }
370− if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
371− lockscreen.hide()
372− } else {
373− lockscreen.reset();
374− lockscreen.show();
386+ if (greeter.narrowMode) {
387+ if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
388+ lockscreen.hide()
389+ } else {
390+ lockscreen.reset();
391+ lockscreen.show();
392+ }
375393 }
376394 }
377395
@@ -422,7 +440,7 @@ Item {
422440 Binding {
423441 target: LightDM.Greeter
424442 property: "active"
425− value: greeter.shown || lockscreen.shown || greeter.fakeActiveForApp != ""
443+ value: greeter.shown || lockscreen.shown || greeter.hasLockedApp
426444 }
427445
428446 Rectangle {
@@ -434,7 +452,8 @@ Item {
434452 Item {
435453 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
436454 id: greeterWrapper
437− x: launcher.progress
455+ objectName: "greeterWrapper"
456+ x: greeter.narrowMode ? launcher.progress : 0
438457 y: panel.panelHeight
439458 width: parent.width
440459 height: parent.height - panel.panelHeight
@@ -447,7 +466,7 @@ Item {
447466 property bool fullyShown: showProgress === 1.0
448467 onFullyShownChanged: {
449468 // 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) {
451470 lockscreen.reset();
452471 lockscreen.show();
453472 }
@@ -456,7 +475,7 @@ Item {
456475 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
457476 onShowProgressChanged: {
458477 if (showProgress === 0) {
459− if (LightDM.Greeter.authenticated) {
478+ if (LightDM.Greeter.promptless && LightDM.Greeter.authenticated) {
460479 greeter.login()
461480 } else if (greeter.narrowMode) {
462481 lockscreen.clear(false) // to reset focus if necessary
@@ -470,13 +489,16 @@ Item {
470489
471490 signal sessionStarted() // helpful for tests
472491
473− property string fakeActiveForApp: ""
492+ property string lockedApp: ""
493+ property bool hasLockedApp: lockedApp !== ""
474494
475495 available: true
476496 hides: [launcher, panel.indicators]
477497 shown: true
478498 loadContent: required || lockscreen.required // keeps content in memory for quick show()
479499
500+ locked: shell.locked
501+
480502 defaultBackground: shell.background
481503
482504 width: parent.width
@@ -484,6 +506,18 @@ Item {
484506
485507 dragHandleWidth: shell.edgeSize
486508
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+
487521 function login() {
488522 enabled = false;
489523 if (LightDM.Greeter.startSessionSync()) {
@@ -504,8 +538,10 @@ Item {
504538
505539 if (greeter.narrowMode) {
506540 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
541+ } else {
542+ reset()
507543 }
508− greeter.fakeActiveForApp = "";
544+ greeter.lockedApp = "";
509545 greeter.forceActiveFocus();
510546 }
511547 }
@@ -550,10 +586,7 @@ Item {
550586 }
551587
552588 if (LightDM.Greeter.active) {
553− if (!LightDM.Greeter.authenticated) {
554− lockscreen.show()
555− }
556− greeter.hide()
589+ greeter.startUnlock()
557590 }
558591
559592 var animate = !LightDM.Greeter.active && !stages.shown
@@ -562,7 +595,8 @@ Item {
562595 }
563596
564597 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)) {
566600 return
567601 }
568602
@@ -589,7 +623,7 @@ Item {
589623 anchors.fill: parent //because this draws indicator menus
590624 indicators {
591625 hides: [launcher]
592− available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && greeter.fakeActiveForApp === ""
626+ available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
593627 contentEnabled: edgeDemo.panelContentEnabled
594628 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
595629 panelHeight: units.gu(3)
@@ -600,7 +634,7 @@ Item {
600634 ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
601635
602636 fullscreenMode: (topmostApplicationIsFullscreen && !LightDM.Greeter.active && launcher.progress == 0)
603− || greeter.fakeActiveForApp !== ""
637+ || greeter.hasLockedApp
604638 }
605639
606640 Launcher {
@@ -613,7 +647,7 @@ Item {
613647 anchors.bottom: parent.bottom
614648 width: parent.width
615649 dragAreaWidth: shell.edgeSize
616− available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && greeter.fakeActiveForApp === ""
650+ available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
617651
618652 onShowDashHome: showHome()
619653 onDash: showDash()
@@ -623,8 +657,8 @@ Item {
623657 }
624658 }
625659 onLauncherApplicationSelected: {
626− if (greeter.fakeActiveForApp !== "") {
627− lockscreen.show()
660+ if (greeter.hasLockedApp) {
661+ greeter.startUnlock()
628662 }
629663 if (!edgeDemo.running)
630664 shell.activateApplication(appId)
qml/Stages/PhoneStage.qml+8
@@ -85,6 +85,14 @@ Rectangle {
8585 if (spreadView.closingIndex == -1) {
8686 spreadView.phase = 0;
8787 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);
8896 }
8997 }
9098 }
qml/Stages/TabletStage.qml+7
@@ -139,9 +139,16 @@ Rectangle {
139139 if (priv.sideStageAppId == appId) {
140140 priv.sideStageAppId = "";
141141 }
142+
142143 if (ApplicationManager.count == 0) {
143144 spreadView.phase = 0;
144145 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);
145152 }
146153 }
147154 }
run_on_device.sh+4−5
@@ -130,11 +130,10 @@ if [ -z "${PASSWORD}" ]; then
130130 usage
131131fi
132132
133−PASSFILE=$( adb shell "FILE=\`mktemp\`
134− echo \"#!/bin/sh\" > \$FILE
135− echo \"echo \\\"${PASSWORD}\\\"\" >> \$FILE
136− chmod +x \$FILE
137− echo \$FILE" | tr -d '\r' )
133+PASSFILE=$(adb shell 'mktemp' | tr -d '\r')
134+adb shell "echo '#!/bin/sh' > ${PASSFILE}"
135+adb shell "echo \"echo \\\"${PASSWORD}\\\"\" >> ${PASSFILE}"
136+adb shell "chmod +x ${PASSFILE}"
138137
139138setup_adb_forwarding
140139
tests/autopilot/unity8/indicators/tests/test_indicators.py+2−2
@@ -72,11 +72,11 @@ class IndicatorPageTitleMatchesWidgetTestCase(IndicatorTestCase):
7272 ('Bluetooth', dict(indicator_name='indicator-bluetooth',
7373 title='Bluetooth')),
7474 ('Datetime', dict(indicator_name='indicator-datetime',
75− title='Upcoming events')),
75+ title='Time & Date')),
7676 ('Location', dict(indicator_name='indicator-location',
7777 title='Location')),
7878 ('Messaging', dict(indicator_name='indicator-messages',
79− title='Notification center')),
79+ title='Notifications')),
8080 ('Network', dict(indicator_name='indicator-network',
8181 title='Network')),
8282 ('Power', dict(indicator_name='indicator-power',
tests/mocks/Unity/Launcher/MockLauncherModel.cpp+1
@@ -54,6 +54,7 @@ MockLauncherModel::MockLauncherModel(QObject* parent): LauncherModelInterface(pa
5454 item = new MockLauncherItem("ubuntu-weather-app", "/usr/share/applications/ubuntu-weather-app.desktop", "Weather", "weather", this);
5555 item->setCount(1234567890);
5656 item->setCountVisible(true);
57+ item->setPinned(true);
5758 m_list.append(item);
5859 item = new MockLauncherItem("notes-app", "/usr/share/applications/notes-app.desktop", "Notepad", "notepad", this);
5960 item->setProgress(50);
tests/mocks/libusermetrics/UserMetrics.cpp+2−2
@@ -238,7 +238,7 @@ void UserMetricsPrivate::setUsername(const QString &username)
238238 m_username = username;
239239
240240 m_dataIndex = m_fakeData.constFind(m_username);
241− if (m_dataIndex == m_fakeData.end())
241+ if (m_dataIndex == m_fakeData.constEnd())
242242 {
243243 m_dataIndex = m_fakeData.constFind("");
244244 }
@@ -383,7 +383,7 @@ void UserMetricsPrivate::finishSetFakeData()
383383void UserMetricsPrivate::nextFakeData()
384384{
385385 ++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)
387387 {
388388 m_dataIndex = m_fakeData.constFind(m_username);
389389 }
tests/qmltests/CMakeLists.txt+1
@@ -21,6 +21,7 @@ set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
2121
2222add_qml_test(. Shell ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
2323add_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")
2425add_qml_test(Components Background)
2526add_qml_test(Components Carousel)
2627add_qml_test(Components DraggingArea)
tests/qmltests/Greeter/tst_MultiGreeter.qml+2−2
@@ -29,6 +29,7 @@ Item {
2929 Greeter {
3030 id: greeter
3131 anchors.fill: parent
32+ locked: !LightDM.Greeter.authenticated
3233 }
3334
3435 Component {
@@ -115,8 +116,7 @@ Item {
115116 var waitForSignal = data.uid != 0 && userList.currentIndex != data.uid
116117 select_index(data.uid)
117118 tryCompare(userList, "currentIndex", data.uid)
118− tryCompare(greeter, "locked", data.tag !== "no-password" &&
119− data.tag !== "auth-error")
119+ tryCompare(greeter, "locked", data.tag !== "no-password")
120120 if (waitForSignal) {
121121 selectionSpy.wait()
122122 tryCompare(selectionSpy, "count", 1)
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+24
@@ -37,6 +37,11 @@ Item {
3737 property int modelIndex: 0
3838 property var data
3939
40+ anchors {
41+ left: parent.left
42+ right: parent.right
43+ }
44+
4045 onLoaded: {
4146 if (item.hasOwnProperty("menuData")) {
4247 item.menuData = data;
@@ -734,5 +739,24 @@ Item {
734739 compare(loader.item.canGoPrevious, false, "CanGoPrevious should be false");
735740 compare(loader.item.enabled, data.enabled, "Enabled does not match data");
736741 }
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+ }
737761 }
738762}
tests/qmltests/Stages/tst_PhoneStage.qml+17
@@ -322,5 +322,22 @@ Item {
322322 }
323323 phoneStage.orientation = Qt.PortraitOrientation;
324324 }
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+ }
325342 }
326343}
tests/qmltests/tst_Shell.qml+1
@@ -484,6 +484,7 @@ Row {
484484 tryCompare(greeter, "showProgress", 0)
485485 waitForRendering(greeter);
486486 LightDM.Greeter.showGreeter()
487+ waitForRendering(greeter)
487488 tryCompare(greeter, "showProgress", 1)
488489 LightDM.Greeter.hideGreeter()
489490 tryCompare(greeter, "showProgress", 0)
tests/qmltests/tst_ShellWithPin.qml+106−25
@@ -27,10 +27,9 @@ import Powerd 0.1
2727
2828import "../../qml"
2929
30−Item {
30+Row {
3131 id: root
32− width: shell.width + units.gu(20)
33− height: shell.height
32+ spacing: 0
3433
3534 QtObject {
3635 id: applicationArguments
@@ -48,21 +47,55 @@ Item {
4847 }
4948 }
5049
51− Shell {
52− id: shell
53− maxFailedLogins: maxRetriesTextField.text
54− }
55− Column {
56− anchors { top: parent.top; right: parent.right; bottom: parent.bottom; margins:units.gu(1) }
57− width: units.gu(18)
50+ Loader {
51+ id: shellLoader
52+
53+ width: units.gu(40)
54+ height: units.gu(71)
5855
59− Label {
60− text: "Max retries:"
61− color: "black"
56+ property bool itemDestroyed: false
57+ sourceComponent: Component {
58+ Shell {
59+ Component.onDestruction: {
60+ shellLoader.itemDestroyed = true
61+ }
62+ maxFailedLogins: maxRetriesTextField.text
63+ }
6264 }
63− TextField {
64− id: maxRetriesTextField
65− text: "-1"
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+ }
95+ TextField {
96+ id: maxRetriesTextField
97+ text: "-1"
98+ }
6699 }
67100 }
68101
@@ -81,12 +114,10 @@ Item {
81114 name: "ShellWithPin"
82115 when: windowShown
83116
84− function initTestCase() {
85−
86− sessionSpy.target = findChild(shell, "greeter")
87− }
117+ property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
88118
89119 function init() {
120+ sessionSpy.target = findChild(shell, "greeter")
90121 swipeAwayGreeter()
91122 shell.failedLoginsDelayAttempts = -1
92123 maxRetriesTextField.text = "-1"
@@ -95,12 +126,28 @@ Item {
95126 }
96127
97128 function cleanup() {
98− LightDM.Greeter.showGreeter()
99− var greeter = findChild(shell, "greeter")
100− tryCompare(greeter, "showProgress", 1)
129+ shellLoader.itemDestroyed = false
130+
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)
101142
102143 // kill all (fake) running apps
103144 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)
104151 }
105152
106153 function killApps() {
@@ -113,6 +160,7 @@ Item {
113160
114161 function swipeAwayGreeter() {
115162 var greeter = findChild(shell, "greeter");
163+ waitForRendering(greeter)
116164 tryCompare(greeter, "showProgress", 1);
117165
118166 var touchX = shell.width - (shell.edgeSize / 2);
@@ -168,7 +216,8 @@ Item {
168216
169217 mouseClick(emergencyButton, units.gu(1), units.gu(1))
170218
171− tryCompare(greeter, "fakeActiveForApp", "dialer-app")
219+ tryCompare(greeter, "lockedApp", "dialer-app")
220+ tryCompare(greeter, "hasLockedApp", true)
172221 tryCompare(lockscreen, "shown", false)
173222 tryCompare(panel, "fullscreenMode", true)
174223 tryCompare(indicators, "available", false)
@@ -180,7 +229,8 @@ Item {
180229 LightDM.Greeter.showGreeter()
181230
182231 tryCompare(greeter, "shown", true)
183− tryCompare(greeter, "fakeActiveForApp", "")
232+ tryCompare(greeter, "lockedApp", "")
233+ tryCompare(greeter, "hasLockedApp", false)
184234 tryCompare(lockscreen, "shown", true)
185235 tryCompare(panel, "fullscreenMode", false)
186236 tryCompare(indicators, "available", true)
@@ -253,5 +303,36 @@ Item {
253303 enterPin("1111")
254304 tryCompare(resetSpy, "count", 1)
255305 }
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+ }
256337 }
257338}
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
2020import Unity.Test 0.1 as UT
2121
2222TestCase {
23+ id: testCase
2324 TestUtil {id:util}
2425
26+ ActivityIndicator {
27+ visible: testCase.running
28+ anchors.centerIn: parent
29+ Component.onCompleted: parent = testCase.parent
30+ z: 100
31+ running: visible
32+ }
33+
2534 // Fake implementation to be provided to items under test
2635 property var fakeDateTime: new function() {
2736 this.currentTimeMs = 0