First Commit
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
|
||||
import com.android.build.api.variant.FilterConfiguration.FilterType.ABI
|
||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
||||
import com.android.build.gradle.tasks.GenerateBuildConfig
|
||||
import com.google.firebase.appdistribution.gradle.firebaseAppDistribution
|
||||
import config.BuildTimeConfig
|
||||
import extension.AssetCopyTask
|
||||
import extension.GitBranchNameValueSource
|
||||
import extension.GitRevisionValueSource
|
||||
import extension.allEnterpriseImpl
|
||||
import extension.allFeaturesImpl
|
||||
import extension.allLibrariesImpl
|
||||
import extension.allServicesImpl
|
||||
import extension.buildConfigFieldStr
|
||||
import extension.koverDependencies
|
||||
import extension.locales
|
||||
import extension.setupDependencyInjection
|
||||
import extension.setupKover
|
||||
import extension.testCommonDependencies
|
||||
import java.util.Locale
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-application")
|
||||
alias(libs.plugins.kotlin.android)
|
||||
// When using precompiled plugins, we need to apply the firebase plugin like this
|
||||
id(libs.plugins.firebaseAppDistribution.get().pluginId)
|
||||
alias(libs.plugins.knit)
|
||||
id("kotlin-parcelize")
|
||||
alias(libs.plugins.licensee)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
// To be able to update the firebase.xml files, uncomment and build the project
|
||||
// alias(libs.plugins.gms.google.services)
|
||||
}
|
||||
|
||||
setupKover()
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.x"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = BuildTimeConfig.APPLICATION_ID
|
||||
targetSdk = Versions.TARGET_SDK
|
||||
versionCode = Versions.VERSION_CODE
|
||||
versionName = Versions.VERSION_NAME
|
||||
|
||||
// Keep abiFilter for the universalApk
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
|
||||
}
|
||||
|
||||
// Ref: https://developer.android.com/studio/build/configure-apk-splits.html#configure-abi-split
|
||||
splits {
|
||||
// Configures multiple APKs based on ABI.
|
||||
abi {
|
||||
val buildingAppBundle = gradle.startParameter.taskNames.any { it.contains("bundle") }
|
||||
|
||||
// Enables building multiple APKs per ABI. This should be disabled when building an AAB.
|
||||
isEnable = !buildingAppBundle
|
||||
|
||||
// By default all ABIs are included, so use reset() and include to specify that we only
|
||||
// want APKs for armeabi-v7a, x86, arm64-v8a and x86_64.
|
||||
// Resets the list of ABIs that Gradle should create APKs for to none.
|
||||
reset()
|
||||
|
||||
if (!buildingAppBundle) {
|
||||
// Specifies a list of ABIs that Gradle should create APKs for.
|
||||
include("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
|
||||
// Generate a universal APK that includes all ABIs, so user who installs from CI tool can use this one by default.
|
||||
isUniversalApk = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
androidResources {
|
||||
localeFilters += locales
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
getByName("debug") {
|
||||
keyAlias = "androiddebugkey"
|
||||
keyPassword = "android"
|
||||
storeFile = file("./signature/debug.keystore")
|
||||
storePassword = "android"
|
||||
}
|
||||
register("nightly") {
|
||||
keyAlias = System.getenv("ELEMENT_ANDROID_NIGHTLY_KEYID")
|
||||
?: project.property("signing.element.nightly.keyId") as? String?
|
||||
keyPassword = System.getenv("ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD")
|
||||
?: project.property("signing.element.nightly.keyPassword") as? String?
|
||||
storeFile = file("./signature/nightly.keystore")
|
||||
storePassword = System.getenv("ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD")
|
||||
?: project.property("signing.element.nightly.storePassword") as? String?
|
||||
}
|
||||
}
|
||||
|
||||
val baseAppName = BuildTimeConfig.APPLICATION_NAME
|
||||
val buildType = if (isEnterpriseBuild) "Enterprise" else "FOSS"
|
||||
logger.warnInBox("Building ${defaultConfig.applicationId} ($baseAppName) [$buildType]")
|
||||
|
||||
buildTypes {
|
||||
val oidcRedirectSchemeBase = BuildTimeConfig.METADATA_HOST_REVERSED ?: "io.element.android"
|
||||
getByName("debug") {
|
||||
resValue("string", "app_name", "$baseAppName dbg")
|
||||
resValue(
|
||||
"string",
|
||||
"login_redirect_scheme",
|
||||
"$oidcRedirectSchemeBase.debug",
|
||||
)
|
||||
applicationIdSuffix = ".debug"
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
|
||||
getByName("release") {
|
||||
resValue("string", "app_name", baseAppName)
|
||||
resValue(
|
||||
"string",
|
||||
"login_redirect_scheme",
|
||||
oidcRedirectSchemeBase,
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
|
||||
optimization {
|
||||
enable = true
|
||||
keepRules {
|
||||
files.add(File(projectDir, "proguard-rules.pro"))
|
||||
files.add(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register("nightly") {
|
||||
val release = getByName("release")
|
||||
initWith(release)
|
||||
applicationIdSuffix = ".nightly"
|
||||
versionNameSuffix = "-nightly"
|
||||
resValue("string", "app_name", "$baseAppName nightly")
|
||||
resValue(
|
||||
"string",
|
||||
"login_redirect_scheme",
|
||||
"$oidcRedirectSchemeBase.nightly",
|
||||
)
|
||||
matchingFallbacks += listOf("release")
|
||||
signingConfig = signingConfigs.getByName("nightly")
|
||||
|
||||
firebaseAppDistribution {
|
||||
artifactType = "APK"
|
||||
// We upload the universal APK to fix this error:
|
||||
// "App Distribution found more than 1 output file for this variant.
|
||||
// Please contact firebase-support@google.com for help using APK splits with App Distribution."
|
||||
artifactPath = "$rootDir/app/build/outputs/apk/gplay/nightly/app-gplay-universal-nightly.apk"
|
||||
// artifactType = "AAB"
|
||||
// artifactPath = "$rootDir/app/build/outputs/bundle/nightly/app-nightly.aab"
|
||||
releaseNotesFile = "tools/release/ReleaseNotesNightly.md"
|
||||
groups = if (isEnterpriseBuild) {
|
||||
"enterprise-testers"
|
||||
} else {
|
||||
"external-testers"
|
||||
}
|
||||
// This should not be required, but if I do not add the appId, I get this error:
|
||||
// "App Distribution halted because it had a problem uploading the APK: [404] Requested entity was not found."
|
||||
appId = if (isEnterpriseBuild) {
|
||||
"1:912726360885:android:3f7e1fe644d99d5a00427c"
|
||||
} else {
|
||||
"1:912726360885:android:e17435e0beb0303000427c"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
flavorDimensions += "store"
|
||||
productFlavors {
|
||||
create("gplay") {
|
||||
dimension = "store"
|
||||
isDefault = true
|
||||
buildConfigFieldStr("SHORT_FLAVOR_DESCRIPTION", "G")
|
||||
buildConfigFieldStr("FLAVOR_DESCRIPTION", "GooglePlay")
|
||||
}
|
||||
create("fdroid") {
|
||||
dimension = "store"
|
||||
buildConfigFieldStr("SHORT_FLAVOR_DESCRIPTION", "F")
|
||||
buildConfigFieldStr("FLAVOR_DESCRIPTION", "FDroid")
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.pickFirsts += setOf(
|
||||
"META-INF/versions/9/OSGI-INF/MANIFEST.MF",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
// map for the version codes last digit
|
||||
// x86 must have greater values than arm
|
||||
// 64 bits have greater value than 32 bits
|
||||
val abiVersionCodes = mapOf(
|
||||
"armeabi-v7a" to 1,
|
||||
"arm64-v8a" to 2,
|
||||
"x86" to 3,
|
||||
"x86_64" to 4,
|
||||
)
|
||||
|
||||
onVariants { variant ->
|
||||
// Assigns a different version code for each output APK
|
||||
// other than the universal APK.
|
||||
variant.outputs.forEach { output ->
|
||||
val name = output.filters.find { it.filterType == ABI }?.identifier
|
||||
|
||||
// Stores the value of abiCodes that is associated with the ABI for this variant.
|
||||
val abiCode = abiVersionCodes[name] ?: 0
|
||||
// Assigns the new version code to output.versionCode, which changes the version code
|
||||
// for only the output APK, not for the variant itself.
|
||||
output.versionCode.set((output.versionCode.orNull ?: 0) * 10 + abiCode)
|
||||
}
|
||||
}
|
||||
|
||||
val reportingExtension: ReportingExtension = project.extensions.getByType(ReportingExtension::class.java)
|
||||
configureLicensesTasks(reportingExtension)
|
||||
}
|
||||
|
||||
// Knit
|
||||
apply {
|
||||
plugin("kotlinx-knit")
|
||||
}
|
||||
|
||||
knit {
|
||||
files = fileTree(project.rootDir) {
|
||||
include(
|
||||
"**/*.md",
|
||||
"**/*.kt",
|
||||
"*/*.kts",
|
||||
)
|
||||
exclude(
|
||||
"**/build/**",
|
||||
"*/.gradle/**",
|
||||
"**/CHANGES.md",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
|
||||
dependencies {
|
||||
allLibrariesImpl()
|
||||
allServicesImpl()
|
||||
if (isEnterpriseBuild) {
|
||||
allEnterpriseImpl(project)
|
||||
implementation(projects.appicon.enterprise)
|
||||
} else {
|
||||
implementation(projects.features.enterprise.implFoss)
|
||||
implementation(projects.appicon.element)
|
||||
}
|
||||
allFeaturesImpl(project)
|
||||
implementation(projects.features.migration.api)
|
||||
implementation(projects.appnav)
|
||||
implementation(projects.appconfig)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.services.analytics.compose)
|
||||
|
||||
if (ModulesConfig.pushProvidersConfig.includeFirebase) {
|
||||
"gplayImplementation"(projects.libraries.pushproviders.firebase)
|
||||
}
|
||||
if (ModulesConfig.pushProvidersConfig.includeUnifiedPush) {
|
||||
implementation(projects.libraries.pushproviders.unifiedpush)
|
||||
}
|
||||
|
||||
implementation(libs.appyx.core)
|
||||
implementation(libs.androidx.splash)
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.corektx)
|
||||
implementation(libs.androidx.lifecycle.runtime)
|
||||
implementation(libs.androidx.lifecycle.process)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.startup)
|
||||
implementation(libs.androidx.preference)
|
||||
implementation(libs.coil)
|
||||
|
||||
implementation(platform(libs.network.okhttp.bom))
|
||||
implementation(libs.network.okhttp.logging)
|
||||
implementation(libs.serialization.json)
|
||||
|
||||
implementation(libs.matrix.emojibase.bindings)
|
||||
|
||||
testCommonDependencies(libs)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
|
||||
koverDependencies()
|
||||
}
|
||||
|
||||
tasks.withType<GenerateBuildConfig>().configureEach {
|
||||
outputs.upToDateWhen { false }
|
||||
val gitRevision = providers.of(GitRevisionValueSource::class.java) {}.get()
|
||||
val gitBranchName = providers.of(GitBranchNameValueSource::class.java) {}.get()
|
||||
android.defaultConfig.buildConfigFieldStr("GIT_REVISION", gitRevision)
|
||||
android.defaultConfig.buildConfigFieldStr("GIT_BRANCH_NAME", gitBranchName)
|
||||
}
|
||||
|
||||
licensee {
|
||||
allow("Apache-2.0")
|
||||
allow("MIT")
|
||||
allow("BSD-2-Clause")
|
||||
allow("BSD-3-Clause")
|
||||
allow("EPL-1.0")
|
||||
allowUrl("https://opensource.org/licenses/MIT")
|
||||
allowUrl("https://developer.android.com/studio/terms.html")
|
||||
allowUrl("https://www.zetetic.net/sqlcipher/license/")
|
||||
allowUrl("https://jsoup.org/license")
|
||||
allowUrl("https://asm.ow2.io/license.html")
|
||||
allowUrl("https://www.gnu.org/licenses/agpl-3.0.txt")
|
||||
allowUrl("https://github.com/mhssn95/compose-color-picker/blob/main/LICENSE")
|
||||
ignoreDependencies("com.github.matrix-org", "matrix-analytics-events")
|
||||
// Ignore dependency that are not third-party licenses to us.
|
||||
ignoreDependencies(groupId = "io.element.android")
|
||||
}
|
||||
|
||||
fun Project.configureLicensesTasks(reportingExtension: ReportingExtension) {
|
||||
androidComponents {
|
||||
onVariants { variant ->
|
||||
val capitalizedVariantName = variant.name.replaceFirstChar {
|
||||
if (it.isLowerCase()) {
|
||||
it.titlecase(Locale.getDefault())
|
||||
} else {
|
||||
it.toString()
|
||||
}
|
||||
}
|
||||
val artifactsFile = reportingExtension.baseDirectory.file("licensee/android$capitalizedVariantName/artifacts.json")
|
||||
|
||||
val copyArtifactsTask =
|
||||
project.tasks.register<AssetCopyTask>("copy${capitalizedVariantName}LicenseeReportToAssets") {
|
||||
inputFile.set(artifactsFile)
|
||||
targetFileName.set("licensee-artifacts.json")
|
||||
}
|
||||
variant.sources.assets?.addGeneratedSourceDirectory(
|
||||
copyArtifactsTask,
|
||||
AssetCopyTask::outputDirectory,
|
||||
)
|
||||
copyArtifactsTask.dependsOn("licenseeAndroid$capitalizedVariantName")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
dependencySubstitution {
|
||||
val tink = libs.google.tink.get()
|
||||
substitute(module("com.google.crypto.tink:tink")).using(module("${tink.group}:${tink.name}:${tink.version}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.kts.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# JNA
|
||||
-dontwarn java.awt.*
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-keep class * implements com.sun.jna.** { *; }
|
||||
|
||||
# TagSoup, coming from the RTE library
|
||||
-keep class org.ccil.cowan.tagsoup.** { *; }
|
||||
|
||||
# kotlinx.serialization
|
||||
|
||||
# Kotlin serialization looks up the generated serializer classes through a function on companion
|
||||
# objects. The companions are looked up reflectively so we need to explicitly keep these functions.
|
||||
-keepclasseswithmembers class **.*$Companion {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
# If a companion has the serializer function, keep the companion field on the original type so that
|
||||
# the reflective lookup succeeds.
|
||||
-if class **.*$Companion {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
-keepclassmembers class <1>.<2> {
|
||||
<1>.<2>$Companion Companion;
|
||||
}
|
||||
|
||||
# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.
|
||||
# Taken from https://raw.githubusercontent.com/square/okhttp/master/okhttp/src/jvmMain/resources/META-INF/proguard/okhttp3.pro
|
||||
-dontwarn okhttp3.internal.platform.**
|
||||
-dontwarn org.conscrypt.**
|
||||
-dontwarn org.bouncycastle.**
|
||||
-dontwarn org.openjsse.**
|
||||
|
||||
# Needed for Posthog
|
||||
-keepclassmembers class android.view.JavaViewSpy {
|
||||
static int windowAttachCount(android.view.View);
|
||||
}
|
||||
|
||||
|
||||
# Keep LogSessionId class and related classes (https://github.com/androidx/media/issues/2535)
|
||||
-keep class android.media.metrics.LogSessionId { *; }
|
||||
-keep class android.media.metrics.** { *; }
|
||||
|
||||
# Keep Media3 classes that use reflection (https://github.com/androidx/media/issues/2535)
|
||||
-keep class androidx.media3.** { *; }
|
||||
-dontwarn android.media.metrics.**
|
||||
|
||||
# New rules after AGP 8.13.1 upgrade
|
||||
-dontwarn androidx.window.extensions.WindowExtensions
|
||||
-dontwarn androidx.window.extensions.WindowExtensionsProvider
|
||||
-dontwarn androidx.window.extensions.area.ExtensionWindowAreaPresentation
|
||||
-dontwarn androidx.window.extensions.layout.DisplayFeature
|
||||
-dontwarn androidx.window.extensions.layout.FoldingFeature
|
||||
-dontwarn androidx.window.extensions.layout.WindowLayoutComponent
|
||||
-dontwarn androidx.window.extensions.layout.WindowLayoutInfo
|
||||
-dontwarn androidx.window.sidecar.SidecarDeviceState
|
||||
-dontwarn androidx.window.sidecar.SidecarDisplayFeature
|
||||
-dontwarn androidx.window.sidecar.SidecarInterface$SidecarCallback
|
||||
-dontwarn androidx.window.sidecar.SidecarInterface
|
||||
-dontwarn androidx.window.sidecar.SidecarProvider
|
||||
-dontwarn androidx.window.sidecar.SidecarWindowLayoutInfo
|
||||
|
||||
# Also needed after AGP 8.13.1 upgrade, it seems like proguard is now more aggressive on removing unused code
|
||||
-keep class org.matrix.rustcomponents.sdk.** { *;}
|
||||
-keep class uniffi.** { *;}
|
||||
-keep class io.element.android.x.di.** { *; }
|
||||
-keepnames class io.element.android.x.**
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,188 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Element Creations Ltd.
|
||||
~ Copyright 2022 New Vector Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- To be able to install APK from the application -->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
||||
<application
|
||||
android:name=".ElementXApplication"
|
||||
android:allowBackup="false"
|
||||
android:appCategory="social"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.ElementX"
|
||||
tools:targetApi="33">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<meta-data
|
||||
android:name='androidx.lifecycle.ProcessLifecycleInitializer'
|
||||
android:value='androidx.startup' />
|
||||
|
||||
<!-- Remove to use Application workManagerConfiguration -->
|
||||
<meta-data
|
||||
android:name="androidx.work.WorkManagerInitializer"
|
||||
android:value="androidx.startup"
|
||||
tools:node="remove" />
|
||||
</provider>
|
||||
|
||||
<!--
|
||||
Using launchMode singleTask to avoid multiple instances of the Activity
|
||||
when the app is already open. This is important for incoming share (see
|
||||
https://github.com/element-hq/element-x-android/issues/4074) and for opening
|
||||
the application from a mobile.element.io link.
|
||||
-->
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/Theme.ElementX.Splash"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<!-- Handle deep-link for notification ./tools/adb/deeplink.sh -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data
|
||||
android:host="open"
|
||||
android:scheme="elementx" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Oidc redirection
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="@string/login_redirect_scheme" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Element web links
|
||||
-->
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<!-- Note: we can't use "*.element.io" here because it'll intercept the "mas.element.io" domain too. -->
|
||||
<!-- Matching asset file: https://app.element.io/.well-known/assetlinks.json -->
|
||||
<data android:host="app.element.io" />
|
||||
<!-- Matching asset file: https://develop.element.io/.well-known/assetlinks.json -->
|
||||
<data android:host="develop.element.io" />
|
||||
<!-- Matching asset file: https://staging.element.io/.well-known/assetlinks.json -->
|
||||
<data android:host="staging.element.io" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Element mobile links
|
||||
Example: https://mobile.element.io/element/?account_provider=example.org&login_hint=mxid:@alice:example.org
|
||||
-->
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<!-- Matching asset file: https://mobile.element.io/.well-known/assetlinks.json -->
|
||||
<data android:host="mobile.element.io" />
|
||||
<data android:path="/element/" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
matrix.to links
|
||||
Note: On Android 12 and higher clicking a web link (that is not an Android App Link) always shows content in a web browser
|
||||
https://developer.android.com/training/app-links#web-links
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="matrix.to" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
matrix: links
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="matrix" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
links from matrix.to website
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="element" />
|
||||
<data android:host="user" />
|
||||
<data android:host="room" />
|
||||
</intent-filter>
|
||||
<!-- Incoming share simple -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<data android:mimeType="*/*" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.OPENABLE" />
|
||||
</intent-filter>
|
||||
<!-- Incoming share multiple -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<data android:mimeType="*/*" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.OPENABLE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_providers" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x
|
||||
|
||||
import android.app.Application
|
||||
import androidx.startup.AppInitializer
|
||||
import androidx.work.Configuration
|
||||
import dev.zacsweers.metro.createGraphFactory
|
||||
import io.element.android.libraries.di.DependencyInjectionGraphOwner
|
||||
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
|
||||
import io.element.android.x.di.AppGraph
|
||||
import io.element.android.x.info.logApplicationInfo
|
||||
import io.element.android.x.initializer.CacheCleanerInitializer
|
||||
import io.element.android.x.initializer.CrashInitializer
|
||||
import io.element.android.x.initializer.PlatformInitializer
|
||||
|
||||
class ElementXApplication : Application(), DependencyInjectionGraphOwner, Configuration.Provider {
|
||||
override val graph: AppGraph = createGraphFactory<AppGraph.Factory>().create(this)
|
||||
|
||||
override val workManagerConfiguration: Configuration = Configuration.Builder()
|
||||
.setWorkerFactory(MetroWorkerFactory(graph.workerProviders))
|
||||
.build()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
AppInitializer.getInstance(this).apply {
|
||||
initializeComponent(CrashInitializer::class.java)
|
||||
initializeComponent(PlatformInitializer::class.java)
|
||||
initializeComponent(CacheCleanerInitializer::class.java)
|
||||
}
|
||||
|
||||
logApplicationInfo(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.bumble.appyx.core.integration.NodeHost
|
||||
import com.bumble.appyx.core.integrationpoint.NodeActivity
|
||||
import com.bumble.appyx.core.plugin.NodeReadyObserver
|
||||
import io.element.android.compound.colors.SemanticColorsLightDark
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||
import io.element.android.features.lockscreen.api.LockScreenLockState
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.features.lockscreen.api.handleSecureFlag
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.designsystem.theme.ElementThemeApp
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.LocalSnackbarDispatcher
|
||||
import io.element.android.services.analytics.compose.LocalAnalyticsService
|
||||
import io.element.android.x.di.AppBindings
|
||||
import io.element.android.x.intent.SafeUriHandler
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
private val loggerTag = LoggerTag("MainActivity")
|
||||
|
||||
class MainActivity : NodeActivity() {
|
||||
private lateinit var mainNode: MainNode
|
||||
private lateinit var appBindings: AppBindings
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Timber.tag(loggerTag.value).w("onCreate, with savedInstanceState: ${savedInstanceState != null}")
|
||||
installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
appBindings = bindings()
|
||||
setupLockManagement(appBindings.lockScreenService(), appBindings.lockScreenEntryPoint())
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
MainContent(appBindings)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainContent(appBindings: AppBindings) {
|
||||
val migrationState = appBindings.migrationEntryPoint().present()
|
||||
val colors by remember {
|
||||
appBindings.enterpriseService().semanticColorsFlow(sessionId = null)
|
||||
}.collectAsState(SemanticColorsLightDark.default)
|
||||
ElementThemeApp(
|
||||
appPreferencesStore = appBindings.preferencesStore(),
|
||||
compoundLight = colors.light,
|
||||
compoundDark = colors.dark,
|
||||
buildMeta = appBindings.buildMeta()
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalSnackbarDispatcher provides appBindings.snackbarDispatcher(),
|
||||
LocalUriHandler provides SafeUriHandler(this),
|
||||
LocalAnalyticsService provides appBindings.analyticsService(),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(ElementTheme.colors.bgCanvasDefault),
|
||||
) {
|
||||
if (migrationState.migrationAction.isSuccess()) {
|
||||
MainNodeHost()
|
||||
} else {
|
||||
appBindings.migrationEntryPoint().Render(
|
||||
state = migrationState,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainNodeHost() {
|
||||
NodeHost(integrationPoint = appyxV1IntegrationPoint) {
|
||||
MainNode(
|
||||
it,
|
||||
plugins = listOf(
|
||||
object : NodeReadyObserver<MainNode> {
|
||||
override fun init(node: MainNode) {
|
||||
Timber.tag(loggerTag.value).w("onMainNodeInit")
|
||||
mainNode = node
|
||||
mainNode.handleIntent(intent)
|
||||
}
|
||||
}
|
||||
),
|
||||
context = applicationContext
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupLockManagement(
|
||||
lockScreenService: LockScreenService,
|
||||
lockScreenEntryPoint: LockScreenEntryPoint
|
||||
) {
|
||||
lockScreenService.handleSecureFlag(this)
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
lockScreenService.lockState.collect { state ->
|
||||
if (state == LockScreenLockState.Locked) {
|
||||
startActivity(lockScreenEntryPoint.pinUnlockIntent(this@MainActivity))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when:
|
||||
* - the launcher icon is clicked (if the app is already running);
|
||||
* - a notification is clicked.
|
||||
* - a deep link have been clicked
|
||||
* - the app is going to background (<- this is strange)
|
||||
*/
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
Timber.tag(loggerTag.value).w("onNewIntent")
|
||||
// If the mainNode is not init yet, keep the intent for later.
|
||||
// It can happen when the activity is killed by the system. The methods are called in this order :
|
||||
// onCreate(savedInstanceState=true) -> onNewIntent -> onResume -> onMainNodeInit
|
||||
if (::mainNode.isInitialized) {
|
||||
mainNode.handleIntent(intent)
|
||||
} else {
|
||||
setIntent(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
Timber.tag(loggerTag.value).w("onPause")
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Timber.tag(loggerTag.value).w("onResume")
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Timber.tag(loggerTag.value).w("onDestroy")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumble.appyx.core.composable.Children
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.node.ParentNode
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import io.element.android.appnav.RootFlowNode
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.DependencyInjectionGraphOwner
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class MainNode(
|
||||
buildContext: BuildContext,
|
||||
plugins: List<Plugin>,
|
||||
@ApplicationContext context: Context,
|
||||
) : ParentNode<MainNode.RootNavTarget>(
|
||||
navModel = PermanentNavModel(
|
||||
navTargets = setOf(RootNavTarget),
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
),
|
||||
DependencyInjectionGraphOwner {
|
||||
override val graph = (context as DependencyInjectionGraphOwner).graph
|
||||
|
||||
override fun resolve(navTarget: RootNavTarget, buildContext: BuildContext): Node {
|
||||
return createNode<RootFlowNode>(buildContext = buildContext)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
Children(navModel = navModel)
|
||||
}
|
||||
|
||||
fun handleIntent(intent: Intent) {
|
||||
lifecycleScope.launch {
|
||||
waitForChildAttached<RootFlowNode>().handleIntent(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
object RootNavTarget : Parcelable
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.di
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import io.element.android.features.api.MigrationEntryPoint
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.features.rageshake.api.reporter.BugReporter
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.platform.InitPlatformService
|
||||
import io.element.android.libraries.matrix.api.tracing.TracingService
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
interface AppBindings {
|
||||
fun snackbarDispatcher(): SnackbarDispatcher
|
||||
|
||||
fun tracingService(): TracingService
|
||||
|
||||
fun platformService(): InitPlatformService
|
||||
|
||||
fun bugReporter(): BugReporter
|
||||
|
||||
fun lockScreenService(): LockScreenService
|
||||
|
||||
fun preferencesStore(): AppPreferencesStore
|
||||
|
||||
fun migrationEntryPoint(): MigrationEntryPoint
|
||||
|
||||
fun lockScreenEntryPoint(): LockScreenEntryPoint
|
||||
|
||||
fun analyticsService(): AnalyticsService
|
||||
|
||||
fun enterpriseService(): EnterpriseService
|
||||
|
||||
fun featureFlagService(): FeatureFlagService
|
||||
|
||||
fun buildMeta(): BuildMeta
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.ListenableWorker
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.DependencyGraph
|
||||
import dev.zacsweers.metro.Multibinds
|
||||
import dev.zacsweers.metro.Provides
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@DependencyGraph(AppScope::class)
|
||||
interface AppGraph : NodeFactoriesBindings {
|
||||
val sessionGraphFactory: SessionGraph.Factory
|
||||
|
||||
@Multibinds
|
||||
val workerProviders:
|
||||
Map<KClass<out ListenableWorker>, MetroWorkerFactory.WorkerInstanceFactory<*>>
|
||||
|
||||
@DependencyGraph.Factory
|
||||
interface Factory {
|
||||
fun create(
|
||||
@ApplicationContext @Provides
|
||||
context: Context
|
||||
): AppGraph
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.di
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import androidx.preference.PreferenceManager
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.appconfig.ApplicationConfig
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.libraries.androidutils.system.getVersionCodeFromManifest
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.di.BaseDirectory
|
||||
import io.element.android.libraries.di.CacheDirectory
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.recentemojis.api.EmojibaseProvider
|
||||
import io.element.android.libraries.recentemojis.impl.DefaultEmojibaseProvider
|
||||
import io.element.android.x.BuildConfig
|
||||
import io.element.android.x.R
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.plus
|
||||
import java.io.File
|
||||
|
||||
@BindingContainer
|
||||
@ContributesTo(AppScope::class)
|
||||
object AppModule {
|
||||
@Provides
|
||||
@BaseDirectory
|
||||
fun providesBaseDirectory(@ApplicationContext context: Context): File {
|
||||
return File(context.filesDir, "sessions")
|
||||
}
|
||||
|
||||
@Provides
|
||||
@CacheDirectory
|
||||
fun providesCacheDirectory(@ApplicationContext context: Context): File {
|
||||
return context.cacheDir
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun providesResources(@ApplicationContext context: Context): Resources {
|
||||
return context.resources
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AppCoroutineScope
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesAppCoroutineScope(): CoroutineScope {
|
||||
return MainScope() + CoroutineName("ElementX Scope")
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesBuildType(): BuildType {
|
||||
return BuildType.valueOf(BuildConfig.BUILD_TYPE.uppercase())
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesBuildMeta(
|
||||
@ApplicationContext context: Context,
|
||||
buildType: BuildType,
|
||||
enterpriseService: EnterpriseService,
|
||||
): BuildMeta {
|
||||
val applicationName = ApplicationConfig.APPLICATION_NAME.takeIf { it.isNotEmpty() } ?: context.getString(R.string.app_name)
|
||||
return BuildMeta(
|
||||
isDebuggable = BuildConfig.DEBUG,
|
||||
buildType = buildType,
|
||||
applicationName = applicationName,
|
||||
productionApplicationName = if (enterpriseService.isEnterpriseBuild) applicationName else ApplicationConfig.PRODUCTION_APPLICATION_NAME,
|
||||
desktopApplicationName = if (enterpriseService.isEnterpriseBuild) applicationName else ApplicationConfig.DESKTOP_APPLICATION_NAME,
|
||||
applicationId = BuildConfig.APPLICATION_ID,
|
||||
isEnterpriseBuild = enterpriseService.isEnterpriseBuild,
|
||||
// TODO EAx Config.LOW_PRIVACY_LOG_ENABLE,
|
||||
lowPrivacyLoggingEnabled = false,
|
||||
versionName = BuildConfig.VERSION_NAME,
|
||||
versionCode = context.getVersionCodeFromManifest(),
|
||||
gitRevision = BuildConfig.GIT_REVISION,
|
||||
gitBranchName = BuildConfig.GIT_BRANCH_NAME,
|
||||
flavorDescription = BuildConfig.FLAVOR_DESCRIPTION,
|
||||
flavorShortDescription = BuildConfig.SHORT_FLAVOR_DESCRIPTION,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesCoroutineDispatchers(): CoroutineDispatchers {
|
||||
return CoroutineDispatchers.Default
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun provideSnackbarDispatcher(): SnackbarDispatcher {
|
||||
return SnackbarDispatcher()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesEmojibaseProvider(@ApplicationContext context: Context): EmojibaseProvider {
|
||||
return DefaultEmojibaseProvider(context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.di
|
||||
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.appnav.di.RoomGraphFactory
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultRoomGraphFactory(
|
||||
private val sessionGraph: SessionGraph,
|
||||
) : RoomGraphFactory {
|
||||
override fun create(room: JoinedRoom): Any {
|
||||
return sessionGraph.roomGraphFactory
|
||||
.create(room, room)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.di
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.appnav.di.SessionGraphFactory
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultSessionGraphFactory(
|
||||
private val appGraph: AppGraph
|
||||
) : SessionGraphFactory {
|
||||
override fun create(client: MatrixClient): Any {
|
||||
return appGraph.sessionGraphFactory.create(client)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.di
|
||||
|
||||
import dev.zacsweers.metro.GraphExtension
|
||||
import dev.zacsweers.metro.Provides
|
||||
import io.element.android.appnav.di.TimelineBindings
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
|
||||
@GraphExtension(RoomScope::class)
|
||||
interface RoomGraph : NodeFactoriesBindings, TimelineBindings {
|
||||
@GraphExtension.Factory
|
||||
interface Factory {
|
||||
fun create(
|
||||
@Provides joinedRoom: JoinedRoom,
|
||||
@Provides baseRoom: BaseRoom
|
||||
): RoomGraph
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.di
|
||||
|
||||
import dev.zacsweers.metro.GraphExtension
|
||||
import dev.zacsweers.metro.Provides
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
|
||||
@GraphExtension(SessionScope::class)
|
||||
interface SessionGraph : NodeFactoriesBindings {
|
||||
val roomGraphFactory: RoomGraph.Factory
|
||||
|
||||
@GraphExtension.Factory
|
||||
interface Factory {
|
||||
fun create(@Provides matrixClient: MatrixClient): SessionGraph
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.info
|
||||
|
||||
import android.content.Context
|
||||
import io.element.android.libraries.androidutils.system.getVersionCodeFromManifest
|
||||
import io.element.android.x.BuildConfig
|
||||
import timber.log.Timber
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
fun logApplicationInfo(context: Context) {
|
||||
val appVersion = buildString {
|
||||
append(BuildConfig.VERSION_NAME)
|
||||
append(" (")
|
||||
append(context.getVersionCodeFromManifest())
|
||||
append(") - ")
|
||||
append(BuildConfig.BUILD_TYPE)
|
||||
append(" / ")
|
||||
append(BuildConfig.FLAVOR)
|
||||
}
|
||||
// TODO Get SDK version somehow
|
||||
val sdkVersion = "SDK VERSION (TODO)"
|
||||
val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date())
|
||||
|
||||
Timber.d("----------------------------------------------------------------")
|
||||
Timber.d("----------------------------------------------------------------")
|
||||
Timber.d(" Application version: $appVersion")
|
||||
Timber.d(" Git SHA: ${BuildConfig.GIT_REVISION}")
|
||||
Timber.d(" SDK version: $sdkVersion")
|
||||
Timber.d(" Local time: $date")
|
||||
Timber.d("----------------------------------------------------------------")
|
||||
Timber.d("----------------------------------------------------------------\n\n\n\n")
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.initializer
|
||||
|
||||
import android.content.Context
|
||||
import androidx.startup.Initializer
|
||||
import io.element.android.features.cachecleaner.impl.CacheCleanerBindings
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
|
||||
class CacheCleanerInitializer : Initializer<Unit> {
|
||||
override fun create(context: Context) {
|
||||
context.bindings<CacheCleanerBindings>().cacheCleaner().clearCache()
|
||||
}
|
||||
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.initializer
|
||||
|
||||
import android.content.Context
|
||||
import androidx.startup.Initializer
|
||||
import io.element.android.features.rageshake.impl.crash.VectorUncaughtExceptionHandler
|
||||
import io.element.android.features.rageshake.impl.di.RageshakeBindings
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
|
||||
class CrashInitializer : Initializer<Unit> {
|
||||
override fun create(context: Context) {
|
||||
VectorUncaughtExceptionHandler(
|
||||
context.bindings<RageshakeBindings>().preferencesCrashDataStore(),
|
||||
).activate()
|
||||
}
|
||||
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2022-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.initializer
|
||||
|
||||
import android.content.Context
|
||||
import android.system.Os
|
||||
import androidx.startup.Initializer
|
||||
import io.element.android.features.rageshake.api.logs.createWriteToFilesConfiguration
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.tracing.TracingConfiguration
|
||||
import io.element.android.x.di.AppBindings
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
|
||||
private const val ELEMENT_X_TARGET = "elementx"
|
||||
|
||||
class PlatformInitializer : Initializer<Unit> {
|
||||
override fun create(context: Context) {
|
||||
val appBindings = context.bindings<AppBindings>()
|
||||
val tracingService = appBindings.tracingService()
|
||||
val platformService = appBindings.platformService()
|
||||
val bugReporter = appBindings.bugReporter()
|
||||
Timber.plant(tracingService.createTimberTree(ELEMENT_X_TARGET))
|
||||
val preferencesStore = appBindings.preferencesStore()
|
||||
val featureFlagService = appBindings.featureFlagService()
|
||||
val logLevel = runBlocking { preferencesStore.getTracingLogLevelFlow().first() }
|
||||
val tracingConfiguration = TracingConfiguration(
|
||||
writesToLogcat = runBlocking { featureFlagService.isFeatureEnabled(FeatureFlags.PrintLogsToLogcat) },
|
||||
writesToFilesConfiguration = bugReporter.createWriteToFilesConfiguration(),
|
||||
logLevel = logLevel,
|
||||
extraTargets = listOf(ELEMENT_X_TARGET),
|
||||
traceLogPacks = runBlocking { preferencesStore.getTracingLogPacksFlow().first() },
|
||||
)
|
||||
bugReporter.setCurrentTracingLogLevel(logLevel.name)
|
||||
platformService.init(tracingConfiguration)
|
||||
// Also set env variable for rust back trace
|
||||
Os.setenv("RUST_BACKTRACE", "1", true)
|
||||
}
|
||||
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = mutableListOf()
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.intent
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.core.net.toUri
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.deeplink.api.DeepLinkCreator
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.push.impl.intent.IntentProvider
|
||||
import io.element.android.x.MainActivity
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultIntentProvider(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val deepLinkCreator: DeepLinkCreator,
|
||||
) : IntentProvider {
|
||||
override fun getViewRoomIntent(
|
||||
sessionId: SessionId,
|
||||
roomId: RoomId?,
|
||||
threadId: ThreadId?,
|
||||
eventId: EventId?,
|
||||
extras: Bundle?,
|
||||
): Intent {
|
||||
return Intent(context, MainActivity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
data = deepLinkCreator.create(sessionId, roomId, threadId, eventId).toUri()
|
||||
extras?.let(::putExtras)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.intent
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.ui.platform.UriHandler
|
||||
import io.element.android.libraries.androidutils.system.openUrlInExternalApp
|
||||
|
||||
class SafeUriHandler(private val activity: Activity) : UriHandler {
|
||||
override fun openUri(uri: String) {
|
||||
activity.openUrlInExternalApp(uri)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.oidc
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.matrix.api.auth.OidcRedirectUrlProvider
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
import io.element.android.x.R
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultOidcRedirectUrlProvider(
|
||||
private val stringProvider: StringProvider,
|
||||
) : OidcRedirectUrlProvider {
|
||||
override fun provide() = buildString {
|
||||
append(stringProvider.getString(R.string.login_redirect_scheme))
|
||||
append(":/")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Element Creations Ltd.
|
||||
~ Copyright 2023 New Vector Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<solid android:color="#00000000" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) 2025 Element Creations Ltd.
|
||||
# Copyright 2024 New Vector Ltd.
|
||||
#
|
||||
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
# Please see LICENSE files in the repository root for full details.
|
||||
|
||||
unqualifiedResLocale=en
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Element Creations Ltd.
|
||||
~ Copyright 2023 New Vector Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<style name="Theme.ElementX.Splash" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/splashscreen_bg_dark</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/transparent</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.ElementX</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.ElementX" parent="Theme.Material3.Dark.NoActionBar" />
|
||||
</resources>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Element Creations Ltd.
|
||||
~ Copyright 2022 New Vector Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<resources>
|
||||
<!-- Must be equal to DarkColorTokens.colorThemeBg -->
|
||||
<color name="splashscreen_bg_dark">#FF101317</color>
|
||||
<!-- Must be equal to LightColorTokens.colorThemeBg -->
|
||||
<color name="splashscreen_bg_light">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Element Creations Ltd.
|
||||
~ Copyright 2022 New Vector Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="Theme.ElementX.Splash" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/splashscreen_bg_light</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/transparent</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.ElementX</item>
|
||||
</style>
|
||||
<style name="Theme.ElementX" parent="Theme.Material3.Light.NoActionBar">
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Element Creations Ltd.
|
||||
~ Copyright 2025 New Vector Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<automotiveApp>
|
||||
<uses name="notification" />
|
||||
</automotiveApp>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Element Creations Ltd.
|
||||
~ Copyright 2022 New Vector Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<!--
|
||||
All backup is disabled since it would clash with encryption.
|
||||
-->
|
||||
<full-backup-content>
|
||||
<exclude domain="root" path="." />
|
||||
<exclude domain="file" path="." />
|
||||
<exclude domain="database" path="." />
|
||||
<exclude domain="sharedpref" path="." />
|
||||
<exclude domain="external" path="."/>
|
||||
</full-backup-content>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Element Creations Ltd.
|
||||
~ Copyright 2023 New Vector Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<!--
|
||||
All backup is disabled since it would clash with encryption.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<exclude domain="root" path="."/>
|
||||
<exclude domain="file" path="."/>
|
||||
<exclude domain="database" path="."/>
|
||||
<exclude domain="sharedpref" path="."/>
|
||||
<exclude domain="external" path="."/>
|
||||
</cloud-backup>
|
||||
|
||||
<device-transfer>
|
||||
<exclude domain="root" path="."/>
|
||||
<exclude domain="file" path="."/>
|
||||
<exclude domain="database" path="."/>
|
||||
<exclude domain="sharedpref" path="."/>
|
||||
<exclude domain="external" path="."/>
|
||||
</device-transfer>
|
||||
</data-extraction-rules>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Element Creations Ltd.
|
||||
~ Copyright 2023 New Vector Ltd.
|
||||
~
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
~ Please see LICENSE files in the repository root for full details.
|
||||
-->
|
||||
<paths>
|
||||
<cache-path name="cache" path="." />
|
||||
</paths>
|
||||
@@ -0,0 +1,39 @@
|
||||
<!-- File generated by importSupportedLocalesFromLocalazy.py, do not edit -->
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="be"/>
|
||||
<locale android:name="bg"/>
|
||||
<locale android:name="cs"/>
|
||||
<locale android:name="cy"/>
|
||||
<locale android:name="da"/>
|
||||
<locale android:name="de"/>
|
||||
<locale android:name="el"/>
|
||||
<locale android:name="en"/>
|
||||
<locale android:name="en_US"/>
|
||||
<locale android:name="es"/>
|
||||
<locale android:name="et"/>
|
||||
<locale android:name="eu"/>
|
||||
<locale android:name="fa"/>
|
||||
<locale android:name="fi"/>
|
||||
<locale android:name="fr"/>
|
||||
<locale android:name="hu"/>
|
||||
<locale android:name="in"/>
|
||||
<locale android:name="it"/>
|
||||
<locale android:name="ka"/>
|
||||
<locale android:name="ko"/>
|
||||
<locale android:name="lt"/>
|
||||
<locale android:name="nb"/>
|
||||
<locale android:name="nl"/>
|
||||
<locale android:name="pl"/>
|
||||
<locale android:name="pt"/>
|
||||
<locale android:name="pt_BR"/>
|
||||
<locale android:name="ro"/>
|
||||
<locale android:name="ru"/>
|
||||
<locale android:name="sk"/>
|
||||
<locale android:name="sv"/>
|
||||
<locale android:name="tr"/>
|
||||
<locale android:name="uk"/>
|
||||
<locale android:name="ur"/>
|
||||
<locale android:name="uz"/>
|
||||
<locale android:name="zh-CN"/>
|
||||
<locale android:name="zh-TW"/>
|
||||
</locale-config>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Ref: https://developer.android.com/training/articles/security-config.html -->
|
||||
<!-- By default, do not allow clearText traffic -->
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
<certificates
|
||||
src="user"
|
||||
tools:ignore="AcceptsUserCertificates" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<!-- Allow clearText traffic on some specified host -->
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<!-- Localhost -->
|
||||
<domain includeSubdomains="true">localhost</domain>
|
||||
<domain includeSubdomains="true">127.0.0.1</domain>
|
||||
<!-- Localhost for Android emulator -->
|
||||
<domain includeSubdomains="true">10.0.2.2</domain>
|
||||
<!-- Onion services -->
|
||||
<domain includeSubdomains="true">onion</domain>
|
||||
|
||||
<!-- Domains that are used for LANs -->
|
||||
<!-- These are IANA recognized special use domain names, see https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml -->
|
||||
<domain includeSubdomains="true">home.arpa</domain>
|
||||
<domain includeSubdomains="true">local</domain> <!-- Note this has been reserved for use with mDNS -->
|
||||
<domain includeSubdomains="true">test</domain>
|
||||
<!-- These are observed in the wild either by convention or RFCs that have not been accepted, and are not currently TLDs -->
|
||||
<domain includeSubdomains="true">home</domain>
|
||||
<domain includeSubdomains="true">lan</domain>
|
||||
<domain includeSubdomains="true">localdomain</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2023-2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:Suppress("SameParameterValue")
|
||||
|
||||
package io.element.android.x.intent
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.deeplink.api.DeepLinkCreator
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.x.MainActivity
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DefaultIntentProviderTest {
|
||||
@Test
|
||||
fun `test getViewRoomIntent with data`() {
|
||||
val deepLinkCreator = lambdaRecorder<SessionId, RoomId?, ThreadId?, EventId?, String> { _, _, _, _ -> "deepLinkCreatorResult" }
|
||||
val sut = createDefaultIntentProvider(
|
||||
deepLinkCreator = { sessionId, roomId, threadId, eventId -> deepLinkCreator.invoke(sessionId, roomId, threadId, eventId) },
|
||||
)
|
||||
val result = sut.getViewRoomIntent(
|
||||
sessionId = A_SESSION_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
threadId = A_THREAD_ID,
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
result.commonAssertions()
|
||||
assertThat(result.data.toString()).isEqualTo("deepLinkCreatorResult")
|
||||
deepLinkCreator.assertions().isCalledOnce().with(
|
||||
value(A_SESSION_ID),
|
||||
value(A_ROOM_ID),
|
||||
value(A_THREAD_ID),
|
||||
value(AN_EVENT_ID),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDefaultIntentProvider(
|
||||
deepLinkCreator: DeepLinkCreator = DeepLinkCreator { _, _, _, _ -> "" },
|
||||
): DefaultIntentProvider {
|
||||
return DefaultIntentProvider(
|
||||
context = RuntimeEnvironment.getApplication() as Context,
|
||||
deepLinkCreator = deepLinkCreator,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Intent.commonAssertions() {
|
||||
assertThat(action).isEqualTo(Intent.ACTION_VIEW)
|
||||
assertThat(component?.className).isEqualTo(MainActivity::class.java.name)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.x.oidc
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.element.android.x.R
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultOidcRedirectUrlProviderTest {
|
||||
@Test
|
||||
fun `test provide`() {
|
||||
val stringProvider = FakeStringProvider(
|
||||
defaultResult = "str"
|
||||
)
|
||||
val sut = DefaultOidcRedirectUrlProvider(
|
||||
stringProvider = stringProvider,
|
||||
)
|
||||
val result = sut.provide()
|
||||
assertThat(result).isEqualTo("str:/")
|
||||
assertThat(stringProvider.lastResIdParam).isEqualTo(R.string.login_redirect_scheme)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user