Skip to content

Commit

Permalink
Merge pull request #109 from remotv/develop
Browse files Browse the repository at this point in the history
Develop -> Master
  • Loading branch information
SkyeOfBreeze authored May 2, 2020
2 parents 64e940d + 7ce1e71 commit 1aadece
Show file tree
Hide file tree
Showing 23 changed files with 699 additions and 26 deletions.
51 changes: 51 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Handle line endings automatically for files detected as text
# and leave all files detected as binary untouched.
* text=auto

#
# The above will handle all files NOT found below
#
# These files are text and should be normalized (Convert crlf => lf)
*.md text
*.adoc text
*.textile text
*.mustache text
*.csv text
*.tab text
*.tsv text
*.css text
*.df text
*.htm text
*.html text
*.java text
*.kt text
*.js text
*.json text
*.jsp text
*.jspf text
*.properties text
*.sh text
*.sql text
*.svg text
*.tld text
*.txt text
*.xml text
*.sql text

#force these files to unix
*.sh text eol=lf
gradlew text eol=lf

# These files are binary and should be left untouched
# (binary is a macro for -text -diff)
*.class binary
*.dll binary
*.ear binary
*.gif binary
*.ico binary
*.jar binary
*.jpg binary
*.jpeg binary
*.png binary
*.so binary
*.war binary
29 changes: 29 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Android Instrumented Tests

on:
push:
tags:
- 'v*.*.*'

jobs:
test:
runs-on: macOS-latest
strategy:
matrix:
api-level: [21, 23, 26, 29]
target: [default]
arch: [x86]
steps:
- name: checkout
uses: actions/checkout@v2

- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
profile: Nexus 6
script: ./gradlew connectedCheck
env:
TEST_KEY: ${{ secrets.TEST_KEY }}
53 changes: 48 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

def getProps(String propName) {
def propsFile = rootProject.file('local.properties')
if (propsFile.exists()) {
def props = new Properties()
props.load(new FileInputStream(propsFile))
return props[propName]
} else {
return "";
}
}

