diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index faaf42e6b..ff9998bb7 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -6,18 +6,9 @@ - [ ] **Screenshots**: This PR includes screenshots or GIFs of the changes made or an explanation of why it does not - [ ] **Accessibility**: The code in this PR follows [accessibility best practices](https://github.com/mozilla-mobile/shared-docs/blob/master/android/accessibility_guide.md) or does not include any user facing features. In addition, it includes a screenshot of a successful [accessibility scan](https://play.google.com/store/apps/details?id=com.google.android.apps.accessibility.auditor&hl=en_US) to ensure no new defects are added to the product. -### QA - -- [x] **QA Needed** - -### To download an APK when reviewing a PR (after all CI tasks finished running): -1. Click on `Checks` at the top of the PR page. -2. Click on the `firefoxci-taskcluster` group on the left to expand all tasks. -3. Click on the `build-debug` task. -4. Click on `View task in Taskcluster` in the new `DETAILS` section. -5. The APK links should be on the right side of the screen, named for each CPU architecture. - -### GitHub Automation - - -Used by GitHub Actions. +### To download an APK when reviewing a PR: +The PR runs an Android build check (`run-build`) that builds a `forkRelease` variant of the app. If it succeeds, then we upload the apks (signed with debug keys) via Github actions. We also generate a comment with some instructions and a link to help you find the downloads. You can also follow the instructions below: +1. Click Details next to "run-build (pull_request_target)" after it finishes with a green checkmark. +2. Click the "Artifacts" drop-down near the top right of the page. +3. The apk links should be present in the drop-down menu. You can click on the suitable CPU architecture to download a zipped apk file. +4. Unzip the file and install the apk. diff --git a/.github/disabled_workflows/comment-on-pr.yml b/.github/disabled_workflows/comment-on-pr.yml new file mode 100644 index 000000000..9cb1ff193 --- /dev/null +++ b/.github/disabled_workflows/comment-on-pr.yml @@ -0,0 +1,24 @@ +name: PR comment +on: + pull_request_target: + types: [opened] + branches: + - fork +jobs: # Disabled because we cannot build changes from fork PRs using this repo's secrets due to Github limitations. So, the built apk will be from wrong code, so this is pointless. + comment-on-pr: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')" + steps: + - name: Comment on PR with link to checks page + uses: mshick/add-pr-comment@v1 + with: + message: | + ### Download the built apks + You can download the apks built by Github actions **after** the CI checks pass. + Please go to the checks page for this PR to find the zipped apk files under the artifacts drop-down, as seen in the example screenshot below. + + Note that you will have to click on the "Android build PR" tab on the left side to see the artifacts. + + + repo-token: ${{ secrets.GITHUB_TOKEN }} + allow-repeats: false diff --git a/.github/imgs/download-artifacts-screenshot.png b/.github/imgs/download-artifacts-screenshot.png new file mode 100644 index 000000000..fff039553 Binary files /dev/null and b/.github/imgs/download-artifacts-screenshot.png differ diff --git a/.github/workflows/android-build-pr.yml b/.github/workflows/android-build-pr.yml new file mode 100644 index 000000000..28c662c00 --- /dev/null +++ b/.github/workflows/android-build-pr.yml @@ -0,0 +1,115 @@ +name: Android build PR +on: + pull_request: + branches: + - fork +jobs: + run-build: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Install Android SDK with pieces Gradle skips + run: ./automation/iceraven/install-sdk.sh + - name: Create version name + run: echo "VERSION_NAME=$(git describe --tags HEAD)" >> $GITHUB_ENV + - name: Build forkRelease variant of app + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: app:assembleForkRelease -PversionName=${{ env.VERSION_NAME }} + + + run-testDebug: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Run tests + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: testDebug + + + run-detekt: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Run detekt + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: detekt + - name: Archive detekt results + uses: actions/upload-artifact@v2 + with: + name: detekt report + path: build/reports/detekt.html + + + run-ktlint: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Run ktlint + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: ktlint + + + run-lintDebug: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.pull_request.title), '[skip ci]')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Run lintDebug + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: lintDebug + - name: Archive lint results + uses: actions/upload-artifact@v2 + with: + name: lintDebug report + path: app/build/reports/lint-results-debug.html diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml new file mode 100644 index 000000000..9c3f7376c --- /dev/null +++ b/.github/workflows/android-build.yml @@ -0,0 +1,143 @@ +name: Android build +on: + push: + branches: + - fork +jobs: + run-build: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Install Android SDK with pieces Gradle skips + run: ./automation/iceraven/install-sdk.sh + - name: Create version name + run: echo "VERSION_NAME=$(git describe --tags HEAD)" >> $GITHUB_ENV + - name: Build forkRelease variant of app + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: app:assembleForkRelease -PversionName=${{ env.VERSION_NAME }} + - name: Create signed APKs + uses: abhijitvalluri/sign-apks@v0.8 + with: + releaseDirectory: app/build/outputs/apk/forkRelease/ + signingKeyBase64: ${{ secrets.DEBUG_SIGNING_KEY }} + alias: ${{ secrets.DEBUG_ALIAS }} + keyStorePassword: ${{ secrets.DEBUG_KEY_STORE_PASSWORD }} + keyPassword: ${{ secrets.DEBUG_KEY_PASSWORD }} + - name: Archive arm64 apk + uses: actions/upload-artifact@v2 + with: + name: app-arm64-v8a-forkRelease.apk + path: app/build/outputs/apk/forkRelease/app-arm64-v8a-forkRelease.apk + - name: Archive armeabi apk + uses: actions/upload-artifact@v2 + with: + name: app-armeabi-v7a-forkRelease.apk + path: app/build/outputs/apk/forkRelease/app-armeabi-v7a-forkRelease.apk + - name: Archive x86 apk + uses: actions/upload-artifact@v2 + with: + name: app-x86-forkRelease.apk + path: app/build/outputs/apk/forkRelease/app-x86-forkRelease.apk + - name: Archive x86_64 apk + uses: actions/upload-artifact@v2 + with: + name: app-x86_64-forkRelease.apk + path: app/build/outputs/apk/forkRelease/app-x86_64-forkRelease.apk + + + run-testDebug: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Run tests + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: testDebug + + + run-detekt: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Run detekt + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: detekt + - name: Archive detekt results + uses: actions/upload-artifact@v2 + with: + name: detekt report + path: build/reports/detekt.html + + + run-ktlint: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Run ktlint + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: ktlint + + + run-lintDebug: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Run lintDebug + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: lintDebug + - name: Archive lint results + uses: actions/upload-artifact@v2 + with: + name: lintDebug report + path: app/build/reports/lint-results-debug.html diff --git a/.github/workflows/release-automation.yml b/.github/workflows/release-automation.yml new file mode 100644 index 000000000..699325809 --- /dev/null +++ b/.github/workflows/release-automation.yml @@ -0,0 +1,111 @@ +name: Release Automation +on: + create: +jobs: + release-automation: + name: Create Release + runs-on: ubuntu-latest + if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')" + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Install Android SDK with pieces Gradle skips + run: ./automation/iceraven/install-sdk.sh + - name: Build forkRelease variant of app + uses: eskatos/gradle-command-action@v1 + with: + wrapper-cache-enabled: true + dependencies-cache-enabled: true + configuration-cache-enabled: true + arguments: app:assembleForkRelease -PversionName=${{ github.event.ref }} + - name: Create signed APKs + uses: abhijitvalluri/sign-apks@v0.8 + with: + releaseDirectory: app/build/outputs/apk/forkRelease/ + signingKeyBase64: ${{ secrets.DEBUG_SIGNING_KEY }} + alias: ${{ secrets.DEBUG_ALIAS }} + keyStorePassword: ${{ secrets.DEBUG_KEY_STORE_PASSWORD }} + keyPassword: ${{ secrets.DEBUG_KEY_PASSWORD }} + - name: Create changelog + run: | + PREVIOUS_RELEASE_TAG=$(git tag --list iceraven-* --sort=-creatordate | tail -n+2 | head -n 1) + + echo "## Automated release of version ${{ github.event.ref }} browser" >>temp_changelog.md + if [[ "$(git log | grep $PREVIOUS_RELEASE_TAG | wc -l)" != "0" ]] ; then + # There's a path from the previous release to now + echo "
" >>temp_changelog.md + echo "Click to expand" >>temp_changelog.md + echo " " >>temp_changelog.md + echo "This is an automated release, consisting of the following changes:" >>temp_changelog.md + echo "### Change log (commit history since previous release)" >>temp_changelog.md + echo " " >>temp_changelog.md + git log ${{ github.event.ref }}...$PREVIOUS_RELEASE_TAG --pretty='format:%C(auto)%h (%as) %s' | head -n 1000 >>temp_changelog.md + echo " " >>temp_changelog.md + echo " " >>temp_changelog.md + echo "
" >>temp_changelog.md + else + echo "This is an automated release, not directly descended from the previous release." >>temp_changelog.md + fi + echo "**NOTE**: @fork-maintainers, you can edit these auto-generated release notes with a more user-friendly summary of the key changes, if needed." >>temp_changelog.md + echo " " >>temp_changelog.md + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.event.ref }} + release_name: "Version ${{ github.event.ref }}" + draft: false + prerelease: false + body_path: temp_changelog.md + + - name: Upload arm64 apk + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: app/build/outputs/apk/forkRelease/app-arm64-v8a-forkRelease.apk + asset_name: ${{ github.event.ref }}-browser-arm64-v8a-forkRelease.apk + asset_content_type: application/vnd.android.package-archive + + + - name: Upload armeabi apk + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: app/build/outputs/apk/forkRelease/app-armeabi-v7a-forkRelease.apk + asset_name: ${{ github.event.ref }}-browser-armeabi-v7a-forkRelease.apk + asset_content_type: application/vnd.android.package-archive + + + - name: Upload x86 apk + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: app/build/outputs/apk/forkRelease/app-x86-forkRelease.apk + asset_name: ${{ github.event.ref }}-browser-x86-forkRelease.apk + asset_content_type: application/vnd.android.package-archive + + + - name: Upload x86_64 apk + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: app/build/outputs/apk/forkRelease/app-x86_64-forkRelease.apk + asset_name: ${{ github.event.ref }}-browser-x86_64-forkRelease.apk + asset_content_type: application/vnd.android.package-archive diff --git a/.mergify.yml b/.mergify.yml index de81dd583..73ed4d7c5 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,20 +1,7 @@ queue_rules: - name: default conditions: - - or: - - status-success=complete-pr - - and: - # For more context, see "Auto Merge" rules down below - - status-success=complete-push - - or: - - and: - - base~=^releases[_/].* - - or: - - head~=^automation/sync-strings-\d+ - - head~=^relbot/fenix-\d+ - - and: - - base=main - - head~=^relbot/AC-Nightly-.+ + - status-success=pr-complete pull_request_rules: - name: Resolve conflict conditions: @@ -22,64 +9,94 @@ pull_request_rules: actions: comment: message: This pull request has conflicts when rebasing. Could you fix it @{{author}}? 🙏 - - name: Android-Components bump - Auto Merge + - name: MickeyMoz - Auto Merge conditions: - - and: - - files=buildSrc/src/main/java/AndroidComponents.kt - - -files~=^(?!buildSrc/src/main/java/AndroidComponents.kt).+$ - - author=github-actions[bot] - - status-success=complete-push - - or: - - and: - - base=main - - head~=^relbot/AC-Nightly-.+ - - and: - - base~=^releases[_/].* - - head~=^relbot/fenix-\d+ + - author=MickeyMoz + - status-success=pr-complete + - files~=(Gecko.kt|AndroidComponents.kt) actions: review: type: APPROVE - message: 🚢 + message: MickeyMoz 💪 queue: method: rebase name: default rebase_fallback: none - name: L10N - Auto Merge conditions: - - and: - - files~=^(l10n.toml|app/src/main/res/values[A-Za-z-]*/strings\.xml)$ - # /!\ The line above doesn't prevent random files to be changed alongside - # l10n ones. That's why the additional condition exists below. For more - # information: https://docs.mergify.com/conditions/#how-to-match-lists - - -files~=^(?!(l10n.toml|app/src/main/res/values[A-Za-z-]*/strings\.xml)).+$ - - or: - - and: - - author=mozilla-l10n-automation-bot - - base=main - - head=import-l10n - - status-success=complete-pr - - and: - - author=github-actions[bot] - - base~=^releases[_/].* - - head~=^automation/sync-strings-\d+ - - status-success=complete-push - # Taskcluster only runs on git-push events because github-actions[bot] is not considered - # a collaborator, so pull request events are triggered. That said, github-actions[bot] - # doesn't create the PR on a separate fork (unlike mozilla-l10n-automation-bot). That's - # why git-push events are taken into account + - author=mozilla-l10n-automation-bot + - status-success=pr-complete + - files~=(strings.xml|l10n.toml) actions: review: type: APPROVE message: LGTM 😎 queue: - method: squash + method: rebase + name: default + rebase_fallback: none + - name: Release automation (Old) + conditions: + - base~=releases[_/].* + - author=github-actions[bot] + # Listing checks manually beause we do not have a "push complete" check yet. + - check-success=build-android-test-debug + - check-success=build-debug + - check-success=build-nightly-simulation + - check-success=lint-compare-locales + - check-success=lint-detekt + - check-success=lint-ktlint + - check-success=lint-lint + - check-success=signing-android-test-debug + - check-success=signing-debug + - check-success=signing-nightly-simulation + - check-success=test-debug + # TODO Temporarily disabled - should be renamed to -build + #- check-success=ui-test-x86-debug + - files~=(AndroidComponents.kt) + actions: + review: + type: APPROVE + message: 🚢 + queue: + method: rebase + name: default + rebase_fallback: none + delete_head_branch: + force: false + - name: Release automation (New) + conditions: + - base~=releases[_/].* + - author=github-actions[bot] + # Listing checks manually beause we do not have a "push complete" check yet. + - check-success=build-android-test-beta + - check-success=build-android-test-debug + - check-success=build-beta-firebase + - check-success=build-debug + - check-success=build-nightly-simulation + - check-success=lint-compare-locales + - check-success=lint-detekt + - check-success=lint-ktlint + - check-success=lint-lint + - check-success=signing-android-test-beta + - check-success=signing-beta-firebase + - check-success=signing-nightly-simulation + - check-success=test-debug + - check-success=ui-test-x86-beta + - files~=(AndroidComponents.kt) + actions: + review: + type: APPROVE + message: 🚢 + queue: + method: rebase name: default rebase_fallback: none + delete_head_branch: + force: false - name: Needs landing - Rebase conditions: - - or: - - check-success=complete-pr - - check-success=complete-push + - check-success=pr-complete - label=pr:needs-landing - "#approved-reviews-by>=1" - -draft @@ -92,10 +109,8 @@ pull_request_rules: rebase_fallback: none - name: Needs landing - Squash conditions: - - or: - - check-success=complete-pr - - check-success=complete-push - - label=pr:needs-landing-squashed + - check-success=pr-complete + - label=pr:needs-landing-squashed - "#approved-reviews-by>=1" - -draft - label!=pr:work-in-progress diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..66e5d32a5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: android +dist: trusty +script: + # Prepare SDK + - sudo mkdir -p /usr/local/android-sdk/licenses/ + - sudo touch /usr/local/android-sdk/licenses/android-sdk-license + - echo "8933bad161af4178b1185d1a37fbf41ea5269c55" | sudo tee -a /usr/local/android-sdk/licenses/android-sdk-license + - echo "d56f5187479451eabf01fb78af6dfcb131a6481e" | sudo tee -a /usr/local/android-sdk/licenses/android-sdk-license + - echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" | sudo tee -a /usr/local/android-sdk/licenses/android-sdk-license + # The build needs this but Gradle refuses to fetch it. + - sdkmanager "ndk;21.0.6113669" + # Run tests + - ./gradlew -q testDebug 2>&1 + # Make sure a release build builds + - ./gradlew assembleForkRelease -PversionName="$(git describe --tags HEAD)" + diff --git a/app/build.gradle b/app/build.gradle index 8759ccd0f..6baa35576 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,7 +34,7 @@ android { } defaultConfig { - applicationId "org.mozilla" + applicationId "io.github.forkmaintainers" minSdkVersion Config.minSdkVersion targetSdkVersion Config.targetSdkVersion versionCode 1 @@ -66,7 +66,8 @@ android { def deepLinkSchemeValue = "fenix-dev" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" manifestPlaceholders = [ - "deepLinkScheme": deepLinkSchemeValue + "deepLinkScheme": deepLinkSchemeValue, + "requestLegacyExternalStorage": true ] // Build flag for "Mozilla Online" variants. See `Config.isMozillaOnline`. @@ -105,13 +106,19 @@ android { applicationIdSuffix ".fenix.debug" resValue "bool", "IS_DEBUG", "true" pseudoLocalesEnabled true + def deepLinkSchemeValue = "fenix-dev" + buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" + manifestPlaceholders = [ + "deepLinkScheme": deepLinkSchemeValue, + "requestLegacyExternalStorage": false + ] } nightly releaseTemplate >> { applicationIdSuffix ".fenix" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" def deepLinkSchemeValue = "fenix-nightly" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" - manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue] + manifestPlaceholders = ["deepLinkScheme": deepLinkSchemeValue, "requestLegacyExternalStorage": false] } beta releaseTemplate >> { buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" @@ -127,7 +134,8 @@ android { // - https://issuetracker.google.com/issues/36924841 // - https://issuetracker.google.com/issues/36905922 "sharedUserId": "org.mozilla.firefox.sharedID", - "deepLinkScheme": deepLinkSchemeValue + "deepLinkScheme": deepLinkSchemeValue, + "requestLegacyExternalStorage": true ] } release releaseTemplate >> { @@ -144,9 +152,38 @@ android { // - https://issuetracker.google.com/issues/36924841 // - https://issuetracker.google.com/issues/36905922 "sharedUserId": "org.mozilla.firefox.sharedID", + "deepLinkScheme": deepLinkSchemeValue, + "requestLegacyExternalStorage": true + ] + } + forkDebug { + shrinkResources false + minifyEnabled false + applicationIdSuffix ".iceraven.debug" + resValue "bool", "IS_DEBUG", "true" + pseudoLocalesEnabled true + // Need to replicate default debug config features + signingConfig signingConfigs.debug + debuggable true + def deepLinkSchemeValue = "iceraven-debug" + buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" + // Use custom default allowed addon list + buildConfigField "String", "AMO_COLLECTION_USER", "\"16201230\"" + buildConfigField "String", "AMO_COLLECTION_NAME", "\"What-I-want-on-Fenix\"" + } + forkRelease releaseTemplate >> { + buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" + applicationIdSuffix ".iceraven" + def deepLinkSchemeValue = "iceraven" + buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" + manifestPlaceholders = [ "deepLinkScheme": deepLinkSchemeValue ] + // Use custom default allowed addon list + buildConfigField "String", "AMO_COLLECTION_USER", "\"16201230\"" + buildConfigField "String", "AMO_COLLECTION_NAME", "\"What-I-want-on-Fenix\"" } + } buildFeatures { @@ -245,6 +282,7 @@ android.applicationVariants.all { variant -> println("Flavor: " + variant.flavorName) println("Telemetry enabled: " + !isDebug) + if (useReleaseVersioning) { // The Google Play Store does not allow multiple APKs for the same app that all have the // same version code. Therefore we need to have different version codes for our ARM and x86 @@ -281,7 +319,7 @@ android.applicationVariants.all { variant -> buildConfigField 'String', 'SENTRY_TOKEN', 'null' if (!isDebug) { - buildConfigField 'boolean', 'CRASH_REPORTING', 'true' + buildConfigField 'boolean', 'CRASH_REPORTING', 'false' // Reading sentry token from local file (if it exists). In a release task on taskcluster it will be available. try { def token = new File("${rootDir}/.sentry_token").text.trim() @@ -292,7 +330,7 @@ android.applicationVariants.all { variant -> } if (!isDebug) { - buildConfigField 'boolean', 'TELEMETRY', 'true' + buildConfigField 'boolean', 'TELEMETRY', 'false' } else { buildConfigField 'boolean', 'TELEMETRY', 'false' } @@ -555,10 +593,10 @@ dependencies { implementation Deps.mozilla_lib_crash implementation Deps.lib_crash_sentry - implementation Deps.mozilla_lib_push_firebase implementation Deps.mozilla_lib_state implementation Deps.mozilla_lib_dataprotect debugImplementation Deps.leakcanary + forkDebugImplementation Deps.leakcanary implementation Deps.androidx_compose_ui implementation Deps.androidx_compose_ui_tooling @@ -585,13 +623,6 @@ dependencies { implementation Deps.protobuf_javalite implementation Deps.google_material - implementation Deps.adjust - implementation Deps.installreferrer // Required by Adjust - - implementation Deps.google_ads_id // Required for the Google Advertising ID - - implementation Deps.google_play_store // Required for in-app reviews - androidTestImplementation Deps.uiautomator androidTestImplementation "tools.fastlane:screengrab:2.0.0" // This Falcon version is added to maven central now required for Screengrab @@ -715,6 +746,9 @@ if (project.hasProperty("coverage")) { debug { testCoverageEnabled true } + forkDebug { + testCoverageEnabled true + } } } } diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt index 0d0416f03..de3587719 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/HomeScreenTest.kt @@ -98,9 +98,6 @@ class HomeScreenTest { verifyPrivateSessionMessage() verifyHomeToolbar() verifyHomeComponent() - }.openCommonMythsLink { - verifyUrl("common-myths-about-private-browsing") - mDevice.pressBack() } homeScreen { diff --git a/app/src/forkDebug/AndroidManifest.xml b/app/src/forkDebug/AndroidManifest.xml new file mode 100644 index 000000000..3d25c0db6 --- /dev/null +++ b/app/src/forkDebug/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/forkDebug/ic_launcher-web.png b/app/src/forkDebug/ic_launcher-web.png new file mode 100644 index 000000000..e81034d36 Binary files /dev/null and b/app/src/forkDebug/ic_launcher-web.png differ diff --git a/app/src/forkDebug/java/org/mozilla/fenix/DebugFenixApplication.kt b/app/src/forkDebug/java/org/mozilla/fenix/DebugFenixApplication.kt new file mode 100644 index 000000000..7825870b0 --- /dev/null +++ b/app/src/forkDebug/java/org/mozilla/fenix/DebugFenixApplication.kt @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix + +import android.os.StrictMode +import androidx.preference.PreferenceManager +import leakcanary.AppWatcher +import leakcanary.LeakCanary +import org.mozilla.fenix.ext.getPreferenceKey + +class DebugFenixApplication : FenixApplication() { + + override fun setupLeakCanary() { + val isEnabled = components.strictMode.resetAfter(StrictMode.allowThreadDiskReads()) { + PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean(getPreferenceKey(R.string.pref_key_leakcanary), true) + } + updateLeakCanaryState(isEnabled) + } + + override fun updateLeakCanaryState(isEnabled: Boolean) { + AppWatcher.config = AppWatcher.config.copy(enabled = isEnabled) + LeakCanary.config = LeakCanary.config.copy(dumpHeap = isEnabled) + } +} diff --git a/app/src/forkDebug/res/drawable/ic_launcher_foreground.xml b/app/src/forkDebug/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..844e479ef --- /dev/null +++ b/app/src/forkDebug/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/forkDebug/res/mipmap-hdpi/ic_launcher.png b/app/src/forkDebug/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..873b31cf5 Binary files /dev/null and b/app/src/forkDebug/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/forkDebug/res/mipmap-hdpi/ic_launcher_round.png b/app/src/forkDebug/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..1536d6f1b Binary files /dev/null and b/app/src/forkDebug/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/forkDebug/res/mipmap-mdpi/ic_launcher.png b/app/src/forkDebug/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..9cea9ac22 Binary files /dev/null and b/app/src/forkDebug/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/forkDebug/res/mipmap-mdpi/ic_launcher_round.png b/app/src/forkDebug/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..f7e6606ce Binary files /dev/null and b/app/src/forkDebug/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/forkDebug/res/mipmap-xhdpi/ic_launcher.png b/app/src/forkDebug/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..2850da3e9 Binary files /dev/null and b/app/src/forkDebug/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/forkDebug/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/forkDebug/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..e75172b17 Binary files /dev/null and b/app/src/forkDebug/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/forkDebug/res/mipmap-xxhdpi/ic_launcher.png b/app/src/forkDebug/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..0b52a7603 Binary files /dev/null and b/app/src/forkDebug/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/forkDebug/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/forkDebug/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..766d720d7 Binary files /dev/null and b/app/src/forkDebug/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/forkDebug/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/forkDebug/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..2f0901633 Binary files /dev/null and b/app/src/forkDebug/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/forkDebug/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/forkDebug/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..39be5c5da Binary files /dev/null and b/app/src/forkDebug/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/forkDebug/res/values/colors.xml b/app/src/forkDebug/res/values/colors.xml new file mode 100644 index 000000000..608bd8ced --- /dev/null +++ b/app/src/forkDebug/res/values/colors.xml @@ -0,0 +1,7 @@ + + + + @color/debug_launcher_background + diff --git a/app/src/forkDebug/res/xml/shortcuts.xml b/app/src/forkDebug/res/xml/shortcuts.xml new file mode 100644 index 000000000..1873a5af4 --- /dev/null +++ b/app/src/forkDebug/res/xml/shortcuts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/forkRelease/res/drawable-hdpi/fenix_search_widget.png b/app/src/forkRelease/res/drawable-hdpi/fenix_search_widget.png new file mode 100644 index 000000000..ecad435e4 Binary files /dev/null and b/app/src/forkRelease/res/drawable-hdpi/fenix_search_widget.png differ diff --git a/app/src/forkRelease/res/drawable-hdpi/ic_logo_wordmark_normal.png b/app/src/forkRelease/res/drawable-hdpi/ic_logo_wordmark_normal.png new file mode 100644 index 000000000..337ee573b Binary files /dev/null and b/app/src/forkRelease/res/drawable-hdpi/ic_logo_wordmark_normal.png differ diff --git a/app/src/forkRelease/res/drawable-hdpi/ic_logo_wordmark_private.png b/app/src/forkRelease/res/drawable-hdpi/ic_logo_wordmark_private.png new file mode 100644 index 000000000..c7bc68d5b Binary files /dev/null and b/app/src/forkRelease/res/drawable-hdpi/ic_logo_wordmark_private.png differ diff --git a/app/src/forkRelease/res/drawable-mdpi/ic_logo_wordmark_normal.png b/app/src/forkRelease/res/drawable-mdpi/ic_logo_wordmark_normal.png new file mode 100644 index 000000000..767ec48c1 Binary files /dev/null and b/app/src/forkRelease/res/drawable-mdpi/ic_logo_wordmark_normal.png differ diff --git a/app/src/forkRelease/res/drawable-mdpi/ic_logo_wordmark_private.png b/app/src/forkRelease/res/drawable-mdpi/ic_logo_wordmark_private.png new file mode 100644 index 000000000..9dc6e6809 Binary files /dev/null and b/app/src/forkRelease/res/drawable-mdpi/ic_logo_wordmark_private.png differ diff --git a/app/src/forkRelease/res/drawable-xhdpi/ic_logo_wordmark_normal.png b/app/src/forkRelease/res/drawable-xhdpi/ic_logo_wordmark_normal.png new file mode 100644 index 000000000..91bd546d8 Binary files /dev/null and b/app/src/forkRelease/res/drawable-xhdpi/ic_logo_wordmark_normal.png differ diff --git a/app/src/forkRelease/res/drawable-xhdpi/ic_logo_wordmark_private.png b/app/src/forkRelease/res/drawable-xhdpi/ic_logo_wordmark_private.png new file mode 100644 index 000000000..f3b6c2626 Binary files /dev/null and b/app/src/forkRelease/res/drawable-xhdpi/ic_logo_wordmark_private.png differ diff --git a/app/src/forkRelease/res/drawable-xxhdpi/ic_logo_wordmark_normal.png b/app/src/forkRelease/res/drawable-xxhdpi/ic_logo_wordmark_normal.png new file mode 100644 index 000000000..d2c2a9392 Binary files /dev/null and b/app/src/forkRelease/res/drawable-xxhdpi/ic_logo_wordmark_normal.png differ diff --git a/app/src/forkRelease/res/drawable-xxhdpi/ic_logo_wordmark_private.png b/app/src/forkRelease/res/drawable-xxhdpi/ic_logo_wordmark_private.png new file mode 100644 index 000000000..5153f8c8f Binary files /dev/null and b/app/src/forkRelease/res/drawable-xxhdpi/ic_logo_wordmark_private.png differ diff --git a/app/src/forkRelease/res/drawable-xxxhdpi/ic_logo_wordmark_normal.png b/app/src/forkRelease/res/drawable-xxxhdpi/ic_logo_wordmark_normal.png new file mode 100644 index 000000000..bfd0f68f3 Binary files /dev/null and b/app/src/forkRelease/res/drawable-xxxhdpi/ic_logo_wordmark_normal.png differ diff --git a/app/src/forkRelease/res/drawable-xxxhdpi/ic_logo_wordmark_private.png b/app/src/forkRelease/res/drawable-xxxhdpi/ic_logo_wordmark_private.png new file mode 100644 index 000000000..7d970aaae Binary files /dev/null and b/app/src/forkRelease/res/drawable-xxxhdpi/ic_logo_wordmark_private.png differ diff --git a/app/src/forkRelease/res/drawable/ic_launcher_foreground.xml b/app/src/forkRelease/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..83d4b9dd1 --- /dev/null +++ b/app/src/forkRelease/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/app/src/forkRelease/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/forkRelease/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..7353dbd1f --- /dev/null +++ b/app/src/forkRelease/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/forkRelease/res/values/colors.xml b/app/src/forkRelease/res/values/colors.xml new file mode 100644 index 000000000..27cf937c1 --- /dev/null +++ b/app/src/forkRelease/res/values/colors.xml @@ -0,0 +1,7 @@ + + + + #F6F6F6 + diff --git a/app/src/forkRelease/res/values/static_strings.xml b/app/src/forkRelease/res/values/static_strings.xml new file mode 100644 index 000000000..0587640ee --- /dev/null +++ b/app/src/forkRelease/res/values/static_strings.xml @@ -0,0 +1,8 @@ + + + + + Iceraven + diff --git a/app/src/forkRelease/res/xml/shortcuts.xml b/app/src/forkRelease/res/xml/shortcuts.xml new file mode 100644 index 000000000..5f1a6c69e --- /dev/null +++ b/app/src/forkRelease/res/xml/shortcuts.xml @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aab865ec7..1be77d026 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,6 +32,7 @@ android:extractNativeLibs="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:requestLegacyExternalStorage="${requestLegacyExternalStorage}" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/NormalTheme" diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 000000000..5bc1f130f Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/adjust/sdk/Adjust.java b/app/src/main/java/com/adjust/sdk/Adjust.java new file mode 100644 index 000000000..5dc3617fa --- /dev/null +++ b/app/src/main/java/com/adjust/sdk/Adjust.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012-2017 adjust GmbH, + * http://www.adjust.com + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +package com.adjust.sdk; + +public class Adjust { + public static void onCreate(AdjustConfig adjustConfig) { + } + + public static void onResume() { + } + + public static void onPause() { + } + + public static void setEnabled(boolean enabled) { + } + + public static void gdprForgetMe(Object ignored) { + } +} diff --git a/app/src/main/java/com/adjust/sdk/AdjustAttribution.java b/app/src/main/java/com/adjust/sdk/AdjustAttribution.java new file mode 100644 index 000000000..ab6b3badb --- /dev/null +++ b/app/src/main/java/com/adjust/sdk/AdjustAttribution.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012-2017 adjust GmbH, + * http://www.adjust.com + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.adjust.sdk; + +import java.io.Serializable; + +public class AdjustAttribution implements Serializable { + public String network; + public String campaign; + public String adgroup; + public String creative; + + @Override + public boolean equals(Object other) { + return false; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public String toString() { + return ""; + } +} diff --git a/app/src/main/java/com/adjust/sdk/AdjustConfig.java b/app/src/main/java/com/adjust/sdk/AdjustConfig.java new file mode 100644 index 000000000..6753dd7d9 --- /dev/null +++ b/app/src/main/java/com/adjust/sdk/AdjustConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2012-2017 adjust GmbH, + * http://www.adjust.com + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.adjust.sdk; + +import android.content.Context; + +import java.util.List; + +public class AdjustConfig { + public static final String ENVIRONMENT_SANDBOX = "sandbox"; + public static final String ENVIRONMENT_PRODUCTION = "production"; + + public AdjustConfig(Context context, String appToken, String environment) { + } + + public AdjustConfig(Context context, String appToken, String environment, boolean allowSuppressLogLevel) { + } + + public void setOnAttributionChangedListener(OnAttributionChangedListener onAttributionChangedListener) { + } + + public void setLogLevel(LogLevel logLevel) { + } +} diff --git a/app/src/main/java/com/adjust/sdk/LogLevel.java b/app/src/main/java/com/adjust/sdk/LogLevel.java new file mode 100644 index 000000000..27ac3de54 --- /dev/null +++ b/app/src/main/java/com/adjust/sdk/LogLevel.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012-2017 adjust GmbH, + * http://www.adjust.com + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.adjust.sdk; + +import android.util.Log; + +/** + * Created by pfms on 11/03/15. + */ +public enum LogLevel { + VERBOSE(Log.VERBOSE), DEBUG(Log.DEBUG), INFO(Log.INFO), WARN(Log.WARN), ERROR(Log.ERROR), ASSERT(Log.ASSERT), SUPRESS(8); + final int androidLogLevel; + + LogLevel(final int androidLogLevel) { + this.androidLogLevel = androidLogLevel; + } + + public int getAndroidLogLevel() { + return androidLogLevel; + } +} diff --git a/app/src/main/java/com/adjust/sdk/OnAttributionChangedListener.java b/app/src/main/java/com/adjust/sdk/OnAttributionChangedListener.java new file mode 100644 index 000000000..7efa1c680 --- /dev/null +++ b/app/src/main/java/com/adjust/sdk/OnAttributionChangedListener.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012-2017 adjust GmbH, + * http://www.adjust.com + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.adjust.sdk; + +public interface OnAttributionChangedListener { + void onAttributionChanged(AdjustAttribution attribution); +} diff --git a/app/src/main/java/com/google/android/gms/ads/identifier/AdvertisingIdClient.java b/app/src/main/java/com/google/android/gms/ads/identifier/AdvertisingIdClient.java new file mode 100644 index 000000000..3c238f76c --- /dev/null +++ b/app/src/main/java/com/google/android/gms/ads/identifier/AdvertisingIdClient.java @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package com.google.android.gms.ads.identifier; + +import android.content.Context; + + +public class AdvertisingIdClient { + + public static final class Info { + + private String mId; + + public Info() { + mId = ""; + } + + public Info(String id, Boolean ignored) { + // We need to preserve the passed ID to pass Mozilla's tests. + mId = id; + } + + public String getId() { + return mId; + } + + public String toString() { + return mId; + } + + } + + public static Info getAdvertisingIdInfo(Context context) { + return new Info(); + } + +} diff --git a/app/src/main/java/com/google/android/play/core/review/ReviewManager.kt b/app/src/main/java/com/google/android/play/core/review/ReviewManager.kt new file mode 100644 index 000000000..789802987 --- /dev/null +++ b/app/src/main/java/com/google/android/play/core/review/ReviewManager.kt @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +package com.google.android.play.core.review +class ReviewManager { + + class FakeReviewFlowTaskResult { + val isSuccessful: Boolean = false + val result: Any = false + } + class FakeReviewFlowTask { + @Suppress("UNUSED_PARAMETER", "UNUSED_EXPRESSION") + fun addOnCompleteListener(ignored: (FakeReviewFlowTaskResult) -> Unit) { + 1 + } + } + fun requestReviewFlow(): FakeReviewFlowTask { + return FakeReviewFlowTask() + } + @Suppress("UNUSED_PARAMETER", "UNUSED_EXPRESSION") + fun launchReviewFlow(ignored1: Any, ignored2: Any) { + 1 + } +} diff --git a/app/src/main/java/com/google/android/play/core/review/ReviewManagerFactory.java b/app/src/main/java/com/google/android/play/core/review/ReviewManagerFactory.java new file mode 100644 index 000000000..3506da2ff --- /dev/null +++ b/app/src/main/java/com/google/android/play/core/review/ReviewManagerFactory.java @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package com.google.android.play.core.review; + +import android.content.Context; +import com.google.android.play.core.review.ReviewManager; + + +public class ReviewManagerFactory { + + public static ReviewManager create(Context context) { + return new ReviewManager(); + } + +} diff --git a/app/src/main/java/com/google/firebase/messaging/FirebaseMessagingService.java b/app/src/main/java/com/google/firebase/messaging/FirebaseMessagingService.java new file mode 100644 index 000000000..79944c9ca --- /dev/null +++ b/app/src/main/java/com/google/firebase/messaging/FirebaseMessagingService.java @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.messaging; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; + +public class FirebaseMessagingService extends Service { + + private final IBinder mBinder = new Binder(); + + public void onMessageReceived(RemoteMessage message) { + } + + public void onMessageSent(String msgId) { + } + + public void onNewToken(String token) { + } + + public void onSendError(String msgId, Exception exception) { + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + +} diff --git a/app/src/main/java/com/google/firebase/messaging/RemoteMessage.java b/app/src/main/java/com/google/firebase/messaging/RemoteMessage.java new file mode 100644 index 000000000..24371f8f8 --- /dev/null +++ b/app/src/main/java/com/google/firebase/messaging/RemoteMessage.java @@ -0,0 +1,53 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.firebase.messaging; + +import android.os.Parcel; +import android.os.Parcelable; +import java.util.Map; + +public class RemoteMessage implements Parcelable { + + protected RemoteMessage(Parcel in) + { + } + + public static final Creator CREATOR = new Creator() + { + @Override + public RemoteMessage createFromParcel(Parcel in) + { + return new RemoteMessage(in); + } + + @Override + public RemoteMessage[] newArray(int size) + { + return new RemoteMessage[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + } + + public Map getData() { + return null; + } + +} diff --git a/app/src/main/java/com/leanplum/Leanplum.java b/app/src/main/java/com/leanplum/Leanplum.java new file mode 100644 index 000000000..70a6151fe --- /dev/null +++ b/app/src/main/java/com/leanplum/Leanplum.java @@ -0,0 +1,95 @@ +/* + * Copyright 2016, Leanplum, Inc. All rights reserved. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.leanplum; + +import android.content.Context; +import com.leanplum.callbacks.StartCallback; +import java.util.Map; + +public class Leanplum { + public static void setAppIdForDevelopmentMode(String appId, String accessKey) { + } + + public static void setAppIdForProductionMode(String appId, String accessKey) { + } + + public static void setApplicationContext(Context context) { + } + + public static void setDeviceId(String deviceId) { + } + + public static void setIsTestModeEnabled(boolean isTestModeEnabled) { + } + + public static void setUserAttributes(Map userAttributes) { + } + + public static void start(Context context) { + } + + public static void start(Context context, StartCallback callback) { + } + + public static void start(Context context, Map userAttributes) { + } + + public static void start(Context context, String userId) { + } + + public static void start(Context context, String userId, StartCallback callback) { + } + + public static void start(Context context, String userId, Map userAttributes) { + } + + public static synchronized void start(final Context context, String userId, Map attributes, StartCallback response) { + } + + static synchronized void start(final Context context, final String userId, final Map attributes, StartCallback response, final Boolean isBackground) { + } + + public static void track(final String event, double value, String info, Map params) { + } + + public static void track(String event) { + } + + public static void track(String event, double value) { + } + + public static void track(String event, String info) { + } + + public static void track(String event, Map params) { + } + + public static void track(String event, double value, Map params) { + } + + public static void track(String event, double value, String info) { + } + + public static String getDeviceId() { return "stub"; } + + public static String getUserId() { return "stub"; } +} diff --git a/app/src/main/java/com/leanplum/LeanplumActivityHelper.java b/app/src/main/java/com/leanplum/LeanplumActivityHelper.java new file mode 100644 index 000000000..489871cf5 --- /dev/null +++ b/app/src/main/java/com/leanplum/LeanplumActivityHelper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013, Leanplum, Inc. All rights reserved. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.leanplum; + +import android.app.Application; + +public class LeanplumActivityHelper { + public static void enableLifecycleCallbacks(final Application app) { + } +} diff --git a/app/src/main/java/com/leanplum/LeanplumPushFirebaseMessagingService.java b/app/src/main/java/com/leanplum/LeanplumPushFirebaseMessagingService.java new file mode 100644 index 000000000..57d2f8580 --- /dev/null +++ b/app/src/main/java/com/leanplum/LeanplumPushFirebaseMessagingService.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016, Leanplum, Inc. All rights reserved. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.leanplum; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.os.Bundle; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +@SuppressLint("Registered") +public class LeanplumPushFirebaseMessagingService extends FirebaseMessagingService { + @Override + public void onCreate() { + } + + @Override + public void onNewToken(String token) { + } + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + } +} diff --git a/app/src/main/java/com/leanplum/LeanplumPushNotificationCustomizer.java b/app/src/main/java/com/leanplum/LeanplumPushNotificationCustomizer.java new file mode 100644 index 000000000..f827eb180 --- /dev/null +++ b/app/src/main/java/com/leanplum/LeanplumPushNotificationCustomizer.java @@ -0,0 +1,65 @@ +/* + * Copyright 2015, Leanplum, Inc. All rights reserved. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.leanplum; + +import android.app.Notification; +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.core.app.NotificationCompat; + +/** + * Implement LeanplumPushNotificationCustomizer to customize the appearance of notifications. + */ +public interface LeanplumPushNotificationCustomizer { + /** + * Implement this method to customize push notification. Please call {@link + * LeanplumPushService#setCustomizer(LeanplumPushNotificationCustomizer)} to activate this method. + * Leave this method empty if you want to support 2 lines of text + * in BigPicture style push notification and implement {@link + * LeanplumPushNotificationCustomizer#customize(Notification.Builder, Bundle, Notification.Style)} + * + * @param builder NotificationCompat.Builder for push notification. + * @param notificationPayload Bundle notification payload. + */ + void customize(NotificationCompat.Builder builder, Bundle notificationPayload); + + /** + * Implement this method to support 2 lines of text in BigPicture style push notification, + * otherwise implement {@link + * LeanplumPushNotificationCustomizer#customize(NotificationCompat.Builder, Bundle)} and leave + * this method empty. Please call {@link + * LeanplumPushService#setCustomizer(LeanplumPushNotificationCustomizer, boolean)} with true + * value to activate this method. + * + * @param builder Notification.Builder for push notification. + * @param notificationPayload Bundle notification payload. + * @param notificationStyle - Notification.BigPictureStyle or null - BigPicture style for current + * push notification. Call ((Notification.BigPictureStyle) notificationStyle).bigLargeIcon(largeIcon) + * if you want to set large icon on expanded push notification. If notificationStyle wasn't null + * it will be set to push notification. Note: If you call notificationStyle = new + * Notification.BigPictureStyle() or other Notification.Style - there will be no support 2 lines + * of text on BigPicture push and you need to call builder.setStyle(notificationStyle) to set + * yours expanded layout for push notification. + */ + void customize(Notification.Builder builder, Bundle notificationPayload, + @Nullable Notification.Style notificationStyle); +} diff --git a/app/src/main/java/com/leanplum/LeanplumPushService.java b/app/src/main/java/com/leanplum/LeanplumPushService.java new file mode 100644 index 000000000..436cba348 --- /dev/null +++ b/app/src/main/java/com/leanplum/LeanplumPushService.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014, Leanplum, Inc. All rights reserved. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.leanplum; + +public class LeanplumPushService { + public static void setCustomizer(LeanplumPushNotificationCustomizer customizer) { + } + + public static void setCustomizer(LeanplumPushNotificationCustomizer customizer, + boolean useNotificationBuilderCustomizer) { + } +} diff --git a/app/src/main/java/com/leanplum/annotations/Parser.java b/app/src/main/java/com/leanplum/annotations/Parser.java new file mode 100644 index 000000000..2bb6d2b2a --- /dev/null +++ b/app/src/main/java/com/leanplum/annotations/Parser.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013, Leanplum, Inc. All rights reserved. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.leanplum.annotations; + +public class Parser { + public static void parseVariables(Object... instances) { + } +} diff --git a/app/src/main/java/com/leanplum/callbacks/StartCallback.java b/app/src/main/java/com/leanplum/callbacks/StartCallback.java new file mode 100644 index 000000000..30d437d30 --- /dev/null +++ b/app/src/main/java/com/leanplum/callbacks/StartCallback.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013, Leanplum, Inc. All rights reserved. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.leanplum.callbacks; + +/** + * Callback that gets run when Leanplum is started. + * + * @author Andrew First + */ +public abstract class StartCallback implements Runnable { + private boolean success; + + public void setSuccess(boolean success) { + this.success = success; + } + + public void run() { + this.onResponse(success); + } + + public abstract void onResponse(boolean success); +} diff --git a/app/src/main/java/com/leanplum/internal/LeanplumInternal.java b/app/src/main/java/com/leanplum/internal/LeanplumInternal.java new file mode 100644 index 000000000..8ca36a6b4 --- /dev/null +++ b/app/src/main/java/com/leanplum/internal/LeanplumInternal.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016, Leanplum, Inc. All rights reserved. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.leanplum.internal; + +public class LeanplumInternal { + public static void setCalledStart(boolean calledStart) { + } + + public static void setHasStarted(boolean hasStarted) { + } + + public static void setStartedInBackground(boolean startedInBackground) { + } +} diff --git a/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonCollectionProvider.kt b/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonCollectionProvider.kt new file mode 100644 index 000000000..7050282f5 --- /dev/null +++ b/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonCollectionProvider.kt @@ -0,0 +1,403 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@file:Suppress("TooManyFunctions") + +package io.github.forkmaintainers.iceraven.components + +import android.content.Context +import android.util.AtomicFile +import androidx.annotation.VisibleForTesting +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import mozilla.components.concept.fetch.Client +import mozilla.components.concept.fetch.Request +import mozilla.components.concept.fetch.isSuccess +import mozilla.components.feature.addons.Addon +import mozilla.components.feature.addons.AddonsProvider +import mozilla.components.support.base.log.logger.Logger +import mozilla.components.support.ktx.kotlin.sanitizeURL +import mozilla.components.support.ktx.util.readAndDeserialize +import mozilla.components.support.ktx.util.writeString +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.mozilla.fenix.Config +import org.mozilla.fenix.ext.settings +import java.io.File +import java.io.IOException +import java.util.Date +import java.util.concurrent.TimeUnit + +internal const val API_VERSION = "api/v4" +internal const val DEFAULT_SERVER_URL = "https://addons.mozilla.org" +internal const val COLLECTION_FILE_NAME = "%s_components_addon_collection_%s.json" +internal const val MINUTE_IN_MS = 60 * 1000 +internal const val DEFAULT_READ_TIMEOUT_IN_SECONDS = 20L + +/** + * Provide access to the collections AMO API. + * https://addons-server.readthedocs.io/en/latest/topics/api/collections.html + * + * Unlike the android-components version, supports multiple-page responses and + * custom collection accounts. + * + * Needs to extend AddonCollectionProvider because AddonsManagerAdapter won't + * take just any AddonsProvider. + * + * @property serverURL The url of the endpoint to interact with e.g production, staging + * or testing. Defaults to [DEFAULT_SERVER_URL]. + * @property maxCacheAgeInMinutes maximum time (in minutes) the collection cache + * should remain valid. Defaults to -1, meaning no cache is being used by default. + * @property client A reference of [Client] for interacting with the AMO HTTP api. + */ +@Suppress("LongParameterList") +class PagedAddonCollectionProvider( + private val context: Context, + private val client: Client, + private val serverURL: String = DEFAULT_SERVER_URL, + private val maxCacheAgeInMinutes: Long = -1 +) : AddonsProvider { + + private val logger = Logger("PagedAddonCollectionProvider") + + private val diskCacheLock = Any() + + /** + * Get the account we should be fetching addons from. + */ + private fun getCollectionAccount(): String { + var result = context.settings().customAddonsAccount + if (Config.channel.isNightlyOrDebug && context.settings().amoCollectionOverrideConfigured()) { + result = context.settings().overrideAmoUser + } + + logger.info("Determined collection account: ${result}") + return result + } + + /** + * Get the collection name we should be fetching addons from. + */ + private fun getCollectionName(): String { + var result = context.settings().customAddonsCollection + if (Config.channel.isNightlyOrDebug && context.settings().amoCollectionOverrideConfigured()) { + result = context.settings().overrideAmoCollection + } + + logger.info("Determined collection name: ${result}") + return result + } + + /** + * Interacts with the collections endpoint to provide a list of available + * add-ons. May return a cached response, if available, not expired (see + * [maxCacheAgeInMinutes]) and allowed (see [allowCache]). + * + * @param allowCache whether or not the result may be provided + * from a previously cached response, defaults to true. + * @param readTimeoutInSeconds optional timeout in seconds to use when fetching + * available add-ons from a remote endpoint. If not specified [DEFAULT_READ_TIMEOUT_IN_SECONDS] + * will be used. + * @param language optional language that will be ignored. + * @throws IOException if the request failed, or could not be executed due to cancellation, + * a connectivity problem or a timeout. + */ + @Throws(IOException::class) + override suspend fun getAvailableAddons( + allowCache: Boolean, + readTimeoutInSeconds: Long?, + language: String? + ): List { + val cachedAddons = if (allowCache && !cacheExpired(context)) { + readFromDiskCache() + } else { + null + } + + val collectionAccount = getCollectionAccount() + val collectionName = getCollectionName() + + if (cachedAddons != null) { + logger.info("Providing cached list of addons for ${collectionAccount} collection ${collectionName}") + return cachedAddons + } else { + logger.info("Fetching fresh list of addons for ${collectionAccount} collection ${collectionName}") + return getAllPages( + listOf( + serverURL, + API_VERSION, + "accounts/account", + collectionAccount, + "collections", + collectionName, + "addons" + ).joinToString("/"), + readTimeoutInSeconds ?: DEFAULT_READ_TIMEOUT_IN_SECONDS + ).also { + // Cache the JSON object before we parse out the addons + if (maxCacheAgeInMinutes > 0) { + writeToDiskCache(it.toString()) + } + }.getAddons() + } + } + + /** + * Fetches all pages of add-ons from the given URL (following the "next" + * field in the returned JSON) and combines the "results" arrays into that + * of the first page. Returns that coalesced object. + * + * @param url URL of the first page to fetch + * @param readTimeoutInSeconds timeout in seconds to use when fetching each page. + * @throws IOException if the request failed, or could not be executed due to cancellation, + * a connectivity problem or a timeout. + */ + @Throws(IOException::class) + suspend fun getAllPages(url: String, readTimeoutInSeconds: Long): JSONObject { + // Fetch and compile all the pages into one object we can return + var compiledResponse: JSONObject? = null + // Each page tells us where to get the next page, if there is one + var nextURL: String? = url + while (nextURL != null) { + client.fetch( + Request( + url = nextURL, + readTimeout = Pair(readTimeoutInSeconds, TimeUnit.SECONDS) + ) + ) + .use { response -> + if (!response.isSuccess) { + val errorMessage = "Failed to fetch addon collection. Status code: ${response.status}" + logger.error(errorMessage) + throw IOException(errorMessage) + } + + val currentResponse = try { + JSONObject(response.body.string(Charsets.UTF_8)) + } catch (e: JSONException) { + throw IOException(e) + } + if (compiledResponse == null) { + compiledResponse = currentResponse + } else { + // Write the addons into the first response + compiledResponse!!.getJSONArray("results").concat(currentResponse.getJSONArray("results")) + } + nextURL = if (currentResponse.isNull("next")) null else currentResponse.getString("next") + } + } + return compiledResponse!! + } + + /** + * Fetches given Addon icon from the url and returns a decoded Bitmap + * @throws IOException if the request could not be executed due to cancellation, + * a connectivity problem or a timeout. + */ + @Throws(IOException::class) + suspend fun getAddonIconBitmap(addon: Addon): Bitmap? { + var bitmap: Bitmap? = null + if (addon.iconUrl != "") { + client.fetch( + Request(url = addon.iconUrl.sanitizeURL()) + ).use { response -> + if (response.isSuccess) { + response.body.useStream { + bitmap = BitmapFactory.decodeStream(it) + } + } + } + } + + return bitmap + } + + @VisibleForTesting + internal fun writeToDiskCache(collectionResponse: String) { + logger.info("Storing cache file") + synchronized(diskCacheLock) { + getCacheFile(context).writeString { collectionResponse } + } + } + + @VisibleForTesting + internal fun readFromDiskCache(): List? { + logger.info("Loading cache file") + synchronized(diskCacheLock) { + return getCacheFile(context).readAndDeserialize { + JSONObject(it).getAddons() + } + } + } + + @VisibleForTesting + internal fun cacheExpired(context: Context): Boolean { + return getCacheLastUpdated(context) < Date().time - maxCacheAgeInMinutes * MINUTE_IN_MS + } + + @VisibleForTesting + internal fun getCacheLastUpdated(context: Context): Long { + val file = getBaseCacheFile(context) + return if (file.exists()) file.lastModified() else -1 + } + + private fun getCacheFile(context: Context): AtomicFile { + return AtomicFile(getBaseCacheFile(context)) + } + + private fun getBaseCacheFile(context: Context): File { + val collectionAccount = getCollectionAccount() + val collectionName = getCollectionName() + return File(context.filesDir, COLLECTION_FILE_NAME.format(collectionAccount, collectionName)) + } + + fun deleteCacheFile(context: Context): Boolean { + logger.info("Clearing cache file") + synchronized(diskCacheLock) { + val file = getBaseCacheFile(context) + return if (file.exists()) file.delete() else false + } + } +} + +internal fun JSONObject.getAddons(): List { + val addonsJson = getJSONArray("results") + return (0 until addonsJson.length()).map { index -> + addonsJson.getJSONObject(index).toAddons() + } +} + +internal fun JSONObject.toAddons(): Addon { + return with(getJSONObject("addon")) { + val download = getDownload() + Addon( + id = getSafeString("guid"), + authors = getAuthors(), + categories = getCategories(), + createdAt = getSafeString("created"), + updatedAt = getSafeString("last_updated"), + downloadId = download?.getDownloadId() ?: "", + downloadUrl = download?.getDownloadUrl() ?: "", + version = getCurrentVersion(), + permissions = getPermissions(), + translatableName = getSafeMap("name"), + translatableDescription = getSafeMap("description"), + translatableSummary = getSafeMap("summary"), + iconUrl = getSafeString("icon_url"), + siteUrl = getSafeString("url"), + rating = getRating(), + defaultLocale = getSafeString("default_locale").ifEmpty { Addon.DEFAULT_LOCALE } + ) + } +} + +internal fun JSONObject.getRating(): Addon.Rating? { + val jsonRating = optJSONObject("ratings") + return if (jsonRating != null) { + Addon.Rating( + reviews = jsonRating.optInt("count"), + average = jsonRating.optDouble("average").toFloat() + ) + } else { + null + } +} + +internal fun JSONObject.getCategories(): List { + val jsonCategories = optJSONObject("categories") + return if (jsonCategories == null) { + emptyList() + } else { + val jsonAndroidCategories = jsonCategories.getSafeJSONArray("android") + (0 until jsonAndroidCategories.length()).map { index -> + jsonAndroidCategories.getString(index) + } + } +} + +internal fun JSONObject.getPermissions(): List { + val fileJson = getJSONObject("current_version") + .getSafeJSONArray("files") + .getJSONObject(0) + + val permissionsJson = fileJson.getSafeJSONArray("permissions") + return (0 until permissionsJson.length()).map { index -> + permissionsJson.getString(index) + } +} + +internal fun JSONObject.getCurrentVersion(): String { + return optJSONObject("current_version")?.getSafeString("version") ?: "" +} + +internal fun JSONObject.getDownload(): JSONObject? { + return ( + getJSONObject("current_version") + .optJSONArray("files") + ?.getJSONObject(0) + ) +} + +internal fun JSONObject.getDownloadId(): String { + return getSafeString("id") +} + +internal fun JSONObject.getDownloadUrl(): String { + return getSafeString("url") +} + +internal fun JSONObject.getAuthors(): List { + val authorsJson = getSafeJSONArray("authors") + return (0 until authorsJson.length()).map { index -> + val authorJson = authorsJson.getJSONObject(index) + + Addon.Author( + id = authorJson.getSafeString("id"), + name = authorJson.getSafeString("name"), + username = authorJson.getSafeString("username"), + url = authorJson.getSafeString("url") + ) + } +} + +internal fun JSONObject.getSafeString(key: String): String { + return if (isNull(key)) { + "" + } else { + getString(key) + } +} + +internal fun JSONObject.getSafeJSONArray(key: String): JSONArray { + return if (isNull(key)) { + JSONArray("[]") + } else { + getJSONArray(key) + } +} + +internal fun JSONObject.getSafeMap(valueKey: String): Map { + return if (isNull(valueKey)) { + emptyMap() + } else { + val map = mutableMapOf() + val jsonObject = getJSONObject(valueKey) + + jsonObject.keys() + .forEach { key -> + map[key] = jsonObject.getSafeString(key) + } + map + } +} + +/** + * Concatenates the given JSONArray onto this one. + */ +internal fun JSONArray.concat(other: JSONArray) { + (0 until other.length()).map { index -> + put(length(), other.getJSONObject(index)) + } +} diff --git a/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonInstallationDialogFragment.kt b/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonInstallationDialogFragment.kt new file mode 100644 index 000000000..df9448fa0 --- /dev/null +++ b/app/src/main/java/io/github/forkmaintainers/iceraven/components/PagedAddonInstallationDialogFragment.kt @@ -0,0 +1,309 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package io.github.forkmaintainers.iceraven.components + +import android.annotation.SuppressLint +import android.app.Dialog +import android.graphics.Bitmap +import android.graphics.Color +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.GradientDrawable +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.widget.Button +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.ColorRes +import androidx.annotation.VisibleForTesting +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.appcompat.widget.AppCompatCheckBox +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import mozilla.components.feature.addons.Addon +import mozilla.components.feature.addons.R +import mozilla.components.feature.addons.databinding.MozacFeatureAddonsFragmentDialogAddonInstalledBinding +import mozilla.components.feature.addons.ui.translateName +import mozilla.components.support.base.log.logger.Logger +import mozilla.components.support.ktx.android.content.appName +import mozilla.components.support.ktx.android.content.res.resolveAttribute +import java.io.IOException + +@VisibleForTesting internal const val KEY_INSTALLED_ADDON = "KEY_ADDON" +private const val KEY_DIALOG_GRAVITY = "KEY_DIALOG_GRAVITY" +private const val KEY_DIALOG_WIDTH_MATCH_PARENT = "KEY_DIALOG_WIDTH_MATCH_PARENT" +private const val KEY_CONFIRM_BUTTON_BACKGROUND_COLOR = "KEY_CONFIRM_BUTTON_BACKGROUND_COLOR" +private const val KEY_CONFIRM_BUTTON_TEXT_COLOR = "KEY_CONFIRM_BUTTON_TEXT_COLOR" +private const val KEY_CONFIRM_BUTTON_RADIUS = "KEY_CONFIRM_BUTTON_RADIUS" +@VisibleForTesting internal const val KEY_ICON = "KEY_ICON" + +private const val DEFAULT_VALUE = Int.MAX_VALUE + +/** + * A dialog that shows [Addon] installation confirmation. + */ +// We have an extra "Lint" Android Studio linter pass that Android Components +// where the original code came from doesn't. So we tell it to ignore us. Make +// sure to keep up with changes in Android Components though. +@SuppressLint("all") +class PagedAddonInstallationDialogFragment : AppCompatDialogFragment() { + private val scope = CoroutineScope(Dispatchers.IO) + @VisibleForTesting internal var iconJob: Job? = null + private val logger = Logger("PagedAddonInstallationDialogFragment") + /** + * A lambda called when the confirm button is clicked. + */ + var onConfirmButtonClicked: ((Addon, Boolean) -> Unit)? = null + + /** + * Reference to the application's [PagedAddonCollectionProvider] to fetch add-on icons. + */ + var addonCollectionProvider: PagedAddonCollectionProvider? = null + + private val safeArguments get() = requireNotNull(arguments) + + internal val addon get() = requireNotNull(safeArguments.getParcelable(KEY_ADDON)) + private var allowPrivateBrowsing: Boolean = false + + internal val confirmButtonRadius + get() = + safeArguments.getFloat(KEY_CONFIRM_BUTTON_RADIUS, DEFAULT_VALUE.toFloat()) + + internal val dialogGravity: Int + get() = + safeArguments.getInt( + KEY_DIALOG_GRAVITY, + DEFAULT_VALUE + ) + internal val dialogShouldWidthMatchParent: Boolean + get() = + safeArguments.getBoolean(KEY_DIALOG_WIDTH_MATCH_PARENT) + + internal val confirmButtonBackgroundColor + get() = + safeArguments.getInt( + KEY_CONFIRM_BUTTON_BACKGROUND_COLOR, + DEFAULT_VALUE + ) + + internal val confirmButtonTextColor + get() = + safeArguments.getInt( + KEY_CONFIRM_BUTTON_TEXT_COLOR, + DEFAULT_VALUE + ) + + override fun onStop() { + super.onStop() + iconJob?.cancel() + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val sheetDialog = Dialog(requireContext()) + sheetDialog.requestWindowFeature(Window.FEATURE_NO_TITLE) + sheetDialog.setCanceledOnTouchOutside(true) + + val rootView = createContainer() + + sheetDialog.setContainerView(rootView) + + sheetDialog.window?.apply { + if (dialogGravity != DEFAULT_VALUE) { + setGravity(dialogGravity) + } + + if (dialogShouldWidthMatchParent) { + setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + // This must be called after addContentView, or it won't fully fill to the edge. + setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + } + + return sheetDialog + } + + private fun Dialog.setContainerView(rootView: View) { + if (dialogShouldWidthMatchParent) { + setContentView(rootView) + } else { + addContentView( + rootView, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + ) + } + } + + @SuppressLint("InflateParams") + private fun createContainer(): View { + val rootView = LayoutInflater.from(requireContext()).inflate( + R.layout.mozac_feature_addons_fragment_dialog_addon_installed, + null, + false + ) + + val binding = MozacFeatureAddonsFragmentDialogAddonInstalledBinding.bind(rootView) + + rootView.findViewById(R.id.title).text = + requireContext().getString( + R.string.mozac_feature_addons_installed_dialog_title, + addon.translateName(requireContext()), + requireContext().appName + ) + + val icon = safeArguments.getParcelable(KEY_ICON) + if (icon != null) { + binding.icon.setImageDrawable(BitmapDrawable(resources, icon)) + } else { + iconJob = fetchIcon(addon, binding.icon) + } + + val allowedInPrivateBrowsing = rootView.findViewById(R.id.allow_in_private_browsing) + allowedInPrivateBrowsing.setOnCheckedChangeListener { _, isChecked -> + allowPrivateBrowsing = isChecked + } + + val confirmButton = rootView.findViewById