on xenial help sign in
merge trunk
d856065Lukáš Tinkl
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+
1unity8 (8.12+16.10.20160617-0ubuntu1) yakkety; urgency=medium
8unity8 (8.12+16.10.20160617-0ubuntu1) yakkety; urgency=medium
2
9
3 [ Michael Terry ]
10 [ Michael Terry ]
debian/control+2−1
@@ -103,6 +103,7 @@ Depends: dmz-cursor-theme,
103 libcap2-bin,
103 libcap2-bin,
104 libglib2.0-bin,
104 libglib2.0-bin,
105 qmenumodel-qml (>= 0.2.9),
105 qmenumodel-qml (>= 0.2.9),
106+ qml-module-biometryd,
106 qml-module-qt-labs-folderlistmodel,
107 qml-module-qt-labs-folderlistmodel,
107 qml-module-qtquick-xmllistmodel,
108 qml-module-qtquick-xmllistmodel,
108 qml-module-qtsysteminfo,
109 qml-module-qtsysteminfo,
@@ -192,7 +193,7 @@ Package: unity8-private
192Architecture: any
193Architecture: any
193Multi-Arch: same
194Multi-Arch: same
194Pre-Depends: ${misc:Pre-Depends},
195Pre-Depends: ${misc:Pre-Depends},
195−Depends: accountsservice-ubuntu-schemas (>= 0.0.5),
196+Depends: accountsservice-ubuntu-schemas (>= 0.0.7),
196 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
197 gsettings-ubuntu-schemas (>= 0.0.2+14.10.20140815),
197 libhardware2,
198 libhardware2,
198 pay-service,
199 pay-service,
plugins/AccountsService/AccountsService.cpp+21−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013, 2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -36,8 +36,10 @@
36#define PROP_DEMO_EDGES QStringLiteral("demo-edges")
36#define PROP_DEMO_EDGES QStringLiteral("demo-edges")
37#define PROP_DEMO_EDGES_COMPLETED QStringLiteral("DemoEdgesCompleted")
37#define PROP_DEMO_EDGES_COMPLETED QStringLiteral("DemoEdgesCompleted")
38#define PROP_EMAIL QStringLiteral("Email")
38#define PROP_EMAIL QStringLiteral("Email")
39+#define PROP_ENABLE_FINGERPRINT_IDENTIFICATION QStringLiteral("EnableFingerprintIdentification")
39#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
40#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
40#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
41#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
42+#define PROP_FAILED_FINGERPRINT_LOGINS QStringLiteral("FailedFingerprintLogins")
41#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
43#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
42#define PROP_INPUT_SOURCES QStringLiteral("InputSources")
44#define PROP_INPUT_SOURCES QStringLiteral("InputSources")
43#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
45#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
@@ -93,12 +95,14 @@ AccountsService::AccountsService(QObject* parent, const QString &user)
93 registerProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QStringLiteral("keymapsChanged"));
95 registerProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QStringLiteral("keymapsChanged"));
94 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
96 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
95 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
97 registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
98+ registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_FINGERPRINT_IDENTIFICATION, QStringLiteral("enableFingerprintIdentificationChanged"));
96 registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
99 registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
97 registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED, QStringLiteral("enableIndicatorsWhileLockedChanged"));
100 registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED, QStringLiteral("enableIndicatorsWhileLockedChanged"));
98 registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
101 registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
99 registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
102 registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
100 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
103 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
101 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
104 registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
105+ registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, QStringLiteral("failedFingerprintLoginsChanged"));
102 registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
106 registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
103
107
104 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
108 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
@@ -176,6 +180,12 @@ void AccountsService::markDemoEdgeCompleted(const QString &edge)
176 }
180 }
177}
181}
178
182
183+bool AccountsService::enableFingerprintIdentification() const
184+{
185+ auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_FINGERPRINT_IDENTIFICATION);
186+ return value.toBool();
187+}
188+
179bool AccountsService::enableLauncherWhileLocked() const
189bool AccountsService::enableLauncherWhileLocked() const
180{
190{
181 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
191 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
@@ -274,6 +284,16 @@ QStringList AccountsService::keymaps() const
274 return {QStringLiteral("us")};
284 return {QStringLiteral("us")};
275}
285}
276
286
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+
277uint AccountsService::failedLogins() const
297uint AccountsService::failedLogins() const
278{
298{
279 return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
299 return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
plugins/AccountsService/AccountsService.h+13−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013, 2015 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -41,6 +41,9 @@ class AccountsService: public QObject
41 Q_PROPERTY (QStringList demoEdgesCompleted
41 Q_PROPERTY (QStringList demoEdgesCompleted
42 READ demoEdgesCompleted
42 READ demoEdgesCompleted
43 NOTIFY demoEdgesCompletedChanged)
43 NOTIFY demoEdgesCompletedChanged)
44+ Q_PROPERTY (bool enableFingerprintIdentification
45+ READ enableFingerprintIdentification
46+ NOTIFY enableFingerprintIdentificationChanged)
44 Q_PROPERTY (bool enableLauncherWhileLocked
47 Q_PROPERTY (bool enableLauncherWhileLocked
45 READ enableLauncherWhileLocked
48 READ enableLauncherWhileLocked
46 NOTIFY enableLauncherWhileLockedChanged)
49 NOTIFY enableLauncherWhileLockedChanged)
@@ -60,6 +63,10 @@ class AccountsService: public QObject
60 READ failedLogins
63 READ failedLogins
61 WRITE setFailedLogins
64 WRITE setFailedLogins
62 NOTIFY failedLoginsChanged)
65 NOTIFY failedLoginsChanged)
66+ Q_PROPERTY (uint failedFingerprintLogins
67+ READ failedFingerprintLogins
68+ WRITE setFailedFingerprintLogins
69+ NOTIFY failedFingerprintLoginsChanged)
63 Q_PROPERTY(bool hereEnabled
70 Q_PROPERTY(bool hereEnabled
64 READ hereEnabled
71 READ hereEnabled
65 WRITE setHereEnabled
72 WRITE setHereEnabled
@@ -91,6 +98,7 @@ public:
91 void setDemoEdges(bool demoEdges);
98 void setDemoEdges(bool demoEdges);
92 QStringList demoEdgesCompleted() const;
99 QStringList demoEdgesCompleted() const;
93 Q_INVOKABLE void markDemoEdgeCompleted(const QString &edge);
100 Q_INVOKABLE void markDemoEdgeCompleted(const QString &edge);
101+ bool enableFingerprintIdentification() const;
94 bool enableLauncherWhileLocked() const;
102 bool enableLauncherWhileLocked() const;
95 bool enableIndicatorsWhileLocked() const;
103 bool enableIndicatorsWhileLocked() const;
96 QString backgroundFile() const;
104 QString backgroundFile() const;
@@ -98,6 +106,8 @@ public:
98 PasswordDisplayHint passwordDisplayHint() const;
106 PasswordDisplayHint passwordDisplayHint() const;
99 uint failedLogins() const;
107 uint failedLogins() const;
100 void setFailedLogins(uint failedLogins);
108 void setFailedLogins(uint failedLogins);
109+ uint failedFingerprintLogins() const;
110+ void setFailedFingerprintLogins(uint failedFingerprintLogins);
101 bool hereEnabled() const;
111 bool hereEnabled() const;
102 void setHereEnabled(bool enabled);
112 void setHereEnabled(bool enabled);
103 QString hereLicensePath() const;
113 QString hereLicensePath() const;
@@ -112,12 +122,14 @@ Q_SIGNALS:
112 void userChanged();
122 void userChanged();
113 void demoEdgesChanged();
123 void demoEdgesChanged();
114 void demoEdgesCompletedChanged();
124 void demoEdgesCompletedChanged();
125+ void enableFingerprintIdentificationChanged();
115 void enableLauncherWhileLockedChanged();
126 void enableLauncherWhileLockedChanged();
116 void enableIndicatorsWhileLockedChanged();
127 void enableIndicatorsWhileLockedChanged();
117 void backgroundFileChanged();
128 void backgroundFileChanged();
118 void statsWelcomeScreenChanged();
129 void statsWelcomeScreenChanged();
119 void passwordDisplayHintChanged();
130 void passwordDisplayHintChanged();
120 void failedLoginsChanged();
131 void failedLoginsChanged();
132+ void failedFingerprintLoginsChanged();
121 void hereEnabledChanged();
133 void hereEnabledChanged();
122 void hereLicensePathChanged();
134 void hereLicensePathChanged();
123 void realNameChanged();
135 void realNameChanged();
plugins/AccountsService/com.canonical.unity.AccountsService.xml+4
@@ -47,5 +47,9 @@
47 <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
47 <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
48 </property>
48 </property>
49
49
50+ <property name="FailedFingerprintLogins" type="u" access="readwrite">
51+ <annotation name="org.freedesktop.Accounts.DefaultValue" value="0"/>
52+ </property>
53+
50 </interface>
54 </interface>
51</node>
55</node>
plugins/IntegratedLightDM/liblightdm/UsersModel.cpp+4−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -52,6 +52,7 @@ UsersModel::UsersModel(QObject *parent) :
52 roles[SessionRole] = "session";
52 roles[SessionRole] = "session";
53 roles[HasMessagesRole] = "hasMessages";
53 roles[HasMessagesRole] = "hasMessages";
54 roles[ImagePathRole] = "imagePath";
54 roles[ImagePathRole] = "imagePath";
55+ roles[UidRole] = "uid";
55 setRoleNames(roles);
56 setRoleNames(roles);
56
57
57 connect(d_ptr, &UsersModelPrivate::dataChanged, this, [this](int i) {
58 connect(d_ptr, &UsersModelPrivate::dataChanged, this, [this](int i) {
@@ -101,6 +102,8 @@ QVariant UsersModel::data(const QModelIndex &index, int role) const
101 return d->entries[row].has_messages;
102 return d->entries[row].has_messages;
102 case UsersModel::ImagePathRole:
103 case UsersModel::ImagePathRole:
103 return "";
104 return "";
105+ case UsersModel::UidRole:
106+ return d->entries[row].uid;
104 default:
107 default:
105 return QVariant();
108 return QVariant();
106 }
109 }
plugins/IntegratedLightDM/liblightdm/UsersModel.h+2−1
@@ -50,7 +50,8 @@ public:
50 SessionRole,
50 SessionRole,
51 HasMessagesRole,
51 HasMessagesRole,
52 ImagePathRole,
52 ImagePathRole,
53− BackgroundPathRole
53+ BackgroundPathRole,
54+ UidRole
54 };
55 };
55
56
56 int rowCount(const QModelIndex &parent) const override;
57 int rowCount(const QModelIndex &parent) const override;
plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+5−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@
26#include <QDir>
26#include <QDir>
27#include <QSettings>
27#include <QSettings>
28#include <QStringList>
28#include <QStringList>
29+#include <unistd.h>
29
30
30namespace QLightDM
31namespace QLightDM
31{
32{
@@ -37,6 +38,7 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
37{
38{
38 QFileInfo demoFile(QDir::homePath() + "/.unity8-greeter-demo");
39 QFileInfo demoFile(QDir::homePath() + "/.unity8-greeter-demo");
39 QString currentUser = g_get_user_name();
40 QString currentUser = g_get_user_name();
41+ uid_t currentUid = getuid();
40
42
41 if (demoFile.exists()) {
43 if (demoFile.exists()) {
42 QSettings settings(demoFile.filePath(), QSettings::NativeFormat);
44 QSettings settings(demoFile.filePath(), QSettings::NativeFormat);
@@ -46,10 +48,10 @@ UsersModelPrivate::UsersModelPrivate(UsersModel* parent)
46 Q_FOREACH(const QString &user, users)
48 Q_FOREACH(const QString &user, users)
47 {
49 {
48 QString name = settings.value(user + "/name", user).toString();
50 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++});
50 }
52 }
51 } else {
53 } else {
52− entries.append({currentUser, 0, 0, 0, false, false, 0, 0});
54+ entries.append({currentUser, 0, 0, 0, false, false, 0, 0, currentUid});
53
55
54 connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged,
56 connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged,
55 this, [this](const QString &user) {
57 this, [this](const QString &user) {
plugins/IntegratedLightDM/liblightdm/UsersModelPrivate.h+2−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -40,6 +40,7 @@ public:
40 bool has_messages;
40 bool has_messages;
41 QString session;
41 QString session;
42 QString infographic;
42 QString infographic;
43+ uid_t uid;
43};
44};
44
45
45class UsersModelPrivate : public QObject
46class UsersModelPrivate : public QObject
po/unity8.pot+20−16
@@ -8,7 +8,7 @@ msgid ""
8msgstr ""
8msgstr ""
9"Project-Id-Version: unity8\n"
9"Project-Id-Version: unity8\n"
10"Report-Msgid-Bugs-To: \n"
10"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"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -35,19 +35,19 @@ msgstr ""
35msgid "Unpin shortcut"
35msgid "Unpin shortcut"
36msgstr ""
36msgstr ""
37
37
38−#: qml/Components/DelayedLockscreen.qml:49
38+#: qml/Components/DelayedLockscreen.qml:50
39msgid "Device Locked"
39msgid "Device Locked"
40msgstr ""
40msgstr ""
41
41
42−#: qml/Components/DelayedLockscreen.qml:64
42+#: qml/Components/DelayedLockscreen.qml:65
43msgid "You have been locked out due to too many failed passphrase attempts."
43msgid "You have been locked out due to too many failed passphrase attempts."
44msgstr ""
44msgstr ""
45
45
46−#: qml/Components/DelayedLockscreen.qml:65
46+#: qml/Components/DelayedLockscreen.qml:66
47msgid "You have been locked out due to too many failed passcode attempts."
47msgid "You have been locked out due to too many failed passcode attempts."
48msgstr ""
48msgstr ""
49
49
50−#: qml/Components/DelayedLockscreen.qml:74
50+#: qml/Components/DelayedLockscreen.qml:75
51#, qt-format
51#, qt-format
52msgid "Please wait %1 minute and then try again…"
52msgid "Please wait %1 minute and then try again…"
53msgid_plural "Please wait %1 minutes and then try again…"
53msgid_plural "Please wait %1 minutes and then try again…"
@@ -277,15 +277,15 @@ msgstr ""
277msgid "Closes the current window."
277msgid "Closes the current window."
278msgstr ""
278msgstr ""
279
279
280−#: qml/Components/Lockscreen.qml:231
280+#: qml/Components/Lockscreen.qml:239
281msgid "Return to Call"
281msgid "Return to Call"
282msgstr ""
282msgstr ""
283
283
284−#: qml/Components/Lockscreen.qml:231
284+#: qml/Components/Lockscreen.qml:239
285msgid "Emergency Call"
285msgid "Emergency Call"
286msgstr ""
286msgstr ""
287
287
288−#: qml/Components/Lockscreen.qml:263
288+#: qml/Components/Lockscreen.qml:271
289msgid "OK"
289msgid "OK"
290msgstr ""
290msgstr ""
291
291
@@ -422,10 +422,14 @@ msgid ""
422"touch pad to interact with the pointer."
422"touch pad to interact with the pointer."
423msgstr ""
423msgstr ""
424
424
425−#: qml/Greeter/CoverPage.qml:119
425+#: qml/Greeter/CoverPage.qml:127
426msgid "Unlock"
426msgid "Unlock"
427msgstr ""
427msgstr ""
428
428
429+#: qml/Greeter/Greeter.qml:536
430+msgid "Try again"
431+msgstr ""
432+
429#: qml/Greeter/LoginList.qml:259
433#: qml/Greeter/LoginList.qml:259
430msgid "Retry"
434msgid "Retry"
431msgstr ""
435msgstr ""
@@ -458,34 +462,34 @@ msgid ""
458"and all personal data will be deleted."
462"and all personal data will be deleted."
459msgstr ""
463msgstr ""
460
464
461−#: qml/Greeter/NarrowView.qml:116
465+#: qml/Greeter/NarrowView.qml:129
462#, qt-format
466#, qt-format
463msgid "Enter %1"
467msgid "Enter %1"
464msgstr ""
468msgstr ""
465
469
466−#: qml/Greeter/NarrowView.qml:117
470+#: qml/Greeter/NarrowView.qml:130
467msgid "Enter passphrase"
471msgid "Enter passphrase"
468msgstr ""
472msgstr ""
469
473
470−#: qml/Greeter/NarrowView.qml:118
474+#: qml/Greeter/NarrowView.qml:131
471msgid "Enter passcode"
475msgid "Enter passcode"
472msgstr ""
476msgstr ""
473
477
474−#: qml/Greeter/NarrowView.qml:119
478+#: qml/Greeter/NarrowView.qml:135
475#, qt-format
479#, qt-format
476msgid "Sorry, incorrect %1"
480msgid "Sorry, incorrect %1"
477msgstr ""
481msgstr ""
478
482
479−#: qml/Greeter/NarrowView.qml:120
483+#: qml/Greeter/NarrowView.qml:136
480msgid "Sorry, incorrect passphrase"
484msgid "Sorry, incorrect passphrase"
481msgstr ""
485msgstr ""
482
486
483−#: qml/Greeter/NarrowView.qml:121
487+#: qml/Greeter/NarrowView.qml:137
484msgctxt "passphrase"
488msgctxt "passphrase"
485msgid "Please re-enter"
489msgid "Please re-enter"
486msgstr ""
490msgstr ""
487
491
488−#: qml/Greeter/NarrowView.qml:122
492+#: qml/Greeter/NarrowView.qml:138
489msgid "Sorry, incorrect passcode"
493msgid "Sorry, incorrect passcode"
490msgstr ""
494msgstr ""
491
495
qml/Components/DelayedLockscreen.qml+1
@@ -28,6 +28,7 @@ Item {
28 signal cancel() // unused
28 signal cancel() // unused
29
29
30 function clear(playAnimation) {}
30 function clear(playAnimation) {}
31+ function showText(text) {}
31
32
32 Column {
33 Column {
33 anchors.left: parent.left
34 anchors.left: parent.left
qml/Components/Lockscreen.qml+8
@@ -91,6 +91,14 @@ Showable {
91 popup.z = Number.MAX_VALUE
91 popup.z = Number.MAX_VALUE
92 }
92 }
93
93
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+
94 Rectangle {
102 Rectangle {
95 // In case background fails to load
103 // In case background fails to load
96 id: backgroundBackup
104 id: backgroundBackup
qml/Components/PassphraseLockscreen.qml+4
@@ -43,6 +43,10 @@ FocusScope {
43 }
43 }
44 }
44 }
45
45
46+ function showText(text) {
47+ pinentryField.text = text;
48+ }
49+
46 Column {
50 Column {
47 id: shakeContainer
51 id: shakeContainer
48 anchors.horizontalCenter: parent.horizontalCenter
52 anchors.horizontalCenter: parent.horizontalCenter
qml/Components/PinLockscreen.qml+4
@@ -46,6 +46,10 @@ FocusScope {
46 }
46 }
47 }
47 }
48
48
49+ function showText(text) {
50+ pinentryField.text = text;
51+ }
52+
49 Keys.onPressed: {
53 Keys.onPressed: {
50 if (pinentryField.text.length == root.maxPinLength)
54 if (pinentryField.text.length == root.maxPinLength)
51 return;
55 return;
qml/Greeter/CoverPage.qml+22−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 () {
@@ -111,15 +118,18 @@ Showable {
111
118
112 Label {
119 Label {
113 id: swipeHint
120 id: swipeHint
121+ objectName: "swipeHint"
114 property real baseOpacity: 0.5
122 property real baseOpacity: 0.5
115 opacity: 0.0
123 opacity: 0.0
116 anchors.horizontalCenter: parent.horizontalCenter
124 anchors.horizontalCenter: parent.horizontalCenter
117 anchors.bottom: parent.bottom
125 anchors.bottom: parent.bottom
118 anchors.bottomMargin: units.gu(5)
126 anchors.bottomMargin: units.gu(5)
119− text: "《 " + i18n.tr("Unlock") + " 》"
127+ text: "《 " + (d.errorMessage ? d.errorMessage : i18n.tr("Unlock")) + " 》"
120 color: "white"
128 color: "white"
121 font.weight: Font.Light
129 font.weight: Font.Light
122
130
131+ readonly property var opacityAnimation: showLabelAnimation // for testing
132+
123 SequentialAnimation on opacity {
133 SequentialAnimation on opacity {
124 id: showLabelAnimation
134 id: showLabelAnimation
125 running: false
135 running: false
@@ -136,9 +146,20 @@ Showable {
136 to: 0.0
146 to: 0.0
137 duration: UbuntuAnimation.SleepyDuration
147 duration: UbuntuAnimation.SleepyDuration
138 }
148 }
149+
150+ onRunningChanged: {
151+ if (!running)
152+ d.errorMessage = "";
153+ }
139 }
154 }
140 }
155 }
141
156
157+ WrongPasswordAnimation {
158+ id: errorMessageAnimation
159+ objectName: "errorMessageAnimation"
160+ target: swipeHint
161+ }
162+
142 DragHandle {
163 DragHandle {
143 id: dragHandle
164 id: dragHandle
144 objectName: "coverPageDragHandle"
165 objectName: "coverPageDragHandle"
qml/Greeter/Greeter.qml+97−4
@@ -16,7 +16,9 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import AccountsService 0.1
18import AccountsService 0.1
19+import Biometryd 0.0
19import GSettings 1.0
20import GSettings 1.0
21+import Powerd 0.1
20import Ubuntu.Components 1.3
22import Ubuntu.Components 1.3
21import Ubuntu.SystemImage 0.1
23import Ubuntu.SystemImage 0.1
22import Unity.Launcher 0.1
24import Unity.Launcher 0.1
@@ -37,6 +39,8 @@ Showable {
37 readonly property bool active: required || hasLockedApp
39 readonly property bool active: required || hasLockedApp
38 readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
40 readonly property bool fullyShown: loader.item ? loader.item.fullyShown : false
39
41
42+ property bool allowFingerprint: true
43+
40 // True when the greeter is waiting for PAM or other setup process
44 // True when the greeter is waiting for PAM or other setup process
41 readonly property alias waiting: d.waiting
45 readonly property alias waiting: d.waiting
42
46
@@ -52,6 +56,7 @@ Showable {
52 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
56 property int maxFailedLogins: -1 // disabled by default for now, will enable via settings in future
53 property int failedLoginsDelayAttempts: 7 // number of failed logins
57 property int failedLoginsDelayAttempts: 7 // number of failed logins
54 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
58 property real failedLoginsDelayMinutes: 5 // minutes of forced waiting
59+ property int failedFingerprintLoginsDisableAttempts: 3 // number of failed fingerprint logins
55
60
56 readonly property bool animating: loader.item ? loader.item.animating : false
61 readonly property bool animating: loader.item ? loader.item.animating : false
57
62
@@ -60,6 +65,9 @@ Showable {
60 signal emergencyCall()
65 signal emergencyCall()
61
66
62 function forceShow() {
67 function forceShow() {
68+ if (!active) {
69+ d.isLockscreen = true;
70+ }
63 forcedUnlock = false;
71 forcedUnlock = false;
64 if (!required) {
72 if (!required) {
65 showNow(); // loader.onLoaded will select a user
73 showNow(); // loader.onLoaded will select a user
@@ -119,6 +127,11 @@ Showable {
119 readonly property bool multiUser: lightDM.users.count > 1
127 readonly property bool multiUser: lightDM.users.count > 1
120 property int currentIndex
128 property int currentIndex
121 property bool waiting
129 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
122
135
123 // We want 'launcherOffset' to animate down to zero. But not to animate
136 // We want 'launcherOffset' to animate down to zero. But not to animate
124 // while being dragged. So ideally we change this only when the user
137 // while being dragged. So ideally we change this only when the user
@@ -151,7 +164,7 @@ Showable {
151 if (lightDM.greeter.startSessionSync()) {
164 if (lightDM.greeter.startSessionSync()) {
152 sessionStarted();
165 sessionStarted();
153 if (loader.item) {
166 if (loader.item) {
154− loader.item.notifyAuthenticationSucceeded();
167+ loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
155 }
168 }
156 } else if (loader.item) {
169 } else if (loader.item) {
157 loader.item.notifyAuthenticationFailed();
170 loader.item.notifyAuthenticationFailed();
@@ -170,7 +183,7 @@ Showable {
170 function checkForcedUnlock(hideNow) {
183 function checkForcedUnlock(hideNow) {
171 if (forcedUnlock && shown && loader.item) {
184 if (forcedUnlock && shown && loader.item) {
172 // pretend we were just authenticated
185 // pretend we were just authenticated
173− loader.item.notifyAuthenticationSucceeded();
186+ loader.item.notifyAuthenticationSucceeded(false /* showFakePassword */);
174 loader.item.hide();
187 loader.item.hide();
175 if (hideNow) {
188 if (hideNow) {
176 root.hideNow(); // skip hide animation
189 root.hideNow(); // skip hide animation
@@ -188,6 +201,17 @@ Showable {
188 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
201 onForcedUnlockChanged: d.checkForcedUnlock(false /* hideNow */)
189 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
202 Component.onCompleted: d.checkForcedUnlock(true /* hideNow */)
190
203
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+
191 onRequiredChanged: {
215 onRequiredChanged: {
192 if (required) {
216 if (required) {
193 d.waiting = true;
217 d.waiting = true;
@@ -349,7 +373,7 @@ Showable {
349 Binding {
373 Binding {
350 target: loader.item
374 target: loader.item
351 property: "alphanumeric"
375 property: "alphanumeric"
352− value: AccountsService.passwordDisplayHint === AccountsService.Keyboard
376+ value: d.alphanumeric
353 }
377 }
354
378
355 Binding {
379 Binding {
@@ -408,7 +432,6 @@ Showable {
408 d.waiting = false;
432 d.waiting = false;
409
433
410 if (lightDM.greeter.authenticated) {
434 if (lightDM.greeter.authenticated) {
411− AccountsService.failedLogins = 0;
412 d.login();
435 d.login();
413 if (!lightDM.greeter.promptless) {
436 if (!lightDM.greeter.promptless) {
414 loader.item.hide();
437 loader.item.hide();
@@ -474,4 +497,74 @@ Showable {
474 target: i18n
497 target: i18n
475 onLanguageChanged: lightDM.infographic.readyForDataChange()
498 onLanguageChanged: lightDM.infographic.readyForDataChange()
476 }
499 }
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+ }
477}
570}
qml/Greeter/NarrowView.qml+18−2
@@ -65,15 +65,28 @@ FocusScope {
65 coverPage.hide();
65 coverPage.hide();
66 }
66 }
67
67
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+ }
69 lockscreen.hide();
74 lockscreen.hide();
70 }
75 }
71
76
72 function notifyAuthenticationFailed() {
77 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
73 lockscreen.clear(true);
85 lockscreen.clear(true);
74 }
86 }
75
87
76 function reset() {
88 function reset() {
89+ lockscreen.customError = "";
77 coverPage.show();
90 coverPage.show();
78 }
91 }
79
92
@@ -116,7 +129,10 @@ FocusScope {
116 infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
129 infoText: promptText !== "" ? i18n.tr("Enter %1").arg(promptText) :
117 alphaNumeric ? i18n.tr("Enter passphrase") :
130 alphaNumeric ? i18n.tr("Enter passphrase") :
118 i18n.tr("Enter passcode")
131 i18n.tr("Enter passcode")
132+
133+ property string customError
134+ errorText: customError !== "" ? customError :
119− errorText: promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
135+ promptText !== "" ? i18n.tr("Sorry, incorrect %1").arg(promptText) :
120 alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
136 alphaNumeric ? i18n.tr("Sorry, incorrect passphrase") + "\n" +
121 i18n.ctr("passphrase", "Please re-enter") :
137 i18n.ctr("passphrase", "Please re-enter") :
122 i18n.tr("Sorry, incorrect passcode")
138 i18n.tr("Sorry, incorrect passcode")
qml/Greeter/WideView.qml+5−1
@@ -58,7 +58,7 @@ FocusScope {
58 coverPage.hide();
58 coverPage.hide();
59 }
59 }
60
60
61− function notifyAuthenticationSucceeded() {
61+ function notifyAuthenticationSucceeded(showFakePassword) {
62 // Nothing needed
62 // Nothing needed
63 }
63 }
64
64
@@ -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 }
qml/Shell.qml+3
@@ -422,6 +422,9 @@ StyledItem {
422 launcherOffset: launcher.progress
422 launcherOffset: launcher.progress
423 forcedUnlock: wizard.active || shell.mode === "full-shell"
423 forcedUnlock: wizard.active || shell.mode === "full-shell"
424 background: wallpaperResolver.background
424 background: wallpaperResolver.background
425+ allowFingerprint: !dialogs.hasActiveDialog &&
426+ !notifications.topmostIsFullscreen &&
427+ !panel.indicators.shown
425
428
426 // avoid overlapping with Launcher's edge drag area
429 // avoid overlapping with Launcher's edge drag area
427 // FIXME: Fix TouchRegistry & friends and remove this workaround
430 // FIXME: Fix TouchRegistry & friends and remove this workaround
tests/mocks/AccountsService/AccountsService.cpp+24
@@ -20,11 +20,13 @@
20
20
21AccountsService::AccountsService(QObject* parent)
21AccountsService::AccountsService(QObject* parent)
22 : QObject(parent),
22 : QObject(parent),
23+ m_enableFingerprintIdentification(true),
23 m_enableLauncherWhileLocked(true),
24 m_enableLauncherWhileLocked(true),
24 m_enableIndicatorsWhileLocked(true),
25 m_enableIndicatorsWhileLocked(true),
25 m_backgroundFile(qmlDirectory() + "/graphics/phone_background.jpg"),
26 m_backgroundFile(qmlDirectory() + "/graphics/phone_background.jpg"),
26 m_statsWelcomeScreen(true),
27 m_statsWelcomeScreen(true),
27 m_failedLogins(0),
28 m_failedLogins(0),
29+ m_failedFingerprintLogins(0),
28 m_demoEdges(false),
30 m_demoEdges(false),
29 m_demoEdgesCompleted(),
31 m_demoEdgesCompleted(),
30 m_hereEnabled(false),
32 m_hereEnabled(false),
@@ -74,6 +76,17 @@ void AccountsService::setDemoEdgesCompleted(const QStringList &demoEdgesComplete
74 Q_EMIT demoEdgesCompletedChanged();
76 Q_EMIT demoEdgesCompletedChanged();
75}
77}
76
78
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+
77bool AccountsService::enableLauncherWhileLocked() const
90bool AccountsService::enableLauncherWhileLocked() const
78{
91{
79 return m_enableLauncherWhileLocked;
92 return m_enableLauncherWhileLocked;
@@ -137,6 +150,17 @@ void AccountsService::setFailedLogins(uint failedLogins)
137 failedLoginsChanged();
150 failedLoginsChanged();
138}
151}
139
152
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+
140bool AccountsService::hereEnabled() const
164bool AccountsService::hereEnabled() const
141{
165{
142 return m_hereEnabled;
166 return m_hereEnabled;
tests/mocks/AccountsService/AccountsService.h+16
@@ -38,6 +38,10 @@ class AccountsService: public QObject
38 READ demoEdgesCompleted
38 READ demoEdgesCompleted
39 WRITE setDemoEdgesCompleted // only available in mock
39 WRITE setDemoEdgesCompleted // only available in mock
40 NOTIFY demoEdgesCompletedChanged)
40 NOTIFY demoEdgesCompletedChanged)
41+ Q_PROPERTY (bool enableFingerprintIdentification
42+ READ enableFingerprintIdentification
43+ WRITE setEnableFingerprintIdentification // only available in mock
44+ NOTIFY enableFingerprintIdentificationChanged)
41 Q_PROPERTY (bool enableLauncherWhileLocked
45 Q_PROPERTY (bool enableLauncherWhileLocked
42 READ enableLauncherWhileLocked
46 READ enableLauncherWhileLocked
43 WRITE setEnableLauncherWhileLocked // only available in mock
47 WRITE setEnableLauncherWhileLocked // only available in mock
@@ -61,6 +65,10 @@ class AccountsService: public QObject
61 READ failedLogins
65 READ failedLogins
62 WRITE setFailedLogins
66 WRITE setFailedLogins
63 NOTIFY failedLoginsChanged)
67 NOTIFY failedLoginsChanged)
68+ Q_PROPERTY (uint failedFingerprintLogins
69+ READ failedFingerprintLogins
70+ WRITE setFailedFingerprintLogins
71+ NOTIFY failedFingerprintLoginsChanged)
64 Q_PROPERTY(bool hereEnabled
72 Q_PROPERTY(bool hereEnabled
65 READ hereEnabled
73 READ hereEnabled
66 WRITE setHereEnabled
74 WRITE setHereEnabled
@@ -94,6 +102,8 @@ public:
94 QStringList demoEdgesCompleted() const;
102 QStringList demoEdgesCompleted() const;
95 void setDemoEdgesCompleted(const QStringList &demoEdges);
103 void setDemoEdgesCompleted(const QStringList &demoEdges);
96 Q_INVOKABLE void markDemoEdgeCompleted(const QString &edge);
104 Q_INVOKABLE void markDemoEdgeCompleted(const QString &edge);
105+ bool enableFingerprintIdentification() const;
106+ void setEnableFingerprintIdentification(bool enableFingerprintIdentification);
97 bool enableLauncherWhileLocked() const;
107 bool enableLauncherWhileLocked() const;
98 void setEnableLauncherWhileLocked(bool enableLauncherWhileLocked);
108 void setEnableLauncherWhileLocked(bool enableLauncherWhileLocked);
99 bool enableIndicatorsWhileLocked() const;
109 bool enableIndicatorsWhileLocked() const;
@@ -105,6 +115,8 @@ public:
105 PasswordDisplayHint passwordDisplayHint() const;
115 PasswordDisplayHint passwordDisplayHint() const;
106 uint failedLogins() const;
116 uint failedLogins() const;
107 void setFailedLogins(uint failedLogins);
117 void setFailedLogins(uint failedLogins);
118+ uint failedFingerprintLogins() const;
119+ void setFailedFingerprintLogins(uint failedFingerprintLogins);
108 bool hereEnabled() const;
120 bool hereEnabled() const;
109 void setHereEnabled(bool enabled);
121 void setHereEnabled(bool enabled);
110 QString hereLicensePath() const;
122 QString hereLicensePath() const;
@@ -121,12 +133,14 @@ Q_SIGNALS:
121 void userChanged();
133 void userChanged();
122 void demoEdgesChanged();
134 void demoEdgesChanged();
123 void demoEdgesCompletedChanged();
135 void demoEdgesCompletedChanged();
136+ void enableFingerprintIdentificationChanged();
124 void enableLauncherWhileLockedChanged();
137 void enableLauncherWhileLockedChanged();
125 void enableIndicatorsWhileLockedChanged();
138 void enableIndicatorsWhileLockedChanged();
126 void backgroundFileChanged();
139 void backgroundFileChanged();
127 void statsWelcomeScreenChanged();
140 void statsWelcomeScreenChanged();
128 void passwordDisplayHintChanged();
141 void passwordDisplayHintChanged();
129 void failedLoginsChanged();
142 void failedLoginsChanged();
143+ void failedFingerprintLoginsChanged();
130 void hereEnabledChanged();
144 void hereEnabledChanged();
131 void hereLicensePathChanged();
145 void hereLicensePathChanged();
132 void realNameChanged();
146 void realNameChanged();
@@ -134,12 +148,14 @@ Q_SIGNALS:
134 void keymapsChanged();
148 void keymapsChanged();
135
149
136private:
150private:
151+ bool m_enableFingerprintIdentification;
137 bool m_enableLauncherWhileLocked;
152 bool m_enableLauncherWhileLocked;
138 bool m_enableIndicatorsWhileLocked;
153 bool m_enableIndicatorsWhileLocked;
139 QString m_backgroundFile;
154 QString m_backgroundFile;
140 QString m_user;
155 QString m_user;
141 bool m_statsWelcomeScreen;
156 bool m_statsWelcomeScreen;
142 uint m_failedLogins;
157 uint m_failedLogins;
158+ uint m_failedFingerprintLogins;
143 bool m_demoEdges;
159 bool m_demoEdges;
144 QStringList m_demoEdgesCompleted;
160 QStringList m_demoEdgesCompleted;
145 bool m_hereEnabled;
161 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)
29endmacro()
29endmacro()
30
30
31add_subdirectory(AccountsService)
31add_subdirectory(AccountsService)
32+add_subdirectory(Biometryd)
32add_subdirectory(Cursor)
33add_subdirectory(Cursor)
33add_subdirectory(GSettings.1.0)
34add_subdirectory(GSettings.1.0)
34add_subdirectory(indicator-service)
35add_subdirectory(indicator-service)
tests/mocks/IntegratedLightDM/liblightdm/UsersModel.cpp+4−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -46,6 +46,7 @@ UsersModel::UsersModel(QObject *parent) :
46 roles[SessionRole] = "session";
46 roles[SessionRole] = "session";
47 roles[HasMessagesRole] = "hasMessages";
47 roles[HasMessagesRole] = "hasMessages";
48 roles[ImagePathRole] = "imagePath";
48 roles[ImagePathRole] = "imagePath";
49+ roles[UidRole] = "uid";
49 setRoleNames(roles);
50 setRoleNames(roles);
50}
51}
51
52
@@ -95,6 +96,8 @@ QVariant UsersModel::data(const QModelIndex &index, int role) const
95 return d->entries[row].has_messages;
96 return d->entries[row].has_messages;
96 case UsersModel::ImagePathRole:
97 case UsersModel::ImagePathRole:
97 return "";
98 return "";
99+ case UsersModel::UidRole:
100+ return d->entries[row].uid;
98 default:
101 default:
99 return QVariant();
102 return QVariant();
100 }
103 }
tests/mocks/IntegratedLightDM/liblightdm/UsersModel.h+2−1
@@ -50,7 +50,8 @@ public:
50 SessionRole,
50 SessionRole,
51 HasMessagesRole,
51 HasMessagesRole,
52 ImagePathRole,
52 ImagePathRole,
53− BackgroundPathRole
53+ BackgroundPathRole,
54+ UidRole
54 };
55 };
55
56
56 int rowCount(const QModelIndex &parent) const override;
57 int rowCount(const QModelIndex &parent) const override;
tests/mocks/IntegratedLightDM/liblightdm/UsersModelPrivate.cpp+29−23
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -47,6 +47,12 @@ void UsersModelPrivate::resetEntries()
47 resetEntries_full();
47 resetEntries_full();
48 }
48 }
49
49
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+
50 q->endResetModel();
56 q->endResetModel();
51}
57}
52
58
@@ -54,7 +60,7 @@ void UsersModelPrivate::resetEntries_single()
54{
60{
55 entries =
61 entries =
56 {
62 {
57− { "single", "Single User", 0, 0, false, false, 0, 0 },
63+ { "single", "Single User", 0, 0, false, false, 0, 0, 0 },
58 };
64 };
59}
65}
60
66
@@ -62,7 +68,7 @@ void UsersModelPrivate::resetEntries_singlePassphrase()
62{
68{
63 entries =
69 entries =
64 {
70 {
65− { "single", "Single User", 0, 0, false, false, 0, 0 },
71+ { "single", "Single User", 0, 0, false, false, 0, 0, 0 },
66 };
72 };
67}
73}
68
74
@@ -70,7 +76,7 @@ void UsersModelPrivate::resetEntries_singlePin()
70{
76{
71 entries =
77 entries =
72 {
78 {
73− { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
79+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0, 0 },
74 };
80 };
75}
81}
76
82
@@ -78,26 +84,26 @@ void UsersModelPrivate::resetEntries_full()
78{
84{
79 entries =
85 entries =
80 {
86 {
81− { "has-password", "Has Password", 0, 0, false, false, 0, 0 },
87+ { "has-password", "Has Password", 0, 0, false, false, 0, 0, 0 },
82− { "has-pin", "Has PIN", 0, 0, false, false, 0, 0 },
88+ { "has-pin", "Has PIN", 0, 0, false, false, 0, 0, 0 },
83− { "different-prompt", "Different Prompt", 0, 0, false, false, 0, 0 },
89+ { "different-prompt", "Different Prompt", 0, 0, false, false, 0, 0, 0 },
84− { "no-password", "No Password", 0, 0, false, false, 0, 0 },
90+ { "no-password", "No Password", 0, 0, false, false, 0, 0, 0 },
85− { "auth-error", "Auth Error", 0, 0, false, false, 0, 0 },
91+ { "auth-error", "Auth Error", 0, 0, false, false, 0, 0, 0 },
86− { "two-factor", "Two Factor", 0, 0, false, false, 0, 0 },
92+ { "two-factor", "Two Factor", 0, 0, false, false, 0, 0, 0 },
87− { "info-prompt", "Info Prompt", 0, 0, false, false, 0, 0 },
93+ { "info-prompt", "Info Prompt", 0, 0, false, false, 0, 0, 0 },
88− { "html-info-prompt", "HTML Info Prompt", 0, 0, false, false, 0, 0 },
94+ { "html-info-prompt", "HTML Info Prompt", 0, 0, false, false, 0, 0, 0 },
89− { "long-info-prompt", "Long Info Prompt", 0, 0, false, false, 0, 0 },
95+ { "long-info-prompt", "Long Info Prompt", 0, 0, false, false, 0, 0, 0 },
90− { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, 0, 0 },
96+ { "wide-info-prompt", "Wide Info Prompt", 0, 0, false, false, 0, 0, 0 },
91− { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, 0, 0 },
97+ { "multi-info-prompt", "Multi Info Prompt", 0, 0, false, false, 0, 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 },
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 },
93− { "color-background", "Color Background", "#dd4814", 0, false, false, 0, 0 },
99+ { "color-background", "Color Background", "#dd4814", 0, false, false, 0, 0, 0 },
94 // white and black are a bit redundant, but useful for manually testing if UI is still readable
100 // 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 },
101+ { "white-background", "White Background", "#ffffff", 0, false, false, 0, 0, 0 },
96− { "black-background", "Black Background", "#000000", 0, false, false, 0, 0 },
102+ { "black-background", "Black Background", "#000000", 0, false, false, 0, 0, 0 },
97− { "no-background", "No Background", "", 0, false, false, 0, 0 },
103+ { "no-background", "No Background", "", 0, false, false, 0, 0, 0 },
98− { "unicode", "가나다라마", 0, 0, false, false, 0, 0 },
104+ { "unicode", "가나다라마", 0, 0, false, false, 0, 0, 0 },
99− { "no-response", "No Response", 0, 0, false, false, 0, 0 },
105+ { "no-response", "No Response", 0, 0, false, false, 0, 0, 0 },
100− { "empty-name", "", 0, 0, false, false, 0, 0 },
106+ { "empty-name", "", 0, 0, false, false, 0, 0, 0 },
101 };
107 };
102}
108}
103
109
tests/mocks/IntegratedLightDM/liblightdm/UsersModelPrivate.h+2−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -35,6 +35,7 @@ public:
35 bool has_messages;
35 bool has_messages;
36 QString session;
36 QString session;
37 QString infographic;
37 QString infographic;
38+ uid_t uid;
38};
39};
39
40
40class UsersModelPrivate
41class UsersModelPrivate
tests/qmltests/Components/tst_Lockscreen.qml+20−1
@@ -173,6 +173,7 @@ Rectangle {
173 function cleanup() {
173 function cleanup() {
174 lockscreen.clear(false);
174 lockscreen.clear(false);
175 delayMinutesTextField.text = "0"
175 delayMinutesTextField.text = "0"
176+ enteredLabel.text = "";
176
177
177 // Reset sizing
178 // Reset sizing
178 root.width = units.gu(80)
179 root.width = units.gu(80)
@@ -247,7 +248,6 @@ Rectangle {
247 }
248 }
248
249
249 function test_unlock(data) {
250 function test_unlock(data) {
250− enteredLabel.text = ""
251 minPinLengthTextField.text = data.minPinLength
251 minPinLengthTextField.text = data.minPinLength
252 maxPinLengthTextField.text = data.maxPinLength
252 maxPinLengthTextField.text = data.maxPinLength
253 pinPadCheckBox.checked = data.alphanumeric
253 pinPadCheckBox.checked = data.alphanumeric
@@ -462,6 +462,25 @@ Rectangle {
462 compare(label.text, "Device Locked")
462 compare(label.text, "Device Locked")
463 }
463 }
464
464
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+
465 function test_resize_data() {
484 function test_resize_data() {
466 return [
485 return [
467 { tag: "small", width: units.gu(40), height: units.gu(76) },
486 { tag: "small", width: units.gu(40), height: units.gu(76) },
tests/qmltests/Greeter/TestView.qml+10−3
@@ -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)
@@ -46,8 +48,9 @@ Item {
46 signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
48 signal _showPromptCalled(string text, bool isSecret, bool isDefaultPrompt)
47 signal _showLastChanceCalled()
49 signal _showLastChanceCalled()
48 signal _hideCalled()
50 signal _hideCalled()
49− signal _notifyAuthenticationSucceededCalled()
51+ signal _notifyAuthenticationSucceededCalled(bool showFakePassword)
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
@@ -69,14 +72,18 @@ Item {
69 _fullyShown = false;
72 _fullyShown = false;
70 }
73 }
71
74
72− function notifyAuthenticationSucceeded() {
75+ function notifyAuthenticationSucceeded(showFakePassword) {
73− _notifyAuthenticationSucceededCalled();
76+ _notifyAuthenticationSucceededCalled(showFakePassword);
74 }
77 }
75
78
76 function notifyAuthenticationFailed() {
79 function notifyAuthenticationFailed() {
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+171
@@ -20,6 +20,7 @@ import ".."
20import "../../../qml/Greeter"
20import "../../../qml/Greeter"
21import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
22import AccountsService 0.1
22import AccountsService 0.1
23+import Biometryd 0.0
23import GSettings 1.0
24import GSettings 1.0
24import IntegratedLightDM 0.1 as LightDM
25import IntegratedLightDM 0.1 as LightDM
25import Unity.Test 0.1 as UT
26import Unity.Test 0.1 as UT
@@ -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
@@ -143,12 +150,16 @@ Item {
143 teaseSpy.clear();
150 teaseSpy.clear();
144 sessionStartedSpy.clear();
151 sessionStartedSpy.clear();
145 activeChangedSpy.clear();
152 activeChangedSpy.clear();
153+ Biometryd.available = true;
154+ AccountsService.enableFingerprintIdentification = true;
155+ AccountsService.failedFingerprintLogins = 0;
146 viewShowMessageSpy.clear();
156 viewShowMessageSpy.clear();
147 viewShowPromptSpy.clear();
157 viewShowPromptSpy.clear();
148 viewShowLastChanceSpy.clear();
158 viewShowLastChanceSpy.clear();
149 viewHideSpy.clear();
159 viewHideSpy.clear();
150 viewAuthenticationSucceededSpy.clear();
160 viewAuthenticationSucceededSpy.clear();
151 viewAuthenticationFailedSpy.clear();
161 viewAuthenticationFailedSpy.clear();
162+ viewShowErrorMessageSpy.clear();
152 viewResetSpy.clear();
163 viewResetSpy.clear();
153 viewTryToUnlockSpy.clear();
164 viewTryToUnlockSpy.clear();
154 tryCompare(greeter, "waiting", false);
165 tryCompare(greeter, "waiting", false);
@@ -200,6 +211,14 @@ Item {
200 compare(greeter.shown, false);
211 compare(greeter.shown, false);
201 }
212 }
202
213
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+
203 function test_unlockPass() {
222 function test_unlockPass() {
204 selectUser("has-password");
223 selectUser("has-password");
205 tryCompare(viewShowPromptSpy, "count", 1);
224 tryCompare(viewShowPromptSpy, "count", 1);
@@ -553,5 +572,157 @@ Item {
553 compare(viewResetSpy.count, 1);
572 compare(viewResetSpy.count, 1);
554 tryCompare(viewShowPromptSpy, "count", 1);
573 tryCompare(viewShowPromptSpy, "count", 1);
555 }
574 }
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+ }
556 }
727 }
557}
728}
tests/qmltests/Greeter/tst_NarrowView.qml+22−1
@@ -128,7 +128,7 @@ Item {
128 text: "Authenticated"
128 text: "Authenticated"
129 onClicked: {
129 onClicked: {
130 if (successCheckBox.checked) {
130 if (successCheckBox.checked) {
131− loader.item.notifyAuthenticationSucceeded();
131+ loader.item.notifyAuthenticationSucceeded(fakePasswordCheckBox.checked);
132 } else {
132 } else {
133 loader.item.notifyAuthenticationFailed();
133 loader.item.notifyAuthenticationFailed();
134 }
134 }
@@ -140,6 +140,12 @@ Item {
140 Label {
140 Label {
141 text: "success"
141 text: "success"
142 }
142 }
143+ CheckBox {
144+ id: fakePasswordCheckBox
145+ }
146+ Label {
147+ text: "fake password"
148+ }
143 }
149 }
144 Row {
150 Row {
145 Button {
151 Button {
@@ -529,5 +535,20 @@ Item {
529 mouseClick(coverPage, coverPage.width/2, coverPage.height - units.gu(2));
535 mouseClick(coverPage, coverPage.width/2, coverPage.height - units.gu(2));
530 verify(!coverPage.shown);
536 verify(!coverPage.shown);
531 }
537 }
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+ }
532 }
553 }
533}
554}
tests/qmltests/Greeter/tst_WideView.qml+1−1
@@ -170,7 +170,7 @@ StyledItem {
170 text: "Authenticated"
170 text: "Authenticated"
171 onClicked: {
171 onClicked: {
172 if (successCheckBox.checked) {
172 if (successCheckBox.checked) {
173− loader.item.notifyAuthenticationSucceeded();
173+ loader.item.notifyAuthenticationSucceeded(false);
174 } else {
174 } else {
175 loader.item.notifyAuthenticationFailed();
175 loader.item.notifyAuthenticationFailed();
176 }
176 }
tests/qmltests/tst_Shell.qml+27
@@ -334,6 +334,33 @@ Rectangle {
334 }
334 }
335 }
335 }
336
336
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+ }
337 }
364 }
338 }
365 }
339 }
366 }