on xenial help sign in
Handle incoming calls, the greeter, and the OSK
5e2b709Michael Terry
qml/Shell.qml+4−2
@@ -376,7 +376,7 @@ Item {
376 id: inputMethod
376 id: inputMethod
377 objectName: "inputMethod"
377 objectName: "inputMethod"
378 anchors { fill: parent; topMargin: panel.panelHeight }
378 anchors { fill: parent; topMargin: panel.panelHeight }
379− z: notifications.useModal || panel.indicators.shown || wizard.active ? overlay.z + 1 : overlay.z - 1
379+ z: notifications.useModal || panel.indicators.shown || wizard.active || tutorial.running ? overlay.z + 1 : overlay.z - 1
380 }
380 }
381
381
382 Connections {
382 Connections {
@@ -407,7 +407,7 @@ Item {
407 hides: [launcher, panel.indicators]
407 hides: [launcher, panel.indicators]
408 tabletMode: shell.usageScenario != "phone"
408 tabletMode: shell.usageScenario != "phone"
409 launcherOffset: launcher.progress
409 launcherOffset: launcher.progress
410− forcedUnlock: wizard.active || tutorial.running
410+ forcedUnlock: wizard.active
411 background: shell.background
411 background: shell.background
412
412
413 // avoid overlapping with Launcher's edge drag area
413 // avoid overlapping with Launcher's edge drag area
@@ -570,6 +570,8 @@ Item {
570 objectName: "tutorial"
570 objectName: "tutorial"
571 anchors.fill: parent
571 anchors.fill: parent
572
572
573+ paused: callManager.hasCalls || greeter.shown
574+ keyboardVisible: inputMethod.state === "shown"
573 usageScenario: shell.usageScenario
575 usageScenario: shell.usageScenario
574 launcher: launcher
576 launcher: launcher
575 panel: panel
577 panel: panel
qml/Tutorial/Tutorial.qml+14
@@ -32,6 +32,8 @@ Item {
32 property Item panel
32 property Item panel
33 property Item stage
33 property Item stage
34 property string usageScenario
34 property string usageScenario
35+ property bool paused
36+ property bool keyboardVisible
35
37
36 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
38 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
37 readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
39 readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
@@ -74,6 +76,18 @@ Item {
74 value: root.usageScenario
76 value: root.usageScenario
75 }
77 }
76
78
79+ Binding {
80+ target: loader.item
81+ property: "paused"
82+ value: root.paused
83+ }
84+
85+ Binding {
86+ target: loader.item
87+ property: "keyboardVisible"
88+ value: root.keyboardVisible
89+ }
90+
77 Connections {
91 Connections {
78 target: loader.item
92 target: loader.item
79 onFinished: AccountsService.demoEdges = false
93 onFinished: AccountsService.demoEdges = false
qml/Tutorial/TutorialContent.qml+25−18
@@ -26,6 +26,8 @@ Item {
26 property Item panel
26 property Item panel
27 property Item stage
27 property Item stage
28 property string usageScenario
28 property string usageScenario
29+ property bool paused
30+ property bool keyboardVisible
29
31
30 readonly property bool launcherEnabled: !running || tutorialLeft.shown
32 readonly property bool launcherEnabled: !running || tutorialLeft.shown
31 readonly property bool spreadEnabled: !running || tutorialRight.shown
33 readonly property bool spreadEnabled: !running || tutorialRight.shown
@@ -49,14 +51,15 @@ Item {
49 anchors.fill: parent
51 anchors.fill: parent
50 launcher: root.launcher
52 launcher: root.launcher
51 hides: [launcher, panel.indicators]
53 hides: [launcher, panel.indicators]
54+ paused: root.paused
52
55
53 readonly property bool skipped: (root.usageScenario !== "phone"
56 readonly property bool skipped: (root.usageScenario !== "phone"
54 && root.usageScenario !== "tablet")
57 && root.usageScenario !== "tablet")
55 || AccountsService.demoEdgesCompleted.indexOf("left") != -1
58 || AccountsService.demoEdgesCompleted.indexOf("left") != -1
56− readonly property bool isReady: !skipped
59+ isReady: !skipped && !paused && !keyboardVisible
57
60
58 onSkippedChanged: if (skipped && shown) hide()
61 onSkippedChanged: if (skipped && shown) hide()
59− onIsReadyChanged: if (isReady) show()
62+ onIsReadyChanged: if (isReady && !shown) show()
60 onFinished: AccountsService.markDemoEdgeCompleted("left")
63 onFinished: AccountsService.markDemoEdgeCompleted("left")
61 }
64 }
62
65
@@ -66,11 +69,12 @@ Item {
66 anchors.fill: parent
69 anchors.fill: parent
67 panel: root.panel
70 panel: root.panel
68 hides: [launcher, panel.indicators]
71 hides: [launcher, panel.indicators]
72+ paused: root.paused
69
73
70 readonly property bool skipped: (root.usageScenario !== "phone"
74 readonly property bool skipped: (root.usageScenario !== "phone"
71 && root.usageScenario !== "tablet")
75 && root.usageScenario !== "tablet")
72 || AccountsService.demoEdgesCompleted.indexOf("top") != -1
76 || AccountsService.demoEdgesCompleted.indexOf("top") != -1
73− readonly property bool isReady: tutorialLeft.skipped && !skipped
77+ isReady: tutorialLeft.skipped && !skipped && !paused && !keyboardVisible
74
78
75 Timer {
79 Timer {
76 id: tutorialTopTimer
80 id: tutorialTopTimer
@@ -85,7 +89,7 @@ Item {
85 }
89 }
86
90
87 onSkippedChanged: if (skipped && shown) hide()
91 onSkippedChanged: if (skipped && shown) hide()
88− onIsReadyChanged: if (isReady) tutorialTopTimer.start()
92+ onIsReadyChanged: if (isReady && !shown) tutorialTopTimer.start()
89 onFinished: AccountsService.markDemoEdgeCompleted("top")
93 onFinished: AccountsService.markDemoEdgeCompleted("top")
90 }
94 }
91
95
@@ -96,10 +100,10 @@ Item {
96 stage: root.stage
100 stage: root.stage
97 usageScenario: root.usageScenario
101 usageScenario: root.usageScenario
98 hides: [launcher, panel.indicators]
102 hides: [launcher, panel.indicators]
103+ paused: root.paused
99
104
100 readonly property bool skipped: AccountsService.demoEdgesCompleted.indexOf("right") != -1
105 readonly property bool skipped: AccountsService.demoEdgesCompleted.indexOf("right") != -1
101− readonly property bool isReady: tutorialTop.skipped && !skipped
106+ isReady: tutorialTop.skipped && !skipped && !paused && !keyboardVisible && ApplicationManager.count >= 3
102− && ApplicationManager.count >= 3
103
107
104 Timer {
108 Timer {
105 id: tutorialRightTimer
109 id: tutorialRightTimer
@@ -114,7 +118,7 @@ Item {
114 }
118 }
115
119
116 onSkippedChanged: if (skipped && shown) hide()
120 onSkippedChanged: if (skipped && shown) hide()
117− onIsReadyChanged: if (isReady) tutorialRightTimer.start()
121+ onIsReadyChanged: if (isReady && !shown) tutorialRightTimer.start()
118 onFinished: AccountsService.markDemoEdgeCompleted("right")
122 onFinished: AccountsService.markDemoEdgeCompleted("right")
119 }
123 }
120
124
@@ -123,17 +127,20 @@ Item {
123 objectName: "tutorialBottom"
127 objectName: "tutorialBottom"
124 anchors.fill: parent
128 anchors.fill: parent
125 hides: [launcher, panel.indicators]
129 hides: [launcher, panel.indicators]
130+ paused: root.paused
126
131
132+ readonly property bool skipped: (root.usageScenario !== "phone"
133+ && root.usageScenario !== "tablet")
127− readonly property bool skipped: AccountsService.demoEdgesCompleted.indexOf("bottom") != -1
134+ || AccountsService.demoEdgesCompleted.indexOf("bottom") != -1
128− readonly property bool isReady: tutorialRight.skipped && !skipped &&
135+ isReady: tutorialRight.skipped && !skipped && !paused && !keyboardVisible &&
129− // focused app is an app known to have a bottom edge
136+ // focused app is an app known to have a bottom edge
130− (ApplicationManager.focusedApplicationId == "dialer-app" ||
137+ (ApplicationManager.focusedApplicationId == "dialer-app" ||
131− ApplicationManager.focusedApplicationId == "webbrowser-app" ||
138+ ApplicationManager.focusedApplicationId == "webbrowser-app" ||
132− ApplicationManager.focusedApplicationId == "messaging-app" ||
139+ ApplicationManager.focusedApplicationId == "messaging-app" ||
133− ApplicationManager.focusedApplicationId == "address-book-app" ||
140+ ApplicationManager.focusedApplicationId == "address-book-app" ||
134− ApplicationManager.focusedApplicationId == "camera-app" ||
141+ ApplicationManager.focusedApplicationId == "camera-app" ||
135− ApplicationManager.focusedApplicationId == "calculator-app" ||
142+ ApplicationManager.focusedApplicationId == "ubuntu-calculator-app" ||
136− ApplicationManager.focusedApplicationId == "ubuntu-clock-app")
143+ ApplicationManager.focusedApplicationId == "ubuntu-clock-app")
137
144
138 Timer {
145 Timer {
139 id: tutorialBottomTimer
146 id: tutorialBottomTimer
@@ -148,7 +155,7 @@ Item {
148 }
155 }
149
156
150 onSkippedChanged: if (skipped && shown) hide()
157 onSkippedChanged: if (skipped && shown) hide()
151− onIsReadyChanged: if (isReady) tutorialBottomTimer.start()
158+ onIsReadyChanged: if (isReady && !shown) tutorialBottomTimer.start()
152 onFinished: {
159 onFinished: {
153 AccountsService.markDemoEdgeCompleted("bottom");
160 AccountsService.markDemoEdgeCompleted("bottom");
154 root.finish();
161 root.finish();
qml/Tutorial/TutorialPage.qml+23−1
@@ -26,11 +26,18 @@ Showable {
26 property alias background: background
26 property alias background: background
27 property alias mouseArea: mouseArea
27 property alias mouseArea: mouseArea
28 property real opacityOverride: 1
28 property real opacityOverride: 1
29+ property bool paused
30+ property bool isReady
29
31
30 signal finished()
32 signal finished()
31
33
32 ////
34 ////
33
35
36+ QtObject {
37+ id: d
38+ property bool showOnUnpause
39+ }
40+
34 visible: false
41 visible: false
35 shown: false
42 shown: false
36
43
@@ -38,6 +45,17 @@ Showable {
38 onOpacityOverrideChanged: if (opacityOverride <= 0) hide()
45 onOpacityOverrideChanged: if (opacityOverride <= 0) hide()
39 property real _showOpacity: 0
46 property real _showOpacity: 0
40
47
48+ onPausedChanged: {
49+ if (paused && shown) {
50+ hide();
51+ } else if (!paused && d.showOnUnpause) {
52+ d.showOnUnpause = false;
53+ if (isReady) {
54+ show();
55+ }
56+ }
57+ }
58+
41 showAnimation: StandardAnimation {
59 showAnimation: StandardAnimation {
42 property: "_showOpacity"
60 property: "_showOpacity"
43 from: 0
61 from: 0
@@ -52,7 +70,11 @@ Showable {
52 duration: UbuntuAnimation.BriskDuration
70 duration: UbuntuAnimation.BriskDuration
53 onStopped: {
71 onStopped: {
54 root.visible = false;
72 root.visible = false;
73+ if (root.paused) {
74+ d.showOnUnpause = true;
75+ } else {
55− root.finished();
76+ root.finished();
77+ }
56 }
78 }
57 }
79 }
58
80
qml/Tutorial/TutorialRight.qml+1
@@ -30,6 +30,7 @@ TutorialPage {
30 // Else on desktop, fade out when the spread is shown
30 // Else on desktop, fade out when the spread is shown
31 Connections {
31 Connections {
32 target: usageScenario === "desktop" ? stage : null
32 target: usageScenario === "desktop" ? stage : null
33+ ignoreUnknownSignals: true
33 onSpreadShownChanged: if (stage.spreadShown && root.shown) root.hide()
34 onSpreadShownChanged: if (stage.spreadShown && root.shown) root.hide()
34 }
35 }
35
36
tests/mocks/Unity/Application/SurfaceManager.cpp+1
@@ -56,6 +56,7 @@ MirSurface *SurfaceManager::inputMethodSurface()
56 m_virtualKeyboard = new VirtualKeyboard;
56 m_virtualKeyboard = new VirtualKeyboard;
57 connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
57 connect(m_virtualKeyboard, &QObject::destroyed, this, [this](QObject *obj) {
58 MirSurface* surface = qobject_cast<MirSurface*>(obj);
58 MirSurface* surface = qobject_cast<MirSurface*>(obj);
59+ m_virtualKeyboard = nullptr;
59 Q_EMIT surfaceDestroyed(surface);
60 Q_EMIT surfaceDestroyed(surface);
60 });
61 });
61 Q_EMIT surfaceCreated(m_virtualKeyboard);
62 Q_EMIT surfaceCreated(m_virtualKeyboard);
tests/qmltests/Tutorial/tst_Tutorial.qml+196−15
@@ -19,6 +19,7 @@ import QtTest 1.0
19import AccountsService 0.1
19import AccountsService 0.1
20import IntegratedLightDM 0.1 as LightDM
20import IntegratedLightDM 0.1 as LightDM
21import Ubuntu.Components 1.1
21import Ubuntu.Components 1.1
22+import Ubuntu.Telephony 0.1 as Telephony
22import Unity.Application 0.1
23import Unity.Application 0.1
23import Unity.Test 0.1 as UT
24import Unity.Test 0.1 as UT
24
25
@@ -45,6 +46,11 @@ Item {
45 }
46 }
46 }
47 }
47
48
49+ Telephony.CallEntry {
50+ id: phoneCall
51+ phoneNumber: "+447812221111"
52+ }
53+
48 Component.onCompleted: {
54 Component.onCompleted: {
49 // must set the mock mode before loading the Shell
55 // must set the mock mode before loading the Shell
50 LightDM.Greeter.mockMode = "single-pin";
56 LightDM.Greeter.mockMode = "single-pin";
@@ -77,13 +83,29 @@ Item {
77
83
78 Rectangle {
84 Rectangle {
79 id: buttons
85 id: buttons
80− color: "white"
86+ color: "black"
81 width: units.gu(30)
87 width: units.gu(30)
82 height: shellLoader.height
88 height: shellLoader.height
83
89
84 Column {
90 Column {
85 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
91 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
86 spacing: units.gu(1)
92 spacing: units.gu(1)
93+ Row {
94+ anchors { left: parent.left; right: parent.right }
95+ Button {
96+ text: "Hide Greeter"
97+ onClicked: {
98+ if (shellLoader.status !== Loader.Ready)
99+ return;
100+
101+ var greeter = testCase.findChild(shellLoader.item, "greeter");
102+ if (greeter.shown) {
103+ greeter.hide();
104+ }
105+ }
106+ }
107+ }
108+
87 Row {
109 Row {
88 anchors { left: parent.left; right: parent.right }
110 anchors { left: parent.left; right: parent.right }
89 Button {
111 Button {
@@ -98,6 +120,62 @@ Item {
98 }
120 }
99 }
121 }
100 }
122 }
123+
124+ Row {
125+ anchors { left: parent.left; right: parent.right }
126+ CheckBox {
127+ onCheckedChanged: {
128+ if (checked) {
129+ callManager.foregroundCall = phoneCall;
130+ } else {
131+ callManager.foregroundCall = null;
132+ }
133+ }
134+ }
135+ Label {
136+ text: "Active Call"
137+ anchors.verticalCenter: parent.verticalCenter
138+ }
139+ }
140+
141+ Row {
142+ anchors { left: parent.left; right: parent.right }
143+ CheckBox {
144+ activeFocusOnPress: false
145+ onCheckedChanged: {
146+ var surface = SurfaceManager.inputMethodSurface();
147+ if (checked) {
148+ surface.setState(Mir.RestoredState);
149+ } else {
150+ surface.setState(Mir.MinimizedState);
151+ }
152+ }
153+ }
154+ Label {
155+ text: "Input Method"
156+ anchors.verticalCenter: parent.verticalCenter
157+ }
158+ }
159+
160+ Row {
161+ anchors { left: parent.left; right: parent.right }
162+ CheckBox {
163+ onCheckedChanged: {
164+ if (shellLoader.status !== Loader.Ready)
165+ return;
166+
167+ if (checked) {
168+ shellLoader.item.usageScenario = "desktop";
169+ } else {
170+ shellLoader.item.usageScenario = "phone";
171+ }
172+ }
173+ }
174+ Label {
175+ text: "Desktop Mode"
176+ anchors.verticalCenter: parent.verticalCenter
177+ }
178+ }
101 }
179 }
102 }
180 }
103 }
181 }
@@ -114,14 +192,16 @@ Item {
114 function init() {
192 function init() {
115 tryCompare(shell, "enabled", true); // enabled by greeter when ready
193 tryCompare(shell, "enabled", true); // enabled by greeter when ready
116
194
117− var greeter = findChild(shell, "greeter");
195+ shell.usageScenario = "phone";
118− tryCompare(greeter, "shown", true);
196+ callManager.foregroundCall = null;
119−
120 AccountsService.demoEdges = false;
197 AccountsService.demoEdges = false;
121 AccountsService.demoEdgesCompleted = [];
198 AccountsService.demoEdgesCompleted = [];
122 AccountsService.demoEdges = true;
199 AccountsService.demoEdges = true;
123
200
124− tryCompare(greeter, "shown", false);
201+ LightDM.Greeter.hideGreeter();
202+
203+ var tutorialLeft = findChild(shell, "tutorialLeft");
204+ tryCompare(tutorialLeft, "opacity", 1);
125 }
205 }
126
206
127 function cleanup() {
207 function cleanup() {
@@ -485,20 +565,121 @@ Item {
485 tryCompare(tutorialBottom, "shown", true);
565 tryCompare(tutorialBottom, "shown", true);
486 }
566 }
487
567
488− function disabled_test_skipOnDesktop() {
568+ function test_activeCallInterruptsTutorial() {
489− var tutorial = findChild(shell, "tutorial");
569+ var tutorialLeft = findChild(shell, "tutorialLeft");
490− tryCompare(tutorial, "active", true);
570+ verify(tutorialLeft.shown);
491− tryCompare(tutorial, "running", true);
571+ verify(!tutorialLeft.paused);
492
572
493− shell.usageScenario = "desktop";
573+ callManager.foregroundCall = phoneCall;
494− tryCompare(tutorial, "active", false);
574+ verify(!tutorialLeft.shown);
495− tryCompare(tutorial, "running", false);
575+ verify(tutorialLeft.paused);
576+ tryCompare(tutorialLeft, "visible", false);
577+
578+ callManager.foregroundCall = null;
579+ verify(tutorialLeft.shown);
580+ verify(!tutorialLeft.paused);
581+ }
582+
583+ function test_greeterInterruptsTutorial() {
584+ var tutorialLeft = findChild(shell, "tutorialLeft");
585+ verify(tutorialLeft.shown);
586+ verify(!tutorialLeft.paused);
587+
588+ LightDM.Greeter.showGreeter();
589+ verify(!tutorialLeft.shown);
590+ verify(tutorialLeft.paused);
591+ tryCompare(tutorialLeft, "visible", false);
592+
593+ LightDM.Greeter.hideGreeter();
594+ tryCompare(tutorialLeft, "shown", true);
595+ verify(!tutorialLeft.paused);
596+ }
597+
598+ function test_interruptionChecksReadyStateWhenDone() {
599+ // If we're done with an interruption (like active call), make sure
600+ // that we don't blindly resume the tutorial -- our trigger
601+ // conditions still need to be met. For example, there need to be
602+ // enough apps open for the right edge tutorial.
603+
604+ openTutorialRight();
605+
606+ var tutorialRight = findChild(shell, "tutorialRight");
607+ verify(tutorialRight.isReady);
608+ verify(tutorialRight.shown);
609+ verify(!tutorialRight.paused);
610+
611+ callManager.foregroundCall = phoneCall;
612+ killApps();
613+ callManager.foregroundCall = null;
614+
615+ verify(!tutorialRight.isReady);
616+ verify(!tutorialRight.shown);
617+ verify(!tutorialRight.paused);
618+ compare(AccountsService.demoEdgesCompleted, ["left", "top"]);
496 }
619 }
497
620
498− function disabled_test_interrupted() {
621+ function test_desktopOnlyShowsTutorialRight() {
499− goToPage("tutorialLeft");
622+ shell.usageScenario = "desktop";
623+
624+ var tutorialLeft = findChild(shell, "tutorialLeft");
625+ var tutorialTop = findChild(shell, "tutorialTop");
626+ var tutorialRight = findChild(shell, "tutorialRight");
627+ var tutorialBottom = findChild(shell, "tutorialBottom");
628+ verify(tutorialLeft.skipped);
629+ verify(tutorialTop.skipped);
630+ verify(!tutorialRight.skipped);
631+ verify(tutorialBottom.skipped);
632+ compare(AccountsService.demoEdgesCompleted, []);
633+
500 ApplicationManager.startApplication("dialer-app");
634 ApplicationManager.startApplication("dialer-app");
501− checkFinished();
635+ ApplicationManager.startApplication("camera-app");
636+ ApplicationManager.startApplication("messaging-app");
637+ tryCompare(tutorialRight, "isReady", true);
638+ tryCompare(tutorialRight, "shown", true);
639+ }
640+
641+ function test_oskDoesNotHideTutorial() {
642+ var tutorialLeft = findChild(shell, "tutorialLeft");
643+ verify(tutorialLeft.shown);
644+
645+ var surface = SurfaceManager.inputMethodSurface();
646+ surface.setState(Mir.RestoredState);
647+
648+ var inputMethod = findInvisibleChild(shell, "inputMethod");
649+ tryCompare(inputMethod, "state", "shown");
650+
651+ verify(tutorialLeft.shown);
652+ }
653+
654+ function test_oskPreventsTutorial() {
655+ var surface = SurfaceManager.inputMethodSurface();
656+ var inputMethod = findInvisibleChild(shell, "inputMethod");
657+
658+ AccountsService.demoEdges = false;
659+ surface.setState(Mir.RestoredState);
660+ tryCompare(inputMethod, "state", "shown");
661+
662+ AccountsService.demoEdges = true;
663+ var tutorialLeft = findChild(shell, "tutorialLeft");
664+ verify(!tutorialLeft.shown);
665+
666+ surface.setState(Mir.MinimizedState);
667+ tryCompare(inputMethod, "state", "hidden");
668+ verify(tutorialLeft.shown);
669+ }
670+
671+ function test_accountsServiceSettings() {
672+ var tutorialLeft = findChild(shell, "tutorialLeft");
673+ verify(tutorialLeft != null);
674+ verify(tutorialLeft.shown);
675+
676+ AccountsService.demoEdges = false;
677+ verify(findChild(shell, "tutorialLeft") == null);
678+
679+ AccountsService.demoEdges = true;
680+ tutorialLeft = findChild(shell, "tutorialLeft");
681+ verify(tutorialLeft != null);
682+ verify(tutorialLeft.shown);
502 }
683 }
503 }
684 }
504}
685}