Merge lp:~dandrader/qtmir/detach-state-from-focus into lp:qtmir

Proposed by Daniel d'Andrada on 2015-05-08
Status: Merged
Approved by: Gerry Boland on 2015-06-30
Approved revision: 362
Merged at revision: 350
Proposed branch: lp:~dandrader/qtmir/detach-state-from-focus
Merge into: lp:qtmir
Prerequisite: lp:~saviq/qtmir/fix-application-api-deps
Diff against target: 4704 lines (+1598/-1505)
38 files modified
CMakeLists.txt (+3/-1)
debian/control (+1/-1)
src/modules/Unity/Application/CMakeLists.txt (+1/-0)
src/modules/Unity/Application/application.cpp (+341/-69)
src/modules/Unity/Application/application.h (+61/-19)
src/modules/Unity/Application/application_manager.cpp (+54/-250)
src/modules/Unity/Application/application_manager.h (+2/-13)
src/modules/Unity/Application/applicationcontroller.h (+2/-1)
src/modules/Unity/Application/applicationscreenshotprovider.cpp (+1/-1)
src/modules/Unity/Application/mirsurfaceitem.cpp (+4/-4)
src/modules/Unity/Application/mirsurfaceitem.h (+18/-74)
src/modules/Unity/Application/mirsurfaceiteminterface.h (+116/-0)
src/modules/Unity/Application/mirsurfaceitemmodel.h (+2/-2)
src/modules/Unity/Application/mirsurfacemanager.cpp (+4/-4)
src/modules/Unity/Application/mirsurfacemanager.h (+3/-3)
src/modules/Unity/Application/plugin.cpp (+2/-2)
src/modules/Unity/Application/session.cpp (+123/-73)
src/modules/Unity/Application/session.h (+15/-8)
src/modules/Unity/Application/session_interface.h (+31/-23)
src/modules/Unity/Application/taskcontroller.cpp (+8/-13)
src/modules/Unity/Application/taskcontroller.h (+4/-6)
src/modules/Unity/Application/upstart/applicationcontroller.cpp (+10/-2)
src/platforms/mirserver/CMakeLists.txt (+2/-0)
tests/modules/Application/CMakeLists.txt (+4/-0)
tests/modules/Application/application_test.cpp (+145/-71)
tests/modules/ApplicationManager/CMakeLists.txt (+4/-1)
tests/modules/ApplicationManager/application_manager_test.cpp (+314/-831)
tests/modules/MirSurfaceItem/CMakeLists.txt (+1/-1)
tests/modules/MirSurfaceItem/mirsurfaceitem_test.cpp (+1/-1)
tests/modules/SessionManager/CMakeLists.txt (+3/-0)
tests/modules/SessionManager/session_manager_test.cpp (+1/-1)
tests/modules/SessionManager/session_test.cpp (+57/-24)
tests/modules/TaskController/CMakeLists.txt (+1/-0)
tests/modules/common/fake_mirsurfaceitem.h (+95/-0)
tests/modules/common/mock_mirsurfaceitem.h (+50/-0)
tests/modules/common/mock_session.h (+44/-5)
tests/modules/common/qtmir_test.cpp (+61/-0)
tests/modules/common/qtmir_test.h (+9/-1)
To merge this branch: bzr merge lp:~dandrader/qtmir/detach-state-from-focus
Reviewer Review Type Date Requested Status
Gerry Boland 2015-05-08 Approve on 2015-06-29
PS Jenkins bot continuous-integration Needs Fixing on 2015-06-29
Nick Dedekind (community) Needs Fixing on 2015-06-17
Review via email: mp+258648@code.launchpad.net

Commit Message

Remove focus-based app lifecycle. Let shell control it.

API changes:
- ApplicationManager: removed suspended and forceDashActive
- Added Application.requestedState

Also refactored and clearly defined Application and Session states and state transitions.
- Let Application define its own state based on events provided by ApplicationManager. Previously that responsibility was split between those classes.
- Session now defines its own state based on events and requests fed by Application, SessionManager and SurfaceManager.

Description of the Change

* Are there any related MPs required for this MP to build/function as expected? Please list.
https://code.launchpad.net/~dandrader/unity-api/app-state-handling/+merge/258643
https://code.launchpad.net/~dandrader/unity8/app-state-handling/+merge/258653

* 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.
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
336. By Daniel d'Andrada on 2015-05-08

Application: s/active/requestedState

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Gerry Boland (gerboland) wrote :

+ m_requestedState = value;
+ Q_EMIT requestedStateChanged(m_requestedState);

+ if (m_requestedState == RequestedRunning && m_state == Suspended) {
+ setState(Running);
+ } else if (m_requestedState == RequestedSuspended && m_state == Running) {
+ setState(Suspended);
+ }

please emit the requestedStateChanged signal after the state change work has been done, not before.

Do bump the required unity-api API version in CMakeList.txt and the unity-api package version dependency.

Rest looks good, but will need to test to be sure

review: Needs Fixing (code)
337. By Daniel d'Andrada on 2015-05-11

Emit signal at the end

338. By Daniel d'Andrada on 2015-05-11

Update package version dependency

Daniel d'Andrada (dandrader) wrote :

> + m_requestedState = value;
> + Q_EMIT requestedStateChanged(m_requestedState);
>
> + if (m_requestedState == RequestedRunning && m_state == Suspended) {
> + setState(Running);
> + } else if (m_requestedState == RequestedSuspended && m_state == Running)
> {
> + setState(Suspended);
> + }
>
> please emit the requestedStateChanged signal after the state change work has
> been done, not before.
>

Done.

> Do bump the required unity-api API version in CMakeList.txt

Already did.

> and the unity-api package version dependency.

Done.

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
339. By Daniel d'Andrada on 2015-05-12

Merge trunk

