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/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 1e5ae65..9765b73 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -38,6 +38,7 @@
const val DEXMAKER_MOCKITO = "com.linkedin.dexmaker:dexmaker-mockito:2.25.0"
const val ESPRESSO_CONTRIB = "androidx.test.espresso:espresso-contrib:3.1.0"
const val ESPRESSO_CORE = "androidx.test.espresso:espresso-core:3.1.0"
+const val ESPRESSO_INTENTS = "androidx.test.espresso:espresso-intents:3.1.0"
const val ESPRESSO_IDLING_RESOURCE = "androidx.test.espresso:espresso-idling-resource:3.1.0"
const val ESPRESSO_WEB = "androidx.test.espresso:espresso-web:3.1.0"
const val FINDBUGS = "com.google.code.findbugs:jsr305:3.0.2"
diff --git a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/LeafFragment.kt b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/LeafFragment.kt
new file mode 100644
index 0000000..26e7638
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/LeafFragment.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * 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 androidx.navigation.testapp
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+
+/**
+ * Fragment used to show arguments to a nested Deep Link
+ */
+class LeafFragment : Fragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.leaf_fragment, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val tv = view.findViewById<TextView>(R.id.text)
+ val myarg = arguments?.getString("leafarg")
+ tv.text = myarg
+ }
+}
diff --git a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.kt b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.kt
index 91087cc..e439818 100644
--- a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.kt
+++ b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.kt
@@ -47,7 +47,11 @@
val toolbar = findViewById<Toolbar>(R.id.toolbar)
val drawerLayout = findViewById<DrawerLayout>(R.id.drawer_layout)
toolbar.setupWithNavController(navController,
- AppBarConfiguration(setOf(R.id.main, R.id.android), drawerLayout,
+ AppBarConfiguration(
+ setOf(
+ R.id.main,
+ R.id.android_main
+ ), drawerLayout,
::onSupportNavigateUp))
val navigationView = findViewById<NavigationView>(R.id.nav_view)
diff --git a/navigation/integration-tests/testapp/src/main/res/layout/leaf_fragment.xml b/navigation/integration-tests/testapp/src/main/res/layout/leaf_fragment.xml
new file mode 100644
index 0000000..b190064
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/res/layout/leaf_fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020 The Android Open Source Project
+
+ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="8dp">
+ <TextView android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml b/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml
index fe35192..cafa234 100644
--- a/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml
+++ b/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml
@@ -39,14 +39,33 @@
android:id="@+id/learn_more_about_android"
app:destination="@+id/android"/>
</dialog>
- <fragment android:id="@+id/android"
- android:label="@string/android"
- android:name="androidx.navigation.testapp.AndroidFragment">
- <argument android:name="myarg" android:defaultValue="Android!" />
- <!-- Handle the 'More information' link on www.example.com -->
- <deepLink app:uri="www.iana.org/domains/{myarg}"/>
- <action android:id="@+id/next" app:destination="@+id/first_screen"/>
- </fragment>
+
+ <navigation android:id="@+id/android"
+ app:startDestination="@id/android_main">
+
+ <fragment android:id="@+id/android_main"
+ android:label="@string/android"
+ android:name="androidx.navigation.testapp.AndroidFragment">
+ <argument android:name="myarg" android:defaultValue="Android!" />
+ <!-- Handle the 'More information' link on www.example.com -->
+ <deepLink app:uri="www.iana.org/domains/{myarg}"/>
+ <action android:id="@+id/to_leaf_2" app:destination="@+id/leaf_home"/>
+ </fragment>
+
+ <navigation android:id="@+id/leaf_home"
+ app:startDestination="@+id/android_leaf">
+ <fragment android:id="@+id/android_leaf"
+ android:label="Leaf"
+ android:name=".LeafFragment">
+ <argument android:name="leafarg" android:defaultValue="Default Leaf Arg" />
+
+ <deepLink app:uri="www.iana.org/domain2/{myarg}/{leafarg}"/>
+ <!-- The parent has a default value for {myarg}. It need not be provided. -->
+ <deepLink app:uri="www.iana.org/domain2/{leafarg}"/>
+ </fragment>
+ </navigation>
+ </navigation>
+
<fragment android:id="@+id/first_screen"
android:name="androidx.navigation.testapp.MainFragment"
android:label="@string/first">
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();
diff --git a/navigation/navigation-testing/build.gradle b/navigation/navigation-testing/build.gradle
index b07b4c3..cb682a6 100644
--- a/navigation/navigation-testing/build.gradle
+++ b/navigation/navigation-testing/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")