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.
931c63cMichael Terry
CMakeLists.txt+1−1
@@ -57,7 +57,7 @@ find_package(Qt5DBus 5.4 REQUIRED)
57find_package(Qt5Concurrent 5.4 REQUIRED)
57find_package(Qt5Concurrent 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)
59
59
60−pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=9)
60+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=10)
61
61
62# Standard install paths
62# Standard install paths
63include(GNUInstallDirs)
63include(GNUInstallDirs)
debian/control+2−2
@@ -128,7 +128,7 @@ Depends: qml-module-qtquick-layouts,
128 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1627) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1627),
128 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.3.1627) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.3.1627),
129 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
129 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
130 ubuntu-thumbnailer-impl-0,
130 ubuntu-thumbnailer-impl-0,
131− unity-application-impl-9,
131+ unity-application-impl-10,
132 unity-notifications-impl-3,
132 unity-notifications-impl-3,
133 unity-plugin-scopes | unity-scopes-impl,
133 unity-plugin-scopes | unity-scopes-impl,
134 unity-scopes-impl-7,
134 unity-scopes-impl-7,
@@ -174,7 +174,7 @@ Pre-Depends: ${misc:Pre-Depends},
174Depends: ${misc:Depends},
174Depends: ${misc:Depends},
175 ${shlibs:Depends},
175 ${shlibs:Depends},
176Provides: unity-application-impl,
176Provides: unity-application-impl,
177− unity-application-impl-9,
177+ unity-application-impl-10,
178Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
178Replaces: unity8-autopilot (<< 8.02+15.04.20150422-0ubuntu1)
179Description: Fake environment for running Unity 8 shell
179Description: Fake environment for running Unity 8 shell
180 Provides fake implementations of some QML modules used by Unity 8 shell
180 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"
26import Utils 0.1
26import Utils 0.1
27import Ubuntu.Gestures 0.1
27import Ubuntu.Gestures 0.1
28
28
29−Rectangle {
29+AbstractStage {
30 id: root
30 id: root
31 anchors.fill: parent
31 anchors.fill: parent
32
32
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−
48 // functions to be called from outside
33 // functions to be called from outside
49 function updateFocusedAppOrientation() { /* TODO */ }
34 function updateFocusedAppOrientation() { /* TODO */ }
50 function updateFocusedAppOrientationAnimated() { /* TODO */}
35 function updateFocusedAppOrientationAnimated() { /* TODO */}
51
36
52− // To be read from outside
53− readonly property var mainApp: ApplicationManager.focusedApplicationId
37+ mainApp: ApplicationManager.focusedApplicationId
54 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
38 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
55 : null
39 : null
56− property int mainAppWindowOrientationAngle: 0
57− readonly property bool orientationChangesEnabled: false
58
40
59 Connections {
41 Connections {
60 target: ApplicationManager
42 target: ApplicationManager
@@ -122,6 +104,7 @@ Rectangle {
122 CrossFadeImage {
104 CrossFadeImage {
123 id: wallpaper
105 id: wallpaper
124 anchors.fill: parent
106 anchors.fill: parent
107+ source: root.background
125 sourceSize { height: root.height; width: root.width }
108 sourceSize { height: root.height; width: root.width }
126 fillMode: Image.PreserveAspectCrop
109 fillMode: Image.PreserveAspectCrop
127 }
110 }
@@ -276,5 +259,6 @@ Rectangle {
276 anchors.fill: parent
259 anchors.fill: parent
277 workspace: appContainer
260 workspace: appContainer
278 focus: state == "altTab"
261 focus: state == "altTab"
262+ altTabPressed: root.altTabPressed
279 }
263 }
280}
264}
qml/Stages/PhoneStage.qml+19−30
@@ -22,29 +22,15 @@ import Unity.Session 0.1
22import Utils 0.1
22import Utils 0.1
23import "../Components"
23import "../Components"
24
24
25−Rectangle {
25+AbstractStage {
26 id: root
26 id: root
27
27
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.
34 property QtObject applicationManager: ApplicationManager
28 property QtObject applicationManager: ApplicationManager
35 property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
29 property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
36 property bool altTabEnabled: true
30 property bool altTabEnabled: true
37 property real startScale: 1.1
31 property real startScale: 1.1
38 property real endScale: 0.7
32 property real endScale: 0.7
39− property bool keepDashRunning: true
33+
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
48 onBeingResizedChanged: {
34 onBeingResizedChanged: {
49 if (beingResized) {
35 if (beingResized) {
50 // Brace yourselves for impact!
36 // Brace yourselves for impact!
@@ -56,6 +42,8 @@ Rectangle {
56 priv.reset();
42 priv.reset();
57 }
43 }
58 }
44 }
45+
46+ // Functions to be called from outside
59 function updateFocusedAppOrientation() {
47 function updateFocusedAppOrientation() {
60 if (spreadRepeater.count > 0) {
48 if (spreadRepeater.count > 0) {
61 spreadRepeater.itemAt(0).matchShellOrientation();
49 spreadRepeater.itemAt(0).matchShellOrientation();
@@ -85,16 +73,14 @@ Rectangle {
85 }
73 }
86 }
74 }
87
75
88− // To be read from outside
89− readonly property var mainApp: applicationManager.focusedApplicationId
76+ mainApp: applicationManager.focusedApplicationId
90 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
77 ? applicationManager.findApplication(applicationManager.focusedApplicationId)
91 : null
78 : null
92
79
93− property int mainAppWindowOrientationAngle: 0
94− readonly property bool orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
80+ orientationChangesEnabled: priv.focusedAppOrientationChangesEnabled
95− && !priv.focusedAppDelegateIsDislocated
81+ && !priv.focusedAppDelegateIsDislocated
96− && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
82+ && !(priv.focusedAppDelegate && priv.focusedAppDelegate.xBehavior.running)
97− && spreadView.phase === 0
83+ && spreadView.phase === 0
98
84
99 // How far left the stage has been dragged
85 // How far left the stage has been dragged
100 readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
86 readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
@@ -106,8 +92,6 @@ Rectangle {
106
92
107 signal opened()
93 signal opened()
108
94
109− color: "#111111"
110−
111 function select(appId) {
95 function select(appId) {
112 spreadView.snapTo(priv.indexOf(appId));
96 spreadView.snapTo(priv.indexOf(appId));
113 }
97 }
@@ -438,16 +422,21 @@ Rectangle {
438 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
422 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
439 focusFirstApp: root.focusFirstApp
423 focusFirstApp: root.focusFirstApp
440
424
425+ readonly property bool isDash: model.appId == "unity8-dash"
426+
427+ readonly property bool canSuspend: model.isTouchApp
428+ && !isExemptFromLifecycle(model.appId)
429+
441 Binding {
430 Binding {
442 target: appDelegate.application
431 target: appDelegate.application
443 property: "requestedState"
432 property: "requestedState"
433+ value: !canSuspend
434+ || (isDash && root.keepDashRunning)
444− value: (isDash && root.keepDashRunning) || (!root.suspended && appDelegate.focus)
435+ || (!root.suspended && appDelegate.focus)
445− ? ApplicationInfoInterface.RequestedRunning
436+ ? ApplicationInfoInterface.RequestedRunning
446− : ApplicationInfoInterface.RequestedSuspended
437+ : ApplicationInfoInterface.RequestedSuspended
447 }
438 }
448
439
449− readonly property bool isDash: model.appId == "unity8-dash"
450−
451 z: isDash && !spreadView.active ? -1 : behavioredIndex
440 z: isDash && !spreadView.active ? -1 : behavioredIndex
452
441
453 x: {
442 x: {
qml/Stages/ShimStage.qml+1−1
@@ -17,7 +17,7 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19
19
20−Rectangle {
20+AbstractStage {
21 id: shimStage
21 id: shimStage
22
22
23 anchors.fill: parent
23 anchors.fill: parent
qml/Stages/TabletStage.qml+13−31
@@ -21,30 +21,12 @@ import Unity.Application 0.1
21import Utils 0.1
21import Utils 0.1
22import "../Components"
22import "../Components"
23
23
24−Rectangle {
24+AbstractStage {
25 id: root
25 id: root
26 objectName: "stages"
26 objectName: "stages"
27 anchors.fill: parent
27 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
47
28
29+ // Functions to be called from outside
48 function updateFocusedAppOrientation() {
30 function updateFocusedAppOrientation() {
49 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
31 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
50 if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
32 if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
@@ -87,10 +69,7 @@ Rectangle {
87 }
69 }
88 }
70 }
89
71
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
94
73
95 onWidthChanged: {
74 onWidthChanged: {
96 spreadView.selectedIndex = -1;
75 spreadView.selectedIndex = -1;
@@ -303,7 +282,7 @@ Rectangle {
303 }
282 }
304
283
305 property bool animateX: true
284 property bool animateX: true
306− property bool beingResized: false
285+ property bool beingResized: root.beingResized
307 onBeingResizedChanged: {
286 onBeingResizedChanged: {
308 if (beingResized) {
287 if (beingResized) {
309 // Brace yourselves for impact!
288 // Brace yourselves for impact!
@@ -630,15 +609,18 @@ Rectangle {
630
609
631 readonly property bool isDash: model.appId == "unity8-dash"
610 readonly property bool isDash: model.appId == "unity8-dash"
632
611
612+ readonly property bool canSuspend: model.isTouchApp
613+ && !isExemptFromLifecycle(model.appId)
614+
633 Binding {
615 Binding {
634 target: spreadTile.application
616 target: spreadTile.application
635 property: "requestedState"
617 property: "requestedState"
618+ value: !canSuspend
636− value: (spreadTile.isDash && root.keepDashRunning)
619+ || (isDash && root.keepDashRunning)
637− ||
638− (!root.suspended && (model.appId == priv.mainStageAppId
620+ || (!root.suspended && (model.appId == priv.mainStageAppId
639− || model.appId == priv.sideStageAppId))
621+ || model.appId == priv.sideStageAppId))
640− ? ApplicationInfoInterface.RequestedRunning
622+ ? ApplicationInfoInterface.RequestedRunning
641− : ApplicationInfoInterface.RequestedSuspended
623+ : ApplicationInfoInterface.RequestedSuspended
642 }
624 }
643
625
644 // FIXME: A regular binding doesn't update any more after closing an app.
626 // 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)
75 }
75 }
76}
76}
77
77
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+
78GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
91GSettingsSchemaQml::GSettingsSchemaQml(QObject *parent): QObject(parent) {
79}
92}
80
93
@@ -114,6 +127,8 @@ GSettingsQml::GSettingsQml(QObject *parent)
114 this, &GSettingsQml::usageModeChanged);
127 this, &GSettingsQml::usageModeChanged);
115 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lockedOutTimeChanged,
128 connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lockedOutTimeChanged,
116 this, &GSettingsQml::lockedOutTimeChanged);
129 this, &GSettingsQml::lockedOutTimeChanged);
130+ connect(GSettingsControllerQml::instance(), &GSettingsControllerQml::lifecycleExemptAppidsChanged,
131+ this, &GSettingsQml::lifecycleExemptAppidsChanged);
117}
132}
118
133
119GSettingsSchemaQml * GSettingsQml::schema() const {
134GSettingsSchemaQml * GSettingsQml::schema() const {
@@ -167,3 +182,19 @@ void GSettingsQml::setLockedOutTime(qint64 timestamp)
167 GSettingsControllerQml::instance()->setLockedOutTime(timestamp);
182 GSettingsControllerQml::instance()->setLockedOutTime(timestamp);
168 }
183 }
169}
184}
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 @@
19
19
20#include <QList>
20#include <QList>
21#include <QObject>
21#include <QObject>
22+#include <QStringList>
22
23
23class GSettingsSchemaQml: public QObject
24class GSettingsSchemaQml: public QObject
24{
25{
@@ -48,6 +49,7 @@ class GSettingsQml: public QObject
48 Q_PROPERTY(QString pictureUri READ pictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
49 Q_PROPERTY(QString pictureUri READ pictureUri WRITE setPictureUri NOTIFY pictureUriChanged)
49 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
50 Q_PROPERTY(QString usageMode READ usageMode WRITE setUsageMode NOTIFY usageModeChanged)
50 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
51 Q_PROPERTY(qint64 lockedOutTime READ lockedOutTime WRITE setLockedOutTime NOTIFY lockedOutTimeChanged)
52+ Q_PROPERTY(QStringList lifecycleExemptAppids READ lifecycleExemptAppids WRITE setLifecycleExemptAppids NOTIFY lifecycleExemptAppidsChanged)
51
53
52public:
54public:
53 GSettingsQml(QObject *parent = nullptr);
55 GSettingsQml(QObject *parent = nullptr);
@@ -56,16 +58,19 @@ public:
56 QString pictureUri() const;
58 QString pictureUri() const;
57 QString usageMode() const;
59 QString usageMode() const;
58 qint64 lockedOutTime() const;
60 qint64 lockedOutTime() const;
61+ QStringList lifecycleExemptAppids() const;
59
62
60 void setPictureUri(const QString &str);
63 void setPictureUri(const QString &str);
61 void setUsageMode(const QString &usageMode);
64 void setUsageMode(const QString &usageMode);
62 void setLockedOutTime(qint64 timestamp);
65 void setLockedOutTime(qint64 timestamp);
66+ void setLifecycleExemptAppids(const QStringList &appIds);
63
67
64Q_SIGNALS:
68Q_SIGNALS:
65 void schemaChanged();
69 void schemaChanged();
66 void pictureUriChanged(const QString&);
70 void pictureUriChanged(const QString&);
67 void usageModeChanged(const QString&);
71 void usageModeChanged(const QString&);
68 void lockedOutTimeChanged(qint64);
72 void lockedOutTimeChanged(qint64);
73+ void lifecycleExemptAppidsChanged(const QStringList &);
69
74
70private:
75private:
71 GSettingsSchemaQml* m_schema;
76 GSettingsSchemaQml* m_schema;
@@ -90,10 +95,14 @@ public:
90 qint64 lockedOutTime() const;
95 qint64 lockedOutTime() const;
91 Q_INVOKABLE void setLockedOutTime(qint64 timestamp);
96 Q_INVOKABLE void setLockedOutTime(qint64 timestamp);
92
97
98+ QStringList lifecycleExemptAppids() const;
99+ Q_INVOKABLE void setLifecycleExemptAppids(const QStringList &appIds);
100+
93Q_SIGNALS:
101Q_SIGNALS:
94 void pictureUriChanged(const QString&);
102 void pictureUriChanged(const QString&);
95 void usageModeChanged(const QString&);
103 void usageModeChanged(const QString&);
96 void lockedOutTimeChanged(qint64 timestamp);
104 void lockedOutTimeChanged(qint64 timestamp);
105+ void lifecycleExemptAppidsChanged(const QStringList&);
97
106
98private:
107private:
99 GSettingsControllerQml();
108 GSettingsControllerQml();
@@ -101,6 +110,7 @@ private:
101 QString m_pictureUri;
110 QString m_pictureUri;
102 QString m_usageMode;
111 QString m_usageMode;
103 qint64 m_lockedOutTime;
112 qint64 m_lockedOutTime;
113+ QStringList m_lifecycleExemptAppids;
104
114
105 static GSettingsControllerQml* s_controllerInstance;
115 static GSettingsControllerQml* s_controllerInstance;
106 QList<GSettingsQml *> m_registeredGSettings;
116 QList<GSettingsQml *> m_registeredGSettings;
tests/mocks/Unity/Application/ApplicationInfo.cpp+12
@@ -41,6 +41,7 @@ ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
41 Qt::InvertedLandscapeOrientation)
41 Qt::InvertedLandscapeOrientation)
42 , m_rotatesWindowContents(false)
42 , m_rotatesWindowContents(false)
43 , m_requestedState(RequestedRunning)
43 , m_requestedState(RequestedRunning)
44+ , m_isTouchApp(true)
44 , m_manualSurfaceCreation(false)
45 , m_manualSurfaceCreation(false)
45{
46{
46}
47}
@@ -58,6 +59,7 @@ ApplicationInfo::ApplicationInfo(QObject *parent)
58 Qt::InvertedLandscapeOrientation)
59 Qt::InvertedLandscapeOrientation)
59 , m_rotatesWindowContents(false)
60 , m_rotatesWindowContents(false)
60 , m_requestedState(RequestedRunning)
61 , m_requestedState(RequestedRunning)
62+ , m_isTouchApp(true)
61 , m_manualSurfaceCreation(false)
63 , m_manualSurfaceCreation(false)
62{
64{
63}
65}
@@ -245,6 +247,16 @@ void ApplicationInfo::setRequestedState(RequestedState value)
245 }
247 }
246}
248}
247
249
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+
248void ApplicationInfo::onSessionSurfaceChanged(MirSurface* surface)
260void ApplicationInfo::onSessionSurfaceChanged(MirSurface* surface)
249{
261{
250 if (surface != nullptr && m_state == Starting) {
262 if (surface != nullptr && m_state == Starting) {
tests/mocks/Unity/Application/ApplicationInfo.h+4
@@ -89,6 +89,9 @@ public:
89 bool manualSurfaceCreation() const { return m_manualSurfaceCreation; }
89 bool manualSurfaceCreation() const { return m_manualSurfaceCreation; }
90 void setManualSurfaceCreation(bool value);
90 void setManualSurfaceCreation(bool value);
91
91
92+ bool isTouchApp() const override;
93+ void setIsTouchApp(bool isTouchApp); // only in mock
94+
92public:
95public:
93 void setSession(Session* session);
96 void setSession(Session* session);
94 Session* session() const { return m_session; }
97 Session* session() const { return m_session; }
@@ -121,6 +124,7 @@ private:
121 Qt::ScreenOrientations m_supportedOrientations;
124 Qt::ScreenOrientations m_supportedOrientations;
122 bool m_rotatesWindowContents;
125 bool m_rotatesWindowContents;
123 RequestedState m_requestedState;
126 RequestedState m_requestedState;
127+ bool m_isTouchApp;
124
128
125 bool m_manualSurfaceCreation;
129 bool m_manualSurfaceCreation;
126};
130};
tests/mocks/Unity/Application/ApplicationManager.cpp+9
@@ -105,6 +105,8 @@ QVariant ApplicationManager::data(const QModelIndex& index, int role) const {
105 return app->state();
105 return app->state();
106 case RoleFocused:
106 case RoleFocused:
107 return app->focused();
107 return app->focused();
108+ case RoleIsTouchApp:
109+ return app->isTouchApp();
108 case RoleSession:
110 case RoleSession:
109 return QVariant::fromValue(app->session());
111 return QVariant::fromValue(app->session());
110 case RoleFullscreen:
112 case RoleFullscreen:
@@ -458,6 +460,13 @@ void ApplicationManager::buildListOfAvailableApplications()
458 application->setName("YouTube");
460 application->setName("YouTube");
459 application->setIconId("youtube");
461 application->setIconId("youtube");
460 m_availableApplications.append(application);
462 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);
461}
470}
462
471
463
472
tests/mocks/Unity/Application/ApplicationManager.h+1−1
@@ -43,7 +43,7 @@ class ApplicationManager : public ApplicationManagerInterface {
43 static ApplicationManager *singleton();
43 static ApplicationManager *singleton();
44
44
45 enum MoreRoles {
45 enum MoreRoles {
46− RoleSession = RoleFocused+1,
46+ RoleSession = RoleIsTouchApp+1,
47 RoleFullscreen,
47 RoleFullscreen,
48 };
48 };
49 enum Flag {
49 enum Flag {
tests/plugins/Unity/Launcher/launchermodeltest.cpp+1
@@ -57,6 +57,7 @@ public:
57 QColor splashColorFooter() const override { return QColor(0,0,0,0); }
57 QColor splashColorFooter() const override { return QColor(0,0,0,0); }
58 Qt::ScreenOrientations supportedOrientations() const override { return Qt::PortraitOrientation; }
58 Qt::ScreenOrientations supportedOrientations() const override { return Qt::PortraitOrientation; }
59 bool rotatesWindowContents() const override { return false; }
59 bool rotatesWindowContents() const override { return false; }
60+ bool isTouchApp() const override { return true; }
60
61
61 // Methods used for mocking (not in the interface)
62 // Methods used for mocking (not in the interface)
62 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
63 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
tests/qmltests/tst_Shell.qml+47
@@ -380,6 +380,8 @@ Rectangle {
380 LightDM.Greeter.authenticate(""); // reset greeter
380 LightDM.Greeter.authenticate(""); // reset greeter
381
381
382 sessionSpy.clear();
382 sessionSpy.clear();
383+
384+ GSettingsController.setLifecycleExemptAppids([]);
383 }
385 }
384
386
385 function killApps() {
387 function killApps() {
@@ -1794,5 +1796,50 @@ Rectangle {
1794 waitForRendering(shell);
1796 waitForRendering(shell);
1795 tryCompare(panelButtons, "visible", true);
1797 tryCompare(panelButtons, "visible", true);
1796 }
1798 }
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+ }
1797 }
1844 }
1798}
1845}