Initial commit of navigation controller
Initial structure of the navigation controller and XML based
navigation graph.
Test: navigation/integration-tests/testapp compiles and runs
Change-Id: I08023947b7c510cd9704bda42edefe1c50cc8430
diff --git a/navigation/.gitignore b/navigation/.gitignore
new file mode 100644
index 0000000..be4e6f1
--- /dev/null
+++ b/navigation/.gitignore
@@ -0,0 +1,4 @@
+local.properties
+maven-repo/
+build/
+*.DS_Store
diff --git a/navigation/integration-tests/testapp/.gitignore b/navigation/integration-tests/testapp/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/navigation/integration-tests/testapp/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/navigation/integration-tests/testapp/build.gradle b/navigation/integration-tests/testapp/build.gradle
new file mode 100644
index 0000000..5a9db3e
--- /dev/null
+++ b/navigation/integration-tests/testapp/build.gradle
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+apply plugin: 'com.android.application'
+
+project.ext.noDocs = true
+
+android {
+ compileSdkVersion tools.current_sdk
+ buildToolsVersion tools.build_tools_version
+
+ defaultConfig {
+ applicationId "com.android.support.navigation.testapp"
+ minSdkVersion flatfoot.min_sdk
+ targetSdkVersion tools.current_sdk
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+
+ testOptions {
+ unitTests.returnDefaultValues = true
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile project(":navigation:runtime")
+ compile libs.support.app_compat
+ androidTestCompile(libs.test_runner) {
+ exclude module: 'support-annotations'
+ }
+ androidTestCompile(libs.espresso_core, {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+ testCompile libs.junit
+}
+createAndroidCheckstyle(project)
+
+tasks['check'].dependsOn(tasks['connectedCheck'])
+
+uploadArchives.enabled = false
diff --git a/navigation/integration-tests/testapp/src/main/AndroidManifest.xml b/navigation/integration-tests/testapp/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..009926e
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.support.navigation.testapp">
+
+ <application
+ android:allowBackup="true"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme"
+ tools:ignore="AllowBackup,GoogleAppIndexingWarning,MissingApplicationIcon">
+ <activity android:name="com.android.support.navigation.testapp.MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
diff --git a/navigation/integration-tests/testapp/src/main/java/com/android/support/navigation/testapp/MainActivity.java b/navigation/integration-tests/testapp/src/main/java/com/android/support/navigation/testapp/MainActivity.java
new file mode 100644
index 0000000..7c2061c
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/java/com/android/support/navigation/testapp/MainActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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 com.android.support.navigation.testapp;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.navigation.app.nav.NavController;
+import android.support.navigation.app.nav.NavDestination;
+import android.support.navigation.app.nav.NavHostFragment;
+import android.support.v4.app.FragmentActivity;
+import android.util.Log;
+import android.widget.Toast;
+
+/**
+ * A simple activity demonstrating use of a NavHostFragment.
+ */
+public class MainActivity extends FragmentActivity {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_activity);
+
+ NavHostFragment host = (NavHostFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.my_nav_host_fragment);
+
+
+ if (host != null) {
+ host.getNavController().addOnNavigatedListener(new NavController.OnNavigatedListener() {
+ @Override
+ public void onNavigated(NavController controller, NavDestination destination) {
+ String dest = getResources().getResourceName(destination.getId());
+ Toast.makeText(MainActivity.this, "Navigated to "
+ + dest,
+ Toast.LENGTH_SHORT).show();
+ Log.d("adamp", "Navigated to " + dest, new Throwable());
+ }
+ });
+ }
+ }
+}
diff --git a/navigation/integration-tests/testapp/src/main/java/com/android/support/navigation/testapp/MainFragment.java b/navigation/integration-tests/testapp/src/main/java/com/android/support/navigation/testapp/MainFragment.java
new file mode 100644
index 0000000..2e6750d
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/java/com/android/support/navigation/testapp/MainFragment.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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 com.android.support.navigation.testapp;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.navigation.app.nav.Navigation;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Fragment used to show how to navigate to another destination
+ */
+public class MainFragment extends Fragment {
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.main_fragment, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ TextView tv = (TextView) view.findViewById(R.id.text);
+ tv.setText(getArguments().getString("myarg"));
+
+ Button b = (Button) view.findViewById(R.id.next_button);
+ b.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next));
+
+ Bundle bundle = new Bundle();
+ bundle.putString("myarg", "foo");
+ Navigation.findController(this).navigate(R.id.next, bundle);
+ }
+}
diff --git a/navigation/integration-tests/testapp/src/main/res/layout/main_activity.xml b/navigation/integration-tests/testapp/src/main/res/layout/main_activity.xml
new file mode 100644
index 0000000..1140a2f
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/res/layout/main_activity.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<android.support.v4.widget.DrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <fragment
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/my_nav_host_fragment"
+ android:name="android.support.navigation.app.nav.NavHostFragment"
+ app:navGraph="@xml/nav_main"
+ app:startDestination="@+id/launcher_home"
+ app:defaultNavHost="true"
+ />
+ <android.support.design.widget.NavigationView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"/>
+</android.support.v4.widget.DrawerLayout>
diff --git a/navigation/integration-tests/testapp/src/main/res/layout/main_fragment.xml b/navigation/integration-tests/testapp/src/main/res/layout/main_fragment.xml
new file mode 100644
index 0000000..e7f7060
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/res/layout/main_fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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">
+ <TextView android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+ <Button
+ android:id="@+id/next_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Navigate next"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/navigation/integration-tests/testapp/src/main/res/values/colors.xml b/navigation/integration-tests/testapp/src/main/res/values/colors.xml
new file mode 100644
index 0000000..d0709b5
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/res/values/colors.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/navigation/integration-tests/testapp/src/main/res/values/strings.xml b/navigation/integration-tests/testapp/src/main/res/values/strings.xml
new file mode 100644
index 0000000..35b98bd
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/res/values/strings.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<resources>
+ <string name="app_name">Navigation</string>
+</resources>
diff --git a/navigation/integration-tests/testapp/src/main/res/values/styles.xml b/navigation/integration-tests/testapp/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7d8cf23
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/res/values/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light">
+ <!-- Customize your theme here. -->
+ <item name="colorPrimary">@color/colorPrimary</item>
+ <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="colorAccent">@color/colorAccent</item>
+ </style>
+
+</resources>
diff --git a/navigation/integration-tests/testapp/src/main/res/xml/nav_main.xml b/navigation/integration-tests/testapp/src/main/res/xml/nav_main.xml
new file mode 100644
index 0000000..3fd3764
--- /dev/null
+++ b/navigation/integration-tests/testapp/src/main/res/xml/nav_main.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <destination android:id="@+id/launcher_home"
+ app:navigator="fragment"
+ app:nav_fragment="com.android.support.navigation.testapp.MainFragment">
+ <default-argument app:argname="myarg" app:argvalue="one" />
+ <action android:id="@+id/next" app:destination="@+id/next_fragment"/>
+ </destination>
+ <destination android:id="@+id/next_fragment"
+ app:navigator="fragment"
+ app:nav_fragment="com.android.support.navigation.testapp.MainFragment">
+ <default-argument app:argname="myarg" app:argvalue="two" />
+ <action android:id="@+id/next" app:destination="@+id/launcher_home"/>
+ </destination>
+</navigation>
\ No newline at end of file
diff --git a/navigation/runtime/build.gradle b/navigation/runtime/build.gradle
new file mode 100644
index 0000000..34c8638
--- /dev/null
+++ b/navigation/runtime/build.gradle
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+import com.android.builder.core.BuilderConstants
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+ compileSdkVersion tools.current_sdk
+ buildToolsVersion tools.build_tools_version
+
+ defaultConfig {
+ minSdkVersion flatfoot.min_sdk
+ targetSdkVersion tools.current_sdk
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ testOptions {
+ unitTests.returnDefaultValues = true
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+}
+
+dependencies {
+ compile libs.support.core_utils
+ compile libs.support.fragments
+
+ testCompile libs.junit
+ testCompile libs.mockito.all
+ testCompile(libs.test_runner) {
+ exclude module: 'support-annotations'
+ }
+ androidTestCompile(libs.espresso_core, {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+}
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: rootProject.ext.localMavenRepo)
+ pom.artifactId = "runtime"
+ }
+ }
+}
+
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+ def suffix = name.capitalize()
+ def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+ dependsOn variant.javaCompile
+ from variant.javaCompile.destinationDir
+ destinationDir new File(project.buildDir, "libJar")
+ }
+}
diff --git a/navigation/runtime/proguard-rules.pro b/navigation/runtime/proguard-rules.pro
new file mode 100644
index 0000000..bb2cc55
--- /dev/null
+++ b/navigation/runtime/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /mnt/android-ssd/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/navigation/runtime/src/main/AndroidManifest.xml b/navigation/runtime/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3d744d9
--- /dev/null
+++ b/navigation/runtime/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.support.navigation">
+
+</manifest>
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/ActivityNavigator.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/ActivityNavigator.java
new file mode 100644
index 0000000..4523c70
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/ActivityNavigator.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.AttributeSet;
+
+/**
+ * ActivityNavigator implements cross-activity navigation.
+ */
+public class ActivityNavigator extends Navigator<ActivityNavigator.Params> {
+ public static final String NAME = "activity";
+
+ private static final String EXTRA_NAV_SOURCE =
+ "android-support-navigation:ActivityNavigator:source";
+ private static final String EXTRA_NAV_CURRENT =
+ "android-support-navigation:ActivityNavigator:current";
+
+ private Context mContext;
+ private Activity mHostActivity;
+
+ public ActivityNavigator(Context context) {
+ mContext = context;
+ }
+
+ public ActivityNavigator(Activity hostActivity) {
+ mContext = mHostActivity = hostActivity;
+ }
+
+ @Override
+ public boolean popBackStack() {
+ if (mHostActivity != null) {
+ int destId = 0;
+ final Intent intent = mHostActivity.getIntent();
+ if (intent != null) {
+ destId = intent.getIntExtra(EXTRA_NAV_SOURCE, 0);
+ }
+ mHostActivity.finish();
+ dispatchOnNavigatorNavigated(destId, true);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Params generateDefaultParams() {
+ return new Params();
+ }
+
+ @Override
+ public Params inflateParams(Context context, AttributeSet attrs) {
+ // TODO Implement me!
+ return generateDefaultParams();
+ }
+
+ @Override
+ public boolean navigate(NavDestination destination, Bundle args, NavOptions navOptions) {
+ final Params params = (Params) destination.getNavigatorParams();
+ Intent intent = new Intent(params.getIntent());
+ if (args != null) {
+ intent.getExtras().putAll(args);
+ }
+ if (navOptions.shouldClearTask()) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+ if (navOptions.shouldLaunchDocument()
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ } else if (!(mContext instanceof Activity)) {
+ // If we're not launching from an Activity context we have to launch in a new task.
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+ if (navOptions.shouldLaunchSingleTop()) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ }
+ if (mHostActivity != null) {
+ final Intent hostIntent = mHostActivity.getIntent();
+ if (hostIntent != null) {
+ final int hostCurrentId = hostIntent.getIntExtra(EXTRA_NAV_CURRENT, 0);
+ if (hostCurrentId != 0) {
+ intent.putExtra(EXTRA_NAV_SOURCE, hostCurrentId);
+ }
+ }
+ }
+ final int destId = destination.getId();
+ intent.putExtra(EXTRA_NAV_CURRENT, destId);
+ mContext.startActivity(intent);
+ dispatchOnNavigatorNavigated(destId, false);
+
+ // Always return false. You can't pop the back stack from the caller of a new
+ // Activity, so we don't add this navigator to the controller's back stack.
+ return false;
+ }
+
+ /**
+ * Params for activity navigation
+ */
+ public static class Params extends Navigator.Params {
+ private Intent mIntent;
+
+ public void setIntent(Intent intent) {
+ mIntent = intent;
+ }
+
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ void setComponentName(ComponentName name) {
+ if (mIntent == null) {
+ mIntent = new Intent();
+ }
+ mIntent.setComponent(name);
+ }
+
+ ComponentName getComponent() {
+ if (mIntent == null) {
+ return null;
+ }
+ return mIntent.getComponent();
+ }
+ }
+}
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/FragmentNavigator.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/FragmentNavigator.java
new file mode 100644
index 0000000..a86cacc0
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/FragmentNavigator.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+
+import com.android.support.navigation.R;
+
+import java.util.HashMap;
+
+/**
+ * Navigator that navigates through {@link FragmentTransaction fragment transactions}. Every
+ * destination using this Navigator must set a valid Fragment class name with
+ * <code>app:nav_fragment</code>.
+ */
+public class FragmentNavigator extends Navigator<FragmentNavigator.Params> {
+ public static final String NAME = "fragment";
+
+ private Context mContext;
+ private FragmentManager mFragmentManager;
+ private int mContainerId;
+ private HashMap<String, Class<? extends Fragment>> mFragmentClasses = new HashMap<>();
+
+ private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
+ new FragmentManager.OnBackStackChangedListener() {
+ @Override
+ public void onBackStackChanged() {
+ int newCount = mFragmentManager.getBackStackEntryCount();
+
+ int destId = 0;
+ StateFragment state = getState();
+ if (state != null) {
+ destId = state.mCurrentDestId;
+ }
+ dispatchOnNavigatorNavigated(destId, newCount == 0);
+ }
+ };
+
+ public FragmentNavigator(Context context, FragmentManager manager, int containerId) {
+ mContext = context;
+ mFragmentManager = manager;
+ mContainerId = containerId;
+
+ mFragmentManager.addOnBackStackChangedListener(mOnBackStackChangedListener);
+ }
+
+ @Override
+ public boolean popBackStack() {
+ return mFragmentManager.popBackStackImmediate();
+ }
+
+ @Override
+ public Params generateDefaultParams() {
+ return new Params();
+ }
+
+ @Override
+ public Params inflateParams(Context context, AttributeSet attrs) {
+ Params p = generateDefaultParams();
+ TypedArray a = context.getResources().obtainAttributes(attrs,
+ R.styleable.FragmentNavigatorParams);
+ p.setFragmentClass(getFragmentClassByName(a.getString(
+ R.styleable.FragmentNavigatorParams_nav_fragment)));
+ p.setFlow(a.getString(R.styleable.FragmentNavigatorParams_nav_flow));
+ a.recycle();
+ return p;
+ }
+
+ Class<? extends Fragment> getFragmentClassByName(String name) {
+ Class<? extends Fragment> clazz = mFragmentClasses.get(name);
+ if (clazz == null) {
+ try {
+ clazz = (Class<? extends Fragment>) Class.forName(name, true,
+ mContext.getClassLoader());
+ mFragmentClasses.put(name, clazz);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return clazz;
+ }
+
+ @Override
+ public boolean navigate(NavDestination destination, Bundle args,
+ NavOptions navOptions) {
+ final Params params = (Params) destination.getNavigatorParams();
+ String flowName = navOptions != null ? navOptions.getFlowName() : null;
+ if (flowName == null) {
+ flowName = params.getFlow();
+ }
+
+ // If the first non-null back stack entry name we find matches our flow name, we're still
+ // part of the same flow and we should pass null as the name of this transaction.
+ // This way we can finish a whole flow back to its root by the flow name.
+ if (flowName != null) {
+ final int stackCount = mFragmentManager.getBackStackEntryCount();
+ for (int i = stackCount - 1; i >= 0; i--) {
+ final FragmentManager.BackStackEntry bse = mFragmentManager.getBackStackEntryAt(i);
+ final String bseName = bse.getName();
+ if (bseName != null) {
+ if (TextUtils.equals(bseName, flowName)) {
+ flowName = null;
+ }
+ break;
+ }
+ }
+ }
+ final Fragment frag = params.createFragment(args);
+ final FragmentTransaction ft = mFragmentManager.beginTransaction()
+ .replace(mContainerId, frag);
+
+ final StateFragment oldState = getState();
+ if (oldState != null) {
+ ft.remove(oldState);
+ }
+
+ final int destId = destination.getId();
+ final StateFragment newState = new StateFragment();
+ newState.mCurrentDestId = destId;
+ ft.add(newState, StateFragment.FRAGMENT_TAG);
+
+ final boolean initialNavigation = mFragmentManager.getFragments().isEmpty();
+ if (!initialNavigation) {
+ ft.addToBackStack(flowName);
+ } else {
+ ft.postOnCommit(new Runnable() {
+ @Override
+ public void run() {
+ dispatchOnNavigatorNavigated(destId, false);
+ }
+ });
+ }
+ ft.commit();
+
+ return true;
+ }
+
+ private StateFragment getState() {
+ return (StateFragment) mFragmentManager.findFragmentByTag(StateFragment.FRAGMENT_TAG);
+ }
+
+ /**
+ * Params specific to {@link FragmentNavigator}
+ */
+ public static class Params extends Navigator.Params {
+ private Class<? extends Fragment> mFragmentClass;
+ private String mFlow;
+
+ public Params() {
+ }
+
+ @Override
+ public void copyFrom(Navigator.Params other) {
+ super.copyFrom(other);
+ if (other instanceof Params) {
+ setFragmentClass(((Params) other).getFragmentClass());
+ setFlow(((Params) other).getFlow());
+ }
+ }
+
+ public void setFragmentClass(Class<? extends Fragment> clazz) {
+ mFragmentClass = clazz;
+ }
+
+ public Class<? extends Fragment> getFragmentClass() {
+ return mFragmentClass;
+ }
+
+ public String getFlow() {
+ return mFlow;
+ }
+
+ public void setFlow(String flowName) {
+ mFlow = flowName;
+ }
+
+ Fragment createFragment(Bundle args) {
+ Class<? extends Fragment> clazz = getFragmentClass();
+ if (clazz == null) {
+ throw new IllegalStateException("fragment class not set");
+ }
+
+ Fragment f;
+ try {
+ f = clazz.newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ if (args != null) {
+ f.setArguments(args);
+ }
+ return f;
+ }
+ }
+
+ /**
+ * An internal fragment used by FragmentNavigator to track additional navigation state.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public static class StateFragment extends Fragment {
+ static final String FRAGMENT_TAG = "android-support-nav:FragmentNavigator.StateFragment";
+
+ private static final String KEY_CURRENT_DEST_ID = "currentDestId";
+
+ int mCurrentDestId;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mCurrentDestId = savedInstanceState.getInt(KEY_CURRENT_DEST_ID);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_CURRENT_DEST_ID, mCurrentDestId);
+ }
+ }
+}
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavController.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavController.java
new file mode 100644
index 0000000..73db2ac
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavController.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.support.annotation.IdRes;
+import android.support.annotation.XmlRes;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * NavController manages app navigation within a host.
+ *
+ * <p>A host is a single context or container for navigation, e.g. a {@link NavHostFragment}.
+ * Navigation hosts are responsible for {@link #saveState() saving} and
+ * {@link #restoreState(Bundle) restoring} their controller's state. Apps will generally obtain
+ * a controller directly from a host, or by using one of the utility methods on the
+ * {@link Navigation} class rather than create a controller directly.</p>
+ *
+ * <p>Navigation flows and destinations are determined by the
+ * {@link NavGraph navigation graph} owned by the controller. These graphs are typically
+ * {@link #getNavInflater() inflated} from an Android resource, but, like views, they can also
+ * be constructed or combined programmatically or for the case of dynamic navigation structure.
+ * (For example, if the navigation structure of the application is determined by live data obtained'
+ * from a remote server.)</p>
+ */
+public class NavController implements NavigatorProvider {
+ /**
+ * Metadata key for defining an app's default navigation graph.
+ *
+ * <p>Applications may declare a graph resource in their manifest instead of declaring
+ * or passing this data to each host or controller:</p>
+ *
+ * <pre class="prettyprint">
+ * <meta-data android:name="android.nav.graph" android:resource="@xml/my_nav_graph" />
+ * </pre>
+ *
+ * <p>A graph resource declared in this manner can be inflated into a controller by calling
+ * {@link #addMetadataGraph()}. Navigation host implementations should do this automatically
+ * if no navigation resource is otherwise supplied during host configuration.</p>
+ */
+ public static final String METADATA_KEY_GRAPH = "android.nav.graph";
+
+ private static final String KEY_GRAPH_ID = "android-support-nav:controller:graphId";
+ private static final String KEY_START_DEST_ID = "android-support-nav:controller:startDestId";
+ private static final String KEY_CUR_DEST_ID = "android-support-nav:controller:curDestId";
+
+ private Context mContext;
+ private NavInflater mInflater;
+ private NavGraph mGraph;
+ private int mGraphId;
+ private NavDestination mCurrentNode;
+ private int mStartDestId;
+
+ private final HashMap<String, Navigator> mNavigators = new HashMap<>();
+ private final ArrayList<Navigator> mNavigatorBackStack = new ArrayList<>();
+
+ private final Navigator.OnNavigatorNavigatedListener mOnNavigatedListener =
+ new Navigator.OnNavigatorNavigatedListener() {
+ @Override
+ public void onNavigatorNavigated(Navigator navigator, @IdRes int destId,
+ boolean isBackStackEmpty) {
+ if (destId != 0) {
+ NavDestination newDest = mGraph.findNode(destId);
+ if (newDest == null) {
+ throw new IllegalArgumentException("Navigator " + navigator
+ + " reported navigation to unknown destination id "
+ + mContext.getResources().getResourceName(destId));
+ }
+ mCurrentNode = newDest;
+ dispatchOnNavigated(newDest);
+ }
+ if (isBackStackEmpty) {
+ onNavigatorBackStackEmpty(navigator);
+ }
+ }
+ };
+
+ private final CopyOnWriteArrayList<OnNavigatedListener> mOnNavigatedListeners =
+ new CopyOnWriteArrayList<>();
+
+ /**
+ * OnNavigatorNavigatedListener receives a callback when the associated controller
+ * navigates to a new destination.
+ */
+ public interface OnNavigatedListener {
+ /**
+ * onNavigatorNavigated is called when the controller navigates to a new destination.
+ * This navigation may be to a destination that has not been seen before, or one that
+ * was previously on the back stack. This method is called after navigation is complete,
+ * but associated transitions may still be playing.
+ *
+ * @param controller the controller that navigated
+ * @param destination the new destination
+ */
+ void onNavigated(NavController controller, NavDestination destination);
+ }
+
+ /**
+ * Constructs a new controller for a given {@link Context}. Controllers should not be
+ * used outside of their context and retain a hard reference to the context supplied.
+ * If you need a global controller, pass {@link Context#getApplicationContext()}.
+ *
+ * <p>Apps should generally not construct controllers, instead obtain a relevant controller
+ * directly from a navigation host such as
+ * {@link NavHostFragment#getNavController() NavHostFragment} or by using one of
+ * the utility methods on the {@link Navigation} class.</p>
+ *
+ * <p>Note that controllers that are not constructed with an {@link Activity} context
+ * (or a wrapped activity context) will only be able to navigate to
+ * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK new tasks} or
+ * {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT new document tasks} when
+ * navigating to new activities.</p>
+ *
+ * @param context context for this controller
+ */
+ public NavController(Context context) {
+ mContext = context;
+ final ActivityNavigator activityNavigator = new ActivityNavigator(mContext);
+ mNavigators.put(ActivityNavigator.NAME, activityNavigator);
+ }
+
+ /**
+ * Constructs a new controller for a given {@link Activity}.
+ *
+ * <p>Apps should generally not construct controllers, instead obtain a relevant controller
+ * directly from a navigation host such as
+ * {@link NavHostFragment#getNavController() NavHostFragment} or by using one of
+ * the utility methods on the {@link Navigation} class.</p>
+ *
+ * @param activity activity for this controller
+ */
+ public NavController(Activity activity) {
+ mContext = activity;
+ final ActivityNavigator activityNavigator = new ActivityNavigator(activity);
+ mNavigators.put(ActivityNavigator.NAME, activityNavigator);
+
+ // The host activity is always at the root.
+ mNavigatorBackStack.add(activityNavigator);
+ }
+
+ /**
+ * Adds an {@link OnNavigatedListener} to this controller to receive events when
+ * the controller navigates to a new destination.
+ *
+ * @param listener the listener to receive events
+ */
+ public void addOnNavigatedListener(OnNavigatedListener listener) {
+ mOnNavigatedListeners.add(listener);
+ }
+
+ /**
+ * Removes an {@link OnNavigatedListener} from this controller. It will no longer
+ * receive navigation events.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeOnNavigatedListener(OnNavigatedListener listener) {
+ mOnNavigatedListeners.remove(listener);
+ }
+
+ /**
+ * Attempts to pop the controller's back stack. Analogous to when the user presses
+ * the system {@link android.view.KeyEvent#KEYCODE_BACK Back} button when the associated
+ * navigation host has focus.
+ *
+ * @return true if the stack was popped, false otherwise
+ */
+ public boolean popBackStack() {
+ if (mNavigatorBackStack.isEmpty()) {
+ throw new IllegalArgumentException("NavController back stack is empty");
+ }
+ return mNavigatorBackStack.get(mNavigatorBackStack.size() - 1).popBackStack();
+ }
+
+ /**
+ * Attempts to navigate up in the navigation hierarchy. Suitable for when the
+ * user presses the "Up" button marked with a left (or start)-facing arrow in the upper left
+ * (or starting) corner of the app UI.
+ *
+ * <p>The intended behavior of Up differs from {@link #popBackStack() Back} when the user
+ * did not reach the current destination from the application's own task. e.g. if the user
+ * is viewing a document or link in the current app in an activity hosted on another app's
+ * task where the user clicked the link. In this case the current activity will be
+ * {@link Activity#finish() finished} and the user will be taken to an appropriate
+ * destination in this app on its own task.</p>
+ *
+ * @return true if navigation was successful, false otherwise
+ */
+ public boolean navigateUp() {
+ // TODO check current task; if we're in one from a viewer context on another task, jump.
+ return popBackStack();
+ }
+
+ void dispatchOnNavigated(NavDestination destination) {
+ for (OnNavigatedListener listener : mOnNavigatedListeners) {
+ listener.onNavigated(this, destination);
+ }
+ }
+
+ @Override
+ public Navigator getNavigator(String name) {
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("navigator name cannot be null");
+ }
+
+ return mNavigators.get(name);
+ }
+
+ @Override
+ public void addNavigator(String name, Navigator navigator) {
+ navigator.addOnNavigatorNavigatedListener(mOnNavigatedListener);
+ mNavigators.put(name, navigator);
+ }
+
+ /**
+ * Removes a registered {@link Navigator} by name.
+ *
+ * @param name name of the navigator to remove
+ */
+ public void removeNavigator(String name) {
+ final Navigator removed = mNavigators.remove(name);
+ if (removed != null) {
+ removed.removeOnNavigatorNavigatedListener(mOnNavigatedListener);
+ }
+ }
+
+ /**
+ * Add a {@link NavGraph navigation graph} as specified in the application manifest.
+ *
+ * <p>Applications may declare a graph resource in their manifest instead of declaring
+ * or passing this data to each host or controller:</p>
+ *
+ * <pre class="prettyprint">
+ * <meta-data android:name="android.nav.graph" android:resource="@xml/my_nav_graph" />
+ * </pre>
+ *
+ * @see #METADATA_KEY_GRAPH
+ */
+ public void addMetadataGraph() {
+ final Bundle metaData = mContext.getApplicationInfo().metaData;
+ if (metaData != null) {
+ final int resid = metaData.getInt(METADATA_KEY_GRAPH);
+ if (resid != 0) {
+ addGraph(resid);
+ }
+ }
+ }
+
+ /**
+ * Returns the {@link NavInflater inflater} for this controller.
+ *
+ * @return inflater for loading navigation resources
+ */
+ public NavInflater getNavInflater() {
+ if (mInflater == null) {
+ mInflater = new NavInflater(mContext, this);
+ }
+ return mInflater;
+ }
+
+ /**
+ * Sets the {@link NavGraph navigation graph} to the specified resource.
+ * Any current navigation graph data will be replaced.
+ *
+ * @param resid resource id of the navigation graph to inflate
+ *
+ * @see #getNavInflater()
+ * @see #setGraph(NavGraph)
+ */
+ public void setGraph(@XmlRes int resid) {
+ mGraph = getNavInflater().inflate(resid);
+ mGraphId = resid;
+ }
+
+ /**
+ * Sets the {@link NavGraph navigation graph} to the specified resource.
+ * Any current navigation graph data will be replaced.
+ *
+ * @param graph graph to set
+ * @see #setGraph(int)
+ */
+ public void setGraph(NavGraph graph) {
+ mGraph = graph;
+ mGraphId = 0;
+ }
+
+ /**
+ * Adds the contents of a navigation resource to the current navigation graph.
+ *
+ * @param resid resource id of the navigation graph to inflate
+ *
+ * @see #getNavInflater()
+ * @see #setGraph(NavGraph)
+ */
+ public void addGraph(@XmlRes int resid) {
+ NavInflater inflater = getNavInflater();
+ NavGraph newGraph = inflater.inflate(resid);
+ if (mGraph != null) {
+ mGraph.addAll(newGraph);
+ mGraphId = 0;
+ } else {
+ mGraph = newGraph;
+ mGraphId = resid;
+ }
+ }
+
+ /**
+ * Adds the contents of a navigation resource to the current navigation graph.
+ *
+ * @param graph graph to merge into this controller's graph
+ */
+ public void addGraph(NavGraph graph) {
+ if (mGraph == null) {
+ mGraph = new NavGraph();
+ }
+ mGraph.addAll(graph);
+ mGraphId = 0;
+ }
+
+ /**
+ * Sets the starting navigation destination for this controller.
+ *
+ * @param resid destination id to set
+ */
+ public void setStartDestination(@IdRes int resid) {
+ mStartDestId = resid;
+ if (mCurrentNode == null) {
+ navigateTo(resid);
+ }
+ }
+
+ /**
+ * Navigate directly to a destination.
+ *
+ * <p>Requests navigation to the given destination id from the current navigation graph.
+ * Apps should generally prefer {@link #navigate(int) navigating by action} when possible.</p>
+ *
+ * @param resid destination id to navigate to
+ */
+ public final void navigateTo(@IdRes int resid) {
+ navigateTo(resid, null);
+ }
+
+ /**
+ * Navigate directly to a destination.
+ *
+ * <p>Requests navigation to the given destination id from the current navigation graph.
+ * Apps should generally prefer {@link #navigate(int) navigating by action} when possible.</p>
+ *
+ * @param resid destination id to navigate to
+ * @param args arguments to pass to the destination
+ */
+ public final void navigateTo(@IdRes int resid, Bundle args) {
+ navigateTo(resid, args, null);
+ }
+
+ /**
+ * Navigate directly to a destination.
+ *
+ * <p>Requests navigation to the given destination id from the current navigation graph.
+ * Apps should generally prefer {@link #navigate(int) navigating by action} when possible.</p>
+ *
+ * @param resid destination id to navigate to
+ * @param args arguments to pass to the destination
+ * @param navOptions special options for this navigation operation
+ */
+ public void navigateTo(@IdRes int resid, Bundle args, NavOptions navOptions) {
+ NavDestination node = mGraph.findNode(resid);
+ if (node == null) {
+ final String dest = mContext.getResources().getResourceName(resid);
+ throw new IllegalArgumentException("navigation destination " + dest
+ + " is unknown to this NavController");
+ }
+ node.navigate(args, navOptions);
+ }
+
+ /**
+ * Navigate via an action defined on the current destination.
+ *
+ * <p>Requests navigation to the given {@link NavDestination#getActionDestination(int) action},
+ * appropriate for the current location, e.g. "next" or "home."</p>
+ *
+ * @param action navigation action to invoke
+ */
+ public void navigate(@IdRes int action) {
+ navigate(action, null);
+ }
+
+ /**
+ * Navigate via an action defined on the current destination.
+ *
+ * <p>Requests navigation to the given {@link NavDestination#getActionDestination(int) action},
+ * appropriate for the current location, e.g. "next" or "home."</p>
+ *
+ * @param action navigation action to invoke
+ * @param args arguments to pass to the destination
+ */
+ public void navigate(@IdRes int action, Bundle args) {
+ navigate(action, args, null);
+ }
+
+ /**
+ * Navigate via an action defined on the current destination.
+ *
+ * <p>Requests navigation to the given {@link NavDestination#getActionDestination(int) action},
+ * appropriate for the current location, e.g. "next" or "home."</p>
+ *
+ * @param action navigation action to invoke
+ * @param args arguments to pass to the destination
+ * @param navOptions special options for this navigation operation
+ */
+ public void navigate(@IdRes int action, Bundle args, NavOptions navOptions) {
+ if (mCurrentNode == null) {
+ throw new IllegalStateException("no current navigation node");
+ }
+ final int dest = mCurrentNode.getActionDestination(action);
+ if (dest == 0) {
+ final Resources res = mContext.getResources();
+ throw new IllegalStateException("no destination defined from "
+ + res.getResourceName(mCurrentNode.getId())
+ + " for action " + res.getResourceName(action));
+ }
+ navigateTo(dest, args, navOptions);
+ }
+
+ /**
+ * Saves all navigation controller state to a Bundle.
+ *
+ * <p>State may be restored from a bundle returned from this method by calling
+ * {@link #restoreState(Bundle)}. Saving controller state is the responsibility
+ * of a navigation host, e.g. {@link NavHostFragment}.</p>
+ *
+ * @return saved state for this controller
+ */
+ public Bundle saveState() {
+ Bundle b = null;
+ if (mGraphId != 0) {
+ b = new Bundle();
+ b.putInt(KEY_GRAPH_ID, mGraphId);
+ }
+ if (mStartDestId != 0) {
+ if (b == null) {
+ b = new Bundle();
+ }
+ b.putInt(KEY_START_DEST_ID, mStartDestId);
+ }
+ if (mCurrentNode != null) {
+ if (b == null) {
+ b = new Bundle();
+ }
+ b.putInt(KEY_CUR_DEST_ID, mCurrentNode.getId());
+ }
+ return b;
+ }
+
+ /**
+ * Restores all navigation controller state from a bundle.
+ *
+ * <p>State may be saved to a bundle by calling {@link #saveState()}.
+ * Restoring controller state is the responsibility of a navigation host,
+ * e.g. {@link NavHostFragment}.</p>
+ *
+ * @param navState state bundle to restore
+ */
+ public void restoreState(Bundle navState) {
+ if (navState == null) {
+ return;
+ }
+
+ mGraphId = navState.getInt(KEY_GRAPH_ID);
+ if (mGraphId != 0) {
+ setGraph(mGraphId);
+ }
+ mStartDestId = navState.getInt(KEY_START_DEST_ID);
+
+ // Restore the current location first, or setStartDestination will perform navigation
+ // if mCurrentNode is null.
+ final int loc = navState.getInt(KEY_CUR_DEST_ID);
+ if (loc != 0) {
+ NavDestination node = mGraph.findNode(loc);
+ if (node == null) {
+ throw new IllegalStateException("unknown current destination during restore: "
+ + mContext.getResources().getResourceName(loc));
+ }
+ mCurrentNode = node;
+ }
+ if (mStartDestId != 0) {
+ setStartDestination(mStartDestId);
+ }
+ }
+
+ void onNavigatorBackStackEmpty(Navigator emptyNavigator) {
+ if (mNavigatorBackStack.isEmpty()) {
+ throw new IllegalStateException("Navigator " + emptyNavigator
+ + " reported empty, but this NavController has no back stack!");
+ }
+ // If a navigator's back stack is empty, it can't have any presence in our
+ // navigator stack either. Remove all instances of it.
+ for (int i = mNavigatorBackStack.size() - 1; i >= 0; i--) {
+ final Navigator n = mNavigatorBackStack.get(i);
+ if (n == emptyNavigator) {
+ mNavigatorBackStack.remove(i);
+ }
+ }
+ }
+}
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavDestination.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavDestination.java
new file mode 100644
index 0000000..19d1f5c
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavDestination.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.support.annotation.IdRes;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.util.SparseIntArray;
+
+import com.android.support.navigation.R;
+
+/**
+ * NavDestination represents one node within an overall navigation graph.
+ *
+ * <p>Each destination has a {@link Navigator}. The navigator determines valid
+ * {@link Navigator.Params parameters} that can be {@link #setNavigatorParams(Navigator.Params) set}
+ * for each destination, and how those parameters will be {@link NavInflater inflated} from
+ * a resource.</p>
+ *
+ * <p>Destinations declare a set of {@link #putActionDestination(int, int) actions} that they
+ * support. These actions form a navigation API for the destination; the same actions declared
+ * on different destinations that fill similar roles allow application code to navigate based
+ * on semantic intent.</p>
+ *
+ * <p>Each destination has a set of {@link #getDefaultArguments() default arguments} that will
+ * be applied when {@link NavController#navigate(int, Bundle) navigating} to that destination.
+ * These arguments can be overridden at the time of navigation.</p>
+ */
+public class NavDestination {
+ private int mId;
+ private Navigator mNavigator;
+ private Navigator.Params mNavParams;
+ private Bundle mDefaultArgs;
+ private SparseIntArray mActions;
+ private int mFlowId;
+
+ /**
+ * Called when inflating a destination from a resource.
+ *
+ * @param context local context performing inflation
+ * @param attrs attrs to parse during inflation
+ * @param inflater inflater currently performing the inflation
+ */
+ public void onInflate(Context context, AttributeSet attrs, NavInflater inflater) {
+ final TypedArray a = context.getResources().obtainAttributes(attrs,
+ R.styleable.NavDestination);
+ setId(a.getResourceId(R.styleable.NavDestination_android_id, 0));
+ setNavigator(inflater.getNavigator(a.getString(R.styleable.NavDestination_navigator)));
+ a.recycle();
+
+ setNavigatorParams(mNavigator.inflateParams(context, attrs));
+ }
+
+ /**
+ * Returns the destination's unique ID. This should be an ID resource generated by
+ * the Android resource system.
+ *
+ * @return this destination's ID
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Sets the destination's unique ID. This should be an ID resource generated by
+ * the Android resource system.
+ *
+ * @param id this destination's new ID
+ */
+ public void setId(int id) {
+ mId = id;
+ }
+
+ /**
+ * Sets the destination's {@link Navigator}.
+ *
+ * <p>The {@link #getNavigatorParams() current params} object will be checked for validity
+ * by the new navigator. If the params are no longer valid, the new navigator will be asked
+ * to convert the old params.</p>
+ *
+ * @param navigator navigator to set
+ */
+ public void setNavigator(Navigator navigator) {
+ if (navigator == mNavigator) {
+ return;
+ }
+ mNavigator = navigator;
+ // Apply the navigator params validity checks for the new navigator
+ setNavigatorParams(mNavParams);
+ }
+
+ /**
+ * Returns the destination's {@link Navigator}.
+ *
+ * @return this destination's navigator
+ */
+ public Navigator getNavigator() {
+ return mNavigator;
+ }
+
+ /**
+ * Sets the destination's {@link Navigator.Params}.
+ *
+ * <p>The params object will be checked for validity by the
+ * {@link #getNavigator() current navigator}. If the params are not valid, the navigator
+ * will be asked to convert them.</p>
+ *
+ * @param params params to set
+ */
+ public void setNavigatorParams(Navigator.Params params) {
+ if (mNavigator != null && !mNavigator.checkParams(params)) {
+ Navigator.Params newParams = mNavigator.generateDefaultParams();
+ newParams.copyFrom(params);
+ params = newParams;
+ }
+ mNavParams = params;
+ }
+
+ /**
+ * Returns the destination's {@link Navigator.Params}.
+ *
+ * @return this destination's params
+ */
+ public Navigator.Params getNavigatorParams() {
+ return mNavParams;
+ }
+
+ /**
+ * Returns the destination's default arguments bundle.
+ *
+ * @return the default arguments bundle
+ */
+ public @NonNull Bundle getDefaultArguments() {
+ if (mDefaultArgs == null) {
+ mDefaultArgs = new Bundle();
+ }
+ return mDefaultArgs;
+ }
+
+ /**
+ * Sets the destination's default arguments bundle.
+ *
+ * @param args the new bundle to set
+ */
+ public void setDefaultArguments(Bundle args) {
+ mDefaultArgs = args;
+ }
+
+ /**
+ * Merges a bundle of arguments into the current default arguments for this destination.
+ * New values with the same keys will replace old values with those keys.
+ *
+ * @param args arguments to add
+ */
+ public void addDefaultArguments(Bundle args) {
+ getDefaultArguments().putAll(args);
+ }
+
+ /**
+ * Returns the destination ID for a given action.
+ *
+ * @param id action ID to fetch
+ * @return destination ID mapped to the given action id, or 0 if none
+ */
+ public @IdRes int getActionDestination(@IdRes int id) {
+ if (mActions == null) {
+ return 0;
+ }
+ return mActions.get(id);
+ }
+
+ /**
+ * Sets a destination ID for an action ID.
+ *
+ * @param actionId action ID to bind
+ * @param destId destination ID for the given action
+ */
+ public void putActionDestination(@IdRes int actionId, @IdRes int destId) {
+ if (actionId == 0) {
+ throw new IllegalArgumentException("cannot setActionDestination for actionId 0");
+ }
+ if (mActions == null) {
+ mActions = new SparseIntArray();
+ }
+ mActions.put(actionId, destId);
+ }
+
+ /**
+ * Unsets the destination ID for an action ID.
+ *
+ * @param actionId action ID to remove
+ */
+ public void removeActionDestination(@IdRes int actionId) {
+ if (mActions == null) {
+ return;
+ }
+ mActions.delete(actionId);
+ }
+
+ /**
+ * Navigates to this destination.
+ *
+ * <p>Uses the {@link #getNavigator() configured navigator} to navigate to this destination.
+ * Apps should not call this directly, instead use {@link NavController}'s navigation methods
+ * to ensure consistent back stack tracking and behavior.</p>
+ *
+ * @param args arguments to the new destination
+ * @param navOptions options for navigation
+ */
+ public void navigate(Bundle args, NavOptions navOptions) {
+ Bundle finalArgs = null;
+ Bundle defaultArgs = getDefaultArguments();
+ if (defaultArgs != null) {
+ finalArgs = new Bundle();
+ finalArgs.putAll(defaultArgs);
+ }
+ if (args != null) {
+ if (finalArgs == null) {
+ finalArgs = new Bundle();
+ }
+ finalArgs.putAll(args);
+ }
+ mNavigator.navigate(this, finalArgs, navOptions);
+ }
+}
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavGraph.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavGraph.java
new file mode 100644
index 0000000..bbb4a3f
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavGraph.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+import android.support.annotation.IdRes;
+import android.util.SparseArray;
+
+/**
+ * NavGraph is a collection of {@link NavDestination} nodes fetchable by ID.
+ */
+public class NavGraph {
+ private final SparseArray<NavDestination> mNodes = new SparseArray<>();
+
+ /**
+ * Adds a destination to the collection.
+ *
+ * @param node destination to add
+ */
+ public void addDestination(NavDestination node) {
+ mNodes.put(node.getId(), node);
+ }
+
+ /**
+ * Finds a destination in the collection by ID.
+ *
+ * @param resid ID to locate
+ * @return the node with ID resid
+ */
+ public NavDestination findNode(@IdRes int resid) {
+ return mNodes.get(resid);
+ }
+
+ /**
+ * Add all destinations from another collection to this one.
+ *
+ * @param other collection of destinations to add
+ */
+ public void addAll(NavGraph other) {
+ for (int i = 0, size = other.mNodes.size(); i < size; i++) {
+ mNodes.put(other.mNodes.keyAt(i), other.mNodes.valueAt(i));
+ }
+ }
+}
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavHostFragment.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavHostFragment.java
new file mode 100644
index 0000000..25704fb
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavHostFragment.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.support.annotation.IdRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.XmlRes;
+import android.support.v4.app.Fragment;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.support.navigation.R;
+
+/**
+ * NavHostFragment provides an area within your layout for self-contained navigation to occur.
+ *
+ * <p>NavHostFragment is intended to be used as the content area within a layout resource
+ * defining your app's chrome around it, e.g.:</p>
+ *
+ * <pre class="prettyprint">
+ * <android.support.v4.widget.DrawerLayout
+ * xmlns:android="http://schemas.android.com/apk/res/android"
+ * xmlns:app="http://schemas.android.com/apk/res-auto"
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent">
+ * <fragment
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent"
+ * android:id="@+id/my_nav_host_fragment"
+ * android:name="android.support.navigation.app.nav.NavHostFragment"
+ * app:navGraph="@xml/nav_sample"
+ * app:startDestination="@+id/launcher_home"
+ * app:defaultNavHost="true" />
+ * <android.support.design.widget.NavigationView
+ * android:layout_width="wrap_content"
+ * android:layout_height="match_parent"
+ * android:layout_gravity="start"/>
+ * </android.support.v4.widget.DrawerLayout>
+ * </pre>
+ *
+ * <p>Each NavHostFragment has a {@link NavController} that defines valid navigation within
+ * the navigation host. This includes the {@link NavGraph navigation graph} as well as navigation
+ * state such as current location and back stack that will be saved and restored along with the
+ * NavHostFragment itself.</p>
+ *
+ * <p>NavHostFragments register their navigation controller at the root of their view subtree
+ * such that any descendant can obtain the controller instance through the {@link Navigation}
+ * helper class's methods such as {@link Navigation#findController(View)}. View event listener
+ * implementations such as {@link android.view.View.OnClickListener} within navigation destination
+ * fragments can use these helpers to navigate based on user interaction without creating a tight
+ * coupling to the navigation host.</p>
+ */
+public class NavHostFragment extends Fragment {
+ private static final String KEY_GRAPH_ID = "android-support-nav:fragment:graphId";
+ private static final String KEY_START_DEST_ID = "android-support-nav:fragment:startDestId";
+ private static final String KEY_NAV_CONTROLLER_STATE =
+ "android-support-nav:fragment:navControllerState";
+ private static final String KEY_DEFAULT_NAV_HOST = "android-support-nav:fragment:defaultHost";
+
+ private NavController mNavController;
+
+ // State that will be saved and restored
+ private boolean mDefaultNavHost;
+
+ /**
+ * Create a new NavHostFragment instance with an inflated {@link NavGraph} resource.
+ *
+ * @param graphRes resource id of the navigation graph to inflate
+ * @return a new NavHostFragment instance
+ */
+ public static NavHostFragment create(@XmlRes int graphRes) {
+ return create(graphRes, 0);
+ }
+
+ /**
+ * Create a new NavHostFragment instance with an inflated {@link NavGraph} resource
+ * and a starting destination id.
+ *
+ * @param graphRes resource id of the navigation graph to inflate
+ * @param startDestinationRes id of the initial destination
+ * @return a new NavHostFragment instance
+ */
+ public static NavHostFragment create(@XmlRes int graphRes, @IdRes int startDestinationRes) {
+ Bundle b = null;
+ if (graphRes != 0) {
+ b = new Bundle();
+ b.putInt(KEY_GRAPH_ID, graphRes);
+ }
+ if (startDestinationRes != 0) {
+ if (b == null) {
+ b = new Bundle();
+ }
+ b.putInt(KEY_START_DEST_ID, startDestinationRes);
+ }
+
+ final NavHostFragment result = new NavHostFragment();
+ if (b != null) {
+ result.setArguments(b);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the {@link NavController navigation controller} for this navigation host.
+ * This method will return null until this host fragment's {@link #onCreate(Bundle)}
+ * has been called and it has had an opportunity to restore from a previous instance state.
+ *
+ * @return this host's navigation controller
+ */
+ @Nullable
+ public NavController getNavController() {
+ return mNavController;
+ }
+
+ /**
+ * Set a {@link NavGraph} for this navigation host's {@link NavController} by resource id.
+ * The existing graph will be replaced.
+ *
+ * @param graphRes resource id of the navigation graph to inflate
+ */
+ public void setGraph(@XmlRes int graphRes) {
+ if (mNavController == null) {
+ Bundle args = getArguments();
+ if (args == null) {
+ args = new Bundle();
+ }
+ args.putInt(KEY_GRAPH_ID, graphRes);
+ setArguments(args);
+ } else {
+ mNavController.setGraph(graphRes);
+ }
+ }
+
+ /**
+ * Set a starting destination id for this navigation host.
+ * If this host has not navigated to a destination yet, the host will navigate to the start
+ * destination. The initial navigation to the starting destination is not considered part of the
+ * {@link #getNavController() host controller's} back stack.
+ *
+ * @param destRes id of the initial destination
+ */
+ public void setStartDestination(@IdRes int destRes) {
+ if (mNavController == null) {
+ Bundle args = getArguments();
+ if (args == null) {
+ args = new Bundle();
+ }
+ args.putInt(KEY_START_DEST_ID, destRes);
+ setArguments(args);
+ } else {
+ mNavController.setStartDestination(destRes);
+ }
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ // TODO This feature should probably be a first-class feature of the Fragment system,
+ // but it can stay here until we can add the necessary attr resources to
+ // the fragment lib.
+ if (mDefaultNavHost) {
+ getFragmentManager().beginTransaction().setPrimaryNavigationFragment(this).commit();
+ }
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Context context = getContext();
+ final Activity activity = getActivity();
+
+ if (activity != null) {
+ mNavController = new NavController(activity);
+ } else {
+ mNavController = new NavController(context);
+ }
+ mNavController.addNavigator(FragmentNavigator.NAME,
+ new FragmentNavigator(context, getChildFragmentManager(), getId()));
+
+ Bundle navState = null;
+ if (savedInstanceState != null) {
+ navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
+ if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
+ mDefaultNavHost = true;
+ getFragmentManager().beginTransaction().setPrimaryNavigationFragment(this).commit();
+ }
+ }
+
+ if (navState != null) {
+ // Navigation controller state overrides arguments
+ mNavController.restoreState(navState);
+ } else {
+ final Bundle args = getArguments();
+ final int graphid = args.getInt(KEY_GRAPH_ID);
+ final int destid = args.getInt(KEY_START_DEST_ID);
+ if (graphid != 0) {
+ mNavController.setGraph(graphid);
+ } else {
+ mNavController.addMetadataGraph();
+ }
+ if (destid != 0) {
+ mNavController.setStartDestination(destid);
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ return new FrameLayout(inflater.getContext());
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (!(view instanceof ViewGroup)) {
+ throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
+ }
+ Navigation.setViewNavController(view, mNavController);
+ }
+
+ @Override
+ public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {
+ super.onInflate(context, attrs, savedInstanceState);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
+ final int graphid = a.getResourceId(R.styleable.NavHostFragment_navGraph, 0);
+ final int destid = a.getResourceId(R.styleable.NavHostFragment_startDestination, 0);
+ final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
+
+ if (graphid != 0) {
+ setGraph(graphid);
+ }
+ if (destid != 0) {
+ setStartDestination(destid);
+ }
+ if (defaultHost) {
+ mDefaultNavHost = true;
+ if (isAdded()) {
+ getFragmentManager().beginTransaction().setPrimaryNavigationFragment(this).commit();
+ }
+ }
+ a.recycle();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ Bundle navState = mNavController.saveState();
+ if (navState != null) {
+ outState.putBundle(KEY_NAV_CONTROLLER_STATE, navState);
+ }
+ if (mDefaultNavHost) {
+ outState.putBoolean(KEY_DEFAULT_NAV_HOST, true);
+ }
+ }
+}
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavInflater.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavInflater.java
new file mode 100644
index 0000000..2bc674a
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavInflater.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.support.annotation.XmlRes;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.util.Xml;
+
+import com.android.support.navigation.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Class which translates a navigation XML file into a {@link NavGraph}
+ */
+public class NavInflater {
+ private static final String TAG_ROOT = "navigation";
+ private static final String TAG_DESTINATION = "destination";
+ private static final String TAG_ARGUMENT = "default-argument";
+ private static final String TAG_ACTION = "action";
+
+ private static final ThreadLocal<TypedValue> sTmpValue = new ThreadLocal<>();
+
+ private Context mContext;
+ private NavigatorProvider mNavigatorProvider;
+
+ public NavInflater(Context c, NavigatorProvider navigatorProvider) {
+ mContext = c;
+ mNavigatorProvider = navigatorProvider;
+ }
+
+ /**
+ * Retrieve a Navigator with the given name from the {@link NavigatorProvider} used to
+ * construct this class.
+ *
+ * @param name
+ * @return
+ */
+ public Navigator getNavigator(String name) {
+ return mNavigatorProvider.getNavigator(name);
+ }
+
+ /**
+ * Inflate a NavGraph from the given XML resource id.
+ *
+ * @param navres
+ * @return
+ */
+ public NavGraph inflate(@XmlRes int navres) {
+ Resources res = mContext.getResources();
+ XmlResourceParser parser = res.getXml(navres);
+ NavGraph graph = new NavGraph();
+
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ try {
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Empty loop
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ if (!TAG_ROOT.equals(parser.getName())) {
+ throw new XmlPullParserException("Expected root element <" + TAG_ROOT + ">");
+ }
+
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (TAG_DESTINATION.equals(parser.getName())) {
+ final NavDestination dest;
+ try {
+ dest = inflateDestination(res, parser, attrs);
+ } catch (Exception e) {
+ throw new RuntimeException("Exception inflating "
+ + res.getResourceName(navres) + " line "
+ + parser.getLineNumber(), e);
+ }
+ graph.addDestination(dest);
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ parser.close();
+ }
+ return graph;
+ }
+
+ private NavDestination inflateDestination(Resources res, XmlResourceParser parser,
+ AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+ final NavDestination dest = new NavDestination();
+
+ dest.onInflate(mContext, attrs, this);
+
+ final int innerDepth = parser.getDepth() + 1;
+ int type;
+ int depth;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth
+ || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ if (depth > innerDepth) {
+ continue;
+ }
+
+ final String name = parser.getName();
+ if (TAG_ARGUMENT.equals(name)) {
+ inflateArgument(res, dest, attrs);
+ } else if (TAG_ACTION.equals(name)) {
+ inflateAction(res, dest, attrs);
+ }
+ }
+
+ return dest;
+ }
+
+ private void inflateArgument(Resources res, NavDestination dest, AttributeSet attrs)
+ throws XmlPullParserException {
+ final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavArgument);
+ String name = a.getString(R.styleable.NavArgument_argname);
+
+ TypedValue value = sTmpValue.get();
+ if (value == null) {
+ value = new TypedValue();
+ sTmpValue.set(value);
+ }
+ a.getValue(R.styleable.NavArgument_argvalue, value);
+ switch (value.type) {
+ case TypedValue.TYPE_STRING:
+ dest.getDefaultArguments().putString(name, value.string.toString());
+ break;
+ case TypedValue.TYPE_DIMENSION:
+ dest.getDefaultArguments().putInt(name,
+ (int) value.getDimension(res.getDisplayMetrics()));
+ break;
+ case TypedValue.TYPE_FLOAT:
+ dest.getDefaultArguments().putFloat(name, value.getFloat());
+ break;
+ case TypedValue.TYPE_REFERENCE:
+ dest.getDefaultArguments().putInt(name, value.data);
+ break;
+ default:
+ if (value.type >= TypedValue.TYPE_FIRST_INT
+ && value.type <= TypedValue.TYPE_LAST_INT) {
+ dest.getDefaultArguments().putInt(name, value.data);
+ } else {
+ throw new XmlPullParserException("unsupported argument type " + value.type);
+ }
+ }
+ a.recycle();
+ }
+
+ private void inflateAction(Resources res, NavDestination dest, AttributeSet attrs) {
+ final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavAction);
+ final int id = a.getResourceId(R.styleable.NavAction_android_id, 0);
+ final int destId = a.getResourceId(R.styleable.NavAction_destination, 0);
+ dest.putActionDestination(id, destId);
+ a.recycle();
+ }
+}
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavOptions.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavOptions.java
new file mode 100644
index 0000000..aa21fd2
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavOptions.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+import android.os.Bundle;
+
+/**
+ * NavOptions stores special options for navigate actions
+ */
+public class NavOptions {
+ static final int LAUNCH_SINGLE_TOP = 0x1;
+ static final int LAUNCH_DOCUMENT = 0x2;
+ static final int LAUNCH_CLEAR_TASK = 0x4;
+
+ private static final String KEY_LAUNCH_MODE = "launchMode";
+ private static final String KEY_FLOW_NAME = "flowName";
+
+ private int mLaunchMode;
+ private String mFlowName;
+
+ NavOptions(int launchMode, String flowName) {
+ mLaunchMode = launchMode;
+ mFlowName = flowName;
+ }
+
+ boolean shouldLaunchSingleTop() {
+ return (mLaunchMode & LAUNCH_SINGLE_TOP) != 0;
+ }
+
+ boolean shouldLaunchDocument() {
+ return (mLaunchMode & LAUNCH_DOCUMENT) != 0;
+ }
+
+ boolean shouldClearTask() {
+ return (mLaunchMode & LAUNCH_CLEAR_TASK) != 0;
+ }
+
+ public String getFlowName() {
+ return mFlowName;
+ }
+
+ Bundle toBundle() {
+ return new Bundle();
+ }
+
+ static NavOptions fromBundle(Bundle b) {
+ return new NavOptions(b.getInt(KEY_LAUNCH_MODE, 0),
+ b.getString(KEY_FLOW_NAME));
+ }
+
+ /**
+ * Builder for constructing new instances of NavOptions.
+ */
+ public static class Builder {
+ int mLaunchMode;
+ String mFlowName;
+ String mEndFlow;
+
+ public Builder() {
+ }
+
+ /**
+ * Launch a navigation target as single-top if you are making a lateral navigation
+ * between instances of the same target (e.g. detail pages about similar data items)
+ * that should not preserve history.
+ *
+ * @param singleTop true to launch as single-top
+ */
+ public Builder setLaunchSingleTop(boolean singleTop) {
+ if (singleTop) {
+ mLaunchMode |= LAUNCH_SINGLE_TOP;
+ } else {
+ mLaunchMode &= ~LAUNCH_SINGLE_TOP;
+ }
+ return this;
+ }
+
+ /**
+ * Launch a navigation target as a document if you want it to appear as its own
+ * entry in the system Overview screen. If the same document is launched multiple times
+ * it will not create a new task, it will bring the existing document task to the front.
+ *
+ * <p>If the user presses the system Back key from a new document task they will land
+ * on their previous task. If the user reached the document task from the system Overview
+ * screen they will be taken to their home screen.</p>
+ *
+ * @param launchDocument true to launch a new document task
+ */
+ public Builder setLaunchDocument(boolean launchDocument) {
+ if (launchDocument) {
+ mLaunchMode |= LAUNCH_DOCUMENT;
+ } else {
+ mLaunchMode &= ~LAUNCH_DOCUMENT;
+ }
+ return this;
+ }
+
+ /**
+ * Clear the entire task before launching this target. If you are launching as a
+ * {@link #setLaunchDocument(boolean) document}, this will clear the document task.
+ * Otherwise it will clear the current task.
+ *
+ * @param clearTask
+ * @return
+ */
+ public Builder setClearTask(boolean clearTask) {
+ if (clearTask) {
+ mLaunchMode |= LAUNCH_CLEAR_TASK;
+ } else {
+ mLaunchMode &= ~LAUNCH_CLEAR_TASK;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the flow name for this target.
+ * @param flowName
+ * @return
+ */
+ public Builder setFlowName(String flowName) {
+ mFlowName = flowName;
+ return this;
+ }
+
+ /**
+ * @return a constructed NavOptions
+ */
+ public NavOptions build() {
+ return new NavOptions(mLaunchMode, mFlowName);
+ }
+ }
+}
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/Navigation.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/Navigation.java
new file mode 100644
index 0000000..adfb846
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/Navigation.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+import android.os.Bundle;
+import android.support.annotation.IdRes;
+import android.support.v4.app.Fragment;
+import android.view.View;
+import android.view.ViewParent;
+
+import com.android.support.navigation.R;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Entry point for navigation operations.
+ *
+ * <p>This class provides utilities for finding a relevant {@link NavController} instance from
+ * various common places in your application, or for performing navigation in response to
+ * UI events.</p>
+ */
+public class Navigation {
+ // No instances. Static utilities only.
+ private Navigation() {
+ }
+
+ /**
+ * Find a {@link NavController} given a local {@link Fragment}.
+ *
+ * <p>This method will locate the {@link NavController} associated with this Fragment,
+ * looking first for a {@link NavHostFragment} along the given Fragment's parent chain.
+ * If a {@link NavController} is not found, this method will look for one along this
+ * Fragment's {@link Fragment#getView() view hierarchy} as specified by
+ * {@link #findController(View)}.</p>
+ *
+ * @param fragment the locally scoped Fragment for navigation
+ * @return the locally scoped {@link NavController} for navigating from this {@link Fragment}
+ */
+ public static NavController findController(Fragment fragment) {
+ if (fragment == null) {
+ return null;
+ }
+
+ Fragment findFragment = fragment;
+ while (findFragment != null) {
+ if (findFragment instanceof NavHostFragment) {
+ return ((NavHostFragment) findFragment).getNavController();
+ }
+ findFragment = findFragment.getParentFragment();
+ }
+
+ // Try looking for one associated with the view instead, if applicable
+ return findController(fragment.getView());
+ }
+
+ /**
+ * Find a {@link NavController} given a local {@link View}.
+ *
+ * <p>This method will locate the {@link NavController} associated with this view.
+ * This is automatically populated for views that are managed by a {@link NavHostFragment}
+ * and is intended for use by various {@link android.view.View.OnClickListener listener}
+ * interfaces.</p>
+ *
+ * @param view the view to search from
+ * @return the locally scoped {@link NavController} to the given view
+ */
+ public static NavController findController(View view) {
+ if (view == null) {
+ return null;
+ }
+ while (view != null) {
+ NavController controller = getViewNavController(view);
+ if (controller != null) {
+ return controller;
+ }
+ ViewParent parent = view.getParent();
+ view = parent instanceof View ? (View) parent : null;
+ }
+ return null;
+ }
+
+ /**
+ * Create an {@link android.view.View.OnClickListener} for navigating via an action.
+ *
+ * @param actionId navigation action to take when the view is clicked
+ * @return a new click listener for setting on an arbitrary view
+ */
+ public static View.OnClickListener createNavigateOnClickListener(@IdRes final int actionId) {
+ return createNavigateOnClickListener(actionId, null);
+ }
+
+ /**
+ * Create an {@link android.view.View.OnClickListener} for navigating via an action.
+ *
+ * @param actionId navigation action to take when the view is clicked
+ * @param args arguments to pass to the final destination
+ * @return a new click listener for setting on an arbitrary view
+ */
+ public static View.OnClickListener createNavigateOnClickListener(@IdRes final int actionId,
+ final Bundle args) {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ final NavController controller = findController(view);
+ if (controller != null) {
+ controller.navigate(actionId, args);
+ } else {
+ throw new IllegalStateException(
+ "OnClickListener#onClick could not find NavController for view "
+ + view);
+ }
+ }
+ };
+ }
+
+ /**
+ * Create an {@link android.view.View.OnClickListener} for navigating
+ * to an explicit destination.
+ *
+ * @param destId destination to navigate to when the view is clicked
+ * @return a new click listener for setting on an arbitrary view
+ */
+ public static View.OnClickListener createNavigateToOnClickListener(@IdRes final int destId) {
+ return createNavigateToOnClickListener(destId, null);
+ }
+
+ /**
+ * Create an {@link android.view.View.OnClickListener} for navigating
+ * to an explicit destination.
+ *
+ * @param destId destination to navigate to when the view is clicked
+ * @param args arguments to pass to the final destination
+ * @return a new click listener for setting on an arbitrary view
+ */
+ public static View.OnClickListener createNavigateToOnClickListener(@IdRes final int destId,
+ final Bundle args) {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ final NavController controller = findController(view);
+ if (controller != null) {
+ controller.navigateTo(destId, args);
+ } else {
+ throw new IllegalStateException(
+ "OnClickListener#onClick could not find NavController for view "
+ + view);
+ }
+ }
+ };
+ }
+
+ static void setViewNavController(View view, NavController controller) {
+ view.setTag(R.id.nav_controller_view_tag, controller);
+ }
+
+ static NavController getViewNavController(View view) {
+ Object tag = view.getTag(R.id.nav_controller_view_tag);
+ NavController controller = null;
+ if (tag instanceof WeakReference) {
+ controller = ((WeakReference<NavController>) tag).get();
+ } else if (tag instanceof NavController) {
+ controller = (NavController) tag;
+ }
+ return controller;
+ }
+}
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/Navigator.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/Navigator.java
new file mode 100644
index 0000000..56cfb5a
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/Navigator.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.IdRes;
+import android.util.AttributeSet;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Navigator defines a mechanism for navigating within an app.
+ *
+ * <p>Each Navigator sets the policy for a specific type of navigation, e.g.
+ * {@link ActivityNavigator} knows how to launch into {@link NavDestination destinations}
+ * backed by activities using {@link Context#startActivity(Intent) startActivity} and
+ * {@link FragmentNavigator} knows how to navigate by replacing fragments within a container.</p>
+ *
+ * <p>Navigators should be able to manage their own back stack when navigating between two
+ * destinations that belong to that navigator. The {@link NavController} manages a back stack of
+ * navigators representing the current navigation stack across all navigators.</p>
+ *
+ * @param <P> the subclass of {@link Params} unique to the Navigator subclass
+ */
+public abstract class Navigator<P extends Navigator.Params> {
+ private final CopyOnWriteArrayList<OnNavigatorNavigatedListener> mOnNavigatedListeners =
+ new CopyOnWriteArrayList<>();
+
+ /**
+ * Create and return a {@link Params navigator params} object with default values
+ * for this navigator.
+ * @return a new {@link Params} with default values
+ */
+ public abstract P generateDefaultParams();
+
+ /**
+ * Inflate a {@link Params} object from a resource.
+ *
+ * <p>Parses the navigator params from a {@link NavDestination destination} node
+ * of a navigation graph resource. Navigator param attributes should have the prefix
+ * {@code nav_}.</p>
+ *
+ * @param context Context used to resolve attrs
+ * @param attrs attrs to parse
+ * @return a new {@link Params} instance parsed from attrs
+ */
+ public abstract P inflateParams(Context context, AttributeSet attrs);
+
+ /**
+ * Check if a {@link Params} object is valid for this navigator.
+ *
+ * <p>Returns {@code true} if the given params object is of the right type and
+ * properties are in range. If this method returns false, callers may use
+ * {@link #generateDefaultParams()} to obtain valid params instead.</p>
+ *
+ * @param params params to check for validity
+ * @return {@code true} if the given params are valid
+ */
+ public boolean checkParams(Navigator.Params params) {
+ return true;
+ }
+
+ /**
+ * Navigate to a destination.
+ *
+ * <p>Requests navigation to a given destination associated with this navigator in
+ * the navigation graph. This method generally should not be called directly;
+ * {@link NavController} will delegate to it when appropriate.</p>
+ *
+ * @param destination destination node to navigate to
+ * @param args arguments to use for navigation
+ * @param navOptions additional options for navigation
+ * @return true if navigation created a back stack entry that should be tracked
+ */
+ public abstract boolean navigate(NavDestination destination, Bundle args,
+ NavOptions navOptions);
+
+ /**
+ * Attempt to pop this navigator's back stack, performing the appropriate navigation.
+ *
+ * <p>Implementations should {@link #dispatchOnNavigatorNavigated(int, boolean)} to notify
+ * listeners of the resulting navigation destination and return {@link true} if navigation
+ * was successful. Implementations should return {@code false} if navigation could not
+ * be performed, for example if the navigator's back stack was empty.</p>
+ *
+ * @return {@code true} if pop was successful
+ */
+ public abstract boolean popBackStack();
+
+ /**
+ * Add a listener to be notified when this navigator changes navigation destinations.
+ *
+ * <p>Most application code should use
+ * {@link NavController#addOnNavigatedListener(NavController.OnNavigatedListener)} instead.
+ * </p>
+ *
+ * @param listener listener to add
+ */
+ public final void addOnNavigatorNavigatedListener(OnNavigatorNavigatedListener listener) {
+ mOnNavigatedListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener so that it will no longer be notified when this navigator changes
+ * navigation destinations.
+ *
+ * @param listener listener to remove
+ */
+ public final void removeOnNavigatorNavigatedListener(OnNavigatorNavigatedListener listener) {
+ mOnNavigatedListeners.remove(listener);
+ }
+
+ /**
+ * Dispatch a navigated event to all registered {@link OnNavigatorNavigatedListener listeners}.
+ * Utility for navigator implementations.
+ *
+ * @param destId id of the new destination
+ * @param isBackStackEmpty true if this navigator's back stack is empty after this navigation
+ */
+ public final void dispatchOnNavigatorNavigated(@IdRes int destId, boolean isBackStackEmpty) {
+ for (OnNavigatorNavigatedListener listener : mOnNavigatedListeners) {
+ listener.onNavigatorNavigated(this, destId, isBackStackEmpty);
+ }
+ }
+
+ /**
+ * Base class for navigator parameters.
+ *
+ * <p>Subclasses of {@link Navigator} should also subclass this class to hold any special
+ * data about a {@link NavDestination} that will be needed to navigate to that destination.
+ * Examples include information about an intent to navigate to other activities, or a fragment
+ * class name to instantiate and swap to a new fragment.</p>
+ */
+ public static class Params {
+ /**
+ * Copy all valid fields from the given Params into this instance
+ * @param other the Params to copy all fields from
+ */
+ public void copyFrom(Params other) {
+ }
+ }
+
+ /**
+ * Listener for observing navigation events for this specific navigator. Most app code
+ * should use {@link NavController.OnNavigatedListener} instead.
+ */
+ public interface OnNavigatorNavigatedListener {
+ /**
+ * This method is called after the Navigator navigates to a new destination.
+ *
+ * @param navigator
+ * @param destId
+ * @param isBackStackEmpty
+ */
+ void onNavigatorNavigated(Navigator navigator, @IdRes int destId, boolean isBackStackEmpty);
+ }
+}
diff --git a/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavigatorProvider.java b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavigatorProvider.java
new file mode 100644
index 0000000..1ae1e2a
--- /dev/null
+++ b/navigation/runtime/src/main/java/android/support/navigation/app/nav/NavigatorProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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 android.support.navigation.app.nav;
+
+/**
+ * A NavigationProvider stores a set of {@link Navigator}s that are valid ways to navigate
+ * to a destination.
+ */
+public interface NavigatorProvider {
+ /**
+ * Retrieves a registered {@link Navigator} by name.
+ *
+ * @param name name of the navigator to return
+ * @return the registered navigator with the given name
+ *
+ * @see #addNavigator(String, Navigator)
+ */
+ Navigator getNavigator(String name);
+
+ /**
+ * Register a navigator by name. {@link NavDestination destinations} may refer to any
+ * registered navigator by name for inflation. If a navigator by this name is already
+ * registered, this new navigator will replace it.
+ *
+ * @param name name for this navigator
+ * @param navigator navigator to add
+ */
+ void addNavigator(String name, Navigator navigator);
+}
diff --git a/navigation/runtime/src/main/res/values/attrs.xml b/navigation/runtime/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..b047e13d
--- /dev/null
+++ b/navigation/runtime/src/main/res/values/attrs.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<resources>
+ <declare-styleable name="NavDestination">
+ <attr name="android:id"/>
+ <attr name="navigator" format="string"/>
+ </declare-styleable>
+
+ <declare-styleable name="FragmentNavigatorParams">
+ <attr name="nav_fragment" format="string" />
+ <attr name="nav_flow" format="string" />
+ </declare-styleable>
+
+ <declare-styleable name="NavArgument">
+ <attr name="argname" format="string" />
+ <attr name="argvalue" format="string|dimension|integer|float|reference" />
+ </declare-styleable>
+
+ <declare-styleable name="NavAction">
+ <attr name="android:id" />
+ <attr name="destination" format="reference" />
+ </declare-styleable>
+
+ <declare-styleable name="NavHostFragment">
+ <attr name="navGraph" format="reference" />
+ <attr name="startDestination" format="reference" />
+ <attr name="defaultNavHost" format="boolean" />
+ </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/navigation/runtime/src/main/res/values/ids.xml b/navigation/runtime/src/main/res/values/ids.xml
new file mode 100644
index 0000000..d1571a9
--- /dev/null
+++ b/navigation/runtime/src/main/res/values/ids.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<resources>
+ <item type="id" name="nav_host_fragment_host_container" />
+ <item type="id" name="nav_controller_view_tag" />
+</resources>
\ No newline at end of file