on xenial help sign in
Handle lifecycle policy exceptions ourselves, instead of letting qtmir do it for us and allow non-Touch apps to opt-out of the Touch lifecycle.

This requires using the new isTouchApp unity-api property to ApplicationInfoInterface. Now that qtmir won't decide policy for suspending exemptions anymore, we take over the interpretation of the lifecycleException GSettings key. Since the GSettings key for that was registered under the qtmir namespace (and there's no technical reason to migrate settings), I left the schema in qtmir itself. We merely consume it.

commit931c63cc8dc220f049a4ed9fb388a0ceb8cc6a24 authorMichael Terry <michael.terry@canonical.com> dateWed 4 Nov 2015, 14:57 (11 years ago) parentc2d47ba9ed0d4d
16 files changed+218−86
CMakeLists.txt+1−1 debian/control+2−2 qml/Stages/AbstractStage.qml+63 qml/Stages/DesktopStage.qml+4−20 qml/Stages/PhoneStage.qml+19−30 qml/Stages/ShimStage.qml+1−1 qml/Stages/TabletStage.qml+13−31 qml/graphics/applicationIcons/libreoffice@18.png tests/mocks/GSettings.1.0/fake_gsettings.cpp+31 tests/mocks/GSettings.1.0/fake_gsettings.h+10 tests/mocks/Unity/Application/ApplicationInfo.cpp+12 tests/mocks/Unity/Application/ApplicationInfo.h+4 tests/mocks/Unity/Application/ApplicationManager.cpp+9 tests/mocks/Unity/Application/ApplicationManager.h+1−1 tests/plugins/Unity/Launcher/launchermodeltest.cpp+1 tests/qmltests/tst_Shell.qml+47
CMakeLists.txt+1−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.4 REQUIRED)
5757find_package(Qt5Concurrent 5.4 REQUIRED)
5858find_package(Qt5Sql 5.4 REQUIRED)
5959
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=9)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=10)
6161
6262# Standard install paths
6363include(GNUInstallDirs)
debian/control+2−2
@@ -128,7 +128,7 @@ Depends: qml-module-qtquick-layouts,
128128 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1627) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1627),
129129 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
130130 ubuntu-thumbnailer-impl-0,
131− unity-application-impl-9,
131+ unity-application-impl-10,
132132 unity-notifications-impl-3,
133133 unity-plugin-scopes | unity-scopes-impl,
134134 unity-scopes-impl-7,
@@ -174,7 +174,7 @@ Pre-Depends: ${misc:Pre-Depends},
174174Depends: ${misc:Depends},
175175 ${shlibs:Depends},
176176Provides: unity-application-impl,
177− unity-application-impl-9,
177+ unity-application-impl-10,
178178Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
179179Description: Fake environment for running Unity 8 shell
180180 Provides fake implementations of some QML modules used by Unity 8 shell
qml/Stages/AbstractStage.qmlnew file+63
@@ -0,0 +1,63 @@
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.4
18+import Ubuntu.Components 1.3
19+import GSettings 1.0
20+
21+Rectangle {
22+ id: root
23+
24+ color: "#111111"
25+
26+ // Controls to be set from outside
27+ property bool altTabPressed
28+ property url background
29+ property bool beingResized
30+ property int dragAreaWidth
31+ property bool interactive
32+ property real inverseProgress // This is the progress for left edge drags, in pixels.
33+ property bool keepDashRunning: true
34+ property real maximizedAppTopMargin
35+ property real nativeHeight
36+ property real nativeWidth
37+ property QtObject orientations
38+ property int shellOrientation
39+ property int shellOrientationAngle
40+ property bool spreadEnabled: true // If false, animations and right edge will be disabled
41+ property bool suspended
42+
43+ // To be read from outside
44+ property var mainApp: null
45+ property int mainAppWindowOrientationAngle
46+ property bool orientationChangesEnabled
47+
48+ // Shared code for use in stage implementations
49+ GSettings {
50+ id: lifecycleExceptions
51+ schema.id: "com.canonical.qtmir"
52+ }
53+
54+ function isExemptFromLifecycle(appId) {
55+ var shortAppId = appId.split('_')[0];
56+ for (var i = 0; i < lifecycleExceptions.lifecycleExemptAppids.length; i++) {
57+ if (shortAppId === lifecycleExceptions.lifecycleExemptAppids[i]) {
58+ return true;
59+ }
60+ }
61+ return false;
62+ }
63+}
qml/Stages/DesktopStage.qml+4−20
@@ -26,35 +26,17 @@ import "../Components"
2626import Utils 0.1
2727import Ubuntu.Gestures 0.1
2828
29−Rectangle {
29+AbstractStage {
3030 id: root
3131 anchors.fill: parent
3232
33− // Controls to be set from outside
34− property int dragAreaWidth // just to comply with the interface shared between stages
35− property real maximizedAppTopMargin
36− property bool interactive
37− property bool spreadEnabled // just to comply with the interface shared between stages
38− property real inverseProgress: 0 // just to comply with the interface shared between stages
39− property int shellOrientationAngle: 0
40− property int shellOrientation
41− property QtObject orientations
42− property bool beingResized: false
43− property bool keepDashRunning: true
44− property bool suspended: false
45− property alias background: wallpaper.source
46− property alias altTabPressed: spread.altTabPressed
47−
4833 // functions to be called from outside
4934 function updateFocusedAppOrientation() { /* TODO */ }
5035 function updateFocusedAppOrientationAnimated() { /* TODO */}
5136
52− // To be read from outside
53− readonly property var mainApp: ApplicationManager.focusedApplicationId
37+ mainApp: ApplicationManager.focusedApplicationId
5438 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
5539 : null
56− property int mainAppWindowOrientationAngle: 0
57− readonly property bool orientationChangesEnabled: false
5840
5941 Connections {
6042 target: ApplicationManager
@@ -122,6 +104,7 @@ Rectangle {
122104 CrossFadeImage {
123105 id: wallpaper
124106 anchors.fill: parent
107+ source: root.background
125108 sourceSize { height: root.height; width: root.width }
126109 fillMode: Image.PreserveAspectCrop
127110 }
@@ -276,5 +259,6 @@ Rectangle {
276259 anchors.fill: parent
277260 workspace: appContainer
278261 focus: state == "altTab"
262+ altTabPressed: root.altTabPressed
279263 }
280264}
qml/Stages/PhoneStage.qml+19−30
@@ -22,29 +22,15 @@ import Unity.Session 0.1
2222import Utils 0.1
2323import "../Components"
2424
25−Rectangle {
25+AbstractStage {
2626 id: root
2727
28− // Controls to be set from outside
29− property int dragAreaWidth
30− property real maximizedAppTopMargin
31− property bool interactive
32− property bool spreadEnabled: true // If false, animations and right edge will be disabled
33− property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
3428 property QtObject applicationManager: ApplicationManager
3529 property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
3630 property bool altTabEnabled: true
3731 property real startScale: 1.1
3832 property real endScale: 0.7
39− property bool keepDashRunning: true
40− property bool suspended: false
41− property int shellOrientationAngle: 0
42−
43− property int shellOrientation
44− property QtObject orientations
45− property real nativeWidth
46− property real nativeHeight
47− property bool beingResized: false
33+
4834 onBeingResizedChanged: {
4935 if (beingResized) {
5036 // Brace yourselves for impact!
@@ -56,6 +42,8 @@ Rectangle {
5642 priv.reset();
5743 }
5844 }
45+
46+ // Functions to be called from outside
5947 function updateFocusedAppOrientation() {
6048 if (spreadRepeater.count > 0) {
6149 spreadRepeater.itemAt(0).matchShellOrientation();
@@ -85,16 +73,14 @@ Rectangle {
8573 }
8674 }
8775
88− // To be read from outside
89− readonly property var mainApp: applicationManager.focusedApplicationId
76+ mainApp: applicationManager.focusedApplicationId
9077 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
9178 : null
9279
93− property int mainAppWindowOrientationAngle: 0
94− readonly property bool orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
95− && !priv.focusedAppDelegateIsDislocated
96− && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
97− && spreadView.phase === 0
80+ orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
81+ && !priv.focusedAppDelegateIsDislocated
82+ && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
83+ && spreadView.phase === 0
9884
9985 // How far left the stage has been dragged
10086 readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
@@ -106,8 +92,6 @@ Rectangle {
10692
10793 signal opened()
10894
109− color: "#111111"
110−
11195 function select(appId) {
11296 spreadView.snapTo(priv.indexOf(appId));
11397 }
@@ -438,16 +422,21 @@ Rectangle {
438422 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
439423 focusFirstApp: root.focusFirstApp
440424
425+ readonly property bool isDash: model.appId == "unity8-dash"
426+
427+ readonly property bool canSuspend: model.isTouchApp
428+ && !isExemptFromLifecycle(model.appId)
429+
441430 Binding {
442431 target: appDelegate.application
443432 property: "requestedState"
444− value: (isDash && root.keepDashRunning) || (!root.suspended && appDelegate.focus)
445− ? ApplicationInfoInterface.RequestedRunning
446− : ApplicationInfoInterface.RequestedSuspended
433+ value: !canSuspend
434+ || (isDash && root.keepDashRunning)
435+ || (!root.suspended && appDelegate.focus)
436+ ? ApplicationInfoInterface.RequestedRunning
437+ : ApplicationInfoInterface.RequestedSuspended
447438 }
448439
449− readonly property bool isDash: model.appId == "unity8-dash"
450−
451440 z: isDash && !spreadView.active ? -1 : behavioredIndex
452441
453442 x: {
qml/Stages/ShimStage.qml+1−1
@@ -17,7 +17,7 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919
20−Rectangle {
20+AbstractStage {
2121 id: shimStage
2222
2323 anchors.fill: parent
qml/Stages/TabletStage.qml+13−31
@@ -21,30 +21,12 @@ import Unity.Application 0.1
2121import Utils 0.1
2222import "../Components"
2323
24−Rectangle {
24+AbstractStage {
2525 id: root
2626 objectName: "stages"
2727 anchors.fill: parent
28− color: "#111111"
29−
30− // Controls to be set from outside
31− property int dragAreaWidth
32− property real maximizedAppTopMargin
33− property bool interactive
34− property alias beingResized: spreadView.beingResized
35−
36− property bool spreadEnabled: true // If false, animations and right edge will be disabled
37−
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
41− property int shellOrientationAngle: 0
42−
43− property int shellOrientation
44− property QtObject orientations
45− property real nativeWidth
46− property real nativeHeight
4728
29+ // Functions to be called from outside
4830 function updateFocusedAppOrientation() {
4931 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
5032 if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
@@ -87,10 +69,7 @@ Rectangle {
8769 }
8870 }
8971
90− // To be read from outside
91− property var mainApp: null
92− property int mainAppWindowOrientationAngle: 0
93− readonly property bool orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
72+ orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
9473
9574 onWidthChanged: {
9675 spreadView.selectedIndex = -1;
@@ -303,7 +282,7 @@ Rectangle {
303282 }
304283
305284 property bool animateX: true
306− property bool beingResized: false
285+ property bool beingResized: root.beingResized
307286 onBeingResizedChanged: {
308287 if (beingResized) {
309288 // Brace yourselves for impact!
@@ -630,15 +609,18 @@ Rectangle {
630609
631610 readonly property bool isDash: model.appId == "unity8-dash"
632611
612+ readonly property bool canSuspend: model.isTouchApp
613+ && !isExemptFromLifecycle(model.appId)
614+
633615 Binding {
634616 target: spreadTile.application
635617 property: "requestedState"
636− value: (spreadTile.isDash && root.keepDashRunning)
637− ||
638− (!root.suspended && (model.appId == priv.mainStageAppId
639− || model.appId == priv.sideStageAppId))
640− ? ApplicationInfoInterface.RequestedRunning
641− : ApplicationInfoInterface.RequestedSuspended
618+ value: !canSuspend
619+ || (isDash && root.keepDashRunning)
620+ || (!root.suspended && (model.appId == priv.mainStageAppId
621+ || model.appId == priv.sideStageAppId))
622+ ? ApplicationInfoInterface.RequestedRunning
623+ : ApplicationInfoInterface.RequestedSuspended
642624 }
643625
644626 // FIXME: A regular binding doesn't update any more after closing an app.
qml/graphics/applicationIcons/libreoffice@18.pngnew file

Binary file not shown.

tests/mocks/GSettings.1.0/fake_gsettings.cpp+31
@@ -75,6 +75,19 @@ void GSettingsControllerQml::setLockedOutTime(qint64 timestamp)
7575 }
7676}
7777
78+QStringList GSettingsControllerQml::lifecycleExemptAppids() const
79+{
80+ return m_lifecycleExemptAppids;
81+}
82+
83+void GSettingsControllerQml::setLifecycleExemptAppids(const QStringList &appIds)
84+{
85+ if (m_lifecycleExemptAppids != appIds) {
86+ m_lifecycleExemptAppids = appIds;
87+ Q_EMIT lifecycleExemptAppidsChanged(m_lifecycleExemptAppids);
88+ }
89+}
90+
7891GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
7992}
8093
@@ -114,6 +127,8 @@ GSettingsQml::GSettingsQml(QObject *parent)
114127 this, &GSettingsQml::usageModeChanged);
115128 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lockedOutTimeChanged,
116129 this, &GSettingsQml::lockedOutTimeChanged);
130+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lifecycleExemptAppidsChanged,
131+ this, &GSettingsQml::lifecycleExemptAppidsChanged);
117132}
118133
119134GSettingsSchemaQml * GSettingsQml::schema() const {
@@ -167,3 +182,19 @@ void GSettingsQml::setLockedOutTime(qint64 timestamp)
167182 GSettingsControllerQml::instance()->setLockedOutTime(timestamp);
168183 }
169184}
185+
186+QStringList GSettingsQml::lifecycleExemptAppids() const
187+{
188+ if (m_schema->id() == "com.canonical.qtmir") {
189+ return GSettingsControllerQml::instance()->lifecycleExemptAppids();
190+ } else {
191+ return QStringList();
192+ }
193+}
194+
195+void GSettingsQml::setLifecycleExemptAppids(const QStringList &appIds)
196+{
197+ if (m_schema->id() == "com.canonical.qtmir") {
198+ GSettingsControllerQml::instance()->setLifecycleExemptAppids(appIds);
199+ }
200+}
tests/mocks/GSettings.1.0/fake_gsettings.h+10
@@ -19,6 +19,7 @@
1919
2020#include <QList>
2121#include <QObject>
22+#include <QStringList>
2223
2324class GSettingsSchemaQml: public QObject
2425{
@@ -48,6 +49,7 @@ class GSettingsQml: public QObject
4849 Q_PROPERTY(QString pictureUri READ pictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
4950 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
5051 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
52+ Q_PROPERTY(QStringList lifecycleExemptAppids READ lifecycleExemptAppids WRITE setLifecycleExemptAppids NOTIFY lifecycleExemptAppidsChanged)
5153
5254public:
5355 GSettingsQml(QObject *parent = nullptr);
@@ -56,16 +58,19 @@ public:
5658 QString pictureUri() const;
5759 QString usageMode() const;
5860 qint64 lockedOutTime() const;
61+ QStringList lifecycleExemptAppids() const;
5962
6063 void setPictureUri(const QString &str);
6164 void setUsageMode(const QString &usageMode);
6265 void setLockedOutTime(qint64 timestamp);
66+ void setLifecycleExemptAppids(const QStringList &appIds);
6367
6468Q_SIGNALS:
6569 void schemaChanged();
6670 void pictureUriChanged(const QString&);
6771 void usageModeChanged(const QString&);
6872 void lockedOutTimeChanged(qint64);
73+ void lifecycleExemptAppidsChanged(const QStringList &);
6974
7075private:
7176 GSettingsSchemaQml* m_schema;
@@ -90,10 +95,14 @@ public:
9095 qint64 lockedOutTime() const;
9196 Q_INVOKABLE void setLockedOutTime(qint64 timestamp);
9297
98+ QStringList lifecycleExemptAppids() const;
99+ Q_INVOKABLE void setLifecycleExemptAppids(const QStringList &appIds);
100+
93101Q_SIGNALS:
94102 void pictureUriChanged(const QString&);
95103 void usageModeChanged(const QString&);
96104 void lockedOutTimeChanged(qint64 timestamp);
105+ void lifecycleExemptAppidsChanged(const QStringList&);
97106
98107private:
99108 GSettingsControllerQml();
@@ -101,6 +110,7 @@ private:
101110 QString m_pictureUri;
102111 QString m_usageMode;
103112 qint64 m_lockedOutTime;
113+ QStringList m_lifecycleExemptAppids;
104114
105115 static GSettingsControllerQml* s_controllerInstance;
106116 QList<GSettingsQml *> m_registeredGSettings;
tests/mocks/Unity/Application/ApplicationInfo.cpp+12
@@ -41,6 +41,7 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
4141 Qt::InvertedLandscapeOrientation)
4242 , m_rotatesWindowContents(false)
4343 , m_requestedState(RequestedRunning)
44+ , m_isTouchApp(true)
4445 , m_manualSurfaceCreation(false)
4546{
4647}
@@ -58,6 +59,7 @@ ApplicationInfo::ApplicationInfo(QObject *parent)
5859 Qt::InvertedLandscapeOrientation)
5960 , m_rotatesWindowContents(false)
6061 , m_requestedState(RequestedRunning)
62+ , m_isTouchApp(true)
6163 , m_manualSurfaceCreation(false)
6264{
6365}
@@ -245,6 +247,16 @@ void ApplicationInfo::setRequestedState(RequestedState value)
245247 }
246248}
247249
250+bool ApplicationInfo::isTouchApp() const
251+{
252+ return m_isTouchApp;
253+}
254+
255+void ApplicationInfo::setIsTouchApp(bool isTouchApp)
256+{
257+ m_isTouchApp = isTouchApp;
258+}
259+
248260void ApplicationInfo::onSessionSurfaceChanged(MirSurface* surface)
249261{
250262 if (surface != nullptr && m_state == Starting) {
tests/mocks/Unity/Application/ApplicationInfo.h+4
@@ -89,6 +89,9 @@ public:
8989 bool manualSurfaceCreation() const { return m_manualSurfaceCreation; }
9090 void setManualSurfaceCreation(bool value);
9191
92+ bool isTouchApp() const override;
93+ void setIsTouchApp(bool isTouchApp); // only in mock
94+
9295public:
9396 void setSession(Session* session);
9497 Session* session() const { return m_session; }
@@ -121,6 +124,7 @@ private:
121124 Qt::ScreenOrientations m_supportedOrientations;
122125 bool m_rotatesWindowContents;
123126 RequestedState m_requestedState;
127+ bool m_isTouchApp;
124128
125129 bool m_manualSurfaceCreation;
126130};
tests/mocks/Unity/Application/ApplicationManager.cpp+9
@@ -105,6 +105,8 @@ QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
105105 return app->state();
106106 case RoleFocused:
107107 return app->focused();
108+ case RoleIsTouchApp:
109+ return app->isTouchApp();
108110 case RoleSession:
109111 return QVariant::fromValue(app->session());
110112 case RoleFullscreen:
@@ -458,6 +460,13 @@ void ApplicationManager::buildListOfAvailableApplications()
458460 application->setName("YouTube");
459461 application->setIconId("youtube");
460462 m_availableApplications.append(application);
463+
464+ application = new ApplicationInfo(this);
465+ application->setAppId("libreoffice");
466+ application->setName("LibreOffice");
467+ application->setIconId("libreoffice");
468+ application->setIsTouchApp(false);
469+ m_availableApplications.append(application);
461470}
462471
463472
tests/mocks/Unity/Application/ApplicationManager.h+1−1
@@ -43,7 +43,7 @@ class ApplicationManager : public ApplicationManagerInterface {
4343 static ApplicationManager *singleton();
4444
4545 enum MoreRoles {
46− RoleSession = RoleFocused+1,
46+ RoleSession = RoleIsTouchApp+1,
4747 RoleFullscreen,
4848 };
4949 enum Flag {
tests/plugins/Unity/Launcher/launchermodeltest.cpp+1
@@ -57,6 +57,7 @@ public:
5757 QColor splashColorFooter() const override { return QColor(0,0,0,0); }
5858 Qt::ScreenOrientations supportedOrientations() const override { return Qt::PortraitOrientation; }
5959 bool rotatesWindowContents() const override { return false; }
60+ bool isTouchApp() const override { return true; }
6061
6162 // Methods used for mocking (not in the interface)
6263 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
tests/qmltests/tst_Shell.qml+47
@@ -380,6 +380,8 @@ Rectangle {
380380 LightDM.Greeter.authenticate(""); // reset greeter
381381
382382 sessionSpy.clear();
383+
384+ GSettingsController.setLifecycleExemptAppids([]);
383385 }
384386
385387 function killApps() {
@@ -1794,5 +1796,50 @@ Rectangle {
17941796 waitForRendering(shell);
17951797 tryCompare(panelButtons, "visible", true);
17961798 }
1799+
1800+ function test_lifecyclePolicyForNonTouchApp_data() {
1801+ return [
1802+ {tag: "phone", formFactor: "phone", usageScenario: "phone"},
1803+ {tag: "tablet", formFactor: "tablet", usageScenario: "tablet"}
1804+ ]
1805+ }
1806+
1807+ function test_lifecyclePolicyForNonTouchApp(data) {
1808+ loadShell(data.formFactor);
1809+ shell.usageScenario = data.usageScenario;
1810+
1811+ var app1 = ApplicationManager.startApplication("libreoffice");
1812+ waitUntilAppWindowIsFullyLoaded(app1);
1813+ var app2 = ApplicationManager.startApplication("dialer-app");
1814+ waitUntilAppWindowIsFullyLoaded(app2);
1815+
1816+ // Make sure app1 is unfocused but still running
1817+ compare(app1.session.surface.activeFocus, false);
1818+ compare(app1.isTouchApp, false); // sanity check our mock, which sets this for us
1819+ compare(app1.requestedState, ApplicationInfoInterface.RequestedRunning);
1820+ }
1821+
1822+ function test_lifecyclePolicyExemption_data() {
1823+ return [
1824+ {tag: "phone", formFactor: "phone", usageScenario: "phone", suspendsApps: true},
1825+ {tag: "tablet", formFactor: "tablet", usageScenario: "tablet", suspendsApps: true}
1826+ ]
1827+ }
1828+
1829+ function test_lifecyclePolicyExemption(data) {
1830+ loadShell(data.formFactor);
1831+ shell.usageScenario = data.usageScenario;
1832+
1833+ GSettingsController.setLifecycleExemptAppids(["webbrowser-app"]);
1834+
1835+ var app1 = ApplicationManager.startApplication("webbrowser-app");
1836+ waitUntilAppWindowIsFullyLoaded(app1);
1837+ var app2 = ApplicationManager.startApplication("dialer-app");
1838+ waitUntilAppWindowIsFullyLoaded(app2);
1839+
1840+ // Make sure app1 is unfocused but still running
1841+ compare(app1.session.surface.activeFocus, false);
1842+ compare(app1.requestedState, ApplicationInfoInterface.RequestedRunning);
1843+ }
17971844 }
17981845}