@ -0,0 +1,138 @@
|
||||
name: Release Automation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- iceraven
|
||||
tags:
|
||||
- "*"
|
||||
create:
|
||||
|
||||
jobs:
|
||||
release-automation:
|
||||
name: Build App
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: temurin
|
||||
|
||||
- name: Install Android SDK with pieces Gradle skips
|
||||
run: ./automation/iceraven/install-sdk.sh
|
||||
|
||||
- name: Inspect memory
|
||||
run: free -h
|
||||
|
||||
- name: Create version name
|
||||
run: echo "VERSION_NAME=$(git describe --tags HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Build forkRelease variant of app
|
||||
uses: gradle/gradle-build-action@v2
|
||||
env:
|
||||
# Try to stop the daemon from magically vanishing by adding random memory-related arguments.
|
||||
# See <https://stackoverflow.com/a/70010526> and <https://stackoverflow.com/a/70756876>
|
||||
# The runner seems to have ~6 gigs of memory, so we make sure to stay under that.
|
||||
# We have Java 11 so we don't have a perm size anymore.
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-XX:MaxMetaspaceSize=1g -Xms2g -Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dev/stderr"
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
gradle-executable: /usr/bin/time
|
||||
arguments: -v ./gradlew app:assemblefenixForkRelease -x lintVitalFenixForkRelease -PversionName=${{ env.VERSION_NAME }} --stacktrace
|
||||
|
||||
- name: Create signed APKs
|
||||
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
|
||||
uses: abhijitvalluri/sign-apks@v0.8
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/fenix/forkRelease/
|
||||
signingKeyBase64: ${{ secrets.DEBUG_SIGNING_KEY }}
|
||||
alias: ${{ secrets.DEBUG_ALIAS }}
|
||||
keyStorePassword: ${{ secrets.DEBUG_KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.DEBUG_KEY_PASSWORD }}
|
||||
|
||||
- name: Create changelog
|
||||
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
|
||||
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 "<details>" >>temp_changelog.md
|
||||
echo "<summary>Click to expand</summary>" >>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 "</details>" >>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
|
||||
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
|
||||
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
|
||||
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
|
||||
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/fenix/forkRelease/app-fenix-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
|
||||
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
|
||||
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/fenix/forkRelease/app-fenix-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
|
||||
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
|
||||
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/fenix/forkRelease/app-fenix-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
|
||||
if: "contains(toJSON(github.event.ref_type), 'tag') && contains(toJSON(github.event.ref), 'iceraven')"
|
||||
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/fenix/forkRelease/app-fenix-x86_64-forkRelease.apk
|
||||
asset_name: ${{ github.event.ref }}-browser-x86_64-forkRelease.apk
|
||||
asset_content_type: application/vnd.android.package-archive
|
@ -0,0 +1,3 @@
|
||||
[submodule "android-components"]
|
||||
path = android-components
|
||||
url = https://github.com/akliuxingyuan/android-components
|
@ -0,0 +1 @@
|
||||
Subproject commit 4074006ffff65ca6414d8d0ed77bc924966283c2
|
@ -0,0 +1,23 @@
|
||||
<manifest
|
||||
package="org.mozilla.fenix"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<!-- Allows unlocking your device and activating its screen so UI tests can succeed -->
|
||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
|
||||
<!-- Allows for storing and retrieving screenshots -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<!-- Allows changing locales -->
|
||||
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<application
|
||||
tools:replace="android:name"
|
||||
android:name="org.mozilla.fenix.DebugFenixApplication" />
|
||||
|
||||
</manifest>
|
After Width: | Height: | Size: 38 KiB |
@ -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)
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="208"
|
||||
android:viewportHeight="208">
|
||||
<path android:pathData="M163.2,113.7c6.4,-1.2 3.9,-14.4 6.6,-5.2"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#000"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M171.8,59c2.1,-5.6 5.7,-6.7 8.6,-8.3 -4.7,7.3 -1.4,10.2 -2.5,13 -2.3,5.5 -7.9,0 -6,-4.8z"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#fe9d2a"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M44.3,75.2c1.8,-4.2 4.8,-5 7.3,-6.1 -4,5.4 -1.2,7.6 -2.2,9.7 -1.9,4.1 -6.6,0 -5,-3.6zM27.3,51.9c4,5.4 3,9.6 4,11.7 1.8,4.1 5.9,0.5 4.3,-3.1a19,19 0,0 0,-8.4 -8.6z"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#f3611e"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M89.3,91.9l2.8,12.1L23,104l2.5,-19.7a50.9,50.9 0,0 1,18.3 -19c-6.4,7.6 -5.7,21.1 8.6,24.8 9.2,2.4 28.6,-9.8 36.8,1.8zM120.7,92.6c21.9,-9.5 26.4,-1.8 34.8,-4 17.2,-5.3 15.7,-13.6 9.7,-23.4A51,51 0,0 1,183.5 84l2.5,20h-66.6l1.3,-11.4zM89.3,91.9zM159.5,78c0.3,3 5.5,3.2 6.4,0 -0.3,-7 -5.5,-9.2 -10.7,-10.8 2.8,3.6 4.3,5.3 4.3,10.8zM34.1,64.4c-1.8,-4.1 -6.6,-5.2 -9.1,-6.3 4,5.4 3,7.8 4,9.9 1.8,4.1 6.6,0 5,-3.6z"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#f00027"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M87.3,92.8c-24.6,-8.2 -41,7 -45.9,6.5 -19,-2.3 -19,-16.2 -13.8,-28.6 -4.3,6.5 -9.6,7 -12.3,22.7 -2.3,-2.3 -4.8,-3 -5.8,-11.6 -5.4,16.9 -2,20.8 0.5,26.2 -2.6,-1.6 -5,-3.5 -7.2,-5.5 0.7,7.9 3.5,11.6 5.8,16.3l-5.8,-3.5c9.5,25.9 23.3,35.6 39.6,36.1 16,-1.5 24.2,-4 33.7,-6.3l11.2,-52.3zM123.3,94.8c4,-2.3 10.3,-3.1 15.4,-3 8.7,-0.5 25.2,7.9 29,7.9 9.3,0.1 21.1,-9.6 14,-28.3 4.9,6.5 9.9,10.8 12,22 3.5,-2 4.6,-6.3 6,-10.4 4,9.1 3,17.4 -0.2,25.3 2,-1.1 4,-2.4 6.6,-5.6 0.3,7 -3.6,11 -6,16l5,-2.6c-6,17.8 -15.6,32.7 -36.1,35.6 -13,-2.9 -25.4,-6.9 -41,-6.4l-9.2,-24.4s6.1,-28.4 4.6,-26.1"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#cc002b"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M163,113.5c2.8,-0.6 4.8,-3.2 5.9,-8.7l1,3.6"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#0000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="square"/>
|
||||
<path android:pathData="M177,110a19,19 0,0 0,5.4 -6.8l1,7M154,129.3c5.6,0.8 6.9,-2.7 9.4,-5l-0.5,3.7"
|
||||
android:strokeLineJoin="bevel"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#0000"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M44,112.7c-2.7,-0.6 -4.7,-3.2 -5.9,-8.7l-1,3.6"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#0000"
|
||||
android:strokeColor="#000"
|
||||
android:strokeLineCap="square"/>
|
||||
<path android:pathData="M30.1,109.2a19,19 0,0 1,-5.4 -6.8l-1,7M53,128.4c-5.5,0.7 -6.8,-2.7 -9.3,-5l0.5,3.7"
|
||||
android:strokeLineJoin="bevel"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#0000"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M24.3 105c0.3 7 1.1 13.8 6.2 18.6a24 24 0 0 1-6.8-2.7c2.2 4.4 4.5 8.8 9 12l-4.7-0.3c2.5 4.5 6.3 8.1 10 11.8-8.3-3.7-17.7-5.7-20.6-18.9 2.3 2.2 3 1.6 4.1 1.9-4-4-5.3-9.3-7-13.8 2 2 4.3 3.5 6.8 4.4-6-6.2-4.5-14.3-4.2-22.2 1.8 3.8 3.9 7.3 7.2 9.2zm10.2 18c3 1.6 4.2 4.3 9.7 4 1 5.4 5 8.4 9.4 11.4-6.1 0.7-14.1-0.1-19-15.4zm5.8-0.8C30.3 119 29 109.4 31 98.9c2 6.6 3.9 7.2 6 8.2-0.7 6-0.5 12 3.3 15z"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#f00027"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M182.8 105.8c3.3-1.9 5.3-5.4 7.2-9.1 0.3 7.8 1.8 16-4.2 22.1a17 17 0 0 0 6.7-4.3c-1.6 4.4-2.8 9.6-6.9 13.7 1.2-0.3 1.8 0.3 4.2-1.9-3 13.2-12.4 15.3-20.7 19 3.7-3.8 7.5-7.4 10-12-1.8 0.3-3.4 0.4-4.7 0.4 4.5-3.2 6.8-7.6 9-12a24 24 0 0 1-6.8 2.7c5-4.8 6-11.6 6.2-18.6zm-20 22c5.6 0.3 6.8-2.4 9.8-4-5 15.3-13 16-19.1 15.4 4.3-3 8.4-6 9.4-11.3zm4-3.1c10-3.2 11.3-12.8 9.3-23.3-2 6.6-3.9 7.2-6 8.2 0.7 6 0.5 12-3.3 15z"
|
||||
android:strokeWidth="1.1"
|
||||
android:fillColor="#f00027"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M146.8,95.8c0,-3.6 -2.8,-6.5 -6.4,-6.5a6.5,6.5 0,0 0,-6.4 6.5v26.7c0,3.5 3,6.4 6.4,6.4 3.5,0 6.4,-3 6.4,-6.4L146.8,95.7zM72,95.8c0,-3.6 -2.8,-6.5 -6.4,-6.5a6.5,6.5 0,0 0,-6.4 6.5v26.7c0,3.5 3,6.4 6.4,6.4 3.5,0 6.4,-3 6.5,-6.4L72.1,95.7zM117,65.2l4.6,-8.2s0.4,-0.7 -0.4,-1.2c-0.7,-0.4 -1.2,0.4 -1.2,0.4l-4.6,8.2a30.4,30.4 0,0 0,-24.7 0L86,56.2s-0.5,-0.8 -1.2,-0.4c-0.8,0.4 -0.4,1.3 -0.4,1.3l4.5,8.1a26,26 0,0 0,-14.6 23h57.2a26,26 0,0 0,-14.5 -23zM74.4,90.5v41.4a7,7 0,0 0,7 6.9h4.5v14.1c0,3.6 3,6.4 6.4,6.4 3.6,0 6.4,-2.8 6.5,-6.4v-14.1h8.5v14.1c0,3.6 3,6.4 6.4,6.4 3.5,0 6.4,-2.8 6.4,-6.4v-14.1h4.6a7,7 0,0 0,7 -6.9L131.7,90.4L74.3,90.4z"
|
||||
android:fillColor="#79c257"/>
|
||||
<path android:pathData="M94.6,88.6c4.5,5 11.5,3.3 17.2,3 -6,-1 -12.6,-2.5 -17,-8.6"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#f8db00"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M106.5,90.2c4,-2.8 8.9,-2.5 13.3,-1.4 -2.3,-1.8 -1.5,-3.4 -14.6,-2.6a17,17 0,0 1,-9.4 -2.4c1.8,3.2 6.1,6 10.7,6.4z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#d36224"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M122.2,91c2,-4 -5,-9.6 -6.8,-9.6 -5,-0.5 -14.7,-0.3 -14.4,-3 -3,-0.4 -5,1 -5.8,4 2.7,2.2 5.7,3.7 9.3,3.8 12.3,-0.9 14,0.8 15,2.6l2.7,2.2z"
|
||||
android:strokeLineJoin="bevel"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#ecd404"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M89.7,77.2L84,79.5l1,-2.3L89,74l0.8,1v2.2zM114.8,76.7l-5.7,1.9 1.2,-2.2 4,-3 0.7,1 -0.2,2.3z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#680000"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M84.3,78.6c1.8,-4.7 3.7,-6.3 7.3,-5.1 0.6,0.3 1.3,0.3 1.5,2.2 -0.1,1.4 -0.8,2 -2,2 -2.3,0 -2,-1.5 -2,-3 -2.4,0 -4.8,8.6 -7,3h0.7c0,1.1 1.3,1.4 1.5,0.9zM109.3,77.7c2.2,-4.5 4.2,-6 7.7,-4.6 0.6,0.4 1.3,0.4 1.3,2.4 -0.2,1.3 -0.9,1.9 -2,1.8 -2.3,0 -2,-1.7 -1.8,-3.2 -2.5,-0.1 -5.5,8.3 -7.2,2.6h0.6c0,1.1 1.2,1.5 1.4,1z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#000"
|
||||
android:strokeColor="#000"/>
|
||||
<path android:pathData="M92.3,75.7c0,0.6 -0.4,1 -1,1a1,1 0,0 1,-1 -1c0,-0.5 0.5,-1 1,-1 0.6,0 1,0.5 1,1zM117.6,75.4c0,0.6 -0.5,1 -1,1a1,1 0,0 1,-1 -1.1c0,-0.5 0.5,-1 1,-1a1,1 0,0 1,1 1.1z"
|
||||
android:fillColor="#fff"/>
|
||||
</vector>
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<resources>
|
||||
<color name="ic_launcher_background">@color/debug_launcher_background</color>
|
||||
</resources>
|
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shortcut
|
||||
android:shortcutId="open_new_tab"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_static_shortcut_tab"
|
||||
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_tab_2"
|
||||
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_tab_2">
|
||||
<intent
|
||||
android:action="org.mozilla.fenix.OPEN_TAB"
|
||||
android:targetPackage="org.mozilla.fenix.debug"
|
||||
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
android:shortcutId="open_new_private_tab"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_static_shortcut_private_tab"
|
||||
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_private_tab_2"
|
||||
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_private_tab_2">
|
||||
<intent
|
||||
android:action="org.mozilla.fenix.OPEN_PRIVATE_TAB"
|
||||
android:targetPackage="org.mozilla.fenix.debug"
|
||||
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 64 KiB |
After Width: | Height: | Size: 58 KiB |
@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="1.0711575"
|
||||
android:scaleY="1.0711575"
|
||||
android:translateX="25.634476"
|
||||
android:translateY="22.68">
|
||||
<path
|
||||
android:pathData="M28.3719,28.26m-19.5905,0a19.5905,19.5905 0,1 1,39.181 0a19.5905,19.5905 0,1 1,-39.181 0"
|
||||
android:strokeWidth="0.52916664"
|
||||
android:fillColor="#3fb6e4"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m5.5977,16.5538c0.2014,-1.0367 1.2707,-6.9964 6.2074,-9.3502 0.3143,-0.1499 -2.4423,-0.4567 -2.4423,-0.4567 0,0 4.1451,-0.2164 4.5249,-0.3157 0.5581,-0.146 -1.2765,-0.7155 -1.2765,-0.7155 0,0 1.3305,0.1329 2.6028,0.2679 0.4649,0.0494 -0.8314,-0.8593 -0.8314,-0.8593 0,0 2.9055,1.0617 3.0391,1.1162 1.0769,0.4389 2.4513,1.0046 2.9726,2.6058 4.3403,0.0472 8.3695,0.6379 9.5297,3.9432h-9.5297c-4.506,1.7449 -4.6979,3.3341 -2.3175,4.9098 -3.5193,1.5494 -12.4791,-1.1454 -12.4791,-1.1454z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#000080"
|
||||
android:strokeColor="#000080"
|
||||
android:strokeLineCap="butt"/>
|
||||
<path
|
||||
android:pathData="m5.5977,16.5538c-2.9204,2.3027 8.554,34.4432 16.3678,36.7738 4.1443,-4.8153 1.8719,-31.8116 0.1226,-34.0038 -1.7705,-2.2188 -4.0112,-1.6246 -4.0112,-1.6246z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0.26458332"
|
||||
android:fillColor="#000080"
|
||||
android:strokeColor="#000080"
|
||||
android:strokeLineCap="butt"/>
|
||||
</group>
|
||||
</vector>
|
@ -0,0 +1,22 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:pathData="m23,64.39 l0.42,1.52 0.42,1.52 -0.83,-3.05"
|
||||
android:strokeLineJoin="miter"
|
||||
android:fillColor="#6d6d6d"/>
|
||||
<path
|
||||
android:pathData="m42.32,37.79l0,0l1.13,-1.36c0.01,-0.01 0.02,-0.03 0.04,-0.04l0,0l1.23,-1.26c0.01,-0.01 0.03,-0.02 0.04,-0.04l0,0l1.33,-1.15c0.01,-0.01 0.03,-0.02 0.04,-0.03l0,0l1.41,-1.03c0.01,-0.01 0.03,-0.02 0.04,-0.03l0,0l1.49,-0.9c0.01,-0.01 0.03,-0.02 0.05,-0.03l0,0l1.55,-0.77c0.02,-0.01 0.03,-0.01 0.05,-0.02l0,0L52.32,30.5c0.02,-0.01 0.03,-0.01 0.05,-0.02l0,0l1.65,-0.49c0.02,-0 0.03,-0.01 0.05,-0.01l0,0l1.68,-0.35c0.02,-0 0.03,-0.01 0.05,-0.01l0,0l1.71,-0.2c0.02,-0 0.03,-0 0.05,-0l0,0l1.72,-0.05c0.02,-0 0.03,-0 0.05,0l0,0l1.71,0.1c0.02,0 0.03,0 0.05,0.01l0,0l1.7,0.24c0.02,0 0.03,0.01 0.05,0.01l0,0l1.68,0.39c0.02,0 0.03,0.01 0.05,0.01l0,0l1.64,0.53c0.02,0.01 0.03,0.01 0.05,0.02l0,0L67.85,31.35c0.02,0.01 0.03,0.01 0.05,0.02l0,0l1.53,0.81c0.02,0.01 0.03,0.02 0.05,0.03l0,0l1.47,0.94c0.01,0.01 0.03,0.02 0.04,0.03l0,0l1.39,1.07c0.01,0.01 0.03,0.02 0.04,0.03l0,0l1.3,1.18c0.01,0.01 0.03,0.02 0.04,0.04l0,0l1.2,1.29c0.01,0.01 0.02,0.03 0.03,0.04l0,0l1.1,1.39c0.01,0.01 0.02,0.03 0.03,0.04l0,0l0.99,1.48c0.01,0.01 0.02,0.03 0.03,0.04l0,0l0.83,1.49c0.01,0.02 0.02,0.03 0.02,0.05l0,0l0.72,1.56c0.01,0.02 0.01,0.03 0.02,0.05l0,0l0.6,1.61c0.01,0.02 0.01,0.03 0.02,0.05l0,0l0.48,1.66c0,0.02 0.01,0.03 0.01,0.05l0,0l0.35,1.69c0,0.02 0.01,0.03 0.01,0.05l0,0l0.23,1.71c0,0.02 0,0.03 0,0.05l0,0l0.1,1.73c0,0.02 0,0.03 0,0.05l0,0l-0.03,1.73c-0,0.02 -0,0.03 -0,0.05l0,0l-0.16,1.72c-0,0.02 -0,0.03 -0.01,0.05l0,0l-0.29,1.7c-0,0.02 -0.01,0.03 -0.01,0.05l0,0l-0.41,1.67c-0,0.02 -0.01,0.03 -0.01,0.05l0,0l-0.54,1.64c-0.01,0.02 -0.01,0.03 -0.02,0.05l0,0L78.4,61.89c-0.01,0.02 -0.01,0.03 -0.02,0.05l0,0l-0.77,1.53c-0.01,0.02 -0.02,0.03 -0.03,0.05l0,0l-0.88,1.46c-0.01,0.01 -0.02,0.03 -0.03,0.04l0,0l-0.99,1.38c-0.01,0.01 -0.02,0.03 -0.03,0.04l0,0l-1.09,1.29c-0.23,0.27 -0.63,0.3 -0.89,0.06 -0.26,-0.24 -0.28,-0.66 -0.05,-0.94l0,0l1.09,-1.29 -0.03,0.04 0.99,-1.38 -0.03,0.04 0.88,-1.46 -0.03,0.05 0.77,-1.53 -0.02,0.05 0.66,-1.59 -0.02,0.05 0.54,-1.64 -0.01,0.05 0.41,-1.67 -0.01,0.05 0.29,-1.7 -0.01,0.05 0.16,-1.72 -0,0.05 0.03,-1.73 0,0.05 -0.1,-1.73 0,0.05 -0.23,-1.71 0.01,0.05 -0.35,-1.69 0.01,0.05 -0.48,-1.66 0.02,0.05 -0.6,-1.61 0.02,0.05 -0.72,-1.56 0.02,0.05 -0.83,-1.49 0.03,0.04 -0.99,-1.48 0.03,0.04 -1.1,-1.39 0.03,0.04 -1.2,-1.29 0.04,0.04 -1.3,-1.18 0.04,0.03 -1.39,-1.07 0.04,0.03 -1.47,-0.94 0.05,0.03 -1.53,-0.81 0.05,0.02 -1.59,-0.68 0.05,0.02 -1.64,-0.53 0.05,0.01 -1.68,-0.39 0.05,0.01 -1.7,-0.24 0.05,0.01 -1.71,-0.1 0.05,0 -1.72,0.05 0.05,-0 -1.71,0.2 0.05,-0.01 -1.68,0.35 0.05,-0.01 -1.65,0.49 0.05,-0.02 -1.61,0.63 0.05,-0.02 -1.55,0.77 0.05,-0.03 -1.49,0.9 0.04,-0.03 -1.41,1.03 0.04,-0.03 -1.33,1.15 0.04,-0.04 -1.23,1.26 0.04,-0.04 -1.13,1.36c-0.23,0.28 -0.63,0.3 -0.89,0.06 -0.26,-0.24 -0.29,-0.66 -0.06,-0.94z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:fillColor="#055493"/>
|
||||
<path
|
||||
android:pathData="m-2211.74,-4248.24v0,0 0,0 0,0 0,0 0m0,43.56z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:fillColor="#055493"/>
|
||||
<path
|
||||
android:pathData="m51.91,26.95l0,0 0,0l-0,0 0,0l0,-0 0,-0 0,-0 0,0zM51.91,26.95l0,0 0,0c-3.62,0.08 -7.42,1.24 -9.12,2.19 -7.25,4.08 -10.38,7.59 -15.25,12.89l5.08,-3.64 0,0c-2.37,3.25 -4.05,8.04 -4.3,13.5 -0.11,2.51 0.18,7.11 1.63,10.58 1.79,4.29 3.76,6.63 5.9,8.3 0.05,0.04 0.1,0.08 0.16,0.12l1.1,0.74c4.7,1.64 8.04,2.2 11.97,2.2l0,0c1.15,-0 2.25,-0.11 3.27,-0.36l0,0c3.16,-0.75 5.9,-2.77 8.53,-5.44l0,0c1.43,-1.64 2.66,-3.75 3.64,-6.58 1.13,-3.27 1.51,-5.9 1.05,-8.78l-0,0c-1.68,2.33 -3.17,2.48 -3.25,2.49l-0,0c-0.02,-0 3.13,-5.43 4.21,-9.94l-0,0c-3.87,3.32 -7.45,5.29 -10.71,6.04 -2.09,0.48 -3.99,0.78 -5.73,0.78 -2.18,0 -4.09,-0.41 -5.72,-1.23 -1.82,-3.98 -2.22,-7.5 -1.07,-10.52 0.52,-1.38 1.32,-2.66 2.27,-3.9l6.36,-9.44 -0,0zM77.69,40.88l0,0 0,0l-0,0l0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0,0c1.87,6.34 2.78,9.53 -0.12,16.7 -3.32,8.21 -8.64,10.36 -13.09,10.72l0,0c-3.31,3.69 -7.01,6.91 -11.53,7.98l0,0c-1.25,0.3 -2.55,0.43 -3.88,0.43l-0,0c-0.96,-0 -1.93,-0.07 -2.93,-0.19l-4.12,-0.59c2.08,1.75 7.1,4.14 9.8,4.64 2.24,0.41 5.52,0.48 7.56,0.48 11.1,0 19.23,-7.83 22.61,-18.93 2.1,-6.91 0.41,-14.86 -0.77,-20.47l-0.09,6.21 -3.45,-6.98l0,0z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:fillColor="#055493"/>
|
||||
</vector>
|
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 24 KiB |
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/search"
|
||||
android:icon="@drawable/ic_search"
|
||||
android:title="@string/addons_search_hint"
|
||||
app:iconTint="?textPrimary"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
android:contentDescription="@string/addons_search_hint"
|
||||
app:showAsAction="ifRoom|collapseActionView" />
|
||||
</menu>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_private_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_private_foreground"/>
|
||||
</adaptive-icon>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_private_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_private_foreground"/>
|
||||
</adaptive-icon>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 19 KiB |
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#F6F6F6</color>
|
||||
</resources>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<!-- 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/. -->
|
||||
<resources>
|
||||
<!-- Name of the application -->
|
||||
<string name="app_name" translatable="false">Iceraven</string>
|
||||
</resources>
|
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shortcut
|
||||
android:shortcutId="open_new_tab"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_static_shortcut_tab"
|
||||
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_tab_2"
|
||||
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_tab_2">
|
||||
<intent
|
||||
android:action="org.mozilla.fenix.OPEN_TAB"
|
||||
android:targetPackage="io.github.forkmaintainers.iceraven"
|
||||
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
|
||||
</shortcut>
|
||||
<shortcut
|
||||
android:shortcutId="open_new_private_tab"
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/ic_static_shortcut_private_tab"
|
||||
android:shortcutShortLabel="@string/home_screen_shortcut_open_new_private_tab_2"
|
||||
android:shortcutLongLabel="@string/home_screen_shortcut_open_new_private_tab_2">
|
||||
<intent
|
||||
android:action="org.mozilla.fenix.OPEN_PRIVATE_TAB"
|
||||
android:targetPackage="io.github.forkmaintainers.iceraven"
|
||||
android:targetClass="org.mozilla.fenix.IntentReceiverActivity" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
After Width: | Height: | Size: 45 KiB |
@ -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) {
|
||||
}
|
||||
}
|
@ -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 "";
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/* 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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<RemoteMessage> CREATOR = new Creator<RemoteMessage>()
|
||||
{
|
||||
@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<String, String> getData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -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<String, ?> userAttributes) {
|
||||
}
|
||||
|
||||
public static void start(Context context) {
|
||||
}
|
||||
|
||||
public static void start(Context context, StartCallback callback) {
|
||||
}
|
||||
|
||||
public static void start(Context context, Map<String, ?> 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<String, ?> userAttributes) {
|
||||
}
|
||||
|
||||
public static synchronized void start(final Context context, String userId, Map<String, ?> attributes, StartCallback response) {
|
||||
}
|
||||
|
||||
static synchronized void start(final Context context, final String userId, final Map<String, ?> attributes, StartCallback response, final Boolean isBackground) {
|
||||
}
|
||||
|
||||
public static void track(final String event, double value, String info, Map<String, ?> 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<String, ?> params) {
|
||||
}
|
||||
|
||||
public static void track(String event, double value, Map<String, ?> params) {
|
||||
}
|
||||
|
||||
public static void track(String event, double value, String info) {
|
||||
}
|
||||
|
||||
public static String getDeviceId() { return "stub"; }
|
||||
|
||||
public static String getUserId() { return "stub"; }
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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<Addon> {
|
||||
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<Addon>? {
|
||||
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<Addon> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<Addon.Author> {
|
||||
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<String, String> {
|
||||
return if (isNull(valueKey)) {
|
||||
emptyMap()
|
||||
} else {
|
||||
val map = mutableMapOf<String, String>()
|
||||
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))
|
||||
}
|
||||
}
|
@ -0,0 +1,313 @@
|
||||
/* 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.ui.icons.R as iconsR
|
||||
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 mozilla.components.support.utils.ext.getParcelableCompat
|
||||
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.getParcelableCompat(KEY_ADDON, Addon::class.java))
|
||||
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<TextView>(R.id.title).text =
|
||||
requireContext().getString(
|
||||
R.string.mozac_feature_addons_installed_dialog_title,
|
||||
addon.translateName(requireContext()),
|
||||
requireContext().appName,
|
||||
)
|
||||
|
||||
val icon = safeArguments.getParcelableCompat(KEY_ICON, Bitmap::class.java)
|
||||
if (icon != null) {
|
||||
binding.icon.setImageDrawable(BitmapDrawable(resources, icon))
|
||||
} else {
|
||||
iconJob = fetchIcon(addon, binding.icon)
|
||||
}
|
||||
|
||||
val allowedInPrivateBrowsing = rootView.findViewById<AppCompatCheckBox>(R.id.allow_in_private_browsing)
|
||||
allowedInPrivateBrowsing.setOnCheckedChangeListener { _, isChecked ->
|
||||
allowPrivateBrowsing = isChecked
|
||||
}
|
||||
|
||||
val confirmButton = rootView.findViewById<Button>(R.id.confirm_button)
|
||||
confirmButton.setOnClickListener {
|
||||
onConfirmButtonClicked?.invoke(addon, allowPrivateBrowsing)
|
||||
dismiss()
|
||||
}
|
||||
|
||||
if (confirmButtonBackgroundColor != DEFAULT_VALUE) {
|
||||
val backgroundTintList =
|
||||
ContextCompat.getColorStateList(requireContext(), confirmButtonBackgroundColor)
|
||||
confirmButton.backgroundTintList = backgroundTintList
|
||||
}
|
||||
|
||||
if (confirmButtonTextColor != DEFAULT_VALUE) {
|
||||
val color = ContextCompat.getColor(requireContext(), confirmButtonTextColor)
|
||||
confirmButton.setTextColor(color)
|
||||
}
|
||||
|
||||
if (confirmButtonRadius != DEFAULT_VALUE.toFloat()) {
|
||||
val shape = GradientDrawable()
|
||||
shape.shape = GradientDrawable.RECTANGLE
|
||||
shape.setColor(
|
||||
ContextCompat.getColor(
|
||||
requireContext(),
|
||||
confirmButtonBackgroundColor,
|
||||
),
|
||||
)
|
||||
shape.cornerRadius = confirmButtonRadius
|
||||
confirmButton.background = shape
|
||||
}
|
||||
|
||||
return rootView
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun fetchIcon(addon: Addon, iconView: ImageView, scope: CoroutineScope = this.scope): Job {
|
||||
return scope.launch {
|
||||
try {
|
||||
val iconBitmap = addonCollectionProvider?.getAddonIconBitmap(addon)
|
||||
iconBitmap?.let {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
safeArguments.putParcelable(KEY_ICON, it)
|
||||
iconView.setImageDrawable(BitmapDrawable(iconView.resources, it))
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
val context = iconView.context
|
||||
val att = context.theme.resolveAttribute(android.R.attr.textColorPrimary)
|
||||
iconView.setColorFilter(ContextCompat.getColor(context, att))
|
||||
iconView.setImageDrawable(
|
||||
ContextCompat.getDrawable(context, iconsR.drawable.mozac_ic_extensions),
|
||||
)
|
||||
}
|
||||
logger.error("Attempt to fetch the ${addon.id} icon failed", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun show(manager: FragmentManager, tag: String?) {
|
||||
// This dialog is shown as a result of an async operation (installing
|
||||
// an add-on). Once installation succeeds, the activity may already be
|
||||
// in the process of being destroyed. Since the dialog doesn't have any
|
||||
// state we need to keep, and since it's also fine to not display the
|
||||
// dialog at all in case the user navigates away, we can simply use
|
||||
// commitAllowingStateLoss here to prevent crashing on commit:
|
||||
// https://github.com/mozilla-mobile/android-components/issues/7782
|
||||
val ft = manager.beginTransaction()
|
||||
ft.add(this, tag)
|
||||
ft.commitAllowingStateLoss()
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
companion object {
|
||||
/**
|
||||
* Returns a new instance of [AddonInstallationDialogFragment].
|
||||
* @param addon The addon to show in the dialog.
|
||||
* @param promptsStyling Styling properties for the dialog.
|
||||
* @param onConfirmButtonClicked A lambda called when the confirm button is clicked.
|
||||
*/
|
||||
fun newInstance(
|
||||
addon: Addon,
|
||||
addonCollectionProvider: PagedAddonCollectionProvider,
|
||||
promptsStyling: PromptsStyling? = PromptsStyling(
|
||||
gravity = Gravity.BOTTOM,
|
||||
shouldWidthMatchParent = true,
|
||||
),
|
||||
onConfirmButtonClicked: ((Addon, Boolean) -> Unit)? = null,
|
||||
): PagedAddonInstallationDialogFragment {
|
||||
val fragment = PagedAddonInstallationDialogFragment()
|
||||
val arguments = fragment.arguments ?: Bundle()
|
||||
|
||||
arguments.apply {
|
||||
putParcelable(KEY_INSTALLED_ADDON, addon)
|
||||
|
||||
promptsStyling?.gravity?.apply {
|
||||
putInt(KEY_DIALOG_GRAVITY, this)
|
||||
}
|
||||
promptsStyling?.shouldWidthMatchParent?.apply {
|
||||
putBoolean(KEY_DIALOG_WIDTH_MATCH_PARENT, this)
|
||||
}
|
||||
promptsStyling?.confirmButtonBackgroundColor?.apply {
|
||||
putInt(KEY_CONFIRM_BUTTON_BACKGROUND_COLOR, this)
|
||||
}
|
||||
|
||||
promptsStyling?.confirmButtonTextColor?.apply {
|
||||
putInt(KEY_CONFIRM_BUTTON_TEXT_COLOR, this)
|
||||
}
|
||||
}
|
||||
fragment.onConfirmButtonClicked = onConfirmButtonClicked
|
||||
fragment.arguments = arguments
|
||||
fragment.addonCollectionProvider = addonCollectionProvider
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Styling for the addon installation dialog.
|
||||
*/
|
||||
data class PromptsStyling(
|
||||
val gravity: Int,
|
||||
val shouldWidthMatchParent: Boolean = false,
|
||||
@ColorRes
|
||||
val confirmButtonBackgroundColor: Int? = null,
|
||||
@ColorRes
|
||||
val confirmButtonTextColor: Int? = null,
|
||||
val confirmButtonRadius: Float? = null,
|
||||
)
|
||||
}
|
||||
|
||||
internal const val KEY_ADDON = "KEY_ADDON"
|
@ -0,0 +1,436 @@
|
||||
/* 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.graphics.Bitmap
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.TransitionDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.RatingBar
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.feature.addons.Addon
|
||||
import mozilla.components.feature.addons.R
|
||||
import mozilla.components.ui.icons.R as iconsR
|
||||
import mozilla.components.feature.addons.ui.AddonsManagerAdapterDelegate
|
||||
import mozilla.components.feature.addons.ui.CustomViewHolder
|
||||
import mozilla.components.feature.addons.ui.CustomViewHolder.AddonViewHolder
|
||||
import mozilla.components.feature.addons.ui.CustomViewHolder.SectionViewHolder
|
||||
import mozilla.components.feature.addons.ui.CustomViewHolder.UnsupportedSectionViewHolder
|
||||
import mozilla.components.feature.addons.ui.translateName
|
||||
import mozilla.components.feature.addons.ui.translateSummary
|
||||
import mozilla.components.support.base.log.logger.Logger
|
||||
import mozilla.components.support.ktx.android.content.res.resolveAttribute
|
||||
import java.io.IOException
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
private const val VIEW_HOLDER_TYPE_SECTION = 0
|
||||
private const val VIEW_HOLDER_TYPE_NOT_YET_SUPPORTED_SECTION = 1
|
||||
private const val VIEW_HOLDER_TYPE_ADDON = 2
|
||||
|
||||
/**
|
||||
* An adapter for displaying add-on items. This will display information related to the state of
|
||||
* an add-on such as recommended, unsupported or installed. In addition, it will perform actions
|
||||
* such as installing an add-on.
|
||||
*
|
||||
* @property addonCollectionProvider Provider of AMO collection API.
|
||||
* @property addonsManagerDelegate Delegate that will provides method for handling the add-on items.
|
||||
* @param addons The list of add-on based on the AMO store.
|
||||
* @property style Indicates how items should look like.
|
||||
*/
|
||||
@Suppress("TooManyFunctions", "LargeClass")
|
||||
// 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 PagedAddonsManagerAdapter(
|
||||
private val addonCollectionProvider: PagedAddonCollectionProvider,
|
||||
private val addonsManagerDelegate: AddonsManagerAdapterDelegate,
|
||||
addons: List<Addon>,
|
||||
private val style: Style? = null,
|
||||
) : ListAdapter<Any, CustomViewHolder>(DifferCallback) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
private val logger = Logger("PagedAddonsManagerAdapter")
|
||||
|
||||
/**
|
||||
* Represents all the add-ons that will be distributed in multiple headers like
|
||||
* enabled, recommended and unsupported, this help have the data source of the items,
|
||||
* displayed in the UI.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal var addonsMap: MutableMap<String, Addon> = addons.associateBy({ it.id }, { it }).toMutableMap()
|
||||
|
||||
init {
|
||||
submitList(createListWithSections(addons))
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
|
||||
return when (viewType) {
|
||||
VIEW_HOLDER_TYPE_ADDON -> createAddonViewHolder(parent)
|
||||
VIEW_HOLDER_TYPE_SECTION -> createSectionViewHolder(parent)
|
||||
VIEW_HOLDER_TYPE_NOT_YET_SUPPORTED_SECTION -> createUnsupportedSectionViewHolder(parent)
|
||||
else -> throw IllegalArgumentException("Unrecognized viewType")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSectionViewHolder(parent: ViewGroup): CustomViewHolder {
|
||||
val context = parent.context
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val view = inflater.inflate(R.layout.mozac_feature_addons_section_item, parent, false)
|
||||
val titleView = view.findViewById<TextView>(R.id.title)
|
||||
val divider = view.findViewById<View>(R.id.divider)
|
||||
return SectionViewHolder(view, titleView, divider)
|
||||
}
|
||||
|
||||
private fun createUnsupportedSectionViewHolder(parent: ViewGroup): CustomViewHolder {
|
||||
val context = parent.context
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val view = inflater.inflate(
|
||||
R.layout.mozac_feature_addons_section_unsupported_section_item,
|
||||
parent,
|
||||
false,
|
||||
)
|
||||
val titleView = view.findViewById<TextView>(R.id.title)
|
||||
val descriptionView = view.findViewById<TextView>(R.id.description)
|
||||
|
||||
return UnsupportedSectionViewHolder(view, titleView, descriptionView)
|
||||
}
|
||||
|
||||
private fun createAddonViewHolder(parent: ViewGroup): AddonViewHolder {
|
||||
val context = parent.context
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val view = inflater.inflate(R.layout.mozac_feature_addons_item, parent, false)
|
||||
val iconView = view.findViewById<ImageView>(R.id.add_on_icon)
|
||||
val titleView = view.findViewById<TextView>(R.id.add_on_name)
|
||||
val summaryView = view.findViewById<TextView>(R.id.add_on_description)
|
||||
val ratingView = view.findViewById<RatingBar>(R.id.rating)
|
||||
val ratingAccessibleView = view.findViewById<TextView>(R.id.rating_accessibility)
|
||||
val userCountView = view.findViewById<TextView>(R.id.users_count)
|
||||
val addButton = view.findViewById<ImageView>(R.id.add_button)
|
||||
val allowedInPrivateBrowsingLabel = view.findViewById<ImageView>(R.id.allowed_in_private_browsing_label)
|
||||
return AddonViewHolder(
|
||||
view,
|
||||
iconView,
|
||||
titleView,
|
||||
summaryView,
|
||||
ratingView,
|
||||
ratingAccessibleView,
|
||||
userCountView,
|
||||
addButton,
|
||||
allowedInPrivateBrowsingLabel,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (getItem(position)) {
|
||||
is Addon -> VIEW_HOLDER_TYPE_ADDON
|
||||
is Section -> VIEW_HOLDER_TYPE_SECTION
|
||||
is NotYetSupportedSection -> VIEW_HOLDER_TYPE_NOT_YET_SUPPORTED_SECTION
|
||||
else -> throw IllegalArgumentException("items[position] has unrecognized type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
|
||||
when (holder) {
|
||||
is SectionViewHolder -> bindSection(holder, item as Section)
|
||||
is AddonViewHolder -> bindAddon(holder, item as Addon)
|
||||
is UnsupportedSectionViewHolder -> bindNotYetSupportedSection(
|
||||
holder,
|
||||
item as NotYetSupportedSection,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun bindSection(holder: SectionViewHolder, section: Section) {
|
||||
holder.titleView.setText(section.title)
|
||||
style?.maybeSetSectionsTextColor(holder.titleView)
|
||||
style?.maybeSetSectionsTypeFace(holder.titleView)
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun bindNotYetSupportedSection(
|
||||
holder: UnsupportedSectionViewHolder,
|
||||
section: NotYetSupportedSection,
|
||||
) {
|
||||
val unsupportedAddons = addonsMap.values.filter { it.inUnsupportedSection() }
|
||||
val context = holder.itemView.context
|
||||
holder.titleView.setText(section.title)
|
||||
holder.descriptionView.text =
|
||||
if (unsupportedAddons.size == 1) {
|
||||
context.getString(R.string.mozac_feature_addons_unsupported_caption)
|
||||
} else {
|
||||
context.getString(
|
||||
R.string.mozac_feature_addons_unsupported_caption_plural,
|
||||
unsupportedAddons.size.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
addonsManagerDelegate.onNotYetSupportedSectionClicked(unsupportedAddons)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun bindAddon(holder: AddonViewHolder, addon: Addon) {
|
||||
val context = holder.itemView.context
|
||||
addon.rating?.let {
|
||||
val userCount = context.getString(R.string.mozac_feature_addons_user_rating_count_2)
|
||||
val ratingContentDescription =
|
||||
String.format(
|
||||
context.getString(R.string.mozac_feature_addons_rating_content_description),
|
||||
it.average,
|
||||
)
|
||||
holder.ratingView.contentDescription = ratingContentDescription
|
||||
// Android RatingBar is not very accessibility-friendly, we will use non visible TextView
|
||||
// for contentDescription for the TalkBack feature
|
||||
holder.ratingAccessibleView.text = ratingContentDescription
|
||||
holder.ratingView.rating = it.average
|
||||
holder.userCountView.text = String.format(userCount, getFormattedAmount(it.reviews))
|
||||
}
|
||||
|
||||
holder.titleView.text =
|
||||
if (addon.translatableName.isNotEmpty()) {
|
||||
addon.translateName(context)
|
||||
} else {
|
||||
addon.id
|
||||
}
|
||||
|
||||
if (addon.translatableSummary.isNotEmpty()) {
|
||||
holder.summaryView.text = addon.translateSummary(context)
|
||||
} else {
|
||||
holder.summaryView.visibility = View.GONE
|
||||
}
|
||||
|
||||
holder.itemView.tag = addon
|
||||
holder.itemView.setOnClickListener {
|
||||
addonsManagerDelegate.onAddonItemClicked(addon)
|
||||
}
|
||||
|
||||
holder.addButton.isVisible = !addon.isInstalled()
|
||||
holder.addButton.setOnClickListener {
|
||||
if (!addon.isInstalled()) {
|
||||
addonsManagerDelegate.onInstallAddonButtonClicked(addon)
|
||||
}
|
||||
}
|
||||
|
||||
holder.allowedInPrivateBrowsingLabel.isVisible = addon.isAllowedInPrivateBrowsing()
|
||||
style?.maybeSetPrivateBrowsingLabelDrawale(holder.allowedInPrivateBrowsingLabel)
|
||||
|
||||
fetchIcon(addon, holder.iconView)
|
||||
style?.maybeSetAddonNameTextColor(holder.titleView)
|
||||
style?.maybeSetAddonSummaryTextColor(holder.summaryView)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun fetchIcon(addon: Addon, iconView: ImageView, scope: CoroutineScope = this.scope): Job {
|
||||
return scope.launch {
|
||||
try {
|
||||
// We calculate how much time takes to fetch an icon,
|
||||
// if takes less than a second, we assume it comes
|
||||
// from a cache and we don't show any transition animation.
|
||||
val startTime = System.currentTimeMillis()
|
||||
val iconBitmap = addonCollectionProvider.getAddonIconBitmap(addon)
|
||||
val timeToFetch: Double = (System.currentTimeMillis() - startTime) / 1000.0
|
||||
val isFromCache = timeToFetch < 1
|
||||
iconBitmap?.let {
|
||||
scope.launch(Main) {
|
||||
if (isFromCache) {
|
||||
iconView.setImageDrawable(BitmapDrawable(iconView.resources, it))
|
||||
} else {
|
||||
setWithCrossFadeAnimation(iconView, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
scope.launch(Main) {
|
||||
val context = iconView.context
|
||||
val att = context.theme.resolveAttribute(android.R.attr.textColorPrimary)
|
||||
iconView.setColorFilter(ContextCompat.getColor(context, att))
|
||||
iconView.setImageDrawable(context.getDrawable(iconsR.drawable.mozac_ic_extensions))
|
||||
}
|
||||
logger.error("Attempt to fetch the ${addon.id} icon failed", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
@Suppress("ComplexMethod")
|
||||
internal fun createListWithSections(addons: List<Addon>): List<Any> {
|
||||
val itemsWithSections = ArrayList<Any>()
|
||||
val installedAddons = ArrayList<Addon>()
|
||||
val recommendedAddons = ArrayList<Addon>()
|
||||
val disabledAddons = ArrayList<Addon>()
|
||||
val unsupportedAddons = ArrayList<Addon>()
|
||||
|
||||
addons.forEach { addon ->
|
||||
when {
|
||||
addon.inUnsupportedSection() -> unsupportedAddons.add(addon)
|
||||
addon.inRecommendedSection() -> recommendedAddons.add(addon)
|
||||
addon.inInstalledSection() -> installedAddons.add(addon)
|
||||
addon.inDisabledSection() -> disabledAddons.add(addon)
|
||||
}
|
||||
}
|
||||
|
||||
// Add installed section and addons if available
|
||||
if (installedAddons.isNotEmpty()) {
|
||||
itemsWithSections.add(Section(R.string.mozac_feature_addons_enabled))
|
||||
itemsWithSections.addAll(installedAddons)
|
||||
}
|
||||
|
||||
// Add disabled section and addons if available
|
||||
if (disabledAddons.isNotEmpty()) {
|
||||
itemsWithSections.add(Section(R.string.mozac_feature_addons_disabled_section))
|
||||
itemsWithSections.addAll(disabledAddons)
|
||||
}
|
||||
|
||||
// Add recommended section and addons if available
|
||||
if (recommendedAddons.isNotEmpty()) {
|
||||
itemsWithSections.add(Section(R.string.mozac_feature_addons_recommended_section))
|
||||
itemsWithSections.addAll(recommendedAddons)
|
||||
}
|
||||
|
||||
// Add unsupported section
|
||||
if (unsupportedAddons.isNotEmpty()) {
|
||||
itemsWithSections.add(NotYetSupportedSection(R.string.mozac_feature_addons_unavailable_section))
|
||||
}
|
||||
|
||||
return itemsWithSections
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal data class Section(@StringRes val title: Int)
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal data class NotYetSupportedSection(@StringRes val title: Int)
|
||||
|
||||
/**
|
||||
* Allows to customize how items should look like.
|
||||
*/
|
||||
data class Style(
|
||||
@ColorRes
|
||||
val sectionsTextColor: Int? = null,
|
||||
@ColorRes
|
||||
val addonNameTextColor: Int? = null,
|
||||
@ColorRes
|
||||
val addonSummaryTextColor: Int? = null,
|
||||
val sectionsTypeFace: Typeface? = null,
|
||||
@DrawableRes
|
||||
val addonAllowPrivateBrowsingLabelDrawableRes: Int? = null,
|
||||
) {
|
||||
internal fun maybeSetSectionsTextColor(textView: TextView) {
|
||||
sectionsTextColor?.let {
|
||||
val color = ContextCompat.getColor(textView.context, it)
|
||||
textView.setTextColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun maybeSetSectionsTypeFace(textView: TextView) {
|
||||
sectionsTypeFace?.let {
|
||||
textView.typeface = it
|
||||
}
|
||||
}
|
||||
|
||||
internal fun maybeSetAddonNameTextColor(textView: TextView) {
|
||||
addonNameTextColor?.let {
|
||||
val color = ContextCompat.getColor(textView.context, it)
|
||||
textView.setTextColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun maybeSetAddonSummaryTextColor(textView: TextView) {
|
||||
addonSummaryTextColor?.let {
|
||||
val color = ContextCompat.getColor(textView.context, it)
|
||||
textView.setTextColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun maybeSetPrivateBrowsingLabelDrawale(imageView: ImageView) {
|
||||
addonAllowPrivateBrowsingLabelDrawableRes?.let {
|
||||
imageView.setImageDrawable(ContextCompat.getDrawable(imageView.context, it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the portion of the list that contains the provided [addon].
|
||||
* @property addon The add-on to be updated.
|
||||
*/
|
||||
fun updateAddon(addon: Addon) {
|
||||
addonsMap[addon.id] = addon
|
||||
submitList(createListWithSections(addonsMap.values.toList()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates only the portion of the list that changes between the current list and the new provided [addons].
|
||||
* Be aware that updating a subset of the visible list is not supported, [addons] will replace
|
||||
* the current list, but only the add-ons that have been changed will be updated in the UI.
|
||||
* If you provide a subset it will replace the current list.
|
||||
* @property addons A list of add-on to replace the actual list.
|
||||
*/
|
||||
fun updateAddons(addons: List<Addon>) {
|
||||
addonsMap = addons.associateBy({ it.id }, { it }).toMutableMap()
|
||||
submitList(createListWithSections(addons))
|
||||
}
|
||||
|
||||
internal object DifferCallback : DiffUtil.ItemCallback<Any>() {
|
||||
override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean {
|
||||
return when {
|
||||
oldItem is Addon && newItem is Addon -> oldItem.id == newItem.id
|
||||
oldItem is Section && newItem is Section -> oldItem.title == newItem.title
|
||||
oldItem is NotYetSupportedSection && newItem is NotYetSupportedSection -> oldItem.title == newItem.title
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DiffUtilEquals")
|
||||
override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setWithCrossFadeAnimation(image: ImageView, bitmap: Bitmap, durationMillis: Int = 1700) {
|
||||
with(image) {
|
||||
val bitmapDrawable = BitmapDrawable(context.resources, bitmap)
|
||||
val animation = TransitionDrawable(arrayOf(drawable, bitmapDrawable))
|
||||
animation.isCrossFadeEnabled = true
|
||||
setImageDrawable(animation)
|
||||
animation.startTransition(durationMillis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Addon.inUnsupportedSection() = isInstalled() && !isSupported()
|
||||
private fun Addon.inRecommendedSection() = !isInstalled()
|
||||
private fun Addon.inInstalledSection() = isInstalled() && isSupported() && isEnabled()
|
||||
private fun Addon.inDisabledSection() = isInstalled() && isSupported() && !isEnabled()
|
||||
|
||||
/**
|
||||
* Get the formatted number amount for the current default locale.
|
||||
*/
|
||||
internal fun getFormattedAmount(amount: Int): String {
|
||||
return NumberFormat.getNumberInstance(Locale.getDefault()).format(amount)
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/* 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 mozilla.components.lib.push.firebase
|
||||
|
||||
import android.content.Context
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import mozilla.components.concept.push.PushService
|
||||
|
||||
abstract class AbstractFirebasePushService : FirebaseMessagingService(), PushService {
|
||||
|
||||
override fun start(context: Context) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun onNewToken(newToken: String) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun onMessageReceived(remoteMessage: RemoteMessage?) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
final override fun stop() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun deleteToken() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override fun isServiceAvailable(context: Context): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|