diff --git a/SourceEditor/app/build.gradle b/SourceEditor/app/build.gradle index 775c9fc..9f760d1 100644 --- a/SourceEditor/app/build.gradle +++ b/SourceEditor/app/build.gradle @@ -24,6 +24,10 @@ android { } dependencies { + implementation microsoftDependencies.screenManager + implementation microsoftDependencies.layouts + implementation microsoftDependencies.fragmentsHandler + implementation googleDependencies.material implementation kotlinDependencies.kotlinStdlib @@ -33,8 +37,6 @@ dependencies { implementation androidxDependencies.ktxCore implementation androidxDependencies.ktxFragment - implementation microsoftDependencies.dualScreenLayout - testImplementation testDependencies.junit androidTestImplementation instrumentationTestDependencies.junit diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/ExampleInstrumentedTest.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/ExampleInstrumentedTest.kt deleted file mode 100644 index 2b0b5e4..0000000 --- a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - * - */ - -package com.microsoft.device.display.samples.sourceeditor - -import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * @see [Testing documentation](http://d.android.com/tools/testing) - */ -@RunWith(AndroidJUnit4ClassRunner::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.microsoft.device.display.samples.sourceeditor", appContext.packageName) - } -} \ No newline at end of file diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/InteractiveTest.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/InteractiveTest.kt deleted file mode 100644 index 85e6ba9..0000000 --- a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/InteractiveTest.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. - * - */ - -package com.microsoft.device.display.samples.sourceeditor - -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.replaceText -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches -import androidx.test.espresso.web.sugar.Web.onWebView -import androidx.test.espresso.web.webdriver.DriverAtoms.findElement -import androidx.test.espresso.web.webdriver.DriverAtoms.getText -import androidx.test.espresso.web.webdriver.Locator -import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.ActivityTestRule -import androidx.test.uiautomator.UiDevice -import com.microsoft.device.dualscreen.layout.ScreenHelper -import org.hamcrest.core.StringContains.containsString -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * @see [Testing documentation](http://d.android.com/tools/testing) - */ -@RunWith(AndroidJUnit4ClassRunner::class) -class InteractiveTest { - @get:Rule - val activityRule = ActivityTestRule(MainActivity::class.java) - - @Test - fun useAppContext() { // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.microsoft.device.display.samples.sourceeditor", appContext.packageName) - } - - @Test - fun textTransferTest() { - onView(withId(R.id.btn_switch_to_preview)).perform(click()) - onWebView() - .withElement(findElement(Locator.TAG_NAME, "h1")) - .check(webMatches(getText(), containsString("Testing in a browser"))) - } - - @Test - fun textPreservedOnSpanTest() { - onView(withId(R.id.textinput_code)).perform(replaceText("

$testString

")) - spanFromLeft() - assert(isSpanned()) - onWebView() - .withElement(findElement(Locator.TAG_NAME, "h1")) - .check(webMatches(getText(), containsString(testString))) - } - - @Test - fun testSpanning() { - spanFromLeft() - assert(isSpanned()) - unspanToRight() - assertFalse(isSpanned()) - spanFromRight() - assert(isSpanned()) - unspanToLeft() - assertFalse(isSpanned()) - switchToRight() - switchToLeft() - } - - companion object { - // testing device - val device: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - - const val leftX: Int = 675 // middle of left screen - const val rightX: Int = 2109 // middle of right screen - const val leftMiddleX: Int = 1340 // left of hinge area - const val rightMiddleX: Int = 1434 // right of hinge area - const val bottomY: Int = 1780 // bottom of screen - const val middleY: Int = 900 // middle of screen - const val spanSteps: Int = 400 // spanning/unspanning swipe - const val switchSteps: Int = 600 // switch from one screen to the other - - const val testString = "Testing in a different browser" - } - - private fun spanFromLeft() { - device.swipe(leftX, bottomY, leftMiddleX, middleY, spanSteps) - } - - private fun unspanToLeft() { - device.swipe(rightX, bottomY, leftX, middleY, spanSteps) - } - - private fun spanFromRight() { - device.swipe(rightX, bottomY, rightMiddleX, middleY, spanSteps) - } - - private fun unspanToRight() { - device.swipe(leftX, bottomY, rightX, middleY, spanSteps) - } - - private fun switchToLeft() { - device.swipe(rightX, bottomY, leftX, middleY, switchSteps) - } - - private fun switchToRight() { - device.swipe(leftX, bottomY, rightX, middleY, switchSteps) - } - - private fun isSpanned(): Boolean { - return ScreenHelper.isDualMode(activityRule.activity) - } -} diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/PreviewTest.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/PreviewTest.kt new file mode 100644 index 0000000..4c3abc8 --- /dev/null +++ b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/PreviewTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * + */ + +package com.microsoft.device.display.samples.sourceeditor + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches +import androidx.test.espresso.web.sugar.Web.onWebView +import androidx.test.espresso.web.webdriver.DriverAtoms.findElement +import androidx.test.espresso.web.webdriver.DriverAtoms.getText +import androidx.test.espresso.web.webdriver.Locator +import androidx.test.filters.MediumTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.rule.ActivityTestRule +import com.microsoft.device.display.samples.sourceeditor.utils.ScreenInfoListenerImpl +import com.microsoft.device.display.samples.sourceeditor.utils.moveToLeft +import com.microsoft.device.display.samples.sourceeditor.utils.moveToRight +import com.microsoft.device.display.samples.sourceeditor.utils.spanFromLeft +import com.microsoft.device.display.samples.sourceeditor.utils.spanFromRight +import com.microsoft.device.display.samples.sourceeditor.utils.switchFromSingleToDualScreen +import com.microsoft.device.display.samples.sourceeditor.utils.unspanToLeft +import com.microsoft.device.display.samples.sourceeditor.utils.unspanToRight +import com.microsoft.device.dualscreen.ScreenManagerProvider +import org.hamcrest.core.StringContains.containsString +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@MediumTest +@RunWith(AndroidJUnit4ClassRunner::class) +class PreviewTest { + @get:Rule + val activityRule = ActivityTestRule(MainActivity::class.java, false, false) + private var screenInfoListener = ScreenInfoListenerImpl() + private val testString = "Testing in a different browser" + + @Before + fun setup() { + val screenManager = ScreenManagerProvider.getScreenManager() + screenManager.addScreenInfoListener(screenInfoListener) + activityRule.launchActivity(null) + } + + @After + fun tearDown() { + val screenManager = ScreenManagerProvider.getScreenManager() + screenManager.removeScreenInfoListener(screenInfoListener) + screenInfoListener.resetScreenInfo() + screenInfoListener.resetScreenInfoCounter() + activityRule.finishActivity() + } + + @Test + fun previewTextInSingleScreenMode() { + screenInfoListener.waitForScreenInfoChanges() + + onView(withId(R.id.btn_switch_to_preview)).perform(click()) + onWebView() + .withElement(findElement(Locator.TAG_NAME, "h1")) + .check(webMatches(getText(), containsString("Testing in a browser"))) + } + + @Test + fun previewTextInDualScreenMode() { + screenInfoListener.waitForScreenInfoChanges() + + onView(withId(R.id.textinput_code)).perform(replaceText("

$testString

")) + switchFromSingleToDualScreen() + assert(isSpanned()) + onWebView() + .withElement(findElement(Locator.TAG_NAME, "h1")) + .check(webMatches(getText(), containsString(testString))) + } + + @Test + fun testSpanning() { + screenInfoListener.waitForScreenInfoChanges() + screenInfoListener.resetScreenInfo() + + spanFromLeft() + waitForScreenInfoAndAssert { assertTrue(isSpanned()) } + unspanToRight() + waitForScreenInfoAndAssert { assertFalse(isSpanned()) } + spanFromRight() + waitForScreenInfoAndAssert { assertTrue(isSpanned()) } + unspanToLeft() + waitForScreenInfoAndAssert { assertFalse(isSpanned()) } + moveToRight() + moveToLeft() + } + + private fun waitForScreenInfoAndAssert(assert: () -> Unit) { + screenInfoListener.waitForScreenInfoChanges() + assert() + screenInfoListener.resetScreenInfo() + } + + private fun isSpanned(): Boolean { + return screenInfoListener.screenInfo?.isDualMode() == true + } +} diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/SourceEditorInDualScreenTest.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/SourceEditorInDualScreenTest.kt new file mode 100644 index 0000000..9db80de --- /dev/null +++ b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/SourceEditorInDualScreenTest.kt @@ -0,0 +1,62 @@ +package com.microsoft.device.display.samples.sourceeditor + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.filters.MediumTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.rule.ActivityTestRule +import com.microsoft.device.display.samples.sourceeditor.utils.childOf +import com.microsoft.device.display.samples.sourceeditor.utils.setOrientationLeft +import com.microsoft.device.display.samples.sourceeditor.utils.setOrientationRight +import com.microsoft.device.display.samples.sourceeditor.utils.switchFromSingleToDualScreen +import com.microsoft.device.display.samples.sourceeditor.utils.unfreezeRotation +import org.hamcrest.Matchers +import org.junit.After +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +@MediumTest +class SourceEditorInDualScreenTest { + @get:Rule + val activityRule = ActivityTestRule(MainActivity::class.java) + + @After + fun tearDown() { + unfreezeRotation() + } + + @Test + fun previewWithoutOrientation() { + switchFromSingleToDualScreen() + testPreview() + } + + @Test + fun previewWithOrientationLeft() { + switchFromSingleToDualScreen() + setOrientationLeft() + testPreview() + } + + @Test + fun previewWithOrientationRight() { + switchFromSingleToDualScreen() + setOrientationRight() + testPreview() + } + + private fun testPreview() { + onView( + childOf(withId(R.id.first_container_id), withId(R.id.btn_switch_to_editor)) + ).check(matches(Matchers.not(ViewMatchers.isDisplayed()))) + onView( + childOf(withId(R.id.first_container_id), withId(R.id.btn_switch_to_preview)) + ).check(matches(Matchers.not(ViewMatchers.isDisplayed()))) + onView(withId(R.id.textinput_code)).check(matches(ViewMatchers.isDisplayed())) + onView(withId(R.id.webview_preview)).check(matches(ViewMatchers.isDisplayed())) + } +} \ No newline at end of file diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/SourceEditorInSingleScreenTest.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/SourceEditorInSingleScreenTest.kt new file mode 100644 index 0000000..78a420d --- /dev/null +++ b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/SourceEditorInSingleScreenTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * + */ + +package com.microsoft.device.display.samples.sourceeditor + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.filters.MediumTest +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.rule.ActivityTestRule +import com.microsoft.device.display.samples.sourceeditor.utils.SimpleFragmentBackStackListener +import com.microsoft.device.display.samples.sourceeditor.utils.forceClick +import com.microsoft.device.display.samples.sourceeditor.utils.setOrientationLeft +import com.microsoft.device.display.samples.sourceeditor.utils.setOrientationRight +import com.microsoft.device.display.samples.sourceeditor.utils.unfreezeRotation +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +@MediumTest +class SourceEditorInSingleScreenTest { + @get:Rule + val activityRule = ActivityTestRule(MainActivity::class.java) + private var backStackChangeListener = SimpleFragmentBackStackListener() + + @Before + fun setup() { + activityRule.activity.supportFragmentManager.addOnBackStackChangedListener(backStackChangeListener) + } + + @After + fun tearDown() { + unfreezeRotation() + } + + @Test + fun previewWithoutOrientation() { + onView(withId(R.id.btn_switch_to_editor)).check(matches(isDisplayed())) + onView(withId(R.id.btn_switch_to_preview)).check(matches(isDisplayed())) + onView(withId(R.id.textinput_code)).check(matches(isDisplayed())) + + backStackChangeListener.reset() + onView(withId(R.id.btn_switch_to_preview)).perform(forceClick()) + backStackChangeListener.waitForFragmentToBeAdded() + onView(withId(R.id.webview_preview)).check(matches(isDisplayed())) + } + + @Test + fun previewWithOrientationLeft() { + setOrientationLeft() + previewWithoutOrientation() + } + + @Test + fun previewWithOrientationRight() { + setOrientationRight() + previewWithoutOrientation() + } +} \ No newline at end of file diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/ChildOfMatcher.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/ChildOfMatcher.kt new file mode 100644 index 0000000..ace8fa7 --- /dev/null +++ b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/ChildOfMatcher.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * + */ + +package com.microsoft.device.display.samples.sourceeditor.utils + +import android.view.View +import android.view.ViewGroup +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.TypeSafeMatcher + +/** + * A matcher that matches [View]s child's in parent matcher + */ +class ChildOfMatcher(private val parentMatcher: Matcher, private val childMatcher: Matcher) : TypeSafeMatcher() { + override fun describeTo(description: Description) { + parentMatcher.describeTo(description) + childMatcher.describeTo(description) + } + + public override fun matchesSafely(view: View): Boolean { + return hasParentThatMatches(view) && childMatcher.matches(view) + } + + private fun hasParentThatMatches(view: View): Boolean { + val parent = (view.parent ?: return false) as? ViewGroup ?: return parentMatcher.matches(view.parent) + return parentMatcher.matches(parent) || hasParentThatMatches(parent) + } +} \ No newline at end of file diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/ForceClick.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/ForceClick.kt new file mode 100644 index 0000000..f826ed0 --- /dev/null +++ b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/ForceClick.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * + */ + +package com.microsoft.device.display.samples.sourceeditor.utils + +import android.view.View +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.matcher.ViewMatchers.isClickable +import androidx.test.espresso.matcher.ViewMatchers.isEnabled +import org.hamcrest.Matcher +import org.hamcrest.Matchers.allOf + +/** + * Click action for a view without to check the coordinates for the view on the screen. when device is rotated. + */ +class ForceClick : ViewAction { + override fun getConstraints(): Matcher { + return allOf(isClickable(), isEnabled()) + } + + override fun getDescription(): String { + return "force click" + } + + override fun perform(uiController: UiController?, view: View?) { + view?.performClick() + uiController?.loopMainThreadForAtLeast(500) + } +} \ No newline at end of file diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/ScreenInfoListenerImpl.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/ScreenInfoListenerImpl.kt new file mode 100644 index 0000000..aeec847 --- /dev/null +++ b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/ScreenInfoListenerImpl.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +package com.microsoft.device.display.samples.sourceeditor.utils + +import com.microsoft.device.dualscreen.ScreenInfo +import com.microsoft.device.dualscreen.ScreenInfoListener +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +/** + * Simple implementation for [ScreenInfoListener] that saves internally the last screen info data. + */ +class ScreenInfoListenerImpl : ScreenInfoListener { + private var _screenInfo: ScreenInfo? = null + val screenInfo: ScreenInfo? + get() = _screenInfo + private var screenInfoLatch: CountDownLatch? = null + + override fun onScreenInfoChanged(screenInfo: ScreenInfo) { + _screenInfo = screenInfo + screenInfoLatch?.countDown() + } + + /** + * Resets the last screen info to [null] + */ + fun resetScreenInfo() { + _screenInfo = null + } + + /** + * Resets screen info counter when waiting for a screen changes to happen before calling + * [waitForScreenInfoChanges]. + */ + fun resetScreenInfoCounter() { + screenInfoLatch = CountDownLatch(1) + } + + /** + * Blocks and waits for the next screen info changes to happen. + * @return {@code true} if the screen info changed before the timeout count reached zero and + * {@code false} if the waiting time elapsed before the changes happened. + */ + fun waitForScreenInfoChanges(): Boolean { + return try { + val result = screenInfoLatch?.await(10, TimeUnit.SECONDS) ?: false + result + } catch (e: InterruptedException) { + false + } + } +} \ No newline at end of file diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/SimpleFragmentBackStackListener.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/SimpleFragmentBackStackListener.kt new file mode 100644 index 0000000..76294bd --- /dev/null +++ b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/SimpleFragmentBackStackListener.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * + */ + +package com.microsoft.device.display.samples.sourceeditor.utils + +import androidx.fragment.app.FragmentManager +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +/** + * Simple implementation for [FragmentManager.OnBackStackChangedListener] t + * hat decrease the internal [CountDownLatch] when a fragment is added to the back stack. + */ +class SimpleFragmentBackStackListener : FragmentManager.OnBackStackChangedListener { + private var fragmentCountDownLatch: CountDownLatch? = null + + override fun onBackStackChanged() { + fragmentCountDownLatch?.countDown() + } + + fun reset() { + fragmentCountDownLatch = CountDownLatch(1) + } + + fun waitForFragmentToBeAdded(): Boolean { + return try { + val result = fragmentCountDownLatch?.await(10, TimeUnit.SECONDS) ?: false + result + } catch (e: InterruptedException) { + false + } + } +} \ No newline at end of file diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/SurfaceDuoUtils.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/SurfaceDuoUtils.kt new file mode 100644 index 0000000..c3b058c --- /dev/null +++ b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/SurfaceDuoUtils.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * + */ + +package com.microsoft.device.display.samples.sourceeditor.utils + +import android.view.Surface +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice + +// testing device +private val device: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + +private const val leftX: Int = 675 // middle of left screen +private const val rightX: Int = 2109 // middle of right screen +private const val leftMiddleX: Int = 1340 // left of hinge area +private const val rightMiddleX: Int = 1434 // right of hinge area +private const val bottomY: Int = 1780 // bottom of screen +private const val middleY: Int = 900 // middle of screen +private const val spanSteps: Int = 400 // spanning/unspanning swipe +private const val switchSteps: Int = 600 // switch from one screen to the other + +fun spanFromLeft() { + device.swipe(leftX, bottomY, leftMiddleX, middleY, spanSteps) +} + +fun unspanToLeft() { + device.swipe(rightX, bottomY, leftX, middleY, spanSteps) +} + +fun spanFromRight() { + device.swipe(rightX, bottomY, rightMiddleX, middleY, spanSteps) +} + +fun unspanToRight() { + device.swipe(leftX, bottomY, rightX, middleY, spanSteps) +} + +fun moveToLeft() { + device.swipe(rightX, bottomY, leftX, middleY, switchSteps) +} + +fun moveToRight() { + device.swipe(leftX, bottomY, rightX, middleY, switchSteps) +} + +/** + * Switches application from single screen mode to dual screen mode + */ +fun switchFromSingleToDualScreen() { + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + when (device.displayRotation) { + Surface.ROTATION_0, Surface.ROTATION_180 -> device.swipe(675, 1780, 1350, 900, 400) + Surface.ROTATION_270 -> device.swipe(1780, 675, 900, 1350, 400) + Surface.ROTATION_90 -> device.swipe(1780, 2109, 900, 1400, 400) + } +} + +/** + * Switches application from dual screen mode to single screen + */ +fun switchFromDualToSingleScreen() { + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + when (device.displayRotation) { + Surface.ROTATION_0, Surface.ROTATION_180 -> device.swipe(1500, 1780, 650, 900, 400) + Surface.ROTATION_270 -> device.swipe(1780, 1500, 900, 650, 400) + Surface.ROTATION_90 -> device.swipe(1780, 1250, 900, 1500, 400) + } +} + +/** + * Re-enables the sensors and un-freezes the device rotation allowing its contents + * to rotate with the device physical rotation. During a test execution, it is best to + * keep the device frozen in a specific orientation until the test case execution has completed. + */ +fun unfreezeRotation() { + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + device.unfreezeRotation() +} + +/** + * Simulates orienting the device to the left and also freezes rotation + * by disabling the sensors. + */ +fun setOrientationLeft() { + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + device.setOrientationLeft() +} + +/** + * Simulates orienting the device into its natural orientation and also freezes rotation + * by disabling the sensors. + */ +fun setOrientationNatural() { + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + device.setOrientationNatural() +} + +/** + * Simulates orienting the device to the right and also freezes rotation + * by disabling the sensors. + */ +fun setOrientationRight() { + val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + device.setOrientationRight() +} \ No newline at end of file diff --git a/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/TestUtils.kt b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/TestUtils.kt new file mode 100644 index 0000000..ffc7bd8 --- /dev/null +++ b/SourceEditor/app/src/androidTest/java/com/microsoft/device/display/samples/sourceeditor/utils/TestUtils.kt @@ -0,0 +1,21 @@ +package com.microsoft.device.display.samples.sourceeditor.utils + +import android.view.View +import androidx.test.espresso.ViewAction +import org.hamcrest.Matcher + +/** + * Returns A matcher that matches [View]s child's in parent matcher + * + * @return the matcher + */ +fun childOf(parentMatcher: Matcher, childMatcher: Matcher): Matcher = ChildOfMatcher(parentMatcher, childMatcher) + +/** + * Returns an action that clicks the view without to check the coordinates on the screen. + * Seems that ViewActions.click() finds coordinates of the view on the screen, and then performs the tap on the coordinates. + * Seems tha changing the screen rotations affects these coordinates and ViewActions.click() throws exceptions. + * + * @return the force click action + */ +fun forceClick(): ViewAction = ForceClick() \ No newline at end of file diff --git a/SourceEditor/app/src/main/AndroidManifest.xml b/SourceEditor/app/src/main/AndroidManifest.xml index 398fe64..20ec507 100644 --- a/SourceEditor/app/src/main/AndroidManifest.xml +++ b/SourceEditor/app/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ @@ -105,13 +120,13 @@ class CodeFragment : Fragment() { } // single screen vs. dual screen logic - private fun handleSpannedModeSelection(view: View) { + private fun handleSpannedModeSelection(view: View, screenInfo: ScreenInfo) { activity?.let { codeLayout = view.findViewById(R.id.code_layout) previewBtn = view.findViewById(R.id.btn_switch_to_preview) buttonToolbar = view.findViewById(R.id.button_toolbar) - if (ScreenHelper.isDualMode(it)) { + if (screenInfo.isDualMode()) { initializeDualScreen() } else { initializeSingleScreen() @@ -153,10 +168,10 @@ class CodeFragment : Fragment() { private fun startPreviewFragment() { parentFragmentManager.beginTransaction() .replace( - R.id.single_screen_container_id, + R.id.first_container_id, PreviewFragment(), null - ).addToBackStack(null) + ).addToBackStack("PreviewFragment") .commit() } diff --git a/SourceEditor/app/src/main/java/com/microsoft/device/display/samples/sourceeditor/PreviewFragment.kt b/SourceEditor/app/src/main/java/com/microsoft/device/display/samples/sourceeditor/PreviewFragment.kt index d1a0719..eb71e6a 100644 --- a/SourceEditor/app/src/main/java/com/microsoft/device/display/samples/sourceeditor/PreviewFragment.kt +++ b/SourceEditor/app/src/main/java/com/microsoft/device/display/samples/sourceeditor/PreviewFragment.kt @@ -17,18 +17,18 @@ import android.webkit.WebView import android.widget.Button import android.widget.LinearLayout import android.widget.ScrollView - import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider - import com.microsoft.device.display.samples.sourceeditor.includes.DragHandler import com.microsoft.device.display.samples.sourceeditor.viewmodel.ScrollViewModel import com.microsoft.device.display.samples.sourceeditor.viewmodel.WebViewModel -import com.microsoft.device.dualscreen.layout.ScreenHelper +import com.microsoft.device.dualscreen.ScreenInfo +import com.microsoft.device.dualscreen.ScreenInfoListener +import com.microsoft.device.dualscreen.ScreenManagerProvider /* Fragment that defines functionality for the source code previewer */ -class PreviewFragment : Fragment() { +class PreviewFragment : Fragment(), ScreenInfoListener { private lateinit var buttonToolbar: LinearLayout private lateinit var scrollView: ScrollView @@ -61,13 +61,25 @@ class PreviewFragment : Fragment() { val str: String? = (webVM.getText().value) webView.loadData(str, Defines.HTML_TYPE, Defines.ENCODING) - - handleSpannedModeSelection(view, webView) } return view } + override fun onScreenInfoChanged(screenInfo: ScreenInfo) { + handleSpannedModeSelection(requireView(), webView, screenInfo) + } + + override fun onResume() { + super.onResume() + ScreenManagerProvider.getScreenManager().addScreenInfoListener(this) + } + + override fun onPause() { + super.onPause() + ScreenManagerProvider.getScreenManager().removeScreenInfoListener(this) + } + // mirror scrolling logic private fun handleScrolling(observing: Boolean, int: Int) { if (!rangeFound) { @@ -83,7 +95,7 @@ class PreviewFragment : Fragment() { } // single screen vs. dual screen logic - private fun handleSpannedModeSelection(view: View, webView: WebView) { + private fun handleSpannedModeSelection(view: View, webView: WebView, screenInfo: ScreenInfo) { activity?.let { activity -> editorBtn = view.findViewById(R.id.btn_switch_to_editor) buttonToolbar = view.findViewById(R.id.button_toolbar) @@ -93,7 +105,7 @@ class PreviewFragment : Fragment() { webView.loadData(str, Defines.HTML_TYPE, Defines.ENCODING) }) - if (ScreenHelper.isDualMode(activity)) { + if (screenInfo.isDualMode()) { initializeDualScreen(view) } else { initializeSingleScreen() @@ -136,10 +148,10 @@ class PreviewFragment : Fragment() { private fun startCodeFragment() { parentFragmentManager.beginTransaction() .replace( - R.id.single_screen_container_id, + R.id.first_container_id, CodeFragment(), null - ).addToBackStack(null) + ).addToBackStack("CodeFragment") .commit() } diff --git a/SourceEditor/app/src/main/java/com/microsoft/device/display/samples/sourceeditor/SourceEditorApplication.kt b/SourceEditor/app/src/main/java/com/microsoft/device/display/samples/sourceeditor/SourceEditorApplication.kt new file mode 100644 index 0000000..9658682 --- /dev/null +++ b/SourceEditor/app/src/main/java/com/microsoft/device/display/samples/sourceeditor/SourceEditorApplication.kt @@ -0,0 +1,13 @@ +package com.microsoft.device.display.samples.sourceeditor + +import android.app.Application +import com.microsoft.device.dualscreen.ScreenManagerProvider +import com.microsoft.device.dualscreen.fragmentshandler.FragmentManagerStateHandler + +class SourceEditorApplication : Application() { + override fun onCreate() { + super.onCreate() + ScreenManagerProvider.init(this) + FragmentManagerStateHandler.init(this) + } +} \ No newline at end of file diff --git a/SourceEditor/app/src/main/res/layout/activity_main.xml b/SourceEditor/app/src/main/res/layout/activity_main.xml index 51a5c53..b5d754c 100644 --- a/SourceEditor/app/src/main/res/layout/activity_main.xml +++ b/SourceEditor/app/src/main/res/layout/activity_main.xml @@ -5,7 +5,7 @@ ~ --> -