Merge lp:~dandrader/qtmir/appRestart-lp1527737 into lp:qtmir

Proposed by Daniel d'Andrada on 2016-06-22
Status: Work in progress
Proposed branch: lp:~dandrader/qtmir/appRestart-lp1527737
Merge into: lp:qtmir
Diff against target: 472 lines (+271/-63)
7 files modified
src/modules/Unity/Application/application_manager.cpp (+81/-61)
src/modules/Unity/Application/application_manager.h (+1/-1)
src/modules/Unity/Application/taskcontroller.h (+5/-1)
src/modules/Unity/Application/upstart/taskcontroller.cpp (+8/-0)
src/modules/Unity/Application/upstart/taskcontroller.h (+2/-0)
tests/framework/mock_task_controller.h (+1/-0)
tests/modules/ApplicationManager/application_manager_test.cpp (+173/-0)
To merge this branch: bzr merge lp:~dandrader/qtmir/appRestart-lp1527737
Reviewer Review Type Date Requested Status
Michael Zanetti (community) Abstain on 2016-07-04
Unity8 CI Bot continuous-integration 2016-06-22 Approve on 2016-06-28
Michał Sawicz 2016-06-22 Needs Fixing on 2016-06-28
Nick Dedekind (community) 2016-06-22 Approve on 2016-06-22
Gerry Boland 2016-06-22 Pending
PS Jenkins bot continuous-integration 2016-06-22 Pending
Review via email: mp+298125@code.launchpad.net

This proposal supersedes a proposal from 2016-06-16.

Commit Message

Handle case where TaskController requests an application that is already closing

Happens when you quickly close an application and then tap on its icon in the dash

Description of the Change

* Are there any related MPs required for this MP to build/function as expected? Please list.
No

 * Did you perform an exploratory manual test run of your code change and any related functionality?
Yes

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
Not applicable.

To post a comment you must log in.
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Fresh start. Rebased on top of latest trunk.

Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:506
https://unity8-jenkins.ubuntu.com/job/lp-qtmir-ci/278/
Executed test runs:
    FAILURE: https://unity8-jenkins.ubuntu.com/job/build/2001/console
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/2029
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/1946
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/1946
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/1946
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1937
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1937/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1937
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1937/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=yakkety/1937
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=yakkety/1937/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1937
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1937/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1937
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1937/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=yakkety/1937
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=yakkety/1937/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/1937
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/1937/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/1937
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/1937/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=yakkety/1937
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=yakkety/1937/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-qtmir-ci/278/rebuild

review: Needs Fixing (continuous-integration)
Unity8 CI Bot (unity8-ci-bot) wrote : Posted in a previous version of this proposal

PASSED: Continuous integration, rev:506
https://unity8-jenkins.ubuntu.com/job/lp-qtmir-ci/283/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/2053
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/2081
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/1992
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/1992
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/1992
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1983
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1983/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1983
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1983/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=yakkety/1983
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=yakkety/1983/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1983
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1983/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1983
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1983/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=yakkety/1983
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=yakkety/1983/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/1983
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/1983/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/1983
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/1983/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=yakkety/1983
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=yakkety/1983/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-qtmir-ci/283/rebuild

review: Approve (continuous-integration)
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

small comments.
Otherwise logic looks fine and passes tests.

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 22/06/2016 11:29, Nick Dedekind wrote:
>> +// std
>> >+#include <csignal>
> remove from application_manager.cpp?
>

I tried, but it won't compile otherwise. It's needed for the
"raise(SIGSTOP);" line.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 22/06/2016 11:29, Nick Dedekind wrote:
>> + Application *application = findApplication(appId);
> bit of a optimization. move this to after the check for queued/closing apps.
>

Done.

Nick Dedekind (nick-dedekind) wrote :

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes

 * Did CI run pass? If not, please explain why.
Yes.

review: Approve
Michael Zanetti (mzanetti) wrote :

Found a regression in Silo testing:

* Launch the telegram app
* Launch the gallery
* share a picture from the gallery to telegram

Expected: Telegram app focused
Actual: Focus stays in gallery

review: Needs Fixing
Michał Sawicz (saviq) wrote :

This breaks sharing to an already running app:
- start Telegram
- go to Gallery
- share to Telegram
- BREAKAGE (should focus Telegram, is back to gallery instead)

