[go: nahoru, domu]

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();