Merge "Implementing breadcrumb for tablet devices for navigation in the bar."
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");
}