on xenial help sign in
Merge lp:~dandrader/unity8/app-state-handling
ab328dbDaniel d'Andrada
CMakeLists.txt+1−1
@@ -56,7 +56,7 @@ find_package(Qt5DBus 5.2 REQUIRED)
56find_package(Qt5Concurrent 5.2 REQUIRED)
56find_package(Qt5Concurrent 5.2 REQUIRED)
57find_package(Qt5Sql 5.2 REQUIRED)
57find_package(Qt5Sql 5.2 REQUIRED)
58
58
59−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
59+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=7)
60
60
61# Standard install paths
61# Standard install paths
62include(GNUInstallDirs)
62include(GNUInstallDirs)
debian/control+1−1
@@ -28,7 +28,7 @@ Build-Depends: cmake,
28 libqt5xmlpatterns5-dev,
28 libqt5xmlpatterns5-dev,
29 libsystemsettings-dev,
29 libsystemsettings-dev,
30 libudev-dev,
30 libudev-dev,
31− libunity-api-dev (>= 7.98),
31+ libunity-api-dev (>= 7.99),
32 libusermetricsoutput1-dev,
32 libusermetricsoutput1-dev,
33 libxcb1-dev,
33 libxcb1-dev,
34 pkg-config,
34 pkg-config,
plugins/Greeter/Unity/Launcher/CMakeLists.txt−1
@@ -1,5 +1,4 @@
1pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
1pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
4
3
5add_definitions(-DSM_BUSNAME=systemBus)
4add_definitions(-DSM_BUSNAME=systemBus)
plugins/Unity/Launcher/CMakeLists.txt−1
@@ -1,5 +1,4 @@
1pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
1pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
3pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
2pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
4
3
5add_definitions(-DSM_BUSNAME=systemBus)
4add_definitions(-DSM_BUSNAME=systemBus)
qml/Components/Unity8Settings.qmlnew file+50
@@ -0,0 +1,50 @@
1+/*
2+ * Copyright (C) 2015 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+import QtQuick 2.0
18+import GSettings 1.0
19+
20+QtObject {
21+ id: root
22+
23+ property string usageMode: "Staged"
24+
25+ // FIXME: Works around a bug where if we change a Loader's source in response to a GSettings
26+ // property change in the same event loop, the Loader's previously loaded component does not
27+ // get destroyed and its bindings continue to operate!
28+ //
29+ // Shouldn't be needed after
30+ // https://code.launchpad.net/~lukas-kde/gsettings-qt/queued-processing/+merge/259883 gets
31+ // merged.
32+ property var timer: Timer {
33+ interval: 1
34+ onTriggered: { root.usageMode = root.wrapped.usageMode; }
35+ }
36+ property var wrappedConnections: Connections {
37+ target: root.wrapped
38+ ignoreUnknownSignals: true // don't spam us
39+ onUsageModeChanged: { root.timer.start(); }
40+ }
41+ property var wrapped: GSettings {
42+ schema.id: "com.canonical.Unity8"
43+ Component.onCompleted: {
44+ // init the value. it's a dynamic prop, so we have to check first
45+ if (root.usageMode) {
46+ root.usageMode = root.wrapped.usageMode;
47+ }
48+ }
49+ }
50+}
qml/OrientedShell.qml+3−3
@@ -43,7 +43,7 @@ Rectangle {
43
43
44
44
45 // to be overwritten by tests
45 // to be overwritten by tests
46− property var usageModeSettings: GSettings { schema.id: "com.canonical.Unity8" }
46+ property var unity8Settings: Unity8Settings {}
47 property var oskSettings: GSettings { schema.id: "com.canonical.keyboard.maliit" }
47 property var oskSettings: GSettings { schema.id: "com.canonical.keyboard.maliit" }
48
48
49 property int physicalOrientation: Screen.orientation
49 property int physicalOrientation: Screen.orientation
@@ -160,9 +160,9 @@ Rectangle {
160 // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
160 // TODO: Factor in the connected input devices (eg: physical keyboard, mouse, touchscreen),
161 // what's the output device (eg: big TV, desktop monitor, phone display), etc.
161 // what's the output device (eg: big TV, desktop monitor, phone display), etc.
162 usageScenario: {
162 usageScenario: {
163− if (root.usageModeSettings.usageMode === "Windowed") {
163+ if (root.unity8Settings.usageMode === "Windowed") {
164 return "desktop";
164 return "desktop";
165− } else if (root.usageModeSettings.usageMode === "Staged") {
165+ } else if (root.unity8Settings.usageMode === "Staged") {
166 if (deviceConfiguration.category === "phone") {
166 if (deviceConfiguration.category === "phone") {
167 return "phone";
167 return "phone";
168 } else {
168 } else {
qml/Shell.qml+10−12
@@ -191,12 +191,6 @@ Item {
191 z: dialogs.z + 10
191 z: dialogs.z + 10
192 }
192 }
193
193
194− Binding {
195− target: ApplicationManager
196− property: "forceDashActive"
197− value: launcher.shown || launcher.dashSwipe
198− }
199−
200 WindowKeysFilter {
194 WindowKeysFilter {
201 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
195 Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
202 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
196 Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
@@ -352,6 +346,16 @@ Item {
352 property: "beingResized"
346 property: "beingResized"
353 value: shell.beingResized
347 value: shell.beingResized
354 }
348 }
349+ Binding {
350+ target: applicationsDisplayLoader.item
351+ property: "keepDashRunning"
352+ value: launcher.shown || launcher.dashSwipe
353+ }
354+ Binding {
355+ target: applicationsDisplayLoader.item
356+ property: "suspended"
357+ value: greeter.shown
358+ }
355 Binding {
359 Binding {
356 target: applicationsDisplayLoader.item
360 target: applicationsDisplayLoader.item
357 property: "altTabPressed"
361 property: "altTabPressed"
@@ -459,12 +463,6 @@ Item {
459 }
463 }
460
464
461 onEmergencyCall: startLockedApp("dialer-app")
465 onEmergencyCall: startLockedApp("dialer-app")
462−
463− Binding {
464− target: ApplicationManager
465− property: "suspended"
466− value: greeter.shown
467− }
468 }
466 }
469 }
467 }
470
468
qml/Stages/DesktopStage.qml+13
@@ -39,6 +39,8 @@ Rectangle {
39 property int shellPrimaryOrientation
39 property int shellPrimaryOrientation
40 property int nativeOrientation
40 property int nativeOrientation
41 property bool beingResized: false
41 property bool beingResized: false
42+ property bool keepDashRunning: true
43+ property bool suspended: false
42
44
43 // functions to be called from outside
45 // functions to be called from outside
44 function updateFocusedAppOrientation() { /* TODO */ }
46 function updateFocusedAppOrientation() { /* TODO */ }
@@ -215,6 +217,17 @@ Rectangle {
215 }
217 }
216 }
218 }
217
219
220+ Binding {
221+ target: ApplicationManager.get(index)
222+ property: "requestedState"
223+ // TODO: figure out some lifecycle policy, like suspending minimized apps
224+ // if running on a tablet or something.
225+ // TODO: If the device has a dozen suspended apps because it was running
226+ // in staged mode, when it switches to Windowed mode it will suddenly
227+ // resume all those apps at once. We might want to avoid that.
228+ value: ApplicationInfoInterface.RequestedRunning // Always running for now
229+ }
230+
218 function maximize() {
231 function maximize() {
219 minimized = false;
232 minimized = false;
220 maximized = true;
233 maximized = true;
qml/Stages/PhoneStage.qml+10
@@ -36,6 +36,8 @@ Rectangle {
36 property bool altTabEnabled: true
36 property bool altTabEnabled: true
37 property real startScale: 1.1
37 property real startScale: 1.1
38 property real endScale: 0.7
38 property real endScale: 0.7
39+ property bool keepDashRunning: true
40+ property bool suspended: false
39 property int shellOrientationAngle: 0
41 property int shellOrientationAngle: 0
40 property int shellOrientation
42 property int shellOrientation
41 property int shellPrimaryOrientation
43 property int shellPrimaryOrientation
@@ -428,6 +430,14 @@ Rectangle {
428 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
430 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
429 focusFirstApp: root.focusFirstApp
431 focusFirstApp: root.focusFirstApp
430
432
433+ Binding {
434+ target: appDelegate.application
435+ property: "requestedState"
436+ value: (isDash && root.keepDashRunning) || (!root.suspended && appDelegate.focus)
437+ ? ApplicationInfoInterface.RequestedRunning
438+ : ApplicationInfoInterface.RequestedSuspended
439+ }
440+
431 readonly property bool isDash: model.appId == "unity8-dash"
441 readonly property bool isDash: model.appId == "unity8-dash"
432
442
433 z: isDash && !spreadView.active ? -1 : behavioredIndex
443 z: isDash && !spreadView.active ? -1 : behavioredIndex
qml/Stages/TabletStage.qml+14
@@ -36,6 +36,8 @@ Rectangle {
36 property bool spreadEnabled: true // If false, animations and right edge will be disabled
36 property bool spreadEnabled: true // If false, animations and right edge will be disabled
37
37
38 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
38 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
39+ property bool keepDashRunning: true
40+ property bool suspended: false
39 property int shellOrientationAngle: 0
41 property int shellOrientationAngle: 0
40 property int shellOrientation
42 property int shellOrientation
41 property int shellPrimaryOrientation
43 property int shellPrimaryOrientation
@@ -117,6 +119,7 @@ Rectangle {
117
119
118 QtObject {
120 QtObject {
119 id: priv
121 id: priv
122+ objectName: "stagesPriv"
120
123
121 property string focusedAppId: ApplicationManager.focusedApplicationId
124 property string focusedAppId: ApplicationManager.focusedApplicationId
122 readonly property var focusedAppDelegate: {
125 readonly property var focusedAppDelegate: {
@@ -626,6 +629,17 @@ Rectangle {
626
629
627 readonly property bool isDash: model.appId == "unity8-dash"
630 readonly property bool isDash: model.appId == "unity8-dash"
628
631
632+ Binding {
633+ target: spreadTile.application
634+ property: "requestedState"
635+ value: (spreadTile.isDash && root.keepDashRunning)
636+ ||
637+ (!root.suspended && (model.appId == priv.mainStageAppId
638+ || model.appId == priv.sideStageAppId))
639+ ? ApplicationInfoInterface.RequestedRunning
640+ : ApplicationInfoInterface.RequestedSuspended
641+ }
642+
629 // FIXME: A regular binding doesn't update any more after closing an app.
643 // FIXME: A regular binding doesn't update any more after closing an app.
630 // Using a Binding for now.
644 // Using a Binding for now.
631 Binding {
645 Binding {
tests/mocks/Unity/Application/ApplicationInfo.cpp+40−6
@@ -30,7 +30,7 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
30 : ApplicationInfoInterface(appId, parent)
30 : ApplicationInfoInterface(appId, parent)
31 , m_appId(appId)
31 , m_appId(appId)
32 , m_stage(MainStage)
32 , m_stage(MainStage)
33− , m_state(Starting)
33+ , m_state(Stopped)
34 , m_focused(false)
34 , m_focused(false)
35 , m_fullscreen(false)
35 , m_fullscreen(false)
36 , m_session(0)
36 , m_session(0)
@@ -39,6 +39,7 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
39 Qt::InvertedPortraitOrientation |
39 Qt::InvertedPortraitOrientation |
40 Qt::InvertedLandscapeOrientation)
40 Qt::InvertedLandscapeOrientation)
41 , m_rotatesWindowContents(false)
41 , m_rotatesWindowContents(false)
42+ , m_requestedState(RequestedRunning)
42 , m_manualSurfaceCreation(false)
43 , m_manualSurfaceCreation(false)
43{
44{
44}
45}
@@ -46,7 +47,7 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
46ApplicationInfo::ApplicationInfo(QObject *parent)
47ApplicationInfo::ApplicationInfo(QObject *parent)
47 : ApplicationInfoInterface(QString(), parent)
48 : ApplicationInfoInterface(QString(), parent)
48 , m_stage(MainStage)
49 , m_stage(MainStage)
49− , m_state(Starting)
50+ , m_state(Stopped)
50 , m_focused(false)
51 , m_focused(false)
51 , m_fullscreen(false)
52 , m_fullscreen(false)
52 , m_session(0)
53 , m_session(0)
@@ -55,6 +56,7 @@ ApplicationInfo::ApplicationInfo(QObject *parent)
55 Qt::InvertedPortraitOrientation |
56 Qt::InvertedPortraitOrientation |
56 Qt::InvertedLandscapeOrientation)
57 Qt::InvertedLandscapeOrientation)
57 , m_rotatesWindowContents(false)
58 , m_rotatesWindowContents(false)
59+ , m_requestedState(RequestedRunning)
58 , m_manualSurfaceCreation(false)
60 , m_manualSurfaceCreation(false)
59{
61{
60}
62}
@@ -74,7 +76,6 @@ void ApplicationInfo::createSession()
74
76
75void ApplicationInfo::setSession(Session* session)
77void ApplicationInfo::setSession(Session* session)
76{
78{
77− qDebug() << "Application::setSession - appId=" << appId() << "session=" << session;
78 if (m_session == session)
79 if (m_session == session)
79 return;
80 return;
80
81
@@ -91,6 +92,8 @@ void ApplicationInfo::setSession(Session* session)
91 m_session->setApplication(this);
92 m_session->setApplication(this);
92 m_session->setParent(this);
93 m_session->setParent(this);
93 SessionManager::singleton()->registerSession(m_session);
94 SessionManager::singleton()->registerSession(m_session);
95+ connect(m_session, &Session::surfaceChanged,
96+ this, &ApplicationInfo::onSessionSurfaceChanged);
94
97
95 if (!m_manualSurfaceCreation) {
98 if (!m_manualSurfaceCreation) {
96 QTimer::singleShot(500, m_session, SLOT(createSurface()));
99 QTimer::singleShot(500, m_session, SLOT(createSurface()));
@@ -161,11 +164,12 @@ void ApplicationInfo::setState(State value)
161 m_state = value;
164 m_state = value;
162 Q_EMIT stateChanged(value);
165 Q_EMIT stateChanged(value);
163
166
164− if (!m_manualSurfaceCreation && m_state == ApplicationInfo::Running) {
167+ if (!m_manualSurfaceCreation && !m_session && m_state == ApplicationInfo::Starting) {
165 QTimer::singleShot(500, this, SLOT(createSession()));
168 QTimer::singleShot(500, this, SLOT(createSession()));
166 } else if (m_state == ApplicationInfo::Stopped) {
169 } else if (m_state == ApplicationInfo::Stopped) {
167− delete m_session;
170+ Session *session = m_session;
168− m_session = nullptr;
171+ setSession(nullptr);
172+ delete session;
169 }
173 }
170 }
174 }
171}
175}
@@ -213,3 +217,33 @@ void ApplicationInfo::setRotatesWindowContents(bool value)
213{
217{
214 m_rotatesWindowContents = value;
218 m_rotatesWindowContents = value;
215}
219}
220+
221+ApplicationInfo::RequestedState ApplicationInfo::requestedState() const
222+{
223+ return m_requestedState;
224+}
225+
226+void ApplicationInfo::setRequestedState(RequestedState value)
227+{
228+ if (m_requestedState != value) {
229+ m_requestedState = value;
230+ Q_EMIT requestedStateChanged(m_requestedState);
231+
232+ if (m_requestedState == RequestedRunning && m_state == Suspended) {
233+ setState(Running);
234+ } else if (m_requestedState == RequestedSuspended && m_state == Running) {
235+ setState(Suspended);
236+ }
237+ }
238+}
239+
240+void ApplicationInfo::onSessionSurfaceChanged(MirSurfaceItem* surface)
241+{
242+ if (surface != nullptr && m_state == Starting) {
243+ if (m_requestedState == RequestedRunning) {
244+ setState(Running);
245+ } else {
246+ setState(Suspended);
247+ }
248+ }
249+}
tests/mocks/Unity/Application/ApplicationInfo.h+7
@@ -46,6 +46,9 @@ public:
46 ApplicationInfo(const QString &appId, QObject *parent = nullptr);
46 ApplicationInfo(const QString &appId, QObject *parent = nullptr);
47 ~ApplicationInfo();
47 ~ApplicationInfo();
48
48
49+ RequestedState requestedState() const override;
50+ void setRequestedState(RequestedState) override;
51+
49 void setIconId(const QString &iconId);
52 void setIconId(const QString &iconId);
50 void setScreenshotId(const QString &screenshotId);
53 void setScreenshotId(const QString &screenshotId);
51
54
@@ -101,6 +104,9 @@ Q_SIGNALS:
101public Q_SLOTS:
104public Q_SLOTS:
102 Q_INVOKABLE void createSession();
105 Q_INVOKABLE void createSession();
103
106
107+private Q_SLOTS:
108+ void onSessionSurfaceChanged(MirSurfaceItem*);
109+
104private:
110private:
105 void setIcon(const QUrl &value);
111 void setIcon(const QUrl &value);
106
112
@@ -116,6 +122,7 @@ private:
116 Session* m_session;
122 Session* m_session;
117 Qt::ScreenOrientations m_supportedOrientations;
123 Qt::ScreenOrientations m_supportedOrientations;
118 bool m_rotatesWindowContents;
124 bool m_rotatesWindowContents;
125+ RequestedState m_requestedState;
119
126
120 bool m_manualSurfaceCreation;
127 bool m_manualSurfaceCreation;
121};
128};
tests/mocks/Unity/Application/ApplicationManager.cpp+2−49
@@ -49,8 +49,6 @@ ApplicationManager *ApplicationManager::singleton()
49
49
50ApplicationManager::ApplicationManager(QObject *parent)
50ApplicationManager::ApplicationManager(QObject *parent)
51 : ApplicationManagerInterface(parent)
51 : ApplicationManagerInterface(parent)
52− , m_suspended(false)
53− , m_forceDashActive(false)
54{
52{
55 m_roleNames.insert(RoleSession, "session");
53 m_roleNames.insert(RoleSession, "session");
56 m_roleNames.insert(RoleFullscreen, "fullscreen");
54 m_roleNames.insert(RoleFullscreen, "fullscreen");
@@ -217,7 +215,7 @@ ApplicationInfo* ApplicationManager::startApplication(const QString &appId,
217 && application->stage() == ApplicationInfo::SideStage) {
215 && application->stage() == ApplicationInfo::SideStage) {
218 application->setStage(ApplicationInfo::MainStage);
216 application->setStage(ApplicationInfo::MainStage);
219 }
217 }
220− application->setState(ApplicationInfo::Running);
218+ application->setState(ApplicationInfo::Starting);
221
219
222 return application;
220 return application;
223}
221}
@@ -262,50 +260,6 @@ QString ApplicationManager::focusedApplicationId() const {
262 return QString();
260 return QString();
263}
261}
264
262
265−bool ApplicationManager::suspended() const
266−{
267− return m_suspended;
268−}
269−
270−void ApplicationManager::setSuspended(bool suspended)
271−{
272− ApplicationInfo *focusedApp = findApplication(focusedApplicationId());
273− if (focusedApp) {
274− if (suspended) {
275− focusedApp->setState(ApplicationInfo::Suspended);
276− } else {
277− focusedApp->setState(ApplicationInfo::Running);
278− }
279− }
280− m_suspended = suspended;
281− Q_EMIT suspendedChanged();
282−}
283−
284−bool ApplicationManager::forceDashActive() const
285−{
286− return m_forceDashActive;
287−}
288−
289−void ApplicationManager::setForceDashActive(bool forceDashActive)
290−{
291− if (m_forceDashActive == forceDashActive) {
292− return;
293− }
294−
295− ApplicationInfo *dash = findApplication("unity8-dash");
296− if (dash) {
297− if (forceDashActive) {
298− dash->setState(ApplicationInfo::Running);
299− } else {
300− if (!dash->focused()) {
301− dash->setState(ApplicationInfo::Suspended);
302− }
303− }
304− }
305− m_forceDashActive = forceDashActive;
306− Q_EMIT forceDashActiveChanged();
307−}
308−
309bool ApplicationManager::focusApplication(const QString &appId)
263bool ApplicationManager::focusApplication(const QString &appId)
310{
264{
311 ApplicationInfo *application = findApplication(appId);
265 ApplicationInfo *application = findApplication(appId);
@@ -316,13 +270,11 @@ bool ApplicationManager::focusApplication(const QString &appId)
316 for (ApplicationInfo *app : m_runningApplications) {
270 for (ApplicationInfo *app : m_runningApplications) {
317 if (app->focused()) {
271 if (app->focused()) {
318 app->setFocused(false);
272 app->setFocused(false);
319− app->setState(ApplicationInfo::Suspended);
320 }
273 }
321 }
274 }
322
275
323 // focus this app
276 // focus this app
324 application->setFocused(true);
277 application->setFocused(true);
325− application->setState(ApplicationInfo::Running);
326
278
327 // move app to top of stack
279 // move app to top of stack
328 move(m_runningApplications.indexOf(application), 0);
280 move(m_runningApplications.indexOf(application), 0);
@@ -394,6 +346,7 @@ void ApplicationManager::buildListOfAvailableApplications()
394 application->setScreenshotId("gallery");
346 application->setScreenshotId("gallery");
395 application->setIconId("gallery");
347 application->setIconId("gallery");
396 application->setFullscreen(true);
348 application->setFullscreen(true);
349+ application->setStage(ApplicationInfo::MainStage);
397 m_availableApplications.append(application);
350 m_availableApplications.append(application);
398
351
399 application = new ApplicationInfo(this);
352 application = new ApplicationInfo(this);
tests/mocks/Unity/Application/ApplicationManager.h−7
@@ -69,11 +69,6 @@ class ApplicationManager : public ApplicationManagerInterface {
69 Q_INVOKABLE bool stopApplication(const QString &appId) override;
69 Q_INVOKABLE bool stopApplication(const QString &appId) override;
70
70
71 QString focusedApplicationId() const override;
71 QString focusedApplicationId() const override;
72− bool suspended() const override;
73− void setSuspended(bool suspended) override;
74−
75− bool forceDashActive() const override;
76− void setForceDashActive(bool forceDashActive) override;
77
72
78 // Only for testing
73 // Only for testing
79 QStringList availableApplications();
74 QStringList availableApplications();
@@ -96,8 +91,6 @@ class ApplicationManager : public ApplicationManagerInterface {
96 void remove(ApplicationInfo* application);
91 void remove(ApplicationInfo* application);
97 void buildListOfAvailableApplications();
92 void buildListOfAvailableApplications();
98 void onWindowCreated();
93 void onWindowCreated();
99− bool m_suspended;
100− bool m_forceDashActive;
101 QList<ApplicationInfo*> m_runningApplications;
94 QList<ApplicationInfo*> m_runningApplications;
102 QList<ApplicationInfo*> m_availableApplications;
95 QList<ApplicationInfo*> m_availableApplications;
103 QTimer m_windowCreatedTimer;
96 QTimer m_windowCreatedTimer;
tests/plugins/Greeter/Unity/Launcher/CMakeLists.txt−1
@@ -1,5 +1,4 @@
1pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
1pkg_check_modules(LAUNCHER_API REQUIRED unity-shell-launcher=7)
2−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
3
2
4include_directories(
3include_directories(
5 ${CMAKE_CURRENT_SOURCE_DIR}
4 ${CMAKE_CURRENT_SOURCE_DIR}
tests/plugins/Unity/Launcher/launchermodeltest.cpp+3−4
@@ -39,6 +39,9 @@ class MockApp: public unity::shell::application::ApplicationInfoInterface
39 Q_OBJECT
39 Q_OBJECT
40public:
40public:
41 MockApp(const QString &appId, QObject *parent = 0): ApplicationInfoInterface(appId, parent), m_appId(appId), m_focused(false) { }
41 MockApp(const QString &appId, QObject *parent = 0): ApplicationInfoInterface(appId, parent), m_appId(appId), m_focused(false) { }
42+
43+ RequestedState requestedState() const override { return RequestedRunning; }
44+ void setRequestedState(RequestedState) override {}
42 QString appId() const override { return m_appId; }
45 QString appId() const override { return m_appId; }
43 QString name() const override { return "mock"; }
46 QString name() const override { return "mock"; }
44 QString comment() const override { return "this is a mock"; }
47 QString comment() const override { return "this is a mock"; }
@@ -117,10 +120,6 @@ public:
117 endRemoveRows();
120 endRemoveRows();
118 }
121 }
119 bool requestFocusApplication(const QString &appId) override { Q_UNUSED(appId); return true; }
122 bool requestFocusApplication(const QString &appId) override { Q_UNUSED(appId); return true; }
120− bool suspended() const override { return false; }
121− void setSuspended(bool) override {}
122− bool forceDashActive() const override { return false; }
123− void setForceDashActive(bool) override {}
124
123
125private:
124private:
126 QList<MockApp*> m_list;
125 QList<MockApp*> m_list;
tests/qmltests/Stages/ApplicationCheckBox.qml+21
@@ -86,8 +86,29 @@ RowLayout {
86 }
86 }
87 }
87 }
88 Label {
88 Label {
89+ id: appIdLabel
89 text: root.appId
90 text: root.appId
90 color: "white"
91 color: "white"
91 anchors.verticalCenter: parent.verticalCenter
92 anchors.verticalCenter: parent.verticalCenter
92 }
93 }
94+ Rectangle {
95+ color: {
96+ if (d.application) {
97+ if (d.application.state === ApplicationInfoInterface.Starting) {
98+ return "yellow";
99+ } else if (d.application.state === ApplicationInfoInterface.Running) {
100+ return "green";
101+ } else if (d.application.state === ApplicationInfoInterface.Suspended) {
102+ return "blue";
103+ } else {
104+ return "darkred";
105+ }
106+ } else {
107+ return "darkred";
108+ }
109+ }
110+ width: height
111+ height: appIdLabel.height * 0.7
112+ anchors.verticalCenter: parent.verticalCenter
113+ }
93}
114}
tests/qmltests/Stages/tst_DesktopStage.qml+2−2
@@ -24,9 +24,8 @@ import Utils 0.1
24
24
25import "../../../qml/Stages"
25import "../../../qml/Stages"
26
26
27−Rectangle {
27+Item {
28 id: root
28 id: root
29− color: "darkblue"
30 width: desktopStageLoader.width + controls.width
29 width: desktopStageLoader.width + controls.width
31 height: desktopStageLoader.height
30 height: desktopStageLoader.height
32
31
@@ -57,6 +56,7 @@ Rectangle {
57 property bool itemDestroyed: false
56 property bool itemDestroyed: false
58 sourceComponent: Component {
57 sourceComponent: Component {
59 DesktopStage {
58 DesktopStage {
59+ color: "darkblue"
60 anchors.fill: parent
60 anchors.fill: parent
61 Component.onDestruction: {
61 Component.onDestruction: {
62 desktopStageLoader.itemDestroyed = true;
62 desktopStageLoader.itemDestroyed = true;
tests/qmltests/Stages/tst_PhoneStage.qml+75−3
@@ -301,16 +301,16 @@ Item {
301 addApps(2);
301 addApps(2);
302
302
303 var secondApp = ApplicationManager.get(0);
303 var secondApp = ApplicationManager.get(0);
304− tryCompare(secondApp, "state", ApplicationInfoInterface.Running);
304+ tryCompare(secondApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
305 tryCompare(secondApp, "focused", true);
305 tryCompare(secondApp, "focused", true);
306
306
307 var firstApp = ApplicationManager.get(1);
307 var firstApp = ApplicationManager.get(1);
308− tryCompare(firstApp, "state", ApplicationInfoInterface.Suspended);
308+ tryCompare(firstApp, "requestedState", ApplicationInfoInterface.RequestedSuspended);
309 tryCompare(firstApp, "focused", false);
309 tryCompare(firstApp, "focused", false);
310
310
311 ApplicationManager.stopApplication(secondApp.appId);
311 ApplicationManager.stopApplication(secondApp.appId);
312
312
313− tryCompare(firstApp, "state", ApplicationInfoInterface.Running);
313+ tryCompare(firstApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
314 tryCompare(firstApp, "focused", true);
314 tryCompare(firstApp, "focused", true);
315 }
315 }
316
316
@@ -388,5 +388,77 @@ Item {
388
388
389 tryCompare(focusedDelegate, "x", 0);
389 tryCompare(focusedDelegate, "x", 0);
390 }
390 }
391+
392+ function test_focusedAppIsTheOnlyRunningApp() {
393+ addApps(2);
394+
395+ var delegateA = findChild(phoneStage, "appDelegate0");
396+ verify(delegateA);
397+ var delegateB = findChild(phoneStage, "appDelegate1");
398+ verify(delegateB);
399+
400+ // A is focused and running, B is unfocused and suspended
401+ compare(delegateA.focus, true);
402+ compare(delegateA.application.requestedState, ApplicationInfoInterface.RequestedRunning);
403+ compare(delegateB.focus, false);
404+ compare(delegateB.application.requestedState, ApplicationInfoInterface.RequestedSuspended);
405+
406+ // Switch foreground/focused appp from A to B
407+ goToSpread();
408+ phoneStage.select(delegateB.application.appId);
409+
410+ // Now it's the other way round
411+ // A is unfocused and suspended, B is focused and running
412+ tryCompare(delegateA, "focus", false);
413+ tryCompare(delegateA.application, "requestedState", ApplicationInfoInterface.RequestedSuspended);
414+ tryCompare(delegateB, "focus", true);
415+ tryCompare(delegateB.application, "requestedState", ApplicationInfoInterface.RequestedRunning);
416+ }
417+
418+ function test_dashRemainsRunningIfStageIsToldSo() {
419+ addApps(1);
420+
421+ var delegateDash = findChild(phoneStage, "appDelegate1");
422+ verify(delegateDash);
423+ compare(delegateDash.application.appId, "unity8-dash");
424+
425+ var delegateOther = findChild(phoneStage, "appDelegate0");
426+ verify(delegateOther);
427+
428+ goToSpread();
429+ phoneStage.select("unity8-dash");
430+
431+ tryCompare(delegateDash, "focus", true);
432+ tryCompare(delegateDash.application, "requestedState", ApplicationInfoInterface.RequestedRunning);
433+ compare(delegateOther.focus, false);
434+ compare(delegateOther.application.requestedState, ApplicationInfoInterface.RequestedSuspended);
435+
436+ goToSpread();
437+ phoneStage.select(delegateOther.application.appId);
438+
439+ // The other app gets focused and running but dash is kept running despite being unfocused
440+ tryCompare(delegateDash, "focus", false);
441+ tryCompare(delegateDash.application, "requestedState", ApplicationInfoInterface.RequestedRunning);
442+ compare(delegateOther.focus, true);
443+ compare(delegateOther.application.requestedState, ApplicationInfoInterface.RequestedRunning);
444+ }
445+
446+ function test_foregroundAppIsSuspendedWhenStageIsSuspended() {
447+ addApps(1);
448+
449+ var delegate = findChild(phoneStage, "appDelegate0");
450+ verify(delegate);
451+
452+ compare(delegate.focus, true);
453+ compare(delegate.application.requestedState, ApplicationInfoInterface.RequestedRunning);
454+
455+ phoneStage.suspended = true;
456+
457+ tryCompare(delegate.application, "requestedState", ApplicationInfoInterface.RequestedSuspended);
458+
459+ phoneStage.suspended = false;
460+
461+ tryCompare(delegate.application, "requestedState", ApplicationInfoInterface.RequestedRunning);
462+ }
391 }
463 }
392}
464}
tests/qmltests/Stages/tst_TabletStage.qml+161−1
@@ -76,10 +76,18 @@ Rectangle {
76 id: webbrowserCheckBox
76 id: webbrowserCheckBox
77 appId: "webbrowser-app"
77 appId: "webbrowser-app"
78 }
78 }
79+ ApplicationCheckBox {
80+ id: galleryCheckBox
81+ appId: "gallery-app"
82+ }
79 ApplicationCheckBox {
83 ApplicationCheckBox {
80 id: dialerCheckBox
84 id: dialerCheckBox
81 appId: "dialer-app"
85 appId: "dialer-app"
82 }
86 }
87+ ApplicationCheckBox {
88+ id: facebookCheckBox
89+ appId: "facebook-webapp"
90+ }
83 }
91 }
84 }
92 }
85
93
@@ -93,6 +101,19 @@ Rectangle {
93 function init() {
101 function init() {
94 tabletStageLoader.active = true;
102 tabletStageLoader.active = true;
95 tryCompare(tabletStageLoader, "status", Loader.Ready);
103 tryCompare(tabletStageLoader, "status", Loader.Ready);
104+
105+ // this is very strange, but sometimes the test starts without
106+ // TabletStage components having finished loading themselves
107+ var appWindow = null;
108+ while (!appWindow) {
109+ appWindow = findChild(tabletStage, "appWindow_unity8-dash");
110+ if (!appWindow) {
111+ console.log("didn't find appWindow_unity8-dash in " + tabletStage + ". Trying again...");
112+ wait(50);
113+ }
114+ }
115+
116+ waitUntilAppSurfaceShowsUp("unity8-dash");
96 }
117 }
97
118
98 function cleanup() {
119 function cleanup() {
@@ -109,7 +130,9 @@ Rectangle {
109
130
110 // kill all (fake) running apps
131 // kill all (fake) running apps
111 webbrowserCheckBox.checked = false;
132 webbrowserCheckBox.checked = false;
133+ galleryCheckBox.checked = false;
112 dialerCheckBox.checked = false;
134 dialerCheckBox.checked = false;
135+ facebookCheckBox.checked = false;
113 }
136 }
114
137
115 function waitUntilAppSurfaceShowsUp(appId) {
138 function waitUntilAppSurfaceShowsUp(appId) {
@@ -120,6 +143,34 @@ Rectangle {
120 tryCompare(appWindowStates, "state", "surface");
143 tryCompare(appWindowStates, "state", "surface");
121 }
144 }
122
145
146+ function switchToApp(targetAppId) {
147+ touchFlick(tabletStage,
148+ tabletStage.width - (tabletStage.dragAreaWidth / 2), tabletStage.height / 2,
149+ tabletStage.x + 1, tabletStage.height / 2);
150+
151+ var spreadView = findChild(tabletStage, "spreadView");
152+ verify(spreadView);
153+ tryCompare(spreadView, "phase", 2);
154+ tryCompare(spreadView, "flicking", false);
155+ tryCompare(spreadView, "moving", false);
156+
157+ waitUntilAppDelegateStopsMoving(targetAppId);
158+
159+ var targetAppWindow = findChild(tabletStage, "appWindow_" + targetAppId);
160+ tap(targetAppWindow, 10, 10);
161+ }
162+
163+ function waitUntilAppDelegateStopsMoving(targetAppId)
164+ {
165+ var targetAppDelegate = findChild(tabletStage, "tabletSpreadDelegate_" + targetAppId);
166+ verify(targetAppDelegate);
167+ var lastValue = undefined;
168+ do {
169+ lastValue = targetAppDelegate.animatedProgress;
170+ wait(30);
171+ } while (lastValue != targetAppDelegate.animatedProgress);
172+ }
173+
123 function test_tappingSwitchesFocusBetweenStages() {
174 function test_tappingSwitchesFocusBetweenStages() {
124 webbrowserCheckBox.checked = true;
175 webbrowserCheckBox.checked = true;
125 waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
176 waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId);
@@ -155,7 +206,6 @@ Rectangle {
155 tryCompare(webbrowserApp.session.surface, "activeFocus", false);
206 tryCompare(webbrowserApp.session.surface, "activeFocus", false);
156 }
207 }
157
208
158−
159 function test_closeAppInSideStage() {
209 function test_closeAppInSideStage() {
160 dialerCheckBox.checked = true;
210 dialerCheckBox.checked = true;
161 waitUntilAppSurfaceShowsUp(dialerCheckBox.appId);
211 waitUntilAppSurfaceShowsUp(dialerCheckBox.appId);
@@ -200,6 +250,116 @@ Rectangle {
200 appWindow.width / 2, appWindow.height / 2,
250 appWindow.width / 2, appWindow.height / 2,
201 appWindow.width / 2, -appWindow.height / 2);
251 appWindow.width / 2, -appWindow.height / 2);
202 }
252 }
253+
254+ function test_suspendsAndResumesAppsInMainStage() {
255+ webbrowserCheckBox.checked = true;
256+ var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
257+ compare(webbrowserApp.stage, ApplicationInfoInterface.MainStage);
258+
259+ tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Running);
260+
261+ galleryCheckBox.checked = true;
262+ var galleryApp = ApplicationManager.findApplication(galleryCheckBox.appId);
263+ compare(galleryApp.stage, ApplicationInfoInterface.MainStage);
264+
265+ tryCompare(galleryApp, "state", ApplicationInfoInterface.Running);
266+ tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
267+
268+ switchToApp(webbrowserApp.appId);
269+
270+ tryCompare(galleryApp, "state", ApplicationInfoInterface.Suspended);
271+ tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Running);
272+
273+ switchToApp(galleryApp.appId);
274+
275+ tryCompare(galleryApp, "state", ApplicationInfoInterface.Running);
276+ tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
277+ }
278+
279+
280+ function test_foregroundMainAndSideStageAppsAreKeptRunning() {
281+
282+ var stagesPriv = findInvisibleChild(tabletStage, "stagesPriv");
283+ verify(stagesPriv);
284+
285+ // launch two main stage apps
286+ // gallery will be on foreground and webbrowser on background
287+
288+ webbrowserCheckBox.checked = true;
289+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId)
290+ var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
291+ compare(webbrowserApp.stage, ApplicationInfoInterface.MainStage);
292+
293+ galleryCheckBox.checked = true;
294+ waitUntilAppSurfaceShowsUp(galleryCheckBox.appId)
295+ var galleryApp = ApplicationManager.findApplication(galleryCheckBox.appId);
296+ compare(galleryApp.stage, ApplicationInfoInterface.MainStage);
297+
298+ compare(stagesPriv.mainStageAppId, galleryCheckBox.appId);
299+
300+ // then launch two side stage apps
301+ // facebook will be on foreground and dialer on background
302+
303+ dialerCheckBox.checked = true;
304+ waitUntilAppSurfaceShowsUp(dialerCheckBox.appId)
305+ var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId);
306+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
307+
308+ facebookCheckBox.checked = true;
309+ waitUntilAppSurfaceShowsUp(facebookCheckBox.appId)
310+ var facebookApp = ApplicationManager.findApplication(facebookCheckBox.appId);
311+ compare(facebookApp.stage, ApplicationInfoInterface.SideStage);
312+
313+ compare(stagesPriv.sideStageAppId, facebookCheckBox.appId);
314+
315+ // Now check that the foreground apps are running and that the background ones
316+ // are suspended
317+
318+ tryCompare(galleryApp, "state", ApplicationInfoInterface.Running);
319+ tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
320+ tryCompare(facebookApp, "state", ApplicationInfoInterface.Running);
321+ tryCompare(dialerApp, "state", ApplicationInfoInterface.Suspended);
322+
323+ switchToApp(dialerCheckBox.appId);
324+
325+ tryCompare(galleryApp, "state", ApplicationInfoInterface.Running);
326+ tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Suspended);
327+ tryCompare(facebookApp, "state", ApplicationInfoInterface.Suspended);
328+ tryCompare(dialerApp, "state", ApplicationInfoInterface.Running);
329+
330+ switchToApp(webbrowserCheckBox.appId);
331+
332+ tryCompare(galleryApp, "state", ApplicationInfoInterface.Suspended);
333+ tryCompare(webbrowserApp, "state", ApplicationInfoInterface.Running);
334+ tryCompare(facebookApp, "state", ApplicationInfoInterface.Suspended);
335+ tryCompare(dialerApp, "state", ApplicationInfoInterface.Running);
336+ }
337+
338+ function test_foregroundAppsAreSuspendedWhenStageIsSuspended() {
339+ webbrowserCheckBox.checked = true;
340+ waitUntilAppSurfaceShowsUp(webbrowserCheckBox.appId)
341+ var webbrowserApp = ApplicationManager.findApplication(webbrowserCheckBox.appId);
342+ compare(webbrowserApp.stage, ApplicationInfoInterface.MainStage);
343+
344+ dialerCheckBox.checked = true;
345+ waitUntilAppSurfaceShowsUp(dialerCheckBox.appId)
346+ var dialerApp = ApplicationManager.findApplication(dialerCheckBox.appId);
347+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
348+
349+
350+ compare(webbrowserApp.requestedState, ApplicationInfoInterface.RequestedRunning);
351+ compare(dialerApp.requestedState, ApplicationInfoInterface.RequestedRunning);
352+
353+ tabletStage.suspended = true;
354+
355+ tryCompare(webbrowserApp, "requestedState", ApplicationInfoInterface.RequestedSuspended);
356+ tryCompare(dialerApp, "requestedState", ApplicationInfoInterface.RequestedSuspended);
357+
358+ tabletStage.suspended = false;
359+
360+ tryCompare(webbrowserApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
361+ tryCompare(dialerApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
362+ }
203 }
363 }
204
364
205}
365}
tests/qmltests/tst_OrientedShell.qml+3−3
@@ -46,7 +46,7 @@ Rectangle {
46 }
46 }
47
47
48 QtObject {
48 QtObject {
49− id: mockUsageModeSettings
49+ id: mockUnity8Settings
50 property string usageMode: usageModeSelector.model[usageModeSelector.selectedIndex]
50 property string usageMode: usageModeSelector.model[usageModeSelector.selectedIndex]
51 }
51 }
52
52
@@ -125,7 +125,7 @@ Rectangle {
125 sourceComponent: Component {
125 sourceComponent: Component {
126 OrientedShell {
126 OrientedShell {
127 anchors.fill: parent
127 anchors.fill: parent
128− usageModeSettings: mockUsageModeSettings
128+ unity8Settings: mockUnity8Settings
129 oskSettings: mockOskSettings
129 oskSettings: mockOskSettings
130 physicalOrientation: root.physicalOrientation0
130 physicalOrientation: root.physicalOrientation0
131 orientationLocked: orientationLockedCheckBox.checked
131 orientationLocked: orientationLockedCheckBox.checked
@@ -926,7 +926,7 @@ Rectangle {
926
926
927 function test_attachRemoveInputDevices() {
927 function test_attachRemoveInputDevices() {
928 usageModeSelector.selectedIndex = 2;
928 usageModeSelector.selectedIndex = 2;
929− tryCompare(mockUsageModeSettings, "usageMode", "Automatic")
929+ tryCompare(mockUnity8Settings, "usageMode", "Automatic")
930
930
931 loadShell("mako")
931 loadShell("mako")
932 var shell = findChild(orientedShell, "shell");
932 var shell = findChild(orientedShell, "shell");
tests/qmltests/tst_Shell.qml+52−26
@@ -67,11 +67,6 @@ Rectangle {
67 width: units.gu(40)
67 width: units.gu(40)
68 height: units.gu(71)
68 height: units.gu(71)
69 }
69 }
70− StateChangeScript {
71− script: {
72− GSettingsController.setUsageMode("Staged")
73− }
74− }
75 },
70 },
76 State {
71 State {
77 name: "tablet"
72 name: "tablet"
@@ -80,11 +75,6 @@ Rectangle {
80 width: units.gu(100)
75 width: units.gu(100)
81 height: units.gu(71)
76 height: units.gu(71)
82 }
77 }
83− StateChangeScript {
84− script: {
85− GSettingsController.setUsageMode("Staged")
86− }
87− }
88 },
78 },
89 State {
79 State {
90 name: "desktop"
80 name: "desktop"
@@ -97,11 +87,6 @@ Rectangle {
97 target: mouseEmulation
87 target: mouseEmulation
98 checked: false
88 checked: false
99 }
89 }
100− StateChangeScript {
101− script: {
102− GSettingsController.setUsageMode("Windowed")
103− }
104− }
105 }
90 }
106 ]
91 ]
107
92
@@ -317,7 +302,6 @@ Rectangle {
317 mouseEmulation.checked = true;
302 mouseEmulation.checked = true;
318 tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
303 tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
319 tearDown();
304 tearDown();
320− GSettingsController.setUsageMode("Staged");
321 }
305 }
322
306
323 function loadShell(formFactor) {
307 function loadShell(formFactor) {
@@ -602,9 +586,9 @@ Rectangle {
602 verify(mainAppId != "");
586 verify(mainAppId != "");
603 var mainApp = ApplicationManager.findApplication(mainAppId);
587 var mainApp = ApplicationManager.findApplication(mainAppId);
604 verify(mainApp);
588 verify(mainApp);
605− tryCompare(mainApp, "state", ApplicationInfoInterface.Running);
589+ tryCompare(mainApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
606
590
607− // Suspend while call is active...
591+ // Display off while call is active...
608 callManager.foregroundCall = phoneCall;
592 callManager.foregroundCall = phoneCall;
609 Powerd.status = Powerd.Off;
593 Powerd.status = Powerd.Off;
610 tryCompare(greeter, "shown", false);
594 tryCompare(greeter, "shown", false);
@@ -615,8 +599,7 @@ Rectangle {
615 Powerd.status = Powerd.Off;
599 Powerd.status = Powerd.Off;
616 tryCompare(greeter, "fullyShown", true);
600 tryCompare(greeter, "fullyShown", true);
617
601
618− tryCompare(ApplicationManager, "suspended", true);
619− compare(mainApp.state, ApplicationInfoInterface.Suspended);
602+ compare(mainApp.requestedState, ApplicationInfoInterface.RequestedSuspended);
620
603
621 // And wake up
604 // And wake up
622 Powerd.status = Powerd.On;
605 Powerd.status = Powerd.On;
@@ -632,8 +615,7 @@ Rectangle {
632 removeTimeConstraintsFromDirectionalDragAreas(greeter);
615 removeTimeConstraintsFromDirectionalDragAreas(greeter);
633 swipeAwayGreeter();
616 swipeAwayGreeter();
634
617
635− tryCompare(ApplicationManager, "suspended", false);
636− compare(mainApp.state, ApplicationInfoInterface.Running);
618+ compare(mainApp.requestedState, ApplicationInfoInterface.RequestedRunning);
637 tryCompare(ApplicationManager, "focusedApplicationId", mainAppId);
619 tryCompare(ApplicationManager, "focusedApplicationId", mainAppId);
638 }
620 }
639
621
@@ -804,15 +786,15 @@ Rectangle {
804
786
805 function test_launchedAppHasActiveFocus_data() {
787 function test_launchedAppHasActiveFocus_data() {
806 return [
788 return [
807− {tag: "phone", formFactor: "phone", usageMode: "Staged"},
789+ {tag: "phone", formFactor: "phone", usageScenario: "phone"},
808− {tag: "tablet", formFactor: "tablet", usageMode: "Staged"},
790+ {tag: "tablet", formFactor: "tablet", usageScenario: "tablet"},
809− {tag: "desktop", formFactor: "tablet", usageMode: "Windowed"}
791+ {tag: "desktop", formFactor: "tablet", usageScenario: "desktop"}
810 ]
792 ]
811 }
793 }
812
794
813 function test_launchedAppHasActiveFocus(data) {
795 function test_launchedAppHasActiveFocus(data) {
814− GSettingsController.setUsageMode(data.usageMode);
815 loadShell(data.formFactor);
796 loadShell(data.formFactor);
797+ shell.usageScenario = data.usageScenario;
816 swipeAwayGreeter();
798 swipeAwayGreeter();
817
799
818 var webApp = ApplicationManager.startApplication("webbrowser-app");
800 var webApp = ApplicationManager.startApplication("webbrowser-app");
@@ -1347,6 +1329,50 @@ Rectangle {
1347 compare(launcher.inverted, data.launcherInverted);
1329 compare(launcher.inverted, data.launcherInverted);
1348 }
1330 }
1349
1331
1332+ function test_unfocusedAppsGetSuspendedAfterEnteringStagedMode() {
1333+ loadShell("tablet");
1334+ shell.usageScenario = "desktop";
1335+
1336+ var webBrowserApp = ApplicationManager.startApplication("webbrowser-app");
1337+ waitUntilAppWindowIsFullyLoaded(webBrowserApp);
1338+
1339+ var galleryApp = ApplicationManager.startApplication("gallery-app");
1340+ waitUntilAppWindowIsFullyLoaded(galleryApp);
1341+
1342+ ApplicationManager.requestFocusApplication("unity8-dash");
1343+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
1344+
1345+ compare(webBrowserApp.requestedState, ApplicationInfoInterface.RequestedRunning);
1346+ compare(galleryApp.requestedState, ApplicationInfoInterface.RequestedRunning);
1347+
1348+ shell.usageScenario = "tablet";
1349+
1350+ tryCompare(webBrowserApp, "requestedState", ApplicationInfoInterface.RequestedSuspended);
1351+ tryCompare(galleryApp, "requestedState", ApplicationInfoInterface.RequestedSuspended);
1352+ }
1353+
1354+ function test_unfocusedAppsAreResumedWhenEnteringWindowedMode() {
1355+ loadShell("tablet");
1356+ shell.usageScenario = "tablet";
1357+
1358+ var webBrowserApp = ApplicationManager.startApplication("webbrowser-app");
1359+ waitUntilAppWindowIsFullyLoaded(webBrowserApp);
1360+
1361+ var galleryApp = ApplicationManager.startApplication("gallery-app");
1362+ waitUntilAppWindowIsFullyLoaded(galleryApp);
1363+
1364+ ApplicationManager.requestFocusApplication("unity8-dash");
1365+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
1366+
1367+ compare(webBrowserApp.requestedState, ApplicationInfoInterface.RequestedSuspended);
1368+ compare(galleryApp.requestedState, ApplicationInfoInterface.RequestedSuspended);
1369+
1370+ shell.usageScenario = "desktop";
1371+
1372+ tryCompare(webBrowserApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
1373+ tryCompare(galleryApp, "requestedState", ApplicationInfoInterface.RequestedRunning);
1374+ }
1375+
1350 function test_altTabSwitchesFocus() {
1376 function test_altTabSwitchesFocus() {
1351 loadShell("desktop");
1377 loadShell("desktop");
1352 shell.usageScenario = "desktop"
1378 shell.usageScenario = "desktop"