Fine tune the UI of hamburger Menu
1. Base on the new design, when the provider and app are both existed,
combine them as one item.
2. Rearrange the order of the provider and app roots.
If the pacakge name of providers or apps contains the preferred
root source, the order of the root will be higher than others
that doesn't include it.
3. Enlarge the touch area of action icon (Ex: eject icon).
4. Modify the title color of drawer.
Test: atest DocumentsTest, atest DocumentsUITests
Fix: 118364942
Change-Id: I81dd21894170f3582e1a26fb89b2816d696de1dc
diff --git a/src/com/android/documentsui/DrawerController.java b/src/com/android/documentsui/DrawerController.java
index 2c268d9..3a7e8a2 100644
--- a/src/com/android/documentsui/DrawerController.java
+++ b/src/com/android/documentsui/DrawerController.java
@@ -60,6 +60,7 @@
View drawer = activity.findViewById(R.id.drawer_roots);
Toolbar toolbar = (Toolbar) activity.findViewById(R.id.roots_toolbar);
+ toolbar.setTitleTextColor(activity.getResources().getColor(R.color.drawer_title_color));
drawer.getLayoutParams().width = calculateDrawerWidth(activity);
diff --git a/src/com/android/documentsui/base/RootInfo.java b/src/com/android/documentsui/base/RootInfo.java
index 6cd02b6..0f0a64e 100644
--- a/src/com/android/documentsui/base/RootInfo.java
+++ b/src/com/android/documentsui/base/RootInfo.java
@@ -360,7 +360,7 @@
}
public Drawable loadEjectIcon(Context context) {
- return IconUtils.applyTintColor(context, R.drawable.ic_eject, R.color.item_eject_icon);
+ return IconUtils.applyTintColor(context, R.drawable.ic_eject, R.color.item_action_icon);
}
@Override
diff --git a/src/com/android/documentsui/sidebar/AppItem.java b/src/com/android/documentsui/sidebar/AppItem.java
index 2b74973..e68c8fe 100644
--- a/src/com/android/documentsui/sidebar/AppItem.java
+++ b/src/com/android/documentsui/sidebar/AppItem.java
@@ -16,6 +16,7 @@
package com.android.documentsui.sidebar;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
@@ -25,6 +26,7 @@
import android.widget.TextView;
import com.android.documentsui.ActionHandler;
+import com.android.documentsui.IconUtils;
import com.android.documentsui.R;
/**
@@ -38,10 +40,9 @@
private final ActionHandler mActionHandler;
- public AppItem(ResolveInfo info, ActionHandler actionHandler) {
- super(R.layout.item_root, getStringId(info));
+ public AppItem(ResolveInfo info, String title, ActionHandler actionHandler) {
+ super(R.layout.item_root, title, getStringId(info));
this.info = info;
-
mActionHandler = actionHandler;
}
@@ -62,12 +63,20 @@
@Override
void bindView(View convertView) {
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
- final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+ final TextView titleView = (TextView) convertView.findViewById(android.R.id.title);
final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
+ final View actionIconArea = convertView.findViewById(R.id.action_icon_area);
+ final ImageView actionIcon = (ImageView) convertView.findViewById(R.id.action_icon);
- final PackageManager pm = convertView.getContext().getPackageManager();
+ final Context context = convertView.getContext();
+ final PackageManager pm = context.getPackageManager();
icon.setImageDrawable(info.loadIcon(pm));
- title.setText(info.loadLabel(pm));
+ titleView.setText(title);
+
+ actionIconArea.setVisibility(View.VISIBLE);
+ actionIconArea.setFocusable(false);
+ actionIcon.setImageDrawable(IconUtils.applyTintColor(context, R.drawable.ic_exit_to_app,
+ R.color.item_action_icon));
// TODO: match existing summary behavior from disambig dialog
summary.setVisibility(View.GONE);
@@ -85,6 +94,11 @@
}
@Override
+ String getPackageName() {
+ return info.activityInfo.packageName;
+ }
+
+ @Override
public String toString() {
return "AppItem{"
+ "id=" + stringId
diff --git a/src/com/android/documentsui/sidebar/Item.java b/src/com/android/documentsui/sidebar/Item.java
index 0902677..c600c26 100644
--- a/src/com/android/documentsui/sidebar/Item.java
+++ b/src/com/android/documentsui/sidebar/Item.java
@@ -34,10 +34,12 @@
abstract class Item {
private final @LayoutRes int mLayoutId;
+ public final String title;
final String stringId;
- public Item(@LayoutRes int layoutId, String stringId) {
+ public Item(@LayoutRes int layoutId, String title, String stringId) {
mLayoutId = layoutId;
+ this.title = title;
this.stringId = stringId;
}
@@ -58,6 +60,10 @@
abstract void open();
+ String getPackageName() {
+ return "";
+ }
+
boolean isDropTarget() {
return isRoot();
}
diff --git a/src/com/android/documentsui/sidebar/RootAndAppItem.java b/src/com/android/documentsui/sidebar/RootAndAppItem.java
new file mode 100644
index 0000000..65bed6e
--- /dev/null
+++ b/src/com/android/documentsui/sidebar/RootAndAppItem.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.sidebar;
+
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.provider.DocumentsProvider;
+import android.view.View;
+
+import com.android.documentsui.ActionHandler;
+import com.android.documentsui.R;
+import com.android.documentsui.base.RootInfo;
+
+/**
+ * An {@link Item} for each root provided by {@link DocumentsProvider}s
+ * and apps that supports some picking actions like {@link Intent#ACTION_GET_CONTENT}.
+ * This is only used in pickers.
+ */
+class RootAndAppItem extends RootItem {
+
+ public final ResolveInfo resolveInfo;
+
+ public RootAndAppItem(RootInfo root, ResolveInfo info, ActionHandler actionHandler) {
+ super(root, actionHandler, info.activityInfo.packageName);
+ this.resolveInfo = info;
+ }
+
+ @Override
+ boolean showAppDetails() {
+ mActionHandler.showAppDetails(resolveInfo);
+ return true;
+ }
+
+ @Override
+ public void bindView(View convertView) {
+ final Context context = convertView.getContext();
+
+ String contentDescription =
+ context.getResources().getString(R.string.open_external_app, root.title);
+
+ bindAction(convertView, View.VISIBLE, R.drawable.ic_exit_to_app, contentDescription);
+ bindIconAndTitle(convertView);
+ bindSummary(convertView, root.summary);
+ }
+
+ @Override
+ protected void onActionClick(View view) {
+ mActionHandler.openRoot(resolveInfo);
+ }
+
+ @Override
+ public String toString() {
+ return "RootAndAppItem{"
+ + "id=" + stringId
+ + ", root=" + root
+ + ", resolveInfo=" + resolveInfo
+ + ", docInfo=" + docInfo
+ + "}";
+ }
+}
diff --git a/src/com/android/documentsui/sidebar/RootItem.java b/src/com/android/documentsui/sidebar/RootItem.java
index b9d83d5..b93c409 100644
--- a/src/com/android/documentsui/sidebar/RootItem.java
+++ b/src/com/android/documentsui/sidebar/RootItem.java
@@ -18,6 +18,7 @@
import androidx.annotation.Nullable;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.provider.DocumentsProvider;
import android.text.TextUtils;
import android.text.format.Formatter;
@@ -25,11 +26,11 @@
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.documentsui.ActionHandler;
+import com.android.documentsui.IconUtils;
import com.android.documentsui.MenuManager;
import com.android.documentsui.R;
import com.android.documentsui.base.DocumentInfo;
@@ -44,12 +45,18 @@
public final RootInfo root;
public @Nullable DocumentInfo docInfo;
- private final ActionHandler mActionHandler;
+ protected final ActionHandler mActionHandler;
+ private final String mPackageName;
public RootItem(RootInfo root, ActionHandler actionHandler) {
- super(R.layout.item_root, getStringId(root));
+ this(root, actionHandler, "" /* packageName */);
+ }
+
+ public RootItem(RootInfo root, ActionHandler actionHandler, String packageName) {
+ super(R.layout.item_root, root.title, getStringId(root));
this.root = root;
mActionHandler = actionHandler;
+ mPackageName = packageName;
}
private static String getStringId(RootInfo root) {
@@ -62,27 +69,12 @@
@Override
public void bindView(View convertView) {
- final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
- final TextView title = (TextView) convertView.findViewById(android.R.id.title);
- final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
- final ImageView ejectIcon = (ImageView) convertView.findViewById(R.id.eject_icon);
-
final Context context = convertView.getContext();
- icon.setImageDrawable(root.loadDrawerIcon(context));
- title.setText(root.title);
-
if (root.supportsEject()) {
- ejectIcon.setVisibility(View.VISIBLE);
- ejectIcon.setImageDrawable(root.loadEjectIcon(context));
- ejectIcon.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View unmountIcon) {
- RootsFragment.ejectClicked(unmountIcon, root, mActionHandler);
- }
- });
+ bindAction(convertView, View.VISIBLE, R.drawable.ic_eject,
+ context.getResources().getString(R.string.menu_eject_root));
} else {
- ejectIcon.setVisibility(View.GONE);
- ejectIcon.setOnClickListener(null);
+ bindAction(convertView, View.GONE, -1 /* iconResource */, null /* description */);
}
// Show available space if no summary
String summaryText = root.summary;
@@ -91,8 +83,50 @@
Formatter.formatFileSize(context, root.availableBytes));
}
- summary.setText(summaryText);
- summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
+ bindIconAndTitle(convertView);
+ bindSummary(convertView, summaryText);
+ }
+
+ protected final void bindAction(View view, int visibility, int iconId, String description) {
+ final ImageView actionIcon = (ImageView) view.findViewById(R.id.action_icon);
+ final View verticalDivider = view.findViewById(R.id.vertical_divider);
+ final View actionIconArea = view.findViewById(R.id.action_icon_area);
+
+ verticalDivider.setVisibility(visibility);
+ actionIconArea.setVisibility(visibility);
+ actionIconArea.setOnClickListener(visibility == View.VISIBLE ? this::onActionClick : null);
+ if (description != null) {
+ actionIconArea.setContentDescription(description);
+ }
+ if (iconId > 0) {
+ actionIcon.setImageDrawable(IconUtils.applyTintColor(view.getContext(), iconId,
+ R.color.item_action_icon));
+ }
+ }
+
+ protected void onActionClick(View view) {
+ RootsFragment.ejectClicked(view, root, mActionHandler);
+ }
+
+ protected final void bindIconAndTitle(View view) {
+ bindIcon(view, root.loadDrawerIcon(view.getContext()));
+ bindTitle(view);
+ }
+
+ protected void bindSummary(View view, String summary) {
+ final TextView summaryView = (TextView) view.findViewById(android.R.id.summary);
+ summaryView.setText(summary);
+ summaryView.setVisibility(TextUtils.isEmpty(summary) ? View.GONE : View.VISIBLE);
+ }
+
+ private void bindIcon(View view, Drawable drawable) {
+ final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
+ icon.setImageDrawable(drawable);
+ }
+
+ private void bindTitle(View view) {
+ final TextView titleView = (TextView) view.findViewById(android.R.id.title);
+ titleView.setText(title);
}
@Override
@@ -106,6 +140,11 @@
}
@Override
+ String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
boolean isDropTarget() {
return root.supportsCreate();
}
diff --git a/src/com/android/documentsui/sidebar/RootsAdapter.java b/src/com/android/documentsui/sidebar/RootsAdapter.java
index 77858f6..a08637c 100644
--- a/src/com/android/documentsui/sidebar/RootsAdapter.java
+++ b/src/com/android/documentsui/sidebar/RootsAdapter.java
@@ -36,7 +36,9 @@
class RootsAdapter extends ArrayAdapter<Item> {
private static final int TYPE_ROOT = 0;
private static final int TYPE_APP = 1;
- private static final int TYPE_SPACER = 2;
+ private static final int TYPE_ROOT_AND_APP = 2;
+ private static final int TYPE_SPACER = 3;
+
private static final Map<String, Long> sIdMap = new HashMap<>();
// the next available id to associate with a new string id
private static long sNextAvailableId;
@@ -104,7 +106,9 @@
@Override
public int getItemViewType(int position) {
final Item item = getItem(position);
- if (item instanceof RootItem) {
+ if (item instanceof RootAndAppItem) {
+ return TYPE_ROOT_AND_APP;
+ } else if (item instanceof RootItem) {
return TYPE_ROOT;
} else if (item instanceof AppItem) {
return TYPE_APP;
@@ -115,6 +119,6 @@
@Override
public int getViewTypeCount() {
- return 3;
+ return 4;
}
}
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index f02ce33..c77baa3 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -16,11 +16,10 @@
package com.android.documentsui.sidebar;
+import static com.android.documentsui.base.Shared.compareToIgnoreCaseNullable;
import static com.android.documentsui.base.SharedMinimal.DEBUG;
import static com.android.documentsui.base.SharedMinimal.VERBOSE;
-import androidx.annotation.Nullable;
-import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
@@ -51,6 +50,9 @@
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ListView;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
import com.android.documentsui.ActionHandler;
import com.android.documentsui.BaseActivity;
import com.android.documentsui.DocumentsApplication;
@@ -66,16 +68,17 @@
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
+import com.android.documentsui.roots.ProvidersAccess;
import com.android.documentsui.roots.ProvidersCache;
import com.android.documentsui.roots.RootsLoader;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -238,8 +241,8 @@
final boolean excludeSelf =
intent.getBooleanExtra(DocumentsContract.EXTRA_EXCLUDE_SELF, false);
final String excludePackage = excludeSelf ? activity.getCallingPackage() : null;
- List<Item> sortedItems =
- sortLoadResult(roots, excludePackage, handlerAppIntent);
+ List<Item> sortedItems = sortLoadResult(roots, excludePackage, handlerAppIntent,
+ DocumentsApplication.getProvidersCache(getContext()));
mAdapter = new RootsAdapter(activity, sortedItems, mDragListener);
mList.setAdapter(mAdapter);
@@ -256,6 +259,8 @@
}
/**
+ * If the package name of other providers or apps capable of handling the original intent
+ * include the preferred root source, it will have higher order than others.
* @param excludePackage Exclude activities from this given package
* @param handlerAppIntent When not null, apps capable of handling the original intent will
* be included in list of roots (in special section at bottom).
@@ -264,92 +269,130 @@
List<Item> sortLoadResult(
Collection<RootInfo> roots,
@Nullable String excludePackage,
- @Nullable Intent handlerAppIntent) {
+ @Nullable Intent handlerAppIntent,
+ ProvidersAccess providersAccess) {
final List<Item> result = new ArrayList<>();
final List<RootItem> libraries = new ArrayList<>();
- final List<RootItem> others = new ArrayList<>();
- final List<RootItem> storages = new ArrayList<>();
+ final List<RootItem> storageProviders = new ArrayList<>();
+ final List<RootItem> otherProviders = new ArrayList<>();
for (final RootInfo root : roots) {
- final RootItem item = new RootItem(root, mActionHandler);
+ final RootItem item;
if (root.isExternalStorageHome() && !Shared.shouldShowDocumentsRoot(getContext())) {
continue;
} else if (root.isLibrary() || root.isDownloads()) {
+ item = new RootItem(root, mActionHandler);
libraries.add(item);
} else if (root.isStorage()) {
- storages.add(item);
+ item = new RootItem(root, mActionHandler);
+ storageProviders.add(item);
} else {
- others.add(item);
+ item = new RootItem(root, mActionHandler,
+ providersAccess.getPackageName(root.authority));
+ otherProviders.add(item);
}
}
final RootComparator comp = new RootComparator();
Collections.sort(libraries, comp);
- Collections.sort(storages, comp);
- Collections.sort(others, comp);
+ Collections.sort(storageProviders, comp);
if (VERBOSE) Log.v(TAG, "Adding library roots: " + libraries);
result.addAll(libraries);
// Only add the spacer if it is actually separating something.
- if (!result.isEmpty() && !storages.isEmpty()) {
+ if (!result.isEmpty() && !storageProviders.isEmpty()) {
result.add(new SpacerItem());
}
- if (VERBOSE) Log.v(TAG, "Adding storage roots: " + storages);
- result.addAll(storages);
-
- if (!result.isEmpty() && !others.isEmpty()) {
- result.add(new SpacerItem());
- }
- if (VERBOSE) Log.v(TAG, "Adding plain roots: " + others);
- result.addAll(others);
+ if (VERBOSE) Log.v(TAG, "Adding storage roots: " + storageProviders);
+ result.addAll(storageProviders);
// Include apps that can handle this intent too.
if (handlerAppIntent != null) {
- includeHandlerApps(handlerAppIntent, excludePackage, result);
+ includeHandlerApps(handlerAppIntent, excludePackage, result, otherProviders);
+ } else {
+ // Only add providers
+ Collections.sort(otherProviders, comp);
+ if (!result.isEmpty() && !otherProviders.isEmpty()) {
+ result.add(new SpacerItem());
+ }
+ if (VERBOSE) Log.v(TAG, "Adding plain roots: " + otherProviders);
+ result.addAll(otherProviders);
}
return result;
}
/**
- * Adds apps capable of handling the original intent will be included in list of roots (in
- * special section at bottom).
+ * Adds apps capable of handling the original intent will be included in list of roots. If
+ * the providers and apps are the same package name, combine them as RootAndAppItems.
*/
private void includeHandlerApps(
- Intent handlerAppIntent, @Nullable String excludePackage, List<Item> result) {
+ Intent handlerAppIntent, @Nullable String excludePackage, List<Item> result,
+ List<RootItem> otherProviders) {
if (VERBOSE) Log.v(TAG, "Adding handler apps for intent: " + handlerAppIntent);
Context context = getContext();
final PackageManager pm = context.getPackageManager();
final List<ResolveInfo> infos = pm.queryIntentActivities(
handlerAppIntent, PackageManager.MATCH_DEFAULT_ONLY);
- final List<AppItem> apps = new ArrayList<>();
- AppItem profileItem = null;
+ final List<Item> rootList = new ArrayList<>();
+ final Map<String, ResolveInfo> appsMapping = new HashMap<>();
+ final Map<String, Item> appItems = new HashMap<>();
+ Item profileItem = null;
// Omit ourselves and maybe calling package from the list
for (ResolveInfo info : infos) {
- if (!context.getPackageName().equals(info.activityInfo.packageName) &&
- !TextUtils.equals(excludePackage, info.activityInfo.packageName)) {
- final AppItem app = new AppItem(info, mActionHandler);
- if (VERBOSE) Log.v(TAG, "Adding handler app: " + app);
+ final String packageName = info.activityInfo.packageName;
+ if (!context.getPackageName().equals(packageName) &&
+ !TextUtils.equals(excludePackage, packageName)) {
+ appsMapping.put(packageName, info);
+
+ final Item item = new AppItem(info, info.loadLabel(pm).toString(), mActionHandler);
+
+ if (VERBOSE) Log.v(TAG, "Adding handler app: " + item);
// for change personal profile root.
if (PROFILE_TARGET_ACTIVITY.equals(info.activityInfo.targetActivity)) {
- profileItem = app;
+ profileItem = item;
} else {
- apps.add(app);
+ appItems.put(packageName, item);
}
}
}
- if (apps.size() > 0) {
- result.add(new SpacerItem());
- result.addAll(apps);
+ // If there are some providers and apps has the same package name, combine them as one item.
+ for (RootItem rootItem : otherProviders) {
+ final String packageName = rootItem.getPackageName();
+ final ResolveInfo resolveInfo = appsMapping.get(packageName);
+
+ final Item item;
+ if (resolveInfo != null) {
+ item = new RootAndAppItem(rootItem.root, resolveInfo, mActionHandler);
+ appItems.remove(packageName);
+ } else {
+ item = rootItem;
+ }
+
+ if (VERBOSE) Log.v(TAG, "Adding provider : " + item);
+ rootList.add(item);
}
+ rootList.addAll(appItems.values());
+
+ if (!result.isEmpty() && !rootList.isEmpty()) {
+ result.add(new SpacerItem());
+ }
+
+ final String preferredRootPackage = getResources().getString(
+ R.string.preferred_root_package, "");
+
+ final ItemComparator comp = new ItemComparator(preferredRootPackage);
+ Collections.sort(rootList, comp);
+ result.addAll(rootList);
+
if (profileItem != null) {
result.add(new SpacerItem());
result.add(profileItem);
@@ -429,7 +472,7 @@
final RootItem rootItem = (RootItem) mAdapter.getItem(adapterMenuInfo.position);
switch (item.getItemId()) {
case R.id.root_menu_eject_root:
- final View ejectIcon = adapterMenuInfo.targetView.findViewById(R.id.eject_icon);
+ final View ejectIcon = adapterMenuInfo.targetView.findViewById(R.id.action_icon);
ejectClicked(ejectIcon, rootItem.root, mActionHandler);
return true;
case R.id.root_menu_open_in_new_window:
@@ -492,6 +535,53 @@
}
}
+ /**
+ * The comparator of {@link AppItem}, {@link RootItem} and {@link RootAndAppItem}.
+ * Sort by if the item's package name starts with the preferred package name,
+ * then title, then summary. Because the {@link AppItem} doesn't have summary,
+ * it will have lower order than other same title items.
+ */
+ @VisibleForTesting
+ static class ItemComparator implements Comparator<Item> {
+ private final String mPreferredPackageName;
+
+ ItemComparator(String preferredPackageName) {
+ mPreferredPackageName = preferredPackageName;
+ }
+
+ @Override
+ public int compare(Item lhs, Item rhs) {
+ // Sort by whether the item starts with preferred package name
+ if (!mPreferredPackageName.isEmpty()) {
+ if (lhs.getPackageName().startsWith(mPreferredPackageName)) {
+ if (!rhs.getPackageName().startsWith(mPreferredPackageName)) {
+ // lhs starts with it, but rhs doesn't start with it
+ return -1;
+ }
+ } else {
+ if (rhs.getPackageName().startsWith(mPreferredPackageName)) {
+ // lhs doesn't start with it, but rhs starts with it
+ return 1;
+ }
+ }
+ }
+
+ // Sort by title
+ int score = compareToIgnoreCaseNullable(lhs.title, rhs.title);
+ if (score != 0) {
+ return score;
+ }
+
+ // Sort by summary. If the item is AppItem, it doesn't have summary.
+ // So, the RootItem or RootAndAppItem will have higher order than AppItem.
+ if (lhs instanceof RootItem) {
+ return rhs instanceof RootItem ? compareToIgnoreCaseNullable(
+ ((RootItem) lhs).root.summary, ((RootItem) rhs).root.summary) : 1;
+ }
+ return rhs instanceof RootItem ? -1 : 0;
+ }
+ }
+
@FunctionalInterface
interface RootUpdater {
void updateDocInfoForRoot(DocumentInfo doc);
diff --git a/src/com/android/documentsui/sidebar/SpacerItem.java b/src/com/android/documentsui/sidebar/SpacerItem.java
index 89cea1a..66c814d 100644
--- a/src/com/android/documentsui/sidebar/SpacerItem.java
+++ b/src/com/android/documentsui/sidebar/SpacerItem.java
@@ -33,7 +33,7 @@
public SpacerItem() {
// Multiple spacer items can share the same string id as they're identical.
- super(R.layout.item_root_spacer, STRING_ID);
+ super(R.layout.item_root_spacer, "" /* title */, STRING_ID);
}
@Override