├── .github
└── workflows
│ └── android.yml
├── .gitignore
├── .idea
├── .name
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── encodings.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── kotlinc.xml
├── misc.xml
├── other.xml
├── runConfigurations
│ └── _UiAutomatorHelper.xml
└── vcs.xml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── lint.xml
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── dtmilano
│ │ └── android
│ │ └── culebratester2
│ │ ├── UiAutomatorHelper.kt
│ │ └── utils
│ │ └── SelectorUtilsTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ ├── com
│ │ │ └── dtmilano
│ │ │ │ └── android
│ │ │ │ └── culebratester2
│ │ │ │ ├── ApplicationComponent.kt
│ │ │ │ ├── CulebraTesterApplication.kt
│ │ │ │ ├── CulebraTesterServer.kt
│ │ │ │ ├── Holder.kt
│ │ │ │ ├── KtorApplication.kt
│ │ │ │ ├── LauncherActivity.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── MainActivityOld.kt
│ │ │ │ ├── ObjectStore.kt
│ │ │ │ ├── OnboardingActivity.kt
│ │ │ │ ├── WelcomeActivity.kt
│ │ │ │ ├── WindowHierarchyDumpToJson.kt
│ │ │ │ ├── location
│ │ │ │ ├── Am.kt
│ │ │ │ ├── Configurator.kt
│ │ │ │ ├── Culebra.kt
│ │ │ │ ├── Device.kt
│ │ │ │ ├── Help.kt
│ │ │ │ ├── ObjectStore.kt
│ │ │ │ ├── TargetContext.kt
│ │ │ │ ├── UiDevice.kt
│ │ │ │ ├── UiObject.kt
│ │ │ │ ├── UiObject2.kt
│ │ │ │ └── Until.kt
│ │ │ │ ├── ui
│ │ │ │ └── theme
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ ├── Shape.kt
│ │ │ │ │ ├── Theme.kt
│ │ │ │ │ └── Type.kt
│ │ │ │ └── utils
│ │ │ │ ├── JavaSelectorUtils.java
│ │ │ │ ├── LocaleUtils.kt
│ │ │ │ ├── PackageUtils.kt
│ │ │ │ ├── SelectorBundles.kt
│ │ │ │ └── SelectorUtils.kt
│ │ └── io
│ │ │ ├── ktor
│ │ │ └── swagger
│ │ │ │ └── experimental
│ │ │ │ └── SwaggerUtils.kt
│ │ │ └── swagger
│ │ │ └── server
│ │ │ └── models
│ │ │ ├── AnyValue.kt
│ │ │ ├── BooleanResponse.kt
│ │ │ ├── ClickBody.kt
│ │ │ ├── CulebraInfo.kt
│ │ │ ├── CurrentPackageName.kt
│ │ │ ├── DisplayHeight.kt
│ │ │ ├── DisplayRealSize.kt
│ │ │ ├── DisplayRotationEnum.kt
│ │ │ ├── DisplayRotationEnumCompanion.kt
│ │ │ ├── DisplayRotationResponse.kt
│ │ │ ├── DisplaySizeDp.kt
│ │ │ ├── DisplayWidth.kt
│ │ │ ├── Help.kt
│ │ │ ├── Inline_response_200.kt
│ │ │ ├── Inline_response_200_1.kt
│ │ │ ├── LastTraversedText.kt
│ │ │ ├── Locale.kt
│ │ │ ├── NumberResponse.kt
│ │ │ ├── ObjectRef.kt
│ │ │ ├── Oid_setText_body.kt
│ │ │ ├── Pattern.kt
│ │ │ ├── PerformTwoPointerGestureBody.kt
│ │ │ ├── Pixel.kt
│ │ │ ├── Point.kt
│ │ │ ├── ProductName.kt
│ │ │ ├── Rect.kt
│ │ │ ├── Selector.kt
│ │ │ ├── SelectorCompanion.kt
│ │ │ ├── StatusCode.kt
│ │ │ ├── StatusResponse.kt
│ │ │ ├── StringResponse.kt
│ │ │ ├── SwipeBody.kt
│ │ │ ├── Text.kt
│ │ │ ├── Timeout.kt
│ │ │ ├── WindowHierarchy.kt
│ │ │ └── WindowHierarchyChild.kt
│ └── res
│ │ ├── drawable-hdpi
│ │ └── culebra.png
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-xxhdpi
│ │ ├── culebra.png
│ │ └── monitor.png
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_onboarding.xml
│ │ ├── activity_welcome.xml
│ │ └── content_main.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── culebra.png
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── culebra.png
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── culebra.png
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── culebra.png
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── culebra.png
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── com
│ └── dtmilano
│ └── android
│ └── culebratester2
│ ├── KtorApplicationQuitTest.kt
│ ├── KtorApplicationTest.kt
│ ├── ObjectStoreTest.kt
│ └── utils
│ └── SelectorUtilsTest.kt
├── build.gradle
├── clean-openapi-yaml
├── culebratester2
├── dump-window-hierarchy
├── fix-swagger-generated-files
├── get-api-version
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── local.properties.SAMPLE
├── openapi.yaml
├── publish
├── settings.gradle
├── simple-calculator-test
├── swagger-codegen-config.json
└── take-screenshot
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: set up JDK 11
13 | uses: actions/setup-java@v1
14 | with:
15 | java-version: 11
16 | - name: Build with Gradle
17 | run: ./gradlew build assembleAndroidTest
18 | - name: Upload APK
19 | uses: actions/upload-artifact@v4
20 | with:
21 | name: app
22 | path: app/build/outputs/apk/debug/app-debug.apk
23 | - name: Upload Instrumentation
24 | uses: actions/upload-artifact@v4
25 | with:
26 | name: instrumentation
27 | path: app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # IntelliJ
36 | *.iml
37 | .idea/workspace.xml
38 | .idea/tasks.xml
39 | .idea/gradle.xml
40 | .idea/assetWizardSettings.xml
41 | .idea/dictionaries
42 | .idea/libraries
43 | .idea/caches
44 | .idea/modules.xml
45 | .idea/navEditor.xml
46 | .idea/deploymentTargetDropDown.xml
47 |
48 | # Keystore files
49 | # Uncomment the following line if you do not want to check your keystore files in.
50 | *.jks
51 |
52 | # External native build folder generated in Android Studio 2.2 and later
53 | .externalNativeBuild
54 |
55 | # Google Services (e.g. APIs or Firebase)
56 | google-services.json
57 |
58 | # Freeline
59 | freeline.py
60 | freeline/
61 | freeline_project_description.json
62 |
63 | # fastlane
64 | fastlane/report.xml
65 | fastlane/Preview.html
66 | fastlane/screenshots
67 | fastlane/test_output
68 | fastlane/readme.md
69 |
70 | # MacOS
71 | .DS_Store
72 |
73 | # Other
74 | .cxx
75 |
76 | # backup
77 | .BAK
78 |
79 | # Culebratester scripts
80 | generate-culebratester-*
81 | levenshtein
82 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | CulebraTester2
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | xmlns:android
23 |
24 | ^$
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | xmlns:.*
34 |
35 | ^$
36 |
37 |
38 | BY_NAME
39 |
40 |
41 |
42 |
43 |
44 |
45 | .*:id
46 |
47 | http://schemas.android.com/apk/res/android
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | .*:name
57 |
58 | http://schemas.android.com/apk/res/android
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | name
68 |
69 | ^$
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | style
79 |
80 | ^$
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | .*
90 |
91 | ^$
92 |
93 |
94 | BY_NAME
95 |
96 |
97 |
98 |
99 |
100 |
101 | .*
102 |
103 | http://schemas.android.com/apk/res/android
104 |
105 |
106 | ANDROID_ATTRIBUTE_ORDER
107 |
108 |
109 |
110 |
111 |
112 |
113 | .*
114 |
115 | .*
116 |
117 |
118 | BY_NAME
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/other.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/_UiAutomatorHelper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # CulebraTester2-public
4 | CulebraTester: Snaky Android Testing
5 |
6 | ### Welcome to CulebraTester2.
7 | Android testing can be complicated, time-consuming, and tedious.
8 | What if it didn’t have to be?
9 |
10 | **CulebraTester2** provides an API that facilitates the creation of tests and test automation tools and UI's.
11 | Not sure what we mean?
12 |
13 | Continue reading and see how you can run this early preview.
14 |
15 |
16 | ⚠️ Warning |
17 | :------------------|
18 | **This is an alpha version of CulebraTester2 expect changes** |
19 |
20 |
21 |
22 | # How to run CulebraTester2 ?
23 | 1. Have your device or emulator connected to `adb`
24 | 1. Install APKs
25 | 1. Download prebuilt **app** and **instrumentation** APKs from [Github Actions](https://github.com/dtmilano/CulebraTester2-public/wiki/Prebuilt-APKs)
26 | 1. or build from source and install
27 | 1. Copy `local.properties.SAMPLE` to `local.properties` and adapt the values to your environment
28 | 1. `./culebratester2 install` (or run `./gradlew installDebug installDebugAndroidTest`)
29 | 1. Start server `bash <(curl -sL https://git.io/JT5nc) start-server`
30 | 1. alternatively if you checked out the source you can run `./culebratester2 start-server` instead
31 | 1. Open http://localhost:9987/ with a browser or `curl`
32 | 1. You should see `CulebraTester2: Go to http://localhost:/help for usage details.`
33 | 1. If the previous request worked, you can try something more ambitious like http://localhost:9987/v2/uiDevice/screenshot
34 | 1. Take a look at [CulebraTester2 API](https://mrin9.github.io/OpenAPI-Viewer/#/load/https%3A%2F%2Fraw.githubusercontent.com%2Fdtmilano%2FCulebraTester2-public%2Fmaster%2Fopenapi.yaml) or its spec [`openapi.yaml`](https://github.com/dtmilano/CulebraTester2-public/blob/master/openapi.yaml) for more info
35 | 1. When you are done testing, navigating to http://localhost:9987/quit will terminate the server
36 |
37 | # Want to learn more?
38 | Detailed information can be found in the [CulebraTester2 wiki](https://github.com/dtmilano/CulebraTester2-public/wiki) wiki
39 |
40 | # AndroidViewClient
41 | **CulebraTester2** is the new backend for [AndroidViewClient/culebra](https://github.com/dtmilano/AndroidViewClient).
42 |
43 | It can be used similarly to other backends with the following command options
44 |
45 | ```
46 | -h, --use-uiautomator-helper use UiAutomatorHelper Android app
47 | ```
48 |
49 | for example
50 |
51 | ```sh
52 | $ dump -ah emulator-5554 | jq
53 | ⚠️ CulebraTester2 server should have been started and port redirected.
54 | ```
55 | ```json
56 | {
57 | "id": "hierarchy",
58 | "text": "Window Hierarchy",
59 | "timestamp": "2020-10-12T02:18:45.639Z",
60 | "children": [
61 | {
62 | "id": 0,
63 | "parent": -1,
64 | "text": "",
65 | "package": "com.android.systemui",
66 | "checkable": false,
67 | "clickable": false,
68 | "index": 0,
69 | "content_description": "",
70 | "focusable": false,
71 | ...
72 | ```
73 |
74 | or set `useuiautomatorhelper=True` when you create a `ViewClient` object.
75 |
76 | # culebra
77 | **CulebraTester2** is a new implementation in Kotlin of [culebra](culebra.dtmilano.com).
78 |
79 | A Python client implementation can be found at **[CulebraTester2-client](https://github.com/dtmilano/CulebraTester2-client)**.
80 |
81 | This previous version API specification can be found at [here](https://github.com/dtmilano/CulebraTester-public/wiki/RESTful-API).
82 |
83 | # Example
84 | The script [simple-calculator-test](https://github.com/dtmilano/CulebraTester2-public/blob/master/simple-calculator-test) shows a rudimentary usage of this API by
85 | - starting Calculator activity
86 | - finding one of the digit buttons (can be specified or a random one is selected)
87 | - clicking on that button
88 |
89 | # UI
90 | We mentioned **CulebraTester2** provides an API that facilitates the creation of test automation tools and UIs.
91 |
92 | Here we are, this is in the making.
93 |
94 |
96 |
97 | # Communication
98 | Found issues? Use https://github.com/dtmilano/CulebraTester2-public/issues
99 |
100 | Have questions? Use https://stackoverflow.com/questions/tagged/androidviewclient.
101 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | // this is in documentation https://developer.android.com/training/dependency-injection/hilt-android#kts
5 | //kotlin("kapt")
6 | // but this one works
7 | id("kotlin-kapt")
8 | id("dagger.hilt.android.plugin")
9 | }
10 |
11 | ext {
12 | compose_version = '1.1.1'
13 | ktor_version = '1.6.8'
14 | }
15 |
16 | android {
17 | compileSdk 33
18 |
19 | defaultConfig {
20 | applicationId "com.dtmilano.android.culebratester2"
21 | minSdk 26
22 | targetSdk 33
23 | versionCode 20074
24 | versionName "2.0.74-alpha"
25 |
26 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
27 | vectorDrawables {
28 | useSupportLibrary true
29 | }
30 | }
31 |
32 |
33 | buildTypes {
34 | release {
35 | minifyEnabled false
36 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
37 | }
38 | }
39 |
40 | packagingOptions {
41 | resources {
42 | excludes += ['/META-INF/{AL2.0,LGPL2.1}', 'META-INF/DEPENDENCIES', 'META-INF/io.netty.versions.properties', 'META-INF/INDEX.LIST', 'META-INF/ktor-http.kotlin_module', 'META-INF/ktor-utils.kotlin_module', 'META-INF/kotlinx-coroutines-core.kotlin_module', 'META-INF/ktor-io.kotlin_module', 'META-INF/ktor-http-cio.kotlin_module', 'META-INF/ktor-client-core.kotlin_module']
43 | }
44 | }
45 |
46 | compileOptions {
47 | sourceCompatibility JavaVersion.VERSION_1_8
48 | targetCompatibility JavaVersion.VERSION_1_8
49 | }
50 |
51 | kotlinOptions {
52 | jvmTarget = '1.8'
53 | useIR = true
54 | }
55 |
56 | buildFeatures {
57 | viewBinding true
58 | compose true
59 | }
60 |
61 | testOptions {
62 | unitTests {
63 | returnDefaultValues = true
64 | includeAndroidResources = true
65 | }
66 | }
67 |
68 | composeOptions {
69 | kotlinCompilerExtensionVersion compose_version
70 | }
71 |
72 | lintOptions {
73 | disable "JvmStaticProvidesInObjectDetector", "FieldSiteTargetOnQualifierAnnotation", "ModuleCompanionObjects", "ModuleCompanionObjectsNotInModuleParent"
74 | }
75 | }
76 |
77 | dependencies {
78 |
79 | implementation 'androidx.core:core-ktx:1.8.0'
80 | implementation 'androidx.appcompat:appcompat:1.4.2'
81 | implementation 'com.google.android.material:material:1.6.1'
82 | implementation "com.google.dagger:hilt-android:$hilt_android_version"
83 | //kapt "com.google.dagger:hilt-compiler:$hilt_android_version"
84 | implementation("io.ktor:ktor:$ktor_version")
85 | implementation 'androidx.test.uiautomator:uiautomator:2.2.0'
86 | implementation "androidx.compose.ui:ui:$compose_version"
87 | implementation "androidx.compose.material:material:$compose_version"
88 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
89 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
90 | implementation 'androidx.activity:activity-compose:1.4.0'
91 | testImplementation 'junit:junit:4.13.2'
92 | testImplementation('androidx.test:core:1.5.0')
93 | androidTestImplementation 'androidx.test.ext:junit:1.1.4'
94 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
95 | implementation('com.google.dagger:hilt-android:2.42')
96 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
97 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
98 | kapt("com.google.dagger:hilt-android-compiler:$hilt_android_version")
99 | implementation("io.ktor:ktor-server-core:$ktor_version")
100 | implementation("io.ktor:ktor-server-netty:$ktor_version")
101 | implementation("ch.qos.logback:logback-classic:1.2.11")
102 | implementation("io.ktor:ktor-jackson:$ktor_version")
103 | implementation("io.ktor:ktor-locations:$ktor_version")
104 | implementation("io.ktor:ktor-gson:$ktor_version")
105 | testImplementation("io.ktor:ktor-server-tests:$ktor_version")
106 | androidTestImplementation 'com.github.stefanbirkner:system-rules:1.19.0'
107 | testImplementation 'com.github.stefanbirkner:system-rules:1.19.0'
108 | testImplementation('org.mockito:mockito-core:4.6.0')
109 | testImplementation('org.mockito.kotlin:mockito-kotlin:4.0.0')
110 | testImplementation('org.jetbrains.kotlin:kotlin-test:1.6.10')
111 | androidTestImplementation('org.jetbrains.kotlin:kotlin-test:1.6.10')
112 | testImplementation 'org.robolectric:robolectric:4.8.1'
113 | implementation group: 'commons-io', name: 'commons-io', version: '2.6'
114 | }
115 |
116 | // Allow references to generated code
117 | kapt {
118 | correctErrorTypes true
119 | }
120 |
--------------------------------------------------------------------------------
/app/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/dtmilano/android/culebratester2/UiAutomatorHelper.kt:
--------------------------------------------------------------------------------
1 | package com.dtmilano.android.culebratester2
2 |
3 | import android.app.Instrumentation
4 | import android.content.Context.WINDOW_SERVICE
5 | import android.view.WindowManager
6 | import androidx.test.ext.junit.runners.AndroidJUnit4
7 | import androidx.test.platform.app.InstrumentationRegistry
8 | import androidx.test.uiautomator.UiDevice
9 | import io.ktor.application.*
10 | import io.ktor.locations.*
11 | import org.junit.After
12 | import org.junit.Before
13 | import org.junit.Test
14 | import org.junit.runner.RunWith
15 | import java.io.File
16 | import java.lang.ref.WeakReference
17 |
18 | /**
19 | * The port.
20 | */
21 | private const val PORT = 9987
22 |
23 | @KtorExperimentalLocationsAPI
24 | @RunWith(AndroidJUnit4::class)
25 | class UiAutomatorHelper {
26 | private var appComponent: ApplicationComponent = CulebraTesterApplication().appComponent
27 | private lateinit var instrumentation: Instrumentation
28 |
29 | @Before
30 | fun setUp() {
31 | // Injecting the Instrumentation instance is required
32 | // for your test to run with AndroidJUnitRunner.
33 | instrumentation = InstrumentationRegistry.getInstrumentation()
34 |
35 | val holder = appComponent.holder().instance
36 | holder.targetContext = WeakReference(instrumentation.targetContext)
37 | holder.uiDevice = UiDevice.getInstance(instrumentation)
38 | holder.cacheDir = instrumentation.targetContext.cacheDir
39 | holder.windowManager =
40 | instrumentation.targetContext.getSystemService(WINDOW_SERVICE) as WindowManager
41 | holder.uiAutomation = instrumentation.uiAutomation
42 | }
43 |
44 | @Test
45 | fun uiAutomatorHelper() {
46 | val ktorApplicationConfigurationFile = createKtorApplicationConfigurationFile()
47 | val args = arrayOf("-config=${ktorApplicationConfigurationFile!!.absolutePath}")
48 | io.ktor.server.netty.EngineMain.main(args)
49 | }
50 |
51 | /**
52 | * Creates a temporary file containing the configuration.
53 | */
54 | private fun createKtorApplicationConfigurationFile(): File? {
55 | val tempDir = instrumentation.targetContext.cacheDir
56 | val tempFile = File.createTempFile("ktor-application", "conf", tempDir)
57 | val applicationConfiguration = "ktor {\n" +
58 | " deployment {\n" +
59 | " port = ${PORT}\n" +
60 | " port = \${?PORT}\n" +
61 | "\n" +
62 | " shutdown.url = \"/ktor/application/shutdown\"\n" +
63 | " }\n" +
64 | " application {\n" +
65 | " modules = [ com.dtmilano.android.culebratester2.KtorApplicationKt.module ]\n" +
66 | " }\n" +
67 | "}"
68 | tempFile.writeText(applicationConfiguration)
69 | return tempFile
70 | }
71 |
72 | @After
73 | fun tearDown() {
74 | // TODO: should stop ktor server here
75 | }
76 |
77 | /**
78 | * Quits the server.
79 | */
80 | fun quit() {
81 | // TODO: should stop ktor server here
82 | }
83 |
84 | // WARNING:
85 | // won't be found because this is inside the test application code and not the main app
86 | @Suppress("unused")
87 | fun Application.module(testing: Boolean = false) {
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/dtmilano/android/culebratester2/utils/SelectorUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package com.dtmilano.android.culebratester2.utils
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import org.junit.After
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Assert.assertNotNull
7 | import org.junit.Before
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 |
11 | @RunWith(AndroidJUnit4::class)
12 | class SelectorUtilsTest {
13 |
14 | @Before
15 | fun setUp() {
16 | }
17 |
18 | @After
19 | fun tearDown() {
20 | }
21 |
22 | @Test
23 | fun test_tokenize() {
24 | val selector = "clickable@true"
25 | val delimiters = "(?<=[^\\\\])@"
26 | val tokens = tokenize(selector, delimiters).contentToString()
27 | assertEquals("[clickable, true]", tokens)
28 | }
29 |
30 | @Test
31 | fun test_tokenize_complex_selector() {
32 | val selector =
33 | "clickable@true,depth@3,desc@something with spaces and \\@ special chars@index@3,text@$.*"
34 | val delimiters = "(?<=[^\\\\])@"
35 | val tokens = tokenize(selector, delimiters).contentToString()
36 | assertEquals(
37 | "[clickable, true,depth, 3,desc, something with spaces and \\@ special chars, index, 3,text, \$.*]",
38 | tokens
39 | )
40 | }
41 |
42 | @Test
43 | fun test_unescapeSelectorChars() {
44 | val str = "\\@\\,"
45 | assertEquals("@,", unescapeSelectorChars(str))
46 | }
47 |
48 | @Test
49 | fun test_unescapeSelectorChars_multiple_occurrences() {
50 | val str = "\\@\\@\\,\\,\\@"
51 | assertEquals("@@,,@", unescapeSelectorChars(str))
52 | }
53 |
54 | @Test
55 | fun test_uiSelectorBundleFromString_missing_value() {
56 | val str = "clickable"
57 | val bundle = uiSelectorBundleFromString(str)
58 | assertEquals(str, bundle.selectorStr)
59 | assertNotNull(bundle.selector)
60 | }
61 |
62 | @Test
63 | fun test_uiSelectorBundleFromString_clickable_true() {
64 | val str = "clickable@true"
65 | val bundle = uiSelectorBundleFromString(str)
66 | assertEquals(str, bundle.selectorStr)
67 | assertNotNull(bundle.selector)
68 | }
69 |
70 | @Test
71 | fun test_uiSelectorBundleFromString_selector_with_spaces_and_special_chars() {
72 | val selector = "desc@something with spaces and \\@ special chars"
73 | val bundle = uiSelectorBundleFromString(selector)
74 | assertEquals(selector, bundle.selectorStr)
75 | assertNotNull(bundle.selector)
76 | }
77 |
78 | @Test
79 | fun test_uiSelectorBundleFromString_selector_with_spaces_and_special_chars_followed_by_another() {
80 | val selector = "desc@something with spaces and \\@ special chars,index@3"
81 | val bundle = uiSelectorBundleFromString(selector)
82 | assertEquals(selector, bundle.selectorStr)
83 | assertNotNull(bundle.selector)
84 | }
85 |
86 | @Test
87 | fun test_uiSelectorBundleFromString_complex_selector() {
88 | val selector =
89 | "clickable@true,depth@3,desc@something with spaces and \\@ special chars@index@3,text@$.*"
90 | val bundle = uiSelectorBundleFromString(selector)
91 | assertEquals(selector, bundle.selectorStr)
92 | assertNotNull(bundle.selector)
93 | }
94 |
95 | @Test(expected = KotlinNullPointerException::class)
96 | fun test_bySelectorBundleFromString_missing_value() {
97 | val selector = "clickable"
98 | val bundle = bySelectorBundleFromString(selector)
99 | }
100 |
101 | @Test
102 | fun test_bySelectorBundleFromString_clickable_true() {
103 | val str = "clickable@true"
104 | val bundle = bySelectorBundleFromString(str)
105 | assertEquals(str, bundle.selectorStr)
106 | assertNotNull(bundle.selector)
107 | }
108 |
109 | @Test
110 | fun test_bySelectorBundleFromString_selector_with_spaces_and_special_chars() {
111 | val selector = "desc@something with spaces and \\@ special chars"
112 | val bundle = bySelectorBundleFromString(selector)
113 | assertEquals(selector, bundle.selectorStr)
114 | assertNotNull(bundle.selector)
115 | }
116 |
117 | @Test
118 | fun test_bySelectorBundleFromString_selector_with_spaces_and_special_chars_followed_by_another() {
119 | val selector = "desc@something with spaces and \\@ special chars,depth@3"
120 | val bundle = bySelectorBundleFromString(selector)
121 | assertEquals(selector, bundle.selectorStr)
122 | assertNotNull(bundle.selector)
123 | }
124 |
125 | @Test
126 | fun test_bySelectorBundleFromString_complex_selector() {
127 | val selector =
128 | "clickable@true,depth@3,desc@something with spaces and \\@ special chars,text@$.*"
129 | val bundle = bySelectorBundleFromString(selector)
130 | assertEquals(selector, bundle.selectorStr)
131 | assertNotNull(bundle.selector)
132 | }
133 |
134 | @Test
135 | fun eventConditionFromString() {
136 | }
137 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
9 |
11 |
13 |
14 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
39 |
44 |
45 |
51 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dtmilano/android/culebratester2/ApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package com.dtmilano.android.culebratester2
2 |
3 | import com.dtmilano.android.culebratester2.location.*
4 | import dagger.Component
5 | import io.ktor.locations.KtorExperimentalLocationsAPI
6 | import javax.inject.Singleton
7 |
8 | @KtorExperimentalLocationsAPI
9 | @Singleton
10 | @Component
11 | interface ApplicationComponent {
12 | // Factory to create instances of the ApplicationComponent
13 | @Component.Factory
14 | interface Factory {
15 | // With @BindsInstance, the Context passed in will be available in the graph
16 | //fun create(@BindsInstance context: Context): ApplicationComponent
17 | fun create(): ApplicationComponent
18 | }
19 |
20 | fun holder(): HolderHolder
21 | fun objectStore(): ObjectStore
22 | fun inject(objectStore: com.dtmilano.android.culebratester2.location.ObjectStore)
23 | fun inject(list: com.dtmilano.android.culebratester2.location.ObjectStore.List)
24 | fun inject(startActivity: TargetContext.StartActivity)
25 | fun inject(dumpWindowHierarchy: UiDevice.DumpWindowHierarchy)
26 | fun inject(screenshot: UiDevice.Screenshot)
27 | fun inject(get: UiDevice.Click.Get)
28 | fun inject(currentPackageName: UiDevice.CurrentPackageName)
29 | fun inject(displayHeight: UiDevice.DisplayHeight)
30 | fun inject(displayRotation: UiDevice.DisplayRotation)
31 | fun inject(displaySizeDp: UiDevice.DisplaySizeDp)
32 | fun inject(displayWidth: UiDevice.DisplayWidth)
33 | fun inject(findObject: UiDevice.FindObject)
34 | fun inject(get: UiDevice.FindObject.Get)
35 | fun inject(post: UiDevice.FindObject.Post)
36 | fun inject(lastTraversedText: UiDevice.LastTraversedText)
37 | fun inject(pressBack: UiDevice.PressBack)
38 | fun inject(pressDelete: UiDevice.PressDelete)
39 | fun inject(pressEnter: UiDevice.PressEnter)
40 | fun inject(pressHome: UiDevice.PressHome)
41 | fun inject(pressKeyCode: UiDevice.PressKeyCode)
42 | fun inject(productName: UiDevice.ProductName)
43 | fun inject(waitForIdle: UiDevice.WaitForIdle)
44 | fun inject(waitForWindowUpdate: UiDevice.WaitForWindowUpdate)
45 | fun inject(displayRealSize: Device.DisplayRealSize)
46 | fun inject(click: UiObject2.Click)
47 | fun inject(dump: UiObject2.Dump)
48 | fun inject(get: UiObject2.SetText.Get)
49 | fun inject(post: UiObject2.SetText.Post)
50 | fun inject(longClick: UiObject2.LongClick)
51 | fun inject(get: UiDevice.FindObjects.Get)
52 | fun inject(getText: UiObject2.GetText)
53 | fun inject(pressRecentApps: UiDevice.PressRecentApps)
54 | fun inject(get: UiDevice.Swipe.Get)
55 | fun inject(post: UiDevice.Swipe.Post)
56 | fun inject(clear: com.dtmilano.android.culebratester2.location.ObjectStore.Clear)
57 | fun inject(remove: com.dtmilano.android.culebratester2.location.ObjectStore.Remove)
58 | fun inject(clear: UiObject2.Clear)
59 | fun inject(wait: UiDevice.Wait)
60 | fun inject(newWindow: Until.NewWindow)
61 | fun inject(clickAndWait: UiObject2.ClickAndWait)
62 | fun inject(clearLastTraversedText: UiDevice.ClearLastTraversedText)
63 | fun inject(drag: UiDevice.Drag)
64 | fun inject(freezeRotation: UiDevice.FreezeRotation)
65 | fun inject(get: UiDevice.HasObject.Get)
66 | fun inject(post: UiDevice.HasObject.Post)
67 | fun inject(waitForNewToast: Device.WaitForNewToast)
68 | fun inject(pixel: UiDevice.Pixel)
69 | fun inject(post: UiDevice.Click.Post)
70 | fun inject(unfreezeRotation: UiDevice.UnfreezeRotation)
71 | fun inject(isNaturalOrientation: UiDevice.IsNaturalOrientation)
72 | fun inject(isScreenOn: UiDevice.IsScreenOn)
73 | fun inject(query: Culebra.Help.Query)
74 | fun inject(dumpsys: Device.Dumpsys)
75 | fun inject(getContentDescription: UiObject2.GetContentDescription)
76 | fun inject(post: UiDevice.FindObjects.Post)
77 | fun inject(get: Until.FindObject.Get)
78 | fun inject(post: Until.FindObject.Post)
79 | fun inject(get: Until.FindObjects.Get)
80 | fun inject(post: Until.FindObjects.Post)
81 | fun inject(get: UiObject2.FindObject.Get)
82 | fun inject(post: UiObject2.FindObject.Post)
83 | fun inject(pressDPadCenter: UiDevice.PressDPadCenter)
84 | fun inject(pressDPadDown: UiDevice.PressDPadDown)
85 | fun inject(pressDPadLeft: UiDevice.PressDPadLeft)
86 | fun inject(pressDPadRight: UiDevice.PressDPadRight)
87 | fun inject(pressDPadUp: UiDevice.PressDPadUp)
88 | fun inject(get: Device.Locale.Get)
89 | fun inject(post: Device.Locale.Post)
90 | fun inject(post: UiObject.PerformTwoPointerGesture.Post)
91 | fun inject(pinchIn: UiObject.PinchIn)
92 | fun inject(pinchOut: UiObject.PinchOut)
93 | fun inject(exists: UiObject.Exists)
94 | fun inject(waitForExists: UiObject.WaitForExists)
95 | fun inject(dump: UiObject.Dump)
96 | fun inject(getChildCount: UiObject.GetChildCount)
97 | fun inject(dump: Until.Dump)
98 | fun inject(click: UiObject.Click)
99 | fun inject(clickAndWaitForNewWindow: UiObject.ClickAndWaitForNewWindow)
100 | fun inject(clearTextField: UiObject.ClearTextField)
101 | fun inject(getContentDescription: UiObject.GetContentDescription)
102 | fun inject(getClassName: UiObject.GetClassName)
103 | fun inject(getChildCount: UiObject2.GetChildCount)
104 | fun inject(getChildren: UiObject2.GetChildren)
105 | fun inject(getBounds: UiObject.GetBounds)
106 | fun inject(getChild: UiObject.GetChild)
107 | fun inject(getFromParent: UiObject.GetFromParent)
108 | // fun inject(uiAutomatorHelper: UiAutomatorHelper)
109 | }
110 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dtmilano/android/culebratester2/CulebraTesterApplication.kt:
--------------------------------------------------------------------------------
1 | package com.dtmilano.android.culebratester2
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.content.SharedPreferences
6 | import android.preference.PreferenceManager
7 | import dagger.hilt.android.HiltAndroidApp
8 | import io.ktor.locations.*
9 |
10 | @HiltAndroidApp
11 | class CulebraTesterApplication : Application() {
12 | companion object {
13 |
14 | /**
15 | * Preferences store whether the welcome activity was showed.
16 | */
17 | const val PREFERENCE_ONBOARDING_SHOWED =
18 | "com.dtmilano.android.culebratester2.PREFERENCE_ONBOARDING_SHOWED"
19 |
20 | /**
21 | * Gets the default shared preferences for this application.
22 | *
23 | * @param context the context
24 | * @return the shared preferences
25 | */
26 | fun getPreferences(context: Context): SharedPreferences {
27 | return PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
28 | }
29 | }
30 |
31 | // Instance of the ApplicationComponent that will be used by all the Activities in the project
32 | @KtorExperimentalLocationsAPI
33 | val appComponent: ApplicationComponent by lazy {
34 | initializeComponent()
35 | }
36 |
37 | @KtorExperimentalLocationsAPI
38 | fun initializeComponent(): ApplicationComponent {
39 | // Creates an instance of ApplicationComponent using its factory method
40 | // We pass the applicationContext that will be used as Context in the graph
41 | //return DaggerApplicationComponent.factory().create(applicationContext)
42 | return DaggerApplicationComponent.factory().create()
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dtmilano/android/culebratester2/CulebraTesterServer.kt:
--------------------------------------------------------------------------------
1 | package com.dtmilano.android.culebratester2
2 |
3 | class CulebraTesterServer {
4 | init {
5 | println("==== CulebraTesterServer ====")
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dtmilano/android/culebratester2/Holder.kt:
--------------------------------------------------------------------------------
1 | package com.dtmilano.android.culebratester2
2 |
3 | import android.app.UiAutomation
4 | import android.content.Context
5 | import android.view.WindowManager
6 | import androidx.test.uiautomator.UiDevice
7 | import java.io.File
8 | import java.lang.ref.WeakReference
9 | import javax.inject.Inject
10 | import javax.inject.Singleton
11 |
12 | /**
13 | * Holds some important objects accessible from app and tests.
14 | */
15 | object Holder {
16 | lateinit var targetContext: WeakReference
17 | lateinit var windowManager: WindowManager
18 | lateinit var cacheDir: File
19 | lateinit var uiDevice: UiDevice
20 | lateinit var uiAutomation: UiAutomation
21 | }
22 |
23 | // Didn't work, dagger was injecting different references even though this is annotated with
24 | // singleton, as a workaround, HolderHolder was used
25 | //@Singleton
26 | //class Holder @Inject constructor() {
27 | // lateinit var targetContext: WeakReference
28 | // lateinit var windowManager: WindowManager
29 | // lateinit var cacheDir: File
30 | // lateinit var uiDevice: UiDevice
31 | //}
32 |
33 | @Singleton
34 | class HolderHolder @Inject constructor() {
35 | val instance = Holder
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dtmilano/android/culebratester2/LauncherActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dtmilano.android.culebratester2
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AppCompatActivity
6 |
7 | class LauncherActivity : AppCompatActivity() {
8 |
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 |
12 | if (shouldShowOnboarding(this)) {
13 | OnboardingActivity.start(this)
14 | } else {
15 | MainActivityOld.start(this)
16 | }
17 | }
18 |
19 | companion object {
20 | fun shouldShowOnboarding(context: Context): Boolean {
21 | val preferences = CulebraTesterApplication.getPreferences(context)
22 | return !(preferences.contains(CulebraTesterApplication.PREFERENCE_ONBOARDING_SHOWED) && preferences.getBoolean(
23 | CulebraTesterApplication.PREFERENCE_ONBOARDING_SHOWED, false))
24 | }
25 |
26 | fun onboardingShowed(context: Context) {
27 | val preferences = CulebraTesterApplication.getPreferences(context)
28 | preferences.edit().putBoolean(CulebraTesterApplication.PREFERENCE_ONBOARDING_SHOWED, true).apply()
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dtmilano/android/culebratester2/MainActivity.kt:
--------------------------------------------------------------------------------
1 | @file:JvmName("MainActivityKt")
2 |
3 | package com.dtmilano.android.culebratester2
4 |
5 | import android.os.Bundle
6 | import androidx.activity.ComponentActivity
7 | import androidx.activity.compose.setContent
8 | import androidx.compose.foundation.Image
9 | import androidx.compose.foundation.background
10 | import androidx.compose.foundation.layout.*
11 | import androidx.compose.material.MaterialTheme
12 | import androidx.compose.material.Surface
13 | import androidx.compose.material.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.draw.alpha
18 | import androidx.compose.ui.graphics.Color
19 | import androidx.compose.ui.platform.LocalContext
20 | import androidx.compose.ui.res.painterResource
21 | import androidx.compose.ui.res.stringResource
22 | import androidx.compose.ui.text.font.FontFamily
23 | import androidx.compose.ui.text.font.FontWeight
24 | import androidx.compose.ui.text.style.TextAlign
25 | import androidx.compose.ui.tooling.preview.Preview
26 | import androidx.compose.ui.tooling.preview.PreviewParameter
27 | import androidx.compose.ui.tooling.preview.PreviewParameterProvider
28 | import androidx.compose.ui.unit.dp
29 | import androidx.compose.ui.unit.sp
30 | import com.dtmilano.android.culebratester2.ui.theme.CulebraTester2Theme
31 | import com.dtmilano.android.culebratester2.utils.PackageUtils
32 |
33 | class MainActivity : ComponentActivity() {
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | setContent {
37 | Content()
38 | }
39 | }
40 | }
41 |
42 | @Composable
43 | private fun checkInstrumentationPresent(): Boolean {
44 | val context = LocalContext.current
45 | val instrumentationInfo = PackageUtils.isInstrumentationPresent(context = context)
46 | return (instrumentationInfo != null)
47 | }
48 |
49 | @Preview(showBackground = true)
50 | @Composable
51 | private fun Content() {
52 | CulebraTester2Theme {
53 | Surface(color = MaterialTheme.colors.background) {
54 | Column(
55 | modifier = Modifier.fillMaxSize(),
56 | verticalArrangement = Arrangement.Center,
57 | horizontalAlignment = Alignment.CenterHorizontally
58 | ) {
59 | MonitorImage()
60 | Message(checkInstrumentationPresent())
61 | }
62 | }
63 | }
64 | }
65 |
66 | @Preview(showBackground = true)
67 | @Composable
68 | fun MonitorImage() {
69 | Surface(color = MaterialTheme.colors.background) {
70 | Image(
71 | painter = painterResource(id = R.drawable.monitor),
72 | contentDescription = "monitor"
73 | )
74 | }
75 | }
76 |
77 | class IsInstrumentationPresentPreviewParameterProvider : PreviewParameterProvider {
78 | override val values = sequenceOf(
79 | true,
80 | false
81 | )
82 | }
83 |
84 | @Preview(showBackground = true)
85 | @Composable
86 | fun MessagePreview(
87 | @PreviewParameter(IsInstrumentationPresentPreviewParameterProvider::class) isInstrumentationPresent: Boolean
88 | ) {
89 | Message(isInstrumentationPresent)
90 | }
91 |
92 |
93 | @Composable
94 | fun Message(isInstrumentationPresent: Boolean = false) {
95 | Column(Modifier.absolutePadding(24.dp, 6.dp, 24.dp, 6.dp)) {
96 | if (isInstrumentationPresent) {
97 | Text(
98 | text = stringResource(id = R.string.msg_instrumentation_installed),
99 | Modifier
100 | .fillMaxWidth(),
101 | textAlign = TextAlign.Center,
102 | fontSize = 24.sp,
103 | fontWeight = FontWeight.Light
104 | )
105 | Spacer(modifier = Modifier.height(12.dp))
106 | Text(
107 | text = stringResource(id = R.string.msg_start_server),
108 | Modifier
109 | .fillMaxWidth(),
110 | textAlign = TextAlign.Center
111 | )
112 | Spacer(modifier = Modifier.height(12.dp))
113 | Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
114 | Text(
115 | text = stringResource(id = R.string.msg_start_server_command),
116 | Modifier
117 | .background(Color.DarkGray)
118 | .absolutePadding(12.dp, 12.dp, 12.dp, 12.dp),
119 | fontFamily = FontFamily.Monospace,
120 | textAlign = TextAlign.Center
121 | )
122 | }
123 | Spacer(modifier = Modifier.height(12.dp))
124 | Text(
125 | text = stringResource(id = R.string.msg_activity_will_exit),
126 | Modifier
127 | .fillMaxWidth()
128 | .alpha(0.7f),
129 | textAlign = TextAlign.Center,
130 | fontSize = 12.sp
131 | )
132 | } else {
133 | Text(
134 | text = stringResource(id = R.string.msg_instrumentation_not_installed),
135 | Modifier
136 | .fillMaxWidth(),
137 | textAlign = TextAlign.Center
138 | )
139 | }
140 | }
141 | }
142 |
143 | @Preview(showBackground = true)
144 | @Composable
145 | fun DefaultPreview() {
146 | CulebraTester2Theme {
147 | Content()
148 | }
149 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/dtmilano/android/culebratester2/MainActivityOld.kt:
--------------------------------------------------------------------------------
1 | package com.dtmilano.android.culebratester2
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.view.Menu
7 | import android.view.MenuItem
8 | import androidx.appcompat.app.AppCompatActivity
9 | import com.dtmilano.android.culebratester2.databinding.ActivityMainBinding
10 | import com.dtmilano.android.culebratester2.databinding.ContentMainBinding
11 | import com.dtmilano.android.culebratester2.utils.PackageUtils
12 | import com.google.android.material.snackbar.Snackbar
13 | import dagger.hilt.android.AndroidEntryPoint
14 |
15 |
16 | @AndroidEntryPoint
17 | class MainActivityOld : AppCompatActivity() {
18 | private lateinit var main: ActivityMainBinding
19 | private lateinit var content: ContentMainBinding
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 |
24 | main = ActivityMainBinding.inflate(layoutInflater)
25 | val view = main.root
26 | setContentView(view)
27 |
28 | setSupportActionBar(main.toolbar)
29 |
30 | content.mainMessage.text = getString(R.string.main_message_default)
31 |
32 | main.fab.setOnClickListener { _view ->
33 | Snackbar.make(_view, "Replace with your own action", Snackbar.LENGTH_LONG)
34 | .setAction("Action", null).show()
35 | }
36 |
37 | }
38 |
39 | override fun onResume() {
40 | super.onResume()
41 | checkInstrumentationPresent()
42 | }
43 |
44 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
45 | // Inflate the menu; this adds items to the action bar if it is present.
46 | menuInflater.inflate(R.menu.menu_main, menu)
47 | return true
48 | }
49 |
50 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
51 | // Handle action bar item clicks here. The action bar will
52 | // automatically handle clicks on the Home/Up button, so long
53 | // as you specify a parent activity in AndroidManifest.xml.
54 | return when (item.itemId) {
55 | R.id.action_settings -> true
56 | else -> super.onOptionsItemSelected(item)
57 | }
58 | }
59 |
60 | private fun checkInstrumentationPresent() {
61 | val instrumentationInfo = PackageUtils.isInstrumentationPresent(this)
62 | if (instrumentationInfo != null) {
63 | // Do something
64 | // We are resuming and instrumentation is installed, so we should start the tests, however it's not
65 | // possible from the Activity
66 | content.mainMessage.append("\n" + getString(R.string.msg_instrumentation_installed))
67 | } else {
68 | val snackbar = Snackbar.make(
69 | main.coordinator,
70 | R.string.msg_instrumentation_not_installed,
71 | Snackbar.LENGTH_INDEFINITE
72 | ).setAction(R.string.action_install) {
73 | // FIXME: can we install from github?
74 | }
75 | snackbar.show()
76 | }
77 | }
78 |
79 | companion object {
80 | /**
81 | * Convenience function to start the MainActivity.
82 | */
83 | fun start(context: Context) {
84 | val main = Intent(Intent.ACTION_MAIN)
85 | main.setClass(context, MainActivityOld::class.java)
86 | context.startActivity(main)
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dtmilano/android/culebratester2/ObjectStore.kt:
--------------------------------------------------------------------------------
1 | package com.dtmilano.android.culebratester2
2 |
3 | import java.util.*
4 | import java.util.concurrent.ConcurrentSkipListMap
5 | import javax.inject.Inject
6 | import javax.inject.Singleton
7 |
8 | /**
9 | * Created by diego on 2016-02-26.
10 | */
11 | @Singleton
12 | //class ObjectStore private constructor() {
13 | class ObjectStore @Inject constructor() {
14 | private val nextOid: Int
15 | get() = try {
16 | objectMap.lastKey()
17 | } catch (e: NoSuchElementException) {
18 | 0
19 | } + 1
20 |
21 | fun size(): Int {
22 | return objectMap.size
23 | }
24 |
25 | fun lastKey(): Int {
26 | return objectMap.lastKey()
27 | }
28 |
29 | operator fun get(oid: Int): Any? {
30 | return objectMap[oid]
31 | }
32 |
33 | fun remove(oid: Int) {
34 | objectMap.remove(oid)
35 | }
36 |
37 | fun list(): SortedMap {
38 | return objectMap
39 | }
40 |
41 | fun put(obj: Any): Int {
42 | nextOid.let { put(it, obj); return@put it }
43 | }
44 |
45 | fun clear() {
46 | objectMap.clear()
47 | }
48 |
49 | private fun put(oid: Int, obj: Any) {
50 | objectMap[oid] = obj
51 | }
52 |
53 | companion object {
54 | //val instance = ObjectStore()
55 |
56 | private val objectMap = ConcurrentSkipListMap()
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dtmilano/android/culebratester2/OnboardingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.dtmilano.android.culebratester2
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import android.os.Handler
8 | import android.view.MotionEvent
9 | import android.view.View
10 | import androidx.appcompat.app.AppCompatActivity
11 | import com.dtmilano.android.culebratester2.databinding.ActivityOnboardingBinding
12 |
13 | /**
14 | * An example full-screen activity that shows and hides the system UI (i.e.
15 | * status bar and navigation/system bar) with user interaction.
16 | */
17 | class OnboardingActivity : AppCompatActivity() {
18 | private lateinit var onboarding: ActivityOnboardingBinding
19 |
20 | //private lateinit var fullscreenContent: View
21 | //private lateinit var fullscreenContentControls: LinearLayout
22 | private val hideHandler = Handler()
23 |
24 | @SuppressLint("InlinedApi")
25 | private val hidePart2Runnable = Runnable {
26 | // Delayed removal of status and navigation bar
27 |
28 | // Note that some of these constants are new as of API 16 (Jelly Bean)
29 | // and API 19 (KitKat). It is safe to use them, as they are inlined
30 | // at compile-time and do nothing on earlier devices.
31 | onboarding.fullscreenContent.systemUiVisibility =
32 | View.SYSTEM_UI_FLAG_LOW_PROFILE or
33 | View.SYSTEM_UI_FLAG_FULLSCREEN or
34 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
35 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
36 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
37 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
38 | }
39 | private val showPart2Runnable = Runnable {
40 | // Delayed display of UI elements
41 | supportActionBar?.show()
42 | onboarding.fullscreenContentControls.visibility = View.VISIBLE
43 | }
44 | private var isFullscreen: Boolean = false
45 |
46 | private val hideRunnable = Runnable { hide() }
47 |
48 | /**
49 | * Touch listener to use for in-layout UI controls to delay hiding the
50 | * system UI. This is to prevent the jarring behavior of controls going away
51 | * while interacting with activity UI.
52 | */
53 | private val delayHideTouchListener = View.OnTouchListener { view, motionEvent ->
54 | when (motionEvent.action) {
55 | MotionEvent.ACTION_DOWN -> if (AUTO_HIDE) {
56 | delayedHide(AUTO_HIDE_DELAY_MILLIS)
57 | }
58 | MotionEvent.ACTION_UP -> view.performClick()
59 | else -> {
60 | }
61 | }
62 | false
63 | }
64 |
65 | @SuppressLint("ClickableViewAccessibility")
66 | override fun onCreate(savedInstanceState: Bundle?) {
67 | super.onCreate(savedInstanceState)
68 |
69 | onboarding = ActivityOnboardingBinding.inflate(layoutInflater)
70 | val view = onboarding.root
71 | setContentView(view)
72 |
73 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
74 |
75 | isFullscreen = true
76 |
77 | // Set up the user interaction to manually show or hide the system UI.
78 | //fullscreenContent = findViewById(R.id.fullscreen_content)
79 | //fullscreenContent.setOnClickListener { toggle() }
80 | onboarding.fullscreenContent.setOnClickListener { toggle() }
81 |
82 | //fullscreenContentControls = findViewById(R.id.fullscreen_content_controls)
83 |
84 | // Upon interacting with UI controls, delay any scheduled hide()
85 | // operations to prevent the jarring behavior of controls going away
86 | // while interacting with the UI.
87 | //findViewById