Skip to content

Commit c7f949c

Browse files
authored
Added integration for Photo Editor app with new screen manager SDK (#48)
Added integration for Photo Editor app with new screen manager SDK
1 parent 6c24aaa commit c7f949c

37 files changed

+436
-206
lines changed

PhotoEditor/app/build.gradle

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/*
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License.
4-
*
54
*/
65

76
apply plugin: 'com.android.application'
@@ -33,14 +32,16 @@ android {
3332
}
3433

3534
dependencies {
35+
implementation microsoftDependencies.screenManager
36+
implementation microsoftDependencies.layouts
37+
implementation microsoftDependencies.fragmentsHandler
38+
3639
implementation kotlinDependencies.kotlinStdlib
3740
implementation androidxDependencies.appCompat
3841
implementation androidxDependencies.constraintLayout
3942
implementation androidxDependencies.ktxCore
4043
implementation androidxDependencies.ktxFragment
4144

42-
implementation microsoftDependencies.dualScreenLayout
43-
4445
testImplementation testDependencies.junit
4546
androidTestImplementation instrumentationTestDependencies.junit
4647
androidTestImplementation instrumentationTestDependencies.espressoCore

PhotoEditor/app/src/androidTest/java/com/microsoft/device/display/samples/photoeditor/PhotoEditorUITest.kt

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
16
package com.microsoft.device.display.samples.photoeditor
27

38
import android.content.Intent
@@ -18,8 +23,12 @@ import androidx.test.rule.ActivityTestRule
1823
import androidx.test.uiautomator.By
1924
import androidx.test.uiautomator.UiDevice
2025
import androidx.test.uiautomator.Until
21-
import com.microsoft.device.dualscreen.layout.ScreenHelper
26+
import com.microsoft.device.display.samples.photoeditor.utils.ScreenInfoListenerImpl
27+
import com.microsoft.device.dualscreen.ScreenManagerProvider
2228
import org.hamcrest.CoreMatchers.not
29+
import org.junit.After
30+
import org.junit.Assert
31+
import org.junit.Before
2332
import org.junit.Rule
2433
import org.junit.Test
2534
import org.junit.runner.RunWith
@@ -34,6 +43,21 @@ import org.hamcrest.CoreMatchers.`is` as iz
3443
class PhotoEditorUITest {
3544
@get:Rule
3645
val activityRule = ActivityTestRule(MainActivity::class.java)
46+
private var screenInfoListener = ScreenInfoListenerImpl()
47+
48+
@Before
49+
fun setup() {
50+
val screenManager = ScreenManagerProvider.getScreenManager()
51+
screenManager.addScreenInfoListener(screenInfoListener)
52+
}
53+
54+
@After
55+
fun tearDown() {
56+
val screenManager = ScreenManagerProvider.getScreenManager()
57+
screenManager.removeScreenInfoListener(screenInfoListener)
58+
screenInfoListener.resetScreenInfo()
59+
screenInfoListener.resetScreenInfoCounter()
60+
}
3761

3862
/**
3963
* Tests visibility of controls when app spanned vs. unspanned
@@ -53,7 +77,7 @@ class PhotoEditorUITest {
5377
onView(withId(R.id.warmth)).check(matches(withEffectiveVisibility(Visibility.INVISIBLE)))
5478

5579
spanFromLeft()
56-
assertThat(isSpanned(), iz(true))
80+
waitForScreenInfoAndAssert { Assert.assertTrue(isSpanned()) }
5781

5882
// Switched to dual-screen mode, so dropdown should not exist and all sliders should be visible
5983
onView(withId(R.id.controls)).check(doesNotExist())
@@ -100,7 +124,10 @@ class PhotoEditorUITest {
100124
device.wait(Until.hasObject(By.pkg(filesPackage).depth(0)), 3000) // timeout at 3 seconds
101125

102126
// Before import, drawable is equal to prev
103-
assertThat(prev, iz(activityRule.activity.findViewById<ImageFilterView>(R.id.image).drawable))
127+
assertThat(
128+
prev,
129+
iz(activityRule.activity.findViewById<ImageFilterView>(R.id.image).drawable)
130+
)
104131

105132
// Hardcoded to select most recently saved file in Files app - must be an image file
106133
device.swipe(1550, 1230, 1550, 1230, 100)
@@ -207,8 +234,13 @@ class PhotoEditorUITest {
207234
device.swipe(rightX, bottomY, rightX, middleY, closeSteps)
208235
}
209236

237+
private fun waitForScreenInfoAndAssert(assert: () -> Unit) {
238+
screenInfoListener.waitForScreenInfoChanges()
239+
assert()
240+
screenInfoListener.resetScreenInfo()
241+
}
242+
210243
private fun isSpanned(): Boolean {
211-
onIdle() // wait until layout changes have been fully processed before checking
212-
return ScreenHelper.isDualMode(activityRule.activity)
244+
return screenInfoListener.screenInfo?.isDualMode() == true
213245
}
214246
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
package com.microsoft.device.display.samples.photoeditor.utils
7+
8+
import com.microsoft.device.dualscreen.ScreenInfo
9+
import com.microsoft.device.dualscreen.ScreenInfoListener
10+
import java.util.concurrent.CountDownLatch
11+
import java.util.concurrent.TimeUnit
12+
13+
/**
14+
* Simple implementation for [ScreenInfoListener] that saves internally the last screen info data.
15+
*/
16+
class ScreenInfoListenerImpl : ScreenInfoListener {
17+
private var _screenInfo: ScreenInfo? = null
18+
val screenInfo: ScreenInfo?
19+
get() = _screenInfo
20+
private var screenInfoLatch: CountDownLatch? = null
21+
22+
override fun onScreenInfoChanged(screenInfo: ScreenInfo) {
23+
_screenInfo = screenInfo
24+
screenInfoLatch?.countDown()
25+
}
26+
27+
/**
28+
* Resets the last screen info to [null]
29+
*/
30+
fun resetScreenInfo() {
31+
_screenInfo = null
32+
}
33+
34+
/**
35+
* Resets screen info counter when waiting for a screen changes to happen before calling
36+
* [waitForScreenInfoChanges].
37+
*/
38+
fun resetScreenInfoCounter() {
39+
screenInfoLatch = CountDownLatch(1)
40+
}
41+
42+
/**
43+
* Blocks and waits for the next screen info changes to happen.
44+
* @return {@code true} if the screen info changed before the timeout count reached zero and
45+
* {@code false} if the waiting time elapsed before the changes happened.
46+
*/
47+
fun waitForScreenInfoChanges(): Boolean {
48+
return try {
49+
val result = screenInfoLatch?.await(10, TimeUnit.SECONDS) ?: false
50+
result
51+
} catch (e: InterruptedException) {
52+
false
53+
}
54+
}
55+
}

PhotoEditor/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<?xml version="1.0" encoding="utf-8"?><!--
22
~ Copyright (c) Microsoft Corporation. All rights reserved.
33
~ Licensed under the MIT License.
4-
~
54
-->
65

76
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
87
package="com.microsoft.device.display.samples.photoeditor">
98

109
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
1110
<application
11+
android:name=".PhotoEditorApplication"
1212
android:allowBackup="true"
1313
android:icon="@mipmap/ic_launcher"
1414
android:label="@string/app_name"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
package com.microsoft.device.display.samples.photoeditor
7+
8+
import androidx.lifecycle.LifecycleOwner
9+
import androidx.lifecycle.LiveData
10+
import androidx.lifecycle.Observer
11+
12+
fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
13+
class RemovableObserver : Observer<T> {
14+
override fun onChanged(t: T?) {
15+
observer.onChanged(t)
16+
removeObserver(this)
17+
}
18+
}
19+
20+
observe(lifecycleOwner, RemovableObserver())
21+
}

0 commit comments

Comments
 (0)