MeeGo 1.2 Harmattan Developer Documentation Develop for the Nokia N9

Application lifecycle

MeeGo 1.2 Harmattan applications can be started either by the user directly from the Applications view or by other applications.

Running applications can be either in the Active state (in the foreground) or in the Minimised state (in the background where they cannot be interacted with). The transition between the states takes place as a result of user interaction. State transitions are detected by capturing the change of the property platformWindow.windowState.

Note: Use the platformWindow element when inspecting the visibility state state of an application. The screen.windowState method exists but it has been deprecated. For more information, see the following API documentation:

There are several ways to terminate an application when it is in either of these states. All applications are automatically terminated when the device is shut down.

The windowState properties can return the following information:

  • windowState.visible: returns true if the window or its thumbnail is shown; otherwise false if the window or its thumbnail has been hidden at least for 1000 ms.
  • windowState.active: returns true if the window is active (has focus).
  • windowState.animating: read-write property for the window orientation animation.
  • windowState.viewMode: returns the current view mode, WindowState.Fullsize for full-screen and WindowState.Thumbnail for thumbnail modes.
  • windowState.viewModeString: returns the current view mode as a string, Fullsize for full-size and Thumbnail for thumbnail modes.

The following diagram illustrates application states.

Harmattan application state diagram

Application visibility and activity status

Application visibility and activity status is defined by the platformWindow context property. Details of platformWindow can be found in the Qt Quick Components documentation.

Visibility and activity status are accessible by using the boolean properties platformWindow::visible and platformWindow::active. There are four possible combinations:

Status Description
visible == true and active == true The application is visible to the user and can be interacted with.
visible == true and active == false The application is (at least partly) visible but cannot be interacted with. This can happen when there is, for example, a partly transparent system menu on top of the application, or the application is visible to the user in the task switcher.
visible == false and active == true This combination does not exist.
visible == false and active == false The application is not visible to the user and cannot be interacted with.

Additionally, if you want the application to draw a different UI depending on whether it is full-size or not, you can use platformWindow.viewMode, which has the possible values of WindowState.Fullsize and WindowState.Thumbnail.

Active state

Different modes of interaction require different levels of responsiveness. For example, when scrolling lists in an application, smooth operation requires a faster UI response than 16 ms, whereas when pressing a button, 100 ms is typically perceived as instant. If an Active application UI blocks for several seconds, the system notifies the user and asks if the application can be terminated. To avoid this situation and have a responsive UI, potentially blocking operations (such as file and network access) need to be performed in a separate thread or process.

When an application is in the Active state, it can use more CPU and memory. If an Active application uses an excessive amount of memory and all background applications have already been terminated because of this, the out-of-memory situation can lead to the termination of the Active application.

Minimised state

When an application is in the Minimised state, the application must not perform any background processing, unless this was explicitly requested by the user. Such exceptions can include, for example, music playback and route tracking. However, applications must not update the UI when it is not visible to the end user. You can monitor application background activity by using strace or by using htop. You can also monitor UI updates with xresponse. These tools can be downloaded after activating developer mode.

When applications are in the Minimised state, they can get closed in two different ways:

  • The end user closes the application from the task switcher UI.
  • The system terminates the application. This happens if the system runs out of memory available for applications. In practice, the applications to be killed are selected on the basis of the applications' memory consumption and usage patterns. Applications are terminated with the SIGKILL signal, which they cannot catch or block.

User-friendly applications must be ready to be terminated whenever they are in the Minimised state. When applications detect a transition to the Minimised state they must:

  • Store all relevant unsaved data to a persistent storage to prepare for being abruptly terminated by the system.
  • Free large resources, such as cached images, to postpone or to completely avoid being terminated by the system in out-of-memory situations.

Code examples

The following code examples illustrate the Active and Minimised states.

Example: Implementing a state machine

The following example shows how to implement a state machine for an application. In this case, the developer is mainly interested in whether the application is full-size and visible. If the application is not visible, it shows a different stand-by UI and shuts down all resources that can consume battery. Examples of such applications are camera applications, games, or applications using sensors.

Note: Two states do not suit the needs of every application, but it is easy to add more states with different rules.

The example also illustrates how to use the states together with Page and PageStack Qt Quick Components, which is useful if you want to show a different UI in the non-active state (as thumbnail).

The main.qml file defines pages for the active main view and the stand-by view, which are identified by their ids mainPage and standbyPage.

Sample code for main.qml:

import QtQuick 1.1
import com.nokia.meego 1.0