android {
compileSdkVersion 28

Expand All @@ -25,11 +36,29 @@ android {
applicationId "tv.remo.android.controller"
minSdkVersion 16
targetSdkVersion 28
versionCode 14
versionName "0.16.1"
versionCode 15
versionName "0.17.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

useLibrary 'android.test.runner'

useLibrary 'android.test.base'

buildTypes {
debug{
def testKey = System.getenv("TEST_KEY")
if(testKey == null)
testKey = getProps('TEST_KEY')
if(testKey == null)
testKey = ""

buildConfigField "String", "robot_test_key", "\"$testKey\""

minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
if(System.getenv("KEY_ALIAS_RELEASE")){
signingConfig signingConfigs.release
Expand All @@ -43,13 +72,27 @@ android {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.multidex:multidex:2.0.1'
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.3.0-alpha02'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0-alpha02'
implementation project(':sdk')
implementation project(':licensehelper')
implementation project(':settingsutil')

//Test sdks
testImplementation 'junit:junit:4.12'

// Core library
androidTestImplementation 'androidx.test:core:1.3.0-alpha05'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'

// AndroidJUnitRunner and JUnit Rules
androidTestImplementation 'androidx.test:runner:1.3.0-alpha05'
androidTestImplementation 'androidx.test:rules:1.3.0-alpha05'

androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0-alpha05'
def mockito_version = "2.23.4"
testImplementation "org.mockito:mockito-core:$mockito_version"
androidTestImplementation "org.mockito:mockito-android:$mockito_version"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package tv.remo.android.controller

import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ServiceTestRule
import org.btelman.controlsdk.enums.Operation
import org.btelman.controlsdk.interfaces.ControlSdkServiceWrapper
import org.btelman.controlsdk.models.ComponentHolder
import org.btelman.controlsdk.services.ControlSDKService
import org.btelman.controlsdk.services.ControlSDKServiceConnection
import org.btelman.controlsdk.services.observeAutoCreate
import org.btelman.controlsdk.streaming.factories.VideoRetrieverFactory
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import tv.remo.android.controller.sdk.RemoSettingsUtil
import tv.remo.android.controller.sdk.components.StatusBroadcasterComponent
import tv.remo.android.controller.sdk.components.video.RemoVideoComponent
import tv.remo.android.controller.sdk.utils.ComponentBuilderUtil
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit


/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class DemoBotAndroidTest {
@get:Rule
val serviceRule = ServiceTestRule()
private val listenerControllerList = ArrayList<ComponentHolder<*>>()
private val arrayList = ArrayList<ComponentHolder<*>>()

private lateinit var controlSDKServiceApi: ControlSdkServiceWrapper
private val lifecycleOwner: LifecycleOwner = let{
val owner: LifecycleOwner =
mock(LifecycleOwner::class.java)
val lifecycle = LifecycleRegistry(owner)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
`when`(owner.lifecycle).thenReturn(lifecycle)
return@let owner
}
val appContext = InstrumentationRegistry.getInstrumentation().targetContext

@Test
fun testAndroidStream() {
serviceRule.startService(
Intent(appContext, ControlSDKService::class.java)
)
val handler = Handler(Looper.getMainLooper())
RemoSettingsUtil.with(appContext){
it.apiKey.savePref(BuildConfig.robot_test_key)
it.channelId.savePref("API${Build.VERSION.SDK_INT}")
it.cameraEnabled.savePref(true)
it.microphoneEnabled.savePref(false)
it.cameraResolution.savePref("640x480")
it.ffmpegInputOptions.savePref("-f image2pipe -codec:v mjpeg -i -")
}

controlSDKServiceApi = ControlSDKServiceConnection.getNewInstance(appContext)
val serviceStatusLatch = CountDownLatch(1)
handler.postDelayed({
controlSDKServiceApi.getServiceStateObserver().observeAutoCreate(lifecycleOwner){ serviceStatus ->
if(serviceStatus == Operation.OK)
serviceStatusLatch.countDown()
}
controlSDKServiceApi.getServiceBoundObserver().observeAutoCreate(lifecycleOwner){ connected ->
handleListenerAddOrRemove(connected)
if(connected == Operation.OK){
changeStreamState(Operation.OK)
}
}
controlSDKServiceApi.connectToService()
createComponentHolders()
}, 1000)
serviceStatusLatch.await(1, TimeUnit.MINUTES) //probably too long, but after this it is long dead
Thread.sleep(5*60000) //5 minutes
val serviceStatusCloseLatch = CountDownLatch(1)
handler.post {
controlSDKServiceApi.getServiceStateObserver()
.observeAutoCreate(lifecycleOwner) { serviceStatus ->
if (serviceStatus == Operation.OK)
serviceStatusCloseLatch.countDown()
}
changeStreamState(Operation.NOT_OK)
}
serviceStatusCloseLatch.await(1, TimeUnit.MINUTES)
}

private fun createComponentHolders() {
RemoSettingsUtil.with(appContext){ settings ->
arrayList.add(ComponentBuilderUtil.createSocketComponent(settings))
arrayList.addAll(ComponentBuilderUtil.createTTSComponents(settings))
arrayList.addAll(ComponentBuilderUtil.createStreamingComponents(settings))
arrayList.addAll(ComponentBuilderUtil.createHardwareComponents(settings))
listenerControllerList.add(ComponentHolder(StatusBroadcasterComponent::class.java, null))
injectDemoVideoRetriever(arrayList)
}
}

/**
* We are injecting our own video component. Need to replace the class with our test class,
* but keep properties
*
* 1. Delete old class holder from the list, cache the holder
* 2. Add our demo holder
* 3. Add old properties to holder
* 4. Add our demo retriever to holder properties
*/
private fun injectDemoVideoRetriever(arrayList: ArrayList<ComponentHolder<*>>) {
var holder : ComponentHolder<*>? = null
arrayList.reversed().forEach {
if(it.clazz == RemoVideoComponent::class.java){
//we want to inject a custom video retriever/provider. Remove the old object
holder = it.data?.let { bundle ->
VideoRetrieverFactory.putClassInBundle(
DemoSurfaceVideoComponent::class.java,
bundle
)
arrayList.remove(it)
it
}

}
}

//modify the bundle and put it back in the arrayList. A little ugly but gets the job done
arrayList.add(
ComponentHolder(
DemoVideoComponent::class.java,
(holder?.data ?: Bundle()).also { //it : Bundle
VideoRetrieverFactory.putClassInBundle(
DemoSurfaceVideoComponent::class.java,
it
)
}
)
)
}

private fun handleListenerAddOrRemove(connected : Operation) {
if(connected == Operation.OK){
listenerControllerList.forEach {
controlSDKServiceApi?.addListenerOrController(it)
}
}
}

private fun changeStreamState(desiredState : Operation) {
if(controlSDKServiceApi?.getServiceStateObserver()?.value == desiredState) return //already active
when(desiredState){
Operation.OK -> {
arrayList.forEach {
controlSDKServiceApi?.attachToLifecycle(it)
}
controlSDKServiceApi?.enable()
}
Operation.LOADING -> {} //do nothing
Operation.NOT_OK -> {
//disable the service
controlSDKServiceApi?.disable()
// remove all components that happen to be left over. We may not know what got added
// if the activity was lost due to the Android system
// Listeners and controllers will still stay, and will not be overridden by the same name
controlSDKServiceApi?.reset()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package tv.remo.android.controller

import android.graphics.*
import android.view.Surface
import org.btelman.controlsdk.streaming.models.ImageDataPacket
import org.btelman.controlsdk.streaming.video.retrievers.BaseVideoRetriever
import org.btelman.controlsdk.streaming.video.retrievers.SurfaceTextureVideoRetriever
import org.btelman.controlsdk.streaming.video.retrievers.api16.Camera1SurfaceTextureComponent
import kotlin.math.cos
import kotlin.math.sin

/**
* Created by Brendon on 3/25/2020.
*
* Run the video demo since we will not be able to use a camera
*/
class DemoSurfaceVideoComponent : BaseVideoRetriever() {

private var bitmap: Bitmap = Bitmap.createBitmap(640, 480, Bitmap.Config.RGB_565)
private var canvas = Canvas(bitmap)
private var x = 0f
private var t = 0f

override fun grabImageData(): ImageDataPacket? {
canvas.drawColor(Color.RED)
canvas.drawCircle(x, 240*sin(t/160f)+240, 10f, Paint(Color.BLUE))
canvas.drawCircle(x, 20f, 10f, Paint(Color.GREEN))
x += .1f
t += .1f
if(x > 640)
x = 0f
return ImageDataPacket(
bitmap,
ImageFormat.JPEG
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package tv.remo.android.controller
import tv.remo.android.controller.sdk.components.video.RemoVideoComponent

/**
* Created by Brendon on 3/25/2020.
*/
class DemoVideoComponent : RemoVideoComponent() {
override fun setLoopMode() {
shouldAutoUpdateLoop = true
}
}
Loading

0 comments on commit 1aadece

Please sign in to comment.