Merge "Fix DocsUI cross-profile error screens in landscape" into rvc-dev
diff --git a/Android.bp b/Android.bp
index 7a70bd6..0460570 100644
--- a/Android.bp
+++ b/Android.bp
@@ -45,7 +45,6 @@
     min_sdk_version: "29",
 
     plugins: [
-        "compat-changeid-annotation-processor",
         "java_api_finder",
     ],
 }
diff --git a/res/layout/directory_header.xml b/res/layout/directory_header.xml
index 45abeb8..4c4d845 100644
--- a/res/layout/directory_header.xml
+++ b/res/layout/directory_header.xml
@@ -14,6 +14,7 @@
      limitations under the License.
 -->
 <LinearLayout 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="wrap_content"
     android:layout_marginTop="@dimen/action_bar_space_margin"
@@ -28,11 +29,33 @@
     <!-- used for search chip. -->
     <include layout="@layout/search_chip_row"/>
 
-    <com.google.android.material.tabs.TabLayout
-        android:id="@+id/tabs"
+    <LinearLayout
+        android:id="@+id/tabs_container"
+        android:theme="@style/TabTheme"
+        android:clipToPadding="true"
+        android:clipChildren="true"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:visibility="gone"/>
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <com.google.android.material.tabs.TabLayout
+            android:id="@+id/tabs"
+            android:background="@android:color/transparent"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:tabMaxWidth="0dp"
+            app:tabGravity="fill"
+            app:tabMode="fixed"
+            app:tabIndicatorColor="@color/tab_indicator_color"
+            app:tabSelectedTextColor="@color/tab_indicator_color"
+            app:tabTextAppearance="@style/TabTextAppearance"
+            app:tabTextColor="?android:attr/textColorSecondary"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="?android:attr/listDivider"/>
+    </LinearLayout>
 
     <!-- used for apps row. -->
     <include layout="@layout/apps_row"/>
diff --git a/res/layout/drawer_layout.xml b/res/layout/drawer_layout.xml
index bf5bfb4..a5331d5 100644
--- a/res/layout/drawer_layout.xml
+++ b/res/layout/drawer_layout.xml
@@ -28,14 +28,15 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
-        <FrameLayout
+        <androidx.coordinatorlayout.widget.CoordinatorLayout
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:orientation="vertical">
 
             <FrameLayout
                 android:layout_width="match_parent"
-                android:layout_height="match_parent">
+                android:layout_height="match_parent"
+                app:layout_behavior="@string/scrolling_behavior">
 
                 <LinearLayout
                     android:layout_width="match_parent"
@@ -70,7 +71,7 @@
 
             <include layout="@layout/directory_app_bar"/>
 
-        </FrameLayout>
+        </androidx.coordinatorlayout.widget.CoordinatorLayout>
 
         <LinearLayout
             android:id="@+id/drawer_roots"
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
index f358b02..1a6c174 100644
--- a/res/values-night/colors.xml
+++ b/res/values-night/colors.xml
@@ -21,4 +21,12 @@
     <color name="primary">#8AB4F8</color>
     <color name="secondary">#3D8AB4F8</color>
     <color name="hairline">#5F6368</color>
+
+    <color name="briefcase_icon_color">#669DF6</color> <!-- Blue 400 -->
+    <color name="cross_profile_button_text_color">#669DF6</color> <!-- Blue 400 -->
+    <color name="empty_state_text">@android:color/white</color>
+    <color name="error_image_color">@android:color/white</color>
+
+    <color name="edge_effect">@android:color/white</color>
+    <color name="tab_indicator_color">#669DF6</color> <!-- Blue 400 -->
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index fb00e16..9016664 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -46,4 +46,7 @@
     <color name="cross_profile_button_text_color">#1A73E8</color>
     <color name="empty_state_text">#202124</color>
     <color name="error_image_color">#757575</color>