review: Needs Fixing
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:522
https://unity8-jenkins.ubuntu.com/job/lp-qtmir-ci/298/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/2161
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/2189
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2096
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2096
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2096
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/2087
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/2087/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2087
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2087/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=yakkety/2087
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=yakkety/2087/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/2087
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/2087/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2087
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2087/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=yakkety/2087
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=yakkety/2087/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/2087
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/2087/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/2087
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/2087/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=yakkety/2087
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=yakkety/2087/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-qtmir-ci/298/rebuild

review: Approve (continuous-integration)
523. By Daniel d'Andrada on 2016-06-28

Fix bad rebase on top of latest trunk

Code was still using the old, deprecated signal.

Daniel d'Andrada (dandrader) wrote :

> Found a regression in Silo testing:
>
> * Launch the telegram app
> * Launch the gallery
> * share a picture from the gallery to telegram
>
> Expected: Telegram app focused
> Actual: Focus stays in gallery

Fixed. It was caused by a bad rebase with latest trunk. The original branch was so old it was still using a deprecated signal.

Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:523
https://unity8-jenkins.ubuntu.com/job/lp-qtmir-ci/300/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/2165
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/2193
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2100
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2100
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2100
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/2091
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=vivid+overlay/2091/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2091
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/2091/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=yakkety/2091
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=yakkety/2091/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/2091
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=vivid+overlay/2091/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2091
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/2091/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=yakkety/2091
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=yakkety/2091/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/2091
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=vivid+overlay/2091/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/2091
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/2091/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=yakkety/2091
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=yakkety/2091/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-qtmir-ci/300/rebuild

review: Approve (continuous-integration)
Michael Zanetti (mzanetti) wrote :

Ok. It fixes the regression it had. But tbh in my opinion it doesn't behave totally great for the actual bug it tries to fix. When I close an app and quickly try to relaunch it, I get a haptics feedback from the apps scope, but no splash screen appearing. Only after some seconds later starting the app starts working. But I see there's a TODO in the code for exactly this, so... At least it's not worse than having the splash screen "crashing" as we had it before.

review: Abstain
Albert Astals Cid (aacid) wrote :

Text conflict in src/modules/Unity/Application/application_manager.cpp
Text conflict in src/modules/Unity/Application/application_manager.h
Text conflict in src/modules/Unity/Application/taskcontroller.h
3 conflicts encountered.

Unmerged revisions

523. By Daniel d'Andrada on 2016-06-28

Fix bad rebase on top of latest trunk

Code was still using the old, deprecated signal.

522. By Daniel d'Andrada on 2016-06-22

Optimize code

521. By Daniel d'Andrada on 2016-06-22

Handle case where TaskController requests an application that is already closing

