on xenial help sign in
merge trunk
commitd856065fc9eb8c8e9a6a229692da4be36e606827 authorLukáš Tinkl <lukas.tinkl@canonical.com> dateFri 24 Jun 2016, 15:44 (10 years ago) parent8b20c16f406245
46 files changed+1049−65
debian/changelog+7 debian/control+2−1 plugins/AccountsService/AccountsService.cpp+21−1 plugins/AccountsService/AccountsService.h+13−1 plugins/AccountsService/com.canonical.unity.AccountsService.xml+4 plugins/IntegratedLightDM/liblightdm/UsersModel.cpp+4−1 plugins/IntegratedLightDM/liblightdm/UsersModel.h+2−1 plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+5−3 plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.h+2−1 po/unity8.pot+20−16 qml/Components/DelayedLockscreen.qml+1 qml/Components/Lockscreen.qml+8 qml/Components/PassphraseLockscreen.qml+4 qml/Components/PinLockscreen.qml+4 qml/Greeter/CoverPage.qml+22−1 qml/Greeter/Greeter.qml+97−4 qml/Greeter/NarrowView.qml+18−2 qml/Greeter/WideView.qml+5−1 qml/Shell.qml+3 tests/mocks/AccountsService/AccountsService.cpp+24 tests/mocks/AccountsService/AccountsService.h+16 tests/mocks/Biometryd/CMakeLists.txt+14 tests/mocks/Biometryd/MockBiometryd.cpp+43 tests/mocks/Biometryd/MockBiometryd.h+50 tests/mocks/Biometryd/MockDevice.cpp+29 tests/mocks/Biometryd/MockDevice.h+40 tests/mocks/Biometryd/MockIdentifier.cpp+28 tests/mocks/Biometryd/MockIdentifier.h+36 tests/mocks/Biometryd/MockObserver.cpp+22 tests/mocks/Biometryd/MockObserver.h+37 tests/mocks/Biometryd/MockOperation.cpp+56 tests/mocks/Biometryd/MockOperation.h+48 tests/mocks/Biometryd/plugin.cpp+41 tests/mocks/Biometryd/plugin.h+31 tests/mocks/Biometryd/qmldir+3 tests/mocks/CMakeLists.txt+1 tests/mocks/IntegratedLightDM/liblightdm/UsersModel.cpp+4−1 tests/mocks/IntegratedLightDM/liblightdm/UsersModel.h+2−1 tests/mocks/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+29−23 tests/mocks/IntegratedLightDM/liblightdm/UsersModelPrivate.h+2−1 tests/qmltests/Components/tst_Lockscreen.qml+20−1 tests/qmltests/Greeter/TestView.qml+10−3 tests/qmltests/Greeter/tst_Greeter.qml+171 tests/qmltests/Greeter/tst_NarrowView.qml+22−1 tests/qmltests/Greeter/tst_WideView.qml+1−1 tests/qmltests/tst_Shell.qml+27
debian/changelog+7
@@ -1,3 +1,10 @@
1+unity8 (8.12+16.10.20160620.3-0ubuntu1) yakkety; urgency=medium
2+
3+ [ Michael Terry, Michał Sawicz ]
4+ * Support unlocking via fingerprint, if one is registered.
5+
6+ -- Michael Terry <michael.terry@canonical.com> Mon, 20 Jun 2016 20:25:25 +0000
7+
18unity8 (8.12+16.10.20160617-0ubuntu1) yakkety; urgency=medium
29
310 [ Michael Terry ]
debian/control+2−1
@@ -103,6 +103,7 @@ Depends: dmz-cursor-theme,
103103 libcap2-bin,
104104 libglib2.0-bin,
105105 qmenumodel-qml (>= 0.2.9),
106+ qml-module-biometryd,
106107 qml-module-qt-labs-folderlistmodel,
107108 qml-module-qtquick-xmllistmodel,
108109 qml-module-qtsysteminfo,
@@ -192,7 +193,7 @@ Package: unity8-private
192193Architecture: any
193194Multi-Arch: same
194195Pre-Depends: ${misc:Pre-Depends},
195−Depends: accountsservice-ubuntu-schemas (>= 0.0.5),
196+Depends: accountsservice-ubuntu-schemas (>= 0.0.7),
196197 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
197198 libhardware2,
198199 pay-service,
plugins/AccountsService/AccountsService.cpp+21−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013, 2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -36,8 +36,10 @@
3636#define PROP_DEMO_EDGES QStringLiteral("demo-edges")
3737#define PROP_DEMO_EDGES_COMPLETED QStringLiteral("DemoEdgesCompleted")
3838#define PROP_EMAIL QStringLiteral("Email")
39+#define PROP_ENABLE_FINGERPRINT_IDENTIFICATION QStringLiteral("EnableFingerprintIdentification")
3940#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
4041#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
42+#define PROP_FAILED_FINGERPRINT_LOGINS QStringLiteral("FailedFingerprintLogins")
4143#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
4244#define PROP_INPUT_SOURCES QStringLiteral("InputSources")
4345#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
@@ -93,12 +95,14 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
9395 registerProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QStringLiteral("keymapsChanged"));
9496 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
9597 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
98+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_FINGERPRINT_IDENTIFICATION, QStringLiteral("enableFingerprintIdentificationChanged"));
9699 registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
97100 registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED, QStringLiteral("enableIndicatorsWhileLockedChanged"));
98101 registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
99102 registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
100103 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
101104 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
105+ registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, QStringLiteral("failedFingerprintLoginsChanged"));
102106 registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
103107
104108 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
@@ -176,6 +180,12 @@ void AccountsService::markDemoEdgeCompleted(const QString &edge)
176180 }
177181}
178182
183+bool AccountsService::enableFingerprintIdentification() const
184+{
185+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_FINGERPRINT_IDENTIFICATION);
186+ return value.toBool();
187+}
188+
179189bool AccountsService::enableLauncherWhileLocked() const
180190{
181191 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
@@ -274,6 +284,16 @@ QStringList AccountsService::keymaps() const
274284 return {QStringLiteral("us")};
275285}
276286
287+uint AccountsService::failedFingerprintLogins() const
288+{
289+ return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS).toUInt();
290+}
291+
292+void AccountsService::setFailedFingerprintLogins(uint failedFingerprintLogins)
293+{
294+ setProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, failedFingerprintLogins);
295+}
296+
277297uint AccountsService::failedLogins() const
278298{
279299 return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
plugins/AccountsService/AccountsService.h+13−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013, 2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -41,6 +41,9 @@ class AccountsService: public QObject
4141 Q_PROPERTY (QStringList demoEdgesCompleted
4242 READ demoEdgesCompleted
4343 NOTIFY demoEdgesCompletedChanged)
44+ Q_PROPERTY (bool enableFingerprintIdentification
45+ READ enableFingerprintIdentification
46+ NOTIFY enableFingerprintIdentificationChanged)
4447 Q_PROPERTY (bool enableLauncherWhileLocked
4548 READ enableLauncherWhileLocked
4649 NOTIFY enableLauncherWhileLockedChanged)
@@ -60,6 +63,10 @@ class AccountsService: public QObject
6063 READ failedLogins
6164 WRITE setFailedLogins
6265 NOTIFY failedLoginsChanged)
66+ Q_PROPERTY (uint failedFingerprintLogins
67+ READ failedFingerprintLogins
68+ WRITE setFailedFingerprintLogins
69+ NOTIFY failedFingerprintLoginsChanged)
6370 Q_PROPERTY(bool hereEnabled
6471 READ hereEnabled
6572 WRITE setHereEnabled
@@ -91,6 +98,7 @@ public:
9198 void setDemoEdges(bool demoEdges);
9299 QStringList demoEdgesCompleted() const;
93100 Q_INVOKABLE void markDemoEdgeCompleted(const QString &edge);
101+ bool enableFingerprintIdentification() const;
94102 bool enableLauncherWhileLocked() const;
95103 bool enableIndicatorsWhileLocked() const;
96104 QString backgroundFile() const;
@@ -98,6 +106,8 @@ public:
98106 PasswordDisplayHint passwordDisplayHint() const;
99107 uint failedLogins() const;
100108 void setFailedLogins(uint failedLogins);
109+ uint failedFingerprintLogins() const;
110+ void setFailedFingerprintLogins(uint failedFingerprintLogins);
101111 bool hereEnabled() const;
102112 void setHereEnabled(bool enabled);
103113 QString hereLicensePath() const;
@@ -112,12 +122,14 @@ Q_SIGNALS:
112122 void userChanged();
113123 void demoEdgesChanged();
114124 void demoEdgesCompletedChanged();
125+ void enableFingerprintIdentificationChanged();
115126 void enableLauncherWhileLockedChanged();
116127 void enableIndicatorsWhileLockedChanged();
117128 void backgroundFileChanged();
118129 void statsWelcomeScreenChanged();
119130 void passwordDisplayHintChanged();
120131 void failedLoginsChanged();
132+ void failedFingerprintLoginsChanged();
121133 void hereEnabledChanged();
122134 void hereLicensePathChanged();
123135 void realNameChanged();
plugins/AccountsService/com.canonical.unity.AccountsService.xml+4
@@ -47,5 +47,9 @@
4747 <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
4848 </property>
4949
50+ <property name="FailedFingerprintLogins" type="u" access="readwrite">
51+ <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
52+ </property>
53+
5054 </interface>
5155</node>
plugins/IntegratedLightDM/liblightdm/UsersModel.cpp+4−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -52,6 +52,7 @@ UsersModel::UsersModel(QObject *parent) :
5252 roles[SessionRole] = "session";
5353 roles[HasMessagesRole] = "hasMessages";
5454 roles[ImagePathRole] = "imagePath";
55+ roles[UidRole] = "uid";
5556 setRoleNames(roles);
5657
5758 connect(d_ptr, &UsersModelPrivate::dataChanged, this, [this](int i) {
@@ -101,6 +102,8 @@ QVariant UsersModel::data(const QModelIndex &index, int role) const
101102 return d->entries[row].has_messages;
102103 case UsersModel::ImagePathRole:
103104 return "";
105+ case UsersModel::UidRole:
106+ return d->entries[row].uid;
104107 default:
105108 return QVariant();
106109 }
plugins/IntegratedLightDM/liblightdm/UsersModel.h+2−1
@@ -50,7 +50,8 @@ public:
5050 SessionRole,
5151 HasMessagesRole,
5252 ImagePathRole,
53− BackgroundPathRole
53+ BackgroundPathRole,
54+ UidRole
5455 };
5556
5657 int rowCount(const QModelIndex &parent) const override;
plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+5−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@
2626#include <QDir>
2727#include <QSettings>
2828#include <QStringList>
29+#include <unistd.h>
2930
3031namespace QLightDM
3132{
@@ -37,6 +38,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
3738{
3839 QFileInfo demoFile(QDir::homePath() + "/.unity8-greeter-demo");
3940 QString currentUser = g_get_user_name();
41+ uid_t currentUid = getuid();
4042
4143 if (demoFile.exists()) {
4244 QSettings settings(demoFile.filePath(), QSettings::NativeFormat);
@@ -46,10 +48,10 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
4648 Q_FOREACH(const QString &user, users)
4749 {
4850 QString name = settings.value(user + "/name", user).toString();
49− entries.append({user, name, 0, 0, false, false, 0, 0});
51+ entries.append({user, name, 0, 0, false, false, 0, 0, currentUid++});
5052 }
5153 } else {
52− entries.append({currentUser, 0, 0, 0, false, false, 0, 0});
54+ entries.append({currentUser, 0, 0, 0, false, false, 0, 0, currentUid});
5355
5456 connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged,
5557 this, [this](const QString &user) {
plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.h+2−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -40,6 +40,7 @@ public:
4040 bool has_messages;
4141 QString session;
4242 QString infographic;
43+ uid_t uid;
4344};
4445
4546class UsersModelPrivate : public QObject
po/unity8.pot+20−16
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2016-06-17 01:22+0000\n"
11+"POT-Creation-Date: 2016-06-20 20:25+0000\n"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -35,19 +35,19 @@ msgstr ""
3535msgid "Unpin shortcut"
3636msgstr ""
3737
38−#: qml/Components/DelayedLockscreen.qml:49
38+#: qml/Components/DelayedLockscreen.qml:50
3939msgid "Device Locked"
4040msgstr ""
4141
42−#: qml/Components/DelayedLockscreen.qml:64
42+#: qml/Components/DelayedLockscreen.qml:65
4343msgid "You have been locked out due to too many failed passphrase attempts."
4444msgstr ""
4545
46−#: qml/Components/DelayedLockscreen.qml:65
46+#: qml/Components/DelayedLockscreen.qml:66
4747msgid "You have been locked out due to too many failed passcode attempts."
4848msgstr ""
4949
50−#: qml/Components/DelayedLockscreen.qml:74
50+#: qml/Components/DelayedLockscreen.qml:75
5151#, qt-format
5252msgid "Please wait %1 minute and then try again…"
5353msgid_plural "Please wait %1 minutes and then try again…"
@@ -277,15 +277,15 @@ msgstr ""
277277msgid "Closes the current window."
278278msgstr ""
279279
280−#: qml/Components/Lockscreen.qml:231
280+#: qml/Components/Lockscreen.qml:239
281281msgid "Return to Call"
282282msgstr ""
283283
284−#: qml/Components/Lockscreen.qml:231
284+#: qml/Components/Lockscreen.qml:239
285285msgid "Emergency Call"
286286msgstr ""
287287
288−#: qml/Components/Lockscreen.qml:263
288+#: qml/Components/Lockscreen.qml:271
289289msgid "OK"
290290msgstr ""
291291
@@ -422,10 +422,14 @@ msgid ""
422422"touch pad to interact with the pointer."
423423msgstr ""
424424
425−#: qml/Greeter/CoverPage.qml:119
425+#: qml/Greeter/CoverPage.qml:127
426426msgid "Unlock"
427427msgstr ""
428428
429+#: qml/Greeter/Greeter.qml:536
430+msgid "Try again"
431+msgstr ""
432+
429433#: qml/Greeter/LoginList.qml:259
430434msgid "Retry"
431435msgstr ""
@@ -458,34 +462,34 @@ msgid ""
458462"and all personal data will be deleted."
459463msgstr ""
460464
461−#: qml/Greeter/NarrowView.qml:116
465+#: qml/Greeter/NarrowView.qml:129
462466#, qt-format
463467msgid "Enter %1"
464468msgstr ""
465469
466−#: qml/Greeter/NarrowView.qml:117
470+#: qml/Greeter/NarrowView.qml:130
467471msgid "Enter passphrase"
468472msgstr ""
469473
470−#: qml/Greeter/NarrowView.qml:118
474+#: qml/Greeter/NarrowView.qml:131
471475msgid "Enter passcode"
472476msgstr ""
473477
474−#: qml/Greeter/NarrowView.qml:119
478+#: qml/Greeter/NarrowView.qml:135
475479#, qt-format
476480msgid "Sorry, incorrect %1"
477481msgstr ""
478482
479−#: qml/Greeter/NarrowView.qml:120
483+#: qml/Greeter/NarrowView.qml:136
480484msgid "Sorry, incorrect passphrase"
481485msgstr ""
482486
483−#: qml/Greeter/NarrowView.qml:121
487+#: qml/Greeter/NarrowView.qml:137
484488msgctxt "passphrase"
485489msgid "Please re-enter"
486490msgstr ""
487491
488−#: qml/Greeter/NarrowView.qml:122
492+#: qml/Greeter/NarrowView.qml:138
489493msgid "Sorry, incorrect passcode"
490494msgstr ""
491495
qml/Components/DelayedLockscreen.qml+1
@@ -28,6 +28,7 @@ Item {
2828 signal cancel() // unused
2929
3030 function clear(playAnimation) {}
31+ function showText(text) {}
3132
3233 Column {
3334 anchors.left: parent.left
qml/Components/Lockscreen.qml+8
@@ -91,6 +91,14 @@ Showable {
9191 popup.z = Number.MAX_VALUE
9292 }
9393
94+ function showText(text) {
95+ // Programmatically enters the given text into the lockscreen
96+ if (pinPadLoader.item) {
97+ pinPadLoader.item.showText(text);
98+ pinPadLoader.waiting = true;
99+ }
100+ }
101+
94102 Rectangle {
95103 // In case background fails to load
96104 id: backgroundBackup
qml/Components/PassphraseLockscreen.qml+4
@@ -43,6 +43,10 @@ FocusScope {
4343 }
4444 }
4545
46+ function showText(text) {
47+ pinentryField.text = text;
48+ }
49+
4650 Column {
4751 id: shakeContainer
4852 anchors.horizontalCenter: parent.horizontalCenter
qml/Components/PinLockscreen.qml+4
@@ -46,6 +46,10 @@ FocusScope {
4646 }
4747 }
4848
49+ function showText(text) {
50+ pinentryField.text = text;
51+ }
52+
4953 Keys.onPressed: {
5054 if (pinentryField.text.length == root.maxPinLength)
5155 return;
qml/Greeter/CoverPage.qml+22−1
@@ -41,9 +41,16 @@ Showable {
4141 hide();
4242 }
4343
44+ function showErrorMessage(msg) {
45+ d.errorMessage = msg;
46+ showLabelAnimation.start();
47+ errorMessageAnimation.start();
48+ }
49+
4450 QtObject {
4551 id: d
4652 property bool forceRightOnNextHideAnimation: false
53+ property string errorMessage
4754 }
4855
4956 prepareToHide: function () {
@@ -111,15 +118,18 @@ Showable {
111118
112119 Label {
113120 id: swipeHint
121+ objectName: "swipeHint"
114122 property real baseOpacity: 0.5
115123 opacity: 0.0
116124 anchors.horizontalCenter: parent.horizontalCenter
117125 anchors.bottom: parent.bottom
118126 anchors.bottomMargin: units.gu(5)
119− text: "《 " + i18n.tr("Unlock") + " 》"
127+ text: "《 " + (d.errorMessage ? d.errorMessage : i18n.tr("Unlock")) + " 》"
120128 color: "white"
121129 font.weight: Font.Light
122130
131+ readonly property var opacityAnimation: showLabelAnimation // for testing
132+
123133 SequentialAnimation on opacity {
124134 id: showLabelAnimation
125135 running: false
@@ -136,9 +146,20 @@ Showable {
136146 to: 0.0
137147 duration: UbuntuAnimation.SleepyDuration
138148 }
149+
150+ onRunningChanged: {
151+ if (!running)
152+ d.errorMessage = "";
153+ }
139154 }
140155 }
141156
157+ WrongPasswordAnimation {
158+ id: errorMessageAnimation
159+ objectName: "errorMessageAnimation"
160+ target: swipeHint
161+ }
162+
142163 DragHandle {
143164 id: dragHandle
144165 objectName: "coverPageDragHandle"
qml/Greeter/Greeter.qml+97−4
@@ -16,7 +16,9 @@
1616
1717import QtQuick 2.4
1818import AccountsService 0.1
19+import Biometryd 0.0
1920import GSettings 1.0
21+import Powerd 0.1
2022import Ubuntu.Components 1.3
2123import Ubuntu.SystemImage 0.1
2224import Unity.Launcher 0.1
@@ -37,6 +39,8 @@ Showable {
3739 readonly property bool active: required || hasLockedApp
3840 readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
3941
42+ property bool allowFingerprint: true
43+
4044 // True when the greeter is waiting for PAM or other setup process
4145 readonly property alias waiting: d.waiting
4246
@@ -52,6 +56,7 @@ Showable {
5256 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
5357 property int failedLoginsDelayAttempts: 7 // number of failed logins
5458 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
59+ property int failedFingerprintLoginsDisableAttempts: 3 // number of failed fingerprint logins
5560
5661 readonly property bool animating: loader.item ? loader.item.animating : false
5762
@@ -60,6 +65,9 @@ Showable {
6065 signal emergencyCall()
6166
6267 function forceShow() {
68+ if (!active) {
69+ d.isLockscreen = true;
70+ }
6371 forcedUnlock = false;
6472 if (!required) {
6573 showNow(); // loader.onLoaded will select a user
@@ -119,6 +127,11 @@ Showable {
119127 readonly property bool multiUser: lightDM.users.count > 1
120128 property int currentIndex
121129 property bool waiting
130+ property bool isLockscreen // true when we are locking an active session, rather than first user login
131+ readonly property bool secureFingerprint: isLockscreen &&
132+ AccountsService.failedFingerprintLogins <
133+ root.failedFingerprintLoginsDisableAttempts
134+ readonly property bool alphanumeric: AccountsService.passwordDisplayHint === AccountsService.Keyboard
122135
123136 // We want 'launcherOffset' to animate down to zero. But not to animate
124137 // while being dragged. So ideally we change this only when the user
@@ -151,7 +164,7 @@ Showable {
151164 if (lightDM.greeter.startSessionSync()) {
152165 sessionStarted();
153166 if (loader.item) {
154− loader.item.notifyAuthenticationSucceeded();
167+ loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
155168 }
156169 } else if (loader.item) {
157170 loader.item.notifyAuthenticationFailed();
@@ -170,7 +183,7 @@ Showable {
170183 function checkForcedUnlock(hideNow) {
171184 if (forcedUnlock && shown && loader.item) {
172185 // pretend we were just authenticated
173− loader.item.notifyAuthenticationSucceeded();
186+ loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
174187 loader.item.hide();
175188 if (hideNow) {
176189 root.hideNow(); // skip hide animation
@@ -188,6 +201,17 @@ Showable {
188201 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
189202 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
190203
204+ onLockedChanged: {
205+ if (!locked) {
206+ AccountsService.failedLogins = 0;
207+ AccountsService.failedFingerprintLogins = 0;
208+
209+ // Stop delay timer if they logged in with fingerprint
210+ forcedDelayTimer.stop();
211+ forcedDelayTimer.delayMinutes = 0;
212+ }
213+ }
214+
191215 onRequiredChanged: {
192216 if (required) {
193217 d.waiting = true;
@@ -349,7 +373,7 @@ Showable {
349373 Binding {
350374 target: loader.item
351375 property: "alphanumeric"
352− value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
376+ value: d.alphanumeric
353377 }
354378
355379 Binding {
@@ -408,7 +432,6 @@ Showable {
408432 d.waiting = false;
409433
410434 if (lightDM.greeter.authenticated) {
411− AccountsService.failedLogins = 0;
412435 d.login();
413436 if (!lightDM.greeter.promptless) {
414437 loader.item.hide();
@@ -474,4 +497,74 @@ Showable {
474497 target: i18n
475498 onLanguageChanged: lightDM.infographic.readyForDataChange()
476499 }
500+
501+ Observer {
502+ id: biometryd
503+ objectName: "biometryd"
504+
505+ property var operation: null
506+ readonly property bool idEnabled: root.active &&
507+ root.allowFingerprint &&
508+ Powerd.status === Powerd.On &&
509+ Biometryd.available &&
510+ AccountsService.enableFingerprintIdentification
511+
512+ function cancelOperation() {
513+ if (operation) {
514+ operation.cancel();
515+ operation = null;
516+ }
517+ }
518+
519+ function restartOperation() {
520+ cancelOperation();
521+
522+ if (idEnabled) {
523+ var identifier = Biometryd.defaultDevice.identifier;
524+ operation = identifier.identifyUser();
525+ operation.start(biometryd);
526+ }
527+ }
528+
529+ function failOperation(reason) {
530+ console.log("Failed to identify user by fingerprint:", reason);
531+ restartOperation();
532+ if (!d.secureFingerprint) {
533+ d.startUnlock(false /* toTheRight */); // use normal login instead
534+ }
535+ if (loader.item) {
536+ var msg = d.secureFingerprint ? i18n.tr("Try again") : "";
537+ loader.item.showErrorMessage(msg);
538+ }
539+ }
540+
541+ Component.onCompleted: restartOperation()
542+ Component.onDestruction: cancelOperation()
543+ onIdEnabledChanged: restartOperation()
544+
545+ onSucceeded: {
546+ if (!d.secureFingerprint) {
547+ failOperation("fingerprint reader is locked");
548+ return;
549+ }
550+ if (result !== lightDM.users.data(d.currentIndex, lightDM.userRoles.UidRole)) {
551+ AccountsService.failedFingerprintLogins++;
552+ failOperation("not the selected user");
553+ return;
554+ }
555+ console.log("Identified user by fingerprint:", result);
556+ if (loader.item)
557+ loader.item.notifyAuthenticationSucceeded(true /* showFakePassword */);
558+ if (root.active)
559+ root.forcedUnlock = true;
560+ }
561+ onFailed: {
562+ if (!d.secureFingerprint) {
563+ failOperation("fingerprint reader is locked");
564+ } else {
565+ AccountsService.failedFingerprintLogins++;
566+ failOperation(reason);
567+ }
568+ }
569+ }
477570}
qml/Greeter/NarrowView.qml+18−2
@@ -65,15 +65,28 @@ FocusScope {
6565 coverPage.hide();
6666 }
6767
68− function notifyAuthenticationSucceeded() {
68+ function notifyAuthenticationSucceeded(showFakePassword) {
69+ // When using an alternate log in mechanism like fingerprints, the
70+ // design is it looks like the user entered a passcode.
71+ if (!alphanumeric && showFakePassword) {
72+ lockscreen.showText("...."); // actual text doesn't matter, we show bullets
73+ }
6974 lockscreen.hide();
7075 }
7176
7277 function notifyAuthenticationFailed() {
78+ lockscreen.customError = "";
79+ lockscreen.clear(true);
80+ }
81+
82+ function showErrorMessage(msg) {
83+ coverPage.showErrorMessage(msg);
84+ lockscreen.customError = msg ? msg : " "; // avoid default message
7385 lockscreen.clear(true);
7486 }
7587
7688 function reset() {
89+ lockscreen.customError = "";
7790 coverPage.show();
7891 }
7992
@@ -116,7 +129,10 @@ FocusScope {
116129 infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
117130 alphaNumeric ? i18n.tr("Enter passphrase") :
118131 i18n.tr("Enter passcode")
119− errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
132+
133+ property string customError
134+ errorText: customError !== "" ? customError :
135+ promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
120136 alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
121137 i18n.ctr("passphrase", "Please re-enter") :
122138 i18n.tr("Sorry, incorrect passcode")
qml/Greeter/WideView.qml+5−1
@@ -58,7 +58,7 @@ FocusScope {
5858 coverPage.hide();
5959 }
6060
61− function notifyAuthenticationSucceeded() {
61+ function notifyAuthenticationSucceeded(showFakePassword) {
6262 // Nothing needed
6363 }
6464
@@ -66,6 +66,10 @@ FocusScope {
6666 loginList.showError();
6767 }
6868
69+ function showErrorMessage(msg) {
70+ coverPage.showErrorMessage(msg);
71+ }
72+
6973 function reset() {
7074 loginList.reset();
7175 }
qml/Shell.qml+3
@@ -422,6 +422,9 @@ StyledItem {
422422 launcherOffset: launcher.progress
423423 forcedUnlock: wizard.active || shell.mode === "full-shell"
424424 background: wallpaperResolver.background
425+ allowFingerprint: !dialogs.hasActiveDialog &&
426+ !notifications.topmostIsFullscreen &&
427+ !panel.indicators.shown
425428
426429 // avoid overlapping with Launcher's edge drag area
427430 // FIXME: Fix TouchRegistry & friends and remove this workaround
tests/mocks/AccountsService/AccountsService.cpp+24
@@ -20,11 +20,13 @@
2020
2121AccountsService::AccountsService(QObject* parent)
2222 : QObject(parent),
23+ m_enableFingerprintIdentification(true),
2324 m_enableLauncherWhileLocked(true),
2425 m_enableIndicatorsWhileLocked(true),
2526 m_backgroundFile(qmlDirectory() + "/graphics/phone_background.jpg"),
2627 m_statsWelcomeScreen(true),
2728 m_failedLogins(0),
29+ m_failedFingerprintLogins(0),
2830 m_demoEdges(false),
2931 m_demoEdgesCompleted(),
3032 m_hereEnabled(false),
@@ -74,6 +76,17 @@ void AccountsService::setDemoEdgesCompleted(const QStringList &demoEdgesComplete
7476 Q_EMIT demoEdgesCompletedChanged();
7577}
7678
79+bool AccountsService::enableFingerprintIdentification() const
80+{
81+ return m_enableFingerprintIdentification;
82+}
83+
84+void AccountsService::setEnableFingerprintIdentification(bool enableFingerprintIdentification)
85+{
86+ m_enableFingerprintIdentification = enableFingerprintIdentification;
87+ Q_EMIT enableFingerprintIdentificationChanged();
88+}
89+
7790bool AccountsService::enableLauncherWhileLocked() const
7891{
7992 return m_enableLauncherWhileLocked;
@@ -137,6 +150,17 @@ void AccountsService::setFailedLogins(uint failedLogins)
137150 failedLoginsChanged();
138151}
139152
153+uint AccountsService::failedFingerprintLogins() const
154+{
155+ return m_failedFingerprintLogins;
156+}
157+
158+void AccountsService::setFailedFingerprintLogins(uint failedFingerprintLogins)
159+{
160+ m_failedFingerprintLogins = failedFingerprintLogins;
161+ failedFingerprintLoginsChanged();
162+}
163+
140164bool AccountsService::hereEnabled() const
141165{
142166 return m_hereEnabled;
tests/mocks/AccountsService/AccountsService.h+16
@@ -38,6 +38,10 @@ class AccountsService: public QObject
3838 READ demoEdgesCompleted
3939 WRITE setDemoEdgesCompleted // only available in mock
4040 NOTIFY demoEdgesCompletedChanged)
41+ Q_PROPERTY (bool enableFingerprintIdentification
42+ READ enableFingerprintIdentification
43+ WRITE setEnableFingerprintIdentification // only available in mock
44+ NOTIFY enableFingerprintIdentificationChanged)
4145 Q_PROPERTY (bool enableLauncherWhileLocked
4246 READ enableLauncherWhileLocked
4347 WRITE setEnableLauncherWhileLocked // only available in mock
@@ -61,6 +65,10 @@ class AccountsService: public QObject
6165 READ failedLogins
6266 WRITE setFailedLogins
6367 NOTIFY failedLoginsChanged)
68+ Q_PROPERTY (uint failedFingerprintLogins
69+ READ failedFingerprintLogins
70+ WRITE setFailedFingerprintLogins
71+ NOTIFY failedFingerprintLoginsChanged)
6472 Q_PROPERTY(bool hereEnabled
6573 READ hereEnabled
6674 WRITE setHereEnabled
@@ -94,6 +102,8 @@ public:
94102 QStringList demoEdgesCompleted() const;
95103 void setDemoEdgesCompleted(const QStringList &demoEdges);
96104 Q_INVOKABLE void markDemoEdgeCompleted(const QString &edge);
105+ bool enableFingerprintIdentification() const;
106+ void setEnableFingerprintIdentification(bool enableFingerprintIdentification);
97107 bool enableLauncherWhileLocked() const;
98108 void setEnableLauncherWhileLocked(bool enableLauncherWhileLocked);
99109 bool enableIndicatorsWhileLocked() const;
@@ -105,6 +115,8 @@ public:
105115 PasswordDisplayHint passwordDisplayHint() const;
106116 uint failedLogins() const;
107117 void setFailedLogins(uint failedLogins);
118+ uint failedFingerprintLogins() const;
119+ void setFailedFingerprintLogins(uint failedFingerprintLogins);
108120 bool hereEnabled() const;
109121 void setHereEnabled(bool enabled);
110122 QString hereLicensePath() const;
@@ -121,12 +133,14 @@ Q_SIGNALS:
121133 void userChanged();
122134 void demoEdgesChanged();
123135 void demoEdgesCompletedChanged();
136+ void enableFingerprintIdentificationChanged();
124137 void enableLauncherWhileLockedChanged();
125138 void enableIndicatorsWhileLockedChanged();
126139 void backgroundFileChanged();
127140 void statsWelcomeScreenChanged();
128141 void passwordDisplayHintChanged();
129142 void failedLoginsChanged();
143+ void failedFingerprintLoginsChanged();
130144 void hereEnabledChanged();
131145 void hereLicensePathChanged();
132146 void realNameChanged();
@@ -134,12 +148,14 @@ Q_SIGNALS:
134148 void keymapsChanged();
135149
136150private:
151+ bool m_enableFingerprintIdentification;
137152 bool m_enableLauncherWhileLocked;
138153 bool m_enableIndicatorsWhileLocked;
139154 QString m_backgroundFile;
140155 QString m_user;
141156 bool m_statsWelcomeScreen;
142157 uint m_failedLogins;
158+ uint m_failedFingerprintLogins;
143159 bool m_demoEdges;
144160 QStringList m_demoEdgesCompleted;
145161 bool m_hereEnabled;
tests/mocks/Biometryd/CMakeLists.txtnew file+14
@@ -0,0 +1,14 @@
1+set(MOCK_BIOMETRYD_SOURCES
2+ plugin.cpp
3+ MockBiometryd.cpp
4+ MockDevice.cpp
5+ MockIdentifier.cpp
6+ MockObserver.cpp
7+ MockOperation.cpp
8+)
9+
10+add_library(MockBiometryd MODULE ${MOCK_BIOMETRYD_SOURCES})
11+
12+qt5_use_modules(MockBiometryd Qml Quick Core)
13+
14+add_unity8_mock(Biometryd 0.0 Biometryd TARGETS MockBiometryd)
tests/mocks/Biometryd/MockBiometryd.cppnew file+43
@@ -0,0 +1,43 @@
1+/*
2+ * Copyright (C) 2016 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+#include "MockBiometryd.h"
18+#include "MockDevice.h"
19+
20+MockBiometryd::MockBiometryd(QObject *parent)
21+ : QObject(parent)
22+ , m_device(new MockDevice(this))
23+ , m_available(true)
24+{
25+}
26+
27+MockDevice *MockBiometryd::defaultDevice() const
28+{
29+ return m_device;
30+}
31+
32+bool MockBiometryd::available() const
33+{
34+ return m_available;
35+}
36+
37+void MockBiometryd::setAvailable(bool available)
38+{
39+ if (m_available != available) {
40+ m_available = available;
41+ Q_EMIT availableChanged();
42+ }
43+}
tests/mocks/Biometryd/MockBiometryd.hnew file+50
@@ -0,0 +1,50 @@
1+/*
2+ * Copyright (C) 2016 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+#ifndef MOCK_BIOMETRYD_H
18+#define MOCK_BIOMETRYD_H
19+
20+#include <QObject>
21+
22+#include "MockDevice.h"
23+
24+class MockBiometryd : public QObject
25+{
26+ Q_OBJECT
27+ Q_DISABLE_COPY(MockBiometryd)
28+ Q_PROPERTY(MockDevice *defaultDevice READ defaultDevice CONSTANT)
29+ Q_PROPERTY(bool available
30+ READ available
31+ WRITE setAvailable // only in mock
32+ NOTIFY availableChanged)
33+
34+public:
35+ explicit MockBiometryd(QObject *parent = 0);
36+
37+ MockDevice *defaultDevice() const;
38+
39+ bool available() const;
40+ void setAvailable(bool available);
41+
42+Q_SIGNALS:
43+ void availableChanged();
44+
45+private:
46+ MockDevice *m_device;
47+ bool m_available;
48+};
49+
50+#endif // MOCK_BIOMETRYD_H
tests/mocks/Biometryd/MockDevice.cppnew file+29
@@ -0,0 +1,29 @@
1+/*
2+ * Copyright (C) 2016 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+#include "MockDevice.h"
18+#include "MockIdentifier.h"
19+
20+MockDevice::MockDevice(QObject *parent)
21+ : QObject(parent)
22+ , m_identifier(new MockIdentifier(this))
23+{
24+}
25+
26+MockIdentifier *MockDevice::identifier() const
27+{
28+ return m_identifier;
29+}
tests/mocks/Biometryd/MockDevice.hnew file+40
@@ -0,0 +1,40 @@
1+/*
2+ * Copyright (C) 2016 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+#ifndef MOCK_DEVICE_H
18+#define MOCK_DEVICE_H
19+
20+#include <QObject>
21+
22+#include "MockIdentifier.h"
23+
24+class MockDevice : public QObject
25+{
26+ Q_OBJECT
27+ Q_PROPERTY(MockIdentifier *identifier READ identifier CONSTANT)
28+
29+public:
30+ explicit MockDevice(QObject *parent = 0);
31+
32+ MockIdentifier *identifier() const;
33+
34+private:
35+ MockIdentifier *m_identifier;
36+};
37+
38+Q_DECLARE_METATYPE(MockDevice*)
39+
40+#endif // MOCK_DEVICE_H
tests/mocks/Biometryd/MockIdentifier.cppnew file+28
@@ -0,0 +1,28 @@
1+/*
2+ * Copyright (C) 2016 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+#include "MockIdentifier.h"
18+#include "MockOperation.h"
19+
20+MockIdentifier::MockIdentifier(QObject *parent)
21+ : QObject(parent)
22+{
23+}
24+
25+MockOperation *MockIdentifier::identifyUser()
26+{
27+ return new MockOperation(this);
28+}
tests/mocks/Biometryd/MockIdentifier.hnew file+36
@@ -0,0 +1,36 @@
1+/*
2+ * Copyright (C) 2016 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+#ifndef MOCK_IDENTIFIER_H
18+#define MOCK_IDENTIFIER_H
19+
20+#include <QObject>
21+
22+#include "MockOperation.h"
23+
24+class MockIdentifier : public QObject
25+{
26+ Q_OBJECT
27+
28+public:
29+ explicit MockIdentifier(QObject *parent = 0);
30+
31+ Q_INVOKABLE MockOperation *identifyUser();
32+};
33+
34+Q_DECLARE_METATYPE(MockIdentifier*)
35+
36+#endif // MOCK_IDENTIFIER_H
tests/mocks/Biometryd/MockObserver.cppnew file+22
@@ -0,0 +1,22 @@
1+/*
2+ * Copyright (C) 2016 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+#include "MockObserver.h"
18+
19+MockObserver::MockObserver(QObject *parent)
20+ : QObject(parent)
21+{
22+}
tests/mocks/Biometryd/MockObserver.hnew file+37
@@ -0,0 +1,37 @@
1+/*
2+ * Copyright (C) 2016 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+#ifndef MOCK_OBSERVER_H
18+#define MOCK_OBSERVER_H
19+
20+#include <QObject>
21+#include <QString>
22+#include <QVariant>
23+
24+class MockObserver : public QObject
25+{
26+ Q_OBJECT
27+ Q_DISABLE_COPY(MockObserver)
28+
29+public:
30+ explicit MockObserver(QObject *parent = 0);
31+
32+Q_SIGNALS:
33+ void succeeded(const QVariant &result);
34+ void failed(const QString &reason);
35+};
36+
37+#endif // MOCK_OBSERVER_H
tests/mocks/Biometryd/MockOperation.cppnew file+56
@@ -0,0 +1,56 @@
1+/*
2+ * Copyright (C) 2016 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+#include <QString>
18+#include <QVariant>
19+
20+#include "MockOperation.h"
21+
22+MockOperation::MockOperation(QObject *parent)
23+ : QObject(parent)
24+ , m_observer(nullptr)
25+{
26+}
27+
28+bool MockOperation::running() const
29+{
30+ return m_observer != nullptr;
31+}
32+
33+void MockOperation::start(QObject *observer)
34+{
35+ if (!m_observer)
36+ m_observer = observer;
37+}
38+
39+void MockOperation::cancel()
40+{
41+ m_observer = nullptr;
42+}
43+
44+void MockOperation::mockSuccess(unsigned int uid)
45+{
46+ if (m_observer)
47+ QMetaObject::invokeMethod(m_observer, "succeeded", Qt::DirectConnection,
48+ Q_ARG(QVariant, QVariant::fromValue(uid)));
49+}
50+
51+void MockOperation::mockFailure(const QString &reason)
52+{
53+ if (m_observer)
54+ QMetaObject::invokeMethod(m_observer, "failed", Qt::DirectConnection,
55+ Q_ARG(QString, reason));
56+}
tests/mocks/Biometryd/MockOperation.hnew file+48
@@ -0,0 +1,48 @@
1+/*
2+ * Copyright (C) 2016 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+#ifndef MOCK_OPERATION_H
18+#define MOCK_OPERATION_H
19+
20+#include <QObject>
21+
22+class MockOperation : public QObject
23+{
24+ Q_OBJECT
25+ Q_PROPERTY(bool running READ running NOTIFY runningChanged) // only in mock
26+
27+public:
28+ explicit MockOperation(QObject *parent = 0);
29+
30+ Q_INVOKABLE void start(QObject *observer);
31+ Q_INVOKABLE void cancel();
32+
33+ Q_INVOKABLE void mockSuccess(unsigned int uid); // only in mock
34+ Q_INVOKABLE void mockFailure(const QString &reason); // only in mock
35+
36+ bool running() const; // only in mock
37+
38+Q_SIGNALS:
39+ void runningChanged();
40+
41+private:
42+ QObject *m_observer;
43+ bool m_running;
44+};
45+
46+Q_DECLARE_METATYPE(MockOperation*)
47+
48+#endif // MOCK_OPERATION_H
tests/mocks/Biometryd/plugin.cppnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 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+#include "plugin.h"
18+#include "MockBiometryd.h"
19+#include "MockDevice.h"
20+#include "MockIdentifier.h"
21+#include "MockObserver.h"
22+#include "MockOperation.h"
23+
24+#include <QtQml>
25+
26+static QObject *biometryd_provider(QQmlEngine *, QJSEngine *)
27+{
28+ return new MockBiometryd();
29+}
30+
31+void BackendPlugin::registerTypes(const char *uri)
32+{
33+ Q_ASSERT(uri == QLatin1String("Biometryd"));
34+
35+ qmlRegisterType<MockObserver>(uri, 0, 0, "Observer");
36+ qmlRegisterSingletonType<MockBiometryd>(uri, 0, 0, "Biometryd", biometryd_provider);
37+
38+ qRegisterMetaType<MockDevice*>();
39+ qRegisterMetaType<MockIdentifier*>();
40+ qRegisterMetaType<MockOperation*>();
41+}
tests/mocks/Biometryd/plugin.hnew file+31
@@ -0,0 +1,31 @@
1+/*
2+ * Copyright (C) 2016 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+#ifndef MOCK_BIOMETRYD_PLUGIN_H
18+#define MOCK_BIOMETRYD_PLUGIN_H
19+
20+#include <QQmlExtensionPlugin>
21+
22+class BackendPlugin : public QQmlExtensionPlugin
23+{
24+ Q_OBJECT
25+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
26+
27+public:
28+ void registerTypes(const char *uri) override;
29+};
30+
31+#endif // MOCK_BIOMETRYD_PLUGIN_H
tests/mocks/Biometryd/qmldirnew file+3
@@ -0,0 +1,3 @@
1+module Biometryd
2+plugin MockBiometryd
3+typeinfo Biometryd.qmltypes
tests/mocks/CMakeLists.txt+1
@@ -29,6 +29,7 @@ macro(add_unity8_mock PLUGIN VERSION PATH)
2929endmacro()
3030
3131add_subdirectory(AccountsService)
32+add_subdirectory(Biometryd)
3233add_subdirectory(Cursor)
3334add_subdirectory(GSettings.1.0)
3435add_subdirectory(indicator-service)
tests/mocks/IntegratedLightDM/liblightdm/UsersModel.cpp+4−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -46,6 +46,7 @@ UsersModel::UsersModel(QObject *parent) :
4646 roles[SessionRole] = "session";
4747 roles[HasMessagesRole] = "hasMessages";
4848 roles[ImagePathRole] = "imagePath";
49+ roles[UidRole] = "uid";
4950 setRoleNames(roles);
5051}
5152
@@ -95,6 +96,8 @@ QVariant UsersModel::data(const QModelIndex &index, int role) const
9596 return d->entries[row].has_messages;
9697 case UsersModel::ImagePathRole:
9798 return "";
99+ case UsersModel::UidRole:
100+ return d->entries[row].uid;
98101 default:
99102 return QVariant();
100103 }
tests/mocks/IntegratedLightDM/liblightdm/UsersModel.h+2−1
@@ -50,7 +50,8 @@ public:
5050 SessionRole,
5151 HasMessagesRole,
5252 ImagePathRole,
53− BackgroundPathRole
53+ BackgroundPathRole,
54+ UidRole
5455 };
5556
5657 int rowCount(const QModelIndex &parent) const override;
tests/mocks/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+29−23
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -47,6 +47,12 @@ void UsersModelPrivate::resetEntries()
4747 resetEntries_full();
4848 }
4949
50+ // Assign uids in a loop, just to avoid having to muck with them when
51+ // adding or removing test users.
52+ for (int i = 0; i < entries.size(); i++) {
53+ entries[i].uid = i + 1;
54+ }
55+
5056 q->endResetModel();
5157}
5258
@@ -54,7 +60,7 @@ void UsersModelPrivate::resetEntries_single()
5460{
5561 entries =
5662 {
57− { "single", "Single User", 0, 0, false, false, 0, 0 },
63+ { "single", "Single User", 0, 0, false, false, 0, 0, 0 },
5864 };
5965}
6066
@@ -62,7 +68,7 @@ void UsersModelPrivate::resetEntries_singlePassphrase()
6268{
6369 entries =
6470 {
65− { "single", "Single User", 0, 0, false, false, 0, 0 },
71+ { "single", "Single User", 0, 0, false, false, 0, 0, 0 },
6672 };
6773}
6874
@@ -70,7 +76,7 @@ void UsersModelPrivate::resetEntries_singlePin()
7076{
7177 entries =
7278 {
73− { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
79+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0, 0 },
7480 };
7581}
7682
@@ -78,26 +84,26 @@ void UsersModelPrivate::resetEntries_full()
7884{
7985 entries =
8086 {
81− { "has-password", "Has Password", 0, 0, false, false, 0, 0 },
82− { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
83− { "different-prompt", "Different Prompt", 0, 0, false, false, 0, 0 },
84− { "no-password", "No Password", 0, 0, false, false, 0, 0 },
85− { "auth-error", "Auth Error", 0, 0, false, false, 0, 0 },
86− { "two-factor", "Two Factor", 0, 0, false, false, 0, 0 },
87− { "info-prompt", "Info Prompt", 0, 0, false, false, 0, 0 },
88− { "html-info-prompt", "HTML Info Prompt", 0, 0, false, false, 0, 0 },
89− { "long-info-prompt", "Long Info Prompt", 0, 0, false, false, 0, 0 },
90− { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, 0, 0 },
91− { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, 0, 0 },
92− { "long-name", "Long name (far far too long to fit, seriously this would never fit on the screen, you will never see this part of the name)", 0, 0, false, false, 0, 0 },
93− { "color-background", "Color Background", "#dd4814", 0, false, false, 0, 0 },
87+ { "has-password", "Has Password", 0, 0, false, false, 0, 0, 0 },
88+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0, 0 },
89+ { "different-prompt", "Different Prompt", 0, 0, false, false, 0, 0, 0 },
90+ { "no-password", "No Password", 0, 0, false, false, 0, 0, 0 },
91+ { "auth-error", "Auth Error", 0, 0, false, false, 0, 0, 0 },
92+ { "two-factor", "Two Factor", 0, 0, false, false, 0, 0, 0 },
93+ { "info-prompt", "Info Prompt", 0, 0, false, false, 0, 0, 0 },
94+ { "html-info-prompt", "HTML Info Prompt", 0, 0, false, false, 0, 0, 0 },
95+ { "long-info-prompt", "Long Info Prompt", 0, 0, false, false, 0, 0, 0 },
96+ { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, 0, 0, 0 },
97+ { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, 0, 0, 0 },
98+ { "long-name", "Long name (far far too long to fit, seriously this would never fit on the screen, you will never see this part of the name)", 0, 0, false, false, 0, 0, 0 },
99+ { "color-background", "Color Background", "#dd4814", 0, false, false, 0, 0, 0 },
94100 // white and black are a bit redundant, but useful for manually testing if UI is still readable
95− { "white-background", "White Background", "#ffffff", 0, false, false, 0, 0 },
96− { "black-background", "Black Background", "#000000", 0, false, false, 0, 0 },
97− { "no-background", "No Background", "", 0, false, false, 0, 0 },
98− { "unicode", "가나다라마", 0, 0, false, false, 0, 0 },
99− { "no-response", "No Response", 0, 0, false, false, 0, 0 },
100− { "empty-name", "", 0, 0, false, false, 0, 0 },
101+ { "white-background", "White Background", "#ffffff", 0, false, false, 0, 0, 0 },
102+ { "black-background", "Black Background", "#000000", 0, false, false, 0, 0, 0 },
103+ { "no-background", "No Background", "", 0, false, false, 0, 0, 0 },
104+ { "unicode", "가나다라마", 0, 0, false, false, 0, 0, 0 },
105+ { "no-response", "No Response", 0, 0, false, false, 0, 0, 0 },
106+ { "empty-name", "", 0, 0, false, false, 0, 0, 0 },
101107 };
102108}
103109
tests/mocks/IntegratedLightDM/liblightdm/UsersModelPrivate.h+2−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -35,6 +35,7 @@ public:
3535 bool has_messages;
3636 QString session;
3737 QString infographic;
38+ uid_t uid;
3839};
3940
4041class UsersModelPrivate
tests/qmltests/Components/tst_Lockscreen.qml+20−1
@@ -173,6 +173,7 @@ Rectangle {
173173 function cleanup() {
174174 lockscreen.clear(false);
175175 delayMinutesTextField.text = "0"
176+ enteredLabel.text = "";
176177
177178 // Reset sizing
178179 root.width = units.gu(80)
@@ -247,7 +248,6 @@ Rectangle {
247248 }
248249
249250 function test_unlock(data) {
250− enteredLabel.text = ""
251251 minPinLengthTextField.text = data.minPinLength
252252 maxPinLengthTextField.text = data.maxPinLength
253253 pinPadCheckBox.checked = data.alphanumeric
@@ -462,6 +462,25 @@ Rectangle {
462462 compare(label.text, "Device Locked")
463463 }
464464
465+ function test_showText_data() {
466+ return [
467+ { tag: "alphanumeric", alphanumeric: true },
468+ { tag: "pinPad", alphanumeric: false },
469+ ]
470+ }
471+
472+ function test_showText(data) {
473+ pinPadCheckBox.checked = data.alphanumeric;
474+ waitForLockscreenReady();
475+
476+ lockscreen.showText("test");
477+
478+ var pinPadLoader = findChild(lockscreen, "pinPadLoader");
479+ verify(pinPadLoader.waiting);
480+ compare(enteredLabel.text, ""); // no entered signal should occur
481+ compare(lockscreen.passphrase, "test");
482+ }
483+
465484 function test_resize_data() {
466485 return [
467486 { tag: "small", width: units.gu(40), height: units.gu(76) },
tests/qmltests/Greeter/TestView.qml+10−3
@@ -33,9 +33,11 @@ Item {
3333 property var infographicModel
3434 readonly property bool fullyShown: _fullyShown
3535 readonly property bool required: _required
36+ readonly property bool animating: _animating
3637
3738 property bool _fullyShown: true
3839 property bool _required: true
40+ property bool _animating: true
3941
4042 signal selected(int index)
4143 signal responded(string response)
@@ -46,8 +48,9 @@ Item {
4648 signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
4749 signal _showLastChanceCalled()
4850 signal _hideCalled()
49− signal _notifyAuthenticationSucceededCalled()
51+ signal _notifyAuthenticationSucceededCalled(bool showFakePassword)
5052 signal _notifyAuthenticationFailedCalled()
53+ signal _showErrorMessageCalled(string msg)
5154 signal _resetCalled()
5255 signal _tryToUnlockCalled(bool toTheRight)
5356
@@ -69,14 +72,18 @@ Item {
6972 _fullyShown = false;
7073 }
7174
72− function notifyAuthenticationSucceeded() {
73− _notifyAuthenticationSucceededCalled();
75+ function notifyAuthenticationSucceeded(showFakePassword) {
76+ _notifyAuthenticationSucceededCalled(showFakePassword);
7477 }
7578
7679 function notifyAuthenticationFailed() {
7780 _notifyAuthenticationFailedCalled();
7881 }
7982
83+ function showErrorMessage(msg) {
84+ _showErrorMessageCalled(msg);
85+ }
86+
8087 function reset() {
8188 _resetCalled();
8289 }
tests/qmltests/Greeter/tst_Greeter.qml+171
@@ -20,6 +20,7 @@ import ".."
2020import "../../../qml/Greeter"
2121import Ubuntu.Components 1.3
2222import AccountsService 0.1
23+import Biometryd 0.0
2324import GSettings 1.0
2425import IntegratedLightDM 0.1 as LightDM
2526import Unity.Test 0.1 as UT
@@ -125,6 +126,12 @@ Item {
125126 signalName: "_notifyAuthenticationFailedCalled"
126127 }
127128
129+ SignalSpy {
130+ id: viewShowErrorMessageSpy
131+ target: testCase.view
132+ signalName: "_showErrorMessageCalled"
133+ }
134+
128135 SignalSpy {
129136 id: viewResetSpy
130137 target: testCase.view
@@ -143,12 +150,16 @@ Item {
143150 teaseSpy.clear();
144151 sessionStartedSpy.clear();
145152 activeChangedSpy.clear();
153+ Biometryd.available = true;
154+ AccountsService.enableFingerprintIdentification = true;
155+ AccountsService.failedFingerprintLogins = 0;
146156 viewShowMessageSpy.clear();
147157 viewShowPromptSpy.clear();
148158 viewShowLastChanceSpy.clear();
149159 viewHideSpy.clear();
150160 viewAuthenticationSucceededSpy.clear();
151161 viewAuthenticationFailedSpy.clear();
162+ viewShowErrorMessageSpy.clear();
152163 viewResetSpy.clear();
153164 viewTryToUnlockSpy.clear();
154165 tryCompare(greeter, "waiting", false);
@@ -200,6 +211,14 @@ Item {
200211 compare(greeter.shown, false);
201212 }
202213
214+ function unlockAndShowGreeter() {
215+ // useful to enable "lockscreen mode" in greeter
216+ greeter.forcedUnlock = true;
217+ LightDM.Greeter.showGreeter();
218+ tryCompare(greeter, "waiting", false);
219+ view = findChild(greeter, "testView");
220+ }
221+
203222 function test_unlockPass() {
204223 selectUser("has-password");
205224 tryCompare(viewShowPromptSpy, "count", 1);
@@ -553,5 +572,157 @@ Item {
553572 compare(viewResetSpy.count, 1);
554573 tryCompare(viewShowPromptSpy, "count", 1);
555574 }
575+
576+ function test_fingerprintSuccess() {
577+ var index = selectUser("has-password");
578+ unlockAndShowGreeter(); // turn on lockscreen mode
579+
580+ var biometryd = findInvisibleChild(greeter, "biometryd");
581+ verify(biometryd.operation);
582+ verify(biometryd.operation.running);
583+
584+ biometryd.operation.mockSuccess(LightDM.Users.data(index, LightDM.UserRoles.UidRole));
585+ verify(!greeter.active);
586+ }
587+
588+ function test_fingerprintFirstLoginDisabled() {
589+ var index = selectUser("has-password");
590+ // don't hide/show greeter, we want to test behavior before lockscreen mode is on
591+
592+ var biometryd = findInvisibleChild(greeter, "biometryd");
593+ verify(biometryd.operation);
594+ verify(biometryd.operation.running);
595+
596+ biometryd.operation.mockSuccess(LightDM.Users.data(index, LightDM.UserRoles.UidRole));
597+ compare(viewTryToUnlockSpy.count, 1);
598+ verify(greeter.locked);
599+ }
600+
601+ function test_forcedDisallowedFingerprint() {
602+ var index = selectUser("has-password");
603+ unlockAndShowGreeter(); // turn on lockscreen mode
604+
605+ var biometryd = findInvisibleChild(greeter, "biometryd");
606+ verify(biometryd.operation);
607+ verify(biometryd.operation.running);
608+
609+ greeter.allowFingerprint = false;
610+ verify(!biometryd.operation);
611+ }
612+
613+ function test_fingerprintFailureMessage() {
614+ var index = selectUser("has-password");
615+ unlockAndShowGreeter(); // turn on lockscreen mode
616+
617+ var biometryd = findInvisibleChild(greeter, "biometryd");
618+ verify(biometryd.operation);
619+ verify(biometryd.operation.running);
620+
621+ biometryd.operation.mockFailure("error");
622+ compare(viewShowErrorMessageSpy.count, 1);
623+ compare(viewShowErrorMessageSpy.signalArguments[0][0], i18n.tr("Try again"));
624+ }
625+
626+ function test_fingerprintTooManyFailures() {
627+ var index = selectUser("has-password");
628+ unlockAndShowGreeter(); // turn on lockscreen mode
629+
630+ var biometryd = findInvisibleChild(greeter, "biometryd");
631+ biometryd.operation.mockFailure("error");
632+ biometryd.operation.mockFailure("error");
633+ compare(viewTryToUnlockSpy.count, 0);
634+
635+ biometryd.operation.mockFailure("error");
636+ compare(viewTryToUnlockSpy.count, 1);
637+
638+ // Confirm that we are stuck in this mode until next login
639+ biometryd.operation.mockSuccess(LightDM.Users.data(index, LightDM.UserRoles.UidRole));
640+ compare(viewTryToUnlockSpy.count, 2);
641+
642+ unlockAndShowGreeter();
643+
644+ biometryd.operation.mockSuccess(LightDM.Users.data(index, LightDM.UserRoles.UidRole));
645+ verify(!greeter.active);
646+ }
647+
648+ function test_fingerprintFailureCountReset() {
649+ selectUser("has-password");
650+ unlockAndShowGreeter(); // turn on lockscreen mode
651+
652+ var biometryd = findInvisibleChild(greeter, "biometryd");
653+ biometryd.operation.mockFailure("error");
654+ biometryd.operation.mockFailure("error");
655+ compare(viewTryToUnlockSpy.count, 0);
656+
657+ unlockAndShowGreeter();
658+ biometryd.operation.mockFailure("error");
659+ biometryd.operation.mockFailure("error");
660+ compare(viewTryToUnlockSpy.count, 0);
661+
662+ biometryd.operation.mockFailure("error");
663+ compare(viewTryToUnlockSpy.count, 1);
664+ }
665+
666+ function test_fingerprintWrongUid() {
667+ selectUser("has-password");
668+ unlockAndShowGreeter(); // turn on lockscreen mode
669+
670+ var biometryd = findInvisibleChild(greeter, "biometryd");
671+ biometryd.operation.mockSuccess(0);
672+
673+ verify(greeter.active);
674+ compare(viewShowErrorMessageSpy.count, 1);
675+ compare(viewShowErrorMessageSpy.signalArguments[0][0], i18n.tr("Try again"));
676+ }
677+
678+ function test_fingerprintNotEnabled() {
679+ AccountsService.enableFingerprintIdentification = false;
680+ selectUser("has-password");
681+ unlockAndShowGreeter(); // turn on lockscreen mode
682+
683+ var biometryd = findInvisibleChild(greeter, "biometryd");
684+ verify(!biometryd.operation);
685+
686+ AccountsService.enableFingerprintIdentification = true;
687+ verify(biometryd.operation);
688+ verify(biometryd.operation.running);
689+
690+ AccountsService.enableFingerprintIdentification = false;
691+ verify(!biometryd.operation);
692+ }
693+
694+ function test_fingerprintReaderNotPresent() {
695+ Biometryd.available = false;
696+ selectUser("has-password");
697+ unlockAndShowGreeter(); // turn on lockscreen mode
698+
699+ verify(!Biometryd.available);
700+
701+ var biometryd = findInvisibleChild(greeter, "biometryd");
702+ verify(!biometryd.operation);
703+
704+ Biometryd.available = true;
705+ verify(biometryd.operation);
706+ verify(biometryd.operation.running);
707+
708+ Biometryd.available = false;
709+ verify(!biometryd.operation);
710+ }
711+
712+ function test_fingerprintGreeterNotActive() {
713+ selectUser("has-password");
714+ unlockAndShowGreeter(); // turn on lockscreen mode
715+
716+ var biometryd = findInvisibleChild(greeter, "biometryd");
717+ verify(biometryd.operation);
718+ verify(biometryd.operation.running);
719+
720+ greeter.hideNow();
721+ verify(!biometryd.operation);
722+
723+ greeter.showNow();
724+ verify(biometryd.operation);
725+ verify(biometryd.operation.running);
726+ }
556727 }
557728}
tests/qmltests/Greeter/tst_NarrowView.qml+22−1
@@ -128,7 +128,7 @@ Item {
128128 text: "Authenticated"
129129 onClicked: {
130130 if (successCheckBox.checked) {
131− loader.item.notifyAuthenticationSucceeded();
131+ loader.item.notifyAuthenticationSucceeded(fakePasswordCheckBox.checked);
132132 } else {
133133 loader.item.notifyAuthenticationFailed();
134134 }
@@ -140,6 +140,12 @@ Item {
140140 Label {
141141 text: "success"
142142 }
143+ CheckBox {
144+ id: fakePasswordCheckBox
145+ }
146+ Label {
147+ text: "fake password"
148+ }
143149 }
144150 Row {
145151 Button {
@@ -529,5 +535,20 @@ Item {
529535 mouseClick(coverPage, coverPage.width/2, coverPage.height - units.gu(2));
530536 verify(!coverPage.shown);
531537 }
538+
539+ function test_showErrorMessage() {
540+ var coverPage = findChild(view, "coverPage");
541+ var swipeHint = findChild(coverPage, "swipeHint");
542+ var errorMessageAnimation = findInvisibleChild(coverPage, "errorMessageAnimation");
543+
544+ view.showErrorMessage("hello");
545+ compare(swipeHint.text, "《 hello 》");
546+ verify(errorMessageAnimation.running);
547+ verify(swipeHint.opacityAnimation.running);
548+
549+ errorMessageAnimation.complete();
550+ swipeHint.opacityAnimation.complete();
551+ tryCompare(swipeHint, "text", "《 " + i18n.tr("Unlock") + " 》");
552+ }
532553 }
533554}
tests/qmltests/Greeter/tst_WideView.qml+1−1
@@ -170,7 +170,7 @@ StyledItem {
170170 text: "Authenticated"
171171 onClicked: {
172172 if (successCheckBox.checked) {
173− loader.item.notifyAuthenticationSucceeded();
173+ loader.item.notifyAuthenticationSucceeded(false);
174174 } else {
175175 loader.item.notifyAuthenticationFailed();
176176 }
tests/qmltests/tst_Shell.qml+27
@@ -334,6 +334,33 @@ Rectangle {
334334 }
335335 }
336336
337+ Label {
338+ text: "Fingerprint"
339+ }
340+ Row {
341+ Button {
342+ text: "Success"
343+ onClicked: {
344+ var biometryd = testCase.findInvisibleChild(shellContainer, "biometryd");
345+ var uid = 0;
346+ for (var i = 0; i < LightDM.Users.count; i++) {
347+ if (LightDM.Users.data(i, LightDM.UserRoles.NameRole) == AccountsService.user) {
348+ uid = LightDM.Users.data(i, LightDM.UserRoles.UidRole);
349+ break;
350+ }
351+ }
352+ biometryd.operation.mockSuccess(uid);
353+ }
354+ }
355+
356+ Button {
357+ text: "Failure"
358+ onClicked: {
359+ var biometryd = testCase.findInvisibleChild(shellContainer, "biometryd");
360+ biometryd.operation.mockFailure("error");
361+ }
362+ }
363+ }
337364 }
338365 }
339366 }