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,