+
+    <color name="tab_indicator_color">#1A73E8</color> <!-- Blue 600 -->
+    <color name="edge_effect">@android:color/black</color>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ee71ac7..bac7887 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -514,7 +514,7 @@
     <string name="open_tree_dialog_message">This will let <xliff:g id="appName" example="Drive">%1$s</xliff:g> access current and future content stored in <xliff:g id="directory" example="DCIM">%2$s</xliff:g>.</string>
     <!-- Header message title show on open document tree flow when directory is blocked. [CHAR_LIMIT=48] -->
     <string name="directory_blocked_header_title">Choose another folder</string>
-    <!-- Header message subtitle show on open document tree flow when directory is blocked. [CHAR_LIMIT=80]-->
+    <!-- Header message subtitle show on open document tree flow when directory is blocked. [CHAR_LIMIT=90]-->
     <string name="directory_blocked_header_subtitle">This folder can\u2019t be used for security reasons</string>
     <!-- Button text for the "create new folder" button. [CHAR_LIMIT=48] -->
     <string name="create_new_folder_button">Create new folder</string>
diff --git a/res/values/styles_text.xml b/res/values/styles_text.xml
index 592df8e..3ea510c 100644
--- a/res/values/styles_text.xml
+++ b/res/values/styles_text.xml
@@ -114,4 +114,9 @@
         <item name="fontFamily">@string/config_fontFamilyMedium</item>
     </style>
 
+    <style name="TabTextAppearance" parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">14sp</item>
+        <item name="fontFamily">@string/config_fontFamilyMedium</item>
+    </style>
+
 </resources>
\ No newline at end of file
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 971fa1f..a1029be 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -72,4 +72,8 @@
         <item name="queryBackground">@color/menu_search_background</item>
         <item name="snackbarButtonStyle">@style/SnackbarButtonStyle</item>
     </style>
+
+    <style name="TabTheme" parent="@style/Theme.MaterialComponents.DayNight">
+        <item name="colorPrimary">@color/edge_effect</item>
+    </style>
 </resources>
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index c694c0c..54674e1 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -162,11 +162,11 @@
 
         Breadcrumb breadcrumb = findViewById(R.id.horizontal_breadcrumb);
         assert(breadcrumb != null);
