version: 2.1
orbs:
  android: circleci/android@0.2.1
  sonarcloud: sonarsource/sonarcloud@1.0.2

#######################
# Commands section
# For code reuse.
#######################
commands:
  install-ndk: android/install-ndk
  restore-android-build-cache: android/restore-build-cache
  save-android-build-cache: android/save-build-cache
  scan-sonar: sonarcloud/scan

  restore-gradle-cache:
    description: "Restore gradle caches"
    steps:
      - restore_cache:
          key: gradle-v1-{{ arch }}-{{ checksum "build.gradle" }}-{{ checksum  "Corona-Warn-App/build.gradle" }}-{{ checksum  "Server-Protocol-Buffer/build.gradle" }}

  save-gradle-cache:
    description: "Save gradle caches"
    steps:
      - save_cache:
          paths:
            - ~/.gradle
          key: gradle-v1-{{ arch }}-{{ checksum "build.gradle" }}-{{ checksum  "Corona-Warn-App/build.gradle" }}-{{ checksum  "Server-Protocol-Buffer/build.gradle" }}

  restore-android-build-cache-macos:
    description: "Restore Android build caches on macOS"
    steps:
      - restore_cache:
          key: android-buildcache-v1-{{ arch }}

  save-android-build-cache-macos:
    description: "Save Android build caches on macOS"
    steps:
      - save_cache:
          paths:
            - ~/.android/build-cache
            - ~/.android/cache
          key: android-buildcache-v1-{{ arch }}-{{ epoch }}

  run-gradle-cmd:
    description: "Running gradle command with environment options"
    parameters:
      desc:
        type: string
        default: "Running gradle command"
      cmd:
        type: string
    steps:
      - run:
          name: << parameters.desc >>
          command: >
            ./gradlew -PdisablePreDex
            << parameters.cmd >>
          no_output_timeout: 30m
          environment:
            JVM_OPTS: -Xmx4096m
            GRADLE_OPTS: >
              -Xmx1536m -XX:+HeapDumpOnOutOfMemoryError
              -Dorg.gradle.caching=true
              -Dorg.gradle.configureondemand=true
              -Dkotlin.compiler.execution.strategy=in-process
              -Dkotlin.incremental=false

  run-gradle-cmd-test-splitting:
    description: "Running gradle command with environment options and test splitting"
    parameters:
      desc:
        type: string
        default: "Running gradle command"
      cmd:
        type: string
    steps:
      - run:
          name: Test splitting output
          command: circleci tests glob "**/test*/**/*.kt" | circleci tests split | xargs -n 1 echo
      - run:
          name: << parameters.desc >>
          command: >
            ./gradlew -PdisablePreDex
            << parameters.cmd >>
            -i -PtestFilter="`circleci tests glob "**/test*/**/*.kt" | circleci tests split`"
          environment:
            JVM_OPTS: -Xmx4096m
            GRADLE_OPTS: >
              -Xmx1536m -XX:+HeapDumpOnOutOfMemoryError
              -Dorg.gradle.caching=true
              -Dorg.gradle.configureondemand=true
              -Dkotlin.compiler.execution.strategy=in-process
              -Dkotlin.incremental=false

  skip-for-external-pull-requests:
    description: "Skip for external pull requests due to missing access to secrets."
    steps:
      - run:
          name: Early return if this build is from a forked PR
          command: |
            if [ -n "$CIRCLE_PR_NUMBER" ]; then
              echo "Nothing to do for forked PRs, so marking this step successful"
              circleci step halt
            fi

  setup-android-macos:
    description: "Setup Android environment on macOS executor"
    steps:
      - restore_cache:
          key: android-sdk-v1-{{ arch }}-{{ checksum ".circleci/install-android-sdk.sh" }}
      - run:
          name: Set ANDROID_SDK_ROOT environment variable
          command: echo 'export ANDROID_SDK_ROOT=$HOME/android-sdk'  >> $BASH_ENV
      - run:
          name: Install Android SDK
          command: |
            sh .circleci/install-android-sdk.sh
            echo 'export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH' >> $BASH_ENV
            echo 'export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest:$PATH' >> $BASH_ENV
            echo 'export PATH=$ANDROID_SDK_ROOT/platform-tools:$PATH' >> $BASH_ENV
            echo 'export PATH=$ANDROID_SDK_ROOT/emulator:$PATH' >> $BASH_ENV
            echo 'export PATH=$ANDROID_SDK_ROOT/build-tools/29.0.3:$PATH' >> $BASH_ENV
            source $BASH_ENV
            sdkmanager --list
      - save_cache:
          key: android-sdk-v1-{{ arch }}-{{ checksum ".circleci/install-android-sdk.sh" }}
          paths:
            - /Users/distiller/android-sdk

  run-emulator-for-api:
    parameters:
      apilevel:
        type: integer
    description: "Setup and start emulator for API<< parameters.apilevel >>"
    steps:
      - run:
          name: Create emulator AVD
          command: >
            echo "no" | avdmanager --verbose create avd --force
            --name "emulator_API<< parameters.apilevel >>"
            --package "system-images;android-<< parameters.apilevel >>;google_apis;x86_64"
      - run:
          name: Configure emulator settings
          command: |
            cd $HOME/.android/avd/emulator_API<< parameters.apilevel >>.avd
            sed -i '' -e 's/hw.lcd.density=[0-9]*/hw.lcd.density=560/g' config.ini
            sed -i '' -e 's/hw.lcd.height=[0-9]*/hw.lcd.height=2880/g' config.ini
            sed -i '' -e 's/hw.lcd.width=[0-9]*/hw.lcd.width=1440/g' config.ini
            sed -i '' -e 's/hw.ramSize=[0-9]*/hw.ramSize=1536/g' config.ini
      - run:
          name: Check host state
          command: |
            emulator -accel-check
            emulator -version
      - run:
          name: Start emulator AVD
          command: >
            emulator @emulator_API<< parameters.apilevel >>
            -no-window
            -no-audio
            -no-boot-anim
            -memory 2048
            -nojni
          background: true
          no_output_timeout: 60m
      - run:
          name: Wait for emulator
          command: |
            adb wait-for-device shell 'while [[ -z $(getprop dev.bootcomplete) ]]; do sleep 1; done;'
            adb devices
            sleep 5
      - run:
          name: Disable animations
          command: |
            adb shell settings put global window_animation_scale 0
            adb shell settings put global transition_animation_scale 0
            adb shell settings put global animator_duration_scale 0

  kill-all-emulators:
    description: "Kill all emulators"
    steps:
      - run:
          name: Kill all emulators
          command: |
            adb devices | grep emulator | cut -f1 | while read line; do adb -s $line emu kill; done
            sleep 2
            adb kill-server
            sleep 2

  compress-path:
    parameters:
      input:
        type: string
      output:
        type: string
    description: "Compress << parameters.input >> to << parameters.output >>"
    steps:
      - run:
          name: Compress files
          command: >
            zip -r
            << parameters.output >>
            << parameters.input >>



  setup-google-cloud:
    description: "Setup Google Cloud access."
    steps:
      - run:
          name: Store Google Service Account
          command: echo $GCLOUD_SERVICE_KEY_BASE64 | base64 -di > ${HOME}/gcloud-service-key.json
      - run:
          name: Authorize gcloud and set config defaults
          command: |
            sudo gcloud auth activate-service-account --key-file=${HOME}/gcloud-service-key.json
            sudo gcloud --quiet config set project ${GOOGLE_PROJECT_ID}

