From 5421957b8e12d38217b515e7756f2e19095d7be8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 16 Mar 2023 15:06:04 +0100 Subject: [PATCH 1/6] Add `:services:analytics` modules. For now import the current plan. This will allow to import existing class from EA and not break compatibility with existing analytics call. --- .../kotlin/extension/DependencyHandleScope.kt | 1 + services/analytics/api/build.gradle.kts | 27 + .../analytics/api/AnalyticsTracker.kt | 36 ++ .../analytics/api/VectorAnalyticsEvent.kt | 22 + .../analytics/api/VectorAnalyticsScreen.kt | 22 + .../services/analytics/api/plan/CallEnded.kt | 56 +++ .../services/analytics/api/plan/CallError.kt | 51 ++ .../analytics/api/plan/CallStarted.kt | 51 ++ .../services/analytics/api/plan/Composer.kt | 58 +++ .../analytics/api/plan/CreatedRoom.kt | 41 ++ .../services/analytics/api/plan/Error.kt | 82 +++ .../analytics/api/plan/Interaction.kt | 468 ++++++++++++++++++ .../services/analytics/api/plan/JoinedRoom.kt | 107 ++++ .../analytics/api/plan/MobileScreen.kt | 327 ++++++++++++ .../analytics/api/plan/PerformanceTimer.kt | 109 ++++ .../analytics/api/plan/PermissionChanged.kt | 53 ++ .../services/analytics/api/plan/Signup.kt | 84 ++++ .../analytics/api/plan/SlashCommand.kt | 46 ++ .../api/plan/UnauthenticatedError.kt | 66 +++ .../analytics/api/plan/UserProperties.kt | 98 ++++ .../services/analytics/api/plan/ViewRoom.kt | 306 ++++++++++++ services/analytics/noop/build.gradle.kts | 34 ++ .../analytics/noop/NoopAnalyticsTracker.kt | 35 ++ settings.gradle.kts | 2 + 24 files changed, 2182 insertions(+) create mode 100644 services/analytics/api/build.gradle.kts create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsTracker.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsEvent.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsScreen.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallEnded.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallError.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallStarted.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Composer.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CreatedRoom.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Error.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Interaction.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/JoinedRoom.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PerformanceTimer.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PermissionChanged.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Signup.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/SlashCommand.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UnauthenticatedError.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UserProperties.kt create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/ViewRoom.kt create mode 100644 services/analytics/noop/build.gradle.kts create mode 100644 services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsTracker.kt diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 3d134bcab4..1474ab72d2 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -66,6 +66,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { fun DependencyHandlerScope.allServicesImpl() { implementation(project(":services:appnavstate:impl")) + implementation(project(":services:analytics:noop")) } fun DependencyHandlerScope.allFeaturesApi() { diff --git a/services/analytics/api/build.gradle.kts b/services/analytics/api/build.gradle.kts new file mode 100644 index 0000000000..b82dbabf93 --- /dev/null +++ b/services/analytics/api/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 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.services.analytics.api" +} + +dependencies { + // implementation(libs.coroutines.core) +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsTracker.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsTracker.kt new file mode 100644 index 0000000000..90c2b5cfd4 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsTracker.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 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.services.analytics.api + +import io.element.android.services.analytics.api.plan.UserProperties + +interface AnalyticsTracker { + /** + * Capture an Event. + */ + fun capture(event: VectorAnalyticsEvent) + + /** + * Track a displayed screen. + */ + fun screen(screen: VectorAnalyticsScreen) + + /** + * Update user specific properties. + */ + fun updateUserProperties(userProperties: UserProperties) +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsEvent.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsEvent.kt new file mode 100644 index 0000000000..49534505c5 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsEvent.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 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.services.analytics.api + +interface VectorAnalyticsEvent { + fun getName(): String + fun getProperties(): Map? +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsScreen.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsScreen.kt new file mode 100644 index 0000000000..7720158e20 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/VectorAnalyticsScreen.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 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.services.analytics.api + +interface VectorAnalyticsScreen { + fun getName(): String + fun getProperties(): Map? +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallEnded.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallEnded.kt new file mode 100644 index 0000000000..63adbaff12 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallEnded.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when a call has ended. + */ +data class CallEnded( + /** + * The duration of the call in milliseconds. + */ + val durationMs: Int, + /** + * Whether its a video call or not. + */ + val isVideo: Boolean, + /** + * Number of participants in the call. + */ + val numParticipants: Int, + /** + * Whether this user placed it. + */ + val placed: Boolean, +) : VectorAnalyticsEvent { + + override fun getName() = "CallEnded" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("durationMs", durationMs) + put("isVideo", isVideo) + put("numParticipants", numParticipants) + put("placed", placed) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallError.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallError.kt new file mode 100644 index 0000000000..10225e25c2 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallError.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when an error occurred in a call. + */ +data class CallError( + /** + * Whether its a video call or not. + */ + val isVideo: Boolean, + /** + * Number of participants in the call. + */ + val numParticipants: Int, + /** + * Whether this user placed it. + */ + val placed: Boolean, +) : VectorAnalyticsEvent { + + override fun getName() = "CallError" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("isVideo", isVideo) + put("numParticipants", numParticipants) + put("placed", placed) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallStarted.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallStarted.kt new file mode 100644 index 0000000000..1ee9db0d1b --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CallStarted.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when a call is started. + */ +data class CallStarted( + /** + * Whether its a video call or not. + */ + val isVideo: Boolean, + /** + * Number of participants in the call. + */ + val numParticipants: Int, + /** + * Whether this user placed it. + */ + val placed: Boolean, +) : VectorAnalyticsEvent { + + override fun getName() = "CallStarted" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("isVideo", isVideo) + put("numParticipants", numParticipants) + put("placed", placed) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Composer.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Composer.kt new file mode 100644 index 0000000000..00a827a166 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Composer.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when the user sends a message via the composer. + */ +data class Composer( + /** + * Whether the user was using the composer inside of a thread. + */ + val inThread: Boolean, + /** + * Whether the user's composer interaction was editing a previously sent + * event. + */ + val isEditing: Boolean, + /** + * Whether the user's composer interaction was a reply to a previously + * sent event. + */ + val isReply: Boolean, + /** + * Whether this message begins a new thread or not. + */ + val startsThread: Boolean? = null, +) : VectorAnalyticsEvent { + + override fun getName() = "Composer" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("inThread", inThread) + put("isEditing", isEditing) + put("isReply", isReply) + startsThread?.let { put("startsThread", it) } + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CreatedRoom.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CreatedRoom.kt new file mode 100644 index 0000000000..1112e732ed --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/CreatedRoom.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when the user creates a room. + */ +data class CreatedRoom( + /** + * Whether the room is a DM. + */ + val isDM: Boolean, +) : VectorAnalyticsEvent { + + override fun getName() = "CreatedRoom" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("isDM", isDM) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Error.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Error.kt new file mode 100644 index 0000000000..a10fc46ced --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Error.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when an error occurred. + */ +data class Error( + /** + * Context - client defined, can be used for debugging. + */ + val context: String? = null, + /** + * Which crypto module is the client currently using. + */ + val cryptoModule: CryptoModule? = null, + val domain: Domain, + val name: Name, +) : VectorAnalyticsEvent { + + enum class Domain { + E2EE, + TO_DEVICE, + VOIP, + } + + enum class Name { + OlmIndexError, + OlmKeysNotSentError, + OlmUnspecifiedError, + ToDeviceFailedToDecrypt, + UnknownError, + VoipIceFailed, + VoipIceTimeout, + VoipInviteTimeout, + VoipUserHangup, + VoipUserMediaFailed, + } + + enum class CryptoModule { + + /** + * Native / legacy crypto module specific to each platform. + */ + Native, + + /** + * Shared / cross-platform crypto module written in Rust. + */ + Rust, + } + + override fun getName() = "Error" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + context?.let { put("context", it) } + cryptoModule?.let { put("cryptoModule", it.name) } + put("domain", domain.name) + put("name", name.name) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Interaction.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Interaction.kt new file mode 100644 index 0000000000..f0d0a47d85 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Interaction.kt @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when the user clicks/taps/activates a UI element. + */ +data class Interaction( + /** + * The index of the element, if its in a list of elements. + */ + val index: Int? = null, + /** + * The manner with which the user activated the UI element. + */ + val interactionType: InteractionType? = null, + /** + * The unique name of this element. + */ + val name: Name, +) : VectorAnalyticsEvent { + + enum class Name { + /** + * User tapped the All filter in the All Chats filter tab. + */ + MobileAllChatsFilterAll, + + /** + * User tapped the Favourites filter in the All Chats filter tab. + */ + MobileAllChatsFilterFavourites, + + /** + * User tapped the People filter in the All Chats filter tab. + */ + MobileAllChatsFilterPeople, + + /** + * User tapped the Unreads filter in the All Chats filter tab. + */ + MobileAllChatsFilterUnreads, + + /** + * User disabled filters from the all chats layout settings. + */ + MobileAllChatsFiltersDisabled, + + /** + * User enabled filters from the all chats layout settings. + */ + MobileAllChatsFiltersEnabled, + + /** + * User disabled recents from the all chats layout settings. + */ + MobileAllChatsRecentsDisabled, + + /** + * User enabled recents from the all chats layout settings. + */ + MobileAllChatsRecentsEnabled, + + /** + * User tapped on Add to Home button on Room Details screen. + */ + MobileRoomAddHome, + + /** + * User tapped on Leave Room button on Room Details screen. + */ + MobileRoomLeave, + + /** + * User tapped on Threads button on Room screen. + */ + MobileRoomThreadListButton, + + /** + * User tapped on a thread summary item on Room screen. + */ + MobileRoomThreadSummaryItem, + + /** + * User validated the creation of a new space. + */ + MobileSpaceCreationValidated, + + /** + * User tapped on the filter button on ThreadList screen. + */ + MobileThreadListFilterItem, + + /** + * User selected a thread on ThreadList screen. + */ + MobileThreadListThreadItem, + + /** + * User tapped the already selected space from the space list. + */ + SpacePanelSelectedSpace, + + /** + * User tapped an unselected space from the space list -> space + * switching should occur. + */ + SpacePanelSwitchSpace, + + /** + * User tapped an unselected sub space from the space list -> space + * switching should occur. + */ + SpacePanelSwitchSubSpace, + + /** + * User clicked the create room button in the add existing room to space + * dialog in Element Web/Desktop. + */ + WebAddExistingToSpaceDialogCreateRoomButton, + + /** + * User clicked the create DM button in the home page of Element + * Web/Desktop. + */ + WebHomeCreateChatButton, + + /** + * User clicked the create room button in the home page of Element + * Web/Desktop. + */ + WebHomeCreateRoomButton, + + /** + * User clicked the explore rooms button in the home page of Element + * Web/Desktop. + */ + WebHomeExploreRoomsButton, + + /** + * User clicked on the mini avatar uploader in the home page of Element + * Web/Desktop. + */ + WebHomeMiniAvatarUploadButton, + + /** + * User clicked the explore rooms button next to the search field at the + * top of the left panel in Element Web/Desktop. + */ + WebLeftPanelExploreRoomsButton, + + /** + * User clicked on the avatar uploader in the profile settings of + * Element Web/Desktop. + */ + WebProfileSettingsAvatarUploadButton, + + /** + * User interacted with pin to sidebar checkboxes in the quick settings + * menu of Element Web/Desktop. + */ + WebQuickSettingsPinToSidebarCheckbox, + + /** + * User interacted with the theme dropdown in the quick settings menu of + * Element Web/Desktop. + */ + WebQuickSettingsThemeDropdown, + + /** + * User accessed the room invite flow using the button at the top of the + * room member list in the right panel of Element Web/Desktop. + */ + WebRightPanelMemberListInviteButton, + + /** + * User accessed room member list using the 'People' button in the right + * panel room summary card of Element Web/Desktop. + */ + WebRightPanelRoomInfoPeopleButton, + + /** + * User accessed room settings using the 'Settings' button in the right + * panel room summary card of Element Web/Desktop. + */ + WebRightPanelRoomInfoSettingsButton, + + /** + * User accessed room member list using the back button in the right + * panel user info card of Element Web/Desktop. + */ + WebRightPanelRoomUserInfoBackButton, + + /** + * User invited someone to room by clicking invite on the right panel + * user info card in Element Web/Desktop. + */ + WebRightPanelRoomUserInfoInviteButton, + + /** + * User clicked the threads 'show' filter dropdown in the threads panel + * in Element Web/Desktop. + */ + WebRightPanelThreadPanelFilterDropdown, + + /** + * User clicked the create room button in the room directory of Element + * Web/Desktop. + */ + WebRoomDirectoryCreateRoomButton, + + /** + * User clicked the Threads button in the top right of a room in Element + * Web/Desktop. + */ + WebRoomHeaderButtonsThreadsButton, + + /** + * User adjusted their favourites using the context menu on the header + * of a room in Element Web/Desktop. + */ + WebRoomHeaderContextMenuFavouriteToggle, + + /** + * User accessed the room invite flow using the context menu on the + * header of a room in Element Web/Desktop. + */ + WebRoomHeaderContextMenuInviteItem, + + /** + * User interacted with leave action in the context menu on the header + * of a room in Element Web/Desktop. + */ + WebRoomHeaderContextMenuLeaveItem, + + /** + * User accessed their room notification settings via the context menu + * on the header of a room in Element Web/Desktop. + */ + WebRoomHeaderContextMenuNotificationsItem, + + /** + * User accessed room member list using the context menu on the header + * of a room in Element Web/Desktop. + */ + WebRoomHeaderContextMenuPeopleItem, + + /** + * User accessed room settings using the context menu on the header of a + * room in Element Web/Desktop. + */ + WebRoomHeaderContextMenuSettingsItem, + + /** + * User clicked the create DM button in the + context menu of the room + * list header in Element Web/Desktop. + */ + WebRoomListHeaderPlusMenuCreateChatItem, + + /** + * User clicked the create room button in the + context menu of the room + * list header in Element Web/Desktop. + */ + WebRoomListHeaderPlusMenuCreateRoomItem, + + /** + * User clicked the explore rooms button in the + context menu of the + * room list header in Element Web/Desktop. + */ + WebRoomListHeaderPlusMenuExploreRoomsItem, + + /** + * User adjusted their favourites using the context menu on a room tile + * in the room list in Element Web/Desktop. + */ + WebRoomListRoomTileContextMenuFavouriteToggle, + + /** + * User accessed the room invite flow using the context menu on a room + * tile in the room list in Element Web/Desktop. + */ + WebRoomListRoomTileContextMenuInviteItem, + + /** + * User interacted with leave action in the context menu on a room tile + * in the room list in Element Web/Desktop. + */ + WebRoomListRoomTileContextMenuLeaveItem, + + /** + * User accessed room settings using the context menu on a room tile in + * the room list in Element Web/Desktop. + */ + WebRoomListRoomTileContextMenuSettingsItem, + + /** + * User accessed their room notification settings via the context menu + * on a room tile in the room list in Element Web/Desktop. + */ + WebRoomListRoomTileNotificationsMenu, + + /** + * User clicked the create DM button in the + context menu of the rooms + * sublist in Element Web/Desktop. + */ + WebRoomListRoomsSublistPlusMenuCreateChatItem, + + /** + * User clicked the create room button in the + context menu of the + * rooms sublist in Element Web/Desktop. + */ + WebRoomListRoomsSublistPlusMenuCreateRoomItem, + + /** + * User clicked the explore rooms button in the + context menu of the + * rooms sublist in Element Web/Desktop. + */ + WebRoomListRoomsSublistPlusMenuExploreRoomsItem, + + /** + * User clicked on the button to return to the user onboarding list in + * the room list in Element Web/Desktop. + */ + WebRoomListUserOnboardingButton, + + /** + * User clicked on the button to close the user onboarding button in the + * room list in Element Web/Desktop. + */ + WebRoomListUserOnboardingIgnoreButton, + + /** + * User interacted with leave action in the general tab of the room + * settings dialog in Element Web/Desktop. + */ + WebRoomSettingsLeaveButton, + + /** + * User interacted with the prompt to create a new room when adjusting + * security settings in an existing room in Element Web/Desktop. + */ + WebRoomSettingsSecurityTabCreateNewRoomButton, + + /** + * User clicked a thread summary in the timeline of a room in Element + * Web/Desktop. + */ + WebRoomTimelineThreadSummaryButton, + + /** + * User interacted with the theme radio selector in the Appearance tab + * of Settings in Element Web/Desktop. + */ + WebSettingsAppearanceTabThemeSelector, + + /** + * User interacted with the pre-built space checkboxes in the Sidebar + * tab of Settings in Element Web/Desktop. + */ + WebSettingsSidebarTabSpacesCheckbox, + + /** + * User clicked the explore rooms button in the context menu of a space + * in Element Web/Desktop. + */ + WebSpaceContextMenuExploreRoomsItem, + + /** + * User clicked the home button in the context menu of a space in + * Element Web/Desktop. + */ + WebSpaceContextMenuHomeItem, + + /** + * User clicked the new room button in the context menu of a space in + * Element Web/Desktop. + */ + WebSpaceContextMenuNewRoomItem, + + /** + * User clicked the new room button in the context menu on the space + * home in Element Web/Desktop. + */ + WebSpaceHomeCreateRoomButton, + + /** + * User clicked the back button on a Thread view going back to the + * Threads Panel of Element Web/Desktop. + */ + WebThreadViewBackButton, + + /** + * User selected a thread in the Threads panel in Element Web/Desktop. + */ + WebThreadsPanelThreadItem, + + /** + * User clicked the theme toggle button in the user menu of Element + * Web/Desktop. + */ + WebUserMenuThemeToggleButton, + + /** + * User clicked on the send DM CTA in the header of the new user + * onboarding page in Element Web/Desktop. + */ + WebUserOnboardingHeaderSendDm, + + /** + * User clicked on the action of the download apps task on the new user + * onboarding page in Element Web/Desktop. + */ + WebUserOnboardingTaskDownloadApps, + + /** + * User clicked on the action of the enable notifications task on the + * new user onboarding page in Element Web/Desktop. + */ + WebUserOnboardingTaskEnableNotifications, + + /** + * User clicked on the action of the find people task on the new user + * onboarding page in Element Web/Desktop. + */ + WebUserOnboardingTaskSendDm, + + /** + * User clicked on the action of the your profile task on the new user + * onboarding page in Element Web/Desktop. + */ + WebUserOnboardingTaskSetupProfile, + } + + enum class InteractionType { + Keyboard, + Pointer, + Touch, + } + + override fun getName() = "Interaction" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + index?.let { put("index", it) } + interactionType?.let { put("interactionType", it.name) } + put("name", name.name) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/JoinedRoom.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/JoinedRoom.kt new file mode 100644 index 0000000000..d7c86629c7 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/JoinedRoom.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when the user joins a room. + */ +data class JoinedRoom( + /** + * Whether the room is a DM. + */ + val isDM: Boolean, + /** + * Whether the room is a Space. + */ + val isSpace: Boolean, + /** + * The size of the room. + */ + val roomSize: RoomSize, + /** + * The trigger for a room being joined if known. + */ + val trigger: Trigger? = null, +) : VectorAnalyticsEvent { + + enum class Trigger { + /** + * Room joined via an invite. + */ + Invite, + + /** + * Room joined via link. + */ + MobilePermalink, + + /** + * Room joined via a push/desktop notification. + */ + Notification, + + /** + * Room joined via the public rooms directory. + */ + RoomDirectory, + + /** + * Room joined via its preview. + */ + RoomPreview, + + /** + * Room joined via the /join slash command. + */ + SlashCommand, + + /** + * Room joined via the space hierarchy view. + */ + SpaceHierarchy, + + /** + * Room joined via a timeline pill or link in another room. + */ + Timeline, + } + + enum class RoomSize { + ElevenToOneHundred, + MoreThanAThousand, + One, + OneHundredAndOneToAThousand, + ThreeToTen, + Two, + } + + override fun getName() = "JoinedRoom" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("isDM", isDM) + put("isSpace", isSpace) + put("roomSize", roomSize.name) + trigger?.let { put("trigger", it.name) } + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt new file mode 100644 index 0000000000..69e637b01e --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/MobileScreen.kt @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsScreen + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when the user changed screen on Element Android/iOS. + */ +data class MobileScreen( + /** + * How long the screen was displayed for in milliseconds. + */ + val durationMs: Int? = null, + val screenName: ScreenName, +) : VectorAnalyticsScreen { + + enum class ScreenName { + /** + * The screen that displays the user's breadcrumbs. + */ + Breadcrumbs, + + /** + * The screen shown to create a new (non-direct) room. + */ + CreateRoom, + + /** + * The screen shown to create a new space. + */ + CreateSpace, + + /** + * The confirmation screen shown before deactivating an account. + */ + DeactivateAccount, + + /** + * The tab on mobile that displays the dialpad. + */ + Dialpad, + + /** + * The Favourites tab on mobile that lists your favourite people/rooms. + */ + Favourites, + + /** + * The form for the forgot password use case. + */ + ForgotPassword, + + /** + * Legacy: The screen that shows information about a specific group. + */ + Group, + + /** + * The Home tab on iOS | possibly the same on Android? + */ + Home, + + /** + * The screen shown to share a link to download the app. + */ + InviteFriends, + + /** + * Room accessed via space bottom sheet list. + */ + Invites, + + /** + * The screen that displays the login flow (when the user already has an + * account). + */ + Login, + + /** + * Legacy: The screen that shows all groups/communities you have joined. + */ + MyGroups, + + /** + * The People tab on mobile that lists all the DM rooms you have joined. + */ + People, + + /** + * The screen that displays the registration flow (when the user wants + * to create an account). + */ + Register, + + /** + * The screen that displays the messages and events received in a room. + */ + Room, + + /** + * The room addresses screen shown from the Room Details screen. + */ + RoomAddresses, + + /** + * The screen shown when tapping the name of a room from the Room + * screen. + */ + RoomDetails, + + /** + * The screen that lists public rooms for you to discover. + */ + RoomDirectory, + + /** + * The screen that lists all the user's rooms and let them filter the + * rooms. + */ + RoomFilter, + + /** + * The screen that displays the list of members that are part of a room. + */ + RoomMembers, + + /** + * The notifications settings screen shown from the Room Details screen. + */ + RoomNotifications, + + /** + * The roles permissions screen shown from the Room Details screen. + */ + RoomPermissions, + + /** + * Screen that displays room preview if user hasn't joined yet. + */ + RoomPreview, + + /** + * The screen that allows you to search for messages/files in a specific + * room. + */ + RoomSearch, + + /** + * The settings screen shown from the Room Details screen. + */ + RoomSettings, + + /** + * The screen that allows you to see all of the files sent in a specific + * room. + */ + RoomUploads, + + /** + * The Rooms tab on mobile that lists all the (non-direct) rooms you've + * joined. + */ + Rooms, + + /** + * The Files tab shown in the global search screen on Mobile. + */ + SearchFiles, + + /** + * The Messages tab shown in the global search screen on Mobile. + */ + SearchMessages, + + /** + * The People tab shown in the global search screen on Mobile. + */ + SearchPeople, + + /** + * The Rooms tab shown in the global search screen on Mobile. + */ + SearchRooms, + + /** + * The global settings screen shown in the app. + */ + Settings, + + /** + * The advanced settings screen (developer mode, rageshake, push + * notification rules). + */ + SettingsAdvanced, + + /** + * The settings screen to change the default notification options. + */ + SettingsDefaultNotifications, + + /** + * The settings screen with general profile settings. + */ + SettingsGeneral, + + /** + * The Help and About screen. + */ + SettingsHelp, + + /** + * The settings screen with list of the ignored users. + */ + SettingsIgnoredUsers, + + /** + * The experimental features settings screen. + */ + SettingsLabs, + + /** + * The settings screen with legals information. + */ + SettingsLegals, + + /** + * The settings screen to manage notification mentions and keywords. + */ + SettingsMentionsAndKeywords, + + /** + * The notifications settings screen. + */ + SettingsNotifications, + + /** + * The preferences screen (theme, language, editor preferences, etc. + */ + SettingsPreferences, + + /** + * The global security settings screen. + */ + SettingsSecurity, + + /** + * The calls settings screen. + */ + SettingsVoiceVideo, + + /** + * The sidebar shown on mobile with spaces, settings etc. + */ + Sidebar, + + /** + * Room accessed via space bottom sheet list. + */ + SpaceBottomSheet, + + /** + * Screen that displays the list of rooms and spaces of a space. + */ + SpaceExploreRooms, + + /** + * Screen that displays the list of members of a space. + */ + SpaceMembers, + + /** + * The bottom sheet that list all space options. + */ + SpaceMenu, + + /** + * The screen shown to create a new direct room. + */ + StartChat, + + /** + * The screen shown to select which room directory you'd like to use. + */ + SwitchDirectory, + + /** + * Screen that displays list of threads for a room. + */ + ThreadList, + + /** + * A screen that shows information about a room member. + */ + User, + + /** + * The splash screen. + */ + Welcome, + } + + override fun getName() = screenName.name + + override fun getProperties(): Map? { + return mutableMapOf().apply { + durationMs?.let { put("durationMs", it) } + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PerformanceTimer.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PerformanceTimer.kt new file mode 100644 index 0000000000..8296ae783f --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PerformanceTimer.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered after timing an operation in the app. + */ +data class PerformanceTimer( + /** + * Client defined, can be used for debugging. + */ + val context: String? = null, + /** + * Client defined, an optional value to indicate how many items were + * handled during the operation. + */ + val itemCount: Int? = null, + /** + * The timer that is being reported. + */ + val name: Name, + /** + * The time reported by the timer in milliseconds. + */ + val timeMs: Int, +) : VectorAnalyticsEvent { + + enum class Name { + /** + * The time spent parsing the response from an initial /sync request. In + * this case, `itemCount` should contain the number of joined rooms. + */ + InitialSyncParsing, + + /** + * The time spent waiting for a response to an initial /sync request. In + * this case, `itemCount` should contain the number of joined rooms. + */ + InitialSyncRequest, + + /** + * The time taken to display an event in the timeline that was opened + * from a notification. + */ + NotificationsOpenEvent, + + /** + * The duration of a regular /sync request when resuming the app. In + * this case, `itemCount` should contain the number of joined rooms in + * the response. + */ + StartupIncrementalSync, + + /** + * The duration of an initial /sync request during startup (if the store + * has been wiped). In this case, `itemCount` should contain the number + * of joined rooms. + */ + StartupInitialSync, + + /** + * How long the app launch screen is displayed for. + */ + StartupLaunchScreen, + + /** + * The time to preload data in the MXStore on iOS. In this case, + * `itemCount` should contain the number of rooms in the store. + */ + StartupStorePreload, + + /** + * The time to load all data from the store (including + * StartupStorePreload time). In this case, `itemCount` should contain + * the number of rooms loaded into the session + */ + StartupStoreReady, + } + + override fun getName() = "PerformanceTimer" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + context?.let { put("context", it) } + itemCount?.let { put("itemCount", it) } + put("name", name.name) + put("timeMs", timeMs) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PermissionChanged.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PermissionChanged.kt new file mode 100644 index 0000000000..9f93078f26 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/PermissionChanged.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when the user changes a permission status. + */ +data class PermissionChanged( + /** + * Whether the permission has been granted by the user. + */ + val granted: Boolean, + /** + * The name of the permission. + */ + val permission: Permission, +) : VectorAnalyticsEvent { + + enum class Permission { + /** + * Permissions related to sending notifications have changed. + */ + Notification, + } + + override fun getName() = "PermissionChanged" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("granted", granted) + put("permission", permission.name) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Signup.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Signup.kt new file mode 100644 index 0000000000..00b7ef7b60 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/Signup.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered once onboarding has completed, but only if the user registered a + * new account. + */ +data class Signup( + /** + * The type of authentication that was used to sign up. + */ + val authenticationType: AuthenticationType, +) : VectorAnalyticsEvent { + + enum class AuthenticationType { + /** + * Social login using Apple. + */ + Apple, + + /** + * Social login using Facebook. + */ + Facebook, + + /** + * Social login using GitHub. + */ + GitHub, + + /** + * Social login using GitLab. + */ + GitLab, + + /** + * Social login using Google. + */ + Google, + + /** + * Registration using some other mechanism such as fallback. + */ + Other, + + /** + * Registration with a username and password. + */ + Password, + + /** + * Registration using another SSO provider. + */ + SSO, + } + + override fun getName() = "Signup" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("authenticationType", authenticationType.name) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/SlashCommand.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/SlashCommand.kt new file mode 100644 index 0000000000..de0af607b0 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/SlashCommand.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when the user runs a slash command in their composer. + */ +data class SlashCommand( + /** + * The name of this command. + */ + val command: Command, +) : VectorAnalyticsEvent { + + enum class Command { + Invite, + Part, + } + + override fun getName() = "SlashCommand" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("command", command.name) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UnauthenticatedError.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UnauthenticatedError.kt new file mode 100644 index 0000000000..e235fa994c --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UnauthenticatedError.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when the user becomes unauthenticated without actually clicking + * sign out(E.g. Due to expiry of an access token without a way to refresh). + */ +data class UnauthenticatedError( + /** + * The error code as defined in matrix spec. The source of this error is + * from the homeserver. + */ + val errorCode: ErrorCode, + /** + * The reason for the error. The source of this error is from the + * homeserver, the reason can vary and is subject to change so there is + * no enum of possible values. + */ + val errorReason: String, + /** + * Whether the auth mechanism is refresh-token-based. + */ + val refreshTokenAuth: Boolean, + /** + * Whether a soft logout or hard logout was triggered. + */ + val softLogout: Boolean, +) : VectorAnalyticsEvent { + + enum class ErrorCode { + M_FORBIDDEN, + M_UNKNOWN, + M_UNKNOWN_TOKEN, + } + + override fun getName() = "UnauthenticatedError" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + put("errorCode", errorCode.name) + put("errorReason", errorReason) + put("refreshTokenAuth", refreshTokenAuth) + put("softLogout", softLogout) + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UserProperties.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UserProperties.kt new file mode 100644 index 0000000000..cd72f05af1 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/UserProperties.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2023 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.services.analytics.api.plan + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * The user properties to apply when identifying. This is not an event + * definition. These properties must all be device independent. + */ +data class UserProperties( + /** + * The active filter in the All Chats screen. + */ + val allChatsActiveFilter: AllChatsActiveFilter? = null, + /** + * The selected messaging use case during the onboarding flow. + */ + val ftueUseCaseSelection: FtueUseCaseSelection? = null, + /** + * Number of joined rooms the user has favourited. + */ + val numFavouriteRooms: Int? = null, + /** + * Number of spaces (and sub-spaces) the user is joined to. + */ + val numSpaces: Int? = null, +) { + + enum class FtueUseCaseSelection { + /** + * The third option, Communities. + */ + CommunityMessaging, + + /** + * The first option, Friends and family. + */ + PersonalMessaging, + + /** + * The footer option to skip the question. + */ + Skip, + + /** + * The second option, Teams. + */ + WorkMessaging, + } + + enum class AllChatsActiveFilter { + + /** + * Filters are activated and All is selected. + */ + All, + + /** + * Filters are activated and Favourites is selected. + */ + Favourites, + + /** + * Filters are activated and People is selected. + */ + People, + + /** + * Filters are activated and Unreads is selected. + */ + Unreads, + } + + fun getProperties(): Map? { + return mutableMapOf().apply { + allChatsActiveFilter?.let { put("allChatsActiveFilter", it.name) } + ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) } + numFavouriteRooms?.let { put("numFavouriteRooms", it) } + numSpaces?.let { put("numSpaces", it) } + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/ViewRoom.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/ViewRoom.kt new file mode 100644 index 0000000000..7477b83b13 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/plan/ViewRoom.kt @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2021 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.services.analytics.api.plan + +import io.element.android.services.analytics.api.VectorAnalyticsEvent + +// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT +// https://github.com/matrix-org/matrix-analytics-events/ + +/** + * Triggered when the user changes rooms. + */ +data class ViewRoom( + /** + * active space when user navigated to the room. + */ + val activeSpace: ActiveSpace? = null, + /** + * Whether the room is a DM. + */ + val isDM: Boolean? = null, + /** + * Whether the room is a Space. + */ + val isSpace: Boolean? = null, + /** + * The reason for the room change if known. + */ + val trigger: Trigger? = null, + /** + * Whether the interaction was performed via the keyboard input. + */ + val viaKeyboard: Boolean? = null, +) : VectorAnalyticsEvent { + + enum class Trigger { + /** + * Room accessed due to being just created. + */ + Created, + + /** + * Room switched due to user interacting with a message search result. + */ + MessageSearch, + + /** + * Room switched due to user selecting a user to go to a DM with. + */ + MessageUser, + + /** + * Room accessed via space explore. + */ + MobileExploreRooms, + + /** + * Room switched due to user interacting with a file search result. + */ + MobileFileSearch, + + /** + * Room accessed via interacting with the incall screen. + */ + MobileInCall, + + /** + * Room accessed during external sharing. + */ + MobileLinkShare, + + /** + * Room accessed via link. + */ + MobilePermalink, + + /** + * Room accessed via interacting with direct chat item in the room + * contact detail screen. + */ + MobileRoomMemberDetail, + + /** + * Room accessed via preview. + */ + MobileRoomPreview, + + /** + * Room switched due to user interacting with a room search result. + */ + MobileRoomSearch, + + /** + * Room accessed via interacting with direct chat item in the search + * contact detail screen. + */ + MobileSearchContactDetail, + + /** + * Room accessed via space bottom sheet list. + */ + MobileSpaceBottomSheet, + + /** + * Room accessed via interacting with direct chat item in the space + * contact detail screen. + */ + MobileSpaceMemberDetail, + + /** + * Room accessed via space members list. + */ + MobileSpaceMembers, + + /** + * Space accessed via interacting with the space menu. + */ + MobileSpaceMenu, + + /** + * Space accessed via interacting with a space settings menu item. + */ + MobileSpaceSettings, + + /** + * Room accessed via a push/desktop notification. + */ + Notification, + + /** + * Room accessed via the predecessor link at the top of the upgraded + * room. + */ + Predecessor, + + /** + * Room accessed via the public rooms directory. + */ + RoomDirectory, + + /** + * Room accessed via the room list. + */ + RoomList, + + /** + * Room accessed via a shortcut. + */ + Shortcut, + + /** + * Room accessed via a slash command in Element Web/Desktop like /goto. + */ + SlashCommand, + + /** + * Room accessed via the space hierarchy view. + */ + SpaceHierarchy, + + /** + * Room accessed via a timeline pill or link in another room. + */ + Timeline, + + /** + * Room accessed via a tombstone at the bottom of a predecessor room. + */ + Tombstone, + + /** + * Room switched due to user interacting with incoming verification + * request. + */ + VerificationRequest, + + /** + * Room switched due to accepting a call in a different room in Element + * Web/Desktop. + */ + WebAcceptCall, + + /** + * Room switched due to making a call via the dial pad in Element + * Web/Desktop. + */ + WebDialPad, + + /** + * Room accessed via interacting with the floating call or Jitsi PIP in + * Element Web/Desktop. + */ + WebFloatingCallWindow, + + /** + * Room accessed via the shortcut in Element Web/Desktop's forward + * modal. + */ + WebForwardShortcut, + + /** + * Room accessed via the Element Web/Desktop horizontal breadcrumbs at + * the top of the room list. + */ + WebHorizontalBreadcrumbs, + + /** + * Room accessed via an Element Web/Desktop keyboard shortcut like go to + * next room with unread messages. + */ + WebKeyboardShortcut, + + /** + * Room accessed via Element Web/Desktop's notification panel. + */ + WebNotificationPanel, + + /** + * Room accessed via the predecessor link in Settings > Advanced in + * Element Web/Desktop. + */ + WebPredecessorSettings, + + /** + * Room accessed via clicking on a notifications badge on a room list + * sublist in Element Web/Desktop. + */ + WebRoomListNotificationBadge, + + /** + * Room switched due to the user changing space in Element Web/Desktop. + */ + WebSpaceContextSwitch, + + /** + * Room accessed via clicking on the notifications badge on the + * currently selected space in Element Web/Desktop. + */ + WebSpacePanelNotificationBadge, + + /** + * Room accessed via Element Web/Desktop's Unified Search modal. + */ + WebUnifiedSearch, + + /** + * Room accessed via the Element Web/Desktop vertical breadcrumb hover + * menu. + */ + WebVerticalBreadcrumbs, + + /** + * Room switched due to widget interaction. + */ + Widget, + } + + enum class ActiveSpace { + + /** + * Active space is Home. + */ + Home, + + /** + * Active space is a meta space. + */ + Meta, + + /** + * Active space is a private space. + */ + Private, + + /** + * Active space is a public space. + */ + Public, + } + + override fun getName() = "ViewRoom" + + override fun getProperties(): Map? { + return mutableMapOf().apply { + activeSpace?.let { put("activeSpace", it.name) } + isDM?.let { put("isDM", it) } + isSpace?.let { put("isSpace", it) } + trigger?.let { put("trigger", it.name) } + viaKeyboard?.let { put("viaKeyboard", it) } + }.takeIf { it.isNotEmpty() } + } +} diff --git a/services/analytics/noop/build.gradle.kts b/services/analytics/noop/build.gradle.kts new file mode 100644 index 0000000000..fca489c3d3 --- /dev/null +++ b/services/analytics/noop/build.gradle.kts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 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.services.analytics.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(libs.dagger) + implementation(projects.libraries.di) + api(projects.services.analytics.api) +} diff --git a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsTracker.kt b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsTracker.kt new file mode 100644 index 0000000000..1448a06591 --- /dev/null +++ b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsTracker.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 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.services.analytics.noop + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.services.analytics.api.AnalyticsTracker +import io.element.android.services.analytics.api.VectorAnalyticsEvent +import io.element.android.services.analytics.api.VectorAnalyticsScreen +import io.element.android.services.analytics.api.plan.UserProperties +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class NoopAnalyticsTracker @Inject constructor() : AnalyticsTracker { + + override fun capture(event: VectorAnalyticsEvent) = Unit + + override fun screen(screen: VectorAnalyticsScreen) = Unit + + override fun updateUserProperties(userProperties: UserProperties) = Unit +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7a534a5778..85939a4eea 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -66,6 +66,8 @@ include(":libraries:session-storage:api") include(":libraries:session-storage:impl") include(":libraries:session-storage:impl-memory") +include(":services:analytics:api") +include(":services:analytics:noop") include(":services:appnavstate:api") include(":services:appnavstate:impl") From 049c25e43473121314702f461648028c3d79270f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 16 Mar 2023 18:08:13 +0100 Subject: [PATCH 2/6] Import some classes from Element into :libraries:androidutils --- gradle/libs.versions.toml | 5 +- libraries/androidutils/build.gradle.kts | 1 + .../androidutils/src/main/AndroidManifest.xml | 1 + .../libraries/androidutils/compat/Compat.kt | 42 ++++ .../intent/PendingIntentCompat.kt | 30 +++ .../androidutils/network/WifiDetector.kt | 38 +++ .../system/CopyToClipboardUseCase.kt | 31 +++ .../androidutils/system/SystemUtils.kt | 221 ++++++++++++++++++ .../androidutils/throttler/FirstThrottler.kt | 50 ++++ .../androidutils/uri/UriExtensions.kt | 25 ++ 10 files changed, 442 insertions(+), 2 deletions(-) create mode 100644 libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt create mode 100644 libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/intent/PendingIntentCompat.kt create mode 100644 libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/network/WifiDetector.kt create mode 100644 libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/CopyToClipboardUseCase.kt create mode 100644 libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt create mode 100644 libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt create mode 100644 libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/uri/UriExtensions.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68b8dbc8af..3d81c3798a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ datastore = "1.0.0" constraintlayout = "2.1.4" recyclerview = "1.3.0" lifecycle = "2.5.1" -activity_compose = "1.6.1" +activity = "1.6.1" startup = "1.1.1" # Compose @@ -70,7 +70,8 @@ androidx_lifecycle_process = { module = "androidx.lifecycle:lifecycle-process", androidx_splash = "androidx.core:core-splashscreen:1.0.0" androidx_security_crypto = "androidx.security:security-crypto:1.0.0" -androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity_compose" } +androidx_activity_activity = { module = "androidx.activity:activity", version.ref = "activity" } +androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity" } androidx_startup = { module = "androidx.startup:startup-runtime", version.ref = "startup" } androidx_compose_bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose_bom" } diff --git a/libraries/androidutils/build.gradle.kts b/libraries/androidutils/build.gradle.kts index 13da553bbf..8bc0d8e421 100644 --- a/libraries/androidutils/build.gradle.kts +++ b/libraries/androidutils/build.gradle.kts @@ -26,5 +26,6 @@ android { dependencies { implementation(libs.timber) implementation(libs.androidx.corektx) + implementation(libs.androidx.activity.activity) implementation(projects.libraries.core) } diff --git a/libraries/androidutils/src/main/AndroidManifest.xml b/libraries/androidutils/src/main/AndroidManifest.xml index 5a19e495ae..8b1ccda517 100644 --- a/libraries/androidutils/src/main/AndroidManifest.xml +++ b/libraries/androidutils/src/main/AndroidManifest.xml @@ -17,4 +17,5 @@ + diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt new file mode 100644 index 0000000000..3b95a10eba --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * 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.libraries.androidutils.compat + +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.os.Build + +fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): ApplicationInfo { + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getApplicationInfo( + packageName, + PackageManager.ApplicationInfoFlags.of(flags.toLong()) + ) + else -> @Suppress("DEPRECATION") getApplicationInfo(packageName, flags) + } +} + +fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int): PackageInfo { + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getPackageInfo( + packageName, + PackageManager.PackageInfoFlags.of(flags.toLong()) + ) + else -> @Suppress("DEPRECATION") getPackageInfo(packageName, flags) + } +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/intent/PendingIntentCompat.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/intent/PendingIntentCompat.kt new file mode 100644 index 0000000000..dcdb800a19 --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/intent/PendingIntentCompat.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 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.libraries.androidutils.intent + +import android.app.PendingIntent +import android.os.Build + +object PendingIntentCompat { + const val FLAG_IMMUTABLE = PendingIntent.FLAG_IMMUTABLE + + val FLAG_MUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE + } else { + 0 + } +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/network/WifiDetector.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/network/WifiDetector.kt new file mode 100644 index 0000000000..85b17c6ff8 --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/network/WifiDetector.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 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.libraries.androidutils.network + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import androidx.core.content.getSystemService +import io.element.android.libraries.core.bool.orFalse +import timber.log.Timber + +class WifiDetector( + context: Context +) { + private val connectivityManager = context.getSystemService()!! + + fun isConnectedToWifi(): Boolean { + return connectivityManager.activeNetwork + ?.let { connectivityManager.getNetworkCapabilities(it) } + ?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + .orFalse() + .also { Timber.d("isConnected to WiFi: $it") } + } +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/CopyToClipboardUseCase.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/CopyToClipboardUseCase.kt new file mode 100644 index 0000000000..3eaa907303 --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/CopyToClipboardUseCase.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 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.libraries.androidutils.system + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import androidx.core.content.getSystemService + +class CopyToClipboardUseCase( + private val context: Context, +) { + fun execute(text: CharSequence) { + context.getSystemService() + ?.setPrimaryClip(ClipData.newPlainText("", text)) + } +} 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 new file mode 100644 index 0000000000..7c2da46e0d --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt @@ -0,0 +1,221 @@ +/* + * Copyright 2018 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.libraries.androidutils.system + +import android.annotation.TargetApi +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.PowerManager +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.getSystemService +import io.element.android.libraries.androidutils.compat.getApplicationInfoCompat + +/** + * Tells if the application ignores battery optimizations. + * + * Ignoring them allows the app to run in background to make background sync with the homeserver. + * This user option appears on Android M but Android O enforces its usage and kills apps not + * authorised by the user to run in background. + * + * @return true if battery optimisations are ignored + */ +fun Context.isIgnoringBatteryOptimizations(): Boolean { + // no issue before Android M, battery optimisations did not exist + return getSystemService()?.isIgnoringBatteryOptimizations(packageName) == true +} + +fun Context.isAirplaneModeOn(): Boolean { + return Settings.Global.getInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0 +} + +fun Context.isAnimationEnabled(): Boolean { + return Settings.Global.getFloat(contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) != 0f +} + +@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. + */ +fun Context.getApplicationLabel(packageName: String): String { + return try { + val ai = packageManager.getApplicationInfoCompat(packageName, 0) + packageManager.getApplicationLabel(ai).toString() + } catch (e: PackageManager.NameNotFoundException) { + packageName + } +} + +/** + * display the system dialog for granting this permission. If previously granted, the + * system will not show it (so you should call this method). + * + * Note: If the user finally does not grant the permission, PushManager.isBackgroundSyncAllowed() + * will return false and the notification privacy will fallback to "LOW_DETAIL". + */ +fun requestDisablingBatteryOptimization(activity: Activity, activityResultLauncher: ActivityResultLauncher) { + val intent = Intent() + intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + intent.data = Uri.parse("package:" + activity.packageName) + activityResultLauncher.launch(intent) +} + +// ============================================================================================================== +// Clipboard helper +// ============================================================================================================== + +/** + * Copy a text to the clipboard, and display a Toast when done. + * + * @param context the context + * @param text the text to copy + * @param toastMessage content of the toast message as a String resource. Null for no toast + */ +fun copyToClipboard( + context: Context, + text: CharSequence, + toastMessage: String? = null +) { + CopyToClipboardUseCase(context).execute(text) + toastMessage?.let { context.toast(it) } +} + +/** + * Shows notification settings for the current app. + * In android O will directly opens the notification settings, in lower version it will show the App settings + */ +fun startNotificationSettingsIntent(context: Context, activityResultLauncher: ActivityResultLauncher) { + val intent = Intent() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + } else { + intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + intent.putExtra("app_package", context.packageName) + intent.putExtra("app_uid", context.applicationInfo?.uid) + } + activityResultLauncher.launch(intent) +} + +/** + * Shows notification system settings for the given channel id. + */ +@TargetApi(Build.VERSION_CODES.O) +fun startNotificationChannelSettingsIntent(activity: Activity, channelID: String) { + if (!supportNotificationChannels()) return + val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_APP_PACKAGE, activity.packageName) + putExtra(Settings.EXTRA_CHANNEL_ID, channelID) + } + activity.startActivity(intent) +} + +fun startAddGoogleAccountIntent( + context: Context, + activityResultLauncher: ActivityResultLauncher, + noActivityFoundMessage: String, +) { + val intent = Intent(Settings.ACTION_ADD_ACCOUNT) + intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, arrayOf("com.google")) + try { + activityResultLauncher.launch(intent) + } catch (activityNotFoundException: ActivityNotFoundException) { + context.toast(noActivityFoundMessage) + } +} + +@RequiresApi(Build.VERSION_CODES.O) +fun startInstallFromSourceIntent( + context: Context, + activityResultLauncher: ActivityResultLauncher, + noActivityFoundMessage: String, +) { + val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) + .setData(Uri.parse(String.format("package:%s", context.packageName))) + try { + activityResultLauncher.launch(intent) + } catch (activityNotFoundException: ActivityNotFoundException) { + context.toast(noActivityFoundMessage) + } +} + +fun startSharePlainTextIntent( + context: Context, + activityResultLauncher: ActivityResultLauncher?, + chooserTitle: String?, + text: String, + subject: String? = null, + extraTitle: String? = null, + noActivityFoundMessage: String, +) { + val share = Intent(Intent.ACTION_SEND) + share.type = "text/plain" + share.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) + // Add data to the intent, the receiving app will decide what to do with it. + share.putExtra(Intent.EXTRA_SUBJECT, subject) + share.putExtra(Intent.EXTRA_TEXT, text) + + extraTitle?.let { + share.putExtra(Intent.EXTRA_TITLE, it) + } + + val intent = Intent.createChooser(share, chooserTitle) + try { + if (activityResultLauncher != null) { + activityResultLauncher.launch(intent) + } else { + context.startActivity(intent) + } + } catch (activityNotFoundException: ActivityNotFoundException) { + context.toast(noActivityFoundMessage) + } +} + +fun startImportTextFromFileIntent( + context: Context, + activityResultLauncher: ActivityResultLauncher, + noActivityFoundMessage: String, +) { + val intent = Intent(Intent.ACTION_GET_CONTENT).apply { + type = "text/plain" + } + try { + activityResultLauncher.launch(intent) + } catch (activityNotFoundException: ActivityNotFoundException) { + context.toast(noActivityFoundMessage) + } +} + +// Not in KTX anymore +fun Context.toast(resId: Int) { + Toast.makeText(this, resId, Toast.LENGTH_SHORT).show() +} + +// Not in KTX anymore +fun Context.toast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt new file mode 100644 index 0000000000..7f3de04c6a --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 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.libraries.androidutils.throttler + +import android.os.SystemClock + +/** + * Simple ThrottleFirst + * See https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleFirst.png + */ +class FirstThrottler(private val minimumInterval: Long = 800) { + private var lastDate = 0L + + sealed class CanHandlerResult { + object Yes : CanHandlerResult() + data class No(val shouldWaitMillis: Long) : CanHandlerResult() + + fun waitMillis(): Long { + return when (this) { + Yes -> 0 + is No -> shouldWaitMillis + } + } + } + + fun canHandle(): CanHandlerResult { + val now = SystemClock.elapsedRealtime() + val delaySinceLast = now - lastDate + if (delaySinceLast > minimumInterval) { + lastDate = now + return CanHandlerResult.Yes + } + + // Too soon + return CanHandlerResult.No(minimumInterval - delaySinceLast) + } +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/uri/UriExtensions.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/uri/UriExtensions.kt new file mode 100644 index 0000000000..485a103b5b --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/uri/UriExtensions.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 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.libraries.androidutils.uri + +import android.net.Uri + +const val IGNORED_SCHEMA = "ignored" + +fun Uri.isIgnored() = scheme == IGNORED_SCHEMA + +fun createIgnoredUri(path: String): Uri = Uri.parse("$IGNORED_SCHEMA://$path") From 69c3c8136fb66eebffd852816d822903e032703b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 16 Mar 2023 18:12:54 +0100 Subject: [PATCH 3/6] Add `:services:toolbox` modules. --- .../kotlin/extension/DependencyHandleScope.kt | 3 +- services/toolbox/api/build.gradle.kts | 27 +++++++++ .../toolbox/api/appname/AppNameProvider.kt | 21 +++++++ .../toolbox/api/strings/StringProvider.kt | 46 ++++++++++++++ .../toolbox/api/systemclock/SystemClock.kt | 21 +++++++ services/toolbox/impl/build.gradle.kts | 36 +++++++++++ .../impl/appname/DefaultAppNameProvider.kt | 47 +++++++++++++++ .../toolbox/impl/strings/StringProvider.kt | 60 +++++++++++++++++++ .../impl/systemclock/DefaultSystemClock.kt | 36 +++++++++++ settings.gradle.kts | 2 + 10 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 services/toolbox/api/build.gradle.kts create mode 100644 services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/appname/AppNameProvider.kt create mode 100644 services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt create mode 100644 services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/systemclock/SystemClock.kt create mode 100644 services/toolbox/impl/build.gradle.kts create mode 100644 services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/appname/DefaultAppNameProvider.kt create mode 100644 services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/StringProvider.kt create mode 100644 services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/DefaultSystemClock.kt diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 1474ab72d2..c54adc8492 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -65,8 +65,9 @@ fun DependencyHandlerScope.allLibrariesImpl() { } fun DependencyHandlerScope.allServicesImpl() { - implementation(project(":services:appnavstate:impl")) implementation(project(":services:analytics:noop")) + implementation(project(":services:appnavstate:impl")) + implementation(project(":services:toolbox:impl")) } fun DependencyHandlerScope.allFeaturesApi() { diff --git a/services/toolbox/api/build.gradle.kts b/services/toolbox/api/build.gradle.kts new file mode 100644 index 0000000000..bb748f7ca2 --- /dev/null +++ b/services/toolbox/api/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 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.services.toolbox.api" +} + +dependencies { + implementation(libs.androidx.corektx) +} diff --git a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/appname/AppNameProvider.kt b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/appname/AppNameProvider.kt new file mode 100644 index 0000000000..414c9b632e --- /dev/null +++ b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/appname/AppNameProvider.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 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.services.toolbox.api.appname + +interface AppNameProvider { + fun getAppName(): String +} diff --git a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt new file mode 100644 index 0000000000..4233ea3423 --- /dev/null +++ b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 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.services.toolbox.api.strings + +import androidx.annotation.PluralsRes +import androidx.annotation.StringRes + +interface StringProvider { + /** + * Returns a localized string from the application's package's + * default string table. + * + * @param resId Resource id for the string + * @return The string data associated with the resource, stripped of styled + * text information. + */ + fun getString(@StringRes resId: Int): String + + /** + * Returns a localized formatted string from the application's package's + * default string table, substituting the format arguments as defined in + * [java.util.Formatter] and [java.lang.String.format]. + * + * @param resId Resource id for the format string + * @param formatArgs The format arguments that will be used for + * substitution. + * @return The string data associated with the resource, formatted and + * stripped of styled text information. + */ + fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String + fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String +} diff --git a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/systemclock/SystemClock.kt b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/systemclock/SystemClock.kt new file mode 100644 index 0000000000..7cde29d7c3 --- /dev/null +++ b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/systemclock/SystemClock.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 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.services.toolbox.api.systemclock + +interface SystemClock { + fun epochMillis(): Long +} diff --git a/services/toolbox/impl/build.gradle.kts b/services/toolbox/impl/build.gradle.kts new file mode 100644 index 0000000000..03cca7957d --- /dev/null +++ b/services/toolbox/impl/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 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.services.toolbox.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(libs.dagger) + implementation(projects.libraries.androidutils) + implementation(projects.libraries.di) + api(projects.services.toolbox.api) + implementation(libs.androidx.corektx) +} diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/appname/DefaultAppNameProvider.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/appname/DefaultAppNameProvider.kt new file mode 100644 index 0000000000..7a5cbd46f0 --- /dev/null +++ b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/appname/DefaultAppNameProvider.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 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.services.toolbox.impl.appname + +import android.content.Context +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.androidutils.system.getApplicationLabel +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import io.element.android.services.toolbox.api.appname.AppNameProvider +import timber.log.Timber +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultAppNameProvider @Inject constructor(@ApplicationContext private val context: Context) : + AppNameProvider { + + override fun getAppName(): String { + return try { + val appPackageName = context.packageName + var appName = context.getApplicationLabel(appPackageName) + + // Use appPackageName instead of appName if appName contains any non-ASCII character + if (!appName.matches("\\A\\p{ASCII}*\\z".toRegex())) { + appName = appPackageName + } + appName + } catch (e: Exception) { + Timber.e(e, "## AppNameProvider() : failed") + "ElementAndroid" + } + } +} diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/StringProvider.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/StringProvider.kt new file mode 100644 index 0000000000..657ffc24c2 --- /dev/null +++ b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/StringProvider.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 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.services.toolbox.impl.strings + +import android.content.res.Resources +import androidx.annotation.PluralsRes +import androidx.annotation.StringRes +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.services.toolbox.api.strings.StringProvider +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class AndroidStringProvider @Inject constructor(private val resources: Resources) : StringProvider { + + /** + * Returns a localized string from the application's package's + * default string table. + * + * @param resId Resource id for the string + * @return The string data associated with the resource, stripped of styled + * text information. + */ + override fun getString(@StringRes resId: Int): String { + return resources.getString(resId) + } + + /** + * Returns a localized formatted string from the application's package's + * default string table, substituting the format arguments as defined in + * [java.util.Formatter] and [java.lang.String.format]. + * + * @param resId Resource id for the format string + * @param formatArgs The format arguments that will be used for + * substitution. + * @return The string data associated with the resource, formatted and + * stripped of styled text information. + */ + override fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String { + return resources.getString(resId, *formatArgs) + } + + override fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String { + return resources.getQuantityString(resId, quantity, *formatArgs) + } +} diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/DefaultSystemClock.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/DefaultSystemClock.kt new file mode 100644 index 0000000000..85479d44b0 --- /dev/null +++ b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/DefaultSystemClock.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 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.services.toolbox.impl.systemclock + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.services.toolbox.api.systemclock.SystemClock +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultSystemClock @Inject constructor() : SystemClock { + + /** + * Provides a UTC epoch in milliseconds + * + * This value is not guaranteed to be correct with reality + * as a User can override the system time and date to any values. + */ + override fun epochMillis(): Long { + return System.currentTimeMillis() + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 85939a4eea..1591c5fdb3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -70,6 +70,8 @@ include(":services:analytics:api") include(":services:analytics:noop") include(":services:appnavstate:api") include(":services:appnavstate:impl") +include(":services:toolbox:api") +include(":services:toolbox:impl") include(":features:onboarding:api") include(":features:onboarding:impl") From 7d668e534c3a7c204204e7a047b4f2d8c6df9e17 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 17 Mar 2023 09:38:12 +0100 Subject: [PATCH 4/6] Fix copyright --- .../io/element/android/libraries/androidutils/compat/Compat.kt | 2 +- .../android/libraries/androidutils/system/SystemUtils.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt index 3b95a10eba..69761ccbc2 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * Copyright (c) 2023 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. 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 7c2da46e0d..800da0d5b3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2018 New Vector Ltd + * Copyright (c) 2023 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. From 00c464fe08a0ddfe6802795ca44ae18fe761f3b5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 17 Mar 2023 10:06:18 +0100 Subject: [PATCH 5/6] Rename file. --- .../impl/strings/{StringProvider.kt => AndroidStringProvider.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/{StringProvider.kt => AndroidStringProvider.kt} (100%) diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/StringProvider.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt similarity index 100% rename from services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/StringProvider.kt rename to services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt From ed73e603364404fb2c26eece4726e73eb55766d5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Mar 2023 12:33:50 +0100 Subject: [PATCH 6/6] Cleanup after PR review. --- .../androidutils/throttler/FirstThrottler.kt | 14 ++++++------- services/analytics/api/build.gradle.kts | 4 ---- .../impl/strings/AndroidStringProvider.kt | 20 ------------------- 3 files changed, 7 insertions(+), 31 deletions(-) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt index 7f3de04c6a..fba6066a64 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt @@ -24,9 +24,9 @@ import android.os.SystemClock class FirstThrottler(private val minimumInterval: Long = 800) { private var lastDate = 0L - sealed class CanHandlerResult { - object Yes : CanHandlerResult() - data class No(val shouldWaitMillis: Long) : CanHandlerResult() + sealed class CanHandleResult { + object Yes : CanHandleResult() + data class No(val shouldWaitMillis: Long) : CanHandleResult() fun waitMillis(): Long { return when (this) { @@ -36,15 +36,15 @@ class FirstThrottler(private val minimumInterval: Long = 800) { } } - fun canHandle(): CanHandlerResult { + fun canHandle(): CanHandleResult { val now = SystemClock.elapsedRealtime() val delaySinceLast = now - lastDate if (delaySinceLast > minimumInterval) { lastDate = now - return CanHandlerResult.Yes + return CanHandleResult.Yes } - // Too soon - return CanHandlerResult.No(minimumInterval - delaySinceLast) + // Too early + return CanHandleResult.No(minimumInterval - delaySinceLast) } } diff --git a/services/analytics/api/build.gradle.kts b/services/analytics/api/build.gradle.kts index b82dbabf93..b77829513b 100644 --- a/services/analytics/api/build.gradle.kts +++ b/services/analytics/api/build.gradle.kts @@ -21,7 +21,3 @@ plugins { android { namespace = "io.element.android.services.analytics.api" } - -dependencies { - // implementation(libs.coroutines.core) -} diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt index 657ffc24c2..ee3931eabb 100644 --- a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt +++ b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt @@ -26,30 +26,10 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class AndroidStringProvider @Inject constructor(private val resources: Resources) : StringProvider { - - /** - * Returns a localized string from the application's package's - * default string table. - * - * @param resId Resource id for the string - * @return The string data associated with the resource, stripped of styled - * text information. - */ override fun getString(@StringRes resId: Int): String { return resources.getString(resId) } - /** - * Returns a localized formatted string from the application's package's - * default string table, substituting the format arguments as defined in - * [java.util.Formatter] and [java.lang.String.format]. - * - * @param resId Resource id for the format string - * @param formatArgs The format arguments that will be used for - * substitution. - * @return The string data associated with the resource, formatted and - * stripped of styled text information. - */ override fun getString(@StringRes resId: Int, vararg formatArgs: Any?): String { return resources.getString(resId, *formatArgs) }