Merge "New roots UX, async, performance, docs." into klp-dev
@@ -20988,7 +20988,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
|
||||
@@ -21005,18 +21004,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 {
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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}.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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".
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 184 B |
|
After Width: | Height: | Size: 119 B |
|
After Width: | Height: | Size: 150 B |
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 163 B |
|
After Width: | Height: | Size: 119 B |
|
After Width: | Height: | Size: 132 B |
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 193 B |
|
After Width: | Height: | Size: 119 B |
|
After Width: | Height: | Size: 154 B |
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 233 B |
|
After Width: | Height: | Size: 119 B |
|
After Width: | Height: | Size: 156 B |
@@ -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">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="@dimen/icon_size"
|
||||
android:layout_height="@dimen/icon_size"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_gravity="center_vertical">
|
||||
android:layout_marginEnd="20dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_mime"
|
||||
@@ -49,11 +50,11 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<!-- This is the one special case where we want baseline alignment! -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
@@ -47,7 +48,7 @@
|
||||
<com.android.documentsui.DirectoryContainerView
|
||||
android:id="@+id/container_directory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dip"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<FrameLayout
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<com.android.documentsui.DirectoryContainerView
|
||||
android:id="@+id/container_directory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dip"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<FrameLayout
|
||||
|
||||
@@ -18,4 +18,4 @@
|
||||
android:id="@android:id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="@null" />
|
||||
android:divider="@drawable/ic_drawer_hairline_divider" />
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
<EditText
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="0dip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:singleLine="true"
|
||||
|
||||
@@ -28,37 +28,25 @@
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dip"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="#fff">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_mime"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_thumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
</FrameLayout>
|
||||
android:background="#fff"
|
||||
android:foreground="@drawable/ic_grid_gradient_bg"
|
||||
android:foregroundGravity="fill">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_mime"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_grid_gradient_bg"
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_thumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -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">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:minWidth="80dp"
|
||||
android:layout_weight="0.5"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:textAlignment="viewStart"
|
||||
@@ -112,26 +103,20 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="0.5"
|
||||
android:layout_marginStart="8dp"
|
||||
android:minWidth="80dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:textAlignment="viewStart"
|
||||
style="@style/TextAppearance.Small" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageView
|
||||
android:id="@android:id/icon2"
|
||||
android:layout_width="@dimen/root_icon_size"
|
||||
android:layout_height="@dimen/root_icon_size"
|
||||
android:layout_marginStart="8dip"
|
||||
android:layout_marginStart="8dp"
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@null"
|
||||
android:visibility="gone" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="@dimen/icon_size"
|
||||
android:layout_height="@dimen/icon_size"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_gravity="center_vertical">
|
||||
android:layout_marginEnd="20dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_mime"
|
||||
@@ -50,20 +51,20 @@
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/title"
|
||||
android:layout_width="0dip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:singleLine="true"
|
||||
@@ -75,7 +76,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" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="0.25"
|
||||
android:minWidth="70dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
@@ -100,11 +103,11 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:minWidth="70dp"
|
||||
android:layout_weight="0.25"
|
||||
android:layout_marginStart="8dp"
|
||||
android:minWidth="70dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:textAlignment="viewStart"
|
||||
@@ -114,8 +117,7 @@
|
||||
android:id="@android:id/summary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="0.5"
|
||||
android:layout_marginStart="8dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingTop="8dip"
|
||||
android:paddingBottom="8dip"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ProgressBar
|
||||
|
||||
@@ -20,9 +20,8 @@
|
||||
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">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -21,15 +21,16 @@
|
||||
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:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<ImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="@android:dimen/app_icon_size"
|
||||
android:layout_height="@android:dimen/app_icon_size"
|
||||
android:layout_marginEnd="8dip"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
@@ -22,13 +22,14 @@
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false"
|
||||
android:background="@drawable/item_root">
|
||||
|
||||
<ImageView
|
||||
android:id="@android:id/icon"
|
||||
android:layout_width="@dimen/icon_size"
|
||||
android:layout_height="@dimen/icon_size"
|
||||
android:layout_marginEnd="8dip"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
|
||||
20
packages/DocumentsUI/res/layout/item_root_spacer.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2013 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_drawer_tall_divider" />
|
||||
@@ -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">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/subdir"
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
@@ -35,6 +36,8 @@ import android.widget.Toast;
|
||||
|
||||
import com.android.documentsui.model.DocumentInfo;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Dialog to create a new directory.
|
||||
*/
|
||||
@@ -64,24 +67,45 @@ public class CreateDirectoryFragment extends DialogFragment {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final String displayName = text1.getText().toString();
|
||||
|
||||
final DocumentsActivity activity = (DocumentsActivity) getActivity();
|
||||
final DocumentInfo cwd = activity.getCurrentDirectory();
|
||||
|
||||
try {
|
||||
final Uri childUri = DocumentsContract.createDocument(
|
||||
resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, displayName);
|
||||
|
||||
// Navigate into newly created child
|
||||
final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
|
||||
activity.onDocumentPicked(childDoc);
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(context, R.string.create_error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
new CreateDirectoryTask(displayName).execute();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private class CreateDirectoryTask extends AsyncTask<Void, Void, DocumentInfo> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ class DirectoryResult implements AutoCloseable {
|
||||
}
|
||||
|
||||
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
|
||||
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<DirectoryResult> {
|
||||
|
||||
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);
|
||||
|
||||
@@ -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<Void, Void, DocumentInfo> {
|
||||
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<Void, Void, Uri> {
|
||||
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<Void, Void, Void> {
|
||||
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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<DirectoryResult> {
|
||||
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<DirectoryResult> {
|
||||
}
|
||||
}
|
||||
|
||||
final long rejectBefore = System.currentTimeMillis() - REJECT_OLDER_THAN;
|
||||
|
||||
// Collect all finished tasks
|
||||
List<Cursor> cursors = Lists.newArrayList();
|
||||
for (RecentTask task : mTasks.values()) {
|
||||
@@ -180,7 +190,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
||||
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<DirectoryResult> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<DocumentStack> stacks) {
|
||||
mStacks = stacks;
|
||||
|
||||
if (isEmpty()) {
|
||||
mEmptyView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mEmptyView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -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" +
|
||||
")");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Collection<RootInfo>> 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<RootInfo> 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<ResolveInfo> 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<Item> {
|
||||
public RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps) {
|
||||
super(context, 0);
|
||||
|
||||
public SectionedRootsAdapter(
|
||||
Context context, Collection<RootInfo> 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<RootInfo> clouds = Lists.newArrayList();
|
||||
final List<RootInfo> 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<ResolveInfo> infos = pm.queryIntentActivities(
|
||||
includeApps, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
|
||||
final List<AppItem> 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<RootInfo> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -71,6 +71,25 @@ public class DocumentStack extends LinkedList<DocumentInfo> 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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||