@@ -0,0 +1,1151 @@
2+ * Copyright (C) 2015 Canonical, Ltd.
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+import QtQuick.Layouts 1.1
20+import Ubuntu.Components 1.1
21+import Ubuntu.Components.ListItems 1.0 as ListItem
22+import Unity.Application 0.1
24+import LightDM 0.1 as LightDM
32+ width: units.gu(160) + controls.width
33+ height: units.gu(100)
36+ id: applicationArguments
38+ function hasGeometry() {
49+ property string deviceName: "mako"
53+ id: mockOrientationLock
54+ property int savedOrientation
58+ id: mockUsageModeSettings
59+ property string usageMode: usageModeSelector.model[usageModeSelector.selectedIndex]
62+ property int physicalOrientation0
63+ property int physicalOrientation90
64+ property int physicalOrientation180
65+ property int physicalOrientation270
66+ property real primaryOrientationAngle
68+ state: applicationArguments.deviceName
73+ target: orientedShellLoader
79+ physicalOrientation0: Qt.PortraitOrientation
80+ physicalOrientation90: Qt.InvertedLandscapeOrientation
81+ physicalOrientation180: Qt.InvertedPortraitOrientation
82+ physicalOrientation270: Qt.LandscapeOrientation
83+ primaryOrientationAngle: 0
89+ target: orientedShellLoader
91+ height: units.gu(100)
95+ physicalOrientation90: Qt.PortraitOrientation
96+ physicalOrientation180: Qt.InvertedLandscapeOrientation
97+ physicalOrientation270: Qt.InvertedPortraitOrientation
98+ physicalOrientation0: Qt.LandscapeOrientation
99+ primaryOrientationAngle: 0
105+ target: orientedShellLoader
107+ height: units.gu(100)
111+ physicalOrientation270: Qt.PortraitOrientation
112+ physicalOrientation0: Qt.InvertedLandscapeOrientation
113+ physicalOrientation90: Qt.InvertedPortraitOrientation
114+ physicalOrientation180: Qt.LandscapeOrientation
115+ primaryOrientationAngle: 90
121+ id: orientedShellLoader
123+ x: ((root.width - controls.width) - width) / 2
124+ y: (root.height - height) / 2
128+ property bool itemDestroyed: false
129+ sourceComponent: Component {
131+ anchors.fill: parent
132+ usageModeSettings: mockUsageModeSettings
133+ physicalOrientation: root.physicalOrientation0
134+ orientationLocked: orientationLockedCheckBox.checked
135+ orientationLock: mockOrientationLock
136+ Component.onDestruction: {
137+ orientedShellLoader.itemDestroyed = true;
143+ function orientationsToStr(orientations) {
144+ if (orientations === Qt.PrimaryOrientation) {
148+ if (orientations & Qt.PortraitOrientation) {
151+ if (orientations & Qt.InvertedPortraitOrientation) {
152+ str += " InvertedPortrait";
154+ if (orientations & Qt.LandscapeOrientation) {
155+ str += " Landscape";
157+ if (orientations & Qt.InvertedLandscapeOrientation) {
158+ str += " InvertedLandscape";
165+ width: controls.width
168+ bottom: parent.bottom
178+ bottom: parent.bottom
182+ boundsBehavior: Flickable.StopAtBounds
183+ contentHeight: controlsColumn.height
187+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
188+ spacing: units.gu(1)
190+ text: "Show Greeter"
191+ activeFocusOnPress: false
193+ if (orientedShellLoader.status !== Loader.Ready)
196+ LightDM.Greeter.showGreeter();
200+ text: "Physical Orientation:"
204+ text: root.orientationsToStr(root.physicalOrientation0) + " (0)"
205+ activeFocusOnPress: false
207+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation0;
212+ text: root.orientationsToStr(root.physicalOrientation90) + " (90)"
213+ activeFocusOnPress: false
215+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation90;
220+ text: root.orientationsToStr(root.physicalOrientation180) + " (180)"
221+ activeFocusOnPress: false
223+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation180;
228+ text: root.orientationsToStr(root.physicalOrientation270) + " (270)"
229+ activeFocusOnPress: false
231+ orientedShellLoader.item.physicalOrientation = root.physicalOrientation270;
235+ Layout.fillWidth: true
237+ id: orientationLockedCheckBox
239+ activeFocusOnPress: false
242+ text: "Orientation Locked"
243+ anchors.verticalCenter: parent.verticalCenter
247+ text: "Power dialog"
248+ activeFocusOnPress: false
249+ onClicked: { testCase.showPowerDialog(); }
251+ ListItem.ItemSelector {
252+ id: deviceNameSelector
253+ anchors { left: parent.left; right: parent.right }
254+ activeFocusOnPress: false
256+ model: ["mako", "manta", "flo"]
257+ onSelectedIndexChanged: {
258+ testCase.tearDown();
259+ applicationArguments.deviceName = model[selectedIndex];
260+ orientedShellLoader.active = true;
263+ ListItem.ItemSelector {
264+ id: usageModeSelector
265+ anchors { left: parent.left; right: parent.right }
266+ activeFocusOnPress: false
268+ model: ["Staged", "Windowed", "Automatic"]
271+ text: "Switch fullscreen"
272+ activeFocusOnPress: false
274+ var app = ApplicationManager.findApplication(ApplicationManager.focusedApplicationId);
275+ app.fullscreen = !app.fullscreen;
279+ Layout.fillWidth: true
282+ activeFocusOnPress: false
284+ var surface = SurfaceManager.inputMethodSurface();
286+ surface.setState(MirSurfaceItem.Restored);
288+ surface.setState(MirSurfaceItem.Minimized);
293+ text: "Input Method"
294+ anchors.verticalCenter: parent.verticalCenter
298+ text: Powerd.status === Powerd.On ? "Display ON" : "Display OFF"
299+ activeFocusOnPress: false
301+ if (Powerd.status === Powerd.On) {
302+ Powerd.status = Powerd.Off;
304+ Powerd.status = Powerd.On;
313+ name: "OrientedShell"
316+ property Item orientedShell: orientedShellLoader.status === Loader.Ready ? orientedShellLoader.item : null
319+ SignalSpy { id: signalSpy }
320+ SignalSpy { id: signalSpy2 }
323+ id: spreadRepeaterConnections
324+ ignoreUnknownSignals : true
325+ property var itemAddedCallback: null
327+ if (itemAddedCallback) {
328+ itemAddedCallback(item);
334+ if (orientedShellLoader.active) {
335+ // happens for the very first test function as shell
336+ // is loaded by default
341+ function tearDown() {
342+ orientedShellLoader.itemDestroyed = false;
343+ orientedShellLoader.active = false;
345+ tryCompare(orientedShellLoader, "status", Loader.Null);
346+ tryCompare(orientedShellLoader, "item", null);
347+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
348+ // actually took place. Likely because Loader waits until the next event loop
349+ // iteration to do its work. So to ensure the reload, we will wait until the
350+ // Shell instance gets destroyed.
351+ tryCompare(orientedShellLoader, "itemDestroyed", true);
353+ // kill all (fake) running apps
356+ spreadRepeaterConnections.target = null;
357+ spreadRepeaterConnections.itemAddedCallback = null;
358+ signalSpy.target = null;
359+ signalSpy.signalName = "";
361+ LightDM.Greeter.authenticate(""); // reset greeter
364+ function cleanup() {
365+ tryCompare(shell, "enabled", true); // make sure greeter didn't leave us in disabled state
371+ function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut_data() {
373+ {tag: "mako", deviceName: "mako"},
374+ {tag: "manta", deviceName: "manta"},
375+ {tag: "flo", deviceName: "flo"}
378+ function test_appSupportingOnlyPrimaryOrientationMakesShellStayPut(data) {
379+ loadShell(data.deviceName);
381+ // unity8-dash supports only primary orientation and should be already running
382+ var dashAppWindow = findChild(shell, "appWindow_unity8-dash");
383+ verify(dashAppWindow);
384+ compare(ApplicationManager.focusedApplicationId, "unity8-dash");
385+ var dashApp = dashAppWindow.application
387+ compare(dashApp.rotatesWindowContents, false);
388+ compare(dashApp.supportedOrientations, Qt.PrimaryOrientation);
389+ compare(dashApp.stage, ApplicationInfoInterface.MainStage);
391+ tryCompareFunction(function(){return dashApp.session.surface != null;}, true);
392+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
394+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
397+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
398+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
402+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
403+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
407+ verify(checkAppSurfaceOrientation(dashApp, root.primaryOrientationAngle));
408+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
411+ function test_greeterRemainsInPrimaryOrientation_data() {
413+ {tag: "mako", deviceName: "mako"},
414+ {tag: "manta", deviceName: "manta"},
415+ {tag: "flo", deviceName: "flo"}
418+ function test_greeterRemainsInPrimaryOrientation(data) {
419+ loadShell(data.deviceName);
421+ var gmailApp = ApplicationManager.startApplication("gmail-webapp");
424+ // ensure the mock gmail-webapp is as we expect
425+ compare(gmailApp.rotatesWindowContents, false);
426+ compare(gmailApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
427+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
428+ compare(gmailApp.stage, ApplicationInfoInterface.MainStage);
430+ // wait until it's able to rotate
431+ tryCompare(shell, "orientationChangesEnabled", true);
433+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
435+ tryCompare(shell, "transformRotationAngle", root.primaryOrientationAngle + 90);
439+ tryCompare(shell, "transformRotationAngle", root.primaryOrientationAngle);
441+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
443+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
445+ compare(shell.transformRotationAngle, root.primaryOrientationAngle);
448+ function test_appRotatesWindowContents_data() {
450+ {tag: "mako", deviceName: "mako", orientationAngleAfterRotation: 90},
451+ {tag: "manta", deviceName: "manta", orientationAngleAfterRotation: 90},
452+ {tag: "flo", deviceName: "flo", orientationAngleAfterRotation: 180}
455+ function test_appRotatesWindowContents(data) {
456+ loadShell(data.deviceName);
457+ var cameraApp = ApplicationManager.startApplication("camera-app");
460+ // ensure the mock camera-app is as we expect
461+ compare(cameraApp.fullscreen, true);
462+ compare(cameraApp.rotatesWindowContents, true);
463+ compare(cameraApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
464+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
466+ waitUntilAppSurfaceShowsUp("camera-app")
468+ var cameraSurface = cameraApp.session.surface;
469+ verify(cameraSurface);
471+ var focusChangedSpy = signalSpy;
472+ focusChangedSpy.clear();
473+ focusChangedSpy.target = cameraSurface;
474+ focusChangedSpy.signalName = "activeFocusChanged";
475+ verify(focusChangedSpy.valid);
477+ verify(cameraSurface.activeFocus);
479+ tryCompare(shell, "orientationChangesEnabled", true);
481+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
482+ verify(rotationStates);
483+ var immediateTransition = null
484+ for (var i = 0; i < rotationStates.transitions.length && !immediateTransition; ++i) {
485+ var transition = rotationStates.transitions[i];
486+ if (transition.objectName == "immediateTransition") {
487+ immediateTransition = transition;
490+ verify(immediateTransition);
491+ var transitionSpy = signalSpy2;
492+ transitionSpy.clear();
493+ transitionSpy.target = immediateTransition;
494+ transitionSpy.signalName = "runningChanged";
495+ verify(transitionSpy.valid);
499+ tryCompare(cameraSurface, "orientationAngle", data.orientationAngleAfterRotation);
501+ // the rotation should have been immediate
502+ // false -> true -> false
503+ compare(transitionSpy.count, 2);
505+ // It should retain native dimensions regardless of its rotation/orientation
506+ compare(cameraSurface.width, orientedShell.width);
507+ compare(cameraSurface.height, orientedShell.height);
509+ // Surface focus shouldn't have been touched because of the rotation
510+ compare(focusChangedSpy.count, 0);
515+ Shell orientation angle matches the screen one.
518+ User switches to an app that has an orientation angle different from the
519+ shell one but that also happens to support the current shell orientation
523+ The app should get rotated to match shell's orientation angle
525+ function test_switchingToAppWithDifferentRotation_data() {
527+ {tag: "mako", deviceName: "mako", shellAngleAfterRotation: 90},
528+ {tag: "manta", deviceName: "manta", shellAngleAfterRotation: 90},
529+ {tag: "flo", deviceName: "flo", shellAngleAfterRotation: 180}
532+ function test_switchingToAppWithDifferentRotation(data) {
533+ loadShell(data.deviceName);
534+ var gmailApp = ApplicationManager.startApplication("gmail-webapp");
537+ // ensure the mock gmail-webapp is as we expect
538+ compare(gmailApp.rotatesWindowContents, false);
539+ compare(gmailApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
540+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
541+ compare(gmailApp.stage, ApplicationInfoInterface.MainStage);
543+ waitUntilAppSurfaceShowsUp("gmail-webapp")
545+ var musicApp = ApplicationManager.startApplication("music-app");
548+ // ensure the mock music-app is as we expect
549+ compare(musicApp.rotatesWindowContents, false);
550+ compare(musicApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
551+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
552+ compare(musicApp.stage, ApplicationInfoInterface.MainStage);
554+ waitUntilAppSurfaceShowsUp("music-app")
555+ tryCompare(shell, "orientationChangesEnabled", true);
558+ tryCompare(shell, "transformRotationAngle", data.shellAngleAfterRotation);
560+ performEdgeSwipeToSwitchToPreviousApp();
562+ tryCompare(shell, "mainAppWindowOrientationAngle", data.shellAngleAfterRotation);
563+ compare(shell.transformRotationAngle, data.shellAngleAfterRotation);
568+ - Device supports portrait, landscape and inverted-landscape
571+ 1 - Launch app that supports all orientations
572+ 2 - Rotate device to inverted-landscape
573+ 3 - See that shell gets rotated to inverted-landscape accordingly
574+ 4 - Rotate device to inverted-portrait
577+ Shell stays at inverted-landscape
580+ Shell rotates to landscape
583+ Rationale being that shell should be rotated to the closest supported orientation.
584+ In that case, landscape and inverted-landscape are both 90 degrees away from the physical
585+ orientation (Screen.orientation), so they are both equally good alternatives
587+ function test_rotateToUnsupportedDeviceOrientation(data) {
589+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
592+ // ensure the mock twitter-webapp is as we expect
593+ compare(twitterApp.rotatesWindowContents, false);
594+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
595+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
597+ waitUntilAppSurfaceShowsUp("twitter-webapp")
599+ rotateTo(data.rotationAngle);
600+ tryCompare(shell, "transformRotationAngle", data.rotationAngle);
603+ tryCompare(shell, "transformRotationAngle", data.rotationAngle);
605+ function test_rotateToUnsupportedDeviceOrientation_data() {
607+ {tag: "90", rotationAngle: 90},
608+ {tag: "270", rotationAngle: 270}
612+ function test_launchLandscapeOnlyAppFromPortrait() {
614+ var weatherApp = ApplicationManager.startApplication("ubuntu-weather-app");
617+ // ensure the mock app is as we expect
618+ compare(weatherApp.supportedOrientations, Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation);
620+ waitUntilAppSurfaceShowsUp("ubuntu-weather-app");
622+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
623+ waitUntilTransitionsEnd(rotationStates);
625+ tryCompareFunction(function (){return shell.transformRotationAngle === 90
626+ || shell.transformRotationAngle === 270;}, true);
630+ - launch an app that supports all orientations, such as twitter-webapp
631+ - wait a bit until that app is considered to have finished initializing and is thus
632+ ready to get resized/rotated
633+ - switch back to dash
634+ - rotate device to 90 degrees
635+ - Physical orientation is 90 but Shell orientation is kept at 0 because unity8-dash
636+ doesn't support such orientation
637+ - do a long right-edge drag to show the apps spread
638+ - tap on twitter-webapp
640+ Shell will rotate to match the physical orientation.
642+ This is a kind of tricky case as there are a few things happening at the same time:
643+ 1 - Stage switching from apps spread (phase 2) to showing the focused app (phase 0)
644+ 2 - orientation and aspect ratio (ie size) changes
646+ This may trigger some corner case bugs. such as one were
647+ the greeter is not kept completely outside the shell, causing the black rect in Shell.qml
648+ have an opacity > 0.
650+ function test_greeterStaysAwayAfterRotation() {
652+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
655+ // ensure the mock twitter-webapp is as we expect
656+ compare(twitterApp.rotatesWindowContents, false);
657+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
658+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
660+ waitUntilAppSurfaceShowsUp("twitter-webapp");
661+ waitUntilAppWindowCanRotate("twitter-webapp");
663+ // go back to unity8-dash
664+ performEdgeSwipeToSwitchToPreviousApp();
667+ wait(1); // spin the event loop to let all bindings do their thing
668+ // should not rotat as unity8-dash doesn't support it
669+ tryCompare(shell, "transformRotationAngle", 0);
671+ performEdgeSwipeToShowAppSpread();
673+ // wait until things have settled
674+ var greeter = findChild(shell, "greeter");
675+ tryCompare(greeter, "animating", false);
677+ var twitterDelegate = findChild(shell, "appDelegate1");
678+ compare(twitterDelegate.application.appId, "twitter-webapp");
679+ twitterDelegate.clicked();
681+ // now it should finally follow the physical orientation
682+ tryCompare(shell, "transformRotationAngle", 90);
684+ // greeter should remaing completely hidden
685+ tryCompare(greeter, "shown", false);
688+ function test_appInSideStageDoesntRotateOnStartUp_data() {
690+ {tag: "manta", deviceName: "manta"},
691+ {tag: "flo", deviceName: "flo"}
694+ function test_appInSideStageDoesntRotateOnStartUp(data) {
695+ loadShell(data.deviceName);
697+ var twitterDelegate = null;
699+ verify(spreadRepeaterConnections.target);
700+ spreadRepeaterConnections.itemAddedCallback = function(item) {
701+ verify(item.application.appId, "twitter-webapp");
702+ twitterDelegate = item;
703+ signalSpy.target = findInvisibleChild(item, "orientationChangeAnimation");
704+ verify(signalSpy.valid);
708+ signalSpy.target = null;
709+ signalSpy.signalName = "runningChanged";
711+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
714+ // ensure the mock twitter-webapp is as we expect
715+ compare(twitterApp.rotatesWindowContents, false);
716+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
717+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
718+ compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
720+ // Wait until spreadRepeaterConnections hs caught the new SpreadDelegate and
721+ // set up the signalSpy target accordingly.
722+ tryCompareFunction(function(){ return signalSpy.target != null && signalSpy.valid; }, true);
724+ tryCompare(twitterDelegate, "orientationChangesEnabled", true);
726+ var appWindowWithShadow = findChild(twitterDelegate, "appWindowWithShadow");
727+ verify(appWindowWithShadow);
728+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
730+ // no reason for any rotation animation to have taken place
731+ compare(signalSpy.count, 0);
734+ function test_portraitOnlyAppInSideStage_data() {
736+ {tag: "manta", deviceName: "manta"},
737+ {tag: "flo", deviceName: "flo"}
740+ function test_portraitOnlyAppInSideStage(data) {
741+ loadShell(data.deviceName);
743+ var dialerDelegate = null;
744+ verify(spreadRepeaterConnections.target);
745+ spreadRepeaterConnections.itemAddedCallback = function(item) {
746+ dialerDelegate = item;
747+ verify(item.application.appId, "dialer-app");
750+ var dialerApp = ApplicationManager.startApplication("dialer-app");
753+ // ensure the mock dialer-app is as we expect
754+ compare(dialerApp.rotatesWindowContents, false);
755+ compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
756+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
758+ tryCompareFunction(function(){ return dialerDelegate != null; }, true);
759+ tryCompare(dialerDelegate, "orientationChangesEnabled", true);
761+ var appWindowWithShadow = findChild(dialerDelegate, "appWindowWithShadow");
762+ verify(appWindowWithShadow);
763+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
765+ // app must have portrait aspect ratio
766+ verify(appWindowWithShadow.width < appWindowWithShadow.height);
768+ compare(appWindowWithShadow.rotation, 0);
770+ verifyAppWindowWithinSpreadDelegateBoundaries(dialerDelegate);
772+ // shell should remain in its primery orientation as the app in the main stage
773+ // is the one that dictates its orientation. In this case it's unity8-dash
774+ // which supports only primary orientation
775+ compare(shell.orientation, orientedShell.primaryOrientation);
778+ function test_sideStageAppsRemainPortraitInSpread_data() {
780+ {tag: "manta", deviceName: "manta"},
781+ {tag: "flo", deviceName: "flo"}
784+ function test_sideStageAppsRemainPortraitInSpread(data) {
785+ loadShell(data.deviceName);
790+ var dialerDelegate = null;
791+ verify(spreadRepeaterConnections.target);
792+ spreadRepeaterConnections.itemAddedCallback = function(item) {
793+ dialerDelegate = item;
794+ verify(item.application.appId, "dialer-app");
797+ var dialerApp = ApplicationManager.startApplication("dialer-app");
800+ // ensure the mock dialer-app is as we expect
801+ compare(dialerApp.rotatesWindowContents, false);
802+ compare(dialerApp.supportedOrientations, Qt.PortraitOrientation | Qt.InvertedPortraitOrientation);
803+ compare(dialerApp.stage, ApplicationInfoInterface.SideStage);
805+ tryCompareFunction(function(){ return dialerDelegate != null; }, true);
806+ waitUntilAppDelegateIsFullyInit(dialerDelegate);
811+ var twitterDelegate = null;
812+ spreadRepeaterConnections.itemAddedCallback = function(item) {
813+ twitterDelegate = item;
814+ verify(item.application.appId, "twitter-webapp");
817+ var twitterApp = ApplicationManager.startApplication("twitter-webapp");
820+ // ensure the mock twitter-webapp is as we expect
821+ compare(twitterApp.rotatesWindowContents, false);
822+ compare(twitterApp.supportedOrientations, Qt.PortraitOrientation | Qt.LandscapeOrientation
823+ | Qt.InvertedPortraitOrientation | Qt.InvertedLandscapeOrientation);
824+ compare(twitterApp.stage, ApplicationInfoInterface.SideStage);
826+ tryCompareFunction(function(){ return twitterDelegate != null; }, true);
827+ waitUntilAppDelegateIsFullyInit(twitterDelegate);
830+ // Edge swipe to show spread and check orientations
832+ performEdgeSwipeToShowAppSpread();
835+ var appWindowWithShadow = findChild(dialerDelegate, "appWindowWithShadow");
836+ verify(appWindowWithShadow);
837+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
838+ compare(appWindowWithShadow.width, dialerDelegate.width);
839+ compare(appWindowWithShadow.height, dialerDelegate.height);
840+ compare(appWindowWithShadow.rotation, 0);
843+ var appWindowWithShadow = findChild(twitterDelegate, "appWindowWithShadow");
844+ verify(appWindowWithShadow);
845+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
846+ compare(appWindowWithShadow.width, twitterDelegate.width);
847+ compare(appWindowWithShadow.height, twitterDelegate.height);
848+ compare(appWindowWithShadow.rotation, 0);
851+ verifyAppWindowWithinSpreadDelegateBoundaries(dialerDelegate);
852+ verifyAppWindowWithinSpreadDelegateBoundaries(twitterDelegate);
855+ function test_launchedAppHasActiveFocus_data() {
857+ {tag: "mako", deviceName: "mako"},
858+ {tag: "manta", deviceName: "manta"},
859+ {tag: "flo", deviceName: "flo"}
862+ function test_launchedAppHasActiveFocus(data) {
863+ loadShell(data.deviceName);
865+ var gmailApp = ApplicationManager.startApplication("gmail-webapp");
867+ waitUntilAppSurfaceShowsUp("gmail-webapp")
869+ verify(gmailApp.session.surface);
871+ tryCompare(gmailApp.session.surface, "activeFocus", true);
874+ function test_launchLandscapeOnlyAppOverPortraitOnlyDashThenSwitchToDash() {
877+ // starts as portrait, as unity8-dash is portrait only
878+ tryCompare(shell, "transformRotationAngle", 0);
880+ var weatherApp = ApplicationManager.startApplication("ubuntu-weather-app");
883+ // ensure the mock app is as we expect
884+ compare(weatherApp.supportedOrientations, Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation);
886+ waitUntilAppSurfaceShowsUp("ubuntu-weather-app");
888+ // should have rotated to landscape
889+ tryCompareFunction(function () { return shell.transformRotationAngle == 270
890+ || shell.transformRotationAngle == 90; }, true);
892+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
893+ waitUntilTransitionsEnd(rotationStates);
895+ performLeftEdgeSwipeToSwitchToDash();
897+ // Should be back to portrait
898+ tryCompare(shell, "transformRotationAngle", 0);
901+ // angle - rotation angle in degrees clockwise, relative to the primary orientation.
902+ function rotateTo(angle) {
905+ rotate0Button.clicked();
908+ rotate90Button.clicked();
911+ rotate180Button.clicked();
914+ rotate270Button.clicked();
920+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
921+ waitUntilTransitionsEnd(rotationStates);
924+ function waitUntilAppDelegateIsFullyInit(spreadDelegate) {
925+ tryCompare(spreadDelegate, "orientationChangesEnabled", true);
927+ var appWindowWithShadow = findChild(spreadDelegate, "appWindowWithShadow");
928+ verify(appWindowWithShadow);
929+ tryCompare(appWindowWithShadow, "state", "keepSceneRotation");
931+ var appWindowStates = findInvisibleChild(appWindowWithShadow, "applicationWindowStateGroup");
932+ verify(appWindowStates);
933+ tryCompare(appWindowStates, "state", "surface");
936+ function waitUntilAppSurfaceShowsUp(appId) {
937+ var appWindow = findChild(shell, "appWindow_" + appId);
939+ var appWindowStates = findInvisibleChild(appWindow, "applicationWindowStateGroup");
940+ verify(appWindowStates);
941+ tryCompare(appWindowStates, "state", "surface");
944+ function waitUntilAppWindowCanRotate(appId) {
945+ var appWindow = findChild(shell, "appWindow_" + appId);
947+ tryCompare(appWindow, "orientationChangesEnabled", true);
950+ function waitUntilShellIsInOrientation(orientation) {
951+ tryCompare(shell, "orientation", orientation);
952+ var rotationStates = findInvisibleChild(orientedShell, "rotationStates");
953+ waitUntilTransitionsEnd(rotationStates);
956+ function performLeftEdgeSwipeToSwitchToDash() {
957+ var spreadView = findChild(shell, "spreadView");
958+ var swipeLength = spreadView.width * 0.7;
960+ var touchStartX = 1;
961+ var touchStartY = shell.height / 2;
963+ touchStartX, touchStartY,
964+ touchStartX + swipeLength, touchStartY);
966+ tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
969+ function performEdgeSwipeToSwitchToPreviousApp() {
970+ // swipe just enough to ensure an app switch action.
971+ // If we swipe too much we will trigger the spread mode
972+ // and we don't want that.
973+ var spreadView = findChild(shell, "spreadView");
976+ verify(ApplicationManager.count >= 2);
977+ var previousApp = ApplicationManager.get(1);
979+ var touchStartX = shell.width - 1;
980+ var touchStartY = shell.height / 2;
983+ if (applicationArguments.deviceName === "phone") {
984+ condition = function() {
985+ return spreadView.shiftedContentX > units.gu(2) &&
986+ spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width;
989+ condition = function() {
990+ return spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker1
991+ && spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2;
995+ touchDragUntil(shell,
996+ touchStartX, touchStartY,
1000+ // ensure the app switch animation has ended
1001+ tryCompare(spreadView, "shiftedContentX", 0);
1003+ compare(ApplicationManager.get(0).appId, previousApp.appId);
1006+ function performEdgeSwipeToShowAppSpread() {
1007+ var touchStartY = shell.height / 2;
1009+ shell.width - 1, touchStartY,
1012+ var spreadView = findChild(shell, "spreadView");
1013+ verify(spreadView);
1014+ tryCompare(spreadView, "phase", 2);
1015+ tryCompare(spreadView, "flicking", false);
1016+ tryCompare(spreadView, "moving", false);
1019+ function showPowerDialog() {
1020+ var dialogs = findChild(orientedShell, "dialogs");
1021+ var dialogsPrivate = findInvisibleChild(dialogs, "dialogsPrivate");
1022+ dialogsPrivate.showPowerDialog();
1025+ function verifyAppWindowWithinSpreadDelegateBoundaries(spreadDelegate) {
1026+ var appWindowWithShadow = findChild(spreadDelegate, "appWindowWithShadow");
1027+ verify(appWindowWithShadow);
1029+ var windowInDelegateCoords = appWindowWithShadow.mapToItem(spreadDelegate, 0, 0,
1030+ appWindowWithShadow.width, appWindowWithShadow.height);
1032+ compare(windowInDelegateCoords.x, 0);
1033+ compare(windowInDelegateCoords.y, 0);
1034+ compare(windowInDelegateCoords.width, spreadDelegate.width);
1035+ compare(windowInDelegateCoords.height, spreadDelegate.height);
1038+ function swipeAwayGreeter() {
1039+ var greeter = findChild(shell, "greeter");
1040+ tryCompare(greeter, "fullyShown", true);
1042+ var touchX = shell.width - (shell.edgeSize / 2);
1043+ var touchY = shell.height / 2;
1044+ touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY);
1046+ // wait until the animation has finished
1047+ tryCompare(greeter, "shown", false);
1048+ waitForRendering(greeter);
1051+ function showGreeter() {
1052+ LightDM.Greeter.showGreeter();
1053+ // wait until the animation has finished
1054+ var greeter = findChild(shell, "greeter");
1055+ tryCompare(greeter, "fullyShown", true);
1058+ function killApps() {
1059+ while (ApplicationManager.count > 1) {
1060+ var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
1061+ ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
1063+ compare(ApplicationManager.count, 1)
1066+ function loadShell(deviceName) {
1067+ applicationArguments.deviceName = deviceName;
1069+ // reload our test subject to get it in a fresh state once again
1070+ orientedShellLoader.active = true;
1072+ tryCompare(orientedShellLoader, "status", Loader.Ready);
1073+ removeTimeConstraintsFromDirectionalDragAreas(orientedShellLoader.item);
1075+ shell = findChild(orientedShell, "shell");
1077+ tryCompare(shell, "enabled", true); // enabled by greeter when ready
1079+ waitUntilShellIsInOrientation(root.physicalOrientation0);
1081+ waitForGreeterToStabilize();
1083+ swipeAwayGreeter();
1085+ var spreadRepeater = findChild(shell, "spreadRepeater");
1086+ if (spreadRepeater) {
1087+ spreadRepeaterConnections.target = spreadRepeater;
1091+ function waitForGreeterToStabilize() {
1092+ var greeter = findChild(shell, "greeter");
1095+ var loginList = findChild(greeter, "loginList");
1096+ // Only present in WideView
1098+ var userList = findChild(loginList, "userList");
1100+ tryCompare(userList, "movingInternally", false);
1104+ // expectedAngle is in orientedShell's coordinate system
1105+ function checkAppSurfaceOrientation(app, expectedAngle) {
1106+ var surface = app.session.surface;
1108+ console.warn("no surface");
1112+ if (!itemIsChildOfOrientedShell(surface)) {
1113+ console.warn("surface not a child of OrientedShell");
1117+ var topMargin = 0.;
1118+ if (!app.fullscreen) {
1119+ var appsDisplayLoader = findChild(shell, "applicationsDisplayLoader");
1120+ verify(appsDisplayLoader);
1121+ verify(appsDisplayLoader.item);
1122+ verify(appsDisplayLoader.item.maximizedAppTopMargin !== undefined);
1123+ topMargin = appsDisplayLoader.item.maximizedAppTopMargin;
1126+ var point = surface.mapToItem(orientedShell, 0, 0);
1128+ switch (expectedAngle) {
1130+ return point.x === 0. && point.y === topMargin;
1132+ return point.x === orientedShell.width - topMargin && point.y === 0;
1134+ return point.x === orientedShell.width && point.y === orientedShell.height - topMargin;
1136+ return point.x === topMargin && point.y === orientedShell.height;
1140+ function itemIsChildOfOrientedShell(item) {
1141+ var parent = item.parent;
1143+ while (parent && !found) {
1144+ found = parent === orientedShell;
1145+ parent = parent.parent;