Pass implicit link arguments to parents after leaving other apps' stack.
Test: Ran the example app. Added automated test for this case. Ran all
NavControllerTest.kt tests. Ran a root-level `/.gradlew test`.
Fixes: 147456890
Change-Id: I3ee51ab9ecd6b75f900a1f576045c21dec44e0d9
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index c93a2b0..0d932ad 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-import static androidx.build.dependencies.DependenciesKt.*
-import androidx.build.LibraryGroups
-import androidx.build.LibraryVersions
+
import androidx.build.AndroidXExtension
+import androidx.build.LibraryGroups
import androidx.build.Publish
+import static androidx.build.dependencies.DependenciesKt.*
+
plugins {
id("AndroidXPlugin")
id("com.android.library")
@@ -39,6 +40,7 @@
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(ANDROIDX_TEST_RULES)
androidTestImplementation(ESPRESSO_CORE)
+ androidTestImplementation(ESPRESSO_INTENTS)
androidTestImplementation(TRUTH)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy)
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy)
diff --git a/navigation/navigation-runtime/src/androidTest/AndroidManifest.xml b/navigation/navigation-runtime/src/androidTest/AndroidManifest.xml
index b243562..64e438f 100644
--- a/navigation/navigation-runtime/src/androidTest/AndroidManifest.xml
+++ b/navigation/navigation-runtime/src/androidTest/AndroidManifest.xml
@@ -22,5 +22,6 @@
<activity android:name="androidx.navigation.ActivityNavigatorActivity"/>
<activity android:name="androidx.navigation.TargetActivity"/>
<activity android:name="androidx.navigation.NavControllerActivity"/>
+ <activity android:name="androidx.navigation.TestActivity"/>
</application>
</manifest>
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index 8abba23..d60724e7 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -16,19 +16,36 @@
package androidx.navigation
+import android.app.Activity
import android.content.Context
+import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
+import android.view.View
+import androidx.activity.ComponentActivity
+import androidx.lifecycle.Lifecycle
import androidx.navigation.test.R
+import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.intent.Intents
+import androidx.test.espresso.intent.Intents.intended
+import androidx.test.espresso.intent.matcher.BundleMatchers
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasData
+import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
+import androidx.test.espresso.intent.matcher.IntentMatchers.toPackage
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.testutils.TestNavigator
import androidx.testutils.test
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import org.hamcrest.CoreMatchers.allOf
+import org.hamcrest.CoreMatchers.not
+import org.hamcrest.Matchers
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
@@ -37,6 +54,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
@@ -216,7 +234,7 @@
val deepLink = Uri.parse("android-app://androidx.navigation.test/test")
navController.navigate(deepLink, navOptions {
- popUpTo(0) { inclusive = true }
+ popUpTo(R.id.nav_root) { inclusive = true }
})
assertThat(navController.currentDestination?.id ?: 0).isEqualTo(R.id.second_test)
assertThat(navigator.backStack.size).isEqualTo(1)
@@ -318,6 +336,68 @@
}
@Test
+ fun testNavigateViaImplicitDeepLink() {
+ val intent = Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse("android-app://androidx.navigation.test/test/argument1/argument2"),
+ ApplicationProvider.getApplicationContext() as Context,
+ TestActivity::class.java
+ )
+
+ Intents.init()
+
+ with(ActivityScenario.launch<TestActivity>(intent)) {
+ moveToState(Lifecycle.State.CREATED)
+ onActivity {
+ activity ->
+ run {
+ val navController = activity.navController
+ navController.setGraph(R.navigation.nav_simple)
+
+ val navigator =
+ navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+
+ assertThat(
+ navController.currentDestination!!.id
+ ).isEqualTo(R.id.second_test)
+
+ // Only the leaf destination should be on the stack.
+ assertThat(navigator.backStack.size).isEqualTo(1)
+ // The parent will be constructed in a new Activity after navigateUp()
+ navController.navigateUp()
+ }
+ }
+
+ assertThat(this.state).isEqualTo(Lifecycle.State.DESTROYED)
+ }
+
+ intended(
+ allOf(
+ toPackage((ApplicationProvider.getApplicationContext() as Context).packageName),
+ not(hasData(anyString())), // The rethrow should not use the URI as primary target.
+ hasExtra(NavController.KEY_DEEP_LINK_IDS, intArrayOf(R.id.nav_root)),
+ hasExtra(
+ Matchers.`is`(NavController.KEY_DEEP_LINK_EXTRAS),
+ allOf(
+ BundleMatchers.hasEntry("arg1", "argument1"),
+ BundleMatchers.hasEntry("arg2", "argument2"),
+ BundleMatchers.hasEntry(
+ NavController.KEY_DEEP_LINK_INTENT,
+ allOf(
+ hasAction(intent.action),
+ hasData(intent.data),
+ hasComponent(intent.component)
+ )
+ )
+ )
+ )
+ )
+ )
+
+ Intents.release()
+ }
+
+ @Test
fun testSaveRestoreStateXml() {
val context = ApplicationProvider.getApplicationContext() as Context
var navController = NavController(context)
@@ -637,7 +717,7 @@
assertEquals(1, navigator.backStack.size)
navController.navigate(R.id.second_test, null, navOptions {
- popUpTo(0) { inclusive = true }
+ popUpTo(R.id.nav_root) { inclusive = true }
})
assertEquals(R.id.second_test, navController.currentDestination?.id ?: 0)
assertEquals(1, navigator.backStack.size)
@@ -878,6 +958,24 @@
}
}
+class TestActivity : ComponentActivity() {
+
+ val navController: NavController = createNavController(this)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(View(this))
+ }
+
+ private fun createNavController(activity: Activity): NavController {
+ val navController = NavController(activity)
+ val navigator = TestNavigator()
+ navController.navigatorProvider.addNavigator(navigator)
+ return navController
+ }
+}
+
/**
* [TestNavigator] that helps with testing saving and restoring state.
*/
diff --git a/navigation/navigation-runtime/src/androidTest/res/navigation/nav_simple.xml b/navigation/navigation-runtime/src/androidTest/res/navigation/nav_simple.xml
index ff71fc3..6063a71 100644
--- a/navigation/navigation-runtime/src/androidTest/res/navigation/nav_simple.xml
+++ b/navigation/navigation-runtime/src/androidTest/res/navigation/nav_simple.xml
@@ -16,18 +16,22 @@
-->
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/nav_root"
app:startDestination="@+id/start_test">
<test android:id="@+id/start_test">
+ <argument android:name="arg1" app:argType="string" />
<action android:id="@+id/second" app:destination="@+id/second_test" />
</test>
<test android:id="@+id/second_test">
+ <argument android:name="arg2" app:argType="string" />
<action android:id="@+id/self" app:destination="@+id/second_test"
app:launchSingleTop="true" />
<action android:id="@+id/finish" app:popUpTo="@id/start_test" />
<action android:id="@+id/finish_self" app:popUpTo="@id/second_test"
app:popUpToInclusive="true" />
<deepLink app:uri="android-app://androidx.navigation.test/test" />
+ <deepLink app:uri="android-app://androidx.navigation.test/test/{arg1}/{arg2}" />
</test>
</navigation>
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
index 221ba97..b34e430 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.java
@@ -359,9 +359,29 @@
NavGraph parent = currentDestination.getParent();
while (parent != null) {
if (parent.getStartDestination() != destId) {
+ final Bundle args = new Bundle();
+
+ if (mActivity != null && mActivity.getIntent() != null) {
+ final Uri data = mActivity.getIntent().getData();
+
+ // We were started via a URI intent.
+ if (data != null) {
+ // Include the original deep link Intent so the Destinations can
+ // synthetically generate additional arguments as necessary.
+ args.putParcelable(KEY_DEEP_LINK_INTENT, mActivity.getIntent());
+ NavDestination.DeepLinkMatch matchingDeepLink = mGraph.matchDeepLink(
+ data);
+ if (matchingDeepLink != null) {
+ args.putAll(matchingDeepLink.getMatchingArgs());
+ }
+ }
+ }
+
TaskStackBuilder parentIntents = new NavDeepLinkBuilder(this)
.setDestination(parent.getId())
+ .setArguments(args)
.createTaskStackBuilder();
+
parentIntents.startActivities();
if (mActivity != null) {
mActivity.finish();