Happens when you quickly close an application and then tap on its icon in the dash

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/modules/Unity/Application/application_manager.cpp'
2--- src/modules/Unity/Application/application_manager.cpp 2016-05-06 08:41:15 +0000
3+++ src/modules/Unity/Application/application_manager.cpp 2016-06-28 14:10:50 +0000
4@@ -56,6 +56,8 @@
5 namespace unityapi = unity::shell::application;
6
7 #define DEBUG_MSG qCDebug(QTMIR_APPLICATIONS).nospace() << "ApplicationManager::" << __func__
8+#define WARNING_MSG qCWarning(QTMIR_APPLICATIONS).nospace() << "ApplicationManager::" << __func__
9+#define CRITICAL_MSG qCCritical(QTMIR_APPLICATIONS).nospace() << "ApplicationManager::" << __func__
10
11 namespace qtmir
12 {
13@@ -102,8 +104,10 @@
14 manager, &ApplicationManager::onProcessFailed);
15 QObject::connect(controller, &TaskController::focusRequested,
16 manager, &ApplicationManager::onFocusRequested);
17+ // We interpret this as a focus request for a suspended app.
18+ // Shell will have this app resumed if it complies with the focus request
19 QObject::connect(controller, &TaskController::resumeRequested,
20- manager, &ApplicationManager::onResumeRequested);
21+ manager, &ApplicationManager::onFocusRequested);
22 }
23
24 } // namespace
25@@ -368,35 +372,61 @@
26 void ApplicationManager::onProcessStarting(const QString &appId)
27 {
28 tracepoint(qtmir, onProcessStarting);
29- qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessStarting - appId=" << appId;
30-
31- Application *application = findApplication(appId);
32- if (!application) { // then shell did not start this application, so ubuntu-app-launch must have - add to list
33- auto appInfo = m_taskController->getInfoForApp(appId);
34- if (!appInfo) {
35- qCWarning(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessStarting - Unable to instantiate application with appId" << appId;
36- return;
37- }
38-
39- application = new Application(
40- m_sharedWakelock,
41- appInfo,
42- QStringList(),
43- this);
44- add(application);
45- application->requestFocus();
46- }
47- else {
48- if (application->internalState() == Application::InternalState::StoppedResumable) {
49- // url-dispatcher can relaunch apps which have been OOM-killed - AppMan must accept the newly spawned
50- // application and focus it immediately (as user expects app to still be running).
51- qCDebug(QTMIR_APPLICATIONS) << "Stopped application appId=" << appId << "is being resumed externally";
52- application->requestFocus();
53- } else {
54- qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessStarting application already found with appId"
55- << appId;
56- }
57- }
58+
59+ Application *application = nullptr;
60+
61+ application = findClosingApplication(appId);
62+ if (application) {
63+ // TODO: We should create a new Application instance right away in order to give immediate
64+ // feedback to the user, even though there's no actual process backing it up yet.
65+ if (!m_queuedStartApplications.contains(appId)) {
66+ m_queuedStartApplications.append(appId);
67+ DEBUG_MSG << "(" << appId << ") User wants to start a new instance of an application that is still closing."
68+ " Queuing its start.";
69+ connect(application, &QObject::destroyed, this, [this, appId]() {
70+ m_queuedStartApplications.removeAll(appId);
71+ startApplication(appId, QStringList());
72+ }, Qt::QueuedConnection); // Queued so that we finish the app removal before starting again.
73+ } else {
74+ // Just ignore it.
75+ DEBUG_MSG << "(" << appId << ") User wants to start a new instance of an application that is still closing"
76+ " and is already queued to start later.";
77+ }
78+ } else {
79+ application = findApplication(appId);
80+ if (!application) { // then shell did not start this application, so ubuntu-app-launch must have - add to list
81+ createNewApplication(appId);
82+ } else {
83+ if (application->internalState() == Application::InternalState::StoppedResumable) {
84+ // url-dispatcher can relaunch apps which have been OOM-killed - AppMan must accept the newly spawned
85+ // application and focus it immediately (as user expects app to still be running).
86+ DEBUG_MSG << "(" << appId << ") Stopped application is being resumed externally";
87+ application->requestFocus();
88+ } else {
89+ DEBUG_MSG << "(" << appId << ") application already exists";
90+ }
91+ application->setProcessState(Application::ProcessRunning);
92+ }
93+ }
94+}
95+
96+void ApplicationManager::createNewApplication(const QString &appId)
97+{
98+ DEBUG_MSG << "(" << appId << ")";
99+
100+ auto appInfo = m_taskController->getInfoForApp(appId);
101+ if (!appInfo) {
102+ WARNING_MSG << "(" << appId << ") Unable to instantiate application";
103+ return;
104+ }
105+
106+ auto application = new Application(
107+ m_sharedWakelock,
108+ appInfo,
109+ QStringList(),
110+ this);
111+ add(application);
112+ application->requestFocus();
113 application->setProcessState(Application::ProcessRunning);
114 }
115
116@@ -412,7 +442,7 @@
117
118 Application *application = findApplication(appId);
119 if (!application) {
120- qCritical() << "No such running application with appId" << appId;
121+ CRITICAL_MSG << "(" << inputAppId << ") No such running application";
122 return false;
123 }
124
125@@ -454,14 +484,18 @@
126 tracepoint(qtmir, onProcessStopped);
127 qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessStopped - appId=" << appId;
128
129- Application *application = findApplication(appId);
130+ // TaskController doesn't give us any PID so we have no way of telling apart two instances
131+ // of an application.
132+ // The only situation where we expect to have two simultaneous instances is where there's one
133+ // closing and a newer one just starting, in which case we assume he's referring to the former.
134+ Application *application = findClosingApplication(appId);
135 if (!application) {
136- application = findClosingApplication(appId);
137+ application = findApplication(appId);
138 }
139
140 if (!application) {
141- qDebug() << "ApplicationManager::onProcessStopped reports stop of appId=" << appId
142- << "which AppMan is not managing, ignoring the event";
143+ // Might happen if the onProcessFailed() was called for this app and it already got deleted in response.
144+ DEBUG_MSG << "(" << appId << ") Not managing it, ignoring.";
145 return;
146 }
147
148@@ -490,29 +524,15 @@
149
150 void ApplicationManager::onFocusRequested(const QString& appId)
151 {
152- qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onFocusRequested - appId=" << appId;
153-
154- Application *application = findApplication(appId);
155- if (application) {
156- application->requestFocus();
157- }
158-}
159-
160-void ApplicationManager::onResumeRequested(const QString& appId)
161-{
162- qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onResumeRequested - appId=" << appId;
163-
164- Application *application = findApplication(appId);
165-
166- if (!application) {
167- qCritical() << "ApplicationManager::onResumeRequested: No such running application" << appId;
168- return;
169- }
170-
171- // We interpret this as a focus request for a suspended app.
172- // Shell will have this app resumed if it complies with the focus request
173- if (application->state() == Application::Suspended) {
174- application->requestFocus();
175+ if (m_queuedStartApplications.contains(appId) || findClosingApplication(appId) != nullptr) {
176+ DEBUG_MSG << "(" << appId << ") Ignoring request as the application is closing and/or queued to start";
177+ } else {
178+ Application *application = findApplication(appId);
179+ if (application != nullptr) {
180+ application->requestFocus();
181+ } else {
182+ WARNING_MSG << "(" << appId << ") No such running application";
183+ }
184 }
185 }
186
187@@ -702,9 +722,9 @@
188
189 connect(application, &Application::stopProcessRequested, this, [=]() {
190 if (!m_taskController->stop(appId) && application->pid() > 0) {
191- qWarning() << "FAILED to ask Upstart to stop application with appId" << appId
192- << "Sending SIGTERM to process:" << appId;
193- kill(application->pid(), SIGTERM);
194+ qCWarning(QTMIR_APPLICATIONS) << "FAILED to ask Upstart to stop application with appId" << appId
195+ << "Sending SIGTERM to process";
196+ m_taskController->kill(application->pid());
197 application->setProcessState(Application::ProcessStopped);
198 }
199 });
200
201=== modified file 'src/modules/Unity/Application/application_manager.h'
202--- src/modules/Unity/Application/application_manager.h 2016-05-05 15:02:01 +0000
203+++ src/modules/Unity/Application/application_manager.h 2016-06-28 14:10:50 +0000
204@@ -111,7 +111,6 @@
205 void onProcessSuspended(const QString& appId);
206 void onProcessFailed(const QString& appId, TaskController::Error error);
207 void onFocusRequested(const QString& appId);
208- void onResumeRequested(const QString& appId);
209
210 Q_SIGNALS:
211 void emptyChanged();
212@@ -132,6 +131,7 @@
213 QModelIndex findIndex(Application* application);
214 void resumeApplication(Application *application);
215 QString toString() const;
216+ void createNewApplication(const QString &appId);
217
218 Application* findApplicationWithPromptSession(const mir::scene::PromptSession* promptSession);
219 Application *findClosingApplication(const QString &inputAppId) const;
220
221=== modified file 'src/modules/Unity/Application/taskcontroller.h'
222--- src/modules/Unity/Application/taskcontroller.h 2016-04-22 14:03:26 +0000
223+++ src/modules/Unity/Application/taskcontroller.h 2016-06-28 14:10:50 +0000
224@@ -1,5 +1,5 @@
225 /*
226- * Copyright (C) 2014-2015 Canonical, Ltd.
227+ * Copyright (C) 2014-2016 Canonical, Ltd.
228 *
229 * This program is free software: you can redistribute it and/or modify it under
230 * the terms of the GNU Lesser General Public License version 3, as published by
231@@ -54,6 +54,10 @@
232
233 virtual QSharedPointer<qtmir::ApplicationInfo> getInfoForApp(const QString &appId) const = 0;
234
235+ // Not a perfect fit for this class but we need the kill(pid_t pid, int sig) function
236+ // behind an interface so we can test it (replacing with a mock or fake)
237+ virtual void kill(pid_t pid) = 0;
238+
239 Q_SIGNALS:
240 void processStarting(const QString &appId);
241 void applicationStarted(const QString &appId);
242
243=== modified file 'src/modules/Unity/Application/upstart/taskcontroller.cpp'
244--- src/modules/Unity/Application/upstart/taskcontroller.cpp 2016-05-27 07:27:44 +0000
245+++ src/modules/Unity/Application/upstart/taskcontroller.cpp 2016-06-28 14:10:50 +0000
246@@ -30,6 +30,9 @@
247 }
248 #include <ubuntu-app-launch/registry.h>
249
250+// std
251+#include <csignal>
252+
253 namespace ual = ubuntu::app_launch;
254
255 namespace qtmir
256@@ -243,5 +246,10 @@
257 return QSharedPointer<qtmir::ApplicationInfo>(appInfo);
258 }
259
260+void TaskController::kill(pid_t pid)
261+{
262+ ::kill(pid, SIGTERM);
263+}
264+
265 } // namespace upstart
266 } // namespace qtmir
267
268=== modified file 'src/modules/Unity/Application/upstart/taskcontroller.h'
269--- src/modules/Unity/Application/upstart/taskcontroller.h 2016-04-22 14:03:26 +0000
270+++ src/modules/Unity/Application/upstart/taskcontroller.h 2016-06-28 14:10:50 +0000
271@@ -41,6 +41,8 @@
272
273 QSharedPointer<qtmir::ApplicationInfo> getInfoForApp(const QString &appId) const override;
274
275+ void kill(pid_t pid) override;
276+
277 private:
278 struct Private;
279 QScopedPointer<Private> impl;
280
281=== modified file 'tests/framework/mock_task_controller.h'
282--- tests/framework/mock_task_controller.h 2016-04-22 14:03:26 +0000
283+++ tests/framework/mock_task_controller.h 2016-06-28 14:10:50 +0000
284@@ -38,6 +38,7 @@
285 MOCK_METHOD2(start, bool(const QString&, const QStringList&));
286 MOCK_METHOD1(suspend, bool(const QString&));
287 MOCK_METHOD1(resume, bool(const QString&));
288+ MOCK_METHOD1(kill, void(pid_t pid));
289
290 bool doAppIdHasProcessId(const QString& appId, pid_t pid);
291
292
293=== modified file 'tests/modules/ApplicationManager/application_manager_test.cpp'
294--- tests/modules/ApplicationManager/application_manager_test.cpp 2016-05-30 14:50:26 +0000
295+++ tests/modules/ApplicationManager/application_manager_test.cpp 2016-06-28 14:10:50 +0000
296@@ -1964,3 +1964,176 @@
297
298 delete surface;
299 }
300+
301+/*
302+ * Context:
303+ * - we are waiting for an application to quit following a close()
304+ *
305+ * Event:
306+ * - TaskController requests that application to be resumed.
307+ * Most likely because the user tapped the app icon in the Dash. From the user's
308+ * perspective the application is already closed and he wants to start a new
309+ * instance of it.
310+ * But from TaskController's perspective the application is still running and
311+ * therefore should just be resumed/focused.
312+ *
313+ * Expected outcome:
314+ * - ApplicationManager should wait for the closing application to finally end (or
315+ be killed) and then finally launch a new instance of it.
316+ *
317+ * Regression test for https://bugs.launchpad.net/ubuntu/+source/qtmir/+bug/1527737
318+ * "Apps do not start if restarted quickly after closing"
319+ *
320+ * Two test versions:
321+ * _appQuits - Application complies and quits
322+ * _appKilled - Application ignores close request and thus get killed
323+ */
324+TEST_F(ApplicationManagerTests, focusRequestedForClosingApplication_appQuits)
325+{
326+ using namespace ::testing;
327+
328+ int argc = 0;
329+ char* argv[0];
330+ QCoreApplication qtApp(argc, argv); // app for deleteLater event
331+
332+ const QString appId("testAppId");
333+ quint64 procId = 5551;
334+
335+ ON_CALL(*taskController, appIdHasProcessId(appId, procId)).WillByDefault(Return(true));
336+
337+ EXPECT_CALL(*taskController, start(appId, _))
338+ .Times(1)
339+ .WillOnce(Return(true));
340+
341+ auto app = applicationManager.startApplication(appId);
342+ applicationManager.onProcessStarting(appId);
343+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
344+ bool authed = true;
345+ applicationManager.authorizeSession(procId, authed);
346+ ASSERT_TRUE(authed);
347+ onSessionStarting(session);
348+
349+ FakeMirSurface *surface = new FakeMirSurface;
350+ onSessionCreatedSurface(session.get(), surface);
351+ surface->drawFirstFrame();
352+
353+ ASSERT_EQ(Application::InternalState::Running, app->internalState());
354+
355+ QSignalSpy closeRequestedSpy(surface, SIGNAL(closeRequested()));
356+
357+ // User swipes away the application, visually closing/removing it.
358+ applicationManager.stopApplication(appId);
359+
360+ // But asking ApplicationManager to stop the application just makes it request its
361+ // surfaces to be closed
362+ EXPECT_EQ(Application::InternalState::Closing, app->internalState());
363+ EXPECT_EQ(1, closeRequestedSpy.count());
364+
365+ // User then taps on the app icon
366+ applicationManager.onProcessStarting(appId);
367+ applicationManager.onFocusRequested(appId);
368+
369+ // But no application instance should have been created because of that
370+ EXPECT_TRUE(applicationManager.findApplication(appId) == nullptr);
371+
372+ // ApplicationManager will ask TaskController to start the application again
373+ // once the closing instance is gone
374+ Mock::VerifyAndClearExpectations(taskController);
375+ EXPECT_CALL(*taskController, start(appId, _))
376+ .Times(1)
377+ .WillOnce(Return(true));
378+
379+ // Simulates that the application complied to the close() request and stopped itself
380+ onSessionStopping(session);
381+ applicationManager.onProcessStopped(appId);
382+
383+ // DeferredDelete is special: likes to be called out specifically or it won't come out
384+ qtApp.sendPostedEvents(app, QEvent::DeferredDelete);
385+ qtApp.sendPostedEvents();
386+
387+ EXPECT_TRUE(applicationManager.findApplication(appId) != nullptr);
388+}
389+TEST_F(ApplicationManagerTests, focusRequestedForClosingApplication_appKilled)
390+{
391+ using namespace ::testing;
392+
393+ int argc = 0;
394+ char* argv[0];
395+ QCoreApplication qtApp(argc, argv); // app for deleteLater event
396+
397+ const QString appId("testAppId");
398+ quint64 procId = 5551;
399+
400+ ON_CALL(*taskController, appIdHasProcessId(appId, procId)).WillByDefault(Return(true));
401+
402+ EXPECT_CALL(*taskController, start(appId, _))
403+ .Times(1)
404+ .WillOnce(Return(true));
405+
406+ auto app = applicationManager.startApplication(appId);
407+ applicationManager.onProcessStarting(appId);
408+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
409+ bool authed = true;
410+ applicationManager.authorizeSession(procId, authed);
411+ ASSERT_TRUE(authed);
412+ onSessionStarting(session);
413+
414+ FakeMirSurface *surface = new FakeMirSurface;
415+ onSessionCreatedSurface(session.get(), surface);
416+ surface->drawFirstFrame();
417+
418+ EXPECT_EQ(Application::InternalState::Running, app->internalState());
419+
420+ QSharedPointer<FakeTimeSource> fakeTimeSource(new FakeTimeSource);
421+ FakeTimer *fakeStopTimer = new FakeTimer(fakeTimeSource);
422+ app->setStopTimer(fakeStopTimer);
423+
424+ QSignalSpy closeRequestedSpy(surface, SIGNAL(closeRequested()));
425+
426+ // User swipes away the application, visually closing/removing it.
427+ applicationManager.stopApplication(appId);
428+
429+ // But asking ApplicationManager to stop the application just makes it request its
430+ // surfaces to be closed
431+ EXPECT_EQ(Application::InternalState::Closing, app->internalState());
432+ EXPECT_EQ(1, closeRequestedSpy.count());
433+
434+ // User then taps on the app icon
435+ applicationManager.onProcessStarting(appId);
436+ applicationManager.onFocusRequested(appId);
437+
438+ // But no application instance should have been created because of that
439+ EXPECT_TRUE(applicationManager.findApplication(appId) == nullptr);
440+
441+ // ApplicationManager will ask TaskController to start the application again
442+ // once the closing instance is gone
443+ Mock::VerifyAndClearExpectations(taskController);
444+ EXPECT_CALL(*taskController, start(appId, _))
445+ .Times(1)
446+ .WillOnce(Return(true));
447+
448+ EXPECT_CALL(*taskController, stop(appId))
449+ .Times(1)
450+ .WillOnce(Return(false));
451+
452+ EXPECT_CALL(*taskController, kill(procId))
453+ .Times(1)
454+ .WillOnce(Invoke(
455+ [this, session](pid_t) {
456+ // simulate application dying and thus severing the mir connection
457+ onSessionStopping(session);
458+ }
459+ ));
460+
461+ if (fakeStopTimer->isRunning()) {
462+ // Simulate that it has timed out.
463+ fakeTimeSource->m_msecsSinceReference = fakeStopTimer->nextTimeoutTime() + 1;
464+ fakeStopTimer->update();
465+ }
466+
467+ // DeferredDelete is special: likes to be called out specifically or it won't come out
468+ qtApp.sendPostedEvents(app, QEvent::DeferredDelete);
469+ qtApp.sendPostedEvents();
470+
471+ EXPECT_TRUE(applicationManager.findApplication(appId) != nullptr);
472+}

Subscribers

People subscribed via source and target branches