-        TabLayout profileTabs = findViewById(R.id.tabs);
-        assert (profileTabs != null);
+        View profileTabsContainer = findViewById(R.id.tabs_container);
+        assert (profileTabsContainer != null);
 
         mNavigator = new NavigationViewManager(this, mDrawer, mState, this, breadcrumb,
-                profileTabs, DocumentsApplication.getUserIdManager(this));
+                profileTabsContainer, DocumentsApplication.getUserIdManager(this));
         SearchManagerListener searchListener = new SearchManagerListener() {
             /**
              * Called when search results changed. Refreshes the content of the directory. It
diff --git a/src/com/android/documentsui/HorizontalBreadcrumb.java b/src/com/android/documentsui/HorizontalBreadcrumb.java
index d5987e1..5cfc65e 100644
--- a/src/com/android/documentsui/HorizontalBreadcrumb.java
+++ b/src/com/android/documentsui/HorizontalBreadcrumb.java
@@ -30,7 +30,6 @@
 
 import com.android.documentsui.NavigationViewManager.Breadcrumb;
 import com.android.documentsui.NavigationViewManager.Environment;
-import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.dirlist.AccessibilityEventRouter;
 
 import java.util.function.Consumer;
@@ -65,7 +64,7 @@
             IntConsumer listener) {
 
         mClickListener = listener;
-        mLayoutManager = new LinearLayoutManager(
+        mLayoutManager = new HorizontalBreadcrumbLinearLayoutManager(
                 getContext(), LinearLayoutManager.HORIZONTAL, false);
         mAdapter = new BreadcrumbAdapter(state, env, this::onKey);
         // Since we are using GestureDetector to detect click events, a11y services don't know which
@@ -163,7 +162,7 @@
             mState = state;
             mEnv = env;
             mClickListener = clickListener;
-            mLastItemSize = mState.stack.size();
+            mLastItemSize = getItemCount();
         }
 
         @Override
@@ -175,14 +174,16 @@
 
         @Override
         public void onBindViewHolder(BreadcrumbHolder holder, int position) {
-            final DocumentInfo doc = getItem(position);
             final int padding = (int) holder.itemView.getResources()
                     .getDimension(R.dimen.breadcrumb_item_padding);
             final int enableColor = holder.itemView.getContext().getColor(R.color.primary);
             final boolean isFirst = position == 0;
+            // Note that when isFirst is true, there might not be a DocumentInfo on the stack as it
+            // could be an error state screen accessible from the root info.
             final boolean isLast = position == getItemCount() - 1;
 
-            holder.mTitle.setText(isFirst ? mEnv.getCurrentRoot().title : doc.displayName);
+            holder.mTitle.setText(
+                    isFirst ? mEnv.getCurrentRoot().title : mState.stack.get(position).displayName);
             holder.mTitle.setTextColor(isLast ? enableColor : holder.mDefaultTextColor);
             holder.mTitle.setPadding(isFirst ? padding * 3 : padding,
                     padding, isLast ? padding * 2 : padding, padding);
@@ -192,12 +193,19 @@
             holder.setLast(isLast);
         }
 
-        private DocumentInfo getItem(int position) {
-            return mState.stack.get(position);
-        }
-
         @Override
         public int getItemCount() {
+            // Don't show recents in the breadcrumb.
+            if (mState.stack.isRecents()) {
+                return 0;
+            }
+            // Continue showing the root title in the breadcrumb for cross-profile error screens.
+            if (mState.supportsCrossProfile()
+                    && mState.stack.size() == 0
+                    && mState.stack.getRoot() != null
+                    && mState.stack.getRoot().supportsCrossProfile()) {
+                return 1;
+            }
             return mState.stack.size();
         }
 
@@ -206,7 +214,7 @@
         }
 
         public void updateLastItemSize() {
-            mLastItemSize = mState.stack.size();
+            mLastItemSize = getItemCount();
         }
     }
 
@@ -238,4 +246,22 @@
         public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
         }
     }
+
+    private static class HorizontalBreadcrumbLinearLayoutManager extends LinearLayoutManager {
+
+        /**
+         * Disable predictive animations. There is a bug in RecyclerView which causes views that
+         * are being reloaded to pull invalid view holders from the internal recycler stack if the
+         * adapter size has decreased since the ViewHolder was recycled.
+         */
+        @Override
+        public boolean supportsPredictiveItemAnimations() {
+            return false;
+        }
+
+        HorizontalBreadcrumbLinearLayoutManager(
+                Context context, int orientation, boolean reverseLayout) {
+            super(context, orientation, reverseLayout);
+        }
+    }
 }
diff --git a/src/com/android/documentsui/NavigationViewManager.java b/src/com/android/documentsui/NavigationViewManager.java
index 00591d7..204971d 100644
--- a/src/com/android/documentsui/NavigationViewManager.java
+++ b/src/com/android/documentsui/NavigationViewManager.java
@@ -64,7 +64,7 @@
             State state,
             NavigationViewManager.Environment env,
             Breadcrumb breadcrumb,
