diff options
11 files changed, 122 insertions, 42 deletions
diff --git a/api/current.txt b/api/current.txt index d4e23f3..57384a7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20812,6 +20812,7 @@ package android.provider { field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type"; field public static final java.lang.String COLUMN_SIZE = "_size"; field public static final java.lang.String COLUMN_SUMMARY = "summary"; + field public static final int FLAG_DIR_HIDE_GRID_TITLES = 64; // 0x40 field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10 field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 231e6a6..3f33e80 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -257,6 +257,18 @@ public final class DocumentsContract { * @see #COLUMN_FLAGS */ public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5; + + /** + * Flag indicating that document titles should be hidden when viewing + * this directory in a larger format grid. For example, a directory + * containing only images may want the image thumbnails to speak for + * themselves. Only valid when {@link #COLUMN_MIME_TYPE} is + * {@link #MIME_TYPE_DIR}. + * + * @see #COLUMN_FLAGS + * @see #FLAG_DIR_PREFERS_GRID + */ + public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 6; } /** diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml index 3cfae64..b745bb9 100644 --- a/packages/DocumentsUI/res/layout/item_doc_grid.xml +++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml @@ -30,6 +30,7 @@ android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" + android:layout_marginBottom="6dp" android:background="#fff"> <FrameLayout @@ -63,10 +64,10 @@ </FrameLayout> <LinearLayout + android:id="@+id/line1" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:paddingTop="6dp" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> @@ -121,6 +122,20 @@ android:textAlignment="viewStart" style="@style/TextAppearance.Small" /> + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1" /> + + <ImageView + android:id="@android:id/icon2" + android:layout_width="@dimen/root_icon_size" + android:layout_height="@dimen/root_icon_size" + android:layout_marginStart="8dip" + android:scaleType="centerInside" + android:contentDescription="@null" + android:visibility="gone" /> + </LinearLayout> </LinearLayout> diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml index 6c37a4e..d995376 100644 --- a/packages/DocumentsUI/res/menu/activity.xml +++ b/packages/DocumentsUI/res/menu/activity.xml @@ -19,7 +19,7 @@ android:id="@+id/menu_create_dir" android:title="@string/menu_create_dir" android:icon="@drawable/ic_menu_new_folder" - android:showAsAction="ifRoom" /> + android:showAsAction="always" /> <item android:id="@+id/menu_search" android:title="@string/menu_search" @@ -48,12 +48,12 @@ android:id="@+id/menu_grid" android:title="@string/menu_grid" android:icon="@drawable/ic_menu_view_grid" - android:showAsAction="ifRoom" /> + android:showAsAction="never" /> <item android:id="@+id/menu_list" android:title="@string/menu_list" android:icon="@drawable/ic_menu_view_list" - android:showAsAction="ifRoom" /> + android:showAsAction="never" /> <item android:id="@+id/menu_settings" android:title="@string/menu_settings" diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index aee865b..198927c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -102,6 +102,8 @@ public class DirectoryFragment extends Fragment { private int mLastSortOrder = SORT_ORDER_UNKNOWN; private boolean mLastShowSize = false; + private boolean mHideGridTitles = false; + private Point mThumbSize; private DocumentsAdapter mAdapter; @@ -112,11 +114,6 @@ public class DirectoryFragment extends Fragment { private static final String EXTRA_DOC = "doc"; private static final String EXTRA_QUERY = "query"; - /** - * MIME types that should always show thumbnails in list mode. - */ - private static final String[] LIST_THUMBNAIL_MIMES = new String[] { "image/*", "video/*" }; - private static AtomicInteger sLoaderId = new AtomicInteger(4000); private final int mLoaderId = sLoaderId.incrementAndGet(); @@ -182,14 +179,23 @@ public class DirectoryFragment extends Fragment { final Context context = getActivity(); final State state = getDisplayState(DirectoryFragment.this); + final RootInfo root = getArguments().getParcelable(EXTRA_ROOT); + final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC); + mAdapter = new DocumentsAdapter(); mType = getArguments().getInt(EXTRA_TYPE); + if (mType == TYPE_RECENT_OPEN) { + // Hide titles when showing recents for picking images/videos + mHideGridTitles = MimePredicate.mimeMatches( + MimePredicate.VISUAL_MIMES, state.acceptMimes); + } else { + mHideGridTitles = (doc != null) && doc.isGridTitlesHidden(); + } + mCallbacks = new LoaderCallbacks<DirectoryResult>() { @Override public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) { - final RootInfo root = getArguments().getParcelable(EXTRA_ROOT); - final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC); final String query = getArguments().getString(EXTRA_QUERY); Uri contentsUri; @@ -643,6 +649,8 @@ public class DirectoryFragment extends Fragment { final Context context = parent.getContext(); final State state = getDisplayState(DirectoryFragment.this); + final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC); + final RootsCache roots = DocumentsApplication.getRootsCache(context); final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( context, mThumbSize); @@ -671,12 +679,15 @@ public class DirectoryFragment extends Fragment { final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY); final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE); + final View line1 = convertView.findViewById(R.id.line1); + final View line2 = convertView.findViewById(R.id.line2); + final View icon = convertView.findViewById(android.R.id.icon); final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime); final ImageView iconThumb = (ImageView) convertView.findViewById(R.id.icon_thumb); final TextView title = (TextView) convertView.findViewById(android.R.id.title); - final View line2 = convertView.findViewById(R.id.line2); final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1); + final ImageView icon2 = (ImageView) convertView.findViewById(android.R.id.icon2); final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); final TextView date = (TextView) convertView.findViewById(R.id.date); final TextView size = (TextView) convertView.findViewById(R.id.size); @@ -692,10 +703,11 @@ public class DirectoryFragment extends Fragment { final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0; final boolean allowThumbnail = (state.derivedMode == MODE_GRID) - || MimePredicate.mimeMatches(LIST_THUMBNAIL_MIMES, docMimeType); + || MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, docMimeType); + final boolean showThumbnail = supportsThumbnail && allowThumbnail; boolean cacheHit = false; - if (supportsThumbnail && allowThumbnail) { + if (showThumbnail) { final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); final Bitmap cachedResult = thumbs.get(uri); if (cachedResult != null) { @@ -726,15 +738,19 @@ public class DirectoryFragment extends Fragment { } } - title.setText(docDisplayName); - + boolean hasLine1 = false; boolean hasLine2 = false; + final boolean hideTitle = (state.derivedMode == MODE_GRID) && mHideGridTitles; + if (!hideTitle) { + title.setText(docDisplayName); + hasLine1 = true; + } + + Drawable iconDrawable = null; if (mType == TYPE_RECENT_OPEN) { final RootInfo root = roots.getRoot(docAuthority, docRootId); - final Drawable iconDrawable = root.loadIcon(context); - icon1.setVisibility(View.VISIBLE); - icon1.setImageDrawable(iconDrawable); + iconDrawable = root.loadIcon(context); if (summary != null) { final boolean alwaysShowSummary = getResources() @@ -756,7 +772,13 @@ public class DirectoryFragment extends Fragment { } } } else { - icon1.setVisibility(View.GONE); + // Directories showing thumbnails in grid mode get a little icon + // hint to remind user they're a directory. + if (Document.MIME_TYPE_DIR.equals(docMimeType) && state.derivedMode == MODE_GRID + && showThumbnail) { + iconDrawable = context.getResources().getDrawable(R.drawable.ic_root_folder); + } + if (summary != null) { if (docSummary != null) { summary.setText(docSummary); @@ -768,6 +790,19 @@ public class DirectoryFragment extends Fragment { } } + if (icon1 != null) icon1.setVisibility(View.GONE); + if (icon2 != null) icon2.setVisibility(View.GONE); + + if (iconDrawable != null) { + if (hasLine1) { + icon1.setVisibility(View.VISIBLE); + icon1.setImageDrawable(iconDrawable); + } else { + icon2.setVisibility(View.VISIBLE); + icon2.setImageDrawable(iconDrawable); + } + } + if (docLastModified == -1) { date.setText(null); } else { @@ -787,6 +822,9 @@ public class DirectoryFragment extends Fragment { size.setVisibility(View.GONE); } + if (line1 != null) { + line1.setVisibility(hasLine1 ? View.VISIBLE : View.GONE); + } if (line2 != null) { line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE); } @@ -796,11 +834,13 @@ public class DirectoryFragment extends Fragment { if (enabled) { setEnabledRecursive(convertView, true); icon.setAlpha(1f); - icon1.setAlpha(1f); + if (icon1 != null) icon1.setAlpha(1f); + if (icon2 != null) icon2.setAlpha(1f); } else { setEnabledRecursive(convertView, false); icon.setAlpha(0.5f); - icon1.setAlpha(0.5f); + if (icon1 != null) icon1.setAlpha(0.5f); + if (icon2 != null) icon2.setAlpha(0.5f); } return convertView; @@ -943,6 +983,7 @@ public class DirectoryFragment extends Fragment { } private void setEnabledRecursive(View v, boolean enabled) { + if (v == null) return; if (v.isEnabled() == enabled) return; v.setEnabled(enabled); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index b1e51a0..f6cb481 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -244,6 +244,7 @@ public class DocumentsActivity extends Activity { } else { // Restore last stack for calling package // TODO: move into async loader + boolean restoredStack = false; final String packageName = getCallingPackage(); final Cursor cursor = getContentResolver() .query(RecentsProvider.buildResume(packageName), null, null, null, null); @@ -252,6 +253,7 @@ public class DocumentsActivity extends Activity { final byte[] rawStack = cursor.getBlob( cursor.getColumnIndex(ResumeColumns.STACK)); DurableUtils.readFromArray(rawStack, mState.stack); + restoredStack = true; } } catch (IOException e) { Log.w(TAG, "Failed to resume", e); @@ -264,10 +266,13 @@ public class DocumentsActivity extends Activity { final List<RootInfo> matchingRoots = mRoots.getMatchingRoots(mState); if (!matchingRoots.contains(root)) { mState.stack.reset(); + restoredStack = false; } - // Only open drawer when showing recents - if (mState.stack.isRecents()) { + // Only open drawer when not restoring stack, and when not showing + // visual content. + if (!restoredStack + && !MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) { setRootsDrawerOpen(true); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java index b55ce82..2d96876 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java @@ -22,6 +22,12 @@ import com.android.internal.util.Predicate; public class MimePredicate implements Predicate<DocumentInfo> { private final String[] mFilters; + /** + * MIME types that are visual in nature. For example, they should always be + * shown as thumbnails in list mode. + */ + public static final String[] VISUAL_MIMES = new String[] { "image/*", "video/*" }; + public MimePredicate(String[] filters) { mFilters = filters; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java index a7173b6..1912010 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java @@ -27,6 +27,7 @@ import android.database.Cursor; import android.database.MergeCursor; import android.net.Uri; import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.util.Log; @@ -176,7 +177,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { try { final Cursor cursor = task.get(); final FilteringCursorWrapper filtered = new FilteringCursorWrapper( - cursor, mAcceptMimes) { + cursor, mAcceptMimes, new String[] { Document.MIME_TYPE_DIR }) { @Override public void close() { // Ignored, since we manage cursor lifecycle internally diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index 4d313e8..9b54948 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -137,12 +137,14 @@ public class RootsCache { @GuardedBy("ActivityThread") public boolean isIconUnique(RootInfo root) { + final int rootIcon = root.derivedIcon != 0 ? root.derivedIcon : root.icon; for (RootInfo test : mRoots) { if (Objects.equal(test.authority, root.authority)) { if (Objects.equal(test.rootId, root.rootId)) { continue; } - if (test.icon == root.icon) { + final int testIcon = test.derivedIcon != 0 ? test.derivedIcon : test.icon; + if (testIcon == rootIcon) { return false; } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 54dcf1c..908729c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -35,6 +35,7 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; +import android.widget.Space; import android.widget.TextView; import com.android.documentsui.DocumentsActivity.State; @@ -136,11 +137,8 @@ public class RootsFragment extends Fragment { }; private static class RootsAdapter extends ArrayAdapter<RootInfo> implements SectionAdapter { - private int mHeaderId; - - public RootsAdapter(Context context, int headerId) { + public RootsAdapter(Context context) { super(context, 0); - mHeaderId = headerId; } @Override @@ -177,13 +175,8 @@ public class RootsFragment extends Fragment { @Override public View getHeaderView(View convertView, ViewGroup parent) { if (convertView == null) { - convertView = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_root_header, parent, false); + convertView = new Space(parent.getContext()); } - - final TextView title = (TextView) convertView.findViewById(android.R.id.title); - title.setText(mHeaderId); - return convertView; } } @@ -237,9 +230,9 @@ public class RootsFragment extends Fragment { private final AppsAdapter mApps; public SectionedRootsAdapter(Context context, List<RootInfo> roots, Intent includeApps) { - mServices = new RootsAdapter(context, R.string.root_type_service); - mShortcuts = new RootsAdapter(context, R.string.root_type_shortcut); - mDevices = new RootsAdapter(context, R.string.root_type_device); + mServices = new RootsAdapter(context); + mShortcuts = new RootsAdapter(context); + mDevices = new RootsAdapter(context); mApps = new AppsAdapter(context); for (RootInfo root : roots) { @@ -274,15 +267,15 @@ public class RootsFragment extends Fragment { mShortcuts.sort(comp); mDevices.sort(comp); - if (mServices.getCount() > 0) { - addSection(mServices); - } if (mShortcuts.getCount() > 0) { addSection(mShortcuts); } if (mDevices.getCount() > 0) { addSection(mDevices); } + if (mServices.getCount() > 0) { + addSection(mServices); + } if (mApps.getCount() > 0) { addSection(mApps); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java index ab55d94..681cc9b 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java @@ -204,6 +204,10 @@ public class DocumentInfo implements Durable, Parcelable { return (flags & Document.FLAG_SUPPORTS_DELETE) != 0; } + public boolean isGridTitlesHidden() { + return (flags & Document.FLAG_DIR_HIDE_GRID_TITLES) != 0; + } + public static String getCursorString(Cursor cursor, String columnName) { final int index = cursor.getColumnIndex(columnName); return (index != -1) ? cursor.getString(index) : null; |