PageStackWindow {
    id: appWindow

    initialPage: mainPage

    MainPage {
        id: mainPage
    }
    StandbyPage {
        id: standbyPage
    }

    states: [
        State {
            name: "fullsize-visible"
            when: platformWindow.viewMode == WindowState.Fullsize && platformWindow.visible
            StateChangeScript {
                script: {
                    if (pageStack.currentPage == standbyPage) { pageStack.pop() }
                    console.log("Visibility: Fullsize and visible!")
                }
            }
        },
        State {
            name: "thumbnail-or-invisible"
            when: platformWindow.viewMode == WindowState.Thumbnail || !platformWindow.visible
            StateChangeScript {
                script: {
                    if (pageStack.currentPage == mainPage) { pageStack.push(standbyPage) }
                    console.log("Visibility: Not fullsize (Thumbnail and/or invisible)")
                }
            }
        }
    ]
}

Sample code for the main page. Note the state change handling in onStatusChanged method.

Note: In the following example, an active page is always visible and an inactive one is not. However, the Active and Inactive states of the Page component do not always correspond with the visibility of the Page. For more information, see Qt Quick Components documentation.

import QtQuick 1.1
import com.nokia.meego 1.0

Page {
    id: mainPage

    Label {
        id: label
        y: 200
        anchors.horizontalCenter: parent.horizontalCenter
        text: qsTr("The visible and active page")
    }
    BusyIndicator {
        id: indicator
        platformStyle: BusyIndicatorStyle { size: "large" }
        running: true
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: label.bottom
    }

    onStatusChanged: {
        if (status == PageStatus.Active) {
            // In our example we know that the page is now visible,
            // activate normal state of operations:
            // start up whatever is stopped when page becomes inactive
            indicator.running = true
        }
        else if (status == PageStatus.Inactive) {
            // In this case the page is no longer visible:
            // Stop all the resource-consuming activities like camera, video playback,
            // sensors, gps access, and so on. In general, there should be no activity in
            // case of most of the applications.
            indicator.running = false
        }
    }
}

Sample code for the stand-by page:

import QtQuick 1.1
import com.nokia.meego 1.0

Page {
    id: standbyPage

    Rectangle {
        anchors.fill: parent
        color: "#444444"

        Label {
            id: standbyLabel
            anchors.centerIn: parent
            text: qsTr("Standby")
            color: "white"
            font.bold: true
            font.pixelSize: 100
        }
    }
}

Example: Accessing states

The following example shows how to access the Minimised and Active states and how the states handle different situations.

import QtQuick 1.1
import com.nokia.meego 1.0

Page {
    id: visibilityPage
    anchors.margins: rootWindow.pageMargin
    tools: commonTools

    function updateViewMode() {
        if (platformWindow.viewMode == WindowState.Fullsize) {
            l1.item.color = "green";
        } else {
            l1.item.color = "red";
        }

        l1.item.text = platformWindow.viewModeString;
    }

    function updateVisible() {
        if (platformWindow.visible) {
            l2.item.color = "green";
            l2.item.text = "visible";
        } else {
            l2.item.color = "red";
            l2.item.text = "invisible";
        }
    }

    function updateActive() {
        if (platformWindow.active) {
            l3.item.color = "green";
            l3.item.text = "active";
        } else {
            l3.item.color = "red";
            l3.item.text = "inactive";
        }
    }

    Connections {
        target: platformWindow

        onViewModeChanged: updateViewMode()
        onVisibleChanged: updateVisible()
        onActiveChanged: updateActive()
    }

    Component {
        id: textBox

        Rectangle {
            property alias text: textItem.text

            width: 200; height: 150
            color: "yellow"
            border.color: "black"
            border.width: 5
            radius: 10

            Text {
                id: textItem
                anchors.centerIn: parent
                font.pointSize: 32
                color: "black"
            }
        }
    }
    
    Flickable {
        id: flickable
        anchors.fill: parent
        contentWidth: col.width
        contentHeight: col.height
        flickableDirection: Flickable.VerticalFlick    

        Column {
            id: col
            spacing: 10
            width: flickable.width
            
            Loader {
                id: l1
                sourceComponent: textBox
            }
    
            Loader {
                id: l2
                sourceComponent: textBox
            }
    
            Loader {
                id: l3
                sourceComponent: textBox
            }
    
            Component.onCompleted: {
                updateViewMode();
                updateVisible();
                updateActive();
                
                var count = children.length;
                for (var i = 0; i < count; i++) {
                    var item = children[i];
                    item.anchors.horizontalCenter = item.parent.horizontalCenter;
                }                
            }
        }
    }
}