#######################
# Jobs section
# Tasks that get executed
#######################
jobs:
  detekt:
    executor: android/android
    resource_class: large
    working_directory: ~/project
    steps:
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd:
          desc: Detekt check
          cmd: ":Corona-Warn-App:detekt"
      - store_artifacts:
          path: Corona-Warn-App/build/reports
          destination: reports

  quick_build_device_release_no_tests:
    executor: android/android
    resource_class: xlarge
    working_directory: ~/project
    steps:
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd:
          desc: Quick Build
          cmd: "assembleDeviceRelease"
      - save-gradle-cache
      - save-android-build-cache
      - store_artifacts:
          path: Corona-Warn-App/build/reports
          destination: reports

  quick_build_device_for_testers_release_no_tests:
    executor: android/android
    resource_class: xlarge
    working_directory: ~/project
    steps:
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd:
          desc: Quick Build
          cmd: ":Corona-Warn-App:assembleDeviceForTestersRelease"
      - save-gradle-cache
      - save-android-build-cache
      - store_artifacts:
          path: Corona-Warn-App/build/reports
          destination: reports

  unit_tests_device_release:
    executor: android/android
    resource_class: xlarge
    working_directory: ~/project
    parallelism: 3
    steps:
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd-test-splitting:
          desc: Unit tests
          cmd: ":Corona-Warn-App:testDeviceReleaseUnitTest -i"
      - save-gradle-cache
      - save-android-build-cache
      - store_test_results:
          path: Corona-Warn-App/build/test-results
      - persist_to_workspace:
          root: /home/circleci
          paths:
            - ./project
      - compress-path:
          input: ./Corona-Warn-App/build/reports
          output: /tmp/unit_tests_device_release.zip
      - store_artifacts:
          path: /tmp/unit_tests_device_release.zip
          destination: zips/unit_tests_device_release.zip

  unit_tests_device_for_testers_release:
    executor: android/android
    resource_class: xlarge
    working_directory: ~/project
    parallelism: 3
    steps:
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd-test-splitting:
          desc: Unit tests with splitting
          cmd: ":Corona-Warn-App:testDeviceForTestersReleaseUnitTest"
      - save-gradle-cache
      - save-android-build-cache
      - store_test_results:
          path: Corona-Warn-App/build/test-results
      - compress-path:
          input: ./Corona-Warn-App/build/reports
          output: /tmp/unit_tests_device_for_testers_release.zip
      - store_artifacts:
          path: /tmp/unit_tests_device_for_testers_release.zip
          destination: zips/unit_tests_device_for_testers_release.zip

  lint_device_release_check:
    executor: android/android
    resource_class: xlarge
    working_directory: ~/project
    steps:
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd:
          desc: Lint check deviceRelease
          cmd: ":Corona-Warn-App:lintDeviceRelease -i"
      - store_artifacts:
          path: Corona-Warn-App/build/reports
          destination: reports

  lint_device_for_testers_release_check:
    executor: android/android
    resource_class: xlarge
    working_directory: ~/project
    steps:
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd:
          desc: Lint check deviceForTestersRelease
          cmd: ":Corona-Warn-App:lintDeviceForTestersRelease -i"
      - store_artifacts:
          path: Corona-Warn-App/build/reports
          destination: reports

  ktlint_check:
    executor: android/android
    resource_class: medium
    working_directory: ~/project
    steps:
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd:
          desc: Ktlint check
          cmd: ":Corona-Warn-App:ktlintCheck"
      - store_artifacts:
          path: Corona-Warn-App/build/reports
          destination: reports

  run_sonar:
    executor: android/android
    resource_class: xlarge
    working_directory: ~/project
    steps:
      - skip-for-external-pull-requests
      - attach_workspace:
          at: /home/circleci
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd:
          desc: JaCoCo report
          cmd: ":Corona-Warn-App:jacocoTestReportDeviceRelease -i"
      - scan-sonar

  quick_build_device_for_testers_signed:
    executor: android/android
    resource_class: xlarge
    working_directory: ~/project
    steps:
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run:
          name: Download Keystore
          command: |
            curl --header "Authorization: token $keystore_download_token" --header "Accept: application/vnd.github.v3.raw" --remote-name --location "$keystore_download_url$keystore_download_filename"
      - run:
          name: Download Environment Properties
          command: |
            curl --header "Authorization: token $keystore_download_token" --header "Accept: application/vnd.github.v3.raw" --remote-name --location "$environments_download_url$env_prop_download_filename"
      - run:
          name: Decrypt Keystore
          command: openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 -in $keystore_download_filename -out $keystore_filename -k $keystore_encrypt_key
      - run:
          name: Prepare commit hash
          command: |
            commit_hash=$(echo $CIRCLE_SHA1 | cut -c1-5)
            echo $commit_hash
            echo "" >> "./gradle.properties"
            echo "commit_hash=$commit_hash" >> "./gradle.properties"
      - run:
          name: Prepare keystore properties for Signing
          command: |
            echo "" >> "./keystore.properties"
            echo "deviceForTestersRelease.storePath=../$keystore_filename" >> "./keystore.properties"
            echo "deviceForTestersRelease.storePassword=$keystore_password" >> "./keystore.properties"
            echo "deviceForTestersRelease.keyAlias=$key_alias" >> "./keystore.properties"
            echo "deviceForTestersRelease.keyPassword=$key_password" >> "./keystore.properties"
      - run-gradle-cmd:
          desc: Quick Build
          cmd: ":Corona-Warn-App:assembleDeviceForTestersRelease"
      - save-gradle-cache
      - save-android-build-cache
      - store_artifacts:
          path: Corona-Warn-App/build/outputs/apk
          destination: apk
      - store_artifacts:
          path: Corona-Warn-App/build/reports
          destination: reports
      - run:
          name: Send to T-System
          command: |
            fileName=$(find Corona-Warn-App/build/outputs/apk/deviceForTesters/release -name '*Corona-Warn-App*.apk')
            curl --location --request POST $tsystems_upload_url \
            --header "Authorization: Bearer $tsystems_upload_bearer" \
            --form "file=@${fileName}" \

  instrumentation_tests_device:
    resource_class: xlarge
    docker:
      - image: circleci/android:api-28
    steps:
      - skip-for-external-pull-requests
      - setup-google-cloud
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd:
          desc: Build app APK
          cmd: ":Corona-Warn-App:assembleDeviceForTestersDebug"
      - run-gradle-cmd:
          desc: Build instrumentation test APK
          cmd: ":Corona-Warn-App:assembleDeviceForTestersDebugAndroidTest"
      - run:
          name: Setup Testlab environment
          command: |
            echo "export BUCKETDIR=\"`date "+%Y-%m-%d-%H:%M:%S:%3N"`-${RANDOM}\"" >> $BASH_ENV
            source $BASH_ENV
            echo "$BUCKETDIR is setup."
      - run:
          name: Test with Firebase Test Lab
          command: |
            echo "Using bucketdir $BUCKETDIR"
            sudo gcloud firebase test android run \
              --type instrumentation \
              --app Corona-Warn-App/build/outputs/apk/deviceForTesters/debug/*.apk \
              --test Corona-Warn-App/build/outputs/apk/androidTest/deviceForTesters/debug/*.apk \
              --results-dir ${BUCKETDIR} \
              --results-bucket ${GOOGLE_PROJECT_ID}-circleci-android \
              --environment-variables clearPackageData=true \
              --test-targets "notAnnotation testhelpers.Screenshot" \
              --timeout 20m \
              --device-ids flame \
              --os-version-ids 29 \
              --locales de_DE \
              --orientations portrait \
              --no-record-video
          no_output_timeout: 30m
      - run:
          name: Create directory to store test results
          command: mkdir firebase-results
          when: always
      - run:
          name: Install gsutil dependency and copy test results data
          command: |
            sudo pip install -U crcmod
            sudo gsutil -m cp -R -U gs://${GOOGLE_PROJECT_ID}-circleci-android/${BUCKETDIR}/flame* firebase-results
          when: always
      - store_test_results:
          path: ./firebase-results/flame-29-de_DE-portrait
      - compress-path:
          input: ./firebase-results/flame-29-de_DE-portrait/*.xml
          output: /tmp/instrumentation_tests_device.zip
      - store_artifacts:
          path: /tmp/instrumentation_tests_device.zip
          destination: zips/instrumentation_tests_device.zip
  # Keep it until Firebase Test Lab proves reliability
  device_screenshots:
    macos:
      xcode: "12.3.0"
    resource_class: large
    steps:
      - checkout
      - restore_cache:
          key: gem-cache-v1-{{ arch }}-{{ checksum "Gemfile.lock" }}
      - run: bundle check || bundle install --path vendor/bundle
      - save_cache:
          key: gem-cache-v1-{{ arch }}-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
      - setup-android-macos
      - restore-gradle-cache
      - restore-android-build-cache-macos
      - run-gradle-cmd:
          desc: Build APKs for screenshots
          cmd: >
            :Corona-Warn-App:assembleDebug
            :Corona-Warn-App:assembleAndroidTest
      - save-gradle-cache
      - save-android-build-cache-macos
      - run-emulator-for-api:
          apilevel: 29
      - run:
          name: Run fastlane screengrab
          command: |
            for i in {1..5}; do bundle exec fastlane screengrab && break || sleep 15; done
          no_output_timeout: 30m
      - kill-all-emulators
      - store_artifacts:
          path: fastlane/metadata/android
          destination: screenshots
      - compress-path:
          input: ./fastlane/metadata/android
          output: /tmp/device_screenshots.zip
      - store_artifacts:
          path: /tmp/device_screenshots.zip
          destination: zips/device_screenshots.zip

  firebase_screenshots:
    resource_class: xlarge
    docker:
      - image: circleci/android:api-28
    steps:
      - skip-for-external-pull-requests
      - setup-google-cloud
      - checkout
      - restore-gradle-cache
      - restore-android-build-cache
      - run-gradle-cmd:
          desc: Build APKs for screenshots
          cmd: >
            :Corona-Warn-App:assembleDebug
            :Corona-Warn-App:assembleAndroidTest
      - run:
          name: Setup Testlab environment
          command: |
            echo "export BUCKETDIR=\"`date "+%Y-%m-%d-%H:%M:%S:%3N"`-${RANDOM}\"" >> $BASH_ENV
            source $BASH_ENV
            echo "$BUCKETDIR is setup."
      - run:
          name: Test with Firebase Test Lab
          command: |
            echo "Using bucketdir $BUCKETDIR"
            sudo gcloud firebase test android run \
              --type instrumentation \
              --app Corona-Warn-App/build/outputs/apk/deviceForTesters/debug/*.apk \
              --test Corona-Warn-App/build/outputs/apk/androidTest/deviceForTesters/debug/*.apk \
              --results-dir ${BUCKETDIR} \
              --results-bucket ${GOOGLE_PROJECT_ID}-circleci-android \
              --environment-variables clearPackageData=true \
              --test-targets "annotation testhelpers.Screenshot" \
              --timeout 20m \
              --device-ids flame \
              --os-version-ids 29 \
              --locales de_DE,en_US \
              --orientations portrait \
              --no-record-video
          no_output_timeout: 30m
      - run:
          name: Create directory to store test results
          command: mkdir firebase-screenshots
          when: always
      - run:
          name: Install gsutil dependency and copy test results data
          command: |
            sudo pip install -U crcmod
            sudo gsutil -m cp -R -U gs://${GOOGLE_PROJECT_ID}-circleci-android/${BUCKETDIR}/flame* firebase-screenshots
            sudo chown -R circleci:circleci firebase-screenshots
          when: always
      - store_test_results:
          path: ./firebase-screenshots
      - run:
          name: Clean up pulled bucket
          command: |
            rm -rf firebase-screenshots/*/artifacts/sdcard
            rm -rf firebase-screenshots/*/test_cases
            rm -f firebase-screenshots/*/instrumentation.results
            rm -f firebase-screenshots/*/logcat
            rm -f firebase-screenshots/*/test_result_1.xml
            mkdir /tmp/screenshots
      - compress-path:
          input: ./firebase-screenshots
          output: /tmp/screenshots/CWA-Android-Screenshots-${CIRCLE_SHA1}.zip
      - store_artifacts:
          path: /tmp/screenshots
          destination: zips/

#######################
# Workflow section
# Job execution orders
#######################
workflows:
  version: 2
  check_buildtype_device:
    jobs:
      - detekt
      - lint_device_release_check
      - ktlint_check
      - quick_build_device_release_no_tests
      - unit_tests_device_release
      - run_sonar:
          requires:
            - unit_tests_device_release
      - instrumentation_tests_device:
          requires:
            - ktlint_check
            - detekt

  check_buildtype_device_for_testers:
    jobs:
      - detekt
      - lint_device_for_testers_release_check
      - ktlint_check
      - quick_build_device_for_testers_release_no_tests
      - unit_tests_device_for_testers_release

  signed_build:
    jobs:
      - quick_build_device_for_testers_signed:
          filters:
            tags:
              only:
                - /^v.*/
            branches:
              ignore: /.*/
      - firebase_screenshots:
          filters:
            tags:
              only:
                - /^v.*/
            branches:
              ignore: /.*/
