[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/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")