-            TabLayout tabLayout,
+            View tabLayoutContainer,
             UserIdManager userIdManager) {
 
         mToolbar = activity.findViewById(R.id.toolbar);
@@ -73,7 +73,7 @@
         mEnv = env;
         mBreadcrumb = breadcrumb;
         mBreadcrumb.setup(env, state, this::onNavigationItemSelected);
-        mProfileTabs = new ProfileTabs(tabLayout, mState, userIdManager, mEnv, activity);
+        mProfileTabs = new ProfileTabs(tabLayoutContainer, mState, userIdManager, mEnv, activity);
 
         mToolbar.setNavigationOnClickListener(
                 new View.OnClickListener() {
diff --git a/src/com/android/documentsui/ProfileTabs.java b/src/com/android/documentsui/ProfileTabs.java
index 0e30ea6..7149de6 100644
--- a/src/com/android/documentsui/ProfileTabs.java
+++ b/src/com/android/documentsui/ProfileTabs.java
@@ -39,6 +39,7 @@
 public class ProfileTabs implements ProfileTabsAddons {
     private static final float DISABLED_TAB_OPACITY = 0.38f;
 
+    private final View mTabsContainer;
     private final TabLayout mTabs;
     private final State mState;
     private final NavigationViewManager.Environment mEnv;
@@ -49,10 +50,11 @@
     private Listener mListener;
     private TabLayout.OnTabSelectedListener mOnTabSelectedListener;
 
-    public ProfileTabs(TabLayout tabLayout, State state, UserIdManager userIdManager,
+    public ProfileTabs(View tabLayoutContainer, State state, UserIdManager userIdManager,
             NavigationViewManager.Environment env,
             AbstractActionHandler.CommonAddons commonAddons) {
-        mTabs = checkNotNull(tabLayout);
+        mTabsContainer = checkNotNull(tabLayoutContainer);
+        mTabs = tabLayoutContainer.findViewById(R.id.tabs);
         mState = checkNotNull(state);
         mEnv = checkNotNull(env);
         mCommonAddons = checkNotNull(commonAddons);
@@ -93,7 +95,7 @@
             mTabs.selectTab(mTabs.getTabAt(mUserIds.indexOf(currentRoot.userId)));
             mTabs.addOnTabSelectedListener(mOnTabSelectedListener);
         }
-        mTabs.setVisibility(shouldShow() ? View.VISIBLE : View.GONE);
+        mTabsContainer.setVisibility(shouldShow() ? View.VISIBLE : View.GONE);
     }
 
     public void setListener(@Nullable Listener listener) {
diff --git a/src/com/android/documentsui/picker/PickFragment.java b/src/com/android/documentsui/picker/PickFragment.java
index f7b98e3..bd25f00 100644
--- a/src/com/android/documentsui/picker/PickFragment.java
+++ b/src/com/android/documentsui/picker/PickFragment.java
@@ -51,6 +51,7 @@
     private static final String ACTION_KEY = "action";
     private static final String COPY_OPERATION_SUBTYPE_KEY = "copyOperationSubType";
     private static final String PICK_TARGET_KEY = "pickTarget";
+    private static final String RESTRICT_SCOPE_STORAGE_KEY = "restrictScopeStorage";
 
     private final View.OnClickListener mPickListener = new View.OnClickListener() {
         @Override
@@ -120,6 +121,7 @@
             mCopyOperationSubType =
                     savedInstanceState.getInt(COPY_OPERATION_SUBTYPE_KEY);
             mPickTarget = savedInstanceState.getParcelable(PICK_TARGET_KEY);
+            mRestrictScopeStorage = savedInstanceState.getBoolean(RESTRICT_SCOPE_STORAGE_KEY);
             updateView();
         }
 
@@ -132,6 +134,7 @@
         outState.putInt(ACTION_KEY, mAction);
         outState.putInt(COPY_OPERATION_SUBTYPE_KEY, mCopyOperationSubType);
         outState.putParcelable(PICK_TARGET_KEY, mPickTarget);
+        outState.putBoolean(RESTRICT_SCOPE_STORAGE_KEY, mRestrictScopeStorage);
     }
 
     /**
diff --git a/tests/common/com/android/documentsui/bots/DirectoryListBot.java b/tests/common/com/android/documentsui/bots/DirectoryListBot.java
index 08d2995..9fbc39f 100644
--- a/tests/common/com/android/documentsui/bots/DirectoryListBot.java
+++ b/tests/common/com/android/documentsui/bots/DirectoryListBot.java
@@ -164,7 +164,7 @@
     private UiObject findHeaderMessageButton() {
         return findObject(
                 mDirContainerId,
-                mTargetPackage + ":id/button_dismiss");
+                mTargetPackage + ":id/dismiss_button");
     }
 
     private UiObject findPlaceholderMessageTextView() {