Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kotlin implemtentation of minicap #12

Merged
merged 3 commits into from
Apr 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.gradle
/*.tgz
/.env
/gdb.setup
Expand Down
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/.env
/.gitmodules
/.npmignore
/.semaphore
/*.tgz
/build-remote.sh
/CONTRIBUTING.md
Expand All @@ -14,3 +15,4 @@
/run.sh
/temp/
/yarn-error.log
/experimental
1 change: 1 addition & 0 deletions .semaphore/semaphore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ blocks:
- 'export PATH=$PATH:~/android-sdk/cmdline-tools/tools/bin'
- yes | sdkmanager "ndk;21.3.6528147"
- 'export PATH=$PATH:~/android-sdk/ndk/21.3.6528147/'
- export ANDROID_SDK_ROOT=~/android-sdk
- sem-version node 12
- cache restore
- npm install
Expand Down
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@ NDKBUILT := \
libs/x86_64/minicap \
libs/x86_64/minicap-nopie \

GRADLEBUILT := \
experimental/app/build/outputs/apk/debug/minicap-debug.apk

default: prebuilt

clean:
ndk-build clean
rm -rf prebuilt
experimental/gradlew -p experimental clean

$(NDKBUILT):
ndk-build

$(GRADLEBUILT):
experimental/gradlew -p experimental assembleDebug

# It may feel a bit redundant to list everything here. However it also
# acts as a safeguard to make sure that we really are including everything
# that is supposed to be there.
Expand Down Expand Up @@ -85,6 +92,7 @@ prebuilt: \
prebuilt/x86_64/lib/android-28/minicap.so \
prebuilt/x86_64/lib/android-29/minicap.so \
prebuilt/x86_64/lib/android-30/minicap.so \
prebuilt/noarch/minicap.apk \

prebuilt/%/bin/minicap: libs/%/minicap
mkdir -p $(@D)
Expand All @@ -109,3 +117,8 @@ prebuilt/x86/lib/%/minicap.so: jni/minicap-shared/aosp/libs/%/x86/minicap.so
prebuilt/x86_64/lib/%/minicap.so: jni/minicap-shared/aosp/libs/%/x86_64/minicap.so
mkdir -p $(@D)
cp $^ $@

prebuilt/noarch/minicap.apk: experimental/app/build/outputs/apk/debug/minicap-debug.apk
mkdir -p $(@D)
cp $^ $@

16 changes: 16 additions & 0 deletions experimental/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
1 change: 1 addition & 0 deletions experimental/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
59 changes: 59 additions & 0 deletions experimental/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'org.jetbrains.dokka'
}

android {
compileSdkVersion 30
buildToolsVersion "30.0.2"

defaultConfig {
applicationId "io.devicefarmer.minicap"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "0.3"
archivesBaseName = "minicap"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}

dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'org.slf4j:slf4j-api:1.7.30'
implementation 'com.github.tony19:logback-android:2.0.0'
}
21 changes: 21 additions & 0 deletions experimental/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
26 changes: 26 additions & 0 deletions experimental/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 Orange
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.devicefarmer.minicap">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Minicap" />
</manifest>
42 changes: 42 additions & 0 deletions experimental/app/src/main/assets/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!--
~ Copyright (C) 2020 Orange
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<configuration>
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>/data/local/tmp/minicap.log</file>
<append>true</append>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>

<appender name="LOGCAT" class="ch.qos.logback.classic.android.LogcatAppender">
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<!--appender-ref ref="FILE" /-->
<appender-ref ref="LOGCAT" />
<appender-ref ref="STDERR" />
</root>
</configuration>
151 changes: 151 additions & 0 deletions experimental/app/src/main/java/io/devicefarmer/minicap/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright (C) 2020 Orange
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.devicefarmer.minicap

import android.os.Looper
import android.util.Size
import io.devicefarmer.minicap.provider.SurfaceProvider
import org.slf4j.LoggerFactory
import java.io.PrintStream
import kotlin.system.exitProcess


/**
* Main entry point that can be launched as follow:
* adb shell CLASSPATH=/data/local/tmp/minicap.apk app_process /system/bin io.devicefarmer.minicap.Main
*/
class Main {
companion object {

@JvmStatic
fun main(args: Array<String>) {
val provider: SurfaceProvider
//called because we are not run from the Android environment
Looper.prepareMainLooper()

val params = args.fold(Pair(Parameters.Builder(), "")) { (p, lastKey), elem ->
if (elem.startsWith("-")) {
when (elem) {
"-s" -> p.screenshot(true)
"-i" -> p.displayInfo(true)
"-h" -> showHelp().also { System.exit(0) }
}
Pair(p, elem)
} else {
when (lastKey) {
"-n" -> p.socket(elem)
"-P" -> p.projection(elem)
"-Q" -> p.quality(elem.toInt())
"-r" -> p.frameRate(elem.toFloat())
}
Pair(p, lastKey)
}
}.first.build()

provider = if (params.projection == null) {
SurfaceProvider()
} else {
SurfaceProvider(
Size(
params.projection.targetSize.width,
params.projection.targetSize.height
)
)
}
provider.quality = params.quality
provider.frameRate = params.frameRate
when {
params.displayInfo -> {
println("${provider.displayInfo}")
exitProcess(0)
}
params.screenshot -> {
//for now this is asynchronous and requires the main looper to run
provider.screenshot(System.out)
}
else -> {
//the stf process reads this
System.err.println("PID: ${android.os.Process.myPid()}")
System.err.println("INFO: ${params.projection}")
val server = SimpleServer(params.socket, provider)
server.start()
}
}
Looper.loop()
}

private fun showHelp() {
System.out.println(
"Usage: %s [-h] [-n <name>]\n" +
" -d <id>: Display ID. (%d)\n" +
" -n <name>: Change the name of the abtract unix domain socket. (%s)\n" +
" -P <value>: Display projection (<w>x<h>@<w>x<h>/{0|90|180|270}).\n" +
" -Q <value>: JPEG quality (0-100).\n" +
" -s: Take a screenshot and output it to stdout. Needs -P.\n" +
" -S: Skip frames when they cannot be consumed quickly enough.\n" +
" -r <value>: Frame rate (frames/s)" +
" -t: Attempt to get the capture method running, then exit.\n" +
" -i: Get display information in JSON format. May segfault.\n" +
" -h: Show help.\n"
)
}
}
}