[ Albert Astals Cid ]
* Fix debug line
[ Daniel d'Andrada ]
* When synthesizing touch releases for absent touches, send them in
  separate events (LP: #1437357)
[ Gerry Boland ]
* If Mir fails to start, exit the process immediately as nothing else
  can be done
* Remove boost dependence, it supplies almost nothing of benefit to
  offset its cost
* Remove legacy surface configuration change code, use newer
  SurfaceObserver
* Remove useless profiling information, fixes build with Qt5.5 (LP:
  #1437181)
[ Michael Zanetti ]
* read exception list from gsettings instead of a hardcoded list
[ Michał Sawicz ]
* Require an application API version, fix the provided version and use
  include dir from the .pc file

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
340. By Daniel d'Andrada on 2015-05-27

Merge trunk

[ Alan Griffiths ]
* Release in step with Mir 0.13.0
[ Daniel van Vugt ]
* Release in step with Mir 0.13.0
[ Gerry Boland ]
* Release in step with Mir 0.13.0

341. By Daniel d'Andrada on 2015-05-27

Fixed all issues pointed out by code review

- Refactor and clearly define Application and Session states and state transitions.
   * Let Application define its own state based on events provided by
     ApplicationManager. Previously that responsibility was split between those
     classes
   * Session now defines its own state based on events and requests fed by Application,
     SessionManager and SurfaceManager.
- Respond to "resume requested" upstart notifications. This was erroniously deleted earlier

Gerry Boland (gerboland) wrote :

/«BUILDDIR»/qtmir-0.4.4+15.04.20150513/tests/modules/ApplicationManager/application_manager_test.cpp: In member function 'virtual void ApplicationManagerTests_appStartedByShell_Test::TestBody()':
/«BUILDDIR»/qtmir-0.4.4+15.04.20150513/tests/modules/ApplicationManager/application_manager_test.cpp:483:167: error: converting 'false' to pointer type for argument 1 of 'char testing::internal::IsNullLiteralHelper(testing::internal::Secret*)' [-Werror=conversion-null]
     EXPECT_EQ(false, theApp->canBeResumed());
                                                                                                                                                                       ^
cc1plus: all warnings being treated as errors

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote :

On 28/05/15 09:36, Gerry Boland wrote:
> Review: Needs Fixing
>
> /«BUILDDIR»/qtmir-0.4.4+15.04.20150513/tests/modules/ApplicationManager/application_manager_test.cpp: In member function 'virtual void ApplicationManagerTests_appStartedByShell_Test::TestBody()':
> /«BUILDDIR»/qtmir-0.4.4+15.04.20150513/tests/modules/ApplicationManager/application_manager_test.cpp:483:167: error: converting 'false' to pointer type for argument 1 of 'char testing::internal::IsNullLiteralHelper(testing::internal::Secret*)' [-Werror=conversion-null]
> EXPECT_EQ(false, theApp->canBeResumed());
> ^
> cc1plus: all warnings being treated as errors
>

Builds fine here and this error doesn't make sense to me.
Maybe it only happens when cross-building?

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Daniel d'Andrada (dandrader) wrote :

On 28/05/15 10:09, Daniel d'Andrada wrote:
> On 28/05/15 09:36, Gerry Boland wrote:
>> Review: Needs Fixing
>>
>> /«BUILDDIR»/qtmir-0.4.4+15.04.20150513/tests/modules/ApplicationManager/application_manager_test.cpp: In member function 'virtual void ApplicationManagerTests_appStartedByShell_Test::TestBody()':
>> /«BUILDDIR»/qtmir-0.4.4+15.04.20150513/tests/modules/ApplicationManager/application_manager_test.cpp:483:167: error: converting 'false' to pointer type for argument 1 of 'char testing::internal::IsNullLiteralHelper(testing::internal::Secret*)' [-Werror=conversion-null]
>> EXPECT_EQ(false, theApp->canBeResumed());
>> ^
>> cc1plus: all warnings being treated as errors
>>
> Builds fine here and this error doesn't make sense to me.
> Maybe it only happens when cross-building?
>

After spending most of the day struggling to get my cross-build setup up
and running, I was able to cross-build this branch just fine here. Could
it be that you have an old chroot?

I cross-built it in a vivid+overlay_ppa chroot.

342. By Daniel d'Andrada on 2015-05-29

work around a bug in gtest+gcc+cross-compilation

343. By Daniel d'Andrada on 2015-05-29

s/KilledOutOfMemory/DiedUnexpectedly

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
344. By Daniel d'Andrada on 2015-06-01

Session does Starting->Running only once first surface frame is drawn

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
345. By Daniel d'Andrada on 2015-06-02

Workaround a weird behavior in unity8's PhoneStage

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Gerry Boland (gerboland) wrote :
Download full text (3.4 KiB)

+++ src/modules/Unity/Application/application.h
+ enum ProcessState {
can you not do "enum class"? The type safety would be nice

+ enum class InternalState {
+ RunningWithoutWakelock,
I'd prefer "RunningInBackground" just to avoid the wakelock term (which isn't applicable on desktop). Note that's a state we could maybe remove in future, as unity8 should deal with lifecycle exemptions, not qtmir. Let's see.

+ DiedUnexpectedly,
+ Stopped // It closed itself, crashed or it stopped and we can't respawn it
+ // In any case, this is a dead end.
on second thought, let's make the names similar.

+ };
+ Application(const QSharedPointer<SharedWakelock>& sharedWakelock,
add line break please.

+ void setProcessState(ProcessState value);
This called by AppMan (which is a friend class), and by tests. If wasn't for test, I'd ask to make this protected.

+ ProcessState processState() const;
I see nothing calling this.

+ void setState(InternalState state);
we have 3 different state properties now, let's avoid the confusion and be explicit: rename to setInternalState please.

I like your removal of TaskController from Application. +1
I'm not so keen on there being 3 separate state variables to keep track of, as it introduces risk of invalid state combinations appearing. The code in applyRequestedState() does scare me. But it is more explicit in its intentions, so am gonna accept.

+++ src/modules/Unity/Application/application.cpp

+const char* Application::internalStateToStr(InternalState state)
+ default:
+ return "???";
should never happen, so don't include it. If a new entry is added to enum, the compiler will warn us (if no default set).

+void Application::applyRequestedState()
could you rewrite this to use a switch/case statement, it would help ensure all m_state options are being considered, and might make it more readable too.

+void Application::setSession(SessionInterface *newSession)
+ switch (m_state) {
+ case InternalState::Starting:
+ case InternalState::Running:
+ case InternalState::RunningWithoutWakelock:
+ m_session->resume();
Why fire resume? I'm unsure of a realistic situation when a Running app would have the associated Session change.

+void Application::setState(Application::InternalState state)
+ case InternalState::DiedUnexpectedly:
+ releaseWakelock();
If app is in foreground (i.e. has a surface with activeFocus) and stops unexpectedly, the app should be removed from the list. But if in background, we assume OOM killer killed it, so leave it in the app list. This worked when I tested it, but now am unsure how!

+void Application::setProcessState(ProcessState newProcessState)
again, I'd prefer a switch/case statement, as compiler does warn if you missed an option (if no default defined).

+void Application::resume()
+ if (m_state == InternalState::Suspended) {
+ setState(InternalState::Running);
+ Q_EMIT resumeProcessRequested();
+ if (m_processState == ProcessSuspended) {
+ setProcessState(ProcessRunning); // should we wait for a resumed() signal?
I think we should, no? Otherwise the...

Read more...

review: Needs Fixing (code)
Gerry Boland (gerboland) wrote :

+++ src/modules/Unity/Application/application_manager.cpp

+ shouldRequestFocus = true;
I don't see the use for this variable, you could just emit focusRequested here like the previous code did.

+ // Applications fail if they fail to launch, crash or are killed.
well crash == 'fail to launch' :) Something more exact would be that the process stopped with a failure return code. </bikeshed>

+ application->setPid(0);
why bother, you've already set the application's process state to Stopped which makes that fact clear.

+ // The connection is queued as a workaround an issue in the PhoneStage animation....
yep, we need to consider a separate thread for costly operations. Later todo.

+ QStringList arguments =application->arguments();
missing space

review: Needs Fixing (code)
Gerry Boland (gerboland) wrote :

+++ src/modules/Unity/Application/mirsurfaceiteminterface.h
Why this change in this MR?

review: Needs Information
Gerry Boland (gerboland) wrote :

+++ src/modules/Unity/Application/session.cpp
All changes look ok. Just a niggle: Session::insertChildSession, remove the default case from the switch.

src/modules/Unity/Application/session.h
+ // it's public to ease testing
+ void doSuspend();
I do dislike having to do this, but the purpose is clear.

Gerry Boland (gerboland) wrote :

+++ tests/modules/Application/application_test.cpp
+ delete application;
QScopedPointer? :)

You're testing how the setState*() calls impact your state machine. I'd like to see more such tests please.

Gerry Boland (gerboland) wrote :

+++ tests/modules/ApplicationManager/application_manager_test.cpp

 TEST_F(ApplicationManagerTests,shellStopsBackgroundAppCorrectly)
the text name is incorrect now.

+// For better output in ASSERT_* and EXPECT_* error messages
+void PrintTo(const Application::InternalState& state, ::std::ostream* os);
+void PrintTo(const SessionInterface::State& state, ::std::ostream* os);
very nice, didn't know you could do that

Gerry Boland (gerboland) wrote :

> +++ src/modules/Unity/Application/mirsurfaceiteminterface.h
> Why this change in this MR?
I should've read on, you need it to mock it for testing. Is fine.

Gerry Boland (gerboland) wrote :

The following tests FAILED:
   5 - ApplicationManager (SEGFAULT)
Errors while running CTest

review: Needs Fixing
346. By Daniel d'Andrada on 2015-06-15

s/RunningWithoutWakelock/RunningInBackground

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
347. By Daniel d'Andrada on 2015-06-15

Merge trunk

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
348. By Daniel d'Andrada on 2015-06-15

Update tests and remove duplicate ones

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
349. By Daniel d'Andrada on 2015-06-15

s/DiedUnexpectedly/StoppedUnexpectedly

350. By Daniel d'Andrada on 2015-06-15

Removed unused Application::processState()

351. By Daniel d'Andrada on 2015-06-15

Application: s/setState/setInternalState

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
352. By Daniel d'Andrada on 2015-06-15

Refactor Application::applyRequestedState()

353. By Daniel d'Andrada on 2015-06-15

Removed redundant code

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
354. By Daniel d'Andrada on 2015-06-15

Refactor Application::setProcessState

Daniel d'Andrada (dandrader) wrote :
Download full text (4.3 KiB)

On 12/06/15 06:40, Gerry Boland wrote:
> Review: Needs Fixing code
>
> +++ src/modules/Unity/Application/application.h
> + enum ProcessState {
> can you not do "enum class"? The type safety would be nice

Not changing it as discussed on IRC.

>
> + enum class InternalState {
> + RunningWithoutWakelock,
> I'd prefer "RunningInBackground" just to avoid the wakelock term (which isn't applicable on desktop).

Done.

>
> + DiedUnexpectedly,
> + Stopped // It closed itself, crashed or it stopped and we can't respawn it
> + // In any case, this is a dead end.
> on second thought, let's make the names similar.

Done.

>
> + };
> + Application(const QSharedPointer<SharedWakelock>& sharedWakelock,
> add line break please.

Already has it. Maybe you were looking at an older version.

> + ProcessState processState() const;
> I see nothing calling this.

Removed.

>
> + void setState(InternalState state);
> we have 3 different state properties now, let's avoid the confusion and be explicit: rename to setInternalState please.

Done. I didn't rename it to setInternalState earlier out of pure
laziness during the refactoring.

> I like your removal of TaskController from Application. +1
> I'm not so keen on there being 3 separate state variables to keep track of, as it introduces risk of invalid state combinations appearing. The code in applyRequestedState() does scare me. But it is more explicit in its intentions, so am gonna accept.
Believe me, the original code was way scarier, as it didn't track
anything and was drowning in assumptions. :)
>
> +++ src/modules/Unity/Application/application.cpp
>
> +const char* Application::internalStateToStr(InternalState state)
> + default:
> + return "???";
> should never happen, so don't include it. If a new entry is added to enum, the compiler will warn us (if no default set).

Code doesn't compile without it.

> +void Application::applyRequestedState()
> could you rewrite this to use a switch/case statement, it would help ensure all m_state options are being considered, and might make it more readable too.
Done.
> +void Application::setSession(SessionInterface *newSession)
> + switch (m_state) {
> + case InternalState::Starting:
> + case InternalState::Running:
> + case InternalState::RunningWithoutWakelock:
> + m_session->resume();
> Why fire resume? I'm unsure of a realistic situation when a Running app would have the associated Session change.

For the same reason I fire suspend() and stop(). To ensure the session
is consistent with the application state. It might be unrealistic
indeed, but better safe than sorry I guess. And if I would remove the
resume() and would also have to remove the other cases otherwise it
would be inconsistent.

> +void Application::setState(Application::InternalState state)
> + case InternalState::DiedUnexpectedly:
> + releaseWakelock();
> If app is in foreground (i.e. has a surface with activeFocus) and stops unexpectedly, the app should be removed from the list. But if in background, we assume OOM killer killed it, so leave it in the app list. This worked when I tested it, but now ...

Read more...

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
355. By Daniel d'Andrada on 2015-06-15

Remove redundant variable

356. By Daniel d'Andrada on 2015-06-15

Code cosmetics

Daniel d'Andrada (dandrader) wrote :

On 12/06/15 07:21, Gerry Boland wrote:
> Review: Needs Fixing code
>
> +++ src/modules/Unity/Application/application_manager.cpp
>
> + shouldRequestFocus = true;
> I don't see the use for this variable, you could just emit focusRequested here like the previous code did.
It made sense at some point during the refactoring, but as code kept
moving around, it's now redundant indeed. Removed
> + // Applications fail if they fail to launch, crash or are killed.
> well crash == 'fail to launch' :) Something more exact would be that the process stopped with a failure return code. </bikeshed>
Don't blame me, this is your own writing! :-D
I just moved it around, along with the code.

> + application->setPid(0);
> why bother, you've already set the application's process state to Stopped which makes that fact clear.
Consistency. It's like setting pointers to null after deleting them.
> + QStringList arguments =application->arguments();
> missing space
Fixed.

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
357. By Daniel d'Andrada on 2015-06-16

Fix Session::insertChildSession switch statement

Daniel d'Andrada (dandrader) wrote :

On 12/06/15 07:40, Gerry Boland wrote:
> +++ src/modules/Unity/Application/session.cpp
> All changes look ok. Just a niggle: Session::insertChildSession, remove the default case from the switch.

Done.

358. By Daniel d'Andrada on 2015-06-16

Rename test

Daniel d'Andrada (dandrader) wrote :

On 12/06/15 08:23, Gerry Boland wrote:
> +++ tests/modules/ApplicationManager/application_manager_test.cpp
>
> TEST_F(ApplicationManagerTests,shellStopsBackgroundAppCorrectly)
> the text name is incorrect now.

Renamed the test.

Daniel d'Andrada (dandrader) wrote :

On 12/06/15 10:13, Gerry Boland wrote:
> Review: Needs Fixing
>
> The following tests FAILED:
> 5 - ApplicationManager (SEGFAULT)
> Errors while running CTest
>
Fixed.

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
359. By Daniel d'Andrada on 2015-06-16

Use QScopedPointer in Application tests

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Daniel d'Andrada (dandrader) wrote :

On 12/06/2015 07:52, Gerry Boland wrote:
> +++ tests/modules/Application/application_test.cpp
> + delete application;
> QScopedPointer? :)

Done.

> You're testing how the setState*() calls impact your state machine. I'd like to see more such tests please.

I think ApplicationManager tests have it pretty much covered.Some tests
might event make more sense to be moved from ApplicationManager to here
as the former is now mainly a demux for qtmir::Applications.

Nick Dedekind (nick-dedekind) wrote :

Lools like there's an issue in MockMirSurfaceItem.

orientationAngle pure virtual.
setOrientationAngle pure virtual.

Looks like it's changed from orientation/setOrientation

review: Needs Fixing
360. By Daniel d'Andrada on 2015-06-17

Update MockMirSurfaceItem

Daniel d'Andrada (dandrader) wrote :

On 17/06/15 06:14, Nick Dedekind wrote:
> Review: Needs Fixing
>
> Lools like there's an issue in MockMirSurfaceItem.
>
> orientationAngle pure virtual.
> setOrientationAngle pure virtual.
>
> Looks like it's changed from orientation/setOrientation

Fixed.

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Gerry Boland (gerboland) wrote :

please bump unity-api dependence to 7.98

review: Needs Fixing
361. By Daniel d'Andrada on 2015-06-18

Update unity-api version dependency

Daniel d'Andrada (dandrader) wrote :

On 18/06/15 08:30, Gerry Boland wrote:
> Review: Needs Fixing
>
> please bump unity-api dependence to 7.98

Done

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Gerry Boland (gerboland) wrote :

Code looks good, need to functional test but building unity8 in a chroot is currently broken

review: Approve (code)
Gerry Boland (gerboland) wrote :

-- checking for module 'unity-shell-application=6'
-- package 'unity-shell-application=6' not found
Need to bump that dep in CMake please

review: Needs Fixing
362. By Daniel d'Andrada <dandrader@panzer> on 2015-06-29

Update unity-shell-application version dependency

Daniel d'Andrada (dandrader) wrote :

On 29/06/15 10:20, Gerry Boland wrote:
> Review: Needs Fixing
>
> -- checking for module 'unity-shell-application=6'
> -- package 'unity-shell-application=6' not found
> Need to bump that dep in CMake please

Done.

PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Gerry Boland (gerboland) wrote :

Ok, working well!

review: Approve
363. By Daniel d'Andrada on 2015-07-24

Merge trunk

[ Andreas Pokorny ]
* Release in step with Mir 0.14.0
[ CI Train Bot ]
* New rebuild forced.
[ Gerry Boland ]
* [qpa] refactor QMirServer to clean up its API, and fix strange
  thread design.
[ Michał Sawicz ]
* Depend on same-version qtmir-{desktop,android}
* No-change rebuild against Qt 5.4.2.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2015-05-21 18:48:59 +0000
3+++ CMakeLists.txt 2015-07-24 22:01:37 +0000
4@@ -80,7 +80,9 @@
5 pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt)
6 pkg_check_modules(QTDBUSTEST libqtdbustest-1 REQUIRED)
7 pkg_check_modules(QTDBUSMOCK libqtdbusmock-1 REQUIRED)
8-pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=6)
9+pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=7)
10+
11+include_directories(${APPLICATION_API_INCLUDE_DIRS})
12
13 add_definitions(-DMIR_REQUIRE_DEPRECATED_EVENT_OPT_IN=1)
14
15
16=== modified file 'debian/control'
17--- debian/control 2015-06-25 06:03:38 +0000
18+++ debian/control 2015-07-24 22:01:37 +0000
19@@ -28,7 +28,7 @@
20 libubuntu-app-launch2-dev,
21 libubuntu-application-api-dev (>= 2.1.0),
22 libudev-dev,
23- libunity-api-dev (>= 7.97),
24+ libunity-api-dev (>= 7.98),
25 liburl-dispatcher1-dev,
26 libxkbcommon-dev,
27 libxrender-dev,
28
29=== modified file 'src/modules/Unity/Application/CMakeLists.txt'
30--- src/modules/Unity/Application/CMakeLists.txt 2015-05-21 18:48:59 +0000
31+++ src/modules/Unity/Application/CMakeLists.txt 2015-07-24 22:01:37 +0000
32@@ -46,6 +46,7 @@
33 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
34 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
35 # Feed the automoc monster
36+ mirsurfaceiteminterface.h
37 session_interface.h
38 applicationcontroller.h
39 settings_interface.h
40
41=== modified file 'src/modules/Unity/Application/application.cpp'
42--- src/modules/Unity/Application/application.cpp 2015-04-10 14:54:44 +0000
43+++ src/modules/Unity/Application/application.cpp 2015-07-24 22:01:37 +0000
44@@ -1,5 +1,5 @@
45 /*
46- * Copyright (C) 2013-2014 Canonical, Ltd.
47+ * Copyright (C) 2013-2015 Canonical, Ltd.
48 *
49 * This program is free software: you can redistribute it and/or modify it under
50 * the terms of the GNU Lesser General Public License version 3, as published by
51@@ -37,25 +37,28 @@
52 namespace qtmir
53 {
54
55-Application::Application(const QSharedPointer<TaskController>& taskController,
56- const QSharedPointer<SharedWakelock>& sharedWakelock,
57+QStringList Application::lifecycleExceptions;
58+
59+Application::Application(const QSharedPointer<SharedWakelock>& sharedWakelock,
60 DesktopFileReader *desktopFileReader,
61- State state,
62 const QStringList &arguments,
63 ApplicationManager *parent)
64 : ApplicationInfoInterface(desktopFileReader->appId(), parent)
65- , m_taskController(taskController)
66 , m_sharedWakelock(sharedWakelock)
67 , m_desktopData(desktopFileReader)
68 , m_pid(0)
69 , m_stage((m_desktopData->stageHint() == "SideStage") ? Application::SideStage : Application::MainStage)
70- , m_state(state)
71+ , m_state(InternalState::Starting)
72 , m_focused(false)
73- , m_canBeResumed(true)
74 , m_arguments(arguments)
75 , m_session(nullptr)
76+ , m_requestedState(RequestedRunning)
77+ , m_processState(ProcessUnknown)
78 {
79- qCDebug(QTMIR_APPLICATIONS) << "Application::Application - appId=" << desktopFileReader->appId() << "state=" << state;
80+ qCDebug(QTMIR_APPLICATIONS) << "Application::Application - appId=" << desktopFileReader->appId();
81+
82+ // Because m_state is InternalState::Starting
83+ acquireWakelock();
84
85 // FIXME(greyback) need to save long appId internally until ubuntu-app-launch can hide it from us
86 m_longAppId = desktopFileReader->file().remove(QRegExp(".desktop$")).split('/').last();
87@@ -69,10 +72,33 @@
88 {
89 qCDebug(QTMIR_APPLICATIONS) << "Application::~Application";
90
91+ // (ricmm) -- To be on the safe side, better wipe the application QML compile cache if it crashes on startup
92+ if (m_processState == Application::ProcessUnknown
93+ || state() == Application::Starting
94+ || state() == Application::Running) {
95+ wipeQMLCache();
96+ }
97+
98 delete m_session;
99 delete m_desktopData;
100 }
101
102+
103+void Application::wipeQMLCache()
104+{
105+ QString path(QDir::homePath() + QStringLiteral("/.cache/QML/Apps/"));
106+ QDir dir(path);
107+ QStringList apps = dir.entryList();
108+ for (int i = 0; i < apps.size(); i++) {
109+ if (apps.at(i).contains(appId())) {
110+ qCDebug(QTMIR_APPLICATIONS) << "Application appId=" << apps.at(i) << " Wiping QML Cache";
111+ dir.cd(apps.at(i));
112+ dir.removeRecursively();
113+ break;
114+ }
115+ }
116+}
117+
118 bool Application::isValid() const
119 {
120 return m_desktopData->loaded();
121@@ -159,6 +185,30 @@
122 return color;
123 }
124
125+const char* Application::internalStateToStr(InternalState state)
126+{
127+ switch (state) {
128+ case InternalState::Starting:
129+ return "Starting";
130+ case InternalState::Running:
131+ return "Running";
132+ case InternalState::RunningInBackground:
133+ return "RunningInBackground";
134+ case InternalState::SuspendingWaitSession:
135+ return "SuspendingWaitSession";
136+ case InternalState::SuspendingWaitProcess:
137+ return "SuspendingWaitProcess";
138+ case InternalState::Suspended:
139+ return "Suspended";
140+ case InternalState::StoppedUnexpectedly:
141+ return "StoppedUnexpectedly";
142+ case InternalState::Stopped:
143+ return "Stopped";
144+ default:
145+ return "???";
146+ }
147+}
148+
149 bool Application::splashShowHeader() const
150 {
151 QString showHeader = m_desktopData->splashShowHeader();
152@@ -204,7 +254,104 @@
153
154 Application::State Application::state() const
155 {
156- return m_state;
157+ // The public state is a simplified version of the internal one as our consumers
158+ // don't have to know or care about all the nasty details.
159+ switch (m_state) {
160+ case InternalState::Starting:
161+ return Starting;
162+ case InternalState::Running:
163+ case InternalState::RunningInBackground:
164+ case InternalState::SuspendingWaitSession:
165+ case InternalState::SuspendingWaitProcess:
166+ return Running;
167+ case InternalState::Suspended:
168+ return Suspended;
169+ case InternalState::Stopped:
170+ default:
171+ return Stopped;
172+ }
173+}
174+
175+Application::RequestedState Application::requestedState() const
176+{
177+ return m_requestedState;
178+}
179+
180+void Application::setRequestedState(RequestedState value)
181+{
182+ if (m_requestedState == value) {
183+ // nothing to do
184+ return;
185+ }
186+
187+ qCDebug(QTMIR_APPLICATIONS) << "Application::setRequestedState - appId=" << appId()
188+ << "requestedState=" << applicationStateToStr(value);
189+ m_requestedState = value;
190+ Q_EMIT requestedStateChanged(m_requestedState);
191+
192+ applyRequestedState();
193+}
194+
195+void Application::applyRequestedState()
196+{
197+ if (m_requestedState == RequestedRunning) {
198+ applyRequestedRunning();
199+ } else {
200+ applyRequestedSuspended();
201+ }
202+}
203+
204+void Application::applyRequestedRunning()
205+{
206+ switch (m_state) {
207+ case InternalState::Starting:
208+ // should leave the app alone until it reaches Running state
209+ break;
210+ case InternalState::Running:
211+ // already where it's wanted to be
212+ break;
213+ case InternalState::RunningInBackground:
214+ case InternalState::SuspendingWaitSession:
215+ case InternalState::Suspended:
216+ resume();
217+ break;
218+ case InternalState::SuspendingWaitProcess:
219+ // should leave the app alone until it reaches Suspended state
220+ break;
221+ case InternalState::StoppedUnexpectedly:
222+ respawn();
223+ break;
224+ case InternalState::Stopped:
225+ // dead end.
226+ break;
227+ }
228+}
229+
230+void Application::applyRequestedSuspended()
231+{
232+ switch (m_state) {
233+ case InternalState::Starting:
234+ // should leave the app alone until it reaches Running state
235+ break;
236+ case InternalState::Running:
237+ if (m_processState == ProcessRunning) {
238+ suspend();
239+ } else {
240+ // we can't suspend it since we have no information on the app process
241+ Q_ASSERT(m_processState == ProcessUnknown);
242+ }
243+ break;
244+ case InternalState::RunningInBackground:
245+ case InternalState::SuspendingWaitSession:
246+ case InternalState::SuspendingWaitProcess:
247+ case InternalState::Suspended:
248+ // it's already going where we it's wanted
249+ break;
250+ case InternalState::StoppedUnexpectedly:
251+ case InternalState::Stopped:
252+ // the app doesn't have a process in the first place, so there's nothing to suspend
253+ break;
254+ }
255 }
256
257 bool Application::focused() const
258@@ -219,12 +366,7 @@
259
260 bool Application::canBeResumed() const
261 {
262- return m_canBeResumed;
263-}
264-
265-void Application::setCanBeResumed(const bool resume)
266-{
267- m_canBeResumed = resume;
268+ return m_processState != ProcessUnknown;
269 }
270
271 pid_t Application::pid() const
272@@ -242,7 +384,7 @@
273 m_arguments = arguments;
274 }
275
276-void Application::setSession(Session *newSession)
277+void Application::setSession(SessionInterface *newSession)
278 {
279 qCDebug(QTMIR_APPLICATIONS) << "Application::setSession - appId=" << appId() << "session=" << newSession;
280
281@@ -261,10 +403,25 @@
282 if (m_session) {
283 m_session->setParent(this);
284 m_session->setApplication(this);
285- m_session->setState(state());
286-
287- connect(m_session, &SessionInterface::suspended, this, &Application::onSessionSuspended);
288- connect(m_session, &SessionInterface::resumed, this, &Application::onSessionResumed);
289+
290+ switch (m_state) {
291+ case InternalState::Starting:
292+ case InternalState::Running:
293+ case InternalState::RunningInBackground:
294+ m_session->resume();
295+ break;
296+ case InternalState::SuspendingWaitSession:
297+ case InternalState::SuspendingWaitProcess:
298+ case InternalState::Suspended:
299+ m_session->suspend();
300+ break;
301+ case InternalState::Stopped:
302+ default:
303+ m_session->stop();
304+ break;
305+ }
306+
307+ connect(m_session, &SessionInterface::stateChanged, this, &Application::onSessionStateChanged);
308 connect(m_session, &SessionInterface::fullscreenChanged, this, &Application::fullscreenChanged);
309
310 if (oldFullscreen != fullscreen())
311@@ -288,37 +445,53 @@
312 }
313 }
314
315-void Application::setState(Application::State state)
316+void Application::setInternalState(Application::InternalState state)
317 {
318- qCDebug(QTMIR_APPLICATIONS) << "Application::setState - appId=" << appId() << "state=" << applicationStateToStr(state);
319- if (m_state != state) {
320- if (session()) {
321- session()->setState((Session::State)state);
322- } else {
323- // If we have have no session, we may have to respawn it.
324- switch (state)
325- {
326- case Session::State::Running:
327- if (m_state == Session::State::Stopped) {
328- respawn();
329- state = Session::State::Starting;
330- }
331- break;
332- default:
333- break;
334- }
335- }
336- m_state = state;
337- Q_EMIT stateChanged(state);
338- }
339+ if (m_state == state) {
340+ return;
341+ }
342+
343+ qCDebug(QTMIR_APPLICATIONS) << "Application::setInternalState - appId=" << appId()
344+ << "state=" << internalStateToStr(state);
345+
346+ auto oldPublicState = this->state();
347+ m_state = state;
348+
349+ switch (m_state) {
350+ case InternalState::Starting:
351+ case InternalState::Running:
352+ acquireWakelock();
353+ break;
354+ case InternalState::RunningInBackground:
355+ releaseWakelock();
356+ break;
357+ case InternalState::Suspended:
358+ releaseWakelock();
359+ break;
360+ case InternalState::StoppedUnexpectedly:
361+ releaseWakelock();
362+ break;
363+ case InternalState::Stopped:
364+ Q_EMIT stopped();
365+ releaseWakelock();
366+ break;
367+ case InternalState::SuspendingWaitSession:
368+ case InternalState::SuspendingWaitProcess:
369+ // transitory states. leave as it is
370+ default:
371+ break;
372+ };
373+
374+ if (this->state() != oldPublicState) {
375+ Q_EMIT stateChanged(this->state());
376+ }
377+
378+ applyRequestedState();
379 }
380
381 void Application::setFocused(bool focused)
382 {
383 qCDebug(QTMIR_APPLICATIONS) << "Application::setFocused - appId=" << appId() << "focused=" << focused;
384- if (focused) {
385- holdWakelock(true);
386- }
387
388 if (m_focused != focused) {
389 m_focused = focused;
390@@ -326,25 +499,84 @@
391 }
392 }
393
394-void Application::onSessionSuspended()
395-{
396- qCDebug(QTMIR_APPLICATIONS) << "Application::onSessionSuspended - appId=" << appId();
397- m_taskController->suspend(longAppId());
398- holdWakelock(false);
399-}
400-
401-void Application::onSessionResumed()
402-{
403- qCDebug(QTMIR_APPLICATIONS) << "Application::onSessionResumed - appId=" << appId();
404- holdWakelock(true);
405- m_taskController->resume(longAppId());
406+void Application::setProcessState(ProcessState newProcessState)
407+{
408+ if (m_processState == newProcessState) {
409+ return;
410+ }
411+
412+ m_processState = newProcessState;
413+
414+ switch (m_processState) {
415+ case ProcessUnknown:
416+ // it would be a coding error
417+ Q_ASSERT(false);
418+ break;
419+ case ProcessRunning:
420+ if (m_state == InternalState::StoppedUnexpectedly) {
421+ setInternalState(InternalState::Starting);
422+ }
423+ break;
424+ case ProcessSuspended:
425+ Q_ASSERT(m_state == InternalState::SuspendingWaitProcess);
426+ setInternalState(InternalState::Suspended);
427+ break;
428+ case ProcessStopped:
429+ // we assume the session always stop before the process
430+ Q_ASSERT(!m_session || m_session->state() == Session::Stopped);
431+ if (m_state == InternalState::Starting) {
432+ setInternalState(InternalState::Stopped);
433+ } else {
434+ Q_ASSERT(m_state == InternalState::Stopped
435+ || m_state == InternalState::StoppedUnexpectedly);
436+ }
437+ break;
438+ }
439+
440+ applyRequestedState();
441+}
442+
443+void Application::suspend()
444+{
445+ Q_ASSERT(m_state == InternalState::Running);
446+ Q_ASSERT(m_session != nullptr);
447+
448+ if (!lifecycleExceptions.filter(appId().section('_',0,0)).empty()) {
449+ // Present in exceptions list.
450+ // There's no need to keep the wakelock as the process is never suspended
451+ // and thus has no cleanup to perform when (for example) the display is
452+ // blanked.
453+ setInternalState(InternalState::RunningInBackground);
454+ } else {
455+ setInternalState(InternalState::SuspendingWaitSession);
456+ m_session->suspend();
457+ }
458+}
459+
460+void Application::resume()
461+{
462+ if (m_state == InternalState::Suspended) {
463+ setInternalState(InternalState::Running);
464+ Q_EMIT resumeProcessRequested();
465+ if (m_processState == ProcessSuspended) {
466+ setProcessState(ProcessRunning); // should we wait for a resumed() signal?
467+ }
468+ m_session->resume();
469+ } else if (m_state == InternalState::SuspendingWaitSession) {
470+ setInternalState(InternalState::Running);
471+ m_session->resume();
472+ } else if (m_state == InternalState::RunningInBackground) {
473+ setInternalState(InternalState::Running);
474+ }
475 }
476
477 void Application::respawn()
478 {
479 qCDebug(QTMIR_APPLICATIONS) << "Application::respawn - appId=" << appId();
480- holdWakelock(true);
481- m_taskController->start(appId(), m_arguments);
482+
483+ setInternalState(InternalState::Starting);
484+
485+ Q_EMIT startProcessRequested();
486 }
487
488 QString Application::longAppId() const
489@@ -362,20 +594,60 @@
490 return m_rotatesWindowContents;
491 }
492
493-Session* Application::session() const
494+SessionInterface* Application::session() const
495 {
496 return m_session;
497 }
498
499-void Application::holdWakelock(bool enable) const
500-{
501- if (appId() == "unity8-dash")
502- return;
503-
504- if (enable) {
505- m_sharedWakelock->acquire(this);
506- } else {
507- m_sharedWakelock->release(this);
508+void Application::acquireWakelock() const
509+{
510+ if (appId() == "unity8-dash")
511+ return;
512+
513+ m_sharedWakelock->acquire(this);
514+}
515+
516+void Application::releaseWakelock() const
517+{
518+ if (appId() == "unity8-dash")
519+ return;
520+
521+ m_sharedWakelock->release(this);
522+}
523+
524+void Application::onSessionStateChanged(Session::State sessionState)
525+{
526+ switch (sessionState) {
527+ case Session::Starting:
528+ break;
529+ case Session::Running:
530+ if (m_state == InternalState::Starting) {
531+ setInternalState(InternalState::Running);
532+ }
533+ break;
534+ case Session::Suspending:
535+ break;
536+ case Session::Suspended:
537+ Q_ASSERT(m_state == InternalState::SuspendingWaitSession);
538+ setInternalState(InternalState::SuspendingWaitProcess);
539+ Q_EMIT suspendProcessRequested();
540+ break;
541+ case Session::Stopped:
542+ if (!canBeResumed()
543+ || m_state == InternalState::Starting
544+ || m_state == InternalState::Running) {
545+ /* 1. application is not managed by upstart
546+ * 2. application is managed by upstart, but has stopped before it managed
547+ * to create a surface, we can assume it crashed on startup, and thus
548+ * cannot be resumed
549+ * 3. application is managed by upstart and is in foreground (i.e. has
550+ * Running state), if Mir reports the application disconnects, it
551+ * either crashed or stopped itself.
552+ */
553+ setInternalState(InternalState::Stopped);
554+ } else {
555+ setInternalState(InternalState::StoppedUnexpectedly);
556+ }
557 }
558 }
559
560
561=== modified file 'src/modules/Unity/Application/application.h'
562--- src/modules/Unity/Application/application.h 2015-01-19 11:48:32 +0000
563+++ src/modules/Unity/Application/application.h 2015-07-24 22:01:37 +0000
564@@ -1,5 +1,5 @@
565 /*
566- * Copyright (C) 2013-2014 Canonical, Ltd.
567+ * Copyright (C) 2013-2015 Canonical, Ltd.
568 *
569 * This program is free software: you can redistribute it and/or modify it under
570 * the terms of the GNU Lesser General Public License version 3, as published by
571@@ -28,6 +28,8 @@
572 // Unity API
573 #include <unity/shell/application/ApplicationInfoInterface.h>
574
575+#include "session_interface.h"
576+
577 namespace mir {
578 namespace scene {
579 class Session;
580@@ -39,7 +41,6 @@
581
582 class ApplicationManager;
583 class DesktopFileReader;
584-class TaskController;
585 class Session;
586 class SharedWakelock;
587
588@@ -51,15 +52,32 @@
589 Q_PROPERTY(QString exec READ exec CONSTANT)
590 Q_PROPERTY(bool fullscreen READ fullscreen NOTIFY fullscreenChanged)
591 Q_PROPERTY(Stage stage READ stage WRITE setStage NOTIFY stageChanged)
592- Q_PROPERTY(Session* session READ session NOTIFY sessionChanged DESIGNABLE false)
593+ Q_PROPERTY(SessionInterface* session READ session NOTIFY sessionChanged DESIGNABLE false)
594
595 public:
596 Q_DECLARE_FLAGS(Stages, Stage)
597
598- Application(const QSharedPointer<TaskController>& taskController,
599- const QSharedPointer<SharedWakelock>& sharedWakelock,
600+ enum ProcessState {
601+ ProcessUnknown,
602+ ProcessRunning,
603+ ProcessSuspended,
604+ ProcessStopped
605+ };
606+
607+ enum class InternalState {
608+ Starting,
609+ Running,
610+ RunningInBackground,
611+ SuspendingWaitSession,
612+ SuspendingWaitProcess,
613+ Suspended,
614+ StoppedUnexpectedly,
615+ Stopped // It closed itself, crashed or it stopped and we can't respawn it
616+ // In any case, this is a dead end.
617+ };
618+
619+ Application(const QSharedPointer<SharedWakelock>& sharedWakelock,
620 DesktopFileReader *desktopFileReader,
621- State state,
622 const QStringList &arguments,
623 ApplicationManager *parent);
624 virtual ~Application();
625@@ -71,6 +89,8 @@
626 QUrl icon() const override;
627 Stage stage() const override;
628 State state() const override;
629+ RequestedState requestedState() const override;
630+ void setRequestedState(RequestedState) override;
631 bool focused() const override;
632 QString splashTitle() const override;
633 QUrl splashImage() const override;
634@@ -82,12 +102,16 @@
635 bool rotatesWindowContents() const override;
636
637 void setStage(Stage stage);
638- void setState(State state);
639-
640- Session* session() const;
641+
642+
643+ void setProcessState(ProcessState value);
644+
645+ QStringList arguments() const { return m_arguments; }
646+
647+ SessionInterface* session() const;
648+ void setSession(SessionInterface *session);
649
650 bool canBeResumed() const;
651- void setCanBeResumed(const bool);
652
653 bool isValid() const;
654 QString desktopFile() const;
655@@ -98,40 +122,58 @@
656
657 pid_t pid() const;
658
659+ // for tests
660+ InternalState internalState() const { return m_state; }
661+
662+ static QStringList lifecycleExceptions;
663+
664 Q_SIGNALS:
665 void fullscreenChanged(bool fullscreen);
666 void stageChanged(Stage stage);
667- void sessionChanged(Session *session);
668+ void sessionChanged(SessionInterface *session);
669+
670+ void startProcessRequested();
671+ void suspendProcessRequested();
672+ void resumeProcessRequested();
673+ void stopped();
674
675 private Q_SLOTS:
676- void onSessionSuspended();
677- void onSessionResumed();
678+ void onSessionStateChanged(SessionInterface::State sessionState);
679
680 void respawn();
681
682 private:
683+
684 QString longAppId() const;
685- void holdWakelock(bool enable) const;
686+ void acquireWakelock() const;
687+ void releaseWakelock() const;
688 void setPid(pid_t pid);
689 void setArguments(const QStringList arguments);
690 void setFocused(bool focus);
691- void setSession(Session *session);
692+ void setInternalState(InternalState state);
693+ void wipeQMLCache();
694+ void suspend();
695+ void resume();
696 QColor colorFromString(const QString &colorString, const char *colorName) const;
697+ static const char* internalStateToStr(InternalState state);
698+ void applyRequestedState();
699+ void applyRequestedRunning();
700+ void applyRequestedSuspended();
701
702- QSharedPointer<TaskController> m_taskController;
703 QSharedPointer<SharedWakelock> m_sharedWakelock;
704 DesktopFileReader* m_desktopData;
705 QString m_longAppId;
706 qint64 m_pid;
707 Stage m_stage;
708 Stages m_supportedStages;
709- State m_state;
710+ InternalState m_state;
711 bool m_focused;
712- bool m_canBeResumed;
713 QStringList m_arguments;
714 Qt::ScreenOrientations m_supportedOrientations;
715 bool m_rotatesWindowContents;
716- Session *m_session;
717+ SessionInterface *m_session;
718+ RequestedState m_requestedState;
719+ ProcessState m_processState;
720
721 friend class ApplicationManager;
722 friend class SessionManager;
723
724=== modified file 'src/modules/Unity/Application/application_manager.cpp'
725--- src/modules/Unity/Application/application_manager.cpp 2015-05-21 18:38:27 +0000
726+++ src/modules/Unity/Application/application_manager.cpp 2015-07-24 22:01:37 +0000
727@@ -1,5 +1,5 @@
728 /*
729- * Copyright (C) 2013,2014 Canonical, Ltd.
730+ * Copyright (C) 2013-2015 Canonical, Ltd.
731 *
732 * This program is free software: you can redistribute it and/or modify it under
733 * the terms of the GNU Lesser General Public License version 3, as published by
734@@ -96,11 +96,13 @@
735 manager, &ApplicationManager::onProcessStarting);
736 QObject::connect(controller, &TaskController::processStopped,
737 manager, &ApplicationManager::onProcessStopped);
738+ QObject::connect(controller, &TaskController::processSuspended,
739+ manager, &ApplicationManager::onProcessSuspended);
740 QObject::connect(controller, &TaskController::processFailed,
741 manager, &ApplicationManager::onProcessFailed);
742- QObject::connect(controller, &TaskController::requestFocus,
743+ QObject::connect(controller, &TaskController::focusRequested,
744 manager, &ApplicationManager::onFocusRequested);
745- QObject::connect(controller, &TaskController::requestResume,
746+ QObject::connect(controller, &TaskController::resumeRequested,
747 manager, &ApplicationManager::onResumeRequested);
748 }
749
750@@ -180,16 +182,12 @@
751 : ApplicationManagerInterface(parent)
752 , m_mirServer(mirServer)
753 , m_focusedApplication(nullptr)
754- , m_mainStageApplication(nullptr)
755- , m_sideStageApplication(nullptr)
756 , m_dbusWindowStack(new DBusWindowStack(this))
757 , m_taskController(taskController)
758 , m_desktopFileReaderFactory(desktopFileReaderFactory)
759 , m_procInfo(procInfo)
760 , m_sharedWakelock(sharedWakelock)
761 , m_settings(settings)
762- , m_suspended(false)
763- , m_forceDashActive(false)
764 {
765 qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::ApplicationManager (this=%p)" << this;
766 setObjectName("qtmir::ApplicationManager");
767@@ -198,7 +196,7 @@
768 m_roleNames.insert(RoleFullscreen, "fullscreen");
769
770 if (settings.data()) {
771- m_lifecycleExceptions = m_settings->get("lifecycleExemptAppids").toStringList();
772+ Application::lifecycleExceptions = m_settings->get("lifecycleExemptAppids").toStringList();
773 connect(m_settings.data(), &Settings::changed, this, &ApplicationManager::onSettingsChanged);
774 }
775 }
776@@ -289,98 +287,6 @@
777 }
778 }
779
780-bool ApplicationManager::suspended() const
781-{
782- return m_suspended;
783-}
784-
785-void ApplicationManager::setSuspended(bool suspended)
786-{
787- if (suspended == m_suspended) {
788- return;
789- }
790- m_suspended = suspended;
791- Q_EMIT suspendedChanged();
792-
793- if (m_suspended) {
794- suspendApplication(m_mainStageApplication);
795- suspendApplication(m_sideStageApplication);
796- if (m_focusedApplication) {
797- m_focusedApplication->setFocused(false);
798- m_dbusWindowStack->FocusedWindowChanged(0, QString(), 0);
799- }
800- } else {
801- resumeApplication(m_mainStageApplication);
802- resumeApplication(m_sideStageApplication);
803- if (m_focusedApplication) {
804- m_focusedApplication->setFocused(true);
805- m_dbusWindowStack->FocusedWindowChanged(0, m_focusedApplication->appId(), m_focusedApplication->stage());
806- }
807- }
808-}
809-
810-bool ApplicationManager::forceDashActive() const
811-{
812- return m_forceDashActive;
813-}
814-
815-void ApplicationManager::setForceDashActive(bool forceDashActive)
816-{
817- if (m_forceDashActive == forceDashActive) {
818- return;
819- }
820-
821- m_forceDashActive = forceDashActive;
822- Q_EMIT forceDashActiveChanged();
823-
824- Application *dashApp = findApplication("unity8-dash");
825- if (!dashApp) {
826- qCWarning(QTMIR_APPLICATIONS) << "Dash doesn't seem to be running... Ignoring.";
827- return;
828- }
829-
830- if (m_forceDashActive && dashApp->state() != Application::Running) {
831- resumeApplication(dashApp);
832- } else if (!m_forceDashActive && dashApp->state() == Application::Running
833- && m_mainStageApplication != dashApp
834- && m_sideStageApplication != dashApp) {
835- suspendApplication(dashApp);
836- }
837-}
838-
839-bool ApplicationManager::suspendApplication(Application *application)
840-{
841- if (application == nullptr)
842- return false;
843- qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::suspendApplication - appId=" << application->appId();
844-
845- // Present in exceptions list, explicitly release wakelock and return. There's no need to keep the wakelock
846- // as the process is never suspended and thus has no cleanup to perform when (for example) the display is blanked
847- if (!m_lifecycleExceptions.filter(application->appId().section('_',0,0)).empty()) {
848- m_sharedWakelock->release(application);
849- return false;
850- }
851-
852- if (m_forceDashActive && application->appId() == "unity8-dash") {
853- return false;
854- }
855-
856- if (application->state() == Application::Running)
857- application->setState(Application::Suspended);
858-
859- return true;
860-}
861-
862-void ApplicationManager::resumeApplication(Application *application)
863-{
864- if (application == nullptr)
865- return;
866- qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::resumeApplication - appId=" << application->appId();
867-
868- if (application->state() == Application::Suspended || application->state() == Application::Stopped)
869- application->setState(Application::Running);
870-}
871-
872 bool ApplicationManager::focusApplication(const QString &inputAppId)
873 {
874 const QString appId = toShortAppIdIfPossible(inputAppId);
875@@ -392,27 +298,8 @@
876 return false;
877 }
878
879- resumeApplication(application);
880-
881- // set state of previously focused app to suspended
882 if (m_focusedApplication) {
883 m_focusedApplication->setFocused(false);
884- Application *lastApplication = applicationForStage(application->stage());
885- if (lastApplication != application) {
886- suspendApplication(lastApplication);
887- }
888- }
889-
890- if (application->stage() == Application::MainStage) {
891- m_mainStageApplication = application;
892- } else {
893- m_sideStageApplication = application;
894- }
895-
896- if (!m_suspended) {
897- resumeApplication(application); // in case unfocusCurrentApplication() was last called
898- } else {
899- suspendApplication(application); // Make sure we also have this one suspended if everything is suspended
900 }
901
902 m_focusedApplication = application;
903@@ -422,9 +309,6 @@
904 Q_EMIT focusedApplicationIdChanged();
905 m_dbusWindowStack->FocusedWindowChanged(0, application->appId(), application->stage());
906
907- // FIXME(dandrader): lying here. The operation is async. So we will only know whether
908- // the focusing was successful once the server replies. Maybe the API in unity-api should
909- // reflect that?
910 return true;
911 }
912
913@@ -432,9 +316,6 @@
914 {
915 qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::unfocusCurrentApplication";
916
917- suspendApplication(m_sideStageApplication);
918- suspendApplication(m_mainStageApplication);
919-
920 m_focusedApplication = nullptr;
921 Q_EMIT focusedApplicationIdChanged();
922 }
923@@ -484,10 +365,8 @@
924 application->setArguments(arguments);
925 } else {
926 application = new Application(
927- m_taskController,
928 m_sharedWakelock,
929 m_desktopFileReaderFactory->createInstance(appId, m_taskController->findDesktopFileForAppId(appId)),
930- Application::Starting,
931 arguments,
932 this);
933
934@@ -514,10 +393,8 @@
935 Application *application = findApplication(appId);
936 if (!application) { // then shell did not start this application, so ubuntu-app-launch must have - add to list
937 application = new Application(
938- m_taskController,
939 m_sharedWakelock,
940 m_desktopFileReaderFactory->createInstance(appId, m_taskController->findDesktopFileForAppId(appId)),
941- Application::Starting,
942 QStringList(),
943 this);
944
945@@ -530,17 +407,17 @@
946 Q_EMIT focusRequested(appId);
947 }
948 else {
949- // url-dispatcher can relaunch apps which have been OOM-killed - AppMan must accept the newly spawned
950- // application and focus it immediately (as user expects app to still be running).
951 if (application->state() == Application::Stopped) {
952+ // url-dispatcher can relaunch apps which have been OOM-killed - AppMan must accept the newly spawned
953+ // application and focus it immediately (as user expects app to still be running).
954 qCDebug(QTMIR_APPLICATIONS) << "Stopped application appId=" << appId << "is being resumed externally";
955- application->setState(Application::Starting);
956 Q_EMIT focusRequested(appId);
957 } else {
958 qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessStarting application already found with appId"
959 << appId;
960 }
961 }
962+ application->setProcessState(Application::ProcessRunning);
963 }
964
965 /**
966@@ -559,14 +436,7 @@
967 return false;
968 }
969
970- if (application == m_focusedApplication) {
971- // unfocus, and let shell decide what next to focus
972- m_focusedApplication = nullptr;
973- Q_EMIT focusedApplicationIdChanged();
974- }
975-
976 remove(application);
977- m_dbusWindowStack->WindowDestroyed(0, appId);
978
979 bool result = m_taskController->stop(application->longAppId());
980
981@@ -583,10 +453,7 @@
982
983 void ApplicationManager::onProcessFailed(const QString &appId, const bool duringStartup)
984 {
985- /* Applications fail if they fail to launch, crash or are killed. If failed to start, must
986- * immediately remove from list of applications. If crash or kill, instead we set flag on the
987- * Application to indicate it can be resumed.
988- */
989+ // Applications fail if they fail to launch, crash or are killed.
990
991 qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessFailed - appId=" << appId << "duringStartup=" << duringStartup;
992
993@@ -598,20 +465,8 @@
994 }
995
996 Q_UNUSED(duringStartup); // FIXME(greyback) upstart reports app that fully started up & crashes as failing during startup??
997- if (application->state() == Application::Starting) {
998- if (application == m_focusedApplication) {
999- m_focusedApplication = nullptr;
1000- Q_EMIT focusedApplicationIdChanged();
1001- }
1002- remove(application);
1003- m_dbusWindowStack->WindowDestroyed(0, application->appId());
1004- delete application;
1005- } else {
1006- // We need to set flags on the Application to say the app can be resumed, and thus should not be removed
1007- // from the list by onProcessStopped.
1008- application->setCanBeResumed(true);
1009- application->setPid(0);
1010- }
1011+ application->setProcessState(Application::ProcessStopped);
1012+ application->setPid(0);
1013 }
1014
1015 void ApplicationManager::onProcessStopped(const QString &appId)
1016@@ -626,29 +481,15 @@
1017 return;
1018 }
1019
1020- // if shell did not stop the application, but ubuntu-app-launch says it died, we assume the process has been
1021- // killed, so it can be respawned later. Only exception is if that application is focused or running
1022- // as then it most likely crashed. Update this logic when ubuntu-app-launch gives some failure info.
1023- bool removeApplication = true;
1024-
1025- if (application == m_focusedApplication) {
1026- // Very bad case where focused application dies. Remove from list. Should give error message
1027- m_focusedApplication = nullptr;
1028- Q_EMIT focusedApplicationIdChanged();
1029- }
1030-
1031- // The following scenario is the only time that we do NOT remove the application from the app list:
1032- if ((application->state() == Application::Suspended || application->state() == Application::Stopped)
1033- && application->pid() == 0 // i.e. onProcessFailed was called, which resets the PID of this application
1034- && application->canBeResumed()) {
1035- removeApplication = false;
1036- }
1037-
1038- if (removeApplication) {
1039- qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onProcessStopped - removing appId=" << appId;
1040- remove(application);
1041- m_dbusWindowStack->WindowDestroyed(0, application->appId());
1042- delete application;
1043+ application->setProcessState(Application::ProcessStopped);
1044+ application->setPid(0);
1045+}
1046+
1047+void ApplicationManager::onProcessSuspended(const QString &appId)
1048+{
1049+ Application *application = findApplication(appId);
1050+ if (application) {
1051+ application->setProcessState(Application::ProcessSuspended);
1052 }
1053 }
1054
1055@@ -670,10 +511,10 @@
1056 return;
1057 }
1058
1059- // If app Stopped, trust that ubuntu-app-launch respawns it itself, and AppManager will
1060- // be notified of that through the onProcessStartReportReceived slot. Else resume.
1061+ // We interpret this as a focus request for a suspended app.
1062+ // Shell will have this app resumed if it complies with the focus request
1063 if (application->state() == Application::Suspended) {
1064- application->setState(Application::Running);
1065+ Q_EMIT focusRequested(appId);
1066 }
1067 }
1068
1069@@ -693,7 +534,7 @@
1070 void ApplicationManager::onSettingsChanged(const QString &key)
1071 {
1072 if (key == "lifecycleExemptAppids") {
1073- m_lifecycleExceptions = m_settings->get("lifecycleExemptAppids").toStringList();
1074+ Application::lifecycleExceptions = m_settings->get("lifecycleExemptAppids").toStringList();
1075 }
1076 }
1077
1078@@ -732,7 +573,6 @@
1079
1080 if (info->startsWith("maliit-server") || info->contains("qt5/libexec/QtWebProcess")) {
1081 authorized = true;
1082- m_hiddenPIDs << pid;
1083 return;
1084 }
1085
1086@@ -792,15 +632,12 @@
1087
1088 QStringList arguments(info->asStringList());
1089 application = new Application(
1090- m_taskController,
1091 m_sharedWakelock,
1092 desktopData,
1093- Application::Starting,
1094 arguments,
1095 this);
1096 application->setPid(pid);
1097 application->setStage(stage);
1098- application->setCanBeResumed(false);
1099 add(application);
1100 authorized = true;
1101 }
1102@@ -812,51 +649,10 @@
1103
1104 void ApplicationManager::onSessionStopping(std::shared_ptr<ms::Session> const& session)
1105 {
1106- qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onSessionStopping - sessionName=" << session->name().c_str();
1107-
1108- // in case application closed not by hand of shell, check again here:
1109 Application* application = findApplicationWithSession(session);
1110 if (application) {
1111- /* Can remove the application from the running apps list immediately in these curcumstances:
1112- * 1. application is not managed by upstart (this message from Mir is only notice the app has stopped, must do
1113- * it here)
1114- * 2. application is managed by upstart, but has stopped before it managed to create a surface, we can assume
1115- * it crashed on startup, and thus cannot be resumed - so remove it.
1116- * 3. application is managed by upstart and is in foreground (i.e. has Running state), if Mir reports the
1117- * application disconnects, it either crashed or stopped itself. Either case, remove it.
1118- */
1119- if (!application->canBeResumed()
1120- || application->state() == Application::Starting
1121- || application->state() == Application::Running) {
1122- m_dbusWindowStack->WindowDestroyed(0, application->appId());
1123- remove(application);
1124-
1125- // (ricmm) -- To be on the safe side, better wipe the application QML compile cache if it crashes on startup
1126- QString path(QDir::homePath() + QStringLiteral("/.cache/QML/Apps/"));
1127- QDir dir(path);
1128- QStringList apps = dir.entryList();
1129- for (int i = 0; i < apps.size(); i++) {
1130- if (apps.at(i).contains(application->appId())) {
1131- qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::onSessionStopping appId=" << apps.at(i) << " Wiping QML Cache";
1132- dir.cd(apps.at(i));
1133- dir.removeRecursively();
1134- break;
1135- }
1136- }
1137-
1138- delete application;
1139-
1140- if (application == m_focusedApplication) {
1141- m_focusedApplication = nullptr;
1142- Q_EMIT focusedApplicationIdChanged();
1143- }
1144- } else {
1145- // otherwise, we do not have enough information to make any changes to the model, so await events from
1146- // upstart to go further, but set the app state
1147- application->setState(Application::Stopped);
1148- }
1149+ m_dbusWindowStack->WindowDestroyed(0, application->appId());
1150 }
1151- m_hiddenPIDs.removeOne(session->process_id());
1152 }
1153
1154 void ApplicationManager::onSessionCreatedSurface(ms::Session const* session,
1155@@ -866,12 +662,8 @@
1156 Q_UNUSED(surface);
1157
1158 Application* application = findApplicationWithSession(session);
1159- if (application && application->state() == Application::Starting) {
1160+ if (application) {
1161 m_dbusWindowStack->WindowCreated(0, application->appId());
1162- application->setState(Application::Running);
1163- if ((application != m_mainStageApplication && application != m_sideStageApplication) || m_suspended) {
1164- suspendApplication(application);
1165- }
1166 }
1167 }
1168
1169@@ -900,16 +692,6 @@
1170 return nullptr;
1171 }
1172
1173-Application* ApplicationManager::applicationForStage(Application::Stage stage)
1174-{
1175- qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::focusedApplicationForStage" << stage;
1176-
1177- if (stage == Application::MainStage)
1178- return m_mainStageApplication;
1179- else
1180- return m_sideStageApplication;
1181-}
1182-
1183 void ApplicationManager::add(Application* application)
1184 {
1185 Q_ASSERT(application != nullptr);
1186@@ -920,6 +702,28 @@
1187 connect(application, &Application::stateChanged, this, [this](Application::State) { onAppDataChanged(RoleState); });
1188 connect(application, &Application::stageChanged, this, [this](Application::Stage) { onAppDataChanged(RoleStage); });
1189
1190+ QString appId = application->appId();
1191+ QString longAppId = application->longAppId();
1192+ QStringList arguments = application->arguments();
1193+
1194+ // The connection is queued as a workaround an issue in the PhoneStage animation that
1195+ // happens when you tap on a killed app in the spread to bring it to foreground, causing
1196+ // a Application::respawn() to take place.
1197+ // In any case, it seems like in general QML works better when don't do too many things
1198+ // in the same event loop iteration.
1199+ connect(application, &Application::startProcessRequested,
1200+ this, [=]() { m_taskController->start(appId, arguments); },
1201+ Qt::QueuedConnection);
1202+
1203+ connect(application, &Application::suspendProcessRequested, this, [=]() { m_taskController->suspend(longAppId); } );
1204+ connect(application, &Application::resumeProcessRequested, this, [=]() { m_taskController->resume(longAppId); } );
1205+
1206+ connect(application, &Application::stopped, this, [=]() {
1207+ remove(application);
1208+ application->deleteLater();
1209+ });
1210+
1211+
1212 beginInsertRows(QModelIndex(), m_applications.count(), m_applications.count());
1213 m_applications.append(application);
1214 endInsertRows();
1215@@ -935,11 +739,6 @@
1216 Q_ASSERT(application != nullptr);
1217 qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::remove - appId=" << application->appId();
1218
1219- if (application == m_sideStageApplication)
1220- m_sideStageApplication = nullptr;
1221- if (application == m_mainStageApplication)
1222- m_mainStageApplication = nullptr;
1223-
1224 application->disconnect(this);
1225
1226 int i = m_applications.indexOf(application);
1227@@ -953,6 +752,11 @@
1228 Q_EMIT emptyChanged();
1229 }
1230 }
1231+
1232+ if (application == m_focusedApplication) {
1233+ m_focusedApplication = nullptr;
1234+ Q_EMIT focusedApplicationIdChanged();
1235+ }
1236 }
1237
1238 void ApplicationManager::move(int from, int to) {
1239
1240=== modified file 'src/modules/Unity/Application/application_manager.h'
1241--- src/modules/Unity/Application/application_manager.h 2015-03-24 23:38:33 +0000
1242+++ src/modules/Unity/Application/application_manager.h 2015-07-24 22:01:37 +0000
1243@@ -1,5 +1,5 @@
1244 /*
1245- * Copyright (C) 2013 Canonical, Ltd.
1246+ * Copyright (C) 2013-2015 Canonical, Ltd.
1247 *
1248 * This program is free software: you can redistribute it and/or modify it under
1249 * the terms of the GNU Lesser General Public License version 3, as published by
1250@@ -93,10 +93,6 @@
1251
1252 // ApplicationManagerInterface
1253 QString focusedApplicationId() const override;
1254- bool suspended() const override;
1255- void setSuspended(bool suspended) override;
1256- bool forceDashActive() const override;
1257- void setForceDashActive(bool forceDashActive) override;
1258 Q_INVOKABLE qtmir::Application* get(int index) const override;
1259 Q_INVOKABLE qtmir::Application* findApplication(const QString &appId) const override;
1260 Q_INVOKABLE bool requestFocusApplication(const QString &appId) override;
1261@@ -128,6 +124,7 @@
1262
1263 void onProcessStarting(const QString& appId);
1264 void onProcessStopped(const QString& appId);
1265+ void onProcessSuspended(const QString& appId);
1266 void onProcessFailed(const QString& appId, const bool duringStartup);
1267 void onFocusRequested(const QString& appId);
1268 void onResumeRequested(const QString& appId);
1269@@ -146,9 +143,7 @@
1270 void remove(Application* application);
1271 Application* findApplicationWithSession(const std::shared_ptr<mir::scene::Session> &session);
1272 Application* findApplicationWithSession(const mir::scene::Session *session);
1273- Application* applicationForStage(Application::Stage stage);
1274 QModelIndex findIndex(Application* application);
1275- bool suspendApplication(Application *application);
1276 void resumeApplication(Application *application);
1277 QString toString() const;
1278
1279@@ -158,9 +153,6 @@
1280
1281 QList<Application*> m_applications;
1282 Application* m_focusedApplication;
1283- Application* m_mainStageApplication;
1284- Application* m_sideStageApplication;
1285- QStringList m_lifecycleExceptions;
1286 DBusWindowStack* m_dbusWindowStack;
1287 QSharedPointer<TaskController> m_taskController;
1288 QSharedPointer<DesktopFileReader::Factory> m_desktopFileReaderFactory;
1289@@ -168,9 +160,6 @@
1290 QSharedPointer<SharedWakelock> m_sharedWakelock;
1291 QSharedPointer<SettingsInterface> m_settings;
1292 static ApplicationManager* the_application_manager;
1293- QList<pid_t> m_hiddenPIDs;
1294- bool m_suspended;
1295- bool m_forceDashActive;
1296
1297 friend class Application;
1298 friend class DBusWindowStack;
1299
1300=== modified file 'src/modules/Unity/Application/applicationcontroller.h'
1301--- src/modules/Unity/Application/applicationcontroller.h 2014-09-18 22:03:02 +0000
1302+++ src/modules/Unity/Application/applicationcontroller.h 2015-07-24 22:01:37 +0000
1303@@ -57,8 +57,9 @@
1304 void applicationAboutToBeStarted(const QString &appId);
1305 void applicationStarted(const QString &appId);
1306 void applicationStopped(const QString &appId);
1307+ void applicationPaused(const QString &appId);
1308 void applicationFocusRequest(const QString &appId);
1309- void applicationResumeRequest(const QString &appId);
1310+ void applicationResumeRequested(const QString &appId);
1311
1312 void applicationError(const QString &appId, ApplicationController::Error error);
1313
1314
1315=== modified file 'src/modules/Unity/Application/applicationscreenshotprovider.cpp'
1316--- src/modules/Unity/Application/applicationscreenshotprovider.cpp 2014-09-18 09:38:41 +0000
1317+++ src/modules/Unity/Application/applicationscreenshotprovider.cpp 2015-07-24 22:01:37 +0000
1318@@ -55,7 +55,7 @@
1319
1320 // TODO: if app not ready, return an app-provided splash image. If app has been stopped with saved state
1321 // return the screenshot that was saved to disk.
1322- Session* session = app->session();
1323+ SessionInterface* session = app->session();
1324 if (!session || !session->session() || !session->session()->default_surface()) {
1325 qWarning() << "ApplicationScreenshotProvider - app session not found - asking for screenshot too early";
1326 return QImage();
1327
1328=== modified file 'src/modules/Unity/Application/mirsurfaceitem.cpp'
1329--- src/modules/Unity/Application/mirsurfaceitem.cpp 2015-07-16 06:53:53 +0000
1330+++ src/modules/Unity/Application/mirsurfaceitem.cpp 2015-07-24 22:01:37 +0000
1331@@ -190,7 +190,7 @@
1332 MirShell *shell,
1333 std::shared_ptr<SurfaceObserver> observer,
1334 QQuickItem *parent)
1335- : QQuickItem(parent)
1336+ : MirSurfaceItemInterface(parent)
1337 , m_surface(surface)
1338 , m_session(session)
1339 , m_shell(shell)
1340@@ -375,7 +375,7 @@
1341 {
1342 if (!m_firstFrameDrawn) {
1343 m_firstFrameDrawn = true;
1344- Q_EMIT firstFrameDrawn(this);
1345+ Q_EMIT firstFrameDrawn();
1346 }
1347
1348 scheduleTextureUpdate();
1349@@ -561,7 +561,7 @@
1350
1351 touchEvent.updateTouchPointStatesAndType();
1352
1353- auto ev = makeMirEvent(touchEvent.modifiers, touchEvent.touchPoints,
1354+ auto ev = makeMirEvent(touchEvent.modifiers, touchEvent.touchPoints,
1355 touchEvent.touchPointStates, touchEvent.timestamp);
1356 m_surface->consume(*ev);
1357
1358@@ -672,7 +672,7 @@
1359 }
1360 }
1361
1362-void MirSurfaceItem::setLive(const bool live)
1363+void MirSurfaceItem::setLive(bool live)
1364 {
1365 if (m_live != live) {
1366 m_live = live;
1367
1368=== modified file 'src/modules/Unity/Application/mirsurfaceitem.h'
1369--- src/modules/Unity/Application/mirsurfaceitem.h 2015-05-13 17:18:45 +0000
1370+++ src/modules/Unity/Application/mirsurfaceitem.h 2015-07-24 22:01:37 +0000
1371@@ -22,8 +22,6 @@
1372 // Qt
1373 #include <QMutex>
1374 #include <QPointer>
1375-#include <QSet>
1376-#include <QQuickItem>
1377 #include <QTimer>
1378 #include <QQmlListProperty>
1379
1380@@ -31,6 +29,7 @@
1381 #include <mir/scene/surface.h>
1382 #include <mir_toolkit/common.h>
1383
1384+#include "mirsurfaceiteminterface.h"
1385 #include "session_interface.h"
1386
1387 class SurfaceObserver;
1388@@ -41,24 +40,10 @@
1389 class MirSurfaceManager;
1390 class QSGMirSurfaceNode;
1391 class QMirSurfaceTextureProvider;
1392-class Application;
1393
1394-class MirSurfaceItem : public QQuickItem
1395+class MirSurfaceItem : public MirSurfaceItemInterface
1396 {
1397 Q_OBJECT
1398- Q_ENUMS(Type)
1399- Q_ENUMS(State)
1400- Q_ENUMS(OrientationAngle)
1401-
1402- Q_PROPERTY(Type type READ type NOTIFY typeChanged)
1403- Q_PROPERTY(State state READ state NOTIFY stateChanged)
1404- Q_PROPERTY(QString name READ name NOTIFY nameChanged)
1405- Q_PROPERTY(bool live READ live NOTIFY liveChanged)
1406-
1407- // How many degrees, clockwise, the UI in the surface has to rotate to match with the
1408- // shell UI orientation
1409- Q_PROPERTY(OrientationAngle orientationAngle READ orientationAngle WRITE setOrientationAngle
1410- NOTIFY orientationAngleChanged DESIGNABLE false)
1411
1412 public:
1413 explicit MirSurfaceItem(std::shared_ptr<mir::scene::Surface> surface,
1414@@ -66,57 +51,29 @@
1415 MirShell *shell,
1416 std::shared_ptr<SurfaceObserver> observer,
1417 QQuickItem *parent = 0);
1418- ~MirSurfaceItem();
1419-
1420- enum Type {
1421- Normal = mir_surface_type_normal,
1422- Utility = mir_surface_type_utility,
1423- Dialog = mir_surface_type_dialog,
1424- Overlay = mir_surface_type_overlay,
1425- Freestyle = mir_surface_type_freestyle,
1426- Popover = mir_surface_type_popover,
1427- InputMethod = mir_surface_type_inputmethod,
1428- };
1429-
1430- enum State {
1431- Unknown = mir_surface_state_unknown,
1432- Restored = mir_surface_state_restored,
1433- Minimized = mir_surface_state_minimized,
1434- Maximized = mir_surface_state_maximized,
1435- VertMaximized = mir_surface_state_vertmaximized,
1436- /* SemiMaximized = mir_surface_state_semimaximized, // see mircommon/mir_toolbox/common.h*/
1437- Fullscreen = mir_surface_state_fullscreen,
1438- };
1439-
1440- enum OrientationAngle {
1441- Angle0 = 0,
1442- Angle90 = 90,
1443- Angle180 = 180,
1444- Angle270 = 270
1445- };
1446+ virtual ~MirSurfaceItem();
1447
1448 //getters
1449- Type type() const;
1450- State state() const;
1451- QString name() const;
1452- bool live() const;
1453- SessionInterface *session() const;
1454+ Type type() const override;
1455+ State state() const override;
1456+ QString name() const override;
1457+ bool live() const override;
1458+ SessionInterface *session() const override;
1459+ OrientationAngle orientationAngle() const override;
1460
1461- Q_INVOKABLE void release();
1462+ Q_INVOKABLE void release() override;
1463
1464 // Item surface/texture management
1465 bool isTextureProvider() const { return true; }
1466 QSGTextureProvider *textureProvider() const;
1467
1468- void stopFrameDropper();
1469- void startFrameDropper();
1470-
1471- bool isFirstFrameDrawn() const { return m_firstFrameDrawn; }
1472-
1473- OrientationAngle orientationAngle() const;
1474- void setOrientationAngle(OrientationAngle angle);
1475-
1476- void setSession(SessionInterface *app);
1477+ void stopFrameDropper() override;
1478+ void startFrameDropper() override;
1479+
1480+ bool isFirstFrameDrawn() const override { return m_firstFrameDrawn; }
1481+
1482+ void setOrientationAngle(OrientationAngle angle) override;
1483+ void setSession(SessionInterface *app) override;
1484
1485 // to allow easy touch event injection from tests
1486 bool processTouchEvent(int eventType,
1487@@ -125,14 +82,6 @@
1488 const QList<QTouchEvent::TouchPoint> &touchPoints,
1489 Qt::TouchPointStates touchPointStates);
1490
1491-Q_SIGNALS:
1492- void typeChanged();
1493- void stateChanged();
1494- void nameChanged();
1495- void orientationAngleChanged(OrientationAngle angle);
1496- void liveChanged(bool live);
1497- void firstFrameDrawn(MirSurfaceItem *item);
1498-
1499 protected Q_SLOTS:
1500 void onSessionStateChanged(SessionInterface::State state);
1501
1502@@ -169,7 +118,7 @@
1503
1504 void setType(const Type&);
1505 void setState(const State&);
1506- void setLive(const bool);
1507+ void setLive(bool) override;
1508
1509 // called by MirSurfaceManager
1510 void setSurfaceValid(const bool);
1511@@ -226,13 +175,8 @@
1512 QList<QTouchEvent::TouchPoint> touchPoints;
1513 Qt::TouchPointStates touchPointStates;
1514 } *m_lastTouchEvent;
1515-
1516- friend class MirSurfaceManager;
1517 };
1518
1519 } // namespace qtmir
1520
1521-Q_DECLARE_METATYPE(qtmir::MirSurfaceItem*)
1522-Q_DECLARE_METATYPE(qtmir::MirSurfaceItem::OrientationAngle)
1523-
1524 #endif // MIRSURFACEITEM_H
1525
1526=== added file 'src/modules/Unity/Application/mirsurfaceiteminterface.h'
1527--- src/modules/Unity/Application/mirsurfaceiteminterface.h 1970-01-01 00:00:00 +0000
1528+++ src/modules/Unity/Application/mirsurfaceiteminterface.h 2015-07-24 22:01:37 +0000
1529@@ -0,0 +1,116 @@
1530+/*
1531+ * Copyright (C) 2015 Canonical, Ltd.
1532+ *
1533+ * This program is free software: you can redistribute it and/or modify it under
1534+ * the terms of the GNU Lesser General Public License version 3, as published by
1535+ * the Free Software Foundation.
1536+ *
1537+ * This program is distributed in the hope that it will be useful, but WITHOUT
1538+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
1539+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1540+ * Lesser General Public License for more details.
1541+ *
1542+ * You should have received a copy of the GNU Lesser General Public License
1543+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1544+ */
1545+
1546+#ifndef MIRSURFACEITEMINTERFACE_H
1547+#define MIRSURFACEITEMINTERFACE_H
1548+
1549+// Qt
1550+#include <QQuickItem>
1551+
1552+// mir
1553+#include <mir_toolkit/common.h>
1554+
1555+#include "session_interface.h"
1556+
1557+namespace qtmir {
1558+
1559+class MirSurfaceItemInterface : public QQuickItem
1560+{
1561+ Q_OBJECT
1562+ Q_ENUMS(Type)
1563+ Q_ENUMS(State)
1564+ Q_ENUMS(OrientationAngle)
1565+
1566+ Q_PROPERTY(Type type READ type NOTIFY typeChanged)
1567+ Q_PROPERTY(State state READ state NOTIFY stateChanged)
1568+ Q_PROPERTY(QString name READ name NOTIFY nameChanged)
1569+ Q_PROPERTY(bool live READ live NOTIFY liveChanged)
1570+
1571+ // How many degrees, clockwise, the UI in the surface has to rotate to match with the
1572+ // shell UI orientation
1573+ Q_PROPERTY(OrientationAngle orientationAngle READ orientationAngle WRITE setOrientationAngle
1574+ NOTIFY orientationAngleChanged DESIGNABLE false)
1575+
1576+public:
1577+ MirSurfaceItemInterface(QQuickItem *parent) : QQuickItem(parent) {}
1578+ virtual ~MirSurfaceItemInterface() {}
1579+
1580+ enum Type {
1581+ Normal = mir_surface_type_normal,
1582+ Utility = mir_surface_type_utility,
1583+ Dialog = mir_surface_type_dialog,
1584+ Overlay = mir_surface_type_overlay,
1585+ Freestyle = mir_surface_type_freestyle,
1586+ Popover = mir_surface_type_popover,
1587+ InputMethod = mir_surface_type_inputmethod,
1588+ };
1589+
1590+ enum State {
1591+ Unknown = mir_surface_state_unknown,
1592+ Restored = mir_surface_state_restored,
1593+ Minimized = mir_surface_state_minimized,
1594+ Maximized = mir_surface_state_maximized,
1595+ VertMaximized = mir_surface_state_vertmaximized,
1596+ /* SemiMaximized = mir_surface_state_semimaximized, // see mircommon/mir_toolbox/common.h*/
1597+ Fullscreen = mir_surface_state_fullscreen,
1598+ };
1599+
1600+ enum OrientationAngle {
1601+ Angle0 = 0,
1602+ Angle90 = 90,
1603+ Angle180 = 180,
1604+ Angle270 = 270
1605+ };
1606+
1607+ //getters
1608+ virtual Type type() const = 0;
1609+ virtual State state() const = 0;
1610+ virtual QString name() const = 0;
1611+ virtual bool live() const = 0;
1612+ virtual SessionInterface *session() const = 0;
1613+ virtual OrientationAngle orientationAngle() const = 0;
1614+
1615+ virtual Q_INVOKABLE void release() = 0;
1616+
1617+ virtual void stopFrameDropper() = 0;
1618+ virtual void startFrameDropper() = 0;
1619+
1620+ virtual bool isFirstFrameDrawn() const = 0;
1621+
1622+ virtual void setOrientationAngle(OrientationAngle angle) = 0;
1623+ virtual void setSession(SessionInterface *app) = 0;
1624+
1625+Q_SIGNALS:
1626+ void typeChanged();
1627+ void stateChanged();
1628+ void nameChanged();
1629+ void orientationAngleChanged(OrientationAngle angle);
1630+ void liveChanged(bool live);
1631+ void firstFrameDrawn();
1632+
1633+private:
1634+ virtual void setLive(bool) = 0;
1635+
1636+ friend class MirSurfaceManager;
1637+};
1638+
1639+} // namespace qtmir
1640+
1641+Q_DECLARE_METATYPE(qtmir::MirSurfaceItemInterface*)
1642+Q_DECLARE_METATYPE(qtmir::MirSurfaceItemInterface::OrientationAngle)
1643+
1644+#endif // MIRSURFACEITEMINTERFACE_H
1645+
1646
1647=== modified file 'src/modules/Unity/Application/mirsurfaceitemmodel.h'
1648--- src/modules/Unity/Application/mirsurfaceitemmodel.h 2014-08-29 11:15:51 +0000
1649+++ src/modules/Unity/Application/mirsurfaceitemmodel.h 2015-07-24 22:01:37 +0000
1650@@ -22,8 +22,8 @@
1651
1652 namespace qtmir {
1653
1654-class MirSurfaceItem;
1655-typedef ObjectListModel<MirSurfaceItem> MirSurfaceItemModel;
1656+class MirSurfaceItemInterface;
1657+typedef ObjectListModel<MirSurfaceItemInterface> MirSurfaceItemModel;
1658
1659 } // namespace qtmir
1660
1661
1662=== modified file 'src/modules/Unity/Application/mirsurfacemanager.cpp'
1663--- src/modules/Unity/Application/mirsurfacemanager.cpp 2015-03-11 10:10:49 +0000
1664+++ src/modules/Unity/Application/mirsurfacemanager.cpp 2015-07-24 22:01:37 +0000
1665@@ -112,11 +112,11 @@
1666 session->setSurface(qmlSurface);
1667
1668 // Only notify QML of surface creation once it has drawn its first frame.
1669- connect(qmlSurface, &MirSurfaceItem::firstFrameDrawn, this, [&](MirSurfaceItem *item) {
1670+ connect(qmlSurface, &MirSurfaceItemInterface::firstFrameDrawn, this, [=]() {
1671 tracepoint(qtmir, firstFrameDrawn);
1672- Q_EMIT surfaceCreated(item);
1673+ Q_EMIT surfaceCreated(qmlSurface);
1674
1675- insert(0, item);
1676+ insert(0, qmlSurface);
1677 });
1678
1679 // clean up after MirSurfaceItem is destroyed
1680@@ -139,7 +139,7 @@
1681 qCDebug(QTMIR_SURFACES) << "MirSurfaceManager::onSessionDestroyingSurface - session=" << session
1682 << "surface=" << surface.get() << "surface.name=" << surface->name().c_str();
1683
1684- MirSurfaceItem* item = nullptr;
1685+ MirSurfaceItemInterface* item = nullptr;
1686 {
1687 QMutexLocker lock(&m_mutex);
1688 auto it = m_mirSurfaceToItemHash.find(surface.get());
1689
1690=== modified file 'src/modules/Unity/Application/mirsurfacemanager.h'
1691--- src/modules/Unity/Application/mirsurfacemanager.h 2015-03-11 10:10:49 +0000
1692+++ src/modules/Unity/Application/mirsurfacemanager.h 2015-07-24 22:01:37 +0000
1693@@ -64,8 +64,8 @@
1694 static MirSurfaceManager* singleton();
1695
1696 Q_SIGNALS:
1697- void surfaceCreated(MirSurfaceItem* surface);
1698- void surfaceDestroyed(MirSurfaceItem* surface);
1699+ void surfaceCreated(MirSurfaceItemInterface* surface);
1700+ void surfaceDestroyed(MirSurfaceItemInterface* surface);
1701 // void surfaceResized(MirSurface*);
1702 // void fullscreenSurfaceChanged();
1703
1704@@ -74,7 +74,7 @@
1705 void onSessionDestroyingSurface(const mir::scene::Session *, const std::shared_ptr<mir::scene::Surface> &);
1706
1707 protected:
1708- QHash<const mir::scene::Surface *, MirSurfaceItem *> m_mirSurfaceToItemHash;
1709+ QHash<const mir::scene::Surface *, MirSurfaceItemInterface *> m_mirSurfaceToItemHash;
1710 QMutex m_mutex;
1711
1712 private:
1713
1714=== modified file 'src/modules/Unity/Application/plugin.cpp'
1715--- src/modules/Unity/Application/plugin.cpp 2015-02-05 10:28:31 +0000
1716+++ src/modules/Unity/Application/plugin.cpp 2015-07-24 22:01:37 +0000
1717@@ -73,7 +73,7 @@
1718
1719 qRegisterMetaType<qtmir::ApplicationManager*>("ApplicationManager*"); //need for queueing signals
1720 qRegisterMetaType<qtmir::Application*>("Application*");
1721- qRegisterMetaType<qtmir::MirSurfaceItem*>("MirSurfaceItem*");
1722+ qRegisterMetaType<qtmir::MirSurfaceItemInterface*>("MirSurfaceItemInterface*");
1723 qRegisterMetaType<qtmir::MirSurfaceItemModel*>("MirSurfaceItemModel*");
1724 qRegisterMetaType<qtmir::Session*>("Session*");
1725 qRegisterMetaType<qtmir::SessionInterface*>("SessionInterface*");
1726@@ -92,7 +92,7 @@
1727 uri, 0, 1, "SurfaceManager", surfaceManagerSingleton);
1728 qmlRegisterSingletonType<qtmir::SessionManager>(
1729 uri, 0, 1, "SessionManager", sessionManagerSingleton);
1730- qmlRegisterUncreatableType<qtmir::MirSurfaceItem>(
1731+ qmlRegisterUncreatableType<qtmir::MirSurfaceItemInterface>(
1732 uri, 0, 1, "MirSurfaceItem", "MirSurfaceItem can't be instantiated from QML");
1733 qmlRegisterUncreatableType<qtmir::Session>(
1734 uri, 0, 1, "Session", "Session can't be instantiated from QML");
1735
1736=== modified file 'src/modules/Unity/Application/session.cpp'
1737--- src/modules/Unity/Application/session.cpp 2015-01-15 15:19:26 +0000
1738+++ src/modules/Unity/Application/session.cpp 2015-07-24 22:01:37 +0000
1739@@ -1,5 +1,5 @@
1740 /*
1741- * Copyright (C) 2014 Canonical, Ltd.
1742+ * Copyright (C) 2014,2015 Canonical, Ltd.
1743 *
1744 * This program is free software; you can redistribute it and/or modify
1745 * it under the terms of the GNU General Public License as published by
1746@@ -60,14 +60,7 @@
1747 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
1748
1749 m_suspendTimer->setSingleShot(true);
1750- connect(m_suspendTimer, &QTimer::timeout, this, [this]() {
1751- if (m_surface) {
1752- m_surface->stopFrameDropper();
1753- } else {
1754- qDebug() << "Application::suspend - no surface to call stopFrameDropper() on!";
1755- }
1756- Q_EMIT suspended();
1757- });
1758+ connect(m_suspendTimer, &QTimer::timeout, this, &Session::doSuspend);
1759 }
1760
1761 Session::~Session()
1762@@ -89,6 +82,17 @@
1763 delete m_children; m_children = nullptr;
1764 }
1765
1766+void Session::doSuspend()
1767+{
1768+ Q_ASSERT(m_state == Session::Suspending);
1769+ if (m_surface) {
1770+ m_surface->stopFrameDropper();
1771+ } else {
1772+ qDebug() << "Application::suspend - no surface to call stopFrameDropper() on!";
1773+ }
1774+ setState(Suspended);
1775+}
1776+
1777 void Session::release()
1778 {
1779 qCDebug(QTMIR_SESSIONS) << "Session::release " << name();
1780@@ -120,7 +124,7 @@
1781 return m_application;
1782 }
1783
1784-MirSurfaceItem* Session::surface() const
1785+MirSurfaceItemInterface* Session::surface() const
1786 {
1787 // Only notify QML of surface creation once it has drawn its first frame.
1788 if (m_surface && m_surface->isFirstFrameDrawn()) {
1789@@ -140,6 +144,13 @@
1790 return m_state;
1791 }
1792
1793+void Session::setState(State state) {
1794+ if (state != m_state) {
1795+ m_state = state;
1796+ Q_EMIT stateChanged(m_state);
1797+ }
1798+}
1799+
1800 bool Session::fullscreen() const
1801 {
1802 return m_fullscreen;
1803@@ -159,7 +170,7 @@
1804 Q_EMIT applicationChanged(application);
1805 }
1806
1807-void Session::setSurface(MirSurfaceItem *newSurface)
1808+void Session::setSurface(MirSurfaceItemInterface *newSurface)
1809 {
1810 qCDebug(QTMIR_SESSIONS) << "Session::setSurface - session=" << name() << "surface=" << newSurface;
1811
1812@@ -173,34 +184,46 @@
1813 m_surface->setParent(nullptr);
1814 }
1815
1816- MirSurfaceItem *previousSurface = surface();
1817+ MirSurfaceItemInterface *previousSurface = surface();
1818 m_surface = newSurface;
1819
1820 if (newSurface) {
1821 m_surface->setParent(this);
1822 m_surface->setSession(this);
1823
1824+ connect(newSurface, &MirSurfaceItemInterface::stateChanged,
1825+ this, &Session::updateFullscreenProperty);
1826+
1827 // Only notify QML of surface creation once it has drawn its first frame.
1828- if (!surface()) {
1829- connect(newSurface, &MirSurfaceItem::firstFrameDrawn,
1830- this, [this] { Q_EMIT surfaceChanged(m_surface); });
1831+ if (m_surface->isFirstFrameDrawn()) {
1832+ setState(Running);
1833+ } else {
1834+ connect(newSurface, &MirSurfaceItemInterface::firstFrameDrawn,
1835+ this, &Session::onFirstSurfaceFrameDrawn);
1836 }
1837-
1838- connect(newSurface, &MirSurfaceItem::stateChanged,
1839- this, &Session::updateFullscreenProperty);
1840 }
1841
1842 if (previousSurface != surface()) {
1843+ qCDebug(QTMIR_SESSIONS).nospace() << "Session::surfaceChanged - session=" << this
1844+ << " surface=" << m_surface;
1845 Q_EMIT surfaceChanged(m_surface);
1846 }
1847
1848 updateFullscreenProperty();
1849 }
1850
1851+void Session::onFirstSurfaceFrameDrawn()
1852+{
1853+ qCDebug(QTMIR_SESSIONS).nospace() << "Session::surfaceChanged - session=" << this
1854+ << " surface=" << m_surface;
1855+ Q_EMIT surfaceChanged(m_surface);
1856+ setState(Running);
1857+}
1858+
1859 void Session::updateFullscreenProperty()
1860 {
1861 if (m_surface) {
1862- setFullscreen(m_surface->state() == MirSurfaceItem::Fullscreen);
1863+ setFullscreen(m_surface->state() == MirSurfaceItemInterface::Fullscreen);
1864 } else {
1865 // Keep the current value of the fullscreen property until we get a new
1866 // surface
1867@@ -216,59 +239,71 @@
1868 }
1869 }
1870
1871-void Session::setState(State state)
1872-{
1873- qCDebug(QTMIR_SESSIONS) << "Session::setState - session=" << this << "state=" << applicationStateToStr(state);
1874- if (m_state != state) {
1875- switch (state)
1876- {
1877- case Session::State::Suspended:
1878- if (m_state == Session::State::Running) {
1879- session()->set_lifecycle_state(mir_lifecycle_state_will_suspend);
1880- m_suspendTimer->start(1500);
1881- }
1882- break;
1883- case Session::State::Running:
1884- if (m_suspendTimer->isActive())
1885- m_suspendTimer->stop();
1886-
1887- if (m_state == Session::State::Suspended) {
1888- if (m_surface)
1889- m_surface->startFrameDropper();
1890- Q_EMIT resumed();
1891- session()->set_lifecycle_state(mir_lifecycle_state_resumed);
1892- }
1893- break;
1894- case Session::State::Stopped:
1895- stopPromptSessions();
1896- if (m_suspendTimer->isActive())
1897- m_suspendTimer->stop();
1898- if (m_surface)
1899- m_surface->stopFrameDropper();
1900- break;
1901- default:
1902- break;
1903- }
1904-
1905- m_state = state;
1906- Q_EMIT stateChanged(state);
1907-
1908- foreachPromptSession([this, state](const std::shared_ptr<ms::PromptSession>& promptSession) {
1909- switch (state) {
1910- case Session::State::Suspended:
1911- m_promptSessionManager->suspend_prompt_session(promptSession);
1912- break;
1913- case Session::State::Running:
1914- m_promptSessionManager->resume_prompt_session(promptSession);
1915- break;
1916- default:
1917- break;
1918- }
1919- });
1920-
1921- foreachChildSession([state](SessionInterface* session) {
1922- session->setState(state);
1923- });
1924+void Session::suspend()
1925+{
1926+ qCDebug(QTMIR_SESSIONS) << "Session::suspend - session=" << this << "state=" << applicationStateToStr(m_state);
1927+ if (m_state == Running) {
1928+ session()->set_lifecycle_state(mir_lifecycle_state_will_suspend);
1929+ m_suspendTimer->start(1500);
1930+
1931+ foreachPromptSession([this](const std::shared_ptr<ms::PromptSession>& promptSession) {
1932+ m_promptSessionManager->suspend_prompt_session(promptSession);
1933+ });
1934+
1935+ foreachChildSession([](SessionInterface* session) {
1936+ session->suspend();
1937+ });
1938+
1939+ setState(Suspending);
1940+ }
1941+}
1942+
1943+void Session::resume()
1944+{
1945+ qCDebug(QTMIR_SESSIONS) << "Session::resume - session=" << this << "state=" << applicationStateToStr(m_state);
1946+
1947+ if (m_state == Suspending || m_state == Suspended) {
1948+ doResume();
1949+ }
1950+}
1951+
1952+void Session::doResume()
1953+{
1954+ if (m_state == Suspending) {
1955+ Q_ASSERT(m_suspendTimer->isActive());
1956+ m_suspendTimer->stop();
1957+ } else if (m_state == Suspended) {
1958+ Q_ASSERT(m_surface);
1959+ m_surface->startFrameDropper();
1960+ }
1961+
1962+ session()->set_lifecycle_state(mir_lifecycle_state_resumed);
1963+
1964+ foreachPromptSession([this](const std::shared_ptr<ms::PromptSession>& promptSession) {
1965+ m_promptSessionManager->resume_prompt_session(promptSession);
1966+ });
1967+
1968+ foreachChildSession([](SessionInterface* session) {
1969+ session->resume();
1970+ });
1971+
1972+ setState(Running);
1973+}
1974+
1975+void Session::stop()
1976+{
1977+ if (m_state != Stopped) {
1978+ stopPromptSessions();
1979+ if (m_suspendTimer->isActive())
1980+ m_suspendTimer->stop();
1981+ if (m_surface)
1982+ m_surface->stopFrameDropper();
1983+
1984+ foreachChildSession([](SessionInterface* session) {
1985+ session->stop();
1986+ });
1987+
1988+ setState(Stopped);
1989 }
1990 }
1991
1992@@ -277,6 +312,9 @@
1993 if (m_live != live) {
1994 m_live = live;
1995 Q_EMIT liveChanged(m_live);
1996+ if (!live) {
1997+ setState(Stopped);
1998+ }
1999 }
2000 }
2001
2002@@ -302,7 +340,19 @@
2003 static_cast<Session*>(session)->setParentSession(this);
2004 m_children->insert(index, session);
2005
2006- session->setState(state());
2007+ switch (m_state) {
2008+ case Starting:
2009+ case Running:
2010+ session->resume();
2011+ break;
2012+ case Suspending:
2013+ case Suspended:
2014+ session->suspend();
2015+ break;
2016+ case Stopped:
2017+ session->stop();
2018+ break;
2019+ }
2020 }
2021
2022 void Session::removeChildSession(SessionInterface* session)
2023
2024=== modified file 'src/modules/Unity/Application/session.h'
2025--- src/modules/Unity/Application/session.h 2014-09-11 16:18:40 +0000
2026+++ src/modules/Unity/Application/session.h 2015-07-24 22:01:37 +0000
2027@@ -1,5 +1,5 @@
2028 /*
2029- * Copyright (C) 2014 Canonical, Ltd.
2030+ * Copyright (C) 2014-2015 Canonical, Ltd.
2031 *
2032 * This program is free software; you can redistribute it and/or modify
2033 * it under the terms of the GNU General Public License as published by
2034@@ -52,15 +52,18 @@
2035 //getters
2036 QString name() const override;
2037 unity::shell::application::ApplicationInfoInterface* application() const override;
2038- MirSurfaceItem* surface() const override;
2039+ MirSurfaceItemInterface* surface() const override;
2040 SessionInterface* parentSession() const override;
2041 State state() const override;
2042 bool fullscreen() const override;
2043 bool live() const override;
2044
2045 void setApplication(unity::shell::application::ApplicationInfoInterface* item) override;
2046- void setSurface(MirSurfaceItem* surface) override;
2047- void setState(State state) override;
2048+ void setSurface(MirSurfaceItemInterface* surface) override;
2049+
2050+ void suspend() override;
2051+ void resume() override;
2052+ void stop() override;
2053
2054 void addChildSession(SessionInterface* session) override;
2055 void insertChildSession(uint index, SessionInterface* session) override;
2056@@ -74,23 +77,29 @@
2057
2058 SessionModel* childSessions() const override;
2059
2060-protected:
2061 void setFullscreen(bool fullscreen) override;
2062 void setLive(const bool) override;
2063 void appendPromptSession(const std::shared_ptr<mir::scene::PromptSession>& session) override;
2064 void removePromptSession(const std::shared_ptr<mir::scene::PromptSession>& session) override;
2065
2066+public Q_SLOTS:
2067+ // it's public to ease testing
2068+ void doSuspend();
2069+
2070 private Q_SLOTS:
2071 void updateFullscreenProperty();
2072+ void onFirstSurfaceFrameDrawn();
2073
2074 private:
2075 void setParentSession(Session* session);
2076+ void setState(State state);
2077+ void doResume();
2078
2079 void stopPromptSessions();
2080
2081 std::shared_ptr<mir::scene::Session> m_session;
2082 Application* m_application;
2083- MirSurfaceItem* m_surface;
2084+ MirSurfaceItemInterface* m_surface;
2085 SessionInterface* m_parentSession;
2086 SessionModel* m_children;
2087 bool m_fullscreen;
2088@@ -103,6 +112,4 @@
2089
2090 } // namespace qtmir
2091
2092-Q_DECLARE_METATYPE(qtmir::Session*)
2093-
2094 #endif // SESSION_H
2095
2096=== modified file 'src/modules/Unity/Application/session_interface.h'
2097--- src/modules/Unity/Application/session_interface.h 2014-09-22 18:06:58 +0000
2098+++ src/modules/Unity/Application/session_interface.h 2015-07-24 22:01:37 +0000
2099@@ -1,5 +1,5 @@
2100 /*
2101- * Copyright (C) 2014 Canonical, Ltd.
2102+ * Copyright (C) 2014,2015 Canonical, Ltd.
2103 *
2104 * This program is free software; you can redistribute it and/or modify
2105 * it under the terms of the GNU General Public License as published by
2106@@ -35,11 +35,11 @@
2107
2108 namespace qtmir {
2109
2110-class MirSurfaceItem;
2111+class MirSurfaceItemInterface;
2112
2113 class SessionInterface : public QObject {
2114 Q_OBJECT
2115- Q_PROPERTY(MirSurfaceItem* surface READ surface NOTIFY surfaceChanged)
2116+ Q_PROPERTY(MirSurfaceItemInterface* surface READ surface NOTIFY surfaceChanged)
2117 Q_PROPERTY(unity::shell::application::ApplicationInfoInterface* application READ application NOTIFY applicationChanged DESIGNABLE false)
2118 Q_PROPERTY(SessionInterface* parentSession READ parentSession NOTIFY parentSessionChanged DESIGNABLE false)
2119 Q_PROPERTY(SessionModel* childSessions READ childSessions DESIGNABLE false CONSTANT)
2120@@ -49,58 +49,66 @@
2121 SessionInterface(QObject *parent = 0) : QObject(parent) {}
2122 virtual ~SessionInterface() {}
2123
2124- // Session State
2125- typedef unity::shell::application::ApplicationInfoInterface::State State;
2126+ enum State {
2127+ Starting,
2128+ Running,
2129+ Suspending,
2130+ Suspended,
2131+ Stopped
2132+ };
2133
2134 Q_INVOKABLE virtual void release() = 0;
2135
2136 //getters
2137 virtual QString name() const = 0;
2138 virtual unity::shell::application::ApplicationInfoInterface* application() const = 0;
2139- virtual MirSurfaceItem* surface() const = 0;
2140+ virtual MirSurfaceItemInterface* surface() const = 0;
2141 virtual SessionInterface* parentSession() const = 0;
2142+ virtual SessionModel* childSessions() const = 0;
2143 virtual State state() const = 0;
2144 virtual bool fullscreen() const = 0;
2145 virtual bool live() const = 0;
2146
2147+ virtual std::shared_ptr<mir::scene::Session> session() const = 0;
2148+
2149+ // For MirSurfaceItem and MirSurfaceManager use
2150+
2151+ virtual void setSurface(MirSurfaceItemInterface* surface) = 0;
2152+
2153+ // For Application use
2154+
2155 virtual void setApplication(unity::shell::application::ApplicationInfoInterface* item) = 0;
2156- virtual void setSurface(MirSurfaceItem* surface) = 0;
2157- virtual void setState(State state) = 0;
2158+ virtual void suspend() = 0;
2159+ virtual void resume() = 0;
2160+ virtual void stop() = 0;
2161+
2162+ // For SessionManager use
2163
2164 virtual void addChildSession(SessionInterface* session) = 0;
2165 virtual void insertChildSession(uint index, SessionInterface* session) = 0;
2166 virtual void removeChildSession(SessionInterface* session) = 0;
2167 virtual void foreachChildSession(std::function<void(SessionInterface* session)> f) const = 0;
2168
2169- virtual std::shared_ptr<mir::scene::Session> session() const = 0;
2170-
2171 virtual std::shared_ptr<mir::scene::PromptSession> activePromptSession() const = 0;
2172 virtual void foreachPromptSession(std::function<void(const std::shared_ptr<mir::scene::PromptSession>&)> f) const = 0;
2173
2174- virtual SessionModel* childSessions() const = 0;
2175+ virtual void setFullscreen(bool fullscreen) = 0;
2176+ virtual void setLive(const bool) = 0;
2177+ virtual void appendPromptSession(const std::shared_ptr<mir::scene::PromptSession>& session) = 0;
2178+ virtual void removePromptSession(const std::shared_ptr<mir::scene::PromptSession>& session) = 0;
2179
2180 Q_SIGNALS:
2181- void surfaceChanged(MirSurfaceItem*);
2182+ void surfaceChanged(MirSurfaceItemInterface*);
2183 void parentSessionChanged(SessionInterface*);
2184 void applicationChanged(unity::shell::application::ApplicationInfoInterface* application);
2185 void aboutToBeDestroyed();
2186 void stateChanged(State state);
2187 void fullscreenChanged(bool fullscreen);
2188 void liveChanged(bool live);
2189-
2190- void suspended();
2191- void resumed();
2192-
2193-protected:
2194- virtual void setFullscreen(bool fullscreen) = 0;
2195- virtual void setLive(const bool) = 0;
2196- virtual void appendPromptSession(const std::shared_ptr<mir::scene::PromptSession>& session) = 0;
2197- virtual void removePromptSession(const std::shared_ptr<mir::scene::PromptSession>& session) = 0;
2198-
2199- friend class SessionManager;
2200 };
2201
2202 } // namespace qtmir
2203
2204+Q_DECLARE_METATYPE(qtmir::SessionInterface*)
2205
2206 #endif // SESSION_INTERFACE_H
2207
2208=== modified file 'src/modules/Unity/Application/taskcontroller.cpp'
2209--- src/modules/Unity/Application/taskcontroller.cpp 2014-10-20 18:48:53 +0000
2210+++ src/modules/Unity/Application/taskcontroller.cpp 2015-07-24 22:01:37 +0000
2211@@ -50,14 +50,19 @@
2212 &TaskController::processStopped);
2213
2214 connect(m_appController.data(),
2215+ &ApplicationController::applicationPaused,
2216+ this,
2217+ &TaskController::processSuspended);
2218+
2219+ connect(m_appController.data(),
2220 &ApplicationController::applicationFocusRequest,
2221 this,
2222- &TaskController::onApplicationFocusRequest);
2223+ &TaskController::focusRequested);
2224
2225 connect(m_appController.data(),
2226- &ApplicationController::applicationResumeRequest,
2227+ &ApplicationController::applicationResumeRequested,
2228 this,
2229- &TaskController::onApplicationResumeRequest);
2230+ &TaskController::resumeRequested);
2231
2232 connect(m_appController.data(),
2233 &ApplicationController::applicationError,
2234@@ -108,16 +113,6 @@
2235 return m_appController->resumeApplicationWithAppId(appId);
2236 }
2237
2238-void TaskController::onApplicationFocusRequest(const QString& id)
2239-{
2240- Q_EMIT requestFocus(id);
2241-}
2242-
2243-void TaskController::onApplicationResumeRequest(const QString& id)
2244-{
2245- Q_EMIT requestResume(id);
2246-}
2247-
2248 void TaskController::onApplicationError(const QString& id, ApplicationController::Error error)
2249 {
2250 Q_EMIT processFailed(id, (error == ApplicationController::Error::APPLICATION_FAILED_TO_START) );
2251
2252=== modified file 'src/modules/Unity/Application/taskcontroller.h'
2253--- src/modules/Unity/Application/taskcontroller.h 2014-10-07 03:21:30 +0000
2254+++ src/modules/Unity/Application/taskcontroller.h 2015-07-24 22:01:37 +0000
2255@@ -1,5 +1,5 @@
2256 /*
2257- * Copyright (C) 2013-2014 Canonical, Ltd.
2258+ * Copyright (C) 2013-2015 Canonical, Ltd.
2259 *
2260 * This program is free software: you can redistribute it and/or modify it under
2261 * the terms of the GNU Lesser General Public License version 3, as published by
2262@@ -48,14 +48,12 @@
2263 Q_SIGNALS:
2264 void processStarting(const QString &appId);
2265 void processStopped(const QString &appId);
2266+ void processSuspended(const QString &appId);
2267 void processFailed(const QString &appId, const bool duringStartup);
2268- void requestFocus(const QString &appId);
2269- void requestResume(const QString &appId);
2270+ void focusRequested(const QString &appId);
2271+ void resumeRequested(const QString &appId);
2272
2273 private Q_SLOTS:
2274- void onApplicationFocusRequest(const QString &id);
2275- void onApplicationResumeRequest(const QString &id);
2276-
2277 void onApplicationError(const QString &id, ApplicationController::Error error);
2278
2279 private:
2280
2281=== modified file 'src/modules/Unity/Application/upstart/applicationcontroller.cpp'
2282--- src/modules/Unity/Application/upstart/applicationcontroller.cpp 2014-11-13 15:47:30 +0000
2283+++ src/modules/Unity/Application/upstart/applicationcontroller.cpp 2015-07-24 22:01:37 +0000
2284@@ -1,5 +1,5 @@
2285 /*
2286- * Copyright (C) 2014 Canonical, Ltd.
2287+ * Copyright (C) 2014,2015 Canonical, Ltd.
2288 *
2289 * This program is free software: you can redistribute it and/or modify it under
2290 * the terms of the GNU Lesser General Public License version 3, as published by
2291@@ -40,6 +40,7 @@
2292 UbuntuAppLaunchAppObserver stopCallback = nullptr;
2293 UbuntuAppLaunchAppObserver focusCallback = nullptr;
2294 UbuntuAppLaunchAppObserver resumeCallback = nullptr;
2295+ UbuntuAppLaunchAppPausedResumedObserver pausedCallback = nullptr;
2296 UbuntuAppLaunchAppFailedObserver failureCallback = nullptr;
2297 };
2298
2299@@ -125,7 +126,12 @@
2300
2301 impl->resumeCallback = [](const gchar * appId, gpointer userData) {
2302 auto thiz = static_cast<ApplicationController*>(userData);
2303- Q_EMIT(thiz->applicationResumeRequest(toShortAppIdIfPossible(appId)));
2304+ Q_EMIT(thiz->applicationResumeRequested(toShortAppIdIfPossible(appId)));
2305+ };
2306+
2307+ impl->pausedCallback = [](const gchar * appId, GPid *, gpointer userData) {
2308+ auto thiz = static_cast<ApplicationController*>(userData);
2309+ Q_EMIT(thiz->applicationPaused(toShortAppIdIfPossible(appId)));
2310 };
2311
2312 impl->failureCallback = [](const gchar * appId, UbuntuAppLaunchAppFailed failureType, gpointer userData) {
2313@@ -145,6 +151,7 @@
2314 ubuntu_app_launch_observer_add_app_stop(impl->stopCallback, this);
2315 ubuntu_app_launch_observer_add_app_focus(impl->focusCallback, this);
2316 ubuntu_app_launch_observer_add_app_resume(impl->resumeCallback, this);
2317+ ubuntu_app_launch_observer_add_app_paused(impl->pausedCallback, this);
2318 ubuntu_app_launch_observer_add_app_failed(impl->failureCallback, this);
2319 }
2320
2321@@ -155,6 +162,7 @@
2322 ubuntu_app_launch_observer_delete_app_stop(impl->stopCallback, this);
2323 ubuntu_app_launch_observer_delete_app_focus(impl->focusCallback, this);
2324 ubuntu_app_launch_observer_delete_app_resume(impl->resumeCallback, this);
2325+ ubuntu_app_launch_observer_delete_app_paused(impl->pausedCallback, this);
2326 ubuntu_app_launch_observer_delete_app_failed(impl->failureCallback, this);
2327 }
2328
2329
2330=== modified file 'src/platforms/mirserver/CMakeLists.txt'
2331--- src/platforms/mirserver/CMakeLists.txt 2015-05-19 15:10:48 +0000
2332+++ src/platforms/mirserver/CMakeLists.txt 2015-07-24 22:01:37 +0000
2333@@ -31,6 +31,8 @@
2334 ${QT5PLATFORM_SUPPORT_INCLUDE_DIRS}
2335 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
2336 ${QT5_PLATFORMSUPPORT_INCLUDE_DIRS}
2337+
2338+ ${APPLICATION_API_INCLUDE_DIRS}
2339 )
2340
2341 # We have to remove -pedantic for tracepoints.c
2342
2343=== modified file 'tests/modules/Application/CMakeLists.txt'
2344--- tests/modules/Application/CMakeLists.txt 2015-05-21 18:48:59 +0000
2345+++ tests/modules/Application/CMakeLists.txt 2015-07-24 22:01:37 +0000
2346@@ -1,9 +1,11 @@
2347 set(
2348 APPLICATION_TEST_SOURCES
2349 application_test.cpp
2350+ ${CMAKE_SOURCE_DIR}/tests/modules/common/qtmir_test.cpp
2351 )
2352
2353 include_directories(
2354+ ${APPLICATION_API_INCLUDE_DIRS}
2355 ${CMAKE_SOURCE_DIR}/src/platforms/mirserver
2356 ${CMAKE_SOURCE_DIR}/src/modules
2357 ${CMAKE_SOURCE_DIR}/tests/modules/common
2358@@ -18,6 +20,8 @@
2359 unityapplicationplugin
2360 qpa-mirserver
2361
2362+ Qt5::Test
2363+
2364 ${GTEST_BOTH_LIBRARIES}
2365 ${GMOCK_LIBRARIES}
2366 )
2367
2368=== modified file 'tests/modules/Application/application_test.cpp'
2369--- tests/modules/Application/application_test.cpp 2015-03-04 22:13:06 +0000
2370+++ tests/modules/Application/application_test.cpp 2015-07-24 22:01:37 +0000
2371@@ -20,6 +20,10 @@
2372
2373 #include "qtmir_test.h"
2374
2375+#include <mock_session.h>
2376+
2377+#include <QScopedPointer>
2378+#include <QSignalSpy>
2379
2380 using namespace qtmir;
2381
2382@@ -30,93 +34,163 @@
2383 {}
2384 };
2385
2386-TEST_F(ApplicationTests, checkFocusAcquiresWakeLock)
2387-{
2388- using namespace ::testing;
2389-
2390- EXPECT_CALL(sharedWakelock, acquire(_)).Times(1);
2391-
2392- startApplication(123, "app");
2393- applicationManager.focusApplication("app");
2394-}
2395-
2396-TEST_F(ApplicationTests, checkSuspendReleasesWakeLock)
2397-{
2398- using namespace ::testing;
2399-
2400- auto app = startApplication(123, "app");
2401- auto session = app->session();
2402-
2403- applicationManager.focusApplication("app");
2404-
2405- Q_EMIT session->suspended();
2406+TEST_F(ApplicationTests, acquiresWakelockWhenRunningAndReleasesWhenSuspended)
2407+{
2408+ using namespace ::testing;
2409+ QString appId("foo-app");
2410+
2411+ auto desktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
2412+ ON_CALL(*desktopFileReader, loaded()).WillByDefault(Return(true));
2413+
2414+ QScopedPointer<Application> application(new Application(
2415+ QSharedPointer<MockSharedWakelock>(&sharedWakelock, [](MockSharedWakelock *){}),
2416+ desktopFileReader, QStringList(), nullptr));
2417+
2418+ application->setProcessState(Application::ProcessRunning);
2419+
2420+ NiceMock<MockSession> *session = new NiceMock<MockSession>;
2421+
2422+ EXPECT_CALL(*session, setApplication(_));
2423+ EXPECT_CALL(*session, fullscreen()).WillRepeatedly(Return(false));
2424+
2425+ application->setSession(session);
2426+
2427+ ASSERT_EQ(Application::InternalState::Starting, application->internalState());
2428+
2429+ session->setState(SessionInterface::Running);
2430+
2431+ EXPECT_TRUE(sharedWakelock.enabled());
2432+
2433+ ASSERT_EQ(Application::InternalState::Running, application->internalState());
2434+
2435+ application->setRequestedState(Application::RequestedSuspended);
2436+
2437+ ASSERT_EQ(SessionInterface::Suspending, session->state());
2438+ ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState());
2439+
2440+ session->setState(SessionInterface::Suspended);
2441+
2442+ ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState());
2443+
2444+ application->setProcessState(Application::ProcessSuspended);
2445+
2446+ ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
2447+
2448 EXPECT_FALSE(sharedWakelock.enabled());
2449 }
2450
2451 TEST_F(ApplicationTests, checkResumeAcquiresWakeLock)
2452 {
2453 using namespace ::testing;
2454-
2455- EXPECT_CALL(sharedWakelock, acquire(_)).Times(1);
2456-
2457- auto app = startApplication(123, "app");
2458- auto session = app->session();
2459-
2460- Q_EMIT session->resumed();
2461+ QString appId("foo-app");
2462+
2463+ auto desktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
2464+ ON_CALL(*desktopFileReader, loaded()).WillByDefault(Return(true));
2465+
2466+ QScopedPointer<Application> application(new Application(
2467+ QSharedPointer<MockSharedWakelock>(&sharedWakelock, [](MockSharedWakelock *){}),
2468+ desktopFileReader, QStringList(), nullptr));
2469+ NiceMock<MockSession> *session = new NiceMock<MockSession>;
2470+
2471+ // Get it running and then suspend it
2472+ application->setProcessState(Application::ProcessRunning);
2473+ application->setSession(session);
2474+ session->setState(SessionInterface::Running);
2475+ application->setRequestedState(Application::RequestedSuspended);
2476+ session->setState(SessionInterface::Suspended);
2477+ application->setProcessState(Application::ProcessSuspended);
2478+ ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
2479+
2480+ EXPECT_FALSE(sharedWakelock.enabled());
2481+
2482+ application->setRequestedState(Application::RequestedRunning);
2483+
2484+ ASSERT_EQ(Application::InternalState::Running, application->internalState());
2485+
2486+ EXPECT_TRUE(sharedWakelock.enabled());
2487 }
2488
2489 TEST_F(ApplicationTests, checkRespawnAcquiresWakeLock)
2490 {
2491 using namespace ::testing;
2492-
2493- EXPECT_CALL(sharedWakelock, acquire(_)).Times(1);
2494- const QString appId = "app";
2495-
2496- auto app = startApplication(123, "app");
2497-
2498- // as respawn fires startApplicationWithAppIdAndArgs again, keep gmock quiet about another call
2499- EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
2500- .Times(1)
2501- .WillRepeatedly(Return(true));
2502-
2503- // respawn by setting app state as Stopped, delete the Session associated, then set to Running state
2504- app->setState(Session::State::Stopped);
2505- delete app->session();
2506- app->setState(Session::State::Running);
2507-}
2508-
2509-TEST_F(ApplicationTests, checkDashFocusDoesNotAcquireWakeLock)
2510-{
2511- using namespace ::testing;
2512-
2513- EXPECT_CALL(sharedWakelock, acquire(_)).Times(0);
2514-
2515- startApplication(123, "unity8-dash");
2516- applicationManager.focusApplication("unity8-dash");
2517-}
2518-
2519-TEST_F(ApplicationTests, checkDashSuspendDoesNotImpactWakeLock)
2520-{
2521- using namespace ::testing;
2522-
2523- auto app = startApplication(123, "unity8-dash");
2524- auto session = app->session();
2525-
2526- applicationManager.focusApplication("unity8-dash");
2527-
2528- Q_EMIT session->suspended();
2529+ QString appId("foo-app");
2530+
2531+ auto desktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
2532+ ON_CALL(*desktopFileReader, loaded()).WillByDefault(Return(true));
2533+
2534+ QScopedPointer<Application> application(new Application(
2535+ QSharedPointer<MockSharedWakelock>(&sharedWakelock, [](MockSharedWakelock *){}),
2536+ desktopFileReader, QStringList(), nullptr));
2537+ NiceMock<MockSession> *session = new NiceMock<MockSession>;
2538+
2539+ // Get it running, suspend it, and finally stop it
2540+ application->setProcessState(Application::ProcessRunning);
2541+ application->setSession(session);
2542+ session->setState(SessionInterface::Running);
2543+ application->setRequestedState(Application::RequestedSuspended);
2544+ session->setState(SessionInterface::Suspended);
2545+ application->setProcessState(Application::ProcessSuspended);
2546+ ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
2547+ session->setState(SessionInterface::Stopped);
2548+ application->setProcessState(Application::ProcessStopped);
2549+ ASSERT_EQ(Application::InternalState::StoppedUnexpectedly, application->internalState());
2550+
2551 EXPECT_FALSE(sharedWakelock.enabled());
2552+
2553+ QSignalSpy spyStartProcess(application.data(), SIGNAL(startProcessRequested()));
2554+ application->setRequestedState(Application::RequestedRunning);
2555+ ASSERT_EQ(1, spyStartProcess.count());
2556+ application->setProcessState(Application::ProcessRunning);
2557+
2558+ ASSERT_EQ(Application::InternalState::Starting, application->internalState());
2559+
2560+ EXPECT_TRUE(sharedWakelock.enabled());
2561 }
2562
2563-TEST_F(ApplicationTests, checkDashResumeDoesNotAcquireWakeLock)
2564+TEST_F(ApplicationTests, checkDashDoesNotImpactWakeLock)
2565 {
2566 using namespace ::testing;
2567+ QString appId("unity8-dash");
2568
2569 EXPECT_CALL(sharedWakelock, acquire(_)).Times(0);
2570-
2571- auto app = startApplication(123, "unity8-dash");
2572- auto session = app->session();
2573-
2574- Q_EMIT session->resumed();
2575+ EXPECT_CALL(sharedWakelock, release(_)).Times(0);
2576+
2577+ auto desktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
2578+ ON_CALL(*desktopFileReader, loaded()).WillByDefault(Return(true));
2579+
2580+ QScopedPointer<Application> application(new Application(
2581+ QSharedPointer<MockSharedWakelock>(&sharedWakelock, [](MockSharedWakelock *){}),
2582+ desktopFileReader, QStringList(), nullptr));
2583+
2584+ application->setProcessState(Application::ProcessRunning);
2585+
2586+ NiceMock<MockSession> *session = new NiceMock<MockSession>;
2587+
2588+ EXPECT_CALL(*session, setApplication(_));
2589+ EXPECT_CALL(*session, fullscreen()).WillRepeatedly(Return(false));
2590+
2591+ application->setSession(session);
2592+
2593+ ASSERT_EQ(Application::InternalState::Starting, application->internalState());
2594+
2595+ session->setState(SessionInterface::Running);
2596+
2597+ ASSERT_EQ(Application::InternalState::Running, application->internalState());
2598+
2599+ application->setRequestedState(Application::RequestedSuspended);
2600+
2601+ ASSERT_EQ(SessionInterface::Suspending, session->state());
2602+ ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState());
2603+
2604+ session->setState(SessionInterface::Suspended);
2605+
2606+ ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState());
2607+
2608+ application->setProcessState(Application::ProcessSuspended);
2609+
2610+ ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
2611+
2612+ application->setRequestedState(Application::RequestedRunning);
2613+
2614+ ASSERT_EQ(Application::InternalState::Running, application->internalState());
2615 }
2616-
2617
2618=== modified file 'tests/modules/ApplicationManager/CMakeLists.txt'
2619--- tests/modules/ApplicationManager/CMakeLists.txt 2015-05-21 18:48:59 +0000
2620+++ tests/modules/ApplicationManager/CMakeLists.txt 2015-07-24 22:01:37 +0000
2621@@ -2,9 +2,12 @@
2622 APPLICATION_MANAGER_TEST_SOURCES
2623 application_manager_test.cpp
2624 ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp
2625+ ${CMAKE_SOURCE_DIR}/tests/modules/common/qtmir_test.cpp
2626+ ../common/fake_mirsurfaceitem.h
2627 )
2628
2629 include_directories(
2630+ ${APPLICATION_API_INCLUDE_DIRS}
2631 ${CMAKE_SOURCE_DIR}/src/platforms/mirserver
2632 ${CMAKE_SOURCE_DIR}/src/modules
2633 ${CMAKE_SOURCE_DIR}/tests/modules/common
2634@@ -15,7 +18,7 @@
2635
2636 target_link_libraries(
2637 applicationmanager_test
2638-
2639+
2640 qpa-mirserver
2641 unityapplicationplugin
2642
2643
2644=== modified file 'tests/modules/ApplicationManager/application_manager_test.cpp'
2645--- tests/modules/ApplicationManager/application_manager_test.cpp 2015-05-13 17:18:45 +0000
2646+++ tests/modules/ApplicationManager/application_manager_test.cpp 2015-07-24 22:01:37 +0000
2647@@ -1,5 +1,5 @@
2648 /*
2649- * Copyright (C) 2013 Canonical, Ltd.
2650+ * Copyright (C) 2013-2015 Canonical, Ltd.
2651 *
2652 * This program is free software: you can redistribute it and/or modify it under
2653 * the terms of the GNU Lesser General Public License version 3, as published by
2654@@ -23,8 +23,9 @@
2655
2656 #include <Unity/Application/applicationscreenshotprovider.h>
2657
2658- #include "mock_surface.h"
2659- #include "qtmir_test.h"
2660+ #include <fake_mirsurfaceitem.h>
2661+ #include <mock_surface.h>
2662+ #include <qtmir_test.h>
2663
2664 using namespace qtmir;
2665 using mir::scene::MockSession;
2666@@ -45,100 +46,41 @@
2667 applicationManager.onSessionStopping(session);
2668 sessionManager.onSessionStopping(session);
2669 }
2670+ inline void onSessionCreatedSurface(const mir::scene::Session *mirSession,
2671+ MirSurfaceItemInterface *qmlSurface) {
2672+
2673+ SessionInterface* qmlSession = sessionManager.findSession(mirSession);
2674+ if (qmlSession) {
2675+ qmlSession->setSurface(qmlSurface);
2676+ }
2677+
2678+ // I assume that applicationManager ignores the mirSurface parameter, so sending
2679+ // a null shared pointer must suffice
2680+ std::shared_ptr<mir::scene::Surface> mirSurface(nullptr);
2681+ applicationManager.onSessionCreatedSurface(mirSession, mirSurface);
2682+ }
2683+
2684+ inline void suspend(Application *application) {
2685+ application->setRequestedState(Application::RequestedSuspended);
2686+ ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState());
2687+ static_cast<qtmir::Session*>(application->session())->doSuspend();
2688+ ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState());
2689+ applicationManager.onProcessSuspended(application->appId());
2690+ ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
2691+ }
2692 };
2693
2694-TEST_F(ApplicationManagerTests, SuspendingAndResumingARunningApplicationResultsInOomScoreAdjustment)
2695-{
2696- using namespace ::testing;
2697-
2698- const QString appId("com.canonical.does.not.exist");
2699-
2700- EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(_, _)).Times(1);
2701- EXPECT_CALL(appController, findDesktopFileForAppId(appId)).Times(1);
2702-
2703- EXPECT_CALL(desktopFileReaderFactory, createInstance(_, _)).Times(1);
2704-
2705- auto application = applicationManager.startApplication(
2706- appId,
2707- ApplicationManager::NoFlag,
2708- QStringList());
2709-
2710- // FIXME - this is doesn't really excerise the actualt behaviour since suspend/resume should be
2711- // controlled by state changes. Requires using suspend timer.
2712- QMetaObject::invokeMethod(application, "onSessionSuspended");
2713- QMetaObject::invokeMethod(application, "onSessionResumed");
2714-}
2715-
2716-TEST_F(ApplicationManagerTests, SuspendingAndResumingDashResultsInOomScoreAdjustment)
2717-{
2718- using namespace ::testing;
2719-
2720- quint64 procId = 5921;
2721- std::shared_ptr<mir::scene::Surface> aSurface(nullptr);
2722- QByteArray cmdLine( "/usr/bin/app1 --desktop_file_hint=unity8-dash");
2723-
2724- EXPECT_CALL(procInfo,command_line(procId))
2725- .Times(1)
2726- .WillOnce(Return(cmdLine));
2727-
2728- ON_CALL(appController,appIdHasProcessId(_,_)).WillByDefault(Return(false));
2729-
2730- bool authed = true;
2731-
2732- std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("Oo", procId);
2733- applicationManager.authorizeSession(procId, authed);
2734- onSessionStarting(session);
2735-
2736- auto application = applicationManager.findApplication("unity8-dash");
2737- applicationManager.onSessionCreatedSurface(session.get(), aSurface);
2738-
2739- // FIXME - this is doesn't really excerise the actualt behaviour since suspend/resume should be
2740- // controlled by state changes. Requires using suspend timer.
2741- QMetaObject::invokeMethod(application, "onSessionSuspended");
2742- QMetaObject::invokeMethod(application, "onSessionResumed");
2743-}
2744-
2745-// Currently disabled as we need to make sure that we have a corresponding mir session, too.
2746-TEST_F(ApplicationManagerTests, DISABLED_FocusingRunningApplicationResultsInOomScoreAdjustment)
2747-{
2748- using namespace ::testing;
2749-
2750- const QString appId("com.canonical.does.not.exist");
2751-
2752- QSet<QString> appIds;
2753-
2754- for (unsigned int i = 0; i < 50; i++)
2755- {
2756- QString appIdFormat("%1.does.not.exist");
2757- auto appId = appIdFormat.arg(i);
2758-
2759- auto application = applicationManager.startApplication(
2760- appId,
2761- ApplicationManager::NoFlag,
2762- QStringList());
2763-
2764- std::shared_ptr<mir::scene::Session> mirSession = std::make_shared<MockSession>(appIdFormat.toStdString(), i);
2765- onSessionStarting( mirSession );
2766-
2767- EXPECT_NE(nullptr, application);
2768- }
2769-
2770- for (auto appId : appIds)
2771- {
2772- applicationManager.focusApplication(appId);
2773- }
2774-}
2775-
2776 TEST_F(ApplicationManagerTests,bug_case_1240400_second_dialer_app_fails_to_authorize_and_gets_mixed_up_with_first_one)
2777 {
2778 using namespace ::testing;
2779- std::shared_ptr<mir::scene::Surface> aSurface(nullptr);
2780 quint64 firstProcId = 5921;
2781 quint64 secondProcId = 5922;
2782 const char dialer_app_id[] = "dialer-app";
2783 QByteArray cmdLine( "/usr/bin/dialer-app --desktop_file_hint=dialer-app");
2784 QByteArray secondcmdLine( "/usr/bin/dialer-app");
2785
2786+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
2787+
2788 EXPECT_CALL(procInfo,command_line(firstProcId))
2789 .Times(1)
2790 .WillOnce(Return(cmdLine));
2791@@ -150,18 +92,20 @@
2792
2793 std::shared_ptr<mir::scene::Session> mirSession = std::make_shared<MockSession>(dialer_app_id, firstProcId);
2794 applicationManager.authorizeSession(firstProcId, authed);
2795- EXPECT_EQ(true, authed);
2796+ ASSERT_EQ(true, authed);
2797 onSessionStarting(mirSession);
2798- applicationManager.onSessionCreatedSurface(mirSession.get(),aSurface);
2799- Application * app = applicationManager.findApplication(dialer_app_id);
2800- EXPECT_NE(nullptr,app);
2801+ onSessionCreatedSurface(mirSession.get(), surface);
2802+ surface->drawFirstFrame();
2803+ Application * application = applicationManager.findApplication(dialer_app_id);
2804+ ASSERT_NE(nullptr,application);
2805+ ASSERT_EQ(Application::InternalState::Running, application->internalState());
2806
2807 // now a second session without desktop file is launched:
2808 applicationManager.authorizeSession(secondProcId, authed);
2809 applicationManager.onProcessStarting(dialer_app_id);
2810
2811 EXPECT_FALSE(authed);
2812- EXPECT_EQ(app,applicationManager.findApplication(dialer_app_id));
2813+ EXPECT_EQ(application, applicationManager.findApplication(dialer_app_id));
2814 }
2815
2816 TEST_F(ApplicationManagerTests,application_dies_while_starting)
2817@@ -182,6 +126,7 @@
2818 onSessionStarting(mirSession);
2819 Application * beforeFailure = applicationManager.findApplication(app_id);
2820 applicationManager.onProcessStarting(app_id);
2821+ onSessionStopping(mirSession);
2822 applicationManager.onProcessFailed(app_id, true);
2823 Application * afterFailure = applicationManager.findApplication(app_id);
2824
2825@@ -190,34 +135,6 @@
2826 EXPECT_EQ(nullptr, afterFailure);
2827 }
2828
2829-TEST_F(ApplicationManagerTests,application_start_failure_after_starting)
2830-{
2831- using namespace ::testing;
2832- quint64 procId = 5921;
2833- std::shared_ptr<mir::scene::Surface> aSurface(nullptr);
2834- const char app_id[] = "my-app";
2835- QByteArray cmdLine( "/usr/bin/my-app --desktop_file_hint=my-app");
2836-
2837- EXPECT_CALL(procInfo,command_line(procId))
2838- .Times(1)
2839- .WillOnce(Return(cmdLine));
2840-
2841- bool authed = true;
2842-
2843- std::shared_ptr<mir::scene::Session> mirSession = std::make_shared<MockSession>(app_id, procId);
2844- applicationManager.authorizeSession(procId, authed);
2845- onSessionStarting(mirSession);
2846- Application * beforeFailure = applicationManager.findApplication(app_id);
2847- applicationManager.onSessionCreatedSurface(mirSession.get(), aSurface);
2848- applicationManager.onProcessStarting(app_id);
2849- applicationManager.onProcessFailed(app_id, false);
2850- Application * afterFailure = applicationManager.findApplication(app_id);
2851-
2852- EXPECT_EQ(true, authed);
2853- EXPECT_NE(nullptr, beforeFailure);
2854- EXPECT_EQ(beforeFailure, afterFailure);
2855-}
2856-
2857 TEST_F(ApplicationManagerTests,startApplicationSupportsShortAppId)
2858 {
2859 using namespace ::testing;
2860@@ -400,7 +317,7 @@
2861 quint64 a_procId = 5921;
2862 const char an_app_id[] = "some_app";
2863 QByteArray a_cmd( "/usr/bin/app1 --desktop_file_hint=some_app");
2864- std::shared_ptr<mir::scene::Surface> aSurface(nullptr);
2865+ FakeMirSurfaceItem *aSurface = new FakeMirSurfaceItem;
2866
2867 ON_CALL(procInfo,command_line(_)).WillByDefault(Return(a_cmd));
2868
2869@@ -413,8 +330,8 @@
2870 applicationManager.authorizeSession(a_procId, authed);
2871
2872 onSessionStarting(first_session);
2873- applicationManager.focusApplication(an_app_id);
2874- applicationManager.onSessionCreatedSurface(first_session.get(), aSurface);
2875+ onSessionCreatedSurface(first_session.get(), aSurface);
2876+ aSurface->drawFirstFrame();
2877 onSessionStarting(second_session);
2878
2879 Application * the_app = applicationManager.findApplication(an_app_id);
2880@@ -430,211 +347,62 @@
2881 quint64 a_procId = 5921;
2882 const char an_app_id[] = "some_app";
2883 QByteArray a_cmd("/usr/bin/app1 --desktop_file_hint=some_app");
2884- std::shared_ptr<mir::scene::Surface> aSurface(nullptr);
2885+ FakeMirSurfaceItem *aSurface = new FakeMirSurfaceItem;
2886
2887 ON_CALL(procInfo, command_line(_)).WillByDefault(Return(a_cmd));
2888 ON_CALL(appController, appIdHasProcessId(_,_)).WillByDefault(Return(false));
2889-
2890+
2891 bool authed = true;
2892
2893 std::shared_ptr<mir::scene::Session> a_session = std::make_shared<MockSession>("Oo", a_procId);
2894-
2895+
2896 applicationManager.authorizeSession(a_procId, authed);
2897 onSessionStarting(a_session);
2898- applicationManager.onSessionCreatedSurface(a_session.get(), aSurface);
2899-
2900- Application * the_app = applicationManager.findApplication(an_app_id);
2901- applicationManager.focusApplication(an_app_id);
2902-
2903- EXPECT_EQ(Application::Running, the_app->state());
2904- EXPECT_EQ(true, the_app->focused());
2905-
2906- applicationManager.focusApplication(an_app_id);
2907- EXPECT_EQ(true, the_app->focused());
2908-}
2909-
2910-TEST_F(ApplicationManagerTests,suspended_suspends_focused_app_and_marks_it_unfocused_in_the_model)
2911-{
2912- using namespace ::testing;
2913- quint64 a_procId = 5921;
2914- const char an_app_id[] = "some_app";
2915- QByteArray a_cmd( "/usr/bin/app1 --desktop_file_hint=some_app");
2916- std::shared_ptr<mir::scene::Surface> aSurface(nullptr);
2917-
2918- ON_CALL(procInfo,command_line(_)).WillByDefault(Return(a_cmd));
2919-
2920- ON_CALL(appController,appIdHasProcessId(_,_)).WillByDefault(Return(false));
2921-
2922- bool authed = true;
2923-
2924- std::shared_ptr<mir::scene::Session> first_session = std::make_shared<MockSession>("Oo", a_procId);
2925- std::shared_ptr<mir::scene::Session> second_session = std::make_shared<MockSession>("oO", a_procId);
2926- applicationManager.authorizeSession(a_procId, authed);
2927-
2928- onSessionStarting(first_session);
2929- applicationManager.onSessionCreatedSurface(first_session.get(), aSurface);
2930- onSessionStarting(second_session);
2931-
2932- Application * the_app = applicationManager.findApplication(an_app_id);
2933- applicationManager.focusApplication(an_app_id);
2934-
2935- EXPECT_EQ(Application::Running, the_app->state());
2936-
2937- applicationManager.setSuspended(true);
2938-
2939- EXPECT_EQ(Application::Suspended, the_app->state());
2940- EXPECT_FALSE(the_app->focused());
2941-
2942- applicationManager.setSuspended(false);
2943-
2944- EXPECT_EQ(Application::Running, the_app->state());
2945- EXPECT_EQ(true, the_app->focused());
2946-}
2947-
2948-TEST_F(ApplicationManagerTests,suspended_suspends_starting_app_when_it_gets_ready)
2949-{
2950- using namespace ::testing;
2951- quint64 a_procId = 5921;
2952- const char an_app_id[] = "some_app";
2953- QByteArray a_cmd( "/usr/bin/app1 --desktop_file_hint=some_app");
2954- std::shared_ptr<mir::scene::Surface> aSurface(nullptr);
2955-
2956- ON_CALL(procInfo,command_line(_)).WillByDefault(Return(a_cmd));
2957-
2958- ON_CALL(appController,appIdHasProcessId(_,_)).WillByDefault(Return(false));
2959-
2960- bool authed = true;
2961-
2962- std::shared_ptr<mir::scene::Session> first_session = std::make_shared<MockSession>("Oo", a_procId);
2963- applicationManager.authorizeSession(a_procId, authed);
2964-
2965- onSessionStarting(first_session);
2966-
2967- Application * the_app = applicationManager.findApplication(an_app_id);
2968- applicationManager.focusApplication(an_app_id);
2969- EXPECT_EQ(Application::Starting, the_app->state());
2970-
2971- applicationManager.setSuspended(true);
2972-
2973- // Not suspending yet, as it's still starting
2974- EXPECT_EQ(Application::Starting, the_app->state());
2975-
2976- // This signals the app is ready now
2977- applicationManager.onSessionCreatedSurface(first_session.get(), aSurface);
2978-
2979- // And given that the AppManager is suspended now, this should go to suspended too
2980- EXPECT_EQ(Application::Suspended, the_app->state());
2981- EXPECT_FALSE(the_app->focused());
2982-
2983- applicationManager.setSuspended(false);
2984-
2985- EXPECT_EQ(Application::Running, the_app->state());
2986- EXPECT_EQ(true, the_app->focused());
2987-}
2988-
2989-TEST_F(ApplicationManagerTests,focus_change_suspends_starting_app_when_it_gets_ready)
2990-{
2991- using namespace ::testing;
2992- quint64 first_procId = 5921;
2993- quint64 second_procId = 5922;
2994- std::shared_ptr<mir::scene::Surface> aSurface(nullptr);
2995- QByteArray first_cmdLine( "/usr/bin/app1 --desktop_file_hint=app1");
2996- QByteArray second_cmdLine( "/usr/bin/app2--desktop_file_hint=app2");
2997-
2998- EXPECT_CALL(procInfo,command_line(first_procId))
2999- .Times(1)
3000- .WillOnce(Return(first_cmdLine));
3001-
3002- ON_CALL(appController,appIdHasProcessId(_,_)).WillByDefault(Return(false));
3003-
3004- EXPECT_CALL(procInfo,command_line(second_procId))
3005- .Times(1)
3006- .WillOnce(Return(second_cmdLine));
3007-
3008- bool authed = true;
3009-
3010- std::shared_ptr<mir::scene::Session> first_session = std::make_shared<MockSession>("Oo", first_procId);
3011- std::shared_ptr<mir::scene::Session> second_session = std::make_shared<MockSession>("oO", second_procId);
3012- applicationManager.authorizeSession(first_procId, authed);
3013- applicationManager.authorizeSession(second_procId, authed);
3014- onSessionStarting(first_session);
3015-
3016- Application * app1 = applicationManager.findApplication("app1");
3017- applicationManager.focusApplication("app1");
3018+ onSessionCreatedSurface(a_session.get(), aSurface);
3019+ aSurface->drawFirstFrame();
3020+
3021+ Application * the_app = applicationManager.findApplication(an_app_id);
3022+ applicationManager.focusApplication(an_app_id);
3023+
3024+ EXPECT_EQ(Application::Running, the_app->state());
3025+ EXPECT_EQ(true, the_app->focused());
3026+
3027+ applicationManager.focusApplication(an_app_id);
3028+ EXPECT_EQ(true, the_app->focused());
3029+}
3030+
3031+TEST_F(ApplicationManagerTests,starting_app_is_suspended_when_it_gets_ready_if_requested)
3032+{
3033+ using namespace ::testing;
3034+ quint64 procId = 5921;
3035+ FakeMirSurfaceItem *aSurface = new FakeMirSurfaceItem;
3036+ QByteArray cmdLine( "/usr/bin/app --desktop_file_hint=app");
3037+
3038+ EXPECT_CALL(procInfo,command_line(procId))
3039+ .Times(1)
3040+ .WillOnce(Return(cmdLine));
3041+
3042+ ON_CALL(appController,appIdHasProcessId(_,_)).WillByDefault(Return(false));
3043+
3044+ bool authed = true;
3045+
3046+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("Oo", procId);
3047+ applicationManager.authorizeSession(procId, authed);
3048+ onSessionStarting(session);
3049+
3050+ Application * app = applicationManager.findApplication("app");
3051+ app->setRequestedState(Application::RequestedSuspended);
3052
3053 // First app starting...
3054- EXPECT_EQ(Application::Starting, app1->state());
3055-
3056- onSessionStarting(second_session);
3057- Application * app2 = applicationManager.findApplication("app2");
3058- applicationManager.focusApplication("app2");
3059-
3060- // Second app starting...
3061- EXPECT_EQ(Application::Starting, app2->state());
3062-
3063- // Make sure first one is still in starting state
3064- EXPECT_EQ(Application::Starting, app1->state());
3065-
3066- // Signal app1 is ready now
3067- applicationManager.onSessionCreatedSurface(first_session.get(), aSurface);
3068-
3069- // Make sure AppMan suspended it now that its ready
3070- EXPECT_EQ(Application::Suspended, app1->state());
3071-}
3072-
3073-TEST_F(ApplicationManagerTests,forceDashActive_activates_dash_while_not_focused)
3074-{
3075- using namespace ::testing;
3076- quint64 first_procId = 5921;
3077- quint64 second_procId = 5922;
3078- std::shared_ptr<mir::scene::Surface> aSurface(nullptr);
3079- QByteArray first_cmdLine( "/usr/bin/app1 --desktop_file_hint=unity8-dash");
3080- QByteArray second_cmdLine( "/usr/bin/app2--desktop_file_hint=app2");
3081-
3082- EXPECT_CALL(procInfo,command_line(first_procId))
3083- .Times(1)
3084- .WillOnce(Return(first_cmdLine));
3085-
3086- ON_CALL(appController,appIdHasProcessId(_,_)).WillByDefault(Return(false));
3087-
3088- EXPECT_CALL(procInfo,command_line(second_procId))
3089- .Times(1)
3090- .WillOnce(Return(second_cmdLine));
3091-
3092- bool authed = true;
3093-
3094- std::shared_ptr<mir::scene::Session> first_session = std::make_shared<MockSession>("Oo", first_procId);
3095- std::shared_ptr<mir::scene::Session> second_session = std::make_shared<MockSession>("oO", second_procId);
3096- applicationManager.authorizeSession(first_procId, authed);
3097- applicationManager.authorizeSession(second_procId, authed);
3098- onSessionStarting(first_session);
3099-
3100- Application * dashApp = applicationManager.findApplication("unity8-dash");
3101- applicationManager.onSessionCreatedSurface(first_session.get(), aSurface);
3102- applicationManager.focusApplication("unity8-dash");
3103-
3104- // Dash app should be ready now...
3105- EXPECT_EQ(Application::Running, dashApp->state());
3106-
3107- // Launch second app
3108- onSessionStarting(second_session);
3109- applicationManager.onSessionCreatedSurface(second_session.get(), aSurface);
3110- applicationManager.focusApplication("app2");
3111- EXPECT_EQ(applicationManager.focusedApplicationId(), "app2");
3112-
3113- // Make sure the dash is suspended
3114- EXPECT_EQ(dashApp->state(), Application::Suspended);
3115-
3116- // Now set the dashactive flag
3117- applicationManager.setForceDashActive(true);
3118-
3119- // And make sure the dash is woken up but not focused
3120- EXPECT_EQ(applicationManager.focusedApplicationId(), "app2");
3121- EXPECT_EQ(dashApp->state(), Application::Running);
3122-
3123- // Unset the dashactive flag
3124- applicationManager.setForceDashActive(false);
3125- EXPECT_EQ(dashApp->state(), Application::Suspended);
3126+ EXPECT_EQ(Application::Starting, app->state());
3127+
3128+ // Signal app is ready now
3129+ applicationManager.onProcessStarting("app");
3130+ onSessionCreatedSurface(session.get(), aSurface);
3131+ aSurface->drawFirstFrame();
3132+
3133+ // now that its ready, suspend process should have begun
3134+ EXPECT_EQ(Application::InternalState::SuspendingWaitSession, app->internalState());
3135 }
3136
3137 TEST_F(ApplicationManagerTests,requestFocusApplication)
3138@@ -713,20 +481,20 @@
3139 Application *theApp = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3140
3141 // check application data
3142- EXPECT_EQ(theApp->state(), Application::Starting);
3143- EXPECT_EQ(theApp->appId(), appId);
3144- EXPECT_EQ(theApp->name(), name);
3145- EXPECT_EQ(theApp->canBeResumed(), true);
3146+ EXPECT_EQ(Application::Starting, theApp->state());
3147+ EXPECT_EQ(appId, theApp->appId());
3148+ EXPECT_EQ(name, theApp->name());
3149+ EXPECT_FALSE(theApp->canBeResumed());
3150
3151 // check signals were emitted
3152- EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
3153- EXPECT_EQ(applicationManager.count(), 1);
3154- EXPECT_EQ(addedSpy.count(), 1);
3155- EXPECT_EQ(addedSpy.takeFirst().at(0).toString(), appId);
3156+ EXPECT_EQ(2, countSpy.count()); //FIXME(greyback)
3157+ EXPECT_EQ(1, applicationManager.count());
3158+ EXPECT_EQ(1, addedSpy.count());
3159+ EXPECT_EQ(appId, addedSpy.takeFirst().at(0).toString());
3160
3161 // check application in list of apps
3162 Application *theAppAgain = applicationManager.findApplication(appId);
3163- EXPECT_EQ(theAppAgain, theApp);
3164+ EXPECT_EQ(theApp, theAppAgain);
3165 }
3166
3167 /*
3168@@ -757,18 +525,18 @@
3169 Application *theApp = applicationManager.findApplication(appId);
3170
3171 // check application data
3172- EXPECT_EQ(theApp->state(), Application::Starting);
3173- EXPECT_EQ(theApp->appId(), appId);
3174- EXPECT_EQ(theApp->name(), name);
3175- EXPECT_EQ(theApp->canBeResumed(), true);
3176+ EXPECT_EQ(Application::Starting, theApp->state());
3177+ EXPECT_EQ(appId, theApp->appId());
3178+ EXPECT_EQ(name, theApp->name());
3179+ EXPECT_EQ(true, theApp->canBeResumed());
3180
3181 // check signals were emitted
3182- EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
3183- EXPECT_EQ(applicationManager.count(), 1);
3184- EXPECT_EQ(addedSpy.count(), 1);
3185- EXPECT_EQ(addedSpy.takeFirst().at(0).toString(), appId);
3186- EXPECT_EQ(focusSpy.count(), 1);
3187- EXPECT_EQ(focusSpy.takeFirst().at(0).toString(), appId);
3188+ EXPECT_EQ(2, countSpy.count()); //FIXME(greyback)
3189+ EXPECT_EQ(1, applicationManager.count());
3190+ EXPECT_EQ(1, addedSpy.count());
3191+ EXPECT_EQ(appId, addedSpy.takeFirst().at(0).toString());
3192+ EXPECT_EQ(1, focusSpy.count());
3193+ EXPECT_EQ(appId, focusSpy.takeFirst().at(0).toString());
3194 }
3195
3196 /*
3197@@ -1067,7 +835,6 @@
3198
3199 applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3200 applicationManager.onProcessStarting(appId);
3201- applicationManager.focusApplication(appId);
3202
3203 std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3204
3205@@ -1075,9 +842,10 @@
3206 applicationManager.authorizeSession(procId, authed);
3207 onSessionStarting(session);
3208
3209- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3210+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3211
3212- applicationManager.onSessionCreatedSurface(session.get(), surface);
3213+ onSessionCreatedSurface(session.get(), surface);
3214+ surface->drawFirstFrame();
3215
3216 // Check application state is correctly set
3217 Application *theApp = applicationManager.findApplication(appId);
3218@@ -1124,7 +892,7 @@
3219 }
3220
3221 /*
3222- * Test that the foreground application is stopped correctly (is in Running state, has surface)
3223+ * Test that a running application is stopped correctly (is in Running state, has surface)
3224 */
3225 TEST_F(ApplicationManagerTests,shellStopsForegroundAppCorrectly)
3226 {
3227@@ -1150,100 +918,61 @@
3228 applicationManager.authorizeSession(procId, authed);
3229 onSessionStarting(session);
3230
3231- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3232- applicationManager.onSessionCreatedSurface(session.get(), surface);
3233- applicationManager.focusApplication(appId);
3234- EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
3235-
3236- QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3237- QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3238-
3239- // Stop app
3240- applicationManager.stopApplication(appId);
3241-
3242- EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
3243- EXPECT_EQ(applicationManager.count(), 0);
3244- EXPECT_EQ(removedSpy.count(), 1);
3245- EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
3246-}
3247-
3248-/*
3249- * Test that the background application is stopped correctly
3250- */
3251-TEST_F(ApplicationManagerTests,shellStopsBackgroundAppCorrectly)
3252-{
3253- using namespace ::testing;
3254- const QString appId("testAppId");
3255- quint64 procId = 5551;
3256-
3257- // Set up Mocks & signal watcher
3258- auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
3259- ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
3260- ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
3261-
3262- ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
3263-
3264- EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
3265- .Times(1)
3266- .WillOnce(Return(true));
3267-
3268- applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3269- applicationManager.onProcessStarting(appId);
3270- std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3271- bool authed = true;
3272- applicationManager.authorizeSession(procId, authed);
3273- onSessionStarting(session);
3274-
3275- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3276- applicationManager.onSessionCreatedSurface(session.get(), surface);
3277- applicationManager.unfocusCurrentApplication();
3278-
3279- EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
3280-
3281- QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3282- QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3283-
3284- // Stop app
3285- applicationManager.stopApplication(appId);
3286-
3287- EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
3288- EXPECT_EQ(applicationManager.count(), 0);
3289- EXPECT_EQ(removedSpy.count(), 1);
3290- EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
3291-}
3292-
3293-/*
3294- * Test that if an application is stopped by upstart, before it has created a surface, AppMan cleans up after it ok
3295- */
3296-TEST_F(ApplicationManagerTests,upstartNotificationOfStartingAppBeingStopped)
3297-{
3298- using namespace ::testing;
3299- const QString appId("testAppId");
3300- quint64 procId = 5551;
3301-
3302- // Set up Mocks & signal watcher
3303- auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
3304- ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
3305- ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
3306-
3307- ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
3308-
3309- EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
3310- .Times(1)
3311- .WillOnce(Return(true));
3312-
3313- applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3314- applicationManager.onProcessStarting(appId);
3315- std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3316- bool authed = true;
3317- applicationManager.authorizeSession(procId, authed);
3318- onSessionStarting(session);
3319-
3320- QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3321- QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3322-
3323- // Upstart notifies of stopping app
3324- applicationManager.onProcessStopped(appId);
3325+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3326+ onSessionCreatedSurface(session.get(), surface);
3327+ surface->drawFirstFrame();
3328+
3329+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3330+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3331+
3332+ // Stop app
3333+ applicationManager.stopApplication(appId);
3334+
3335+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
3336+ EXPECT_EQ(applicationManager.count(), 0);
3337+ EXPECT_EQ(removedSpy.count(), 1);
3338+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
3339+}
3340+
3341+/*
3342+ * Test that a suspended application is stopped correctly
3343+ */
3344+TEST_F(ApplicationManagerTests,shellStopsSuspendedAppCorrectly)
3345+{
3346+ using namespace ::testing;
3347+ const QString appId("testAppId");
3348+ quint64 procId = 5551;
3349+
3350+ // Set up Mocks & signal watcher
3351+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
3352+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
3353+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
3354+
3355+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
3356+
3357+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
3358+ .Times(1)
3359+ .WillOnce(Return(true));
3360+
3361+ Application *application = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3362+ applicationManager.onProcessStarting(appId);
3363+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3364+ bool authed = true;
3365+ applicationManager.authorizeSession(procId, authed);
3366+ onSessionStarting(session);
3367+ applicationManager.onProcessStarting(appId);
3368+
3369+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3370+ onSessionCreatedSurface(session.get(), surface);
3371+ surface->drawFirstFrame();
3372+
3373+ suspend(application);
3374+
3375+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3376+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3377+
3378+ // Stop app
3379+ applicationManager.stopApplication(appId);
3380
3381 EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
3382 EXPECT_EQ(applicationManager.count(), 0);
3383@@ -1278,28 +1007,28 @@
3384 applicationManager.authorizeSession(procId, authed);
3385 onSessionStarting(session);
3386
3387- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3388- applicationManager.onSessionCreatedSurface(session.get(), surface);
3389- applicationManager.focusApplication(appId);
3390- EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
3391+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3392+ onSessionCreatedSurface(session.get(), surface);
3393+ surface->drawFirstFrame();
3394
3395 QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3396 QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3397
3398+ onSessionStopping(session);
3399 // Upstart notifies of stopping app
3400 applicationManager.onProcessStopped(appId);
3401
3402- EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
3403- EXPECT_EQ(applicationManager.count(), 0);
3404- EXPECT_EQ(removedSpy.count(), 1);
3405- EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
3406+ EXPECT_EQ(2, countSpy.count()); //FIXME(greyback)
3407+ EXPECT_EQ(0, applicationManager.count());
3408+ EXPECT_EQ(1, removedSpy.count());
3409+ EXPECT_EQ(appId, removedSpy.takeFirst().at(0).toString());
3410 }
3411
3412 /*
3413- * Test that if the foreground Running application is reported to unexpectedly stop by upstart, AppMan
3414- * cleans up after it ok (as was not in background, had not lifecycle saved its state, so cannot be resumed)
3415+ * Test that if a running application is reported to have stopped unexpectedly by upstart, AppMan
3416+ * cleans up after it ok (as was not suspended, had not lifecycle saved its state, so cannot be resumed)
3417 */
3418-TEST_F(ApplicationManagerTests,upstartNotifiesOfUnexpectedStopOfForegroundApp)
3419+TEST_F(ApplicationManagerTests,upstartNotifiesOfUnexpectedStopOfRunningApp)
3420 {
3421 using namespace ::testing;
3422 const QString appId("testAppId");
3423@@ -1323,14 +1052,15 @@
3424 applicationManager.authorizeSession(procId, authed);
3425 onSessionStarting(session);
3426
3427- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3428- applicationManager.onSessionCreatedSurface(session.get(), surface);
3429- applicationManager.focusApplication(appId);
3430- EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
3431+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3432+ onSessionCreatedSurface(session.get(), surface);
3433+ surface->drawFirstFrame();
3434
3435 QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3436 QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3437
3438+ onSessionStopping(session);
3439+
3440 // Upstart notifies of crashing / OOM killed app
3441 applicationManager.onProcessFailed(appId, false);
3442
3443@@ -1344,53 +1074,6 @@
3444 }
3445
3446 /*
3447- * Test that if a background application is stopped by upstart, AppMan removes it from the app list
3448- * as the event is a result of direct user interaction
3449- */
3450-TEST_F(ApplicationManagerTests,upstartNotifiesOfStoppingBackgroundApp)
3451-{
3452- using namespace ::testing;
3453- const QString appId("testAppId");
3454- quint64 procId = 5551;
3455-
3456- // Set up Mocks & signal watcher
3457- auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
3458- ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
3459- ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
3460-
3461- ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
3462-
3463- EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
3464- .Times(1)
3465- .WillOnce(Return(true));
3466-
3467- applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3468- applicationManager.onProcessStarting(appId);
3469- std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3470- bool authed = true;
3471- applicationManager.authorizeSession(procId, authed);
3472- onSessionStarting(session);
3473-
3474- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3475- applicationManager.onSessionCreatedSurface(session.get(), surface);
3476- applicationManager.unfocusCurrentApplication();
3477- EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
3478-
3479- QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3480- QSignalSpy focusSpy(&applicationManager, SIGNAL(focusedApplicationIdChanged()));
3481- QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3482-
3483- // Upstart notifies of stopping app
3484- applicationManager.onProcessStopped(appId);
3485-
3486- EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
3487- EXPECT_EQ(applicationManager.count(), 0);
3488- EXPECT_EQ(focusSpy.count(), 0);
3489- EXPECT_EQ(removedSpy.count(), 1);
3490- EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
3491-}
3492-
3493-/*
3494 * Test that if a background application is reported to unexpectedly stop by upstart, AppMan does not remove
3495 * it from the app lists but instead considers it Stopped, ready to be resumed. This is due to the fact the
3496 * app should have saved its state, so can be resumed. This situation can occur due to the OOM killer, or
3497@@ -1413,18 +1096,18 @@
3498 .Times(1)
3499 .WillOnce(Return(true));
3500
3501- applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3502+ Application *app = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3503 applicationManager.onProcessStarting(appId);
3504 std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3505 bool authed = true;
3506 applicationManager.authorizeSession(procId, authed);
3507 onSessionStarting(session);
3508
3509- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3510- applicationManager.onSessionCreatedSurface(session.get(), surface);
3511- applicationManager.focusApplication(appId);
3512- applicationManager.unfocusCurrentApplication();
3513- EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
3514+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3515+ onSessionCreatedSurface(session.get(), surface);
3516+ surface->drawFirstFrame();
3517+
3518+ suspend(app);
3519
3520 QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3521 QSignalSpy focusSpy(&applicationManager, SIGNAL(focusedApplicationIdChanged()));
3522@@ -1436,14 +1119,14 @@
3523 // Upstart notifies of crashing / OOM-killed app
3524 applicationManager.onProcessFailed(appId, false);
3525
3526- EXPECT_EQ(focusSpy.count(), 0);
3527+ EXPECT_EQ(0, focusSpy.count());
3528
3529 // Upstart finally notifies the app stopped
3530 applicationManager.onProcessStopped(appId);
3531
3532- EXPECT_EQ(countSpy.count(), 0);
3533- EXPECT_EQ(applicationManager.count(), 1);
3534- EXPECT_EQ(removedSpy.count(), 0);
3535+ EXPECT_EQ(0, countSpy.count());
3536+ EXPECT_EQ(1, applicationManager.count());
3537+ EXPECT_EQ(0, removedSpy.count());
3538 }
3539
3540 /*
3541@@ -1471,18 +1154,18 @@
3542 .Times(1)
3543 .WillOnce(Return(true));
3544
3545- applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3546+ Application *app = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3547 applicationManager.onProcessStarting(appId);
3548 std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3549 bool authed = true;
3550 applicationManager.authorizeSession(procId, authed);
3551 onSessionStarting(session);
3552
3553- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3554- applicationManager.onSessionCreatedSurface(session.get(), surface);
3555- applicationManager.focusApplication(appId);
3556- applicationManager.unfocusCurrentApplication();
3557- EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
3558+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3559+ onSessionCreatedSurface(session.get(), surface);
3560+ surface->drawFirstFrame();
3561+
3562+ suspend(app);
3563
3564 QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3565 QSignalSpy focusSpy(&applicationManager, SIGNAL(focusedApplicationIdChanged()));
3566@@ -1571,10 +1254,9 @@
3567 onSessionStarting(session);
3568
3569 // Associate a surface so AppMan considers app Running, check focused
3570- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3571- applicationManager.onSessionCreatedSurface(session.get(), surface);
3572- applicationManager.focusApplication(appId);
3573- EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
3574+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3575+ onSessionCreatedSurface(session.get(), surface);
3576+ surface->drawFirstFrame();
3577
3578 QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3579 QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3580@@ -1589,10 +1271,10 @@
3581 }
3582
3583 /*
3584- * Test that if a foreground application (one launched via desktop_file_hint) is reported to be stopping by
3585+ * Test that if an application (one launched via desktop_file_hint) is reported to be stopping by
3586 * Mir, AppMan removes it from the model immediately
3587 */
3588-TEST_F(ApplicationManagerTests,mirNotifiesOfStoppingForegroundAppLaunchedWithDesktopFileHint)
3589+TEST_F(ApplicationManagerTests,mirNotifiesOfStoppingAppLaunchedWithDesktopFileHint)
3590 {
3591 using namespace ::testing;
3592 const QString appId("testAppId");
3593@@ -1621,10 +1303,9 @@
3594 onSessionStarting(session);
3595
3596 // Associate a surface so AppMan considers app Running, check focused
3597- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3598- applicationManager.onSessionCreatedSurface(session.get(), surface);
3599- applicationManager.focusApplication(appId);
3600- EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
3601+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3602+ onSessionCreatedSurface(session.get(), surface);
3603+ surface->drawFirstFrame();
3604
3605 QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3606 QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3607@@ -1661,86 +1342,43 @@
3608 .Times(1)
3609 .WillOnce(Return(true));
3610
3611- applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3612+ Application *app = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3613 applicationManager.onProcessStarting(appId);
3614 std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3615 bool authed = true;
3616 applicationManager.authorizeSession(procId, authed);
3617 onSessionStarting(session);
3618-
3619- // Associate a surface so AppMan considers app Running, check in background
3620- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3621- applicationManager.onSessionCreatedSurface(session.get(), surface);
3622- applicationManager.focusApplication(appId);
3623- applicationManager.unfocusCurrentApplication();
3624- EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
3625-
3626- QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3627- QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3628-
3629- // Mir notifies of stopping app
3630- onSessionStopping(session);
3631-
3632- EXPECT_EQ(countSpy.count(), 0);
3633- EXPECT_EQ(applicationManager.count(), 1);
3634- EXPECT_EQ(removedSpy.count(), 0);
3635-
3636- Application * app = applicationManager.findApplication(appId);
3637- EXPECT_NE(nullptr,app);
3638- EXPECT_EQ(app->state(), Application::Stopped);
3639-}
3640-
3641-/*
3642- * Test that if a background application (one launched via desktop_file_hint) is reported to be stopping by
3643- * Mir, AppMan removes it from the model immediately
3644- */
3645-TEST_F(ApplicationManagerTests,mirNotifiesOfStoppingBackgroundAppLaunchedWithDesktopFileHint)
3646-{
3647- using namespace ::testing;
3648- const QString appId("testAppId");
3649- const QString name("Test App");
3650- quint64 procId = 5551;
3651- QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint=");
3652- cmdLine = cmdLine.append(appId);
3653-
3654- // Set up Mocks & signal watcher
3655- EXPECT_CALL(procInfo,command_line(procId))
3656- .Times(1)
3657- .WillOnce(Return(cmdLine));
3658-
3659- auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
3660- ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
3661- ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
3662- ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name));
3663-
3664- ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
3665-
3666- // Mir requests authentication for an application that was started
3667- bool authed = true;
3668- applicationManager.authorizeSession(procId, authed);
3669- EXPECT_EQ(authed, true);
3670- std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3671- onSessionStarting(session);
3672-
3673- // Associate a surface so AppMan considers app Running, check in background
3674- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3675- applicationManager.onSessionCreatedSurface(session.get(), surface);
3676- applicationManager.focusApplication(appId);
3677- applicationManager.unfocusCurrentApplication();
3678- EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
3679-
3680- QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3681- QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3682-
3683- // Mir notifies of stopping app
3684- onSessionStopping(session);
3685-
3686- EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
3687- EXPECT_EQ(applicationManager.count(), 0);
3688- EXPECT_EQ(removedSpy.count(), 1);
3689-
3690- Application * app = applicationManager.findApplication(appId);
3691- EXPECT_EQ(nullptr,app);
3692+ EXPECT_EQ(Application::Starting, app->state());
3693+
3694+ app->setRequestedState(Application::RequestedSuspended);
3695+
3696+ // should not suspend an app that`s still starting up
3697+ ASSERT_EQ(Application::InternalState::Starting, app->internalState());
3698+
3699+ // Associate a surface so AppMan considers app Running
3700+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3701+ onSessionCreatedSurface(session.get(), surface);
3702+ surface->drawFirstFrame();
3703+
3704+ ASSERT_EQ(Application::InternalState::SuspendingWaitSession, app->internalState());
3705+
3706+ static_cast<qtmir::Session*>(app->session())->doSuspend();
3707+ ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, app->internalState());
3708+
3709+ applicationManager.onProcessSuspended(app->appId());
3710+ ASSERT_EQ(Application::InternalState::Suspended, app->internalState());
3711+
3712+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3713+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3714+
3715+ // Mir notifies of stopping app
3716+ onSessionStopping(session);
3717+
3718+ EXPECT_EQ(0, countSpy.count());
3719+ EXPECT_EQ(1, applicationManager.count());
3720+ EXPECT_EQ(0, removedSpy.count());
3721+
3722+ EXPECT_EQ(Application::Stopped, app->state());
3723 }
3724
3725 /*
3726@@ -1822,45 +1460,6 @@
3727 }
3728
3729 /*
3730- * Test that if an application is stopped by upstart, the Mir stopping event is ignored
3731- */
3732-TEST_F(ApplicationManagerTests,appStoppedByUpstart_mirSessionStoppingEventIgnored)
3733-{
3734- using namespace ::testing;
3735- const QString appId("testAppId");
3736- quint64 procId = 5551;
3737-
3738- // Set up Mocks & signal watcher
3739- auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
3740- ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
3741- ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
3742-
3743- ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
3744-
3745- EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
3746- .Times(1)
3747- .WillOnce(Return(true));
3748-
3749- applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3750- applicationManager.onProcessStarting(appId);
3751- std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3752- bool authed = true;
3753- applicationManager.authorizeSession(procId, authed);
3754- onSessionStarting(session);
3755-
3756- applicationManager.onProcessStopped(appId);
3757-
3758- QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3759- QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3760-
3761- // Mir notifies of stopping app
3762- onSessionStopping(session);
3763-
3764- EXPECT_EQ(countSpy.count(), 0);
3765- EXPECT_EQ(removedSpy.count(), 0);
3766-}
3767-
3768-/*
3769 * Webapps have multiple sessions, but only one is linked to the application (other is considered a hidden session).
3770 * If webapp in foreground stops unexpectedly, remove it and it alone from app list
3771 */
3772@@ -1903,8 +1502,9 @@
3773 applicationManager.authorizeSession(procId2, authed);
3774 onSessionStarting(session2);
3775 EXPECT_EQ(authed, true);
3776- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3777- applicationManager.onSessionCreatedSurface(session2.get(), surface);
3778+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3779+ onSessionCreatedSurface(session2.get(), surface);
3780+ surface->drawFirstFrame();
3781
3782 QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3783 QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3784@@ -1950,7 +1550,7 @@
3785 .Times(1)
3786 .WillOnce(Return(true));
3787
3788- applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3789+ Application *app = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3790 applicationManager.onProcessStarting(appId);
3791 std::shared_ptr<mir::scene::Session> session1 = std::make_shared<MockSession>("", procId1);
3792 std::shared_ptr<mir::scene::Session> session2 = std::make_shared<MockSession>("", procId2);
3793@@ -1958,19 +1558,20 @@
3794 bool authed = false;
3795 applicationManager.authorizeSession(procId1, authed);
3796 onSessionStarting(session1);
3797- EXPECT_EQ(authed, true);
3798+ EXPECT_EQ(true, authed);
3799 applicationManager.authorizeSession(procId2, authed);
3800 onSessionStarting(session2);
3801- EXPECT_EQ(authed, true);
3802+ EXPECT_EQ(true, authed);
3803
3804- // both sessions create surfaces, then unfocus everything.
3805- std::shared_ptr<mir::scene::Surface> surface1(nullptr);
3806- applicationManager.onSessionCreatedSurface(session1.get(), surface1);
3807- std::shared_ptr<mir::scene::Surface> surface2(nullptr);
3808- applicationManager.onSessionCreatedSurface(session2.get(), surface2);
3809- applicationManager.focusApplication(appId);
3810- applicationManager.unfocusCurrentApplication();
3811- EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
3812+ // both sessions create surfaces, then get them all suspended
3813+ FakeMirSurfaceItem *surface1 = new FakeMirSurfaceItem;
3814+ onSessionCreatedSurface(session1.get(), surface1);
3815+ surface1->drawFirstFrame();
3816+ FakeMirSurfaceItem *surface2 = new FakeMirSurfaceItem;
3817+ onSessionCreatedSurface(session2.get(), surface2);
3818+ surface2->drawFirstFrame();
3819+ suspend(app);
3820+ EXPECT_EQ(Application::Suspended, app->state());
3821
3822 QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
3823 QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
3824@@ -1979,8 +1580,8 @@
3825 onSessionStopping(session2);
3826 onSessionStopping(session1);
3827
3828- EXPECT_EQ(countSpy.count(), 0);
3829- EXPECT_EQ(removedSpy.count(), 0);
3830+ EXPECT_EQ(0, countSpy.count());
3831+ EXPECT_EQ(0, removedSpy.count());
3832 }
3833
3834 /*
3835@@ -2007,34 +1608,33 @@
3836 .Times(1)
3837 .WillOnce(Return(true));
3838
3839- applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3840+ Application *app = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
3841 applicationManager.onProcessStarting(appId);
3842 std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
3843 bool authed = true;
3844 applicationManager.authorizeSession(procId, authed);
3845 onSessionStarting(session);
3846
3847- // App creates surface, focuses it, puts it in background, then is OOM killed.
3848- std::shared_ptr<mir::scene::Surface> surface(nullptr);
3849- applicationManager.onSessionCreatedSurface(session.get(), surface);
3850- applicationManager.focusApplication(appId);
3851- applicationManager.unfocusCurrentApplication();
3852+ // App creates surface, puts it in background, then is OOM killed.
3853+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
3854+ onSessionCreatedSurface(session.get(), surface);
3855+ surface->drawFirstFrame();
3856+ suspend(app);
3857
3858 onSessionStopping(session);
3859 applicationManager.onProcessFailed(appId, false);
3860 applicationManager.onProcessStopped(appId);
3861
3862- Application *app = applicationManager.findApplication(appId);
3863- EXPECT_EQ(app->state(), Application::Stopped);
3864+ EXPECT_EQ(Application::Stopped, app->state());
3865
3866 QSignalSpy focusRequestSpy(&applicationManager, SIGNAL(focusRequested(const QString &)));
3867
3868 // Upstart re-launches app
3869 applicationManager.onProcessStarting(appId);
3870
3871- EXPECT_EQ(app->state(), Application::Starting);
3872- EXPECT_EQ(focusRequestSpy.count(), 1);
3873- EXPECT_EQ(applicationManager.count(), 1);
3874+ EXPECT_EQ(Application::Starting, app->state());
3875+ EXPECT_EQ(1, focusRequestSpy.count());
3876+ EXPECT_EQ(1, applicationManager.count());
3877 }
3878
3879 /*
3880@@ -2138,142 +1738,61 @@
3881 }
3882 }
3883
3884-/*
3885- 1 - launch and focus a main stage app
3886- * main stage app is running and focused
3887- 2 - launch and focus a side stage app
3888- * main stage app is running but is not focused
3889- * side stage app is running and has focus
3890- 3 - focus the main stage app
3891- * main stage app is running and has focus
3892- * side stage app is running but is not focused
3893-
3894- This is a regression test for the bug where on step 3, the main stage app was momentarily
3895- suspended and then resumed again.
3896- */
3897-TEST_F(ApplicationManagerTests, focusMainStageAfterSideStage)
3898-{
3899- using namespace testing;
3900-
3901- QString webbrowserAppId("webbrowser-app");
3902- quint64 webbrowserPID = 123;
3903- std::shared_ptr<mir::scene::Surface> webbrowserSurface(nullptr);
3904-
3905- QString dialerAppId("dialer-app");
3906- quint64 dialerPID = 456;
3907- std::shared_ptr<mir::scene::Surface> dialerSurface(nullptr);
3908-
3909- /*** Start webbrowser-app (main stage) ***/
3910-
3911- ON_CALL(appController,appIdHasProcessId(webbrowserPID, webbrowserAppId)).WillByDefault(Return(true));
3912-
3913- {
3914- auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(webbrowserAppId, QFileInfo());
3915- ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
3916- ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(webbrowserAppId));
3917- ON_CALL(*mockDesktopFileReader, stageHint()).WillByDefault(Return("MainStage"));
3918-
3919- ON_CALL(desktopFileReaderFactory, createInstance(webbrowserAppId, _))
3920- .WillByDefault(Return(mockDesktopFileReader));
3921- }
3922-
3923- EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(webbrowserAppId, _))
3924- .Times(1)
3925- .WillOnce(Return(true));
3926-
3927- /*auto application =*/ applicationManager.startApplication(webbrowserAppId, ApplicationManager::NoFlag);
3928- applicationManager.onProcessStarting(webbrowserAppId);
3929-
3930- {
3931- bool authed = false;
3932- applicationManager.authorizeSession(webbrowserPID, authed);
3933- EXPECT_EQ(authed, true);
3934- }
3935-
3936- auto webbrowserSession = std::make_shared<mir::scene::MockSession>(webbrowserAppId.toStdString(), webbrowserPID);
3937- sessionManager.onSessionStarting(webbrowserSession);
3938- applicationManager.focusApplication(webbrowserAppId);
3939- applicationManager.onSessionCreatedSurface(webbrowserSession.get(), webbrowserSurface);
3940-
3941- /*** Start dialer-app (side stage) ***/
3942-
3943- ON_CALL(appController, appIdHasProcessId(dialerPID, dialerAppId)).WillByDefault(Return(true));
3944-
3945- {
3946- auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(dialerAppId, QFileInfo());
3947- ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
3948- ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(dialerAppId));
3949- ON_CALL(*mockDesktopFileReader, stageHint()).WillByDefault(Return("SideStage"));
3950-
3951- ON_CALL(desktopFileReaderFactory, createInstance(dialerAppId, _))
3952- .WillByDefault(Return(mockDesktopFileReader));
3953- }
3954-
3955- EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(dialerAppId, _))
3956- .Times(1)
3957- .WillOnce(Return(true));
3958-
3959- /*auto application =*/ applicationManager.startApplication(dialerAppId, ApplicationManager::NoFlag);
3960- applicationManager.onProcessStarting(dialerAppId);
3961-
3962- {
3963- bool authed = false;
3964- applicationManager.authorizeSession(dialerPID, authed);
3965- EXPECT_EQ(authed, true);
3966- }
3967-
3968- auto dialerSession = std::make_shared<mir::scene::MockSession>(dialerAppId.toStdString(), dialerPID);
3969- sessionManager.onSessionStarting(dialerSession);
3970- applicationManager.focusApplication(dialerAppId);
3971- applicationManager.onSessionCreatedSurface(dialerSession.get(), dialerSurface);
3972-
3973- /*** Focus webbrowser ***/
3974-
3975- // Nothing should happen as it's already the running main stage app
3976- EXPECT_CALL(*webbrowserSession.get(), set_lifecycle_state(_))
3977- .Times(0);
3978- applicationManager.focusApplication(webbrowserAppId);
3979-}
3980-
3981 TEST_F(ApplicationManagerTests,lifecycle_exempt_appId_is_not_suspended)
3982 {
3983 using namespace ::testing;
3984 quint64 a_procId = 5921;
3985- const char an_app_id[] = "some_app";
3986- QByteArray a_cmd( "/usr/bin/app1 --desktop_file_hint=some_app");
3987- std::shared_ptr<mir::scene::Surface> aSurface(nullptr);
3988+ const QString appId("some_app");
3989+ QByteArray a_cmd("/usr/bin/app1");
3990
3991 ON_CALL(procInfo,command_line(_)).WillByDefault(Return(a_cmd));
3992
3993- ON_CALL(appController,appIdHasProcessId(_,_)).WillByDefault(Return(false));
3994-
3995- bool authed = true;
3996+ ON_CALL(appController,appIdHasProcessId(a_procId, appId)).WillByDefault(Return(true));
3997+
3998+ // Set up Mocks & signal watcher
3999+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
4000+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
4001+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
4002+
4003+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
4004+
4005+
4006+ Application *the_app = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
4007+ applicationManager.onProcessStarting(appId);
4008
4009 std::shared_ptr<mir::scene::Session> first_session = std::make_shared<MockSession>("Oo", a_procId);
4010 std::shared_ptr<mir::scene::Session> second_session = std::make_shared<MockSession>("oO", a_procId);
4011- applicationManager.authorizeSession(a_procId, authed);
4012+ {
4013+ bool authed = false;
4014+ applicationManager.authorizeSession(a_procId, authed);
4015+ ASSERT_EQ(authed, true);
4016+ }
4017
4018 onSessionStarting(first_session);
4019- applicationManager.onSessionCreatedSurface(first_session.get(), aSurface);
4020+ FakeMirSurfaceItem *aSurface = new FakeMirSurfaceItem;
4021+ onSessionCreatedSurface(first_session.get(), aSurface);
4022+ aSurface->drawFirstFrame();
4023 onSessionStarting(second_session);
4024
4025- Application * the_app = applicationManager.findApplication(an_app_id);
4026- applicationManager.focusApplication(an_app_id);
4027-
4028 // Add to other apps to the list (Not "some_app")
4029 QVariantList lifecycleExemptAppIds;
4030 lifecycleExemptAppIds << "one_app" << "another_app";
4031 ON_CALL(settings,get(_)).WillByDefault(Return(lifecycleExemptAppIds));
4032 settings.changed("lifecycleExemptAppids");
4033
4034- EXPECT_EQ(Application::Running, the_app->state());
4035-
4036- applicationManager.setSuspended(true);
4037-
4038- // And expect "some_app" to get suspended
4039- EXPECT_EQ(Application::Suspended, the_app->state());
4040-
4041- applicationManager.setSuspended(false);
4042+ ASSERT_EQ(Application::InternalState::Running, the_app->internalState());
4043+
4044+ EXPECT_CALL(*(mir::scene::MockSession*)first_session.get(), set_lifecycle_state(mir_lifecycle_state_will_suspend));
4045+ the_app->setRequestedState(Application::RequestedSuspended);
4046+ ASSERT_EQ(Application::InternalState::SuspendingWaitSession, the_app->internalState());
4047+
4048+ static_cast<qtmir::Session*>(the_app->session())->doSuspend();
4049+ ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, the_app->internalState());
4050+ applicationManager.onProcessSuspended(the_app->appId());
4051+ ASSERT_EQ(Application::InternalState::Suspended, the_app->internalState());
4052+
4053+ EXPECT_CALL(*(mir::scene::MockSession*)first_session.get(), set_lifecycle_state(mir_lifecycle_state_resumed));
4054+ the_app->setRequestedState(Application::RequestedRunning);
4055
4056 EXPECT_EQ(Application::Running, the_app->state());
4057
4058@@ -2284,14 +1803,16 @@
4059
4060 EXPECT_EQ(Application::Running, the_app->state());
4061
4062- applicationManager.setSuspended(true);
4063+ EXPECT_CALL(*(mir::scene::MockSession*)first_session.get(), set_lifecycle_state(_)).Times(0);
4064+ the_app->setRequestedState(Application::RequestedSuspended);
4065
4066 // And expect it to be running still
4067- EXPECT_EQ(Application::Running, the_app->state());
4068-
4069- applicationManager.setSuspended(false);
4070-
4071- EXPECT_EQ(Application::Running, the_app->state());
4072+ ASSERT_EQ(Application::InternalState::RunningInBackground, the_app->internalState());
4073+
4074+ the_app->setRequestedState(Application::RequestedRunning);
4075+
4076+ EXPECT_EQ(Application::Running, the_app->state());
4077+ ASSERT_EQ(Application::InternalState::Running, the_app->internalState());
4078 }
4079
4080 /*
4081@@ -2323,51 +1844,13 @@
4082 onSessionStarting(session);
4083
4084 // App creates surface, focuses it so state is running
4085- std::shared_ptr<mir::scene::Surface> surface(nullptr);
4086- applicationManager.onSessionCreatedSurface(session.get(), surface);
4087- applicationManager.focusApplication(appId);
4088-
4089- applicationManager.unfocusCurrentApplication();
4090-
4091- EXPECT_FALSE(sharedWakelock.enabled());
4092- EXPECT_EQ(application->state(), Application::Running);
4093-}
4094-
4095-/*
4096- * Test lifecycle exempt applications have their wakelocks released on suspend
4097- */
4098-TEST_F(ApplicationManagerTests,lifecycleExemptAppsHaveWakelockReleasedOnUnSuspend)
4099-{
4100- using namespace ::testing;
4101-
4102- const QString appId("com.ubuntu.music"); // member of lifecycle exemption list
4103- const quint64 procId = 12345;
4104-
4105- // Set up Mocks & signal watcher
4106- auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
4107- ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
4108- ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
4109-
4110- ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
4111-
4112- EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
4113- .Times(1)
4114- .WillOnce(Return(true));
4115-
4116- auto application = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
4117- applicationManager.onProcessStarting(appId);
4118- std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
4119- bool authed = true;
4120- applicationManager.authorizeSession(procId, authed);
4121- onSessionStarting(session);
4122-
4123- // App creates surface, focuses it so state is running
4124- std::shared_ptr<mir::scene::Surface> surface(nullptr);
4125- applicationManager.onSessionCreatedSurface(session.get(), surface);
4126- applicationManager.focusApplication(appId);
4127-
4128- applicationManager.setSuspended(true);
4129-
4130- EXPECT_FALSE(sharedWakelock.enabled());
4131- EXPECT_EQ(application->state(), Application::Running);
4132+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
4133+ onSessionCreatedSurface(session.get(), surface);
4134+ surface->drawFirstFrame();
4135+
4136+ application->setRequestedState(Application::RequestedSuspended);
4137+
4138+ EXPECT_FALSE(sharedWakelock.enabled());
4139+ ASSERT_EQ(Application::InternalState::RunningInBackground, application->internalState());
4140+ EXPECT_EQ(Application::Running, application->state());
4141 }
4142
4143=== modified file 'tests/modules/MirSurfaceItem/CMakeLists.txt'
4144--- tests/modules/MirSurfaceItem/CMakeLists.txt 2014-12-03 08:56:35 +0000
4145+++ tests/modules/MirSurfaceItem/CMakeLists.txt 2015-07-24 22:01:37 +0000
4146@@ -5,7 +5,7 @@
4147 )
4148
4149 include_directories(
4150- ${CMAKE_SOURCE_DIR}/src/modules/Unity/Application
4151+ ${CMAKE_SOURCE_DIR}/src/modules
4152 ${CMAKE_SOURCE_DIR}/tests/modules/common
4153 ${MIRSERVER_INCLUDE_DIRS}
4154 )
4155
4156=== modified file 'tests/modules/MirSurfaceItem/mirsurfaceitem_test.cpp'
4157--- tests/modules/MirSurfaceItem/mirsurfaceitem_test.cpp 2015-05-01 13:37:03 +0000
4158+++ tests/modules/MirSurfaceItem/mirsurfaceitem_test.cpp 2015-07-24 22:01:37 +0000
4159@@ -23,7 +23,7 @@
4160 #include <QTest>
4161
4162 // the test subject
4163-#include <mirsurfaceitem.h>
4164+#include <Unity/Application/mirsurfaceitem.h>
4165
4166 // mocks
4167 #include <mock_surface.h>
4168
4169=== modified file 'tests/modules/SessionManager/CMakeLists.txt'
4170--- tests/modules/SessionManager/CMakeLists.txt 2015-05-21 18:48:59 +0000
4171+++ tests/modules/SessionManager/CMakeLists.txt 2015-07-24 22:01:37 +0000
4172@@ -3,9 +3,12 @@
4173 session_manager_test.cpp
4174 session_test.cpp
4175 ${CMAKE_SOURCE_DIR}/src/common/debughelpers.cpp
4176+ ${CMAKE_SOURCE_DIR}/tests/modules/common/qtmir_test.cpp
4177+ ${CMAKE_SOURCE_DIR}/tests/modules/common/fake_mirsurfaceitem.h
4178 )
4179
4180 include_directories(
4181+ ${APPLICATION_API_INCLUDE_DIRS}
4182 ${CMAKE_SOURCE_DIR}/src/platforms/mirserver
4183 ${CMAKE_SOURCE_DIR}/src/modules
4184 ${CMAKE_SOURCE_DIR}/tests/modules/common
4185
4186=== modified file 'tests/modules/SessionManager/session_manager_test.cpp'
4187--- tests/modules/SessionManager/session_manager_test.cpp 2014-12-01 11:05:01 +0000
4188+++ tests/modules/SessionManager/session_manager_test.cpp 2015-07-24 22:01:37 +0000
4189@@ -21,7 +21,7 @@
4190
4191 #include <Unity/Application/session.h>
4192
4193- #include "qtmir_test.h"
4194+#include "qtmir_test.h"
4195
4196 using namespace qtmir;
4197 using mir::scene::MockSession;
4198
4199=== modified file 'tests/modules/SessionManager/session_test.cpp'
4200--- tests/modules/SessionManager/session_test.cpp 2015-01-09 15:13:29 +0000
4201+++ tests/modules/SessionManager/session_test.cpp 2015-07-24 22:01:37 +0000
4202@@ -1,5 +1,5 @@
4203 /*
4204- * Copyright (C) 2014 Canonical, Ltd.
4205+ * Copyright (C) 2014,2015 Canonical, Ltd.
4206 *
4207 * This program is free software: you can redistribute it and/or modify it under
4208 * the terms of the GNU Lesser General Public License version 3, as published by
4209@@ -15,16 +15,17 @@
4210 *
4211 */
4212
4213+#include <qtmir_test.h>
4214+#include <fake_mirsurfaceitem.h>
4215+
4216 #include <Unity/Application/application.h>
4217 #include <Unity/Application/mirsurfaceitem.h>
4218
4219-#include "qtmir_test.h"
4220 #include "stub_scene_surface.h"
4221
4222 using namespace qtmir;
4223 using mir::scene::MockSession;
4224
4225-
4226 namespace ms = mir::scene;
4227 namespace mtd = mir::test::doubles;
4228
4229@@ -43,6 +44,30 @@
4230 }
4231 };
4232
4233+TEST_F(SessionTests, FromStartingToRunningOnceSurfaceDrawsFirstFrame)
4234+{
4235+ using namespace testing;
4236+
4237+ const QString appId("test-app");
4238+ quint64 procId = 5551;
4239+
4240+ auto mirSession = std::make_shared<MockSession>(appId.toStdString(), procId);
4241+
4242+ auto session = std::make_shared<qtmir::Session>(mirSession, mirServer->the_prompt_session_manager());
4243+
4244+ // On Starting as it has no surface.
4245+ EXPECT_EQ(Session::Starting, session->state());
4246+
4247+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
4248+ session->setSurface(surface);
4249+
4250+ // Still on Starting as the surface hasn't drawn its first frame yet
4251+ EXPECT_EQ(Session::Starting, session->state());
4252+
4253+ surface->drawFirstFrame();
4254+ EXPECT_EQ(Session::Running, session->state());
4255+}
4256+
4257 TEST_F(SessionTests, AddChildSession)
4258 {
4259 using namespace testing;
4260@@ -192,17 +217,6 @@
4261 EXPECT_THAT(destroyed, UnorderedElementsAre(session1, session2, session3));
4262 }
4263
4264-class MockQtMirSession : public qtmir::Session
4265-{
4266-public:
4267- MockQtMirSession(const std::shared_ptr<ms::Session>& session,
4268- const std::shared_ptr<ms::PromptSessionManager>& promptSessionManager)
4269- : Session(session, promptSessionManager)
4270- {}
4271-
4272- using SessionInterface::appendPromptSession;
4273-};
4274-
4275 TEST_F(SessionTests, SuspendPromptSessionWhenSessionSuspends)
4276 {
4277 using namespace testing;
4278@@ -211,17 +225,25 @@
4279 quint64 procId = 5551;
4280
4281 auto mirSession = std::make_shared<MockSession>(appId.toStdString(), procId);
4282- EXPECT_CALL(*mirSession, set_lifecycle_state(_));
4283
4284- auto session = std::make_shared<MockQtMirSession>(mirSession, mirServer->the_prompt_session_manager());
4285- session->setState(Session::State::Running);
4286+ auto session = std::make_shared<qtmir::Session>(mirSession, mirServer->the_prompt_session_manager());
4287+ {
4288+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
4289+ session->setSurface(surface);
4290+ surface->drawFirstFrame();
4291+ }
4292+ EXPECT_EQ(Session::Running, session->state());
4293
4294 auto mirPromptSession = std::make_shared<ms::MockPromptSession>();
4295 session->appendPromptSession(mirPromptSession);
4296
4297 EXPECT_CALL(*mirServer->the_mock_prompt_session_manager(), suspend_prompt_session(_)).Times(1);
4298
4299- session->setState(Session::State::Suspended);
4300+ EXPECT_CALL(*mirSession, set_lifecycle_state(mir_lifecycle_state_will_suspend));
4301+ session->suspend();
4302+ EXPECT_EQ(Session::Suspending, session->state());
4303+ session->doSuspend();
4304+ EXPECT_EQ(Session::Suspended, session->state());
4305
4306 Mock::VerifyAndClear(mirServer->the_mock_prompt_session_manager().get());
4307 }
4308@@ -234,18 +256,29 @@
4309 quint64 procId = 5551;
4310
4311 auto mirSession = std::make_shared<MockSession>(appId.toStdString(), procId);
4312- EXPECT_CALL(*mirSession, set_lifecycle_state(_));
4313-
4314- auto session = std::make_shared<MockQtMirSession>(mirSession, mirServer->the_prompt_session_manager());
4315- session->setState(Session::State::Suspended);
4316+
4317+ auto session = std::make_shared<qtmir::Session>(mirSession, mirServer->the_prompt_session_manager());
4318+ {
4319+ FakeMirSurfaceItem *surface = new FakeMirSurfaceItem;
4320+ session->setSurface(surface);
4321+ surface->drawFirstFrame();
4322+ }
4323+ EXPECT_EQ(Session::Running, session->state());
4324+
4325+ EXPECT_CALL(*mirSession, set_lifecycle_state(mir_lifecycle_state_will_suspend));
4326+ session->suspend();
4327+ EXPECT_EQ(Session::Suspending, session->state());
4328+ session->doSuspend();
4329+ EXPECT_EQ(Session::Suspended, session->state());
4330
4331 auto mirPromptSession = std::make_shared<ms::MockPromptSession>();
4332 session->appendPromptSession(mirPromptSession);
4333
4334 EXPECT_CALL(*mirServer->the_mock_prompt_session_manager(), resume_prompt_session(_)).Times(1);
4335
4336- session->setState(Session::State::Running);
4337+ EXPECT_CALL(*mirSession, set_lifecycle_state(mir_lifecycle_state_resumed));
4338+ session->resume();
4339+ EXPECT_EQ(Session::Running, session->state());
4340
4341 Mock::VerifyAndClear(mirServer->the_mock_prompt_session_manager().get());
4342 }
4343-
4344
4345=== modified file 'tests/modules/TaskController/CMakeLists.txt'
4346--- tests/modules/TaskController/CMakeLists.txt 2015-05-21 18:48:59 +0000
4347+++ tests/modules/TaskController/CMakeLists.txt 2015-07-24 22:01:37 +0000
4348@@ -5,6 +5,7 @@
4349 )
4350
4351 include_directories(
4352+ ${APPLICATION_API_INCLUDE_DIRS}
4353 ${CMAKE_SOURCE_DIR}/src/modules
4354 ${CMAKE_SOURCE_DIR}/tests/modules/common
4355 ${MIRSERVER_INCLUDE_DIRS}
4356
4357=== added file 'tests/modules/common/fake_mirsurfaceitem.h'
4358--- tests/modules/common/fake_mirsurfaceitem.h 1970-01-01 00:00:00 +0000
4359+++ tests/modules/common/fake_mirsurfaceitem.h 2015-07-24 22:01:37 +0000
4360@@ -0,0 +1,95 @@
4361+/*
4362+ * Copyright (C) 2015 Canonical, Ltd.
4363+ *
4364+ * This program is free software: you can redistribute it and/or modify it under
4365+ * the terms of the GNU Lesser General Public License version 3, as published by
4366+ * the Free Software Foundation.
4367+ *
4368+ * This program is distributed in the hope that it will be useful, but WITHOUT
4369+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4370+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4371+ * Lesser General Public License for more details.
4372+ *
4373+ * You should have received a copy of the GNU Lesser General Public License
4374+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4375+ */
4376+
4377+#ifndef FAKE_MIRSURFACEITEMINTERFACE_H
4378+#define FAKE_MIRSURFACEITEMINTERFACE_H
4379+
4380+#include <Unity/Application/mirsurfaceiteminterface.h>
4381+
4382+namespace qtmir {
4383+
4384+class FakeMirSurfaceItem : public MirSurfaceItemInterface
4385+{
4386+ Q_OBJECT
4387+
4388+public:
4389+ FakeMirSurfaceItem(QQuickItem *parent = nullptr)
4390+ : MirSurfaceItemInterface(parent)
4391+ , m_state(Restored)
4392+ , m_live(true)
4393+ , m_session(nullptr)
4394+ , m_isFirstFrameDrawn(false)
4395+ , m_isFrameDropperRunning(true)
4396+ {}
4397+
4398+ Type type() const override { return Normal; }
4399+ State state() const override { return m_state; }
4400+ QString name() const override { return QString("fake app surface"); }
4401+ bool live() const override { return m_live; }
4402+ SessionInterface *session() const override { return m_session; }
4403+ OrientationAngle orientationAngle() const override { return Angle0; }
4404+
4405+ Q_INVOKABLE void release() override {}
4406+
4407+ void stopFrameDropper() override {
4408+ m_isFrameDropperRunning = false;
4409+ }
4410+ void startFrameDropper() override {
4411+ m_isFrameDropperRunning = true;
4412+ }
4413+
4414+ bool isFirstFrameDrawn() const override {
4415+ return m_isFirstFrameDrawn;
4416+ }
4417+
4418+ void setOrientationAngle(OrientationAngle) override {
4419+ }
4420+
4421+ void setSession(SessionInterface *session) override {
4422+ m_session = session;
4423+ }
4424+
4425+ void setState(State state) {
4426+ if (m_state != state) {
4427+ m_state = state;
4428+ Q_EMIT stateChanged();
4429+ }
4430+ }
4431+
4432+ void drawFirstFrame() {
4433+ if (!m_isFirstFrameDrawn) {
4434+ m_isFirstFrameDrawn = true;
4435+ Q_EMIT firstFrameDrawn();
4436+ }
4437+ }
4438+
4439+ bool isFrameDropperRunning() const {
4440+ return m_isFrameDropperRunning;
4441+ }
4442+
4443+private:
4444+ void setLive(bool value) override { m_live = value; }
4445+
4446+ State m_state;
4447+ bool m_live;
4448+ SessionInterface *m_session;
4449+ bool m_isFirstFrameDrawn;
4450+ bool m_isFrameDropperRunning;
4451+};
4452+
4453+} // namespace qtmir
4454+
4455+#endif // FAKE_MIRSURFACEITEMINTERFACE_H
4456
4457=== added file 'tests/modules/common/mock_mirsurfaceitem.h'
4458--- tests/modules/common/mock_mirsurfaceitem.h 1970-01-01 00:00:00 +0000
4459+++ tests/modules/common/mock_mirsurfaceitem.h 2015-07-24 22:01:37 +0000
4460@@ -0,0 +1,50 @@
4461+/*
4462+ * Copyright (C) 2015 Canonical, Ltd.
4463+ *
4464+ * This program is free software: you can redistribute it and/or modify it under
4465+ * the terms of the GNU Lesser General Public License version 3, as published by
4466+ * the Free Software Foundation.
4467+ *
4468+ * This program is distributed in the hope that it will be useful, but WITHOUT
4469+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
4470+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
4471+ * Lesser General Public License for more details.
4472+ *
4473+ * You should have received a copy of the GNU Lesser General Public License
4474+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
4475+ *
4476+ */
4477+
4478+#ifndef MOCK_QTMIR_MIRSURFACEITEM_H
4479+#define MOCK_QTMIR_MIRSURFACEITEM_H
4480+
4481+#include <Unity/Application/mirsurfaceiteminterface.h>
4482+#include <gmock/gmock.h>
4483+
4484+namespace qtmir {
4485+
4486+class MockMirSurfaceItem : public MirSurfaceItemInterface {
4487+public:
4488+ MockMirSurfaceItem(QQuickItem *parent = nullptr) : MirSurfaceItemInterface(parent) {}
4489+
4490+ MOCK_CONST_METHOD0(type, Type());
4491+ MOCK_CONST_METHOD0(state, State());
4492+ MOCK_CONST_METHOD0(name, QString());
4493+ MOCK_CONST_METHOD0(live, bool());
4494+ MOCK_CONST_METHOD0(orientationAngle, OrientationAngle());
4495+ MOCK_CONST_METHOD0(session, SessionInterface*());
4496+
4497+ MOCK_METHOD0(release, void());
4498+
4499+ MOCK_METHOD0(stopFrameDropper, void());
4500+ MOCK_METHOD0(startFrameDropper, void());
4501+
4502+ MOCK_CONST_METHOD0(isFirstFrameDrawn, bool());
4503+
4504+ MOCK_METHOD1(setOrientationAngle, void(OrientationAngle angle));
4505+ MOCK_METHOD1(setSession, void(SessionInterface *app));
4506+};
4507+
4508+} // namespace qtmir
4509+
4510+#endif // MOCK_QTMIR_MIRSURFACEITEM_H
4511
4512=== modified file 'tests/modules/common/mock_session.h'
4513--- tests/modules/common/mock_session.h 2014-09-11 16:18:40 +0000
4514+++ tests/modules/common/mock_session.h 2015-07-24 22:01:37 +0000
4515@@ -18,28 +18,39 @@
4516 #ifndef MOCK_QTMIR_SESSION_H
4517 #define MOCK_QTMIR_SESSION_H
4518
4519-#include <session_interface.h>
4520+#include <Unity/Application/session_interface.h>
4521 #include <gmock/gmock.h>
4522
4523 namespace qtmir {
4524
4525 class MockSession : public SessionInterface {
4526 public:
4527- MockSession() : SessionInterface(0) {}
4528+ MockSession() : SessionInterface(0) {
4529+ m_state = Starting;
4530+ ON_CALL(*this, suspend()).WillByDefault(::testing::Invoke(this, &MockSession::doSuspend));
4531+ ON_CALL(*this, resume()).WillByDefault(::testing::Invoke(this, &MockSession::doResume));
4532+ ON_CALL(*this, stop()).WillByDefault(::testing::Invoke(this, &MockSession::doStop));
4533+ ON_CALL(*this, state()).WillByDefault(::testing::Invoke(this, &MockSession::doState));
4534+ }
4535
4536 MOCK_METHOD0(release, void());
4537
4538 MOCK_CONST_METHOD0(name, QString());
4539 MOCK_CONST_METHOD0(application, unity::shell::application::ApplicationInfoInterface*());
4540- MOCK_CONST_METHOD0(surface, MirSurfaceItem*());
4541+ MOCK_CONST_METHOD0(surface, MirSurfaceItemInterface*());
4542 MOCK_CONST_METHOD0(parentSession, SessionInterface*());
4543+
4544 MOCK_CONST_METHOD0(state, State());
4545+
4546 MOCK_CONST_METHOD0(fullscreen, bool());
4547 MOCK_CONST_METHOD0(live, bool());
4548
4549 MOCK_METHOD1(setApplication, void(unity::shell::application::ApplicationInfoInterface* item));
4550- MOCK_METHOD1(setSurface, void(MirSurfaceItem* surface));
4551- MOCK_METHOD1(setState, void(State state));
4552+ MOCK_METHOD1(setSurface, void(MirSurfaceItemInterface* surface));
4553+
4554+ MOCK_METHOD0(suspend, void());
4555+ MOCK_METHOD0(resume, void());
4556+ MOCK_METHOD0(stop, void());
4557
4558 MOCK_METHOD1(addChildSession, void(SessionInterface* session));
4559 MOCK_METHOD2(insertChildSession, void(uint index, SessionInterface* session));
4560@@ -53,11 +64,39 @@
4561
4562 MOCK_CONST_METHOD0(childSessions, SessionModel*());
4563
4564+ void setState(State state) {
4565+ if (m_state != state) {
4566+ m_state = state;
4567+ Q_EMIT stateChanged(m_state);
4568+ }
4569+ }
4570+
4571+ void doSuspend() {
4572+ if (m_state == Running) {
4573+ setState(Suspending);
4574+ }
4575+ }
4576+ void doResume() {
4577+ if (m_state == Suspending || m_state == Suspended) {
4578+ setState(Running);
4579+ }
4580+ }
4581+ void doStop() {
4582+ setState(Stopped);
4583+ }
4584+ State doState() const {
4585+ return m_state;
4586+ }
4587+
4588 protected:
4589 MOCK_METHOD1(setFullscreen, void(bool fullscreen));
4590 MOCK_METHOD1(setLive, void(const bool));
4591 MOCK_METHOD1(appendPromptSession, void(const std::shared_ptr<mir::scene::PromptSession>& session));
4592 MOCK_METHOD1(removePromptSession, void(const std::shared_ptr<mir::scene::PromptSession>& session));
4593+
4594+private:
4595+
4596+ State m_state;
4597 };
4598
4599 } // namespace qtmir
4600
4601=== added file 'tests/modules/common/qtmir_test.cpp'
4602--- tests/modules/common/qtmir_test.cpp 1970-01-01 00:00:00 +0000
4603+++ tests/modules/common/qtmir_test.cpp 2015-07-24 22:01:37 +0000
4604@@ -0,0 +1,61 @@
4605+#include "qtmir_test.h"
4606+
4607+namespace qtmir {
4608+
4609+void PrintTo(const Application::InternalState& state, ::std::ostream* os) {
4610+ switch (state) {
4611+ case Application::InternalState::Starting:
4612+ *os << "Starting";
4613+ break;
4614+ case Application::InternalState::Running:
4615+ *os << "Running";
4616+ break;
4617+ case Application::InternalState::RunningInBackground:
4618+ *os << "RunningInBackground";
4619+ break;
4620+ case Application::InternalState::SuspendingWaitSession:
4621+ *os << "SuspendingWaitSession";
4622+ break;
4623+ case Application::InternalState::SuspendingWaitProcess:
4624+ *os << "SuspendingWaitProcess";
4625+ break;
4626+ case Application::InternalState::Suspended:
4627+ *os << "Suspended";
4628+ break;
4629+ case Application::InternalState::StoppedUnexpectedly:
4630+ *os << "StoppedUnexpectedly";
4631+ break;
4632+ case Application::InternalState::Stopped:
4633+ *os << "Stopped";
4634+ break;
4635+ default:
4636+ *os << "???";
4637+ break;
4638+ }
4639+}
4640+
4641+void PrintTo(const Session::State& state, ::std::ostream* os)
4642+{
4643+ switch (state) {
4644+ case SessionInterface::Starting:
4645+ *os << "Starting";
4646+ break;
4647+ case SessionInterface::Running:
4648+ *os << "Running";
4649+ break;
4650+ case SessionInterface::Suspending:
4651+ *os << "Suspending";
4652+ break;
4653+ case SessionInterface::Suspended:
4654+ *os << "Suspended";
4655+ break;
4656+ case SessionInterface::Stopped:
4657+ *os << "Stopped";
4658+ break;
4659+ default:
4660+ *os << "???";
4661+ break;
4662+ }
4663+}
4664+
4665+} // namespace qtmir
4666
4667=== modified file 'tests/modules/common/qtmir_test.h'
4668--- tests/modules/common/qtmir_test.h 2015-03-24 23:38:33 +0000
4669+++ tests/modules/common/qtmir_test.h 2015-07-24 22:01:37 +0000
4670@@ -1,5 +1,5 @@
4671 /*
4672- * Copyright (C) 2014 Canonical, Ltd.
4673+ * Copyright (C) 2014,2015 Canonical, Ltd.
4674 *
4675 * This program is free software: you can redistribute it and/or modify it under
4676 * the terms of the GNU Lesser General Public License version 3, as published by
4677@@ -18,12 +18,16 @@
4678 #ifndef QT_MIR_TEST_FRAMEWORK_H
4679 #define QT_MIR_TEST_FRAMEWORK_H
4680
4681+#include <memory>
4682+
4683 #include <gtest/gtest.h>
4684
4685+#include <Unity/Application/application.h>
4686 #include <Unity/Application/application_manager.h>
4687 #include <Unity/Application/applicationcontroller.h>
4688 #include <Unity/Application/mirsurfacemanager.h>
4689 #include <Unity/Application/sessionmanager.h>
4690+#include <Unity/Application/session_interface.h>
4691 #include <Unity/Application/sharedwakelock.h>
4692 #include <Unity/Application/taskcontroller.h>
4693 #include <Unity/Application/proc_info.h>
4694@@ -43,6 +47,10 @@
4695
4696 namespace qtmir {
4697
4698+// For better output in ASSERT_* and EXPECT_* error messages
4699+void PrintTo(const Application::InternalState& state, ::std::ostream* os);
4700+void PrintTo(const SessionInterface::State& state, ::std::ostream* os);
4701+
4702 // Initialization of mir::Server needed for by tests
4703 class TestMirServerInit : virtual mir::Server
4704 {

Subscribers

People subscribed via source and target branches