diff --git a/.github/workflows/check_tests.yaml b/.github/workflows/check_tests.yaml index d804b682..6500c54a 100644 --- a/.github/workflows/check_tests.yaml +++ b/.github/workflows/check_tests.yaml @@ -43,4 +43,23 @@ jobs: disk-size: 4G script: | adb root - ./gradlew :androidApp:connectedPlayStoreDebugAndroidTest + ./gradlew :androidApp:connectedPlayStoreDebugAndroidTest ; RESULT=$? + mkdir -p artifacts/ + adb pull /storage/emulated/0/Movies/ artifactsScreenRecording/ || true + exit $RESULT + - name: 'Upload screen recordings' + uses: actions/upload-artifact@v4 + if: always() + with: + name: baselineprofile-screen-recordings + path: | + artifactsScreenRecording/ + retention-days: 7 + - name: 'Upload logs' + uses: actions/upload-artifact@v4 + if: always() + with: + name: baselineprofile-logs + path: | + androidApp/outputs/androidTest-results/ + retention-days: 7 diff --git a/.github/workflows/new_daily_tag_play_store_internal_track.yaml b/.github/workflows/new_daily_tag_play_store_internal_track.yaml index 51410982..e3b0cb7c 100644 --- a/.github/workflows/new_daily_tag_play_store_internal_track.yaml +++ b/.github/workflows/new_daily_tag_play_store_internal_track.yaml @@ -47,7 +47,26 @@ jobs: disk-size: 4G script: | adb root - ./gradlew :androidApp:generatePlayStoreBaselineProfile + ./gradlew :androidApp:generatePlayStoreBaselineProfile ; RESULT=$? + mkdir -p artifacts/ + adb pull /storage/emulated/0/Movies/ artifactsScreenRecording/ || true + exit $RESULT + - name: 'Upload baseline profile screen recordings' + uses: actions/upload-artifact@v4 + if: always() + with: + name: baselineprofile-screen-recordings + path: | + artifactsScreenRecording/ + retention-days: 7 + - name: 'Upload baseline profile logs' + uses: actions/upload-artifact@v4 + if: always() + with: + name: baselineprofile-logs + path: | + androidBenchmark/outputs/androidTest-results/ + retention-days: 7 - name: "Build" run: ./gradlew :androidApp:bundlePlayStoreRelease - name: 'Upload .aab' diff --git a/.github/workflows/new_tag_release.yaml b/.github/workflows/new_tag_release.yaml index cfe13646..4e6767b8 100644 --- a/.github/workflows/new_tag_release.yaml +++ b/.github/workflows/new_tag_release.yaml @@ -247,6 +247,7 @@ jobs: - name: "Generate baseline profiles" id: baseline-profiles uses: reactivecircus/android-emulator-runner@v2 + continue-on-error: true with: api-level: 30 arch: x86_64 @@ -254,7 +255,26 @@ jobs: disk-size: 4G script: | adb root - ./gradlew :androidApp:generateNoneBaselineProfile + ./gradlew :androidApp:generateNoneBaselineProfile ; RESULT=$? + mkdir -p artifacts/ + adb pull /storage/emulated/0/Movies/ artifactsScreenRecording/ || true + exit $RESULT + - name: 'Upload baseline profile screen recordings' + uses: actions/upload-artifact@v4 + if: always() + with: + name: baselineprofile-android-screen-recordings + path: | + artifactsScreenRecording/ + retention-days: 7 + - name: 'Upload baseline profile logs' + uses: actions/upload-artifact@v4 + if: always() + with: + name: baselineprofile-android-logs + path: | + androidBenchmark/outputs/androidTest-results/ + retention-days: 7 - name: "Build" run: ./gradlew androidApp:assembleNoneRelease - name: 'Upload .apk' diff --git a/androidApp/src/androidTest/kotlin/com/artemchep/keyguard/test/BaseTest.kt b/androidApp/src/androidTest/kotlin/com/artemchep/keyguard/test/BaseTest.kt index b25ed469..1a01fd19 100644 --- a/androidApp/src/androidTest/kotlin/com/artemchep/keyguard/test/BaseTest.kt +++ b/androidApp/src/androidTest/kotlin/com/artemchep/keyguard/test/BaseTest.kt @@ -4,9 +4,14 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice +import com.artemchep.test.util.ScreenRecorderTestWatcher import org.junit.Before +import org.junit.Rule abstract class BaseTest { + @get:Rule + val screenRecorder = ScreenRecorderTestWatcher() + lateinit var device: UiDevice lateinit var context: Context diff --git a/androidBenchmark/src/main/java/com/artemchep/macrobenchmark/baselineprofile/BaselineProfileGenerator.kt b/androidBenchmark/src/main/java/com/artemchep/macrobenchmark/baselineprofile/BaselineProfileGenerator.kt index 5fb3c3a3..cd25ea66 100644 --- a/androidBenchmark/src/main/java/com/artemchep/macrobenchmark/baselineprofile/BaselineProfileGenerator.kt +++ b/androidBenchmark/src/main/java/com/artemchep/macrobenchmark/baselineprofile/BaselineProfileGenerator.kt @@ -6,6 +6,7 @@ import androidx.benchmark.macro.junit4.BaselineProfileRule import com.artemchep.macrobenchmark.PACKAGE_NAME import com.artemchep.test.feature.coreFeature import com.artemchep.test.feature.ensureMainScreen +import com.artemchep.test.util.ScreenRecorderTestWatcher import org.junit.Rule import org.junit.Test @@ -15,6 +16,9 @@ import org.junit.Test */ @RequiresApi(Build.VERSION_CODES.P) class BaselineProfileGenerator { + @get:Rule + val screenRecorder = ScreenRecorderTestWatcher() + @get:Rule val baselineProfileRule = BaselineProfileRule() diff --git a/androidTest/src/main/java/com/artemchep/test/util/ScreenRecorderTestWatcher.kt b/androidTest/src/main/java/com/artemchep/test/util/ScreenRecorderTestWatcher.kt new file mode 100644 index 00000000..dfa6a293 --- /dev/null +++ b/androidTest/src/main/java/com/artemchep/test/util/ScreenRecorderTestWatcher.kt @@ -0,0 +1,84 @@ +package com.artemchep.test.util + +import android.os.Environment +import androidx.annotation.WorkerThread +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import org.junit.AssumptionViolatedException +import org.junit.Rule +import org.junit.rules.TestName +import org.junit.rules.TestWatcher +import org.junit.runner.Description +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.Date + +class ScreenRecorderTestWatcher : TestWatcher() { + @JvmField + @Rule + var testName = TestName() + + override fun starting(description: Description) { + super.starting(description) + val instrumentation = InstrumentationRegistry.getInstrumentation() + val device = UiDevice.getInstance(instrumentation) + + val date = kotlin.run { + val simpleDateFormat = SimpleDateFormat("HHmmss") + simpleDateFormat.format(Date()) + } + val fileName = kotlin.run { + val className = description.className + .removePrefix("com.artemchep.keyguard.") + .replace(".", "_") + val methodName = description.methodName + "${date}_${className}_$methodName.mp4" + } + val filePath = Environment.getExternalStorageDirectory() + .resolve("Movies/") + .resolve(fileName) + .absolutePath + Thread { + device.startScreenRecord(filePath) + }.start() + } + + override fun skipped( + e: AssumptionViolatedException, + description: Description, + ) { + super.skipped(e, description) + } + + override fun finished(description: Description) { + super.finished(description) + val instrumentation = InstrumentationRegistry.getInstrumentation() + val device = UiDevice.getInstance(instrumentation) + Thread { + device.stopScreenRecord() + }.start() + } +} + +@WorkerThread +fun UiDevice.startScreenRecord(fileName: String) { + kotlin.runCatching { + executeShellCommand("rm $fileName") + } + + try { + executeShellCommand("screenrecord --bit-rate 4000000 $fileName") + } catch (e: IOException) { + e.printStackTrace() + } +} + +@WorkerThread +fun UiDevice.stopScreenRecord( +) { + // Send Ctrl+C command to the process. This stop the screen + // recorder gracefully. + kotlin.runCatching { + executeShellCommand("pkill -2 screenrecord") + } +} diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/settings/impl/SettingsRepositoryImpl.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/settings/impl/SettingsRepositoryImpl.kt index 09b4e485..d844a591 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/settings/impl/SettingsRepositoryImpl.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/settings/impl/SettingsRepositoryImpl.kt @@ -19,6 +19,7 @@ import com.artemchep.keyguard.common.service.settings.SettingsReadWriteRepositor import com.artemchep.keyguard.common.service.settings.entity.VersionLogEntity import com.artemchep.keyguard.common.service.settings.entity.of import com.artemchep.keyguard.common.service.settings.entity.toDomain +import com.artemchep.keyguard.platform.util.isRelease import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.datetime.Instant @@ -125,8 +126,13 @@ class SettingsRepositoryImpl( private val concealFieldsPref = store.getBoolean(KEY_CONCEAL_FIELDS, true) - private val allowScreenshotsPref = - store.getBoolean(KEY_ALLOW_SCREENSHOTS, false) + private val allowScreenshotsPref = kotlin.run { + // Be default allow screenshots when running a + // debug build. This is needed to allow recording of + // connected tests. + val default = !isRelease + store.getBoolean(KEY_ALLOW_SCREENSHOTS, default) + } private val checkPwnedPasswordsPref = store.getBoolean(KEY_CHECK_PWNED_PASSWORDS, true)