data class Projection(
val realSize: Size, val targetSize: Size,
val orientation: Int
) {
override fun toString(): String =
"${realSize.width}x${realSize.height}@${targetSize.width}x${targetSize.height}/${orientation}"
}

class Parameters private constructor(
val projection: Projection?,
val screenshot: Boolean,
val socket: String,
val quality: Int,
val displayInfo: Boolean,
val frameRate: Float
) {
data class Builder(
var projection: Projection? = null,
var screenshot: Boolean = false,
var socket: String = "minicap",
var quality: Int = 100,
var displayInfo: Boolean = false,
var frameRate: Float = Float.MAX_VALUE
) {
//TODO make something more robust
fun projection(p: String) = apply {
this.projection = p.run {
val s = this.split('@', '/', 'x')
Projection(
Size(s[0].toInt(), s[1].toInt()),
Size(s[2].toInt(), s[3].toInt()),
s[4].toInt()
)
}
}

fun screenshot(s: Boolean) = apply { this.screenshot = s }
fun socket(name: String) = apply { this.socket = name }
fun quality(value: Int) = apply { this.quality = value }
fun displayInfo(enabled: Boolean) = apply { this.displayInfo = enabled }
fun frameRate(value: Float) = apply { this.frameRate = value }
fun build() = Parameters(projection, screenshot, socket, quality, displayInfo, frameRate)
}
}
Loading