on xenial help sign in
enhance lockscreen

* add an additional label (e.g. phone number for multi sim) * add a infoPopup (e.g. to display a warning for last retry) * add min and max limit values * add tests for the above

commitba4f0be83bfbe71fc373821be09dbf2a058b91f2 authorMichael Zanetti <michael.zanetti@canonical.com> dateMon 19 May 2014, 19:29 (12 years ago) parent7d2c56a
5 files changed+293−48
qml/Greeter/Lockscreen.qml+79−7 qml/Greeter/PassphraseLockscreen.qml+3−1 qml/Greeter/PinLockscreen.qml+21−9 qml/Shell.qml+2−1 tests/qmltests/Greeter/tst_Lockscreen.qml+188−30
qml/Greeter/Lockscreen.qml+79−7
@@ -15,7 +15,8 @@
1515 */
1616
1717import QtQuick 2.0
18−import Ubuntu.Components 0.1
18+import Ubuntu.Components 1.0
19+import Ubuntu.Components.Popups 1.0
1920import "../Components"
2021
2122Showable {
@@ -27,12 +28,24 @@ Showable {
2728 // Placeholder text
2829 property string placeholderText: ""
2930
31+ // Informational text. (e.g. some text to tell which domain this is pin is entered for.
32+ property string infoText: ""
33+
3034 // In case the Lockscreen can show a greeter message, this is the username
3135 property string username: ""
3236
33− // Set this to a value greater 0 to enable auto-confirm behavior for the lockscreen.
34− // This is ignored by the alphaNumeric lockscreen as that one is confirmed with pressing enter on the OSK.
35− property int pinLength: -1
37+ // Set those to a value greater 0 to restrict the pin length.
38+ // If both are unset, the Lockscreen will show a confirm button and allow typing any length of pin before
39+ // confirming. If minPinLength is set to a value > 0, the confirm button will only become active when the
40+ // entered pin is at least that long. If maxPinLength is set, the lockscreen won't allow entering any
41+ // more numbers than that. If both are set, the lockscreen will enter auto confirming behavior, hiding the
42+ // confirmation button and triggering that automatically when the entered pin reached that length.
43+ // This is ignored by the alphaNumeric lockscreen as that one is always confirmed by pressing enter on the OSK.
44+ property int minPinLength: -1
45+ property int maxPinLength: -1
46+
47+ // Set this to a value greater 0 to show a label telling the user how many retries are left
48+ property int retryCount: -1
3649
3750 property url background: ""
3851
@@ -56,6 +69,10 @@ Showable {
5669 pinPadLoader.item.clear(showAnimation);
5770 }
5871
72+ function showInfoPopup(title, text) {
73+ PopupUtils.open(infoPopupComponent, root, {title: title, text: text})
74+ }
75+
5976 Rectangle {
6077 // In case background fails to load or is undefined
6178 id: backgroundBackup
@@ -68,7 +85,6 @@ Showable {
6885 objectName: "lockscreenBackground"
6986 anchors {
7087 fill: parent
71− topMargin: backgroundTopMargin
7288 }
7389 source: root.required ? root.background : ""
7490 fillMode: Image.PreserveAspectCrop
@@ -78,6 +94,43 @@ Showable {
7894 anchors.fill: root
7995 }
8096
97+ Column {
98+ spacing: units.gu(2)
99+ anchors {
100+ left: parent.left
101+ right: parent.right
102+ bottom: pinPadLoader.top
103+ bottomMargin: units.gu(2)
104+ }
105+ Label {
106+ objectName: "retryCountLabel"
107+ anchors {
108+ left: parent.left
109+ right: parent.right
110+ }
111+ text: i18n.tr("%1 retries remaining").arg(root.retryCount)
112+ horizontalAlignment: Text.AlignHCenter
113+ color: "#f3f3e7"
114+ opacity: 0.6
115+ visible: root.retryCount >= 0
116+ }
117+ Label {
118+ id: infoTextLabel
119+ objectName: "infoTextLabel"
120+ anchors {
121+ left: parent.left
122+ right: parent.right
123+ }
124+ text: root.infoText
125+ horizontalAlignment: Text.AlignHCenter
126+ color: "#f3f3e7"
127+ opacity: 0.6
128+ visible: root.infoText.length > 0
129+ }
130+ }
131+
132+
133+
81134 Loader {
82135 id: pinPadLoader
83136 objectName: "pinPadLoader"
@@ -105,8 +158,13 @@ Showable {
105158
106159 Binding {
107160 target: pinPadLoader.item
108− property: "pinLength"
109− value: root.pinLength
161+ property: "minPinLength"
162+ value: root.minPinLength
163+ }
164+ Binding {
165+ target: pinPadLoader.item
166+ property: "maxPinLength"
167+ value: root.maxPinLength
110168 }
111169 Binding {
112170 target: pinPadLoader.item
@@ -153,4 +211,18 @@ Showable {
153211 anchors.horizontalCenter: parent.horizontalCenter
154212 }
155213 }
214+
215+ Component {
216+ id: infoPopupComponent
217+ Dialog {
218+ id: dialog
219+ objectName: "infoPopup"
220+
221+ Button {
222+ objectName: "infoPopupOkButton"
223+ text: i18n.tr("OK")
224+ onClicked: PopupUtils.close(dialog)
225+ }
226+ }
227+ }
156228}
qml/Greeter/PassphraseLockscreen.qml+3−1
@@ -75,7 +75,9 @@ Item {
7575 hasClearButton: false
7676
7777 onAccepted: {
78− root.entered(pinentryField.text);
78+ if (pinentryField.text) {
79+ root.entered(pinentryField.text);
80+ }
7981 }
8082 }
8183 }
qml/Greeter/PinLockscreen.qml+21−9
@@ -27,7 +27,8 @@ Column {
2727 property alias placeholderText: pinentryField.placeholderText
2828 property int padWidth: units.gu(34)
2929 property int padHeight: units.gu(28)
30− property int pinLength: -1
30+ property int minPinLength: -1
31+ property int maxPinLength: -1
3132
3233 signal entered(string passphrase)
3334 signal cancel()
@@ -41,6 +42,10 @@ Column {
4142 }
4243 }
4344
45+ QtObject {
46+ id: priv
47+ property bool autoConfirm: root.minPinLength == root.maxPinLength && root.minPinLength != -1
48+ }
4449
4550 UbuntuShape {
4651 id: pinentryField
@@ -52,12 +57,19 @@ Column {
5257 radius: "medium"
5358 property string text: ""
5459 property string placeholderText: ""
60+
61+ function appendChar(character) {
62+ if (root.maxPinLength == -1 || pinentryField.text.length < root.maxPinLength) {
63+ pinentryField.text = pinentryField.text + character;
64+ }
65+ }
66+
5567 onTextChanged: {
5668 pinentryFieldLabel.text = "";
5769 for (var i = 0; i < text.length; ++i) {
5870 pinentryFieldLabel.text += "•";
5971 }
60− if (text.length === root.pinLength) {
72+ if (priv.autoConfirm && text.length === root.maxPinLength) {
6173 root.entered(text);
6274 }
6375 }
@@ -91,7 +103,7 @@ Column {
91103 bottom: parent.bottom
92104 bottomMargin: units.gu(1)
93105 }
94− visible: root.pinLength == -1
106+ visible: !priv.autoConfirm
95107 width: height
96108 name: "erase"
97109 MouseArea {
@@ -168,7 +180,7 @@ Column {
168180 enabled: entryEnabled
169181
170182 onClicked: {
171− pinentryField.text = pinentryField.text + text;
183+ pinentryField.appendChar(text);
172184 }
173185 }
174186 }
@@ -186,7 +198,7 @@ Column {
186198 width: root.padWidth / 3
187199 height: root.padHeight / 4
188200 text: "0"
189− onClicked: pinentryField.text = pinentryField.text + text
201+ onClicked: pinentryField.appendChar(text);
190202 enabled: entryEnabled
191203 }
192204
@@ -194,16 +206,16 @@ Column {
194206 objectName: "pinPadButtonErase"
195207 width: root.padWidth / 3
196208 height: root.padHeight / 4
197− iconName: root.pinLength == -1 ? "" : "erase"
198− subText: root.pinLength == -1 ? "DONE" : ""
209+ iconName: priv.autoConfirm ? "erase" : ""
210+ subText: priv.autoConfirm ? "" : "DONE"
199211 onClicked: {
200− if (root.pinLength !== -1) {
212+ if (priv.autoConfirm) {
201213 pinentryField.text = pinentryField.text.substring(0, pinentryField.text.length-1);
202214 } else {
203215 root.entered(pinentryField.text);
204216 }
205217 }
206− enabled: entryEnabled
218+ enabled: priv.autoConfirm ? entryEnabled : pinentryField.text.length >= root.minPinLength
207219 }
208220 }
209221 }
qml/Shell.qml+2−1
@@ -318,7 +318,8 @@ FocusScope {
318318 width: parent.width
319319 height: parent.height - panel.panelHeight
320320 background: shell.background
321− pinLength: 4
321+ minPinLength: 4
322+ maxPinLength: 4
322323
323324 onEntered: LightDM.Greeter.respond(passphrase);
324325 onCancel: greeter.show()
tests/qmltests/Greeter/tst_Lockscreen.qml+188−30
@@ -23,25 +23,33 @@ import LightDM 0.1 as LightDM
2323import Unity.Test 0.1 as UT
2424
2525Rectangle {
26− width: units.gu(60)
26+ id: root
27+ width: units.gu(80)
2728 height: units.gu(80)
2829 color: "orange"
2930
3031 Lockscreen {
3132 id: lockscreen
3233 anchors.fill: parent
33− anchors.rightMargin: units.gu(18)
34+ anchors.rightMargin: units.gu(30)
3435 placeholderText: "Please enter your PIN"
3536 alphaNumeric: pinPadCheckBox.checked
36− pinLength: pinLengthTextField.text
37+ minPinLength: minPinLengthTextField.text
38+ maxPinLength: maxPinLengthTextField.text
39+ retryCount: retryCountTextField.text
3740 username: "Lola"
41+ background: "../../../qml/graphics/phone_background.jpg"
42+ infoText: infoTextTextField.text
3843 }
3944
4045 Connections {
4146 target: lockscreen
4247
4348 onEmergencyCall: emergencyCheckBox.checked = true
44− onEntered: enteredLabel.text = passphrase
49+ onEntered: {
50+ enteredLabel.text = passphrase
51+ lockscreen.clear(true)
52+ }
4553 }
4654
4755 Connections {
@@ -49,9 +57,9 @@ Rectangle {
4957
5058 onShowPrompt: {
5159 if (text.indexOf("PIN") >= 0) {
52− lockscreen.alphaNumeric = false
60+ pinPadCheckBox.checked = false
5361 } else {
54− lockscreen.alphaNumeric = true
62+ pinPadCheckBox.checked = true
5563 }
5664 lockscreen.placeholderText = text;
5765 }
@@ -65,6 +73,7 @@ Rectangle {
6573 Column {
6674 anchors.fill: parent
6775 anchors.margins: units.gu(1)
76+ spacing: units.gu(1)
6877
6978 Row {
7079 CheckBox {
@@ -86,12 +95,32 @@ Rectangle {
8695 }
8796 Row {
8897 TextField {
89− id: pinLengthTextField
98+ id: minPinLengthTextField
99+ width: units.gu(7)
100+ text: "4"
101+ }
102+ Label {
103+ text: "Min PIN length"
104+ }
105+ }
106+ Row {
107+ TextField {
108+ id: maxPinLengthTextField
90109 width: units.gu(7)
91110 text: "4"
92111 }
93112 Label {
94− text: "PIN length"
113+ text: "Max PIN length"
114+ }
115+ }
116+ Row {
117+ TextField {
118+ id: retryCountTextField
119+ width: units.gu(7)
120+ text: "3"
121+ }
122+ Label {
123+ text: "Retries left"
95124 }
96125 }
97126 Label {
@@ -108,12 +137,40 @@ Rectangle {
108137 }
109138 Button {
110139 text: "start auth (1234)"
140+ width: parent.width
111141 onClicked: LightDM.Greeter.authenticate("has-pin")
112142 }
113143 Button {
114144 text: "start auth (password)"
145+ width: parent.width
115146 onClicked: LightDM.Greeter.authenticate("has-password")
116147 }
148+
149+ TextField {
150+ id: infoTextTextField
151+ width: parent.width
152+ placeholderText: "Infotext"
153+ text: "+49 179 3253553"
154+ }
155+
156+ TextField {
157+ id: infoPopupTitleTextField
158+ width: parent.width
159+ placeholderText: "Popup title"
160+ text: "This will be the last attempt"
161+ }
162+
163+ TextArea {
164+ id: infoPopupTextArea
165+ width: parent.width
166+ text: "If the SIM PIN is entered incorrectly, your SIM will be blocked and would require the PUK code to unlock."
167+ }
168+
169+ Button {
170+ text: "open info popup"
171+ width: parent.width
172+ onClicked: lockscreen.showInfoPopup(infoPopupTitleTextField.text, infoPopupTextArea.text)
173+ }
117174 }
118175 }
119176
@@ -121,6 +178,16 @@ Rectangle {
121178 name: "Lockscreen"
122179 when: windowShown
123180
181+ function cleanup() {
182+ lockscreen.clear(false);
183+ }
184+
185+ function waitForLockscreenReady() {
186+ var pinPadLoader = findChild(lockscreen, "pinPadLoader");
187+ tryCompare(pinPadLoader, "status", Loader.Ready)
188+ waitForRendering(lockscreen)
189+ }
190+
124191 function test_loading_data() {
125192 return [
126193 {tag: "numeric", alphanumeric: false, pinPadAvailable: true },
@@ -129,8 +196,8 @@ Rectangle {
129196 }
130197
131198 function test_loading(data) {
132− lockscreen.alphaNumeric = data.alphanumeric
133− waitForRendering(lockscreen)
199+ pinPadCheckBox.checked = data.alphanumeric
200+ waitForLockscreenReady();
134201 if (data.pinPadAvailable) {
135202 compare(findChild(lockscreen, "pinPadButton8").text, "8", "Could not find number 8 on PinPad")
136203 } else {
@@ -147,8 +214,8 @@ Rectangle {
147214
148215 function test_emergency_call(data) {
149216 emergencyCheckBox.checked = false
150− lockscreen.alphaNumeric = data.alphanumeric
151− waitForRendering(lockscreen)
217+ pinPadCheckBox.checked = data.alphanumeric
218+ waitForLockscreenReady();
152219 var emergencyButton = findChild(lockscreen, "emergencyCallIcon")
153220 mouseClick(emergencyButton, units.gu(1), units.gu(1))
154221 tryCompare(emergencyCheckBox, "checked", true)
@@ -163,9 +230,9 @@ Rectangle {
163230 }
164231
165232 function test_labels(data) {
166− lockscreen.alphaNumeric = data.alphanumeric
233+ pinPadCheckBox.checked = data.alphanumeric
167234 lockscreen.placeholderText = data.placeholderText
168− waitForRendering(lockscreen)
235+ waitForLockscreenReady();
169236 compare(findChild(lockscreen, "pinentryField").placeholderText, data.placeholderText, "Placeholdertext is not what it should be")
170237 if (data.alphanumeric) {
171238 compare(findChild(lockscreen, "greeterLabel").text, "Hello " + data.username, "Greeter is not set correctly")
@@ -175,23 +242,25 @@ Rectangle {
175242
176243 function test_unlock_data() {
177244 return [
178− {tag: "numeric", alphanumeric: false, username: "has-pin", password: "1234", pinLength: 4},
179− {tag: "alphanumeric", alphanumeric: true, username: "has-password", password: "password", pinLength: -1},
180− {tag: "numeric (wrong)", alphanumeric: false, username: "has-pin", password: "4321", pinLength: 4},
181− {tag: "alphanumeric (wrong)", alphanumeric: true, username: "has-password", password: "drowssap", pinLength: -1},
182− {tag: "flexible length", alphanumeric: false, username: "has-pin", password: "1234", pinLength: -1},
245+ {tag: "numeric", alphanumeric: false, username: "has-pin", password: "1234", minPinLength: 4, maxPinLength: 4},
246+ {tag: "alphanumeric", alphanumeric: true, username: "has-password", password: "password", minPinLength: -1, maxPinLength: -1},
247+ {tag: "numeric (wrong)", alphanumeric: false, username: "has-pin", password: "4321", minPinLength: 4, maxPinLength: 4},
248+ {tag: "alphanumeric (wrong)", alphanumeric: true, username: "has-password", password: "drowssap", minPinLength: -1, maxPinLength: -1},
249+ {tag: "flexible length", alphanumeric: false, username: "has-pin", password: "1234", minPinLength: -1, maxPinLength: -1},
183250 ]
184251 }
185252
186253 function test_unlock(data) {
187254 enteredLabel.text = ""
188− pinLengthTextField.text = data.pinLength
255+ minPinLengthTextField.text = data.minPinLength
256+ maxPinLengthTextField.text = data.maxPinLength
189257 LightDM.Greeter.authenticate(data.username)
190− waitForRendering(lockscreen)
258+ waitForLockscreenReady();
191259
192260 var inputField = findChild(lockscreen, "pinentryField")
193261 if (data.alphanumeric) {
194262 mouseClick(inputField, units.gu(1), units.gu(1))
263+ tryCompare(inputField, "focus", true);
195264 typeString(data.password)
196265 keyClick(Qt.Key_Enter)
197266 } else {
@@ -200,7 +269,7 @@ Rectangle {
200269 var button = findChild(lockscreen, "pinPadButton" + character)
201270 mouseClick(button, units.gu(1), units.gu(1))
202271 }
203− if (data.pinLength == -1) {
272+ if (data.minPinLength !== data.maxPinLength || data.minPinLength == -1) {
204273 var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
205274 mouseClick(pinPadButtonErase, units.gu(1), units.gu(1));
206275 }
@@ -219,11 +288,12 @@ Rectangle {
219288
220289 function test_clear(data) {
221290 pinPadCheckBox.checked = data.alphanumeric
222− waitForRendering(lockscreen)
291+ waitForLockscreenReady();
223292
224293 var inputField = findChild(lockscreen, "pinentryField")
225294 if (data.alphanumeric) {
226295 mouseClick(inputField, units.gu(1), units.gu(1))
296+ tryCompare(inputField, "activeFocus", true);
227297 typeString("1")
228298 } else {
229299 var button = findChild(lockscreen, "pinPadButton1")
@@ -246,33 +316,121 @@ Rectangle {
246316
247317 function test_backspace_data() {
248318 return [
249− {tag: "fixed length", pinLength: 4},
250− {tag: "variable length", pinLength: -1}
319+ {tag: "fixed length", minPinLength: 4, maxPinLength: 4},
320+ {tag: "variable undefined length", minPinLength: -1, maxPinLength: -1},
321+ {tag: "variable restricted length", minPinLength: 4, maxPinLength: 8}
251322 ];
252323 }
253324
254325 function test_backspace(data) {
255326 pinPadCheckBox.checked = false
256− pinLengthTextField.text = data.pinLength
257− waitForRendering(lockscreen);
327+ minPinLengthTextField.text = data.minPinLength
328+ maxPinLengthTextField.text = data.maxPinLength
329+ waitForLockscreenReady();
258330
259331 var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
260332 var backspaceIcon = findChild(lockscreen, "backspaceIcon");
261333 var pinEntryField = findChild(lockscreen, "pinentryField");
262334
263− compare(pinPadButtonErase.iconName, data.pinLength == -1 ? "" : "erase");
264− compare(backspaceIcon.visible, data.pinLength == -1);
335+ var autoConfirmEnabled = data.minPinLength === data.maxPinLength && data.minPinLength !== -1;
336+ compare(pinPadButtonErase.iconName, autoConfirmEnabled ? "erase" : "");
337+ compare(backspaceIcon.visible, !autoConfirmEnabled);
265338
266339 var pinPadButton5 = findChild(lockscreen, "pinPadButton5");
267340 mouseClick(pinPadButton5, units.gu(1), units.gu(1));
268341 compare(pinEntryField.text, "5");
269342
270− if (data.pinLength == -1) {
343+ if (data.minPinLength !== data.maxPinLength) {
271344 mouseClick(backspaceIcon, units.gu(1), units.gu(1));
272345 } else {
273346 mouseClick(pinPadButtonErase, units.gu(1), units.gu(1));
274347 }
275348 compare(pinEntryField.text, "");
276349 }
350+
351+ function test_minMaxLength_data() {
352+ return [
353+ {tag: "undefined", minPinLength: -1, maxPinLength: -1},
354+ {tag: "fixed", minPinLength: 4, maxPinLength: 4},
355+ {tag: "variable, limited", minPinLength: 4, maxPinLength: 8},
356+ ];
357+ }
358+
359+ function test_minMaxLength(data) {
360+ pinPadCheckBox.checked = false
361+ minPinLengthTextField.text = data.minPinLength
362+ maxPinLengthTextField.text = data.maxPinLength
363+ waitForLockscreenReady();
364+
365+ var pinPadButton5 = findChild(lockscreen, "pinPadButton5");
366+ var pinPadButtonErase = findChild(lockscreen, "pinPadButtonErase");
367+ var inputField = findChild(lockscreen, "pinentryField")
368+
369+ for (var i = 0; i < 10; i++) {
370+ mouseClick(pinPadButton5, units.gu(1), units.gu(1));
371+
372+ if (data.minPinLength == data.maxPinLength && data.minPinLength != -1) {
373+ // auto confirm mode...
374+ compare(inputField.text.length, (i+1) % data.minPinLength);
375+ } else {
376+ // manual confirm mode
377+ tryCompare(pinPadButtonErase, "enabled", (i+1) >= data.minPinLength)
378+ if (data.maxPinLength == -1) {
379+ compare(inputField.text.length, i+1);
380+ } else {
381+ compare(inputField.text.length, Math.min(data.maxPinLength, i+1));
382+ }
383+
384+ }
385+ }
386+ }
387+
388+ function test_retryDisplay_data() {
389+ return [
390+ {tag: "0", retryCount: 0, shown: true},
391+ {tag: "1", retryCount: 1, shown: true},
392+ {tag: "3", retryCount: 3, shown: true},
393+ {tag: "-1", retryCount: -1, shown: false},
394+ ]
395+ }
396+
397+ function test_retryDisplay(data) {
398+ pinPadCheckBox.checked = false
399+ waitForLockscreenReady();
400+
401+ retryCountTextField.text = data.retryCount;
402+ var label = findChild(lockscreen, "retryCountLabel")
403+ compare(label.visible, data.shown);
404+ }
405+
406+ function test_infoPopup() {
407+ verify(findChild(root, "infoPopup") === null);
408+ lockscreen.showInfoPopup("foo", "bar");
409+ tryCompareFunction(function() { return findChild(root, "infoPopup") !== null}, true);
410+
411+ var infoPopup = findChild(root, "infoPopup");
412+ compare(infoPopup.title, "foo");
413+ compare(infoPopup.text, "bar");
414+ var okButton = findChild(root, "infoPopupOkButton");
415+ mouseClick(okButton, okButton.width / 2, okButton.height / 2);
416+
417+ tryCompareFunction(function() { return findChild(root, "infoPopup") === null}, true);
418+ }
419+
420+ function test_infoTextDisplay_data() {
421+ return [
422+ {tag: "empty string", text: "", shown: false},
423+ {tag: "hello world", text: "hello world", shown: true},
424+ ]
425+ }
426+
427+ function test_infoTextDisplay(data) {
428+ pinPadCheckBox.checked = false
429+ waitForLockscreenReady();
430+
431+ infoTextTextField.text = data.text;
432+ var label = findChild(lockscreen, "infoTextLabel")
433+ compare(label.visible, data.shown);
434+ }
277435 }
278436}