From b0f14bfb15b321ae30431c91f65df49cbf99a1c7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 13 Apr 2023 15:04:51 +0200 Subject: [PATCH] Deeplink: handle notification click to open a room. --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 8 ++++ .../io/element/android/x/MainActivity.kt | 22 ++++++++- .../kotlin/io/element/android/x/MainNode.kt | 20 +++++++- .../android/x/intent/IntentProviderImpl.kt | 20 ++++---- appnav/build.gradle.kts | 1 + .../android/appnav/LoggedInFlowNode.kt | 17 +++++-- .../io/element/android/appnav/RoomFlowNode.kt | 1 - .../io/element/android/appnav/RootFlowNode.kt | 30 ++++++++++++ libraries/deeplink/build.gradle.kts | 40 ++++++++++++++++ .../libraries/deeplink/DeepLinkCreator.kt | 39 +++++++++++++++ .../libraries/deeplink/DeeplinkData.kt | 27 +++++++++++ .../libraries/deeplink/DeeplinkParser.kt | 47 +++++++++++++++++++ .../push/impl/intent/IntentProvider.kt | 4 +- .../notifications/NotificationActionIds.kt | 1 - .../impl/notifications/NotificationUtils.kt | 19 ++------ tools/adb/deeplink.sh | 28 +++++++++++ 17 files changed, 292 insertions(+), 33 deletions(-) create mode 100644 libraries/deeplink/build.gradle.kts create mode 100644 libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeepLinkCreator.kt create mode 100644 libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeeplinkData.kt create mode 100644 libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeeplinkParser.kt create mode 100755 tools/adb/deeplink.sh diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 772609f482..7a90fc62b8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -206,6 +206,7 @@ dependencies { allLibrariesImpl() allServicesImpl() allFeaturesImpl(rootDir) + implementation(projects.libraries.deeplink) implementation(projects.tests.uitests) implementation(projects.anvilannotations) implementation(projects.appnav) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 828788ed80..342e05532c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,6 +40,14 @@ + + { + override fun init(node: MainNode) { + mainNode = node + mainNode.handleIntent(intent) + } + } + ) + ) } } } @@ -63,6 +79,8 @@ class MainActivity : NodeComponentActivity() { override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) Timber.w("onNewIntent") + intent ?: return + mainNode.handleIntent(intent) } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/kotlin/io/element/android/x/MainNode.kt b/app/src/main/kotlin/io/element/android/x/MainNode.kt index 6b7dee92b8..fb551f326d 100644 --- a/app/src/main/kotlin/io/element/android/x/MainNode.kt +++ b/app/src/main/kotlin/io/element/android/x/MainNode.kt @@ -16,14 +16,17 @@ package io.element.android.x +import android.content.Intent import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.ParentNode +import com.bumble.appyx.core.plugin.Plugin import io.element.android.appnav.LoggedInFlowNode import io.element.android.appnav.RoomFlowNode import io.element.android.appnav.RootFlowNode @@ -35,11 +38,13 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.x.di.MainDaggerComponentsOwner import io.element.android.x.di.RoomComponent import io.element.android.x.di.SessionComponent +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize class MainNode( buildContext: BuildContext, private val mainDaggerComponentOwner: MainDaggerComponentsOwner, + plugins: List, ) : ParentNode( navModel = PermanentNavModel( @@ -47,6 +52,7 @@ class MainNode( savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, + plugins = plugins, ), DaggerComponentOwner by mainDaggerComponentOwner { @@ -73,7 +79,13 @@ class MainNode( } override fun resolve(navTarget: RootNavTarget, buildContext: BuildContext): Node { - return createNode(buildContext, plugins = listOf(loggedInFlowNodeCallback, roomFlowNodeCallback)) + return createNode( + context = buildContext, + plugins = listOf( + loggedInFlowNodeCallback, + roomFlowNodeCallback, + ) + ) } @Composable @@ -81,6 +93,12 @@ class MainNode( Children(navModel = navModel) } + fun handleIntent(intent: Intent) { + lifecycleScope.launch { + waitForChildAttached().handleIntent(intent) + } + } + @Parcelize object RootNavTarget : Parcelable } diff --git a/app/src/main/kotlin/io/element/android/x/intent/IntentProviderImpl.kt b/app/src/main/kotlin/io/element/android/x/intent/IntentProviderImpl.kt index b3c7aa98e0..e777b08906 100644 --- a/app/src/main/kotlin/io/element/android/x/intent/IntentProviderImpl.kt +++ b/app/src/main/kotlin/io/element/android/x/intent/IntentProviderImpl.kt @@ -18,7 +18,9 @@ package io.element.android.x.intent import android.content.Context import android.content.Intent +import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.deeplink.DeepLinkCreator import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.core.RoomId @@ -28,17 +30,19 @@ import io.element.android.libraries.push.impl.intent.IntentProvider import io.element.android.x.MainActivity import javax.inject.Inject -// TODO EAx change to deep-link. @ContributesBinding(AppScope::class) class IntentProviderImpl @Inject constructor( @ApplicationContext private val context: Context, + private val deepLinkCreator: DeepLinkCreator, ) : IntentProvider { - override fun getMainIntent(): Intent { - return Intent(context, MainActivity::class.java) - } - - override fun getIntent(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?): Intent { - // TODO Handle deeplink or pass parameters - return Intent(context, MainActivity::class.java) + override fun getViewIntent( + sessionId: SessionId, + roomId: RoomId?, + threadId: ThreadId?, + ): Intent { + return Intent(context, MainActivity::class.java).apply { + action = Intent.ACTION_VIEW + data = deepLinkCreator.create(sessionId, roomId, threadId).toUri() + } } } diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index 17efdc15fc..b672f582f9 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(projects.libraries.core) implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) + implementation(projects.libraries.deeplink) implementation(projects.libraries.matrix.api) implementation(projects.libraries.push.api) implementation(projects.libraries.pushproviders.api) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 6f9319f923..fe2d8aa0dc 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -33,6 +33,7 @@ import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.replace +import com.bumble.appyx.navmodel.backstack.operation.singleTop import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode @@ -56,10 +57,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.ui.di.MatrixUIBindings import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking import kotlinx.parcelize.Parcelize -import kotlin.coroutines.coroutineContext @ContributesNode(AppScope::class) class LoggedInFlowNode @AssistedInject constructor( @@ -217,6 +215,19 @@ class LoggedInFlowNode @AssistedInject constructor( } } + suspend fun attachRoot(): Node { + return attachChild { + backstack.singleTop(NavTarget.RoomList) + } + } + + suspend fun attachRoom(roomId: RoomId): RoomFlowNode { + return attachChild { + backstack.singleTop(NavTarget.RoomList) + backstack.push(NavTarget.Room(roomId)) + } + } + @Composable override fun View(modifier: Modifier) { Box(modifier = modifier) { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt index 69c02d500e..3609dbf57e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt @@ -18,7 +18,6 @@ package io.element.android.appnav import android.os.Parcelable import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.lifecycle.subscribe diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 8251584308..519c4e734b 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -17,6 +17,7 @@ package io.element.android.appnav import android.app.Activity +import android.content.Intent import android.os.Parcelable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -45,6 +46,8 @@ import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.deeplink.DeeplinkData +import io.element.android.libraries.deeplink.DeeplinkParser import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService @@ -65,6 +68,7 @@ class RootFlowNode @AssistedInject constructor( private val matrixClientsHolder: MatrixClientsHolder, private val presenter: RootPresenter, private val bugReportEntryPoint: BugReportEntryPoint, + private val deeplinkParser: DeeplinkParser, ) : BackstackNode( backstack = BackStack( @@ -207,4 +211,30 @@ class RootFlowNode @AssistedInject constructor( CircularProgressIndicator() } } + + suspend fun handleIntent(intent: Intent) { + deeplinkParser.getFromIntent(intent) + ?.let { navigateTo(it) } + } + + private suspend fun navigateTo(deeplinkData: DeeplinkData) { + Timber.d("Navigating to $deeplinkData") + attachSession(deeplinkData.sessionId) + .apply { + val roomId = deeplinkData.roomId + if (roomId == null) { + // In case room is not provided, ensure the app navigate back to the room list + attachRoot() + } else { + attachRoom(roomId) + // TODO .attachThread(deeplinkData.threadId) + } + } + } + + private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode { + return attachChild { + backstack.newRoot(NavTarget.LoggedInFlow(sessionId)) + } + } } diff --git a/libraries/deeplink/build.gradle.kts b/libraries/deeplink/build.gradle.kts new file mode 100644 index 0000000000..5d28470cfc --- /dev/null +++ b/libraries/deeplink/build.gradle.kts @@ -0,0 +1,40 @@ +/* + * 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. + */ + +// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + id("io.element.android-library") + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.libraries.deeplink" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(projects.libraries.di) + implementation(libs.dagger) + implementation(libs.androidx.corektx) + implementation(projects.libraries.matrix.api) + + testImplementation(libs.test.junit) + testImplementation(libs.test.truth) +} diff --git a/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeepLinkCreator.kt b/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeepLinkCreator.kt new file mode 100644 index 0000000000..a135988ca0 --- /dev/null +++ b/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeepLinkCreator.kt @@ -0,0 +1,39 @@ +/* + * 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.deeplink + +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId +import javax.inject.Inject + +class DeepLinkCreator @Inject constructor() { + fun create(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?): String { + return buildString { + append("elementx://open/") + append(sessionId.value) + if (roomId != null) { + append("/") + append(roomId.value) + if (threadId != null) { + append("/") + append(threadId.value) + } + } + } + } +} diff --git a/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeeplinkData.kt b/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeeplinkData.kt new file mode 100644 index 0000000000..d393a37c16 --- /dev/null +++ b/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeeplinkData.kt @@ -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. + */ + +package io.element.android.libraries.deeplink + +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId + +data class DeeplinkData( + val sessionId: SessionId, + val roomId: RoomId? = null, + val threadId: ThreadId? = null, +) diff --git a/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeeplinkParser.kt b/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeeplinkParser.kt new file mode 100644 index 0000000000..2affc50818 --- /dev/null +++ b/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/DeeplinkParser.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.libraries.deeplink + +import android.content.Intent +import android.net.Uri +import io.element.android.libraries.matrix.api.core.asRoomId +import io.element.android.libraries.matrix.api.core.asSessionId +import io.element.android.libraries.matrix.api.core.asThreadId +import javax.inject.Inject + +class DeeplinkParser @Inject constructor() { + fun getFromIntent(intent: Intent): DeeplinkData? { + return intent + .takeIf { it.action == Intent.ACTION_VIEW } + ?.data + ?.toDeeplinkData() + } + + private fun Uri.toDeeplinkData(): DeeplinkData? { + if (scheme != "elementx") return null + if (host != "open") return null + val pathBits = path.orEmpty().split("/").drop(1) + val sessionId = pathBits.elementAtOrNull(0)?.asSessionId() ?: return null + val roomId = pathBits.elementAtOrNull(1)?.asRoomId() + val threadId = pathBits.elementAtOrNull(2)?.asThreadId() + return DeeplinkData( + sessionId = sessionId, + roomId = roomId, + threadId = threadId, + ) + } +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt index 52abb3f6a4..ce2b1d3fce 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt @@ -25,9 +25,7 @@ interface IntentProvider { /** * Provide an intent to start the application. */ - fun getMainIntent(): Intent - - fun getIntent( + fun getViewIntent( sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationActionIds.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationActionIds.kt index 56054bbef8..bc20d49917 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationActionIds.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationActionIds.kt @@ -34,7 +34,6 @@ data class NotificationActionIds @Inject constructor( val smartReply = "${buildMeta.applicationId}.NotificationActions.SMART_REPLY_ACTION" val dismissSummary = "${buildMeta.applicationId}.NotificationActions.DISMISS_SUMMARY_ACTION" val dismissRoom = "${buildMeta.applicationId}.NotificationActions.DISMISS_ROOM_NOTIF_ACTION" - val tapToView = "${buildMeta.applicationId}.NotificationActions.TAP_TO_VIEW_ACTION" val diagnostic = "${buildMeta.applicationId}.NotificationActions.DIAGNOSTIC" val push = "${buildMeta.applicationId}.PUSH" } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt index add8fd74eb..8aeaa998ca 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationUtils.kt @@ -482,15 +482,11 @@ class NotificationUtils @Inject constructor( } private fun buildOpenRoomIntent(sessionId: SessionId, roomId: RoomId): PendingIntent? { - val roomIntent = intentProvider.getIntent(sessionId = sessionId, roomId = roomId, threadId = null) - roomIntent.action = actionIds.tapToView - // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that - roomIntent.data = createIgnoredUri("openRoom?$sessionId&$roomId") - + val intent = intentProvider.getViewIntent(sessionId = sessionId, roomId = roomId, threadId = null) return PendingIntent.getActivity( context, clock.epochMillis().toInt(), - roomIntent, + intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } @@ -498,22 +494,17 @@ class NotificationUtils @Inject constructor( private fun buildOpenThreadIntent(roomInfo: RoomEventGroupInfo, threadId: ThreadId?): PendingIntent? { val sessionId = roomInfo.sessionId val roomId = roomInfo.roomId - val threadIntentTap = intentProvider.getIntent(sessionId = sessionId, roomId = roomId, threadId = threadId) - threadIntentTap.action = actionIds.tapToView - // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that - threadIntentTap.data = createIgnoredUri("openThread?$sessionId&$roomId&$threadId") - + val intent = intentProvider.getViewIntent(sessionId = sessionId, roomId = roomId, threadId = threadId) return PendingIntent.getActivity( context, clock.epochMillis().toInt(), - threadIntentTap, + intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } private fun buildOpenHomePendingIntentForSummary(sessionId: SessionId): PendingIntent { - val intent = intentProvider.getIntent(sessionId = sessionId, roomId = null, threadId = null) - intent.data = createIgnoredUri("tapSummary?$sessionId") + val intent = intentProvider.getViewIntent(sessionId = sessionId, roomId = null, threadId = null) return PendingIntent.getActivity( context, clock.epochMillis().toInt(), diff --git a/tools/adb/deeplink.sh b/tools/adb/deeplink.sh new file mode 100755 index 0000000000..a88d1083b3 --- /dev/null +++ b/tools/adb/deeplink.sh @@ -0,0 +1,28 @@ +#! /bin/bash +# +# 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. +# + +# Format is: +# elementx://open/{sessionId} to open a session +# elementx://open/{sessionId}/{roomId} to open a room +# elementx://open/{sessionId}/{roomId}/{eventId} to open an event + +# Open a session +# adb shell am start -a android.intent.action.VIEW -d elementx://open/@benoit10518:matrix.org +# Open a room +adb shell am start -a android.intent.action.VIEW -d elementx://open/@benoit10518:matrix.org/!dehdDVSkabQLZFYrgo:matrix.org +# Open a thread +# adb shell am start -a android.intent.action.VIEW -d elementx://open/@benoit10518:matrix.org/!dehdDVSkabQLZFYrgo:matrix.org/\\\$threadId