on xenial help sign in
Support fingerprint events; add tests
01498b7Michael Terry
qml/Greeter/CoverPage.qml+21−1
@@ -41,9 +41,16 @@ Showable {
41 hide();
41 hide();
42 }
42 }
43
43
44+ function showErrorMessage(msg) {
45+ d.errorMessage = msg;
46+ showLabelAnimation.start();
47+ errorMessageAnimation.start();
48+ }
49+
44 QtObject {
50 QtObject {
45 id: d
51 id: d
46 property bool forceRightOnNextHideAnimation: false
52 property bool forceRightOnNextHideAnimation: false
53+ property string errorMessage
47 }
54 }
48
55
49 prepareToHide: function () {
56 prepareToHide: function () {
@@ -110,17 +117,19 @@ Showable {
110
117
111 Label {
118 Label {
112 id: swipeHint
119 id: swipeHint
120+ objectName: "swipeHint"
113 property real baseOpacity: 0.5
121 property real baseOpacity: 0.5
114 opacity: 0.0
122 opacity: 0.0
115 anchors.horizontalCenter: parent.horizontalCenter
123 anchors.horizontalCenter: parent.horizontalCenter
116 anchors.bottom: parent.bottom
124 anchors.bottom: parent.bottom
117 anchors.bottomMargin: units.gu(5)
125 anchors.bottomMargin: units.gu(5)
118− text: "《 " + i18n.tr("Unlock") + " 》"
126+ text: "《 " + (d.errorMessage ? d.errorMessage : i18n.tr("Unlock")) + " 》"
119 color: "white"
127 color: "white"
120 font.weight: Font.Light
128 font.weight: Font.Light
121
129
122 SequentialAnimation on opacity {
130 SequentialAnimation on opacity {
123 id: showLabelAnimation
131 id: showLabelAnimation
132+ objectName: "showLabelAnimation"
124 running: false
133 running: false
125 loops: 2
134 loops: 2
126
135
@@ -135,9 +144,20 @@ Showable {
135 to: 0.0
144 to: 0.0
136 duration: UbuntuAnimation.SleepyDuration
145 duration: UbuntuAnimation.SleepyDuration
137 }
146 }
147+
148+ onRunningChanged: {
149+ if (!running)
150+ d.errorMessage = "";
151+ }
138 }
152 }
139 }
153 }
140
154
155+ WrongPasswordAnimation {
156+ id: errorMessageAnimation
157+ objectName: "errorMessageAnimation"
158+ target: swipeHint
159+ }
160+
141 DragHandle {
161 DragHandle {
142 id: dragHandle
162 id: dragHandle
143 objectName: "coverPageDragHandle"
163 objectName: "coverPageDragHandle"
qml/Greeter/Greeter.qml+26−1
@@ -18,6 +18,7 @@ import QtQuick 2.4
18import AccountsService 0.1
18import AccountsService 0.1
19import GSettings 1.0
19import GSettings 1.0
20import Ubuntu.Components 1.3
20import Ubuntu.Components 1.3
21+import Ubuntu.Fingerprints 0.1
21import Ubuntu.SystemImage 0.1
22import Ubuntu.SystemImage 0.1
22import Unity.Launcher 0.1
23import Unity.Launcher 0.1
23import Unity.Session 0.1
24import Unity.Session 0.1
@@ -60,8 +61,9 @@ Showable {
60 signal emergencyCall()
61 signal emergencyCall()
61
62
62 function forceShow() {
63 function forceShow() {
64+ forcedUnlock = false;
63 showNow();
65 showNow();
64− loader.item.reset();
66+ d.selectUser(d.currentIndex, true);
65 }
67 }
66
68
67 function notifyAppFocused(appId) {
69 function notifyAppFocused(appId) {
@@ -114,6 +116,7 @@ Showable {
114 readonly property bool multiUser: lightDM.users.count > 1
116 readonly property bool multiUser: lightDM.users.count > 1
115 property int currentIndex
117 property int currentIndex
116 property bool waiting
118 property bool waiting
119+ property int fingerprintFailureCount
117
120
118 // We want 'launcherOffset' to animate down to zero. But not to animate
121 // We want 'launcherOffset' to animate down to zero. But not to animate
119 // while being dragged. So ideally we change this only when the user
122 // while being dragged. So ideally we change this only when the user
@@ -129,8 +132,16 @@ Showable {
129 UbuntuNumberAnimation {}
132 UbuntuNumberAnimation {}
130 }
133 }
131
134
135+ onFingerprintFailureCountChanged: {
136+ if (d.fingerprintFailureCount >= 3) {
137+ d.startUnlock(false /* toTheRight */);
138+ d.fingerprintFailureCount = 0;
139+ }
140+ }
141+
132 function selectUser(uid, reset) {
142 function selectUser(uid, reset) {
133 d.waiting = true;
143 d.waiting = true;
144+ d.fingerprintFailureCount = 0;
134 if (reset) {
145 if (reset) {
135 loader.item.reset();
146 loader.item.reset();
136 }
147 }
@@ -469,4 +480,18 @@ Showable {
469 target: i18n
480 target: i18n
470 onLanguageChanged: lightDM.infographic.readyForDataChange()
481 onLanguageChanged: lightDM.infographic.readyForDataChange()
471 }
482 }
483+
484+ Connections {
485+ target: Fingerprints
486+ onIdentificationCompleted: {
487+ // FIXME validate uid
488+ if (root.active)
489+ root.forcedUnlock = true;
490+ }
491+ onIdentificationFailed: {
492+ d.fingerprintFailureCount++;
493+ if (loader.item)
494+ loader.item.showErrorMessage(i18n.tr("Try again"));
495+ }
496+ }
472}
497}
qml/Greeter/NarrowView.qml+4
@@ -73,6 +73,10 @@ FocusScope {
73 lockscreen.clear(true);
73 lockscreen.clear(true);
74 }
74 }
75
75
76+ function showErrorMessage(msg) {
77+ coverPage.showErrorMessage(msg);
78+ }
79+
76 function reset() {
80 function reset() {
77 coverPage.show();
81 coverPage.show();
78 }
82 }
qml/Greeter/WideView.qml+4
@@ -66,6 +66,10 @@ FocusScope {
66 loginList.showError();
66 loginList.showError();
67 }
67 }
68
68
69+ function showErrorMessage(msg) {
70+ coverPage.showErrorMessage(msg);
71+ }
72+
69 function reset() {
73 function reset() {
70 loginList.reset();
74 loginList.reset();
71 }
75 }
tests/qmltests/Greeter/TestView.qml+7
@@ -33,9 +33,11 @@ Item {
33 property var infographicModel
33 property var infographicModel
34 readonly property bool fullyShown: _fullyShown
34 readonly property bool fullyShown: _fullyShown
35 readonly property bool required: _required
35 readonly property bool required: _required
36+ readonly property bool animating: _animating
36
37
37 property bool _fullyShown: true
38 property bool _fullyShown: true
38 property bool _required: true
39 property bool _required: true
40+ property bool _animating: true
39
41
40 signal selected(int index)
42 signal selected(int index)
41 signal responded(string response)
43 signal responded(string response)
@@ -48,6 +50,7 @@ Item {
48 signal _hideCalled()
50 signal _hideCalled()
49 signal _notifyAuthenticationSucceededCalled()
51 signal _notifyAuthenticationSucceededCalled()
50 signal _notifyAuthenticationFailedCalled()
52 signal _notifyAuthenticationFailedCalled()
53+ signal _showErrorMessageCalled(string msg)
51 signal _resetCalled()
54 signal _resetCalled()
52 signal _tryToUnlockCalled(bool toTheRight)
55 signal _tryToUnlockCalled(bool toTheRight)
53
56
@@ -77,6 +80,10 @@ Item {
77 _notifyAuthenticationFailedCalled();
80 _notifyAuthenticationFailedCalled();
78 }
81 }
79
82
83+ function showErrorMessage(msg) {
84+ _showErrorMessageCalled(msg);
85+ }
86+
80 function reset() {
87 function reset() {
81 _resetCalled();
88 _resetCalled();
82 }
89 }
tests/qmltests/Greeter/tst_Greeter.qml+50
@@ -19,6 +19,7 @@ import QtTest 1.0
19import ".."
19import ".."
20import "../../../qml/Greeter"
20import "../../../qml/Greeter"
21import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
22+import Ubuntu.Fingerprints 0.1
22import AccountsService 0.1
23import AccountsService 0.1
23import GSettings 1.0
24import GSettings 1.0
24import IntegratedLightDM 0.1 as LightDM
25import IntegratedLightDM 0.1 as LightDM
@@ -125,6 +126,12 @@ Item {
125 signalName: "_notifyAuthenticationFailedCalled"
126 signalName: "_notifyAuthenticationFailedCalled"
126 }
127 }
127
128
129+ SignalSpy {
130+ id: viewShowErrorMessageSpy
131+ target: testCase.view
132+ signalName: "_showErrorMessageCalled"
133+ }
134+
128 SignalSpy {
135 SignalSpy {
129 id: viewResetSpy
136 id: viewResetSpy
130 target: testCase.view
137 target: testCase.view
@@ -149,6 +156,7 @@ Item {
149 viewHideSpy.clear();
156 viewHideSpy.clear();
150 viewAuthenticationSucceededSpy.clear();
157 viewAuthenticationSucceededSpy.clear();
151 viewAuthenticationFailedSpy.clear();
158 viewAuthenticationFailedSpy.clear();
159+ viewShowErrorMessageSpy.clear();
152 viewResetSpy.clear();
160 viewResetSpy.clear();
153 viewTryToUnlockSpy.clear();
161 viewTryToUnlockSpy.clear();
154 tryCompare(greeter, "waiting", false);
162 tryCompare(greeter, "waiting", false);
@@ -552,5 +560,47 @@ Item {
552 LightDM.Greeter.showGreeter();
560 LightDM.Greeter.showGreeter();
553 compare(viewResetSpy.count, 1);
561 compare(viewResetSpy.count, 1);
554 }
562 }
563+
564+ function test_fingerprintSuccess() {
565+ selectUser("has-password");
566+ Fingerprints.mockIdentification(0, Fingerprints.None);
567+ verify(greeter.forcedUnlock);
568+ }
569+
570+ function test_fingerprintFailureMessage() {
571+ selectUser("has-password");
572+ Fingerprints.mockIdentification(0, Fingerprints.Error);
573+ compare(viewShowErrorMessageSpy.count, 1);
574+ compare(viewShowErrorMessageSpy.signalArguments[0][0], i18n.tr("Try again"));
575+ }
576+
577+ function test_fingerprintTooManyFailures() {
578+ selectUser("has-password");
579+ Fingerprints.mockIdentification(0, Fingerprints.Error);
580+ Fingerprints.mockIdentification(0, Fingerprints.Error);
581+ compare(viewTryToUnlockSpy.count, 0);
582+
583+ Fingerprints.mockIdentification(0, Fingerprints.Error);
584+ compare(viewTryToUnlockSpy.count, 1);
585+
586+ compare(viewShowErrorMessageSpy.count, 3);
587+ }
588+
589+ function test_fingerprintFailureCountReset() {
590+ selectUser("has-password");
591+ Fingerprints.mockIdentification(0, Fingerprints.Error);
592+ Fingerprints.mockIdentification(0, Fingerprints.Error);
593+ compare(viewTryToUnlockSpy.count, 0);
594+
595+ greeter.forceShow();
596+ Fingerprints.mockIdentification(0, Fingerprints.Error);
597+ Fingerprints.mockIdentification(0, Fingerprints.Error);
598+ compare(viewTryToUnlockSpy.count, 0);
599+
600+ Fingerprints.mockIdentification(0, Fingerprints.Error);
601+ compare(viewTryToUnlockSpy.count, 1);
602+
603+ compare(viewShowErrorMessageSpy.count, 5);
604+ }
555 }
605 }
556}
606}
tests/qmltests/Greeter/tst_NarrowView.qml+16
@@ -527,5 +527,21 @@ Item {
527 mouseClick(coverPage, coverPage.width/2, coverPage.height - units.gu(2));
527 mouseClick(coverPage, coverPage.width/2, coverPage.height - units.gu(2));
528 verify(!coverPage.shown);
528 verify(!coverPage.shown);
529 }
529 }
530+
531+ function test_showErrorMessage() {
532+ var coverPage = findChild(view, "coverPage");
533+ var swipeHint = findChild(coverPage, "swipeHint");
534+ var errorMessageAnimation = findInvisibleChild(coverPage, "errorMessageAnimation");
535+ var showLabelAnimation = findInvisibleChild(coverPage, "showLabelAnimation");
536+
537+ view.showErrorMessage("hello");
538+ compare(swipeHint.text, "《 hello 》");
539+ verify(errorMessageAnimation.running);
540+ verify(showLabelAnimation.running);
541+
542+ errorMessageAnimation.complete();
543+ showLabelAnimation.complete();
544+ compare(swipeHint.text, "《 " + i18n.tr("Unlock") + " 》");
545+ }
530 }
546 }
531}
547}
tests/qmltests/tst_Shell.qml+15
@@ -21,6 +21,7 @@ import GSettings 1.0
21import IntegratedLightDM 0.1 as LightDM
21import IntegratedLightDM 0.1 as LightDM
22import Ubuntu.Components 1.3
22import Ubuntu.Components 1.3
23import Ubuntu.Components.ListItems 1.3 as ListItem
23import Ubuntu.Components.ListItems 1.3 as ListItem
24+import Ubuntu.Fingerprints 0.1
24import Ubuntu.Telephony 0.1 as Telephony
25import Ubuntu.Telephony 0.1 as Telephony
25import Unity.Application 0.1
26import Unity.Application 0.1
26import Unity.Connectivity 0.1
27import Unity.Connectivity 0.1
@@ -325,6 +326,20 @@ Rectangle {
325 }
326 }
326 }
327 }
327
328
329+ Label {
330+ text: "Fingerprint"
331+ }
332+ Row {
333+ Button {
334+ text: "Success"
335+ onClicked: Fingerprints.mockIdentification(0, Fingerprints.None)
336+ }
337+
338+ Button {
339+ text: "Failure"
340+ onClicked: Fingerprints.mockIdentification(0, Fingerprints.Error)
341+ }
342+ }
328 }
343 }
329 }
344 }
330 }
345 }