diff --git a/api/current.txt b/api/current.txt index fd9c6c21f89c1..1ae9ab76f61d4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20985,7 +20985,6 @@ 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 @@ -21002,18 +21001,12 @@ package android.provider { field public static final java.lang.String COLUMN_ICON = "icon"; field public static final java.lang.String COLUMN_MIME_TYPES = "mime_types"; field public static final java.lang.String COLUMN_ROOT_ID = "root_id"; - field public static final java.lang.String COLUMN_ROOT_TYPE = "root_type"; field public static final java.lang.String COLUMN_SUMMARY = "summary"; field public static final java.lang.String COLUMN_TITLE = "title"; - field public static final int FLAG_ADVANCED = 4; // 0x4 - field public static final int FLAG_EMPTY = 32; // 0x20 field public static final int FLAG_LOCAL_ONLY = 2; // 0x2 field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 - field public static final int FLAG_SUPPORTS_RECENTS = 8; // 0x8 - field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10 - field public static final int ROOT_TYPE_DEVICE = 3; // 0x3 - field public static final int ROOT_TYPE_SERVICE = 1; // 0x1 - field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2 + field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4 + field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8 } public abstract class DocumentsProvider extends android.content.ContentProvider { diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 4c9af19ab1839..85ec80368c2a6 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -99,7 +99,7 @@ public final class DocumentsContract { /** * Unique ID of a document. This ID is both provided by and interpreted * by a {@link DocumentsProvider}, and should be treated as an opaque - * value by client applications. + * value by client applications. This column is required. *

* Each document must have a unique ID within a provider, but that * single document may be included as a child of multiple directories. @@ -117,7 +117,7 @@ public final class DocumentsContract { * Concrete MIME type of a document. For example, "image/png" or * "application/pdf" for openable files. A document can also be a * directory containing additional documents, which is represented with - * the {@link #MIME_TYPE_DIR} MIME type. + * the {@link #MIME_TYPE_DIR} MIME type. This column is required. *

* Type: STRING * @@ -127,15 +127,15 @@ public final class DocumentsContract { /** * Display name of a document, used as the primary title displayed to a - * user. + * user. This column is required. *

* Type: STRING */ public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME; /** - * Summary of a document, which may be shown to a user. The summary may - * be {@code null}. + * Summary of a document, which may be shown to a user. This column is + * optional, and may be {@code null}. *

* Type: STRING */ @@ -143,9 +143,9 @@ public final class DocumentsContract { /** * Timestamp when a document was last modified, in milliseconds since - * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A - * {@link DocumentsProvider} can update this field using events from - * {@link OnCloseListener} or other reliable + * January 1, 1970 00:00:00.0 UTC. This column is required, and may be + * {@code null} if unknown. A {@link DocumentsProvider} can update this + * field using events from {@link OnCloseListener} or other reliable * {@link ParcelFileDescriptor} transports. *

* Type: INTEGER (long) @@ -155,15 +155,16 @@ public final class DocumentsContract { public static final String COLUMN_LAST_MODIFIED = "last_modified"; /** - * Specific icon resource ID for a document, or {@code null} to use - * platform default icon based on {@link #COLUMN_MIME_TYPE}. + * Specific icon resource ID for a document. This column is optional, + * and may be {@code null} to use a platform-provided default icon based + * on {@link #COLUMN_MIME_TYPE}. *

* Type: INTEGER (int) */ public static final String COLUMN_ICON = "icon"; /** - * Flags that apply to a document. + * Flags that apply to a document. This column is required. *

* Type: INTEGER (int) * @@ -171,12 +172,13 @@ public final class DocumentsContract { * @see #FLAG_SUPPORTS_DELETE * @see #FLAG_SUPPORTS_THUMBNAIL * @see #FLAG_DIR_PREFERS_GRID - * @see #FLAG_DIR_SUPPORTS_CREATE + * @see #FLAG_DIR_PREFERS_LAST_MODIFIED */ public static final String COLUMN_FLAGS = "flags"; /** - * Size of a document, in bytes, or {@code null} if unknown. + * Size of a document, in bytes, or {@code null} if unknown. This column + * is required. *

* Type: INTEGER (long) */ @@ -211,7 +213,7 @@ public final class DocumentsContract { * writability of a document may change over time, for example due to * remote access changes. This flag indicates that a document client can * expect {@link ContentResolver#openOutputStream(Uri)} to succeed. - * + * * @see #COLUMN_FLAGS */ public static final int FLAG_SUPPORTS_WRITE = 1 << 1; @@ -265,8 +267,9 @@ public final class DocumentsContract { * * @see #COLUMN_FLAGS * @see #FLAG_DIR_PREFERS_GRID + * @hide */ - public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 6; + public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 16; } /** @@ -282,31 +285,17 @@ public final class DocumentsContract { /** * Unique ID of a root. This ID is both provided by and interpreted by a * {@link DocumentsProvider}, and should be treated as an opaque value - * by client applications. + * by client applications. This column is required. *

* Type: STRING */ public static final String COLUMN_ROOT_ID = "root_id"; /** - * Type of a root, used for clustering when presenting multiple roots to - * a user. + * Flags that apply to a root. This column is required. *

* Type: INTEGER (int) * - * @see #ROOT_TYPE_SERVICE - * @see #ROOT_TYPE_SHORTCUT - * @see #ROOT_TYPE_DEVICE - */ - public static final String COLUMN_ROOT_TYPE = "root_type"; - - /** - * Flags that apply to a root. - *

- * Type: INTEGER (int) - * - * @see #FLAG_ADVANCED - * @see #FLAG_EMPTY * @see #FLAG_LOCAL_ONLY * @see #FLAG_SUPPORTS_CREATE * @see #FLAG_SUPPORTS_RECENTS @@ -315,22 +304,23 @@ public final class DocumentsContract { public static final String COLUMN_FLAGS = "flags"; /** - * Icon resource ID for a root. + * Icon resource ID for a root. This column is required. *

* Type: INTEGER (int) */ public static final String COLUMN_ICON = "icon"; /** - * Title for a root, which will be shown to a user. + * Title for a root, which will be shown to a user. This column is + * required. *

* Type: STRING */ public static final String COLUMN_TITLE = "title"; /** - * Summary for this root, which may be shown to a user. The summary may - * be {@code null}. + * Summary for this root, which may be shown to a user. This column is + * optional, and may be {@code null}. *

* Type: STRING */ @@ -338,7 +328,7 @@ public final class DocumentsContract { /** * Document which is a directory that represents the top directory of - * this root. + * this root. This column is required. *

* Type: STRING * @@ -347,49 +337,26 @@ public final class DocumentsContract { public static final String COLUMN_DOCUMENT_ID = "document_id"; /** - * Number of bytes available in this root, or {@code null} if unknown or - * unbounded. + * Number of bytes available in this root. This column is optional, and + * may be {@code null} if unknown or unbounded. *

* Type: INTEGER (long) */ public static final String COLUMN_AVAILABLE_BYTES = "available_bytes"; /** - * MIME types supported by this root, or {@code null} if the root - * supports all MIME types. Multiple MIME types can be separated by a - * newline. For example, a root supporting audio might use - * "audio/*\napplication/x-flac". + * MIME types supported by this root. This column is optional, and if + * {@code null} the root is assumed to support all MIME types. Multiple + * MIME types can be separated by a newline. For example, a root + * supporting audio might return "audio/*\napplication/x-flac". *

- * Type: String + * Type: STRING */ public static final String COLUMN_MIME_TYPES = "mime_types"; /** {@hide} */ public static final String MIME_TYPE_ITEM = "vnd.android.document/root"; - /** - * Type of root that represents a storage service, such as a cloud-based - * service. - * - * @see #COLUMN_ROOT_TYPE - */ - public static final int ROOT_TYPE_SERVICE = 1; - - /** - * Type of root that represents a shortcut to content that may be - * available elsewhere through another storage root. - * - * @see #COLUMN_ROOT_TYPE - */ - public static final int ROOT_TYPE_SHORTCUT = 2; - - /** - * Type of root that represents a physical storage device. - * - * @see #COLUMN_ROOT_TYPE - */ - public static final int ROOT_TYPE_DEVICE = 3; - /** * Flag indicating that at least one directory under this root supports * creating content. Roots with this flag will be shown when an @@ -408,14 +375,6 @@ public final class DocumentsContract { */ public static final int FLAG_LOCAL_ONLY = 1 << 1; - /** - * Flag indicating that this root should only be visible to advanced - * users. - * - * @see #COLUMN_FLAGS - */ - public static final int FLAG_ADVANCED = 1 << 2; - /** * Flag indicating that this root can report recently modified * documents. @@ -423,7 +382,7 @@ public final class DocumentsContract { * @see #COLUMN_FLAGS * @see DocumentsContract#buildRecentDocumentsUri(String, String) */ - public static final int FLAG_SUPPORTS_RECENTS = 1 << 3; + public static final int FLAG_SUPPORTS_RECENTS = 1 << 2; /** * Flag indicating that this root supports search. @@ -432,19 +391,31 @@ public final class DocumentsContract { * @see DocumentsProvider#querySearchDocuments(String, String, * String[]) */ - public static final int FLAG_SUPPORTS_SEARCH = 1 << 4; + public static final int FLAG_SUPPORTS_SEARCH = 1 << 3; /** * Flag indicating that this root is currently empty. This may be used * to hide the root when opening documents, but the root will still be * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is - * also set. + * also set. If the value of this flag changes, such as when a root + * becomes non-empty, you must send a content changed notification for + * {@link DocumentsContract#buildRootsUri(String)}. * * @see #COLUMN_FLAGS - * @see DocumentsProvider#querySearchDocuments(String, String, - * String[]) + * @see ContentResolver#notifyChange(Uri, + * android.database.ContentObserver, boolean) + * @hide */ - public static final int FLAG_EMPTY = 1 << 5; + public static final int FLAG_EMPTY = 1 << 16; + + /** + * Flag indicating that this root should only be visible to advanced + * users. + * + * @see #COLUMN_FLAGS + * @hide + */ + public static final int FLAG_ADVANCED = 1 << 17; } /** diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png index 02408741c9cfb..904d5253672ca 100644 Binary files a/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png and b/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png new file mode 100644 index 0000000000000..0d75172a1eb11 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png new file mode 100644 index 0000000000000..403eddb4f90a6 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png index 02408741c9cfb..068619be0b43c 100644 Binary files a/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png and b/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png new file mode 100644 index 0000000000000..0d75172a1eb11 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png new file mode 100644 index 0000000000000..9a9cf5ede773a Binary files /dev/null and b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png index 02408741c9cfb..e38a8685dcea8 100644 Binary files a/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png and b/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png new file mode 100644 index 0000000000000..0d75172a1eb11 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png new file mode 100644 index 0000000000000..205c34bcbe608 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png index 02408741c9cfb..0b332e410a77a 100644 Binary files a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png new file mode 100644 index 0000000000000..32b5f9880a892 Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png differ diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png new file mode 100644 index 0000000000000..f47d50a834b8f Binary files /dev/null and b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png differ diff --git a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml index 3bea1663c1f4c..851061fe9b51f 100644 --- a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml +++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml @@ -21,17 +21,18 @@ android:minHeight="?android:attr/listPreferredItemHeight" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:paddingTop="8dip" - android:paddingBottom="8dip" - android:orientation="horizontal"> + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:gravity="center_vertical" + android:orientation="horizontal" + android:baselineAligned="false"> + android:layout_marginEnd="20dp"> + + android:orientation="horizontal" + android:baselineAligned="false"> + android:divider="@drawable/ic_drawer_hairline_divider" /> diff --git a/packages/DocumentsUI/res/layout/fragment_save.xml b/packages/DocumentsUI/res/layout/fragment_save.xml index 570b517f30fd8..891f0a07129fd 100644 --- a/packages/DocumentsUI/res/layout/fragment_save.xml +++ b/packages/DocumentsUI/res/layout/fragment_save.xml @@ -29,6 +29,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" + android:baselineAligned="false" android:gravity="center_vertical" android:background="#ddd" android:minHeight="?android:attr/listPreferredItemHeightSmall"> @@ -44,7 +45,7 @@ - - - - - - - - + android:background="#fff" + android:foreground="@drawable/ic_grid_gradient_bg" + android:foregroundGravity="fill"> + + @@ -67,7 +55,9 @@ android:id="@+id/line1" android:layout_width="match_parent" android:layout_height="wrap_content" + android:gravity="center_vertical" android:orientation="horizontal" + android:baselineAligned="false" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> @@ -85,7 +75,7 @@ android:id="@android:id/icon1" android:layout_width="@dimen/root_icon_size" android:layout_height="@dimen/root_icon_size" - android:layout_marginStart="8dip" + android:layout_marginStart="8dp" android:scaleType="centerInside" android:contentDescription="@null" /> @@ -95,16 +85,17 @@ android:id="@+id/line2" android:layout_width="match_parent" android:layout_height="wrap_content" + android:gravity="center_vertical" android:orientation="horizontal" + android:baselineAligned="false" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> - - diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml index 84fda9d36ad32..4c5b2e304f6ee 100644 --- a/packages/DocumentsUI/res/layout/item_doc_list.xml +++ b/packages/DocumentsUI/res/layout/item_doc_list.xml @@ -21,17 +21,18 @@ android:minHeight="?android:attr/listPreferredItemHeight" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:paddingTop="8dip" - android:paddingBottom="8dip" - android:orientation="horizontal"> + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:gravity="center_vertical" + android:orientation="horizontal" + android:baselineAligned="false"> + android:layout_marginEnd="20dp"> + android:orientation="horizontal" + android:baselineAligned="false"> @@ -85,13 +86,15 @@ android:id="@+id/line2" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal"> + android:gravity="center_vertical" + android:orientation="horizontal" + android:baselineAligned="false"> + android:paddingTop="8dp" + android:paddingBottom="8dp"> + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:orientation="horizontal" + android:baselineAligned="false"> diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml index 98d78dac545f1..9b52d85ca45e9 100644 --- a/packages/DocumentsUI/res/layout/item_root.xml +++ b/packages/DocumentsUI/res/layout/item_root.xml @@ -22,13 +22,14 @@ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:gravity="center_vertical" android:orientation="horizontal" + android:baselineAligned="false" android:background="@drawable/item_root"> diff --git a/packages/DocumentsUI/res/layout/item_root_spacer.xml b/packages/DocumentsUI/res/layout/item_root_spacer.xml new file mode 100644 index 0000000000000..7d96ac8a5c946 --- /dev/null +++ b/packages/DocumentsUI/res/layout/item_root_spacer.xml @@ -0,0 +1,20 @@ + + + + diff --git a/packages/DocumentsUI/res/layout/item_title.xml b/packages/DocumentsUI/res/layout/item_title.xml index 7eb100a245b0c..58016f1fe962f 100644 --- a/packages/DocumentsUI/res/layout/item_title.xml +++ b/packages/DocumentsUI/res/layout/item_title.xml @@ -21,7 +21,8 @@ android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:gravity="center_vertical" - android:orientation="horizontal"> + android:orientation="horizontal" + android:baselineAligned="false"> { + private final String mDisplayName; + + public CreateDirectoryTask(String displayName) { + mDisplayName = displayName; + } + + @Override + protected DocumentInfo doInBackground(Void... params) { + final DocumentsActivity activity = (DocumentsActivity) getActivity(); + final ContentResolver resolver = activity.getContentResolver(); + + final DocumentInfo cwd = activity.getCurrentDirectory(); + final Uri childUri = DocumentsContract.createDocument( + resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, mDisplayName); + try { + return DocumentInfo.fromUri(resolver, childUri); + } catch (FileNotFoundException e) { + return null; + } + } + + @Override + protected void onPostExecute(DocumentInfo result) { + final DocumentsActivity activity = (DocumentsActivity) getActivity(); + if (result != null) { + // Navigate into newly created child + activity.onDocumentPicked(result); + } else { + Toast.makeText(activity, R.string.create_error, Toast.LENGTH_SHORT).show(); + } + } + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index c46dfb2c009b0..1f11aed91358d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -742,7 +742,6 @@ public class DirectoryFragment extends Fragment { 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); @@ -786,10 +785,12 @@ public class DirectoryFragment extends Fragment { // loaded in background. if (cacheHit) { iconMime.setAlpha(0f); + iconMime.setImageDrawable(null); iconThumb.setAlpha(1f); } else { iconMime.setAlpha(1f); iconThumb.setAlpha(0f); + iconThumb.setImageDrawable(null); if (docIcon != 0) { iconMime.setImageDrawable( IconUtils.loadPackageIcon(context, docAuthority, docIcon)); @@ -895,12 +896,14 @@ public class DirectoryFragment extends Fragment { final boolean enabled = isDocumentEnabled(docMimeType, docFlags); if (enabled) { setEnabledRecursive(convertView, true); - icon.setAlpha(1f); + iconMime.setAlpha(1f); + iconThumb.setAlpha(1f); if (icon1 != null) icon1.setAlpha(1f); if (icon2 != null) icon2.setAlpha(1f); } else { setEnabledRecursive(convertView, false); - icon.setAlpha(0.5f); + iconMime.setAlpha(0.5f); + iconThumb.setAlpha(0.5f); if (icon1 != null) icon1.setAlpha(0.5f); if (icon2 != null) icon2.setAlpha(0.5f); } @@ -991,10 +994,11 @@ public class DirectoryFragment extends Fragment { mIconThumb.setTag(null); mIconThumb.setImageBitmap(result); - mIconMime.setAlpha(1f); + final float targetAlpha = mIconMime.isEnabled() ? 1f : 0.5f; + mIconMime.setAlpha(targetAlpha); mIconMime.animate().alpha(0f).start(); mIconThumb.setAlpha(0f); - mIconThumb.animate().alpha(1f).start(); + mIconThumb.animate().alpha(targetAlpha).start(); } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 8627ecfaa2f7e..0b3ecf84900e9 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -63,6 +63,9 @@ class DirectoryResult implements AutoCloseable { } public class DirectoryLoader extends AsyncTaskLoader { + + private static final String[] SEARCH_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR }; + private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver(); private final int mType; @@ -164,8 +167,7 @@ public class DirectoryLoader extends AsyncTaskLoader { if (mType == DirectoryFragment.TYPE_SEARCH) { // Filter directories out of search results, for now - cursor = new FilteringCursorWrapper(cursor, null, new String[] { - Document.MIME_TYPE_DIR }); + cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES); } else { // Normal directories should have sorting applied cursor = new SortingCursorWrapper(cursor, result.sortOrder); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 72fdc57f9caba..4caec8fb0ad23 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -854,14 +854,7 @@ public class DocumentsActivity extends Activity { mState.stackTouched = true; if (!mRoots.isRecentsRoot(root)) { - try { - final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId); - final DocumentInfo doc = DocumentInfo.fromUri(getContentResolver(), uri); - mState.stack.push(doc); - mState.stackTouched = true; - onCurrentDirectoryChanged(ANIM_SIDE); - } catch (FileNotFoundException e) { - } + new PickRootTask(root).execute(); } else { onCurrentDirectoryChanged(ANIM_SIDE); } @@ -871,6 +864,34 @@ public class DocumentsActivity extends Activity { } } + private class PickRootTask extends AsyncTask { + private RootInfo mRoot; + + public PickRootTask(RootInfo root) { + mRoot = root; + } + + @Override + protected DocumentInfo doInBackground(Void... params) { + try { + final Uri uri = DocumentsContract.buildDocumentUri( + mRoot.authority, mRoot.documentId); + return DocumentInfo.fromUri(getContentResolver(), uri); + } catch (FileNotFoundException e) { + return null; + } + } + + @Override + protected void onPostExecute(DocumentInfo result) { + if (result != null) { + mState.stack.push(result); + mState.stackTouched = true; + onCurrentDirectoryChanged(ANIM_SIDE); + } + } + } + public void onAppPicked(ResolveInfo info) { final Intent intent = new Intent(getIntent()); intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT); @@ -909,7 +930,7 @@ public class DocumentsActivity extends Activity { onCurrentDirectoryChanged(ANIM_DOWN); } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) { // Explicit file picked, return - onFinished(doc.derivedUri); + new ExistingFinishTask(doc.derivedUri).execute(); } else if (mState.action == ACTION_CREATE) { // Replace selected file SaveFragment.get(fm).setReplaceTarget(doc); @@ -943,29 +964,19 @@ public class DocumentsActivity extends Activity { for (int i = 0; i < size; i++) { uris[i] = docs.get(i).derivedUri; } - onFinished(uris); + new ExistingFinishTask(uris).execute(); } } public void onSaveRequested(DocumentInfo replaceTarget) { - onFinished(replaceTarget.derivedUri); + new ExistingFinishTask(replaceTarget.derivedUri).execute(); } public void onSaveRequested(String mimeType, String displayName) { - final DocumentInfo cwd = getCurrentDirectory(); - - final Uri childUri = DocumentsContract.createDocument( - getContentResolver(), cwd.derivedUri, mimeType, displayName); - if (childUri != null) { - onFinished(childUri); - } else { - Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show(); - } + new CreateFinishTask(mimeType, displayName).execute(); } - private void onFinished(Uri... uris) { - Log.d(TAG, "onFinished() " + Arrays.toString(uris)); - + private void saveStackBlocking() { final ContentResolver resolver = getContentResolver(); final ContentValues values = new ContentValues(); @@ -973,6 +984,7 @@ public class DocumentsActivity extends Activity { if (mState.action == ACTION_CREATE) { // Remember stack for last create values.clear(); + values.put(RecentColumns.KEY, mState.stack.buildKey()); values.put(RecentColumns.STACK, rawStack); resolver.insert(RecentsProvider.buildRecent(), values); } @@ -983,6 +995,10 @@ public class DocumentsActivity extends Activity { values.put(ResumeColumns.STACK, rawStack); values.put(ResumeColumns.EXTERNAL, 0); resolver.insert(RecentsProvider.buildResume(packageName), values); + } + + private void onFinished(Uri... uris) { + Log.d(TAG, "onFinished() " + Arrays.toString(uris)); final Intent intent = new Intent(); if (uris.length == 1) { @@ -1008,6 +1024,56 @@ public class DocumentsActivity extends Activity { finish(); } + private class CreateFinishTask extends AsyncTask { + private final String mMimeType; + private final String mDisplayName; + + public CreateFinishTask(String mimeType, String displayName) { + mMimeType = mimeType; + mDisplayName = displayName; + } + + @Override + protected Uri doInBackground(Void... params) { + final DocumentInfo cwd = getCurrentDirectory(); + final Uri childUri = DocumentsContract.createDocument( + getContentResolver(), cwd.derivedUri, mMimeType, mDisplayName); + if (childUri != null) { + saveStackBlocking(); + } + return childUri; + } + + @Override + protected void onPostExecute(Uri result) { + if (result != null) { + onFinished(result); + } else { + Toast.makeText(DocumentsActivity.this, R.string.save_error, Toast.LENGTH_SHORT) + .show(); + } + } + } + + private class ExistingFinishTask extends AsyncTask { + private final Uri[] mUris; + + public ExistingFinishTask(Uri... uris) { + mUris = uris; + } + + @Override + protected Void doInBackground(Void... params) { + saveStackBlocking(); + return null; + } + + @Override + protected void onPostExecute(Void result) { + onFinished(mUris); + } + } + public static class State implements android.os.Parcelable { public int action; public String[] acceptMimes; diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java index 5f56963ae463b..52d816f1f968e 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java @@ -34,10 +34,15 @@ public class FilteringCursorWrapper extends AbstractCursor { private int mCount; public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes) { - this(cursor, acceptMimes, null); + this(cursor, acceptMimes, null, Long.MIN_VALUE); } public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes, String[] rejectMimes) { + this(cursor, acceptMimes, rejectMimes, Long.MIN_VALUE); + } + + public FilteringCursorWrapper( + Cursor cursor, String[] acceptMimes, String[] rejectMimes, long rejectBefore) { mCursor = cursor; final int count = cursor.getCount(); @@ -47,9 +52,14 @@ public class FilteringCursorWrapper extends AbstractCursor { while (cursor.moveToNext()) { final String mimeType = cursor.getString( cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)); + final long lastModified = cursor.getLong( + cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED)); if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) { continue; } + if (lastModified < rejectBefore) { + continue; + } if (MimePredicate.mimeMatches(acceptMimes, mimeType)) { mPosition[mCount++] = cursor.getPosition(); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java index e390456773225..9a4fb7da725fd 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java @@ -26,9 +26,11 @@ import android.content.Context; import android.database.Cursor; import android.database.MergeCursor; import android.net.Uri; +import android.os.Bundle; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; +import android.text.format.DateUtils; import android.util.Log; import com.android.documentsui.DocumentsActivity.State; @@ -54,17 +56,23 @@ import java.util.concurrent.TimeUnit; public class RecentLoader extends AsyncTaskLoader { private static final boolean LOGD = true; - public static final int MAX_OUTSTANDING_RECENTS = 2; + // TODO: adjust for svelte devices + // TODO: add support for oneway queries to avoid wedging loader + private static final int MAX_OUTSTANDING_RECENTS = 2; /** * Time to wait for first pass to complete before returning partial results. */ - public static final int MAX_FIRST_PASS_WAIT_MILLIS = 500; + private static final int MAX_FIRST_PASS_WAIT_MILLIS = 500; - /** - * Maximum documents from a single root. - */ - public static final int MAX_DOCS_FROM_ROOT = 64; + /** Maximum documents from a single root. */ + private static final int MAX_DOCS_FROM_ROOT = 64; + + /** Ignore documents older than this age. */ + private static final long REJECT_OLDER_THAN = 45 * DateUtils.DAY_IN_MILLIS; + + /** MIME types that should always be excluded from recents. */ + private static final String[] RECENT_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR }; private static final ExecutorService sExecutor = buildExecutor(); @@ -173,6 +181,8 @@ public class RecentLoader extends AsyncTaskLoader { } } + final long rejectBefore = System.currentTimeMillis() - REJECT_OLDER_THAN; + // Collect all finished tasks List cursors = Lists.newArrayList(); for (RecentTask task : mTasks.values()) { @@ -180,7 +190,7 @@ public class RecentLoader extends AsyncTaskLoader { try { final Cursor cursor = task.get(); final FilteringCursorWrapper filtered = new FilteringCursorWrapper( - cursor, mState.acceptMimes, new String[] { Document.MIME_TYPE_DIR }) { + cursor, mState.acceptMimes, RECENT_REJECT_MIMES, rejectBefore) { @Override public void close() { // Ignored, since we manage cursor lifecycle internally @@ -203,11 +213,22 @@ public class RecentLoader extends AsyncTaskLoader { final DirectoryResult result = new DirectoryResult(); result.sortOrder = SORT_ORDER_LAST_MODIFIED; - if (cursors.size() > 0) { - final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()])); - final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder); - result.cursor = sorted; + // Hint to UI if we're still loading + final Bundle extras = new Bundle(); + if (cursors.size() != mTasks.size()) { + extras.putBoolean(DocumentsContract.EXTRA_LOADING, true); } + + final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()])); + final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder) { + @Override + public Bundle getExtras() { + return extras; + } + }; + + result.cursor = sorted; + return result; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index c975382b8bae3..39541731eda50 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -66,6 +66,7 @@ import java.util.List; */ public class RecentsCreateFragment extends Fragment { + private View mEmptyView; private ListView mListView; private DocumentStackAdapter mAdapter; @@ -87,6 +88,8 @@ public class RecentsCreateFragment extends Fragment { final View view = inflater.inflate(R.layout.fragment_directory, container, false); + mEmptyView = view.findViewById(android.R.id.empty); + mListView = (ListView) view.findViewById(R.id.list); mListView.setOnItemClickListener(mItemListener); @@ -189,6 +192,13 @@ public class RecentsCreateFragment extends Fragment { public void swapStacks(List stacks) { mStacks = stacks; + + if (isEmpty()) { + mEmptyView.setVisibility(View.VISIBLE); + } else { + mEmptyView.setVisibility(View.GONE); + } + notifyDataSetChanged(); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java index 7386cae9b84e7..4313fa71c4904 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java @@ -33,7 +33,7 @@ import android.util.Log; public class RecentsProvider extends ContentProvider { private static final String TAG = "RecentsProvider"; - public static final long MAX_HISTORY_IN_MILLIS = DateUtils.DAY_IN_MILLIS * 45; + public static final long MAX_HISTORY_IN_MILLIS = 45 * DateUtils.DAY_IN_MILLIS; private static final String AUTHORITY = "com.android.documentsui.recents"; @@ -56,6 +56,7 @@ public class RecentsProvider extends ContentProvider { public static final String TABLE_RESUME = "resume"; public static class RecentColumns { + public static final String KEY = "key"; public static final String STACK = "stack"; public static final String TIMESTAMP = "timestamp"; } @@ -99,16 +100,18 @@ public class RecentsProvider extends ContentProvider { private static final int VERSION_INIT = 1; private static final int VERSION_AS_BLOB = 3; private static final int VERSION_ADD_EXTERNAL = 4; + private static final int VERSION_ADD_RECENT_KEY = 5; public DatabaseHelper(Context context) { - super(context, DB_NAME, null, VERSION_ADD_EXTERNAL); + super(context, DB_NAME, null, VERSION_ADD_RECENT_KEY); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + TABLE_RECENT + " (" + - RecentColumns.STACK + " BLOB PRIMARY KEY ON CONFLICT REPLACE," + + RecentColumns.KEY + " TEXT PRIMARY KEY ON CONFLICT REPLACE," + + RecentColumns.STACK + " BLOB DEFAULT NULL," + RecentColumns.TIMESTAMP + " INTEGER" + ")"); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index 15af8aab45a7c..e3908e9ad5150 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -99,7 +99,8 @@ public class RootsCache { */ public void updateAsync() { // Special root for recents - mRecentsRoot.rootType = Root.ROOT_TYPE_SHORTCUT; + mRecentsRoot.authority = null; + mRecentsRoot.rootId = null; mRecentsRoot.icon = R.drawable.ic_root_recent; mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE; mRecentsRoot.title = mContext.getString(R.string.root_recent); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index d6026226e499a..2fb12bb29233c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -26,7 +26,6 @@ import android.content.Loader; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; -import android.provider.DocumentsContract.Root; import android.text.TextUtils; import android.text.format.Formatter; import android.view.LayoutInflater; @@ -37,16 +36,16 @@ 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; -import com.android.documentsui.SectionedListAdapter.SectionAdapter; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.RootInfo; import com.android.internal.util.Objects; +import com.google.common.collect.Lists; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -56,7 +55,7 @@ import java.util.List; public class RootsFragment extends Fragment { private ListView mList; - private SectionedRootsAdapter mAdapter; + private RootsAdapter mAdapter; private LoaderCallbacks> mCallbacks; @@ -112,7 +111,7 @@ public class RootsFragment extends Fragment { final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS); - mAdapter = new SectionedRootsAdapter(context, result, includeApps); + mAdapter = new RootsAdapter(context, result, includeApps); mList.setAdapter(mAdapter); onCurrentRootChanged(); @@ -154,136 +153,148 @@ public class RootsFragment extends Fragment { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this); - final Object item = mAdapter.getItem(position); - if (item instanceof RootInfo) { - activity.onRootPicked((RootInfo) item, true); - } else if (item instanceof ResolveInfo) { - activity.onAppPicked((ResolveInfo) item); + final Item item = mAdapter.getItem(position); + if (item instanceof RootItem) { + activity.onRootPicked(((RootItem) item).root, true); + } else if (item instanceof AppItem) { + activity.onAppPicked(((AppItem) item).info); } else { throw new IllegalStateException("Unknown root: " + item); } } }; - private static class RootsAdapter extends ArrayAdapter implements SectionAdapter { - public RootsAdapter(Context context) { - super(context, 0); + private static abstract class Item { + private final int mLayoutId; + + public Item(int layoutId) { + mLayoutId = layoutId; + } + + public View getView(View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(parent.getContext()) + .inflate(mLayoutId, parent, false); + } + bindView(convertView); + return convertView; + } + + public abstract void bindView(View convertView); + } + + private static class RootItem extends Item { + public final RootInfo root; + + public RootItem(RootInfo root) { + super(R.layout.item_root); + this.root = root; } @Override - public View getView(int position, View convertView, ViewGroup parent) { - final Context context = parent.getContext(); - if (convertView == null) { - convertView = LayoutInflater.from(context) - .inflate(R.layout.item_root, parent, false); - } - + 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 RootInfo root = getItem(position); + final Context context = convertView.getContext(); icon.setImageDrawable(root.loadIcon(context)); title.setText(root.title); - // Device summary is always available space - final String summaryText; - if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) { + // Show available space if no summary + String summaryText = root.summary; + if (TextUtils.isEmpty(summaryText) && root.availableBytes >= 0) { summaryText = context.getString(R.string.root_available_bytes, Formatter.formatFileSize(context, root.availableBytes)); - } else { - summaryText = root.summary; } summary.setText(summaryText); summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE); - - return convertView; - } - - @Override - public View getHeaderView(View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = new Space(parent.getContext()); - } - return convertView; } } - private static class AppsAdapter extends ArrayAdapter implements SectionAdapter { - public AppsAdapter(Context context) { - super(context, 0); + private static class SpacerItem extends Item { + public SpacerItem() { + super(R.layout.item_root_spacer); } @Override - public View getView(int position, View convertView, ViewGroup parent) { - final Context context = parent.getContext(); - final PackageManager pm = context.getPackageManager(); - if (convertView == null) { - convertView = LayoutInflater.from(context) - .inflate(R.layout.item_root, parent, false); - } + public void bindView(View convertView) { + // Nothing to bind + } + } + private static class AppItem extends Item { + public final ResolveInfo info; + + public AppItem(ResolveInfo info) { + super(R.layout.item_root); + this.info = info; + } + + @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 ResolveInfo info = getItem(position); + final PackageManager pm = convertView.getContext().getPackageManager(); icon.setImageDrawable(info.loadIcon(pm)); title.setText(info.loadLabel(pm)); // TODO: match existing summary behavior from disambig dialog summary.setVisibility(View.GONE); - - return convertView; - } - - @Override - public View getHeaderView(View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_root_header, parent, false); - } - - final TextView title = (TextView) convertView.findViewById(android.R.id.title); - title.setText(R.string.root_type_apps); - - return convertView; } } - private static class SectionedRootsAdapter extends SectionedListAdapter { - private final RootsAdapter mRecent; - private final RootsAdapter mServices; - private final RootsAdapter mShortcuts; - private final RootsAdapter mDevices; - private final AppsAdapter mApps; + private static class RootsAdapter extends ArrayAdapter { + public RootsAdapter(Context context, Collection roots, Intent includeApps) { + super(context, 0); - public SectionedRootsAdapter( - Context context, Collection roots, Intent includeApps) { - mRecent = new RootsAdapter(context); - mServices = new RootsAdapter(context); - mShortcuts = new RootsAdapter(context); - mDevices = new RootsAdapter(context); - mApps = new AppsAdapter(context); + RootItem recents = null; + RootItem images = null; + RootItem videos = null; + RootItem audio = null; + RootItem downloads = null; + + final List clouds = Lists.newArrayList(); + final List locals = Lists.newArrayList(); for (RootInfo root : roots) { - if (root.authority == null) { - mRecent.add(root); - continue; + if (root.isRecents()) { + recents = new RootItem(root); + } else if (root.isExternalStorage()) { + locals.add(root); + } else if (root.isDownloads()) { + downloads = new RootItem(root); + } else if (root.isImages()) { + images = new RootItem(root); + } else if (root.isVideos()) { + videos = new RootItem(root); + } else if (root.isAudio()) { + audio = new RootItem(root); + } else { + clouds.add(root); } + } - switch (root.rootType) { - case Root.ROOT_TYPE_SERVICE: - mServices.add(root); - break; - case Root.ROOT_TYPE_SHORTCUT: - mShortcuts.add(root); - break; - case Root.ROOT_TYPE_DEVICE: - mDevices.add(root); - break; - } + final RootComparator comp = new RootComparator(); + Collections.sort(clouds, comp); + Collections.sort(locals, comp); + + if (recents != null) add(recents); + + for (RootInfo cloud : clouds) { + add(new RootItem(cloud)); + } + + if (images != null) add(images); + if (videos != null) add(videos); + if (audio != null) add(audio); + if (downloads != null) add(downloads); + + for (RootInfo local : locals) { + add(new RootItem(local)); } if (includeApps != null) { @@ -291,35 +302,54 @@ public class RootsFragment extends Fragment { final List infos = pm.queryIntentActivities( includeApps, PackageManager.MATCH_DEFAULT_ONLY); + final List apps = Lists.newArrayList(); + // Omit ourselves from the list for (ResolveInfo info : infos) { if (!context.getPackageName().equals(info.activityInfo.packageName)) { - mApps.add(info); + apps.add(new AppItem(info)); + } + } + + if (apps.size() > 0) { + add(new SpacerItem()); + for (Item item : apps) { + add(item); } } } + } - final RootComparator comp = new RootComparator(); - mServices.sort(comp); - mShortcuts.sort(comp); - mDevices.sort(comp); + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final Item item = getItem(position); + return item.getView(convertView, parent); + } - if (mRecent.getCount() > 0) { - addSection(mRecent); - } - if (mServices.getCount() > 0) { - addSection(mServices); - } - if (mShortcuts.getCount() > 0) { - addSection(mShortcuts); - } - if (mDevices.getCount() > 0) { - addSection(mDevices); - } - if (mApps.getCount() > 0) { - addSection(mApps); + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return getItemViewType(position) != 1; + } + + @Override + public int getItemViewType(int position) { + final Item item = getItem(position); + if (item instanceof RootItem || item instanceof AppItem) { + return 0; + } else { + return 1; } } + + @Override + public int getViewTypeCount() { + return 2; + } } public static class RootComparator implements Comparator { diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java index 57fc7e4eb9e59..1a473080fbb3c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java @@ -213,8 +213,13 @@ public class TestActivity extends Activity { if (DocumentsContract.isDocumentUri(this, uri)) { result += "; DOC_ID"; } - getContentResolver() - .takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + try { + getContentResolver().takePersistableUriPermission( + uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (SecurityException e) { + result += "; FAILED TO TAKE"; + Log.e(TAG, "Failed to take", e); + } InputStream is = null; try { is = getContentResolver().openInputStream(uri); @@ -222,7 +227,7 @@ public class TestActivity extends Activity { result += "; read length=" + length; } catch (Exception e) { result += "; ERROR"; - Log.w(TAG, "Failed to read " + uri, e); + Log.e(TAG, "Failed to read " + uri, e); } finally { IoUtils.closeQuietly(is); } @@ -235,15 +240,20 @@ public class TestActivity extends Activity { if (DocumentsContract.isDocumentUri(this, uri)) { result += "; DOC_ID"; } - getContentResolver() - .takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + try { + getContentResolver().takePersistableUriPermission( + uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } catch (SecurityException e) { + result += "; FAILED TO TAKE"; + Log.e(TAG, "Failed to take", e); + } OutputStream os = null; try { os = getContentResolver().openOutputStream(uri); os.write("THE COMPLETE WORKS OF SHAKESPEARE".getBytes()); } catch (Exception e) { result += "; ERROR"; - Log.w(TAG, "Failed to write " + uri, e); + Log.e(TAG, "Failed to write " + uri, e); } finally { IoUtils.closeQuietly(os); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java index 0a378c07532a7..28bab6cf4966e 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java @@ -71,6 +71,25 @@ public class DocumentStack extends LinkedList implements Durable { } } + /** + * Build key that uniquely identifies this stack. It omits most of the raw + * details included in {@link #write(DataOutputStream)}, since they change + * too regularly to be used as a key. + */ + public String buildKey() { + final StringBuilder builder = new StringBuilder(); + if (root != null) { + builder.append(root.authority).append('#'); + builder.append(root.rootId).append('#'); + } else { + builder.append("[null]").append('#'); + } + for (DocumentInfo doc : this) { + builder.append(doc.documentId).append('#'); + } + return builder.toString(); + } + @Override public void reset() { clear(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java index 014901a2937f0..e220c9e382fb1 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java @@ -42,10 +42,10 @@ import java.util.Objects; */ public class RootInfo implements Durable, Parcelable { private static final int VERSION_INIT = 1; + private static final int VERSION_DROP_TYPE = 2; public String authority; public String rootId; - public int rootType; public int flags; public int icon; public String title; @@ -67,7 +67,6 @@ public class RootInfo implements Durable, Parcelable { public void reset() { authority = null; rootId = null; - rootType = 0; flags = 0; icon = 0; title = null; @@ -85,10 +84,9 @@ public class RootInfo implements Durable, Parcelable { public void read(DataInputStream in) throws IOException { final int version = in.readInt(); switch (version) { - case VERSION_INIT: + case VERSION_DROP_TYPE: authority = DurableUtils.readNullableString(in); rootId = DurableUtils.readNullableString(in); - rootType = in.readInt(); flags = in.readInt(); icon = in.readInt(); title = DurableUtils.readNullableString(in); @@ -105,10 +103,9 @@ public class RootInfo implements Durable, Parcelable { @Override public void write(DataOutputStream out) throws IOException { - out.writeInt(VERSION_INIT); + out.writeInt(VERSION_DROP_TYPE); DurableUtils.writeNullableString(out, authority); DurableUtils.writeNullableString(out, rootId); - out.writeInt(rootType); out.writeInt(flags); out.writeInt(icon); DurableUtils.writeNullableString(out, title); @@ -146,7 +143,6 @@ public class RootInfo implements Durable, Parcelable { final RootInfo root = new RootInfo(); root.authority = authority; root.rootId = getCursorString(cursor, Root.COLUMN_ROOT_ID); - root.rootType = getCursorInt(cursor, Root.COLUMN_ROOT_TYPE); root.flags = getCursorInt(cursor, Root.COLUMN_FLAGS); root.icon = getCursorInt(cursor, Root.COLUMN_ICON); root.title = getCursorString(cursor, Root.COLUMN_TITLE); @@ -162,25 +158,44 @@ public class RootInfo implements Durable, Parcelable { derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null; // TODO: remove these special case icons - if ("com.android.externalstorage.documents".equals(authority)) { - if ("documents".equals(rootId)) { - derivedIcon = R.drawable.ic_doc_text; - } else { - derivedIcon = R.drawable.ic_root_sdcard; - } - } - if ("com.android.providers.downloads.documents".equals(authority)) { + if (isExternalStorage()) { + derivedIcon = R.drawable.ic_root_sdcard; + } else if (isDownloads()) { derivedIcon = R.drawable.ic_root_download; + } else if (isImages()) { + derivedIcon = R.drawable.ic_doc_image; + } else if (isVideos()) { + derivedIcon = R.drawable.ic_doc_video; + } else if (isAudio()) { + derivedIcon = R.drawable.ic_doc_audio; } - if ("com.android.providers.media.documents".equals(authority)) { - if ("images_root".equals(rootId)) { - derivedIcon = R.drawable.ic_doc_image; - } else if ("videos_root".equals(rootId)) { - derivedIcon = R.drawable.ic_doc_video; - } else if ("audio_root".equals(rootId)) { - derivedIcon = R.drawable.ic_doc_audio; - } - } + } + + public boolean isRecents() { + return authority == null && rootId == null; + } + + public boolean isExternalStorage() { + return "com.android.externalstorage.documents".equals(authority); + } + + public boolean isDownloads() { + return "com.android.providers.downloads.documents".equals(authority); + } + + public boolean isImages() { + return "com.android.providers.media.documents".equals(authority) + && "images_root".equals(rootId); + } + + public boolean isVideos() { + return "com.android.providers.media.documents".equals(authority) + && "videos_root".equals(rootId); + } + + public boolean isAudio() { + return "com.android.providers.media.documents".equals(authority) + && "audio_root".equals(rootId); } @Override diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index ed28da5225e9e..9328b33a73aea 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -47,9 +47,8 @@ public class ExternalStorageProvider extends DocumentsProvider { // docId format: root:path/to/file private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { - Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON, - Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, - Root.COLUMN_AVAILABLE_BYTES, + Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, + Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, }; private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { @@ -59,7 +58,6 @@ public class ExternalStorageProvider extends DocumentsProvider { private static class RootInfo { public String rootId; - public int rootType; public int flags; public String title; public String docId; @@ -84,7 +82,6 @@ public class ExternalStorageProvider extends DocumentsProvider { final RootInfo root = new RootInfo(); root.rootId = rootId; - root.rootType = Root.ROOT_TYPE_DEVICE; root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED | Root.FLAG_SUPPORTS_SEARCH; root.title = getContext().getString(R.string.root_internal_storage); @@ -198,7 +195,6 @@ public class ExternalStorageProvider extends DocumentsProvider { final RowBuilder row = result.newRow(); row.add(Root.COLUMN_ROOT_ID, root.rootId); - row.add(Root.COLUMN_ROOT_TYPE, root.rootType); row.add(Root.COLUMN_FLAGS, root.flags); row.add(Root.COLUMN_TITLE, root.title); row.add(Root.COLUMN_DOCUMENT_ID, root.docId); diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java index e6fbb1b762e08..5a15cd2dacdc8 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java @@ -65,7 +65,7 @@ public class TestDocumentsProvider extends DocumentsProvider { private static final String MY_DOC_NULL = "myNull"; private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { - Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON, + Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES, }; @@ -114,7 +114,6 @@ public class TestDocumentsProvider extends DocumentsProvider { final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); final RowBuilder row = result.newRow(); row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID); - row.add(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SERVICE); row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS); row.add(Root.COLUMN_TITLE, "_Test title which is really long"); row.add(Root.COLUMN_SUMMARY,