diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59991cdcf9..92380492a2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,8 +16,6 @@ jobs: debug: name: Build APKs runs-on: ubuntu-latest - # Skip for `main` - if: github.ref != 'refs/heads/main' strategy: matrix: variant: [debug, release, nightly, samples] diff --git a/.github/workflows/build_enterprise.yml b/.github/workflows/build_enterprise.yml new file mode 100644 index 0000000000..7547d5291c --- /dev/null +++ b/.github/workflows/build_enterprise.yml @@ -0,0 +1,69 @@ +name: Enterprise APK Build + +on: + workflow_dispatch: + pull_request: + merge_group: + push: + branches: [ develop ] + +# Enrich gradle.properties for CI/CD +env: + GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.incremental=false -XX:+UseG1GC + CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true + +jobs: + build: + name: Build Enterprise APKs + runs-on: ubuntu-latest + # Skip in forks + if: github.repository == 'element-hq/element-x-android' + strategy: + matrix: + variant: [debug, release, nightly] + fail-fast: false + # Allow all jobs on develop. Just one per PR. + concurrency: + group: ${{ github.ref == 'refs/heads/develop' && format('build-develop-enterprise-{0}-{1}', matrix.variant, github.sha) || format('build-enterprise-{0}-{1}', matrix.variant, github.ref) }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + with: + # Ensure we are building the branch and not the branch after being merged on develop + # https://github.com/actions/checkout/issues/881 + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + run: git submodule update --init --recursive + - name: Use JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '17' + - name: Configure gradle + uses: gradle/actions/setup-gradle@v3 + with: + cache-read-only: ${{ github.ref != 'refs/heads/develop' }} + - name: Assemble debug Gplay Enterprise APK + if: ${{ matrix.variant == 'debug' }} + env: + ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} + ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} + ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} + run: ./gradlew :app:assembleGplayDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES + - name: Upload debug Enterprise APKs + if: ${{ matrix.variant == 'debug' }} + uses: actions/upload-artifact@v4 + with: + name: elementx-enterprise-debug + path: | + app/build/outputs/apk/gplay/debug/*-universal-debug.apk + - name: Compile release sources + if: ${{ matrix.variant == 'release' }} + run: ./gradlew compileReleaseSources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES + - name: Compile nightly sources + if: ${{ matrix.variant == 'nightly' }} + run: ./gradlew compileGplayNightlySources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 4d71f56617..b4fd79e5dd 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -8,6 +8,13 @@ jobs: name: Danger main check steps: - uses: actions/checkout@v4 + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + if: github.repository == 'element-hq/element-x-android' + run: git submodule update --init --recursive - run: | npm install --save-dev @babel/plugin-transform-flow-strip-types - name: Danger diff --git a/.github/workflows/nightly_enterprise.yml b/.github/workflows/nightly_enterprise.yml new file mode 100644 index 0000000000..9cd80fe094 --- /dev/null +++ b/.github/workflows/nightly_enterprise.yml @@ -0,0 +1,57 @@ +name: Build and release Enterprise nightly application + +on: + workflow_dispatch: + schedule: + # Every nights at 4 + - cron: "0 4 * * *" + +env: + GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx6g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.incremental=false -XX:+UseG1GC + CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true + +jobs: + nightly: + name: Build and publish Enterprise nightly bundle to Firebase + runs-on: ubuntu-latest + if: ${{ github.repository == 'element-hq/element-x-android' }} + steps: + - uses: actions/checkout@v4 + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + run: git submodule update --init --recursive + - name: Use JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '17' + - name: Install towncrier + run: | + python3 -m pip install towncrier + - name: Prepare changelog file + run: | + mv towncrier.toml towncrier.toml.bak + sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml + rm towncrier.toml.bak + yes n | towncrier build --version nightly + - name: Build and upload Nightly application + run: | + ./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES + env: + ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} + ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} + ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} + ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }} + ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }} + ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD }} + FIREBASE_TOKEN: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_FIREBASE_TOKEN }} + - name: Additionally upload Nightly APK to browserstack for testing + continue-on-error: true # don't block anything by this upload failing (for now) + run: | + curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_PASSWORD" -X POST "https://api-cloud.browserstack.com/app-automate/upload" -F "file=@app/build/outputs/apk/gplay/nightly/app-gplay-universal-nightly.apk" -F "custom_id=element-x-android-nightly" + env: + BROWSERSTACK_USERNAME: ${{ secrets.ELEMENT_ANDROID_BROWSERSTACK_USERNAME }} + BROWSERSTACK_PASSWORD: ${{ secrets.ELEMENT_ANDROID_BROWSERSTACK_ACCESS_KEY }} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 1c3ab1d7d5..ff435ee988 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -18,6 +18,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + if: github.repository == 'element-hq/element-x-android' + run: git submodule update --init --recursive - name: Run code quality check suite run: ./tools/check/check_code_quality.sh @@ -68,6 +75,13 @@ jobs: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + if: github.repository == 'element-hq/element-x-android' + run: git submodule update --init --recursive - name: Use JDK 17 uses: actions/setup-java@v4 with: @@ -100,6 +114,13 @@ jobs: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + if: github.repository == 'element-hq/element-x-android' + run: git submodule update --init --recursive - name: Use JDK 17 uses: actions/setup-java@v4 with: @@ -136,6 +157,13 @@ jobs: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + if: github.repository == 'element-hq/element-x-android' + run: git submodule update --init --recursive - name: Use JDK 17 uses: actions/setup-java@v4 with: @@ -168,6 +196,13 @@ jobs: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + if: github.repository == 'element-hq/element-x-android' + run: git submodule update --init --recursive - name: Use JDK 17 uses: actions/setup-java@v4 with: @@ -200,6 +235,13 @@ jobs: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + if: github.repository == 'element-hq/element-x-android' + run: git submodule update --init --recursive - name: Use JDK 17 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f596d5e438..b58f2f3348 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,40 @@ jobs: path: | app/build/outputs/bundle/gplayRelease/app-gplay-release.aab + enterprise: + name: Create App Bundle Enterprise + runs-on: ubuntu-latest + concurrency: + group: ${{ format('build-release-main-gplay-{0}', github.sha) }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + run: git submodule update --init --recursive + - name: Use JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '17' + - name: Configure gradle + uses: gradle/actions/setup-gradle@v3 + - name: Create Enterprise app bundle + env: + ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} + ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} + ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} + run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES + - name: Upload bundle as artifact + uses: actions/upload-artifact@v4 + with: + name: elementx-enterprise-app-gplay-bundle-unsigned + path: | + app/build/outputs/bundle/gplayRelease/app-gplay-release.aab + fdroid: name: Create APKs (FDroid) runs-on: ubuntu-latest diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1d41100940..a132fd7d14 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,6 +38,13 @@ jobs: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + if: github.repository == 'element-hq/element-x-android' + run: git submodule update --init --recursive - name: ☕️ Use JDK 17 uses: actions/setup-java@v4 with: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..aa54a9d8ae --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "enterprise"] + path = enterprise + url = git@github.com:element-hq/element-android-enterprise.git diff --git a/.idea/copyright/Element_FOSS.xml b/.idea/copyright/Element_FOSS.xml new file mode 100644 index 0000000000..4ab92252ae --- /dev/null +++ b/.idea/copyright/Element_FOSS.xml @@ -0,0 +1,6 @@ + + + + diff --git a/.idea/copyright/Enterprise.xml b/.idea/copyright/Enterprise.xml new file mode 100644 index 0000000000..2d430b4613 --- /dev/null +++ b/.idea/copyright/Enterprise.xml @@ -0,0 +1,6 @@ + + + + diff --git a/.idea/copyright/NewVector.xml b/.idea/copyright/NewVector.xml deleted file mode 100644 index 72a4f2e779..0000000000 --- a/.idea/copyright/NewVector.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml index 0875fcecb1..69d52afca4 100644 --- a/.idea/copyright/profiles_settings.xml +++ b/.idea/copyright/profiles_settings.xml @@ -1,3 +1,7 @@ - + + + + + \ No newline at end of file diff --git a/.idea/scopes/Enterprise.xml b/.idea/scopes/Enterprise.xml new file mode 100644 index 0000000000..83599ae680 --- /dev/null +++ b/.idea/scopes/Enterprise.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c2db5acdf1..7bc3504d84 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,6 +17,7 @@ @file:Suppress("UnstableApiUsage") import com.android.build.api.variant.FilterConfiguration.FilterType.ABI +import extension.allEnterpriseImpl import extension.allFeaturesImpl import extension.allLibrariesImpl import extension.allServicesImpl @@ -46,7 +47,11 @@ android { namespace = "io.element.android.x" defaultConfig { - applicationId = "io.element.android.x" + applicationId = if (isEnterpriseBuild) { + "io.element.enterprise" + } else { + "io.element.android.x" + } targetSdk = Versions.targetSdk versionCode = Versions.versionCode versionName = Versions.versionName @@ -99,15 +104,22 @@ android { } } + val baseAppName = if (isEnterpriseBuild) { + "Element Enterprise" + } else { + "Element X" + } + logger.warnInBox("Building $baseAppName") + buildTypes { getByName("debug") { - resValue("string", "app_name", "Element X dbg") + resValue("string", "app_name", "$baseAppName dbg") applicationIdSuffix = ".debug" signingConfig = signingConfigs.getByName("debug") } getByName("release") { - resValue("string", "app_name", "Element X") + resValue("string", "app_name", baseAppName) signingConfig = signingConfigs.getByName("debug") postprocessing { @@ -124,7 +136,7 @@ android { initWith(release) applicationIdSuffix = ".nightly" versionNameSuffix = "-nightly" - resValue("string", "app_name", "Element X nightly") + resValue("string", "app_name", "$baseAppName nightly") matchingFallbacks += listOf("release") signingConfig = signingConfigs.getByName("nightly") @@ -220,6 +232,9 @@ knit { dependencies { allLibrariesImpl() allServicesImpl() + if (isEnterpriseBuild) { + allEnterpriseImpl(rootDir, logger) + } allFeaturesImpl(rootDir, logger) implementation(projects.features.migration.api) implementation(projects.anvilannotations) diff --git a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt index dd0ad775fe..7614d54e01 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt @@ -24,6 +24,7 @@ import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.Provides import io.element.android.appconfig.ApplicationConfig +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.messages.impl.timeline.components.customreaction.DefaultEmojibaseProvider import io.element.android.features.messages.impl.timeline.components.customreaction.EmojibaseProvider import io.element.android.libraries.androidutils.system.getVersionCodeFromManifest @@ -77,13 +78,18 @@ object AppModule { @Provides @SingleIn(AppScope::class) - fun providesBuildMeta(@ApplicationContext context: Context, buildType: BuildType) = BuildMeta( + fun providesBuildMeta( + @ApplicationContext context: Context, + buildType: BuildType, + enterpriseService: EnterpriseService, + ) = BuildMeta( isDebuggable = BuildConfig.DEBUG, buildType = buildType, applicationName = ApplicationConfig.APPLICATION_NAME.takeIf { it.isNotEmpty() } ?: context.getString(R.string.app_name), productionApplicationName = ApplicationConfig.PRODUCTION_APPLICATION_NAME, desktopApplicationName = ApplicationConfig.DESKTOP_APPLICATION_NAME, applicationId = BuildConfig.APPLICATION_ID, + isEnterpriseBuild = enterpriseService.isEnterpriseBuild, // TODO EAx Config.LOW_PRIVACY_LOG_ENABLE, lowPrivacyLoggingEnabled = false, versionName = BuildConfig.VERSION_NAME, diff --git a/enterprise b/enterprise new file mode 160000 index 0000000000..ceb65e32d9 --- /dev/null +++ b/enterprise @@ -0,0 +1 @@ +Subproject commit ceb65e32d95052c028a37654a4a0410639f69053 diff --git a/features/enterprise/api/build.gradle.kts b/features/enterprise/api/build.gradle.kts new file mode 100644 index 0000000000..6e9cd26834 --- /dev/null +++ b/features/enterprise/api/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * 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. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.enterprise.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) +} diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt new file mode 100644 index 0000000000..3c27af8629 --- /dev/null +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * 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 io.element.android.features.enterprise.api + +import io.element.android.libraries.matrix.api.core.SessionId + +interface EnterpriseService { + val isEnterpriseBuild: Boolean + suspend fun isEnterpriseUser(sessionId: SessionId): Boolean +} diff --git a/features/enterprise/impl/build.gradle.kts b/features/enterprise/impl/build.gradle.kts new file mode 100644 index 0000000000..eb87d9310b --- /dev/null +++ b/features/enterprise/impl/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * 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. + */ +plugins { + id("io.element.android-library") + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.features.enterprise.impl" +} + +dependencies { + implementation(projects.anvilannotations) + api(projects.features.enterprise.api) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + + testImplementation(libs.coroutines.test) + testImplementation(libs.test.junit) + testImplementation(libs.test.truth) + testImplementation(projects.libraries.matrix.test) +} diff --git a/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt b/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt new file mode 100644 index 0000000000..c3387259b8 --- /dev/null +++ b/features/enterprise/impl/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * 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 io.element.android.features.enterprise.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.core.SessionId +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultEnterpriseService @Inject constructor() : EnterpriseService { + override val isEnterpriseBuild = false + + override suspend fun isEnterpriseUser(sessionId: SessionId) = false +} diff --git a/features/enterprise/impl/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt b/features/enterprise/impl/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt new file mode 100644 index 0000000000..003ca2ffc9 --- /dev/null +++ b/features/enterprise/impl/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * 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 io.element.android.features.enterprise.impl + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.test.A_SESSION_ID +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultEnterpriseServiceTest { + @Test + fun `isEnterpriseBuild is false`() { + val defaultEnterpriseService = DefaultEnterpriseService() + assertThat(defaultEnterpriseService.isEnterpriseBuild).isFalse() + } + + @Test + fun `isEnterpriseUser always return false`() = runTest { + val defaultEnterpriseService = DefaultEnterpriseService() + assertThat(defaultEnterpriseService.isEnterpriseUser(A_SESSION_ID)).isFalse() + } +} diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index f9f8d7e5b9..72e668c3f4 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -169,6 +169,9 @@ class DefaultBugReporter @Inject constructor( currentTracingFilter?.let { builder.addFormDataPart("tracing_filter", it) } + if (buildMeta.isEnterpriseBuild) { + builder.addFormDataPart("label", "Enterprise") + } // add the gzipped files, don't cancel the whole upload if only some file failed to upload var totalUploadedSize = 0L var uploadedSomeLogs = false diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt index 49e055cc29..5b9866254d 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt @@ -26,16 +26,12 @@ import android.os.Build import android.provider.Settings import android.widget.Toast import androidx.activity.result.ActivityResultLauncher -import androidx.annotation.ChecksSdkIntAtLeast import androidx.annotation.RequiresApi import androidx.core.content.pm.PackageInfoCompat import io.element.android.libraries.androidutils.R import io.element.android.libraries.androidutils.compat.getApplicationInfoCompat import io.element.android.libraries.core.mimetype.MimeTypes -@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O) -fun supportNotificationChannels() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - /** * Return the application label of the provided package. If not found, the package is returned. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt index a9cb78548f..2d62a5be70 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt @@ -23,6 +23,7 @@ data class BuildMeta( val productionApplicationName: String, val desktopApplicationName: String, val applicationId: String, + val isEnterpriseBuild: Boolean, val lowPrivacyLoggingEnabled: Boolean, val versionName: String, val versionCode: Long, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt index 52c15d05a4..569c33db7d 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt @@ -26,6 +26,7 @@ fun aBuildMeta( productionApplicationName: String = applicationName, desktopApplicationName: String = applicationName, applicationId: String = "", + isEnterpriseBuild: Boolean = false, lowPrivacyLoggingEnabled: Boolean = true, versionName: String = "", versionCode: Long = 0, @@ -40,6 +41,7 @@ fun aBuildMeta( productionApplicationName = productionApplicationName, desktopApplicationName = desktopApplicationName, applicationId = applicationId, + isEnterpriseBuild = isEnterpriseBuild, lowPrivacyLoggingEnabled = lowPrivacyLoggingEnabled, versionName = versionName, versionCode = versionCode, diff --git a/plugins/src/main/kotlin/Enterprise.kt b/plugins/src/main/kotlin/Enterprise.kt new file mode 100644 index 0000000000..65762c05d6 --- /dev/null +++ b/plugins/src/main/kotlin/Enterprise.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * 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. + */ + +import java.io.File + +/** + * Are we building with the enterprise sources? + */ +val isEnterpriseBuild = File("enterprise/README.md").exists() diff --git a/plugins/src/main/kotlin/Logger.kt b/plugins/src/main/kotlin/Logger.kt new file mode 100644 index 0000000000..fa608e7df4 --- /dev/null +++ b/plugins/src/main/kotlin/Logger.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * 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. + */ + +import org.gradle.api.logging.Logger +import kotlin.math.max + +fun Logger.warnInBox( + text: String, + minBoxWidth: Int = 80, + padding: Int = 4, +) { + val textLength = text.length + val boxWidth = max(textLength + 2, minBoxWidth) + val textPadding = max((boxWidth - textLength) / 2, 1) + warn( + buildString { + append(" ".repeat(padding)) + append("┌") + append("─".repeat(boxWidth)) + append("┐") + } + ) + warn( + buildString { + append(" ".repeat(padding)) + append("│") + append(" ".repeat(textPadding)) + append(text) + append(" ".repeat(textPadding)) + if (textLength % 2 == 1 && boxWidth == minBoxWidth) append(" ") + append("│") + } + ) + warn( + buildString { + append(" ".repeat(padding)) + append("└") + append("─".repeat(boxWidth)) + append("┘") + } + ) +} diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 76a66dd519..97ac5aeaf5 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -64,7 +64,7 @@ object Versions { const val compileSdk = 34 const val targetSdk = 33 // When updating the `minSdk`, make sure to update the value of `minSdkVersion` in the file `tools/release/release.sh` - const val minSdk = 24 + val minSdk = if (isEnterpriseBuild) 26 else 24 val javaCompileVersion = JavaVersion.VERSION_17 val javaLanguageVersion: JavaLanguageVersion = JavaLanguageVersion.of(11) } diff --git a/plugins/src/main/kotlin/extension/CommonExtension.kt b/plugins/src/main/kotlin/extension/CommonExtension.kt index 271b1565f5..bc063d5921 100644 --- a/plugins/src/main/kotlin/extension/CommonExtension.kt +++ b/plugins/src/main/kotlin/extension/CommonExtension.kt @@ -18,6 +18,7 @@ package extension import Versions import com.android.build.api.dsl.CommonExtension +import isEnterpriseBuild import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.api.JavaVersion import org.gradle.api.Project @@ -46,6 +47,11 @@ fun CommonExtension<*, *, *, *, *, *>.androidConfig(project: Project) { lint { lintConfig = File("${project.rootDir}/tools/lint/lint.xml") + if (isEnterpriseBuild) { + // Disable check on ObsoleteSdkInt for Enterprise builds + // since the min sdk is higher for Enterprise builds + disable.add("ObsoleteSdkInt") + } checkDependencies = false abortOnError = true ignoreTestFixturesSources = true diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 4aa635b489..9fd82af4ae 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -130,6 +130,11 @@ fun DependencyHandlerScope.allServicesImpl() { implementation(project(":services:toolbox:impl")) } +fun DependencyHandlerScope.allEnterpriseImpl(rootDir: File, logger: Logger) { + val enterpriseDir = File(rootDir, "enterprise") + addImplementationProjects(enterpriseDir, ":enterprise", "impl", logger) +} + fun DependencyHandlerScope.allFeaturesApi(rootDir: File, logger: Logger) { val featuresDir = File(rootDir, "features") addImplementationProjects(featuresDir, ":features", "api", logger) diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt index 9c09d0bc98..22cfd643a1 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt @@ -36,6 +36,7 @@ object Singleton { productionApplicationName = "EAX-Minimal", desktopApplicationName = "EAX-Minimal-Desktop", applicationId = "io.element.android.samples.minimal", + isEnterpriseBuild = false, lowPrivacyLoggingEnabled = false, versionName = "0.1.0", versionCode = 1, diff --git a/settings.gradle.kts b/settings.gradle.kts index b154f4585e..693a258447 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -87,6 +87,7 @@ fun includeProjects(directory: File, path: String, maxDepth: Int = 1) { } } +includeProjects(File(rootDir, "enterprise"), ":enterprise", maxDepth = 2) includeProjects(File(rootDir, "features"), ":features") includeProjects(File(rootDir, "libraries"), ":libraries") includeProjects(File(rootDir, "services"), ":services") diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt index f126640149..a34f0282a7 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt @@ -112,6 +112,7 @@ class KonsistClassNameTest { "DBov", "Default", "DataStore", + "Enterprise", "FileExtensionExtractor", "KeyStore", "Matrix", diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistLicenseTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistLicenseTest.kt new file mode 100644 index 0000000000..e6cbf9ca14 --- /dev/null +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistLicenseTest.kt @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * 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 io.element.android.tests.konsist + +import com.lemonappdev.konsist.api.Konsist +import com.lemonappdev.konsist.api.verify.assertTrue +import org.junit.Test + +class KonsistLicenseTest { + private val publicLicense = """ + /\* + (?:.*\n)* \* Copyright \(c\) 20\d\d New Vector Ltd + (?:.*\n)* \* + \* 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 + \* + \* {5}https?://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\. + \*/ + """.trimIndent().toRegex() + + private val enterpriseLicense = """ + /\* + \* © 20\d\d New Vector Limited, Element Software SARL, Element Software Inc\., + \* and Element Software GmbH \(the "Element Group"\) only make this file available + \* under a proprietary license model\. + \* + \* Without a proprietary license with us, you cannot use this file\. The terms of + \* the proprietary license agreement between you and any member of the Element Group + \* shall always apply to your use of this file\. Unauthorised use, copying, distribution, + \* or modification of this file, via any medium, is strictly prohibited\. + \* + \* For details about the licensing terms, you must either visit our website or contact + \* a member of our sales team\. + \*/ + """.trimIndent().toRegex() + + @Test + fun `assert that FOSS files have the correct license header`() { + Konsist + .scopeFromProject() + .files + .filter { + it.path.contains("/enterprise/features").not() && + it.nameWithExtension != "locales.kt" && + it.name.startsWith("Template ").not() + } + .assertTrue { + publicLicense.containsMatchIn(it.text) + } + } + + @Test + fun `assert that Enterprise files have the correct license header`() { + Konsist + .scopeFromProject() + .files + .filter { + it.path.contains("/enterprise/features") + } + .assertTrue { + enterpriseLicense.containsMatchIn(it.text) + } + } +} diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml index cffb824ea4..7212a58ce3 100644 --- a/tools/detekt/detekt.yml +++ b/tools/detekt/detekt.yml @@ -197,9 +197,7 @@ performance: # Note: all rules for `comments` are disabled by default, but I put them here to be aware of their existence comments: AbsentOrWrongFileLicense: - active: true - licenseTemplateFile: 'license.template' - licenseTemplateIsRegex: true + active: false CommentOverPrivateFunction: active: false CommentOverPrivateProperty: diff --git a/tools/detekt/license.template b/tools/detekt/license.template deleted file mode 100644 index 08cadc82f9..0000000000 --- a/tools/detekt/license.template +++ /dev/null @@ -1,15 +0,0 @@ -\/\* -(?:.*\n)* \* Copyright \(c\) 20\d\d New Vector Ltd -(?:.*\n)* \* - \* 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(?:s)?:\/\/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\. - \*\/