on xenial help sign in
enhance lockscreen
ae0e951Michael Zanetti
qml/Components/PinPadButton.qml+2−1
@@ -19,6 +19,7 @@ import Ubuntu.Components 0.1
19
19
20Item {
20Item {
21 id: root
21 id: root
22+ opacity: enabled ? 1 : 0.6
22
23
23 property alias text: label.text
24 property alias text: label.text
24 property alias subText: subTextLabel.text
25 property alias subText: subTextLabel.text
@@ -62,7 +63,7 @@ Item {
62 Label {
63 Label {
63 id: subTextLabel
64 id: subTextLabel
64 fontSize: "small"
65 fontSize: "small"
65− color: "grey"
66+ color: "#f3f3e7"
66 anchors.horizontalCenter: parent.horizontalCenter
67 anchors.horizontalCenter: parent.horizontalCenter
67 visible: text.length > 0
68 visible: text.length > 0
68 }
69 }
qml/Greeter/Lockscreen.qml+91−7
@@ -15,7 +15,8 @@
15 */
15 */
16
16
17import QtQuick 2.0
17import QtQuick 2.0
18−import Ubuntu.Components 0.1
18+import Ubuntu.Components 1.0
19+import Ubuntu.Components.Popups 1.0
19import "../Components"
20import "../Components"
20
21
21Showable {
22Showable {
@@ -26,19 +27,34 @@ Showable {
26
27
27 // Placeholder text
28 // Placeholder text
28 property string placeholderText: ""
29 property string placeholderText: ""
30+ // Placeholder text while shaking (e.g. Incorrect passprase)
31+ property string wrongPlaceholderText: ""
32+ // Retries text (e.g. 3 retries left)
33+ property string retryText: ""
34+
35+ // Informational text. (e.g. some text to tell which domain this is pin is entered for.
36+ property string infoText: ""
29
37
30 // In case the Lockscreen can show a greeter message, this is the username
38 // In case the Lockscreen can show a greeter message, this is the username
31 property string username: ""
39 property string username: ""
32
40
33− // Set this to a value greater 0 to enable auto-confirm behavior for the lockscreen.
41+ // Set those to a value greater 0 to restrict the pin length.
42+ // If both are unset, the Lockscreen will show a confirm button and allow typing any length of pin before
43+ // confirming. If minPinLength is set to a value > 0, the confirm button will only become active when the
44+ // entered pin is at least that long. If maxPinLength is set, the lockscreen won't allow entering any
45+ // more numbers than that. If both are set to the same value, the lockscreen will enter auto confirming
46+ // behavior, hiding the confirmation button and triggering that automatically when the entered pin reached
34− // This is ignored by the alphaNumeric lockscreen as that one is confirmed with pressing enter on the OSK.
47+ // that length. This is ignored by the alphaNumeric lockscreen as that one is always confirmed by pressing
48+ // enter on the OSK.
35− property int pinLength: -1
49+ property int minPinLength: -1
50+ property int maxPinLength: -1
36
51
37 property url background: ""
52 property url background: ""
38
53
39 signal entered(string passphrase)
54 signal entered(string passphrase)
40 signal cancel()
55 signal cancel()
41 signal emergencyCall()
56 signal emergencyCall()
57+ signal infoPopupConfirmed()
42
58
43 onRequiredChanged: {
59 onRequiredChanged: {
44 if (required && pinPadLoader.item) {
60 if (required && pinPadLoader.item) {
@@ -56,6 +72,10 @@ Showable {
56 pinPadLoader.item.clear(showAnimation);
72 pinPadLoader.item.clear(showAnimation);
57 }
73 }
58
74
75+ function showInfoPopup(title, text) {
76+ PopupUtils.open(infoPopupComponent, root, {title: title, text: text})
77+ }
78+
59 Rectangle {
79 Rectangle {
60 // In case background fails to load or is undefined
80 // In case background fails to load or is undefined
61 id: backgroundBackup
81 id: backgroundBackup
@@ -68,7 +88,6 @@ Showable {
68 objectName: "lockscreenBackground"
88 objectName: "lockscreenBackground"
69 anchors {
89 anchors {
70 fill: parent
90 fill: parent
71− topMargin: backgroundTopMargin
72 }
91 }
73 source: root.required ? root.background : ""
92 source: root.required ? root.background : ""
74 fillMode: Image.PreserveAspectCrop
93 fillMode: Image.PreserveAspectCrop
@@ -78,6 +97,43 @@ Showable {
78 anchors.fill: root
97 anchors.fill: root
79 }
98 }
80
99
100+ Column {
101+ spacing: units.gu(2)
102+ anchors {
103+ left: parent.left
104+ right: parent.right
105+ bottom: pinPadLoader.top
106+ bottomMargin: units.gu(2)
107+ }
108+ Label {
109+ objectName: "retryCountLabel"
110+ anchors {
111+ left: parent.left
112+ right: parent.right
113+ }
114+ text: root.retryText
115+ horizontalAlignment: Text.AlignHCenter
116+ color: "#f3f3e7"
117+ opacity: 0.6
118+ visible: root.retryText.length > 0
119+ }
120+ Label {
121+ id: infoTextLabel
122+ objectName: "infoTextLabel"
123+ anchors {
124+ left: parent.left
125+ right: parent.right
126+ }
127+ text: root.infoText
128+ horizontalAlignment: Text.AlignHCenter
129+ color: "#f3f3e7"
130+ opacity: 0.6
131+ visible: root.infoText.length > 0
132+ }
133+ }
134+
135+
136+
81 Loader {
137 Loader {
82 id: pinPadLoader
138 id: pinPadLoader
83 objectName: "pinPadLoader"
139 objectName: "pinPadLoader"
@@ -105,14 +161,24 @@ Showable {
105
161
106 Binding {
162 Binding {
107 target: pinPadLoader.item
163 target: pinPadLoader.item
108− property: "pinLength"
164+ property: "minPinLength"
165+ value: root.minPinLength
166+ }
167+ Binding {
168+ target: pinPadLoader.item
169+ property: "maxPinLength"
109− value: root.pinLength
170+ value: root.maxPinLength
110 }
171 }
111 Binding {
172 Binding {
112 target: pinPadLoader.item
173 target: pinPadLoader.item
113 property: "placeholderText"
174 property: "placeholderText"
114 value: root.placeholderText
175 value: root.placeholderText
115 }
176 }
177+ Binding {
178+ target: pinPadLoader.item
179+ property: "wrongPlaceholderText"
180+ value: root.wrongPlaceholderText
181+ }
116 Binding {
182 Binding {
117 target: pinPadLoader.item
183 target: pinPadLoader.item
118 property: "username"
184 property: "username"
@@ -153,4 +219,22 @@ Showable {
153 anchors.horizontalCenter: parent.horizontalCenter
219 anchors.horizontalCenter: parent.horizontalCenter
154 }
220 }
155 }
221 }
222+
223+ Component {
224+ id: infoPopupComponent
225+ Dialog {
226+ id: dialog
227+ objectName: "infoPopup"
228+ modal: true
229+
230+ Button {
231+ objectName: "infoPopupOkButton"
232+ text: i18n.tr("OK")
233+ onClicked: {
234+ PopupUtils.close(dialog)
235+ root.infoPopupConfirmed();
236+ }
237+ }
238+ }
239+ }
156}
240}
qml/Greeter/PassphraseLockscreen.qml+6−2
@@ -22,7 +22,8 @@ Item {
22 id: root
22 id: root
23 height: highlightItem.height
23 height: highlightItem.height
24
24
25− property alias placeholderText: pinentryField.placeholderText
25+ property string placeholderText
26+ property string wrongPlaceholderText
26 property string username: ""
27 property string username: ""
27
28
28 signal entered(string passphrase)
29 signal entered(string passphrase)
@@ -73,9 +74,12 @@ Item {
73 echoMode: TextInput.Password
74 echoMode: TextInput.Password
74 opacity: 0.9
75 opacity: 0.9
75 hasClearButton: false
76 hasClearButton: false
77+ placeholderText: wrongPasswordAnimation.running ? root.wrongPlaceholderText : root.placeholderText
76
78
77 onAccepted: {
79 onAccepted: {
80+ if (pinentryField.text) {
78− root.entered(pinentryField.text);
81+ root.entered(pinentryField.text);
82+ }
79 }
83 }
80 }
84 }
81 }
85 }
qml/Greeter/PinLockscreen.qml+29−11
@@ -25,9 +25,11 @@ Column {
25 spacing: units.gu(3.5)
25 spacing: units.gu(3.5)
26
26
27 property alias placeholderText: pinentryField.placeholderText
27 property alias placeholderText: pinentryField.placeholderText
28+ property alias wrongPlaceholderText: pinentryField.wrongPlaceholderText
28 property int padWidth: units.gu(34)
29 property int padWidth: units.gu(34)
29 property int padHeight: units.gu(28)
30 property int padHeight: units.gu(28)
30− property int pinLength: -1
31+ property int minPinLength: -1
32+ property int maxPinLength: -1
31
33
32 signal entered(string passphrase)
34 signal entered(string passphrase)
33 signal cancel()
35 signal cancel()
@@ -41,6 +43,10 @@ Column {
41 }
43 }
42 }
44 }
43
45
46+ QtObject {
47+ id: priv
48+ property bool autoConfirm: root.minPinLength == root.maxPinLength && root.minPinLength != -1
49+ }
44
50
45 UbuntuShape {
51 UbuntuShape {
46 id: pinentryField
52 id: pinentryField
@@ -52,12 +58,20 @@ Column {
52 radius: "medium"
58 radius: "medium"
53 property string text: ""
59 property string text: ""
54 property string placeholderText: ""
60 property string placeholderText: ""
61+ property string wrongPlaceholderText: ""
62+
63+ function appendChar(character) {
64+ if (root.maxPinLength == -1 || pinentryField.text.length < root.maxPinLength) {
65+ pinentryField.text = pinentryField.text + character;
66+ }
67+ }
68+
55 onTextChanged: {
69 onTextChanged: {
56 pinentryFieldLabel.text = "";
70 pinentryFieldLabel.text = "";
57 for (var i = 0; i < text.length; ++i) {
71 for (var i = 0; i < text.length; ++i) {
58 pinentryFieldLabel.text += "•";
72 pinentryFieldLabel.text += "•";
59 }
73 }
60− if (text.length === root.pinLength) {
74+ if (priv.autoConfirm && text.length === root.maxPinLength) {
61 root.entered(text);
75 root.entered(text);
62 }
76 }
63 }
77 }
@@ -74,9 +88,11 @@ Column {
74 }
88 }
75 Label {
89 Label {
76 id: pinentryFieldPlaceHolder
90 id: pinentryFieldPlaceHolder
91+ objectName: "pinentryFieldPlaceHolder"
77 anchors.centerIn: parent
92 anchors.centerIn: parent
78− color: "grey"
93+ color: "#f3f3e7"
94+ opacity: 0.6
79− text: parent.placeholderText
95+ text: wrongPasswordAnimation.running ? parent.wrongPlaceholderText : parent.placeholderText
80 visible: pinentryFieldLabel.text.length == 0
96 visible: pinentryFieldLabel.text.length == 0
81 }
97 }
82
98
@@ -91,9 +107,11 @@ Column {
91 bottom: parent.bottom
107 bottom: parent.bottom
92 bottomMargin: units.gu(1)
108 bottomMargin: units.gu(1)
93 }
109 }
94− visible: root.pinLength == -1
110+ visible: !priv.autoConfirm
95 width: height
111 width: height
96 name: "erase"
112 name: "erase"
113+ color: "#f3f3e7"
114+ opacity: 0.6
97 MouseArea {
115 MouseArea {
98 anchors.fill: parent
116 anchors.fill: parent
99 onClicked: pinentryField.text = pinentryField.text.substring(0, pinentryField.text.length-1);
117 onClicked: pinentryField.text = pinentryField.text.substring(0, pinentryField.text.length-1);
@@ -168,7 +186,7 @@ Column {
168 enabled: entryEnabled
186 enabled: entryEnabled
169
187
170 onClicked: {
188 onClicked: {
171− pinentryField.text = pinentryField.text + text;
189+ pinentryField.appendChar(text);
172 }
190 }
173 }
191 }
174 }
192 }
@@ -186,7 +204,7 @@ Column {
186 width: root.padWidth / 3
204 width: root.padWidth / 3
187 height: root.padHeight / 4
205 height: root.padHeight / 4
188 text: "0"
206 text: "0"
189− onClicked: pinentryField.text = pinentryField.text + text
207+ onClicked: pinentryField.appendChar(text);
190 enabled: entryEnabled
208 enabled: entryEnabled
191 }
209 }
192
210
@@ -194,16 +212,16 @@ Column {
194 objectName: "pinPadButtonErase"
212 objectName: "pinPadButtonErase"
195 width: root.padWidth / 3
213 width: root.padWidth / 3
196 height: root.padHeight / 4
214 height: root.padHeight / 4
197− iconName: root.pinLength == -1 ? "" : "erase"
215+ iconName: priv.autoConfirm ? "erase" : ""
198− subText: root.pinLength == -1 ? "DONE" : ""
216+ subText: priv.autoConfirm ? "" : "DONE"
199 onClicked: {
217 onClicked: {
200− if (root.pinLength !== -1) {
218+ if (priv.autoConfirm) {
201 pinentryField.text = pinentryField.text.substring(0, pinentryField.text.length-1);
219 pinentryField.text = pinentryField.text.substring(0, pinentryField.text.length-1);
202 } else {
220 } else {
203 root.entered(pinentryField.text);
221 root.entered(pinentryField.text);
204 }
222 }
205 }
223 }
206− enabled: entryEnabled
224+ enabled: priv.autoConfirm ? entryEnabled : pinentryField.text.length >= root.minPinLength
207 }
225 }
208 }
226 }
209 }
227 }
qml/Shell.qml+2−1
@@ -316,7 +316,8 @@ FocusScope {
316 width: parent.width
316 width: parent.width
317 height: parent.height - panel.panelHeight
317 height: parent.height - panel.panelHeight
318 background: shell.background
318 background: shell.background
319− pinLength: 4
319+ minPinLength: 4
320+ maxPinLength: 4
320
321
321 onEntered: LightDM.Greeter.respond(passphrase);
322 onEntered: LightDM.Greeter.respond(passphrase);
322 onCancel: greeter.show()
323 onCancel: greeter.show()
tests/qmltests/Greeter/tst_Lockscreen.qml+208−30
@@ -23,25 +23,34 @@ import LightDM 0.1 as LightDM
23import Unity.Test 0.1 as UT
23import Unity.Test 0.1 as UT
24
24
25Rectangle {
25Rectangle {
26+ id: root
26− width: units.gu(60)
27+ width: units.gu(80)
27 height: units.gu(80)
28 height: units.gu(80)
28 color: "orange"
29 color: "orange"
29
30
30 Lockscreen {
31 Lockscreen {
31 id: lockscreen
32 id: lockscreen
32 anchors.fill: parent
33 anchors.fill: parent
33− anchors.rightMargin: units.gu(18)
34+ anchors.rightMargin: units.gu(30)
34 placeholderText: "Please enter your PIN"
35 placeholderText: "Please enter your PIN"
36+ wrongPlaceholderText: "Incorrect PIN"
37+ retryText: retryCountTextField.text
35 alphaNumeric: pinPadCheckBox.checked
38 alphaNumeric: pinPadCheckBox.checked
36− pinLength: pinLengthTextField.text
39+ minPinLength: minPinLengthTextField.text
40+ maxPinLength: maxPinLengthTextField.text
37 username: "Lola"
41 username: "Lola"
42+ background: "../../../qml/graphics/phone_background.jpg"
43+ infoText: infoTextTextField.text
38 }
44 }
39
45
40 Connections {
46 Connections {
41 target: lockscreen
47 target: lockscreen
42
48
43 onEmergencyCall: emergencyCheckBox.checked = true
49 onEmergencyCall: emergencyCheckBox.checked = true
50+ onEntered: {
44− onEntered: enteredLabel.text = passphrase
51+ enteredLabel.text = passphrase
52+ lockscreen.clear(true)
53+ }
45 }
54 }
46
55
47 Connections {
56 Connections {
@@ -49,9 +58,9 @@ Rectangle {
49
58
50 onShowPrompt: {
59 onShowPrompt: {
51 if (text.indexOf("PIN") >= 0) {
60 if (text.indexOf("PIN") >= 0) {
52− lockscreen.alphaNumeric = false
61+ pinPadCheckBox.checked = false
53 } else {
62 } else {
54− lockscreen.alphaNumeric = true
63+ pinPadCheckBox.checked = true
55 }
64 }
56 lockscreen.placeholderText = text;
65 lockscreen.placeholderText = text;
57 }
66 }
@@ -65,6 +74,7 @@ Rectangle {
65 Column {
74 Column {
66 anchors.fill: parent
75 anchors.fill: parent
67 anchors.margins: units.gu(1)
76 anchors.margins: units.gu(1)
77+ spacing: units.gu(1)
68
78
69 Row {
79 Row {
70 CheckBox {
80 CheckBox {
@@ -86,12 +96,32 @@ Rectangle {
86 }
96 }
87 Row {
97 Row {
88 TextField {
98 TextField {
89− id: pinLengthTextField
99+ id: minPinLengthTextField
90 width: units.gu(7)
100 width: units.gu(7)
91 text: "4"
101 text: "4"
92 }
102 }
93 Label {
103 Label {
104+ text: "Min PIN length"
105+ }
106+ }
107+ Row {
108+ TextField {
109+ id: maxPinLengthTextField
110+ width: units.gu(7)
111+ text: "4"
112+ }
113+ Label {
94− text: "PIN length"
114+ text: "Max PIN length"
115+ }
116+ }
117+ Row {
118+ TextField {
119+ id: retryCountTextField
120+ width: units.gu(7)
121+ text: "3 retries left"
122+ }
123+ Label {
124+ text: "Retries left"
95 }
125 }
96 }
126 }
97 Label {
127 Label {
@@ -108,12 +138,40 @@ Rectangle {
108 }
138 }
109 Button {
139 Button {
110 text: "start auth (1234)"
140 text: "start auth (1234)"
141+ width: parent.width
111 onClicked: LightDM.Greeter.authenticate("has-pin")
142 onClicked: LightDM.Greeter.authenticate("has-pin")
112 }
143 }
113 Button {
144 Button {
114 text: "start auth (password)"
145 text: "start auth (password)"
146+ width: parent.width
115 onClicked: LightDM.Greeter.authenticate("has-password")
147 onClicked: LightDM.Greeter.authenticate("has-password")
116 }
148 }
149+
150+ TextField {
151+ id: infoTextTextField
152+ width: parent.width
153+ placeholderText: "Infotext"
154+ text: "+49 179 3253553"
155+ }
156+
157+ TextField {
158+ id: infoPopupTitleTextField
159+ width: parent.width
160+ placeholderText: "Popup title"
161+ text: "This will be the last attempt"
162+ }
163+
164+ TextArea {
165+ id: infoPopupTextArea
166+ width: parent.width
167+ text: "If the SIM PIN is entered incorrectly, your SIM will be blocked and would require the PUK code to unlock."
168+ }
169+
170+ Button {
171+ text: "open info popup"
172+ width: parent.width
173+ onClicked: lockscreen.showInfoPopup(infoPopupTitleTextField.text, infoPopupTextArea.text)
174+ }
117 }
175 }
118 }
176 }
119
177
@@ -121,6 +179,16 @@ Rectangle {
121 name: "Lockscreen"
179 name: "Lockscreen"
122 when: windowShown
180 when: windowShown
123
181
182+ function cleanup() {
183+ lockscreen.clear(false);
184+ }
185+
186+ function waitForLockscreenReady() {
187+ var pinPadLoader = findChild(lockscreen, "pinPadLoader");
188+ tryCompare(pinPadLoader, "status", Loader.Ready)
189+ waitForRendering(lockscreen)
190+ }
191+
124 function test_loading_data() {
192 function test_loading_data() {
125 return [
193 return [
126 {tag: "numeric", alphanumeric: false, pinPadAvailable: true },
194 {tag: "numeric", alphanumeric: false, pinPadAvailable: true },
@@ -129,8 +197,8 @@ Rectangle {
129 }
197 }
130
198
131 function test_loading(data) {
199 function test_loading(data) {
132− lockscreen.alphaNumeric = data.alphanumeric
200+ pinPadCheckBox.checked = data.alphanumeric
133− waitForRendering(lockscreen)
201+ waitForLockscreenReady();
134 if (data.pinPadAvailable) {
202 if (data.pinPadAvailable) {
135 compare(findChild(lockscreen, "pinPadButton8").text, "8", "Could not find number 8 on PinPad")
203 compare(findChild(lockscreen, "pinPadButton8").text, "8", "Could not find number 8 on PinPad")
136 } else {
204 } else {
@@ -147,8 +215,8 @@ Rectangle {
147
215
148 function test_emergency_call(data) {
216 function test_emergency_call(data) {
149 emergencyCheckBox.checked = false
217 emergencyCheckBox.checked = false
150− lockscreen.alphaNumeric = data.alphanumeric
218+ pinPadCheckBox.checked = data.alphanumeric
151− waitForRendering(lockscreen)
219+ waitForLockscreenReady();
152 var emergencyButton = findChild(lockscreen, "emergencyCallIcon")
220 var emergencyButton = findChild(lockscreen, "emergencyCallIcon")
153 mouseClick(emergencyButton, units.gu(1), units.gu(1))
221 mouseClick(emergencyButton, units.gu(1), units.gu(1))
154 tryCompare(emergencyCheckBox, "checked", true)
222 tryCompare(emergencyCheckBox, "checked", true)
@@ -163,9 +231,9 @@ Rectangle {
163 }
231 }
164
232
165 function test_labels(data) {
233 function test_labels(data) {
166− lockscreen.alphaNumeric = data.alphanumeric
234+ pinPadCheckBox.checked = data.alphanumeric
167 lockscreen.placeholderText = data.placeholderText
235 lockscreen.placeholderText = data.placeholderText
168− waitForRendering(lockscreen)
236+ waitForLockscreenReady();
169 compare(findChild(lockscreen, "pinentryField").placeholderText, data.placeholderText, "Placeholdertext is not what it should be")
237 compare(findChild(lockscreen, "pinentryField").placeholderText, data.placeholderText, "Placeholdertext is not what it should be")
170 if (data.alphanumeric) {
238 if (data.alphanumeric) {
171 compare(findChild(lockscreen, "greeterLabel").text, "Hello " + data.username, "Greeter is not set correctly")
239 compare(findChild(lockscreen, "greeterLabel").text, "Hello " + data.username, "Greeter is not set correctly")
@@ -175,23 +243,25 @@ Rectangle {
175
243
176 function test_unlock_data() {
244 function test_unlock_data() {
177 return [
245 return [
178− {tag: "numeric", alphanumeric: false, username: "has-pin", password: "1234", pinLength: 4},
246+ {tag: "numeric", alphanumeric: false, username: "has-pin", password: "1234", minPinLength: 4, maxPinLength: 4},
179− {tag: "alphanumeric", alphanumeric: true, username: "has-password", password: "password", pinLength: -1},
247+ {tag: "alphanumeric", alphanumeric: true, username: "has-password", password: "password", minPinLength: -1, maxPinLength: -1},
180− {tag: "numeric (wrong)", alphanumeric: false, username: "has-pin", password: "4321", pinLength: 4},
248+ {tag: "numeric (wrong)", alphanumeric: false, username: "has-pin", password: "4321", minPinLength: 4, maxPinLength: 4},
181− {tag: "alphanumeric (wrong)", alphanumeric: true, username: "has-password", password: "drowssap", pinLength: -1},
249+ {tag: "alphanumeric (wrong)", alphanumeric: true, username: "has-password", password: "drowssap", minPinLength: -1, maxPinLength: -1},
182− {tag: "flexible length", alphanumeric: false, username: "has-pin", password: "1234", pinLength: -1},
250+ {tag: "flexible length", alphanumeric: false, username: "has-pin", password: "1234", minPinLength: -1, maxPinLength: -1},
183 ]
251 ]
184 }
252 }
185
253
186 function test_unlock(data) {
254 function test_unlock(data) {
187 enteredLabel.text = ""
255 enteredLabel.text = ""
188− pinLengthTextField.text = data.pinLength
256+ minPinLengthTextField.text = data.minPinLength
257+ maxPinLengthTextField.text = data.maxPinLength
189 LightDM.Greeter.authenticate(data.username)
258 LightDM.Greeter.authenticate(data.username)
190− waitForRendering(lockscreen)
259+ waitForLockscreenReady();
191
260
192 var inputField = findChild(lockscreen, "pinentryField")
261 var inputField = findChild(lockscreen, "pinentryField")
193 if (data.alphanumeric) {
262 if (data.alphanumeric) {
194 mouseClick(inputField, units.gu(1), units.gu(1))
263 mouseClick(inputField, units.gu(1), units.gu(1))
264+ tryCompare(inputField, "focus", true);
195 typeString(data.password)
265 typeString(data.password)
196 keyClick(Qt.Key_Enter)
266 keyClick(Qt.Key_Enter)
197 } else {
267 } else {
@@ -200,7 +270,7 @@ Rectangle {
200 var button = findChild(lockscreen, "pinPadButton" + character)
270 var button = findChild(lockscreen, "pinPadButton" + character)
201 mouseClick(button, units.gu(1), units.gu(1))
271 mouseClick(button, units.gu(1), units.gu(1))
202 }
272 }
203− if (data.pinLength == -1) {
273+ if (data.minPinLength !== data.maxPinLength || data.minPinLength == -1) {
204 var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
274 var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
205 mouseClick(pinPadButtonErase, units.gu(1), units.gu(1));
275 mouseClick(pinPadButtonErase, units.gu(1), units.gu(1));
206 }
276 }
@@ -219,11 +289,12 @@ Rectangle {
219
289
220 function test_clear(data) {
290 function test_clear(data) {
221 pinPadCheckBox.checked = data.alphanumeric
291 pinPadCheckBox.checked = data.alphanumeric
222− waitForRendering(lockscreen)
292+ waitForLockscreenReady();
223
293
224 var inputField = findChild(lockscreen, "pinentryField")
294 var inputField = findChild(lockscreen, "pinentryField")
225 if (data.alphanumeric) {
295 if (data.alphanumeric) {
226 mouseClick(inputField, units.gu(1), units.gu(1))
296 mouseClick(inputField, units.gu(1), units.gu(1))
297+ tryCompare(inputField, "activeFocus", true);
227 typeString("1")
298 typeString("1")
228 } else {
299 } else {
229 var button = findChild(lockscreen, "pinPadButton1")
300 var button = findChild(lockscreen, "pinPadButton1")
@@ -239,6 +310,16 @@ Rectangle {
239
310
240 wait(0) // Trigger event loop to make sure the animation would start running
311 wait(0) // Trigger event loop to make sure the animation would start running
241 compare(animation.running, data.animation)
312 compare(animation.running, data.animation)
313+ if (data.animation) {
314+ if (data.alphanumeric) {
315+ tryCompare(inputField, "placeholderText", lockscreen.wrongPlaceholderText)
316+ tryCompare(inputField, "placeholderText", lockscreen.placeholderText)
317+ } else {
318+ var label = findChild(lockscreen, "pinentryFieldPlaceHolder");
319+ tryCompare(label, "text", lockscreen.wrongPlaceholderText)
320+ tryCompare(label, "text", lockscreen.placeholderText)
321+ }
322+ }
242
323
243 // wait for animation to finish to not disturb other tests
324 // wait for animation to finish to not disturb other tests
244 tryCompare(animation, "running", false)
325 tryCompare(animation, "running", false)
@@ -246,33 +327,130 @@ Rectangle {
246
327
247 function test_backspace_data() {
328 function test_backspace_data() {
248 return [
329 return [
249− {tag: "fixed length", pinLength: 4},
330+ {tag: "fixed length", minPinLength: 4, maxPinLength: 4},
250− {tag: "variable length", pinLength: -1}
331+ {tag: "variable undefined length", minPinLength: -1, maxPinLength: -1},
332+ {tag: "variable restricted length", minPinLength: 4, maxPinLength: 8}
251 ];
333 ];
252 }
334 }
253
335
254 function test_backspace(data) {
336 function test_backspace(data) {
255 pinPadCheckBox.checked = false
337 pinPadCheckBox.checked = false
256− pinLengthTextField.text = data.pinLength
338+ minPinLengthTextField.text = data.minPinLength
257− waitForRendering(lockscreen);
339+ maxPinLengthTextField.text = data.maxPinLength
340+ waitForLockscreenReady();
258
341
259 var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
342 var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
260 var backspaceIcon = findChild(lockscreen, "backspaceIcon");
343 var backspaceIcon = findChild(lockscreen, "backspaceIcon");
261 var pinEntryField = findChild(lockscreen, "pinentryField");
344 var pinEntryField = findChild(lockscreen, "pinentryField");
262
345
346+ var autoConfirmEnabled = data.minPinLength === data.maxPinLength && data.minPinLength !== -1;
263− compare(pinPadButtonErase.iconName, data.pinLength == -1 ? "" : "erase");
347+ compare(pinPadButtonErase.iconName, autoConfirmEnabled ? "erase" : "");
264− compare(backspaceIcon.visible, data.pinLength == -1);
348+ compare(backspaceIcon.visible, !autoConfirmEnabled);
265
349
266 var pinPadButton5 = findChild(lockscreen, "pinPadButton5");
350 var pinPadButton5 = findChild(lockscreen, "pinPadButton5");
267 mouseClick(pinPadButton5, units.gu(1), units.gu(1));
351 mouseClick(pinPadButton5, units.gu(1), units.gu(1));
268 compare(pinEntryField.text, "5");
352 compare(pinEntryField.text, "5");
269
353
270− if (data.pinLength == -1) {
354+ if (data.minPinLength !== data.maxPinLength) {
271 mouseClick(backspaceIcon, units.gu(1), units.gu(1));
355 mouseClick(backspaceIcon, units.gu(1), units.gu(1));
272 } else {
356 } else {
273 mouseClick(pinPadButtonErase, units.gu(1), units.gu(1));
357 mouseClick(pinPadButtonErase, units.gu(1), units.gu(1));
274 }
358 }
275 compare(pinEntryField.text, "");
359 compare(pinEntryField.text, "");
276 }
360 }
361+
362+ function test_minMaxLength_data() {
363+ return [
364+ {tag: "undefined", minPinLength: -1, maxPinLength: -1},
365+ {tag: "fixed", minPinLength: 4, maxPinLength: 4},
366+ {tag: "variable, limited", minPinLength: 4, maxPinLength: 8},
367+ ];
368+ }
369+
370+ function test_minMaxLength(data) {
371+ pinPadCheckBox.checked = false
372+ minPinLengthTextField.text = data.minPinLength
373+ maxPinLengthTextField.text = data.maxPinLength
374+ waitForLockscreenReady();
375+
376+ var pinPadButton5 = findChild(lockscreen, "pinPadButton5");
377+ var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
378+ var inputField = findChild(lockscreen, "pinentryField")
379+
380+ for (var i = 0; i < 10; i++) {
381+ mouseClick(pinPadButton5, units.gu(1), units.gu(1));
382+
383+ if (data.minPinLength == data.maxPinLength && data.minPinLength != -1) {
384+ // auto confirm mode...
385+ compare(inputField.text.length, (i+1) % data.minPinLength);
386+ } else {
387+ // manual confirm mode
388+ tryCompare(pinPadButtonErase, "enabled", (i+1) >= data.minPinLength)
389+ if (data.maxPinLength == -1) {
390+ compare(inputField.text.length, i+1);
391+ } else {
392+ compare(inputField.text.length, Math.min(data.maxPinLength, i+1));
393+ }
394+
395+ }
396+ }
397+ }
398+
399+ function test_retryDisplay_data() {
400+ return [
401+ {tag: "empty", retryText: "", shown: false},
402+ {tag: "3 retries left", retryText: "3 retries left", shown: true},
403+ ]
404+ }
405+
406+ function test_retryDisplay(data) {
407+ pinPadCheckBox.checked = false
408+ waitForLockscreenReady();
409+
410+ retryCountTextField.text = data.retryText;
411+ var label = findChild(lockscreen, "retryCountLabel")
412+ compare(label.visible, data.shown);
413+ }
414+
415+ function test_infoPopup() {
416+ verify(findChild(root, "infoPopup") === null);
417+ lockscreen.showInfoPopup("foo", "bar");
418+ tryCompareFunction(function() { return findChild(root, "infoPopup") !== null}, true);
419+
420+ var infoPopup = findChild(root, "infoPopup");
421+ compare(infoPopup.title, "foo");
422+ compare(infoPopup.text, "bar");
423+
424+ signalSpy.signalName = "infoPopupConfirmed"
425+ signalSpy.clear();
426+
427+ var okButton = findChild(root, "infoPopupOkButton");
428+ mouseClick(okButton, okButton.width / 2, okButton.height / 2);
429+
430+ tryCompareFunction(function() { return findChild(root, "infoPopup") === null}, true);
431+
432+ tryCompare(signalSpy, "count", 1);
433+ }
434+
435+ function test_infoTextDisplay_data() {
436+ return [
437+ {tag: "empty string", text: "", shown: false},
438+ {tag: "hello world", text: "hello world", shown: true},
439+ ]
440+ }
441+
442+ function test_infoTextDisplay(data) {
443+ pinPadCheckBox.checked = false
444+ waitForLockscreenReady();
445+
446+ infoTextTextField.text = data.text;
447+ var label = findChild(lockscreen, "infoTextLabel")
448+ compare(label.visible, data.shown);
449+ }
450+ }
451+
452+ SignalSpy {
453+ id: signalSpy
454+ target: lockscreen
277 }
455 }
278}
456}