Implementing breadcrumb for tablet devices for navigation in the bar.

Bug: 29214736
Change-Id: I37de2c39a55c33d7111496e896a0c85e1c7fa553
(cherry picked from commit 6fb1dfcc47b795b7071550a4524667d2f8f4d9a2)
diff --git a/packages/DocumentsUI/res/layout/drawer_layout.xml b/packages/DocumentsUI/res/layout/drawer_layout.xml
index f7824c1..4898f13 100644
--- a/packages/DocumentsUI/res/layout/drawer_layout.xml
+++ b/packages/DocumentsUI/res/layout/drawer_layout.xml
@@ -41,8 +41,8 @@
                 android:theme="?actionBarTheme"
                 android:popupTheme="?actionBarPopupTheme">
 
-                <Spinner
-                    android:id="@+id/stack"
+                <com.android.documentsui.DropdownBreadcrumb
+                    android:id="@+id/breadcrumb"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_marginStart="4dp"
diff --git a/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml
index deb0894..2ea9366 100644
--- a/packages/DocumentsUI/res/layout/fixed_layout.xml
+++ b/packages/DocumentsUI/res/layout/fixed_layout.xml
@@ -39,14 +39,10 @@
             android:theme="?actionBarTheme"
             android:popupTheme="?actionBarPopupTheme">
 
-            <Spinner
-                android:id="@+id/stack"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginStart="4dp"
-                android:popupTheme="?actionBarPopupTheme"
-                android:background="@android:color/transparent"
-                android:overlapAnchor="true" />
+            <com.android.documentsui.HorizontalBreadcrumb
+                android:id="@+id/breadcrumb"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
 
         </com.android.documentsui.DocumentsToolbar>
 
diff --git a/packages/DocumentsUI/res/layout/navigation_breadcrumb_item.xml b/packages/DocumentsUI/res/layout/navigation_breadcrumb_item.xml
new file mode 100644
index 0000000..ab9d3b2
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/navigation_breadcrumb_item.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+
+<!--
+     CoordinatorLayout is necessary for various components (e.g. Snackbars, and
+     floating action buttons) to operate correctly.
+-->
+<!--
+     focusableInTouchMode is set in order to force key events to go to the activity's global key
+     callback, which is necessary for proper event routing. See BaseActivity.onKeyDown.
+-->
+
+<LinearLayout
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="wrap_content"
+  android:layout_height="match_parent"
+  android:layout_alignParentTop="true"
+  android:gravity="center_vertical"
+  android:orientation="horizontal">
+
+    <TextView
+        android:id="@+id/breadcrumb_text"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center_vertical"
+        android:textAppearance="@android:style/TextAppearance.Material.Widget.ActionBar.Title" />
+
+    <ImageView
+        android:id="@+id/breadcrumb_arrow"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/ic_breadcrumb_arrow"
+        android:layout_marginTop="2dp"
+        android:layout_marginRight="3dp"
+        android:layout_marginLeft="3dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 6f17b54..7e1c537 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -48,9 +48,9 @@
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.widget.Spinner;
 
 import com.android.documentsui.MenuManager.DirectoryDetails;
+import com.android.documentsui.NavigationViewManager.Breadcrumb;
 import com.android.documentsui.SearchViewManager.SearchManagerListener;
 import com.android.documentsui.State.ViewMode;
 import com.android.documentsui.dirlist.AnimationView;
@@ -69,7 +69,7 @@
 import java.util.concurrent.Executor;
 
 public abstract class BaseActivity extends Activity
-        implements SearchManagerListener, NavigationView.Environment {
+        implements SearchManagerListener, NavigationViewManager.Environment {
 
     private static final String BENCHMARK_TESTING_PACKAGE = "com.android.documentsui.appperftests";
 
@@ -78,7 +78,7 @@
     RootsCache mRoots;
     SearchViewManager mSearchManager;
     DrawerController mDrawer;
-    NavigationView mNavigator;
+    NavigationViewManager mNavigator;
     List<EventListener> mEventListeners = new ArrayList<>();
 
     private final String mTag;
@@ -141,13 +141,9 @@
         mSearchManager = new SearchViewManager(this, icicle);
 
         DocumentsToolbar toolbar = (DocumentsToolbar) findViewById(R.id.toolbar);
+        Breadcrumb breadcrumb = (Breadcrumb) findViewById(R.id.breadcrumb);
         setActionBar(toolbar);
-        mNavigator = new NavigationView(
-                mDrawer,
-                toolbar,
-                (Spinner) findViewById(R.id.stack),
-                mState,
-                this);
+        mNavigator = new NavigationViewManager(mDrawer, toolbar, mState, this, breadcrumb);
 
         // Base classes must update result in their onCreate.
         setResult(Activity.RESULT_CANCELED);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DropdownBreadcrumb.java b/packages/DocumentsUI/src/com/android/documentsui/DropdownBreadcrumb.java
new file mode 100644
index 0000000..71d7334
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/DropdownBreadcrumb.java
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+
+package com.android.documentsui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.android.documentsui.NavigationViewManager.Breadcrumb;
+import com.android.documentsui.NavigationViewManager.Environment;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.RootInfo;
+
+import java.util.function.Consumer;
+
+/**
+ * Dropdown implementation of breadcrumb used for phone device layouts
+ */
+
+public final class DropdownBreadcrumb extends Spinner implements Breadcrumb {
+
+    private DropdownAdapter mAdapter;
+
+    public DropdownBreadcrumb(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public DropdownBreadcrumb(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public DropdownBreadcrumb(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public DropdownBreadcrumb(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void setup(Environment env, State state, Consumer<Integer> listener) {
+        mAdapter = new DropdownAdapter(state, env);
+        setOnItemSelectedListener(
+                new OnItemSelectedListener() {
+                    @Override
+                    public void onItemSelected(
+                            AdapterView<?> parent, View view, int position, long id) {
+                        listener.accept(position);
+                    }
+
+                    @Override
+                    public void onNothingSelected(AdapterView<?> parent) {}
+                });
+    }
+
+    @Override
+    public void show(boolean visibility) {
+        if (visibility) {
+            setVisibility(VISIBLE);
+            setAdapter(mAdapter);
+        } else {
+            setVisibility(GONE);
+            setAdapter(null);
+        }
+    }
+
+    @Override
+    public void postUpdate() {
+        setSelection(mAdapter.getCount() - 1, false);
+    }
+
+    private static final class DropdownAdapter extends BaseAdapter {
+        private Environment mEnv;
+        private State mState;
+
+        public DropdownAdapter(State state, Environment env) {
+            mState = state;
+            mEnv = env;
+        }
+
+        @Override
+        public int getCount() {
+            return mState.stack.size();
+        }
+
+        @Override
+        public DocumentInfo getItem(int position) {
+            return mState.stack.get(mState.stack.size() - position - 1);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.item_subdir_title, parent, false);
+            }
+
+            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+            final DocumentInfo doc = getItem(position);
+
+            if (position == 0) {
+                final RootInfo root = mEnv.getCurrentRoot();
+                title.setText(root.title);
+            } else {
+                title.setText(doc.displayName);
+            }
+
+            return convertView;
+        }
+
+        @Override
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.item_subdir, parent, false);
+            }
+
+            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+            final DocumentInfo doc = getItem(position);
+
+            if (position == 0) {
+                final RootInfo root = mEnv.getCurrentRoot();
+                title.setText(root.title);
+            } else {
+                title.setText(doc.displayName);
+            }
+
+            return convertView;
+        }
+    }
+
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java b/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java
new file mode 100644
index 0000000..9f6b79b
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/HorizontalBreadcrumb.java
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+
+package com.android.documentsui;
+
+import android.content.Context;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.documentsui.NavigationViewManager.Breadcrumb;
+import com.android.documentsui.NavigationViewManager.Environment;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.RootInfo;
+
+import java.util.function.Consumer;
+
+/**
+ * Horizontal implementation of breadcrumb used for tablet / desktop device layouts
+ */
+public final class HorizontalBreadcrumb extends RecyclerView implements Breadcrumb {
+
+    private LinearLayoutManager mLayoutManager;
+    private BreadcrumbAdapter mAdapter;
+    private Consumer<Integer> mListener;
+
+    public HorizontalBreadcrumb(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public HorizontalBreadcrumb(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public HorizontalBreadcrumb(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void setup(Environment env,
+            com.android.documentsui.State state,
+            Consumer<Integer> listener) {
+
+        mListener = listener;
+        mLayoutManager = new LinearLayoutManager(
+                getContext(), LinearLayoutManager.HORIZONTAL, false);
+        mAdapter = new BreadcrumbAdapter(state, env);
+
+        setLayoutManager(mLayoutManager);
+        addOnItemTouchListener(new ClickListener(getContext(), this::onSingleTapUp));
+    }
+
+    @Override
+    public void show(boolean visibility) {
+        if (visibility) {
+            setVisibility(VISIBLE);
+            setAdapter(mAdapter);
+            mLayoutManager.scrollToPosition(mAdapter.getItemCount() - 1);
+        } else {
+            setVisibility(GONE);
+            setAdapter(null);
+        }
+    }
+
+    @Override
+    public void postUpdate() {
+    }
+
+    private void onSingleTapUp(MotionEvent e) {
+        View itemView = findChildViewUnder(e.getX(), e.getY());
+        int pos = getChildAdapterPosition(itemView);
+        if (pos != mAdapter.getItemCount() - 1) {
+            mListener.accept(pos);
+        }
+    }
+
+    private static final class BreadcrumbAdapter
+            extends RecyclerView.Adapter<BreadcrumbHolder> {
+
+        private Environment mEnv;
+        private com.android.documentsui.State mState;
+
+        public BreadcrumbAdapter(com.android.documentsui.State state, Environment env) {
+            mState = state;
+            mEnv = env;
+        }
+
+        @Override
+        public BreadcrumbHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            View v = LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.navigation_breadcrumb_item, null);
+            return new BreadcrumbHolder(v);
+        }
+
+        @Override
+        public void onBindViewHolder(BreadcrumbHolder holder, int position) {
+            final DocumentInfo doc = getItem(position);
+
+            if (position == 0) {
+                final RootInfo root = mEnv.getCurrentRoot();
+                holder.title.setText(root.title);
+            } else {
+                holder.title.setText(doc.displayName);
+            }
+
+            if (position == getItemCount() - 1) {
+                holder.arrow.setVisibility(View.GONE);
+            }
+        }
+
+        private DocumentInfo getItem(int position) {
+            return mState.stack.get(mState.stack.size() - position - 1);
+        }
+
+        @Override
+        public int getItemCount() {
+            return mState.stack.size();
+        }
+    }
+
+    private static class BreadcrumbHolder extends RecyclerView.ViewHolder {
+
+        protected TextView title;
+        protected ImageView arrow;
+
+        public BreadcrumbHolder(View itemView) {
+            super(itemView);
+            title = (TextView) itemView.findViewById(R.id.breadcrumb_text);
+            arrow = (ImageView) itemView.findViewById(R.id.breadcrumb_arrow);
+        }
+    }
+
+    private static final class ClickListener extends GestureDetector
+            implements OnItemTouchListener {
+
+        public ClickListener(Context context, Consumer<MotionEvent> listener) {
+            super(context, new SimpleOnGestureListener() {
+                @Override
+                public boolean onSingleTapUp(MotionEvent e) {
+                    listener.accept(e);
+                    return true;
+                }
+            });
+        }
+
+        @Override
+        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+            onTouchEvent(e);
+            return false;
+        }
+
+        @Override
+        public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+            onTouchEvent(e);
+        }
+
+        @Override
+        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        }
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java b/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
deleted file mode 100644
index 3373c23..0000000
--- a/packages/DocumentsUI/src/com/android/documentsui/NavigationView.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.documentsui;
-
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-import static com.android.documentsui.Shared.DEBUG;
-
-import android.annotation.Nullable;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.BaseAdapter;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import com.android.documentsui.dirlist.AnimationView;
-import com.android.documentsui.model.DocumentInfo;
-import com.android.documentsui.model.RootInfo;
-
-/**
- * A facade over the portions of the app and drawer toolbars.
- */
-class NavigationView {
-
-    private static final String TAG = "NavigationView";
-
-    private final DrawerController mDrawer;
-    private final DocumentsToolbar mToolbar;
-    private final Spinner mBreadcrumb;
-    private final State mState;
-    private final NavigationView.Environment mEnv;
-    private final BreadcrumbAdapter mBreadcrumbAdapter;
-
-    private boolean mIgnoreNextNavigation;
-
-    public NavigationView(
-            DrawerController drawer,
-            DocumentsToolbar toolbar,
-            Spinner breadcrumb,
-            State state,
-            NavigationView.Environment env) {
-
-        mToolbar = toolbar;
-        mBreadcrumb = breadcrumb;
-        mDrawer = drawer;
-        mState = state;
-        mEnv = env;
-
-        mBreadcrumbAdapter = new BreadcrumbAdapter(mState, mEnv);
-        mToolbar.setNavigationOnClickListener(
-                new View.OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        onNavigationIconClicked();
-                    }
-
-                });
-
-        mBreadcrumb.setOnItemSelectedListener(
-                new OnItemSelectedListener() {
-                    @Override
-                    public void onItemSelected(
-                            AdapterView<?> parent, View view, int position, long id) {
-                        onBreadcrumbItemSelected(position);
-                    }
-
-                    @Override
-                    public void onNothingSelected(AdapterView<?> parent) {}
-                });
-
-    }
-
-    private void onNavigationIconClicked() {
-        if (mDrawer.isPresent()) {
-            mDrawer.setOpen(true, DrawerController.OPENED_HAMBURGER);
-        }
-    }
-
-    private void onBreadcrumbItemSelected(int position) {
-        if (mIgnoreNextNavigation) {
-            mIgnoreNextNavigation = false;
-            return;
-        }
-
-        while (mState.stack.size() > position + 1) {
-            mState.popDocument();
-        }
-        mEnv.refreshCurrentRootAndDirectory(AnimationView.ANIM_LEAVE);
-    }
-
-    void update() {
-
-        // TODO: Looks to me like this block is never getting hit.
-        if (mEnv.isSearchExpanded()) {
-            mToolbar.setTitle(null);
-            mBreadcrumb.setVisibility(View.GONE);
-            mBreadcrumb.setAdapter(null);
-            return;
-        }
-
-        mDrawer.setTitle(mEnv.getDrawerTitle());
-
-        mToolbar.setNavigationIcon(getActionBarIcon());
-        mToolbar.setNavigationContentDescription(R.string.drawer_open);
-
-        if (mState.stack.size() <= 1) {
-            showBreadcrumb(false);
-            String title = mEnv.getCurrentRoot().title;
-            if (DEBUG) Log.d(TAG, "New toolbar title is: " + title);
-            mToolbar.setTitle(title);
-        } else {
-            showBreadcrumb(true);
-            mToolbar.setTitle(null);
-            mIgnoreNextNavigation = true;
-            mBreadcrumb.setSelection(mBreadcrumbAdapter.getCount() - 1, false);
-        }
-
-        if (DEBUG) Log.d(TAG, "Final toolbar title is: " + mToolbar.getTitle());
-    }
-
-    private void showBreadcrumb(boolean visibility) {
-        if (visibility) {
-            mBreadcrumb.setVisibility(VISIBLE);
-            mBreadcrumb.setAdapter(mBreadcrumbAdapter);
-        } else {
-            mBreadcrumb.setVisibility(GONE);
-            mBreadcrumb.setAdapter(null);
-        }
-    }
-
-    // Hamburger if drawer is present, else sad nullness.
-    private @Nullable Drawable getActionBarIcon() {
-        if (mDrawer.isPresent()) {
-            return mToolbar.getContext().getDrawable(R.drawable.ic_hamburger);
-        } else {
-            return null;
-        }
-    }
-
-    void revealRootsDrawer(boolean open) {
-        mDrawer.setOpen(open);
-    }
-
-    /**
-     * Class providing toolbar with runtime access to useful activity data.
-     */
-    static final class BreadcrumbAdapter extends BaseAdapter {
-
-        private Environment mEnv;
-        private State mState;
-
-        public BreadcrumbAdapter(State state, Environment env) {
-            mState = state;
-            mEnv = env;
-        }
-
-        @Override
-        public int getCount() {
-            return mState.stack.size();
-        }
-
-        @Override
-        public DocumentInfo getItem(int position) {
-            return mState.stack.get(mState.stack.size() - position - 1);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = LayoutInflater.from(parent.getContext())
-                        .inflate(R.layout.item_subdir_title, parent, false);
-            }
-
-            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
-            final DocumentInfo doc = getItem(position);
-
-            if (position == 0) {
-                final RootInfo root = mEnv.getCurrentRoot();
-                title.setText(root.title);
-            } else {
-                title.setText(doc.displayName);
-            }
-
-            return convertView;
-        }
-
-        @Override
-        public View getDropDownView(int position, View convertView, ViewGroup parent) {
-            if (convertView == null) {
-                convertView = LayoutInflater.from(parent.getContext())
-                        .inflate(R.layout.item_subdir, parent, false);
-            }
-
-            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
-            final DocumentInfo doc = getItem(position);
-
-            if (position == 0) {
-                final RootInfo root = mEnv.getCurrentRoot();
-                title.setText(root.title);
-            } else {
-                title.setText(doc.displayName);
-            }
-
-            return convertView;
-        }
-    }
-
-    interface Environment {
-        RootInfo getCurrentRoot();
-        String getDrawerTitle();
-        void refreshCurrentRootAndDirectory(int animation);
-        boolean isSearchExpanded();
-    }
-}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/NavigationViewManager.java b/packages/DocumentsUI/src/com/android/documentsui/NavigationViewManager.java
new file mode 100644
index 0000000..2554246
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/NavigationViewManager.java
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+package com.android.documentsui;
+
+import static com.android.documentsui.Shared.DEBUG;
+
+import android.annotation.Nullable;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.View;
+
+import com.android.documentsui.dirlist.AnimationView;
+import com.android.documentsui.model.RootInfo;
+
+import java.util.function.Consumer;
+
+/**
+ * A facade over the portions of the app and drawer toolbars.
+ */
+public class NavigationViewManager {
+
+    private static final String TAG = "NavigationViewManager";
+
+    final DrawerController mDrawer;
+    final DocumentsToolbar mToolbar;
+    final State mState;
+    final NavigationViewManager.Environment mEnv;
+    final Breadcrumb mBreadcrumb;
+
+    public NavigationViewManager(
+            DrawerController drawer,
+            DocumentsToolbar toolbar,
+            State state,
+            NavigationViewManager.Environment env,
+            Breadcrumb breadcrumb) {
+
+        mToolbar = toolbar;
+        mDrawer = drawer;
+        mState = state;
+        mEnv = env;
+        mBreadcrumb = breadcrumb;
+        mBreadcrumb.setup(env, state, this::onNavigationItemSelected);
+
+        mToolbar.setNavigationOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        onNavigationIconClicked();
+                    }
+                });
+    }
+
+    private void onNavigationIconClicked() {
+        if (mDrawer.isPresent()) {
+            mDrawer.setOpen(true, DrawerController.OPENED_HAMBURGER);
+        }
+    }
+
+    void onNavigationItemSelected(int position) {
+        boolean changed = false;
+        while (mState.stack.size() > position + 1) {
+            changed = true;
+            mState.popDocument();
+        }
+        if (changed) {
+            mEnv.refreshCurrentRootAndDirectory(AnimationView.ANIM_LEAVE);
+        }
+    }
+
+    void update() {
+
+        // TODO: Looks to me like this block is never getting hit.
+        if (mEnv.isSearchExpanded()) {
+            mToolbar.setTitle(null);
+            mBreadcrumb.show(false);
+            return;
+        }
+
+        mDrawer.setTitle(mEnv.getDrawerTitle());
+
+        mToolbar.setNavigationIcon(getActionBarIcon());
+        mToolbar.setNavigationContentDescription(R.string.drawer_open);
+
+        if (mState.stack.size() <= 1) {
+            mBreadcrumb.show(false);
+            String title = mEnv.getCurrentRoot().title;
+            if (DEBUG) Log.d(TAG, "New toolbar title is: " + title);
+            mToolbar.setTitle(title);
+        } else {
+            mBreadcrumb.show(true);
+            mToolbar.setTitle(null);
+            mBreadcrumb.postUpdate();
+        }
+
+        if (DEBUG) Log.d(TAG, "Final toolbar title is: " + mToolbar.getTitle());
+    }
+
+    // Hamburger if drawer is present, else sad nullness.
+    private @Nullable Drawable getActionBarIcon() {
+        if (mDrawer.isPresent()) {
+            return mToolbar.getContext().getDrawable(R.drawable.ic_hamburger);
+        } else {
+            return null;
+        }
+    }
+
+    void revealRootsDrawer(boolean open) {
+        mDrawer.setOpen(open);
+    }
+
+    interface Breadcrumb {
+        void setup(Environment env, State state, Consumer<Integer> listener);
+        void show(boolean visibility);
+        void postUpdate();
+    }
+
+    interface Environment {
+        RootInfo getCurrentRoot();
+        String getDrawerTitle();
+        void refreshCurrentRootAndDirectory(int animation);
+        boolean isSearchExpanded();
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 1688f35..2c0a8a8 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -49,6 +49,8 @@
 
     @Override
     public void initTestFiles() throws RemoteException {
+        mDocsHelper.createFolder(rootDir0, dirName1);
+
         mDocsHelper.createDocument(rootDir0, "text/plain", "file0.log");
         mDocsHelper.createDocument(rootDir0, "image/png", "file1.png");
         mDocsHelper.createDocument(rootDir0, "text/csv", "file2.csv");
@@ -124,6 +126,30 @@
         bots.directory.assertDocumentsPresent("Kung Fu Panda");
     }
 
+    public void testOpenBreadcrumb() throws Exception {
+        initTestFiles();
+
+        bots.roots.openRoot(ROOT_0_ID);
+
+        bots.directory.openDocument(dirName1);
+        if (bots.main.isTablet()) {
+            openBreadcrumbTabletHelper();
+        } else {
+            openBreadcrumbPhoneHelper();
+        }
+        bots.main.assertBreadcrumbItemsPresent(dirName1, "TEST_ROOT_0");
+        bots.main.clickBreadcrumbItem("TEST_ROOT_0");
+
+        bots.directory.assertDocumentsPresent(dirName1);
+    }
+
+    private void openBreadcrumbTabletHelper() throws Exception {
+    }
+
+    private void openBreadcrumbPhoneHelper() throws Exception {
+        bots.main.clickDropdownBreadcrumb();
+    }
+
     public void testDeleteDocument() throws Exception {
         initTestFiles();
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
index 15f176b..7484ed9 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/bots/UiBot.java
@@ -17,20 +17,20 @@
 package com.android.documentsui.bots;
 
 import static android.support.test.espresso.Espresso.onView;
-import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
-import static android.support.test.espresso.matcher.ViewMatchers.withResourceName;
-import static android.support.test.espresso.matcher.ViewMatchers.withId;
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.action.ViewActions.click;
 import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
 import static android.support.test.espresso.action.ViewActions.typeText;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withResourceName;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
-import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.is;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -41,21 +41,22 @@
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.UiObjectNotFoundException;
 import android.support.test.uiautomator.UiSelector;
-import android.support.test.uiautomator.Until;
-import android.support.v7.widget.SearchView;
-import android.util.Log;
 import android.widget.ImageView;
 import android.widget.Spinner;
 import android.widget.Toolbar;
-import junit.framework.AssertionFailedError;
 
 import com.android.documentsui.R;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.internal.view.menu.ActionMenuItemView;
 
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
@@ -80,7 +81,10 @@
     }
 
     public void assertBreadcrumbTitle(String expected) {
-        onView(isAssignableFrom(Spinner.class)).check(matches(withBreadcrumbTitle(is(expected))));
+        if (!isTablet()) {
+            onView(allOf(isAssignableFrom(Spinner.class), withId(R.id.breadcrumb)))
+            .check(matches(withBreadcrumbTitle(is(expected))));
+        }
   }
 
     public void assertMenuEnabled(int id, boolean enabled) {
@@ -208,6 +212,43 @@
                 : findObject("com.android.documentsui:id/menu_search", "android:id/search_button");
     }
 
+    public void clickBreadcrumbItem(String label) throws UiObjectNotFoundException {
+        if (isTablet()) {
+            findBreadcrumb(label).click();
+        } else {
+            findMenuWithName(label).click();
+        }
+    }
+
+    public void clickDropdownBreadcrumb() throws UiObjectNotFoundException {
+        assertFalse(isTablet());
+        onView(isAssignableFrom(Spinner.class)).perform(click());
+    }
+
+    public UiObject findBreadcrumb(String label) throws UiObjectNotFoundException {
+        final UiSelector breadcrumbList = new UiSelector().resourceId(
+                "com.android.documentsui:id/breadcrumb");
+
+        // Wait for the first list item to appear
+        new UiObject(breadcrumbList.childSelector(new UiSelector())).waitForExists(mTimeout);
+
+        return mDevice.findObject(breadcrumbList.childSelector(new UiSelector().text(label)));
+    }
+
+    public void assertBreadcrumbItemsPresent(String... labels) throws UiObjectNotFoundException {
+        List<String> absent = new ArrayList<>();
+        for (String label : labels) {
+            // For non-Tablet devices, a dropdown List menu is shown instead
+            if (isTablet() ? !findBreadcrumb(label).exists() : findMenuWithName(label) == null) {
+                absent.add(label);
+            }
+        }
+        if (!absent.isEmpty()) {
+            Assert.fail("Expected documents " + Arrays.asList(labels)
+                    + ", but missing " + absent);
+        }
+    }
+
     UiObject findActionModeBar() {
         return findObject("android:id/action_mode_bar");
     }