From d48145dcf6c2bd8f2bb7c44e7562d15ec06c1f1a Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Sun, 6 Nov 2022 12:53:01 +0530 Subject: [PATCH 01/11] update and add required test dependencies - specify jdk 1.8 - make jni debuggable --- build.gradle | 7 +++++-- mobile/build.gradle | 13 ++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index aa773a3e..25ce7b85 100644 --- a/build.gradle +++ b/build.gradle @@ -2,9 +2,12 @@ buildscript { ext.kotlin_version = '1.3.72' + ext.androidXTestVersion = '1.4.0' + ext.espressoVersion = '3.5.0-rc01' + ext.extJUnitVersion = '1.1.3' repositories { google() - jcenter() + mavenCentral() maven { url "https://plugins.gradle.org/m2/" } @@ -22,7 +25,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/mobile/build.gradle b/mobile/build.gradle index 80bc16ba..9e1e6472 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -32,6 +32,8 @@ android { } debug { applicationIdSuffix ".debug" + jniDebuggable true + renderscriptDebuggable true } } compileOptions { @@ -39,6 +41,9 @@ android { targetCompatibility = 1.8 } + kotlinOptions { + jvmTarget = "1.8" + } // Never got this to work... //if (project.hasProperty("doNotStrip")) { // packagingOptions { @@ -68,9 +73,11 @@ dependencies { implementation 'com.google.android.material:material:1.3.0' implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + testImplementation "junit:junit:4.13.2" + androidTestImplementation "androidx.test.ext:junit-ktx:$extJUnitVersion" + androidTestImplementation "androidx.test:runner:$androidXTestVersion" + androidTestImplementation "androidx.test:rules:$androidXTestVersion" + androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" } // Can be used to build with: ./gradlew cargoBuild From 5c4c25a7da464d1ce7d9023725c91d18252ec4d9 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Sun, 6 Nov 2022 17:12:28 +0530 Subject: [PATCH 02/11] Upgrade gradle and kotlin and fix migration errors - Min targetSDK must be >=30 for playstore now --- build.gradle | 5 +++-- gradle/wrapper/gradle-wrapper.properties | 2 +- mobile/build.gradle | 11 +++++++---- .../activitywatch/android/ExampleInstrumentedTest.kt | 6 +++--- mobile/src/main/AndroidManifest.xml | 11 ++++++----- .../activitywatch/android/fragments/WebUIFragment.kt | 4 ++-- .../activitywatch/android/watcher/ChromeWatcher.kt | 6 +++--- .../android/watcher/UsageStatsWatcher.kt | 3 ++- mobile/src/main/res/values/strings.xml | 4 ++-- 9 files changed, 29 insertions(+), 23 deletions(-) diff --git a/build.gradle b/build.gradle index 25ce7b85..056f55d9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,11 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' + ext.kotlin_version = '1.7.20' ext.androidXTestVersion = '1.4.0' ext.espressoVersion = '3.5.0-rc01' ext.extJUnitVersion = '1.1.3' + ext.servicesVersion = "1.4.2-rc01" repositories { google() mavenCentral() @@ -13,7 +14,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.android.tools.build:gradle:7.3.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'gradle.plugin.org.mozilla.rust-android-gradle:plugin:0.8.3' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 622ab64a..41dfb879 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/mobile/build.gradle b/mobile/build.gradle index 9e1e6472..39506cee 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -3,14 +3,13 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 29 - buildToolsVersion = '29.0.3' + compileSdkVersion 31 ndkVersion "21.4.7075529" defaultConfig { applicationId "net.activitywatch.android" - minSdkVersion 23 - targetSdkVersion 29 + minSdkVersion 24 + targetSdkVersion 31 // Set in CI on tagged commit versionName "0.10.0" @@ -19,6 +18,7 @@ android { versionCode 25 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments useTestStorageService: 'true' // WARNING: Never commit this uncommented! packagingOptions { @@ -44,6 +44,7 @@ android { kotlinOptions { jvmTarget = "1.8" } + namespace 'net.activitywatch.android' // Never got this to work... //if (project.hasProperty("doNotStrip")) { // packagingOptions { @@ -78,6 +79,8 @@ dependencies { androidTestImplementation "androidx.test:runner:$androidXTestVersion" androidTestImplementation "androidx.test:rules:$androidXTestVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" + androidTestUtil "androidx.test.services:test-services:$servicesVersion" + androidTestImplementation "androidx.test.espresso:espresso-web:$espressoVersion" } // Can be used to build with: ./gradlew cargoBuild diff --git a/mobile/src/androidTest/java/net/activitywatch/android/ExampleInstrumentedTest.kt b/mobile/src/androidTest/java/net/activitywatch/android/ExampleInstrumentedTest.kt index 0dc8bb4c..07cce989 100644 --- a/mobile/src/androidTest/java/net/activitywatch/android/ExampleInstrumentedTest.kt +++ b/mobile/src/androidTest/java/net/activitywatch/android/ExampleInstrumentedTest.kt @@ -21,14 +21,14 @@ class ExampleInstrumentedTest { @Test fun useAppContext() { // Context of the app under test. - val appContext = InstrumentationRegistry.getTargetContext() + val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("net.activitywatch.android", appContext.packageName) } @Test fun getBuckets() { // TODO: Clear test buckets before test - val appContext = InstrumentationRegistry.getTargetContext() + val appContext = InstrumentationRegistry.getInstrumentation().targetContext val ri = RustInterface(appContext) val bucketId = "test-${Math.random()}" val oldLen = ri.getBucketsJSON().length() @@ -38,7 +38,7 @@ class ExampleInstrumentedTest { @Test fun createHeartbeat() { - val appContext = InstrumentationRegistry.getTargetContext() + val appContext = InstrumentationRegistry.getInstrumentation().targetContext val ri = RustInterface(appContext) val bucketId = "test-${Math.random()}" ri.createBucket("""{"id": "$bucketId", "type": "test", "hostname": "test", "client": "test"}""") diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 0edc3aef..3f1a9176 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> + android:theme="@style/AppTheme.NoActionBar" + android:exported="true"> @@ -48,7 +48,7 @@ + android:exported="true"> @@ -56,7 +56,8 @@ + android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" + android:exported="true"> diff --git a/mobile/src/main/java/net/activitywatch/android/fragments/WebUIFragment.kt b/mobile/src/main/java/net/activitywatch/android/fragments/WebUIFragment.kt index 504a9fe9..eaa59381 100644 --- a/mobile/src/main/java/net/activitywatch/android/fragments/WebUIFragment.kt +++ b/mobile/src/main/java/net/activitywatch/android/fragments/WebUIFragment.kt @@ -61,7 +61,7 @@ class WebUIFragment : Fragment() { // TODO: Find way to not show the blinking Android error page Log.e(TAG, "WebView received error: $description") arguments?.let { - myWebView.loadUrl(it.getString(ARG_URL)) + it.getString(ARG_URL)?.let { it1 -> myWebView.loadUrl(it1) } } } } @@ -76,7 +76,7 @@ class WebUIFragment : Fragment() { myWebView.settings.javaScriptEnabled = true myWebView.settings.domStorageEnabled = true arguments?.let { - myWebView.loadUrl(it.getString(ARG_URL)) + it.getString(ARG_URL)?.let { it1 -> myWebView.loadUrl(it1) } } return view diff --git a/mobile/src/main/java/net/activitywatch/android/watcher/ChromeWatcher.kt b/mobile/src/main/java/net/activitywatch/android/watcher/ChromeWatcher.kt index fa8452bd..a0b52133 100644 --- a/mobile/src/main/java/net/activitywatch/android/watcher/ChromeWatcher.kt +++ b/mobile/src/main/java/net/activitywatch/android/watcher/ChromeWatcher.kt @@ -58,14 +58,14 @@ class ChromeWatcher : AccessibilityService() { try { if (event != null && event.source != null) { // Get URL - val urlBars = event.source.findAccessibilityNodeInfosByViewId("com.android.chrome:id/url_bar") + val urlBars = event.source!!.findAccessibilityNodeInfosByViewId("com.android.chrome:id/url_bar") if (urlBars.any()) { val newUrl = "http://" + urlBars[0].text.toString() // TODO: We can't access the URI scheme, so we assume HTTP. onUrl(newUrl) } // Get title - var webView = findWebView(event.source) + var webView = findWebView(event.source!!) if (webView != null) { lastTitle = webView.text.toString() Log.i(TAG, "Title: ${lastTitle}") @@ -73,7 +73,7 @@ class ChromeWatcher : AccessibilityService() { } } catch(ex : Exception) { - Log.e(TAG, ex.message) + Log.e(TAG, ex.message!!) } } diff --git a/mobile/src/main/java/net/activitywatch/android/watcher/UsageStatsWatcher.kt b/mobile/src/main/java/net/activitywatch/android/watcher/UsageStatsWatcher.kt index 8e04fc8f..805d1e86 100644 --- a/mobile/src/main/java/net/activitywatch/android/watcher/UsageStatsWatcher.kt +++ b/mobile/src/main/java/net/activitywatch/android/watcher/UsageStatsWatcher.kt @@ -1,5 +1,6 @@ package net.activitywatch.android.watcher +import android.annotation.SuppressLint import android.app.AlarmManager import android.app.AppOpsManager import android.app.PendingIntent @@ -85,7 +86,7 @@ class UsageStatsWatcher constructor(val context: Context) { alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager alarmIntent = Intent(context, AlarmReceiver::class.java).let { intent -> intent.action = "net.activitywatch.android.watcher.LOG_DATA" - PendingIntent.getBroadcast(context, 0, intent, 0) + PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } val interval = AlarmManager.INTERVAL_HOUR // Or if testing: AlarmManager.INTERVAL_HOUR / 60 diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index bda38f7d..fd66293d 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -21,8 +21,8 @@ Click me to log data! Allows ActivityWatch to read the URL and title from your browser. - - unknown version + Hello blank fragment From 7fa95b750f748af25b32224555011825318cf92b Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Sun, 6 Nov 2022 17:12:53 +0530 Subject: [PATCH 03/11] Implement a screenshot instrumentation test --- .../activitywatch/android/ScreenshotTest.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt diff --git a/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt b/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt new file mode 100644 index 00000000..7595a156 --- /dev/null +++ b/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt @@ -0,0 +1,37 @@ +package net.activitywatch.android + +import androidx.test.core.app.takeScreenshot +import androidx.test.core.graphics.writeToTestStorage +import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.ext.junit.rules.activityScenarioRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestName +import java.io.IOException + +/* + * When this test is executed via gradle managed devices, the saved image files will be stored at + * build/outputs/managed_device_android_test_additional_output/debugAndroidTest/managedDevice/nexusOneApi30/ + */ +class ScreenshotTest { + + // a handy JUnit rule that stores the method name, so it can be used to generate unique + // screenshot files per test method + @get:Rule + var nameRule = TestName() + + @get:Rule + val activityScenarioRule = activityScenarioRule() + + /** + * Captures and saves an image of the entire device screen to storage. + */ + @Test + @Throws(IOException::class) + fun saveDeviceScreenBitmap() { + // TODO: Not a good method to sleep, need to properly hook on page load + Thread.sleep(2000) + takeScreenshot() + .writeToTestStorage("${javaClass.simpleName}_${nameRule.methodName}") + } +} \ No newline at end of file From a3b55db0e5b33d7f2d59ea7fac042ed4a7af68a1 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Tue, 8 Nov 2022 19:04:03 +0530 Subject: [PATCH 04/11] Inst + ci test (#4) * Test Screenshot ci (#2) - grant test app permissions in advance - fix SettingsWatcher to properly get Premissions if allowed --- .github/workflows/build.yml | 337 +++++++++++++++++- Makefile | 4 +- mobile/build.gradle | 18 +- .../activitywatch/android/ScreenshotTest.kt | 19 +- .../android/watcher/UsageStatsWatcher.kt | 37 +- 5 files changed, 388 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f63ab047..6bb02e7c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ on: jobs: build: - name: ${{ matrix.os }}, java-${{ matrix.java_version }}, node-${{ matrix.node_version }} + name: Build ${{ matrix.os }}, java-${{ matrix.java_version }}, node-${{ matrix.node_version }} runs-on: ${{ matrix.os }} env: SUPPLY_TRACK: production # used by fastlane to determine track to publish to @@ -18,7 +18,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-20.04] - java_version: [1.8] + java_version: [11] node_version: [16] ruby_version: ['3.0'] ndk_version: ['21.4.7075529'] @@ -158,6 +158,7 @@ jobs: - name: Install age uses: adnsio/setup-age-action@v1.2.0 - name: Load secrets + if: env.KEY_ANDROID_JKS != null env: KEY_ANDROID_JKS: ${{ secrets.KEY_ANDROID_JKS }} run: | @@ -180,6 +181,338 @@ jobs: name: aw-android path: dist/aw-android*.apk + test: + name: Test ${{ matrix.android_avd }} #-${{ matrix.os }}-eAPI-${{ matrix.android_emu_version }}-java-${{ matrix.java_version }}-node-${{ matrix.node_version }} + runs-on: ${{ matrix.os }} + env: + SUPPLY_TRACK: production # used by fastlane to determine track to publish to + MATRIX_E_SDK: ${{ matrix.android_emu_version }} + MATRIX_AVD: ${{ matrix.android_avd }} + strategy: + fail-fast: false + max-parallel: 1 + matrix: + os: [macos-12] # macOS-latest, + android_emu_version: [27] #29, 31, 32] + # android_avd: [Pixel_API_29_AOSP] + java_version: [11] + node_version: [16] + ruby_version: ['3.0'] + rust_build: [false] # if true, will build aw-server-rust, otherwise will fetch artifact from recent CI run + skip_webui: [true] + include: + - android_avd: Pixel_API_27_AOSP + android_emu_version: 27 + # # # Cannot run > 27-emuLevel -_- https://github.com/actions/runner-images/issues/6527 + # - android_avd: Pixel_API_29_AOSP + # android_emu_version: 29 + # - android_avd: Pixel_API_31_AOSP + # android_emu_version: 31 + # - android_avd: Pixel_API_32_AOSP + # android_emu_version: 32 + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + # # # Below code is majorly from https://github.com/actions/runner-images/issues/6152#issuecomment-1243718140 + - name: Create Android emulator + run: | + brew install intel-haxm + # Install AVD files + echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install 'system-images;android-'$MATRIX_E_SDK';default;x86_64' + echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --licenses + + # Create emulator + $ANDROID_HOME/tools/bin/avdmanager create avd -n $MATRIX_AVD -d pixel --package 'system-images;android-'$MATRIX_E_SDK';default;x86_64' + $ANDROID_HOME/emulator/emulator -list-avds + if false; then + emulator_config=~/.android/avd/$MATRIX_AVD.avd/config.ini + # The following madness is to support empty OR populated config.ini files, + # the state of which is dependant on the version of the emulator used (which we don't control), + # so let's be defensive to be safe. + # Replace existing config (NOTE we're on MacOS so sed works differently!) + sed -i .bak 's/hw.lcd.density=.*/hw.lcd.density=420/' "$emulator_config" + sed -i .bak 's/hw.lcd.height=.*/hw.lcd.height=1920/' "$emulator_config" + sed -i .bak 's/hw.lcd.width=.*/hw.lcd.width=1080/' "$emulator_config" + # Or, add new config + if ! grep -q "hw.lcd.density" "$emulator_config"; then + echo "hw.lcd.density=420" >> "$emulator_config" + fi + if ! grep -q "hw.lcd.height" "$emulator_config"; then + echo "hw.lcd.height=1920" >> "$emulator_config" + fi + if ! grep -q "hw.lcd.width" "$emulator_config"; then + echo "hw.lcd.width=1080" >> "$emulator_config" + fi + echo "Emulator settings ($emulator_config)" + cat "$emulator_config" + fi + + - name: Start Android emulator + timeout-minutes: 30 # ~4min normal - 3x DOSafety + env: + SUFFIX: ${{ matrix.android_avd }}-eAPI-${{ matrix.android_emu_version }}-${{ matrix.os }} + HOMEBREW_NO_INSTALL_CLEANUP: 1 + run: | + echo "Starting emulator and waiting for boot to complete...." + ls -la $ANDROID_HOME/emulator + nohup $ANDROID_HOME/tools/emulator -avd $MATRIX_AVD -gpu host -no-audio -no-boot-anim -camera-back none -camera-front none -qemu -m 2048 2>&1 & + $ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do echo "wait..."; sleep 1; done; input keyevent 82' + echo "Emulator has finished booting" + $ANDROID_HOME/platform-tools/adb devices + sleep 30 + screencapture screenshot$SUFFIX.jpg + $ANDROID_HOME/platform-tools/adb exec-out screencap -p > emulator$SUFFIX.png + # # # Have to re-setup everything since we need to run emulator for faster performance on masOS ? Other os'es emulator will not startup ? + # TODO: Optimize the steps taking into consideration all software present by default on macOS runner image + # Build in release mode if: (longer build times) + # - on a tag (release) + # - on the master branch (nightly) + - name: Set RELEASE + run: | + echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> $GITHUB_ENV + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java_version }} + + # Android SDK & NDK + - name: Set up Android SDK + uses: android-actions/setup-android@v2 + + - name: Install NDK + run: sdkmanager "ndk;25.0.8775105" + + - name: Set up Node + uses: actions/setup-node@v1 + if: ${{ !matrix.skip_webui }} + with: + node-version: ${{ matrix.node_version }} + + - name: Set up Rust nightly + uses: actions-rs/toolchain@v1 + if: ${{ matrix.rust_build }} + with: + profile: minimal + toolchain: nightly + override: true + + - name: Set up Ruby + uses: actions/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby_version }} + + - uses: adnsio/setup-age-action@v1.2.0 + + # Set up caches + - name: Get npm cache dir + id: npm-cache-dir + if: ${{ !matrix.skip_webui }} + run: | + echo "::set-output name=dir::$(npm config get cache)" + + - uses: actions/cache@v1 + name: Cache npm + if: ${{ !matrix.skip_webui }} + env: + cache-name: node + with: + path: ${{ steps.npm-cache-dir.outputs.dir }} + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Cache cargo build + uses: actions/cache@v1 + if: ${{ matrix.rust_build }} + env: + cache-name: cargo-build-target + with: + path: aw-server-rust/target + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + # Install fastlane + - name: Install fastlane + run: | + gem install bundler + bundle install + + # Set up Rust toolchain + - name: Set up Rust toolchain for Android NDK + if: ${{ matrix.rust_build }} + run: | + ANDROID_NDK_HOME= + ./aw-server-rust/install-ndk.sh + + # Build webui + - name: Build webui + if: ${{ !matrix.skip_webui }} + run: | + make aw-webui + + # Build or fetch aw-server-rust artifacts + - name: Build aw-server-rust + if: ${{ matrix.rust_build }} + env: + ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + run: | + make aw-server-rust + + - name: Download artifact + uses: dawidd6/action-download-artifact@v2 + if: ${{ !matrix.rust_build }} + with: + repo: ActivityWatch/aw-server-rust + branch: master + workflow: build.yml + # Optional, uploaded artifact name, + # will download all artifacts if not specified + # and extract them in respective subdirectories + # https://github.com/actions/download-artifact#download-all-artifacts + name: binaries-android + path: aw-server-rust/target/ + # Optional, the status or conclusion of a completed workflow to search for + # Can be one of a workflow conculsion:: + # "failure", "success", "neutral", "cancelled", "skipped", "timed_out", "action_required" + # Or a workflow status: + # "completed", "in_progress", "queued" + # Default: "completed" + workflow_conclusion: success + + - name: Install aw-server-rust binaries + if: ${{ !matrix.rust_build }} + # TODO: These are only debug + develop something like "make install" for below + run: | + mkdir -p mobile/src/main/jniLibs/arm64-v8a/ + cp -f ./aw-server-rust/target/aarch64-linux-android/debug/libaw_server.so mobile/src/main/jniLibs/arm64-v8a/libaw_server.so + mkdir -p mobile/src/main/jniLibs/armeabi-v7a/ + cp -f ./aw-server-rust/target/armv7-linux-androideabi/debug/libaw_server.so mobile/src/main/jniLibs/armeabi-v7a/libaw_server.so + mkdir -p mobile/src/main/jniLibs/x86/ + cp -f ./aw-server-rust/target/i686-linux-android/debug/libaw_server.so mobile/src/main/jniLibs/x86/libaw_server.so + mkdir -p mobile/src/main/jniLibs/x86_64/ + cp -f ./aw-server-rust/target/x86_64-linux-android/debug/libaw_server.so mobile/src/main/jniLibs/x86_64/libaw_server.so + + - name: Set version + if: startsWith(github.ref, 'refs/tags/v') # only on runs triggered from tag + run: | + # Sets versionName, tail used to skip "v" at start of tag name + SHORT_VERSION=$(echo "${{ github.ref_name }}" | tail -c +2 -) + sed -i "s/versionName \".*\"/versionName \"$SHORT_VERSION\"/g" \ + mobile/build.gradle + bundle exec fastlane update_version + + - name: Load secrets + if: env.KEY_ANDROID_JKS != null + env: + KEY_ANDROID_JKS: ${{ secrets.KEY_ANDROID_JKS }} + run: | + printf "$KEY_ANDROID_JKS" > android.jks.key + cat android.jks.age | age -d -i android.jks.key -o android.jks + rm android.jks.key + + - name: Cache Assemble APK + env: + JKS_STOREPASS: ${{ secrets.KEY_ANDROID_JKS_STOREPASS }} + JKS_KEYPASS: ${{ secrets.KEY_ANDROID_JKS_KEYPASS }} + run: | + # TODO: Add related stuff in .travis.yml + # TODO: Allow building even if secrets not set + make dist/aw-android.apk + + # # # Test # # reactiveCircus is giving a black screenshot not working + # # # TODO: Take a screenshot of OS to confirm if its Emulator issue or testcode/androidsdk issue - or maybe the emulator is screen off ? + # # # https://github.com/ReactiveCircus/android-emulator-runner + # - name: Test Cache + # uses: reactivecircus/android-emulator-runner@v2 + # with: + # api-level: ${{ matrix.android_emu_version }} + # arch: x86_64 + # profile: Nexus 6 + # target: google_apis + # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim + # script: echo Meoooow ! + # - name: Test + # id: test + # uses: reactivecircus/android-emulator-runner@v2 + # with: + # api-level: ${{ matrix.android_emu_version }} + # arch: x86_64 + # profile: Nexus 6 + # target: google_apis + # emulator-options: -gpu swiftshader_indirect -noaudio -no-boot-anim -no-snapshot-save + # # Only running specific Instrumentation tests cause others are failing right now. TODO: Fix others + # script: ./gradlew connectedCheck -Pandroid.testInstrumentationRunnerArguments.class=net.activitywatch.android.ScreenshotTest --stacktrace + + # - name: Install recorder and record session + # env: + # SUFFIX: ${{ matrix.android_avd }}-eAPI-${{ matrix.android_emu_version }}-${{ matrix.os }} + # run: | + # brew install ffmpeg + # $ANDROID_HOME/tools/emulator -help-all + # # -logcat *:v + # # $ANDROID_HOME/tools/emulator -port 18725 -verbose -no-audio -gpu swiftshader_indirect -logcat *:v @$MATRIX_AVD & + # ffmpeg -f avfoundation -i 0 -t 120 out$SUFFIX.mov & + + - name: Test App + id: test + run: | + adb logcat -v color & + ./gradlew connectedCheck -Pandroid.testInstrumentationRunnerArguments.class=net.activitywatch.android.ScreenshotTest --stacktrace + # adb logcat -d + adb logcat > mobile/build/logcat.log + + - name: Screenshot + if: ${{ success() || steps.test.conclusion == 'failure'}} + env: + SUFFIX: ${{ matrix.android_avd }}-eAPI-${{ matrix.android_emu_version }}-${{ matrix.os }} + run: | + sleep 30 + screencapture pscreenshot$SUFFIX.jpg + $ANDROID_HOME/platform-tools/adb exec-out screencap -p > pemulator$SUFFIX.png + ls -alh + + - name: Upload Build artifact + if: ${{ success() || steps.test.conclusion == 'failure'}} + uses: actions/upload-artifact@v3 + with: + name: Build outputs + # mobile\build\outputs\connected_android_test_additional_output\debugAndroidTest\connected\Pixel_XL_API_32(AVD) - 12\ScreenshotTest_saveDeviceScreenBitmap.png + path: | + mobile/build/reports/* + mobile/build/logcat.log + + # - name: Upload video + # if: ${{ success() || steps.test.conclusion == 'failure'}} + # uses: actions/upload-artifact@master + # with: + # name: video + # path: ./*.mov # out.mov + + - uses: actions/upload-artifact@v3 + if: ${{ success() || steps.test.conclusion == 'failure'}} + with: + name: screenshots #.jpg + #screenshot.jpg + path: | + ./*.jpg + ./*.png + **/mobile/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/* + + - name: Publish Test Report + # # uses: mikepenz/action-junit-report@v3 + # # # # TODO: Format a little ? or Use some utility to confier GITHUB_STE_SUMMARY.html below into markdown before outputting it into $GITHUB_STEP_SUMMARY? + if: ${{ success() || steps.test.conclusion == 'failure'}} + # # with: + # # report_paths: '**/build/reports/*Tests/**/*.html' # '**/build/test-results/test/TEST-*.xml' + run: | + for file in ./mobile/build/reports/androidTests/connected/*.html; do cat $file >> GITHUB_STEP_SUMMARY.html ; done + # echo '' >> GITHUB_STEP_SUMMARY.html; + cat GITHUB_STEP_SUMMARY.html >> $GITHUB_STEP_SUMMARY + + release-fastlane: needs: [build] if: startsWith(github.ref, 'refs/tags/v') # only on runs triggered from tag diff --git a/Makefile b/Makefile index a932d55c..3de499d4 100644 --- a/Makefile +++ b/Makefile @@ -104,6 +104,6 @@ clean: test: bundle exec fastlane test - #- ./gradlew clean lint test - #- ./gradlew connectedAndroidTest || true + # ./gradlew clean lint test + # ./gradlew connectedAndroidTest # || true diff --git a/mobile/build.gradle b/mobile/build.gradle index 39506cee..1d95749d 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -3,13 +3,13 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 31 + compileSdkVersion 33 ndkVersion "21.4.7075529" defaultConfig { applicationId "net.activitywatch.android" minSdkVersion 24 - targetSdkVersion 31 + targetSdkVersion 33 // Set in CI on tagged commit versionName "0.10.0" @@ -37,8 +37,8 @@ android { } } compileOptions { - sourceCompatibility = "1.8" - targetCompatibility = 1.8 + sourceCompatibility = '1.8' + targetCompatibility = '1.8' } kotlinOptions { @@ -63,15 +63,15 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.annotation:annotation:1.1.0' + implementation 'androidx.annotation:annotation:1.5.0' - implementation 'com.google.android.material:material:1.3.0' + implementation 'com.google.android.material:material:1.7.0' implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1' testImplementation "junit:junit:4.13.2" diff --git a/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt b/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt index 7595a156..67376bd8 100644 --- a/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt +++ b/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt @@ -1,14 +1,18 @@ package net.activitywatch.android +import android.content.Intent +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.takeScreenshot import androidx.test.core.graphics.writeToTestStorage import androidx.test.espresso.matcher.ViewMatchers.* -import androidx.test.ext.junit.rules.activityScenarioRule +import androidx.test.rule.GrantPermissionRule import org.junit.Rule import org.junit.Test import org.junit.rules.TestName import java.io.IOException + /* * When this test is executed via gradle managed devices, the saved image files will be stored at * build/outputs/managed_device_android_test_additional_output/debugAndroidTest/managedDevice/nexusOneApi30/ @@ -18,19 +22,26 @@ class ScreenshotTest { // a handy JUnit rule that stores the method name, so it can be used to generate unique // screenshot files per test method @get:Rule - var nameRule = TestName() + var permissionRule = GrantPermissionRule.grant(android.Manifest.permission.PACKAGE_USAGE_STATS) @get:Rule - val activityScenarioRule = activityScenarioRule() + var nameRule = TestName() + lateinit var scenario: ActivityScenario /** * Captures and saves an image of the entire device screen to storage. */ @Test @Throws(IOException::class) fun saveDeviceScreenBitmap() { + + //Thread.sleep(100) + val intent = Intent(ApplicationProvider.getApplicationContext(), MainActivity::class.java) + // TODO: scenarios dont clean up automatically ? + scenario = ActivityScenario.launch(intent) + // TODO: Not a good method to sleep, need to properly hook on page load - Thread.sleep(2000) + Thread.sleep(5000) takeScreenshot() .writeToTestStorage("${javaClass.simpleName}_${nameRule.methodName}") } diff --git a/mobile/src/main/java/net/activitywatch/android/watcher/UsageStatsWatcher.kt b/mobile/src/main/java/net/activitywatch/android/watcher/UsageStatsWatcher.kt index 805d1e86..8ee7a267 100644 --- a/mobile/src/main/java/net/activitywatch/android/watcher/UsageStatsWatcher.kt +++ b/mobile/src/main/java/net/activitywatch/android/watcher/UsageStatsWatcher.kt @@ -1,6 +1,6 @@ package net.activitywatch.android.watcher -import android.annotation.SuppressLint +import android.Manifest import android.app.AlarmManager import android.app.AppOpsManager import android.app.PendingIntent @@ -10,24 +10,20 @@ import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager -import android.os.AsyncTask -import android.os.Handler -import android.os.Looper -import android.os.SystemClock +import android.os.* +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES import android.provider.Settings import android.util.Log import android.widget.Toast import net.activitywatch.android.RustInterface import net.activitywatch.android.models.Event import org.json.JSONObject -import java.text.SimpleDateFormat import org.threeten.bp.DateTimeUtils import org.threeten.bp.Instant -import java.lang.Thread.sleep import java.net.URL import java.text.ParseException - - +import java.text.SimpleDateFormat class UsageStatsWatcher constructor(val context: Context) { @@ -40,7 +36,27 @@ class UsageStatsWatcher constructor(val context: Context) { var lastUpdated: Instant? = null + // https://stackoverflow.com/a/54839499/4957939 + fun getUsageStatsPermissionsStatus(context: Context): PermissionStatus? { + if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) return PermissionStatus.CANNOT_BE_GRANTED + val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager + val mode = appOps.checkOpNoThrow( + AppOpsManager.OPSTR_GET_USAGE_STATS, + Process.myUid(), + context.packageName + ) + val granted = if (mode == AppOpsManager.MODE_DEFAULT) context.checkCallingOrSelfPermission( + Manifest.permission.PACKAGE_USAGE_STATS + ) == PackageManager.PERMISSION_GRANTED else mode == AppOpsManager.MODE_ALLOWED + return if (granted) PermissionStatus.GRANTED else PermissionStatus.DENIED + } + + enum class PermissionStatus { + GRANTED, DENIED, CANNOT_BE_GRANTED + } + fun isUsageAllowed(): Boolean { + // https://stackoverflow.com/questions/27215013/check-if-my-application-has-usage-access-enabled val applicationInfo: ApplicationInfo = try { context.packageManager.getApplicationInfo(context.packageName, 0) @@ -55,7 +71,8 @@ class UsageStatsWatcher constructor(val context: Context) { applicationInfo.uid, applicationInfo.packageName ) - return mode == AppOpsManager.MODE_ALLOWED + // TODO: Use either of below tests, but the 1st test is not working + return mode == AppOpsManager.MODE_ALLOWED || getUsageStatsPermissionsStatus(context) == PermissionStatus.GRANTED } private fun getUSM(): UsageStatsManager? { From 1ac31dfe23214af2a0e7b74627f14df4922219a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Thu, 24 Nov 2022 19:56:44 +0100 Subject: [PATCH 05/11] ci: streamlining e2e workflow to depend on build step --- .github/workflows/build.yml | 228 +++++++++--------------------------- Makefile | 28 ++++- 2 files changed, 78 insertions(+), 178 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6bb02e7c..84d80336 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -154,6 +154,10 @@ jobs: mobile/build.gradle bundle exec fastlane update_version + - name: Assemble debug & test APK + run: | + make build-apk-debug + # Install age & load secrets - name: Install age uses: adnsio/setup-age-action@v1.2.0 @@ -171,8 +175,6 @@ jobs: JKS_STOREPASS: ${{ secrets.KEY_ANDROID_JKS_STOREPASS }} JKS_KEYPASS: ${{ secrets.KEY_ANDROID_JKS_KEYPASS }} run: | - # TODO: Add related stuff in .travis.yml - # TODO: Allow building even if secrets not set make dist/aw-android.apk - name: Upload artifact @@ -181,11 +183,17 @@ jobs: name: aw-android path: dist/aw-android*.apk - test: - name: Test ${{ matrix.android_avd }} #-${{ matrix.os }}-eAPI-${{ matrix.android_emu_version }}-java-${{ matrix.java_version }}-node-${{ matrix.node_version }} + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: apks + path: dist/apk/ + + test-e2e: + needs: [build] + name: Test E2E ${{ matrix.android_avd }} #-${{ matrix.os }}-eAPI-${{ matrix.android_emu_version }}-java-${{ matrix.java_version }}-node-${{ matrix.node_version }} runs-on: ${{ matrix.os }} env: - SUPPLY_TRACK: production # used by fastlane to determine track to publish to MATRIX_E_SDK: ${{ matrix.android_emu_version }} MATRIX_AVD: ${{ matrix.android_avd }} strategy: @@ -193,27 +201,30 @@ jobs: max-parallel: 1 matrix: os: [macos-12] # macOS-latest, - android_emu_version: [27] #29, 31, 32] - # android_avd: [Pixel_API_29_AOSP] + android_avd: [Pixel_API_27_AOSP] java_version: [11] - node_version: [16] - ruby_version: ['3.0'] - rust_build: [false] # if true, will build aw-server-rust, otherwise will fetch artifact from recent CI run - skip_webui: [true] + ndk_version: ['21.4.7075529'] include: - android_avd: Pixel_API_27_AOSP android_emu_version: 27 # # # Cannot run > 27-emuLevel -_- https://github.com/actions/runner-images/issues/6527 - # - android_avd: Pixel_API_29_AOSP - # android_emu_version: 29 - # - android_avd: Pixel_API_31_AOSP - # android_emu_version: 31 # - android_avd: Pixel_API_32_AOSP # android_emu_version: 32 steps: - uses: actions/checkout@v2 with: submodules: 'recursive' + + # Will download all artifacts to path + - name: Download build artifacts + uses: actions/download-artifact@v3 + with: + name: apks + path: dist/apk + - name: Display structure of downloaded files + working-directory: dist + run: ls -R + # # # Below code is majorly from https://github.com/actions/runner-images/issues/6152#issuecomment-1243718140 - name: Create Android emulator run: | @@ -265,162 +276,6 @@ jobs: $ANDROID_HOME/platform-tools/adb exec-out screencap -p > emulator$SUFFIX.png # # # Have to re-setup everything since we need to run emulator for faster performance on masOS ? Other os'es emulator will not startup ? # TODO: Optimize the steps taking into consideration all software present by default on macOS runner image - # Build in release mode if: (longer build times) - # - on a tag (release) - # - on the master branch (nightly) - - name: Set RELEASE - run: | - echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> $GITHUB_ENV - - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: ${{ matrix.java_version }} - - # Android SDK & NDK - - name: Set up Android SDK - uses: android-actions/setup-android@v2 - - - name: Install NDK - run: sdkmanager "ndk;25.0.8775105" - - - name: Set up Node - uses: actions/setup-node@v1 - if: ${{ !matrix.skip_webui }} - with: - node-version: ${{ matrix.node_version }} - - - name: Set up Rust nightly - uses: actions-rs/toolchain@v1 - if: ${{ matrix.rust_build }} - with: - profile: minimal - toolchain: nightly - override: true - - - name: Set up Ruby - uses: actions/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby_version }} - - - uses: adnsio/setup-age-action@v1.2.0 - - # Set up caches - - name: Get npm cache dir - id: npm-cache-dir - if: ${{ !matrix.skip_webui }} - run: | - echo "::set-output name=dir::$(npm config get cache)" - - - uses: actions/cache@v1 - name: Cache npm - if: ${{ !matrix.skip_webui }} - env: - cache-name: node - with: - path: ${{ steps.npm-cache-dir.outputs.dir }} - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-${{ env.cache-name }}- - - - name: Cache cargo build - uses: actions/cache@v1 - if: ${{ matrix.rust_build }} - env: - cache-name: cargo-build-target - with: - path: aw-server-rust/target - key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-${{ env.cache-name }}- - - # Install fastlane - - name: Install fastlane - run: | - gem install bundler - bundle install - - # Set up Rust toolchain - - name: Set up Rust toolchain for Android NDK - if: ${{ matrix.rust_build }} - run: | - ANDROID_NDK_HOME= - ./aw-server-rust/install-ndk.sh - - # Build webui - - name: Build webui - if: ${{ !matrix.skip_webui }} - run: | - make aw-webui - - # Build or fetch aw-server-rust artifacts - - name: Build aw-server-rust - if: ${{ matrix.rust_build }} - env: - ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} - run: | - make aw-server-rust - - - name: Download artifact - uses: dawidd6/action-download-artifact@v2 - if: ${{ !matrix.rust_build }} - with: - repo: ActivityWatch/aw-server-rust - branch: master - workflow: build.yml - # Optional, uploaded artifact name, - # will download all artifacts if not specified - # and extract them in respective subdirectories - # https://github.com/actions/download-artifact#download-all-artifacts - name: binaries-android - path: aw-server-rust/target/ - # Optional, the status or conclusion of a completed workflow to search for - # Can be one of a workflow conculsion:: - # "failure", "success", "neutral", "cancelled", "skipped", "timed_out", "action_required" - # Or a workflow status: - # "completed", "in_progress", "queued" - # Default: "completed" - workflow_conclusion: success - - - name: Install aw-server-rust binaries - if: ${{ !matrix.rust_build }} - # TODO: These are only debug + develop something like "make install" for below - run: | - mkdir -p mobile/src/main/jniLibs/arm64-v8a/ - cp -f ./aw-server-rust/target/aarch64-linux-android/debug/libaw_server.so mobile/src/main/jniLibs/arm64-v8a/libaw_server.so - mkdir -p mobile/src/main/jniLibs/armeabi-v7a/ - cp -f ./aw-server-rust/target/armv7-linux-androideabi/debug/libaw_server.so mobile/src/main/jniLibs/armeabi-v7a/libaw_server.so - mkdir -p mobile/src/main/jniLibs/x86/ - cp -f ./aw-server-rust/target/i686-linux-android/debug/libaw_server.so mobile/src/main/jniLibs/x86/libaw_server.so - mkdir -p mobile/src/main/jniLibs/x86_64/ - cp -f ./aw-server-rust/target/x86_64-linux-android/debug/libaw_server.so mobile/src/main/jniLibs/x86_64/libaw_server.so - - - name: Set version - if: startsWith(github.ref, 'refs/tags/v') # only on runs triggered from tag - run: | - # Sets versionName, tail used to skip "v" at start of tag name - SHORT_VERSION=$(echo "${{ github.ref_name }}" | tail -c +2 -) - sed -i "s/versionName \".*\"/versionName \"$SHORT_VERSION\"/g" \ - mobile/build.gradle - bundle exec fastlane update_version - - - name: Load secrets - if: env.KEY_ANDROID_JKS != null - env: - KEY_ANDROID_JKS: ${{ secrets.KEY_ANDROID_JKS }} - run: | - printf "$KEY_ANDROID_JKS" > android.jks.key - cat android.jks.age | age -d -i android.jks.key -o android.jks - rm android.jks.key - - - name: Cache Assemble APK - env: - JKS_STOREPASS: ${{ secrets.KEY_ANDROID_JKS_STOREPASS }} - JKS_KEYPASS: ${{ secrets.KEY_ANDROID_JKS_KEYPASS }} - run: | - # TODO: Add related stuff in .travis.yml - # TODO: Allow building even if secrets not set - make dist/aw-android.apk # # # Test # # reactiveCircus is giving a black screenshot not working # # # TODO: Take a screenshot of OS to confirm if its Emulator issue or testcode/androidsdk issue - or maybe the emulator is screen off ? @@ -456,11 +311,35 @@ jobs: # # $ANDROID_HOME/tools/emulator -port 18725 -verbose -no-audio -gpu swiftshader_indirect -logcat *:v @$MATRIX_AVD & # ffmpeg -f avfoundation -i 0 -t 120 out$SUFFIX.mov & + # Java + # Needs to be set up after emulator is started, otherwise I get: + # java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema + # This seemes relevant: https://stackoverflow.com/a/58652345/965332 + #- name: Set up JDK + # uses: actions/setup-java@v1 + # with: + # java-version: ${{ matrix.java_version }} + + # Android SDK & NDK + #- name: Set up Android SDK + # uses: android-actions/setup-android@v2 + #- name: Install NDK + # run: | + # sdkmanager "ndk;${{ matrix.ndk_version }}" + # ANDROID_NDK_HOME="$ANDROID_SDK_ROOT/ndk/${{ matrix.ndk_version }}" + # ls $ANDROID_NDK_HOME + # echo "ANDROID_NDK_HOME=$ANDROID_NDK_HOME" >> $GITHUB_ENV + - name: Test App id: test run: | + adb install dist/apk/debug/mobile-debug.apk + adb install dist/apk/androidTest/debug/mobile-debug-androidTest.apk + adb shell pm list instrumentation adb logcat -v color & - ./gradlew connectedCheck -Pandroid.testInstrumentationRunnerArguments.class=net.activitywatch.android.ScreenshotTest --stacktrace + adb shell am instrument -w \ + -e class net.activitywatch.android.ScreenshotTest \ + net.activitywatch.android.debug.test/androidx.test.runner.AndroidJUnitRunner # adb logcat -d adb logcat > mobile/build/logcat.log @@ -512,7 +391,6 @@ jobs: # echo '' >> GITHUB_STEP_SUMMARY.html; cat GITHUB_STEP_SUMMARY.html >> $GITHUB_STEP_SUMMARY - release-fastlane: needs: [build] if: startsWith(github.ref, 'refs/tags/v') # only on runs triggered from tag @@ -526,8 +404,8 @@ jobs: path: dist - name: Display structure of downloaded files - run: ls -R working-directory: dist + run: ls -R # detect if version tag is stable/beta - uses: nowsprinting/check-version-format-action@v2 @@ -565,8 +443,8 @@ jobs: path: dist - name: Display structure of downloaded files - run: ls -R working-directory: dist + run: ls -R # detect if version tag is stable/beta - uses: nowsprinting/check-version-format-action@v2 diff --git a/Makefile b/Makefile index 3de499d4..2277c643 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,33 @@ SHELL := /bin/bash RELEASE_TYPE = $(shell $$RELEASE && echo 'release' || echo 'debug') HAS_SECRETS = $(shell test -n "$$JKS_KEYPASS" && echo 'true' || echo 'false') +APKDIR = mobile/build/outputs/apk + # Main targets all: aw-server-rust aw-webui build: all +# builds a complete, signed apk, puts it in dist build-apk: dist/aw-android.apk -dist/aw-android.apk: mobile/build/outputs/apk/release/mobile-release-unsigned.apk +# builds debug and test apks (unsigned) +build-apk-debug: $(APKDIR)/debug/mobile-debug.apk $(APKDIR)/androidTest/debug/mobile-debug-androidTest.apk + mkdir -p dist + cp -r $(APKDIR) dist + +$(APKDIR)/release/mobile-release-unsigned.apk: + TERM=xterm ./gradlew assembleRelease + tree $(APKDIR) + +$(APKDIR)/debug/mobile-debug.apk: + TERM=xterm ./gradlew assembleDebug + tree $(APKDIR) + +$(APKDIR)/androidTest/debug/mobile-debug-androidTest.apk: + TERM=xterm ./gradlew assembleAndroidTest + tree $(APKDIR) + +dist/aw-android.apk: $(APKDIR)/release/mobile-release-unsigned.apk @# TODO: Name the APK based on the version number or commit hash. mkdir -p dist @# Only sign if we have key secrets set ($JKS_KEYPASS and $JKS_STOREPASS) @@ -25,8 +45,10 @@ else ./scripts/sign_apk.sh $< $@ endif -mobile/build/outputs/apk/release/mobile-release-unsigned.apk: - TERM=xterm ./gradlew assembleRelease +# for mobile-debug.apk and mobile-debug-androidTest.apk +dist/debug/%: $(APKDIR)/debug/% + mkdir -p dist + cp $< $@ # aw-server-rust stuff From e5fa4d24e567588859f0ad08734c74e77f3d105e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Fri, 25 Nov 2022 01:41:25 +0100 Subject: [PATCH 06/11] ci: ensure we actually include jniLibs --- .github/workflows/build.yml | 17 +++++++++++------ Makefile | 11 ++++++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84d80336..a8452406 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -119,12 +119,7 @@ jobs: make aw-webui # Build or fetch aw-server-rust artifacts - - name: Build aw-server-rust - if: ${{ matrix.rust_build }} - run: | - make aw-server-rust - - - name: Download artifact + - name: Download prebuilt aw-server-rust Android libs uses: dawidd6/action-download-artifact@v2 if: ${{ !matrix.rust_build }} with: @@ -144,6 +139,16 @@ jobs: # "completed", "in_progress", "queued" # Default: "completed" workflow_conclusion: success + - name: Build aw-server-rust + env: + USE_PREBUILT: ${{ !matrix.rust_build }} + run: | + # will build if USE_PREBUILT is true, + # otherwise will just move files into the right place + make aw-server-rust + - name: Check that jniLibs present + run: | + test -e mobile/src/main/jniLibs/x86_64/libaw_server.so - name: Set version if: startsWith(github.ref, 'refs/tags/v') # only on runs triggered from tag diff --git a/Makefile b/Makefile index 2277c643..cb0fc7d6 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,16 @@ RUSTFLAGS_ANDROID="-C debuginfo=2 -Awarnings" $(RS_SRCDIR)/target/%/$(RELEASE_TYPE)/libaw_server.so: $(RS_SOURCES) echo $@ echo $(RELEASE_TYPE) - env RUSTFLAGS=$(RUSTFLAGS_ANDROID) make -C aw-server-rust android + # if we indicate in CI via USE_PREBUILT that we've + # fetched prebuilt libaw_server.so from aw-server-rust repo, + # then don't rebuild it + # also check libraries exist, if not, error + if [ $$USE_PREBUILT == "true" ] && [ -f $@ ]; then \ + echo "Using prebuilt libaw_server.so"; \ + else \ + echo "Building libaw_server.so from aw-server-rust repo"; \ + env RUSTFLAGS=$(RUSTFLAGS_ANDROID) make -C aw-server-rust android; \ + fi # aw-webui From 9c76704f6c0bd9db11fdb2cccfda91f3fcb7f9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Fri, 25 Nov 2022 02:11:40 +0100 Subject: [PATCH 07/11] ci: create missing directory --- .github/workflows/build.yml | 7 ++++--- Makefile | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a8452406..8051f479 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -336,6 +336,7 @@ jobs: # echo "ANDROID_NDK_HOME=$ANDROID_NDK_HOME" >> $GITHUB_ENV - name: Test App + timeout-minutes: 20 id: test run: | adb install dist/apk/debug/mobile-debug.apk @@ -345,8 +346,8 @@ jobs: adb shell am instrument -w \ -e class net.activitywatch.android.ScreenshotTest \ net.activitywatch.android.debug.test/androidx.test.runner.AndroidJUnitRunner - # adb logcat -d - adb logcat > mobile/build/logcat.log + mkdir -p mobile/build + adb logcat -d > mobile/build/logcat.log - name: Screenshot if: ${{ success() || steps.test.conclusion == 'failure'}} @@ -365,8 +366,8 @@ jobs: name: Build outputs # mobile\build\outputs\connected_android_test_additional_output\debugAndroidTest\connected\Pixel_XL_API_32(AVD) - 12\ScreenshotTest_saveDeviceScreenBitmap.png path: | - mobile/build/reports/* mobile/build/logcat.log + #mobile/build/reports/* # - name: Upload video # if: ${{ success() || steps.test.conclusion == 'failure'}} diff --git a/Makefile b/Makefile index cb0fc7d6..ecc34f47 100644 --- a/Makefile +++ b/Makefile @@ -98,12 +98,12 @@ RUSTFLAGS_ANDROID="-C debuginfo=2 -Awarnings" # This target runs multiple times because it's matched multiple times, not sure how to fix $(RS_SRCDIR)/target/%/$(RELEASE_TYPE)/libaw_server.so: $(RS_SOURCES) echo $@ - echo $(RELEASE_TYPE) - # if we indicate in CI via USE_PREBUILT that we've - # fetched prebuilt libaw_server.so from aw-server-rust repo, - # then don't rebuild it - # also check libraries exist, if not, error - if [ $$USE_PREBUILT == "true" ] && [ -f $@ ]; then \ + echo "Release type: $(RELEASE_TYPE)" + @# if we indicate in CI via USE_PREBUILT that we've + @# fetched prebuilt libaw_server.so from aw-server-rust repo, + @# then don't rebuild it + @# also check libraries exist, if not, error + @if [ $$USE_PREBUILT == "true" ] && [ -f $@ ]; then \ echo "Using prebuilt libaw_server.so"; \ else \ echo "Building libaw_server.so from aw-server-rust repo"; \ From a2fbf4909fa8d93087326af07f416862306c7157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Fri, 25 Nov 2022 14:47:10 +0100 Subject: [PATCH 08/11] ci: disable e2e report upload --- .github/workflows/build.yml | 20 +++++++++---------- .../activitywatch/android/ScreenshotTest.kt | 13 +++++++----- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8051f479..ea9ec18b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -386,16 +386,16 @@ jobs: ./*.png **/mobile/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/* - - name: Publish Test Report - # # uses: mikepenz/action-junit-report@v3 - # # # # TODO: Format a little ? or Use some utility to confier GITHUB_STE_SUMMARY.html below into markdown before outputting it into $GITHUB_STEP_SUMMARY? - if: ${{ success() || steps.test.conclusion == 'failure'}} - # # with: - # # report_paths: '**/build/reports/*Tests/**/*.html' # '**/build/test-results/test/TEST-*.xml' - run: | - for file in ./mobile/build/reports/androidTests/connected/*.html; do cat $file >> GITHUB_STEP_SUMMARY.html ; done - # echo '' >> GITHUB_STEP_SUMMARY.html; - cat GITHUB_STEP_SUMMARY.html >> $GITHUB_STEP_SUMMARY + #- name: Publish Test Report + # # # uses: mikepenz/action-junit-report@v3 + # # # # # TODO: Format a little ? or Use some utility to confier GITHUB_STE_SUMMARY.html below into markdown before outputting it into $GITHUB_STEP_SUMMARY? + # if: ${{ success() || steps.test.conclusion == 'failure'}} + # # # with: + # # # report_paths: '**/build/reports/*Tests/**/*.html' # '**/build/test-results/test/TEST-*.xml' + # run: | + # for file in ./mobile/build/reports/androidTests/connected/*.html; do cat $file >> GITHUB_STEP_SUMMARY.html ; done + # # echo '' >> GITHUB_STEP_SUMMARY.html; + # cat GITHUB_STEP_SUMMARY.html >> $GITHUB_STEP_SUMMARY release-fastlane: needs: [build] diff --git a/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt b/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt index 67376bd8..0031b46a 100644 --- a/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt +++ b/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt @@ -1,6 +1,7 @@ package net.activitywatch.android import android.content.Intent +import android.util.Log import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.takeScreenshot @@ -12,13 +13,13 @@ import org.junit.Test import org.junit.rules.TestName import java.io.IOException +private const val TAG = "ScreenshotTest" /* * When this test is executed via gradle managed devices, the saved image files will be stored at * build/outputs/managed_device_android_test_additional_output/debugAndroidTest/managedDevice/nexusOneApi30/ */ class ScreenshotTest { - // a handy JUnit rule that stores the method name, so it can be used to generate unique // screenshot files per test method @get:Rule @@ -34,7 +35,6 @@ class ScreenshotTest { @Test @Throws(IOException::class) fun saveDeviceScreenBitmap() { - //Thread.sleep(100) val intent = Intent(ApplicationProvider.getApplicationContext(), MainActivity::class.java) // TODO: scenarios dont clean up automatically ? @@ -42,7 +42,10 @@ class ScreenshotTest { // TODO: Not a good method to sleep, need to properly hook on page load Thread.sleep(5000) - takeScreenshot() - .writeToTestStorage("${javaClass.simpleName}_${nameRule.methodName}") + Log.i(TAG, "Taking screenshot") + + val bitmap = takeScreenshot() + bitmap.writeToTestStorage("${javaClass.simpleName}_${nameRule.methodName}") + Log.i(TAG, "Took screenshot!") } -} \ No newline at end of file +} From cbccd8b34e05c856c04c35d09d1714e373bda891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Fri, 25 Nov 2022 15:13:32 +0100 Subject: [PATCH 09/11] ci: put screenshots in seperate directory --- .github/workflows/build.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea9ec18b..50026255 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -277,8 +277,9 @@ jobs: echo "Emulator has finished booting" $ANDROID_HOME/platform-tools/adb devices sleep 30 - screencapture screenshot$SUFFIX.jpg - $ANDROID_HOME/platform-tools/adb exec-out screencap -p > emulator$SUFFIX.png + mkdir -p screenshots + screencapture screenshots/screenshot-$SUFFIX.jpg + $ANDROID_HOME/platform-tools/adb exec-out screencap -p > screenshots/emulator-$SUFFIX.png # # # Have to re-setup everything since we need to run emulator for faster performance on masOS ? Other os'es emulator will not startup ? # TODO: Optimize the steps taking into consideration all software present by default on macOS runner image @@ -354,12 +355,12 @@ jobs: env: SUFFIX: ${{ matrix.android_avd }}-eAPI-${{ matrix.android_emu_version }}-${{ matrix.os }} run: | - sleep 30 - screencapture pscreenshot$SUFFIX.jpg - $ANDROID_HOME/platform-tools/adb exec-out screencap -p > pemulator$SUFFIX.png + sleep 10 + screencapture screenshots/pscreenshot-$SUFFIX.jpg + $ANDROID_HOME/platform-tools/adb exec-out screencap -p > screenshots/pemulator-$SUFFIX.png ls -alh - - name: Upload Build artifact + - name: Upload logcat if: ${{ success() || steps.test.conclusion == 'failure'}} uses: actions/upload-artifact@v3 with: @@ -376,14 +377,13 @@ jobs: # name: video # path: ./*.mov # out.mov - - uses: actions/upload-artifact@v3 + - name: Upload screenshots + uses: actions/upload-artifact@v3 if: ${{ success() || steps.test.conclusion == 'failure'}} with: - name: screenshots #.jpg - #screenshot.jpg + name: screenshots path: | - ./*.jpg - ./*.png + screenshots/* **/mobile/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/* #- name: Publish Test Report From d6ed60d9e363a2d202d0f6ff30176217de8f8636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Fri, 25 Nov 2022 15:44:16 +0100 Subject: [PATCH 10/11] ci: more e2e ci fixing --- .github/workflows/build.yml | 5 +++-- .../java/net/activitywatch/android/ScreenshotTest.kt | 10 ++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 50026255..e582e488 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -355,16 +355,17 @@ jobs: env: SUFFIX: ${{ matrix.android_avd }}-eAPI-${{ matrix.android_emu_version }}-${{ matrix.os }} run: | + adb shell monkey -p net.activitywatch.android.debug 1 sleep 10 screencapture screenshots/pscreenshot-$SUFFIX.jpg $ANDROID_HOME/platform-tools/adb exec-out screencap -p > screenshots/pemulator-$SUFFIX.png - ls -alh + ls -alh screenshots/ - name: Upload logcat if: ${{ success() || steps.test.conclusion == 'failure'}} uses: actions/upload-artifact@v3 with: - name: Build outputs + name: logcat # mobile\build\outputs\connected_android_test_additional_output\debugAndroidTest\connected\Pixel_XL_API_32(AVD) - 12\ScreenshotTest_saveDeviceScreenBitmap.png path: | mobile/build/logcat.log diff --git a/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt b/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt index 0031b46a..35f7a004 100644 --- a/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt +++ b/mobile/src/androidTest/java/net/activitywatch/android/ScreenshotTest.kt @@ -28,13 +28,19 @@ class ScreenshotTest { @get:Rule var nameRule = TestName() - lateinit var scenario: ActivityScenario + private lateinit var scenario: ActivityScenario + + @Test + fun testScreenshot() { + saveDeviceScreenBitmap() + } + /** * Captures and saves an image of the entire device screen to storage. */ - @Test @Throws(IOException::class) fun saveDeviceScreenBitmap() { + Log.i(TAG, "Running saveDeviceScreenBitmap") //Thread.sleep(100) val intent = Intent(ApplicationProvider.getApplicationContext(), MainActivity::class.java) // TODO: scenarios dont clean up automatically ? From ed39a73d75d7b67c8fc3e8ad9a63b26a0438f164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Fri, 25 Nov 2022 18:27:18 +0100 Subject: [PATCH 11/11] ci: move logcat output to separate step to easier read output --- .github/workflows/build.yml | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e582e488..3da3ecbe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -280,6 +280,7 @@ jobs: mkdir -p screenshots screencapture screenshots/screenshot-$SUFFIX.jpg $ANDROID_HOME/platform-tools/adb exec-out screencap -p > screenshots/emulator-$SUFFIX.png + # # # Have to re-setup everything since we need to run emulator for faster performance on masOS ? Other os'es emulator will not startup ? # TODO: Optimize the steps taking into consideration all software present by default on macOS runner image @@ -317,25 +318,6 @@ jobs: # # $ANDROID_HOME/tools/emulator -port 18725 -verbose -no-audio -gpu swiftshader_indirect -logcat *:v @$MATRIX_AVD & # ffmpeg -f avfoundation -i 0 -t 120 out$SUFFIX.mov & - # Java - # Needs to be set up after emulator is started, otherwise I get: - # java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema - # This seemes relevant: https://stackoverflow.com/a/58652345/965332 - #- name: Set up JDK - # uses: actions/setup-java@v1 - # with: - # java-version: ${{ matrix.java_version }} - - # Android SDK & NDK - #- name: Set up Android SDK - # uses: android-actions/setup-android@v2 - #- name: Install NDK - # run: | - # sdkmanager "ndk;${{ matrix.ndk_version }}" - # ANDROID_NDK_HOME="$ANDROID_SDK_ROOT/ndk/${{ matrix.ndk_version }}" - # ls $ANDROID_NDK_HOME - # echo "ANDROID_NDK_HOME=$ANDROID_NDK_HOME" >> $GITHUB_ENV - - name: Test App timeout-minutes: 20 id: test @@ -343,12 +325,16 @@ jobs: adb install dist/apk/debug/mobile-debug.apk adb install dist/apk/androidTest/debug/mobile-debug-androidTest.apk adb shell pm list instrumentation - adb logcat -v color & adb shell am instrument -w \ -e class net.activitywatch.android.ScreenshotTest \ net.activitywatch.android.debug.test/androidx.test.runner.AndroidJUnitRunner + + - name: Output and save logcat to file + if: ${{ success() || steps.test.conclusion == 'failure'}} + run: | mkdir -p mobile/build adb logcat -d > mobile/build/logcat.log + adb logcat -v color & - name: Screenshot if: ${{ success() || steps.test.conclusion == 'failure'}}