Merge "Style/color fixes to match UX spec." into nyc-andromeda-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 65c7cdc..286871b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -44,7 +44,7 @@
<activity
android:name=".files.LauncherActivity"
- android:label="@string/downloads_label"
+ android:label="@string/app_label"
android:icon="@drawable/files_icon"
android:theme="@android:style/Theme.NoDisplay">
</activity>
@@ -53,7 +53,7 @@
<activity-alias
android:name=".Launcher"
android:targetActivity=".files.LauncherActivity"
- android:label="@string/downloads_label"
+ android:label="@string/app_label"
android:icon="@drawable/files_icon" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -63,7 +63,7 @@
<activity
android:name=".files.FilesActivity"
- android:label="@string/downloads_label"
+ android:label="@string/app_label"
android:icon="@drawable/files_icon"
android:documentLaunchMode="intoExisting"
android:theme="@style/DocumentsTheme">
diff --git a/res/layout/document_debug_info.xml b/res/layout/document_debug_info.xml
new file mode 100644
index 0000000..83664af
--- /dev/null
+++ b/res/layout/document_debug_info.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+
+<com.android.documentsui.ui.DocumentDebugInfo
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/grid_item_margin"
+ android:textAlignment="viewStart"
+ android:typeface="monospace"
+ android:textSize="11sp"
+ android:textColor="#FF000000" />
diff --git a/res/layout/item_dir_grid.xml b/res/layout/item_dir_grid.xml
index 36af9b9..917f3f0 100644
--- a/res/layout/item_dir_grid.xml
+++ b/res/layout/item_dir_grid.xml
@@ -15,70 +15,83 @@
limitations under the License.
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/grid_item_margin"
+ android:orientation="vertical"
android:background="@color/item_doc_background"
android:elevation="@dimen/grid_item_elevation"
- android:focusable="true" >
+ android:focusable="true">
- <!-- The height is 48px.
- paddingTop (9dp) + @dimen/check_icon_size (30dp) + paddingBottom (9dp) -->
- <LinearLayout
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal"
- android:paddingBottom="9dp"
- android:paddingLeft="9dp"
- android:paddingRight="12dp"
- android:paddingTop="9dp"
- android:gravity="center_vertical">
+ android:layout_height="wrap_content">
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp" >
+ <!-- The height is 48px.
+ paddingTop (9dp) + @dimen/check_icon_size (30dp) + paddingBottom (9dp) -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:paddingBottom="9dp"
+ android:paddingLeft="9dp"
+ android:paddingRight="12dp"
+ android:paddingTop="9dp"
+ android:gravity="center_vertical">
- <ImageView
- android:id="@+id/icon_mime_sm"
+ <FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:contentDescription="@null"
- android:scaleType="centerInside"
- android:src="@drawable/ic_doc_folder" />
+ android:layout_marginEnd="8dp" >
- <ImageView
- android:id="@+id/icon_check"
- android:layout_width="@dimen/check_icon_size"
- android:layout_height="@dimen/check_icon_size"
- android:alpha="0"
- android:contentDescription="@null"
- android:scaleType="fitCenter"
- android:src="@drawable/ic_check_circle" />
+ <ImageView
+ android:id="@+id/icon_mime_sm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:contentDescription="@null"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_doc_folder" />
- </FrameLayout>
+ <ImageView
+ android:id="@+id/icon_check"
+ android:layout_width="@dimen/check_icon_size"
+ android:layout_height="@dimen/check_icon_size"
+ android:alpha="0"
+ android:contentDescription="@null"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_check_circle" />
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:singleLine="true"
- android:textAlignment="viewStart"
- android:textAppearance="@android:style/TextAppearance.Material.Subhead"
- android:textColor="@color/item_title" />
+ </FrameLayout>
- </LinearLayout>
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="@color/item_title" />
- <!-- An overlay that draws the item border when it is focused. -->
+ </LinearLayout>
- <View
+ <!-- An overlay that draws the item border when it is focused. -->
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/item_doc_grid_border"
+ android:contentDescription="@null"
+ android:duplicateParentState="true" />
+
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/debug_info"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/item_doc_grid_border"
- android:contentDescription="@null"
- android:duplicateParentState="true" />
+ android:layout_height="wrap_content"
+ android:background="#FFEFEFEF" />
-</FrameLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/item_doc_grid.xml b/res/layout/item_doc_grid.xml
index 0fa9685..5b8ba30 100644
--- a/res/layout/item_doc_grid.xml
+++ b/res/layout/item_doc_grid.xml
@@ -14,126 +14,138 @@
limitations under the License.
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/grid_item_margin"
+ android:orientation="vertical"
android:background="@color/item_doc_background"
android:elevation="@dimen/grid_item_elevation"
android:focusable="true">
- <!-- Main item thumbnail. Comprised of two overlapping images, the
- visibility of which is controlled by code in
- DirectoryFragment.java. -->
-
- <FrameLayout
- android:id="@+id/thumbnail"
+ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <com.android.documentsui.GridItemThumbnail
- android:id="@+id/icon_thumb"
+ <!-- Main item thumbnail. Comprised of two overlapping images, the
+ visibility of which is controlled by code in
+ DirectoryFragment.java. -->
+
+ <FrameLayout
+ android:id="@+id/thumbnail"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <com.android.documentsui.GridItemThumbnail
+ android:id="@+id/icon_thumb"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scaleType="centerCrop"
+ android:contentDescription="@null"
+ android:tint="@color/item_doc_grid_tint"
+ android:tintMode="src_over" />
+
+ <com.android.documentsui.GridItemThumbnail
+ android:id="@+id/icon_mime_lg"
+ android:layout_width="@dimen/icon_size"
+ android:layout_height="@dimen/icon_size"
+ android:layout_gravity="center"
+ android:scaleType="fitCenter"
+ android:contentDescription="@null" />
+
+ </FrameLayout>
+
+ <!-- Item nameplate. Has a mime-type icon and some text fields (title,
+ size, mod-time, etc). -->
+
+ <RelativeLayout
+ android:id="@+id/nameplate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:scaleType="centerCrop"
+ android:layout_below="@id/thumbnail"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:paddingLeft="12dp"
+ android:paddingRight="12dp">
+
+ <ImageView
+ android:id="@+id/icon_mime_sm"
+ android:layout_width="@dimen/grid_item_icon_size"
+ android:layout_height="@dimen/grid_item_icon_size"
+ android:layout_marginEnd="8dp"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:scaleType="center"
+ android:contentDescription="@null"/>
+
+ <ImageView
+ android:id="@+id/icon_check"
+ android:src="@drawable/ic_check_circle"
+ android:alpha="0"
+ android:layout_width="@dimen/check_icon_size"
+ android:layout_height="@dimen/check_icon_size"
+ android:layout_marginEnd="8dp"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:scaleType="fitCenter"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@id/icon_mime_sm"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="@color/item_title" />
+
+ <TextView
+ android:id="@+id/details"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@id/icon_mime_sm"
+ android:layout_below="@android:id/title"
+ android:layout_marginEnd="4dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Caption"
+ android:textColor="@color/item_details" />
+
+ <TextView
+ android:id="@+id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_toEndOf="@id/details"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Caption"
+ android:textColor="@color/item_details" />
+
+ </RelativeLayout>
+
+ <!-- An overlay that draws the item border when it is focused. -->
+ <View
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBottom="@id/nameplate"
+ android:layout_alignTop="@id/thumbnail"
+ android:layout_alignLeft="@id/thumbnail"
+ android:layout_alignRight="@id/thumbnail"
android:contentDescription="@null"
- android:tint="@color/item_doc_grid_tint"
- android:tintMode="src_over" />
-
- <com.android.documentsui.GridItemThumbnail
- android:id="@+id/icon_mime_lg"
- android:layout_width="@dimen/icon_size"
- android:layout_height="@dimen/icon_size"
- android:layout_gravity="center"
- android:scaleType="fitCenter"
- android:contentDescription="@null" />
-
- </FrameLayout>
-
- <!-- Item nameplate. Has a mime-type icon and some text fields (title,
- size, mod-time, etc). -->
-
- <RelativeLayout
- android:id="@+id/nameplate"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/thumbnail"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:paddingLeft="12dp"
- android:paddingRight="12dp">
-
- <ImageView
- android:id="@+id/icon_mime_sm"
- android:layout_width="@dimen/grid_item_icon_size"
- android:layout_height="@dimen/grid_item_icon_size"
- android:layout_marginEnd="8dp"
- android:layout_alignParentStart="true"
- android:layout_centerVertical="true"
- android:scaleType="center"
- android:contentDescription="@null"/>
-
- <ImageView
- android:id="@+id/icon_check"
- android:src="@drawable/ic_check_circle"
- android:alpha="0"
- android:layout_width="@dimen/check_icon_size"
- android:layout_height="@dimen/check_icon_size"
- android:layout_marginEnd="8dp"
- android:layout_alignParentStart="true"
- android:layout_centerVertical="true"
- android:scaleType="fitCenter"
- android:contentDescription="@null"/>
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_toEndOf="@id/icon_mime_sm"
- android:singleLine="true"
- android:ellipsize="end"
- android:textAlignment="viewStart"
- android:textAppearance="@android:style/TextAppearance.Material.Subhead"
- android:textColor="@color/item_title" />
-
- <TextView
- android:id="@+id/details"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toEndOf="@id/icon_mime_sm"
- android:layout_below="@android:id/title"
- android:layout_marginEnd="4dp"
- android:singleLine="true"
- android:ellipsize="end"
- android:textAlignment="viewStart"
- android:textAppearance="@android:style/TextAppearance.Material.Caption"
- android:textColor="@color/item_details" />
-
- <TextView
- android:id="@+id/date"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@android:id/title"
- android:layout_toEndOf="@id/details"
- android:singleLine="true"
- android:ellipsize="end"
- android:textAlignment="viewStart"
- android:textAppearance="@android:style/TextAppearance.Material.Caption"
- android:textColor="@color/item_details" />
+ android:background="@drawable/item_doc_grid_border"
+ android:duplicateParentState="true" />
</RelativeLayout>
- <!-- An overlay that draws the item border when it is focused. -->
- <View
- android:layout_width="wrap_content"
+ <FrameLayout
+ android:id="@+id/debug_info"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignBottom="@id/nameplate"
- android:layout_alignTop="@id/thumbnail"
- android:layout_alignLeft="@id/thumbnail"
- android:layout_alignRight="@id/thumbnail"
- android:contentDescription="@null"
- android:background="@drawable/item_doc_grid_border"
- android:duplicateParentState="true" />
-
-</RelativeLayout>
+ android:background="#FFEFEFEF" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index ec8c544..17b9574 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -15,16 +15,13 @@
-->
<resources>
- <!-- Allow Advanced Devices default value to be customised -->
- <bool name="config_defaultAdvancedDevices">false</bool>
-
<!-- Intentionally unset. Vendors should set this in an overlay. -->
<string name="trusted_quick_viewer_package" translatable="false"></string>
- <!-- Flags setup as productivity oriented in which case Downloads app will be presented
- as Files app. Including showing of the Documents and "advanced" roots. -->
- <bool name="productivity_device">false</bool>
+ <!-- Enable productivity oriented features like "Documents" root, and new window view. -->
+ <bool name="productivity_device">true</bool>
- <!-- Indicates if search view is taking the whole toolbar space -->
+ <!-- Indicates if search view is taking the whole toolbar space. On larger
+ layouts we reduce this to an input-box adjacent to menu actions. -->
<bool name="full_bar_search_view">true</bool>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index ce6f247..ecd598e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -24,10 +24,9 @@
<dimen name="list_item_thumbnail_size">40dp</dimen>
<dimen name="grid_item_icon_size">30dp</dimen>
<dimen name="progress_bar_height">4dp</dimen>
+ <fraction name="grid_scale_min">85%</fraction>
+ <fraction name="grid_scale_max">200%</fraction>
<dimen name="grid_width">152dp</dimen>
- <dimen name="grid_height">176dp</dimen>
- <dimen name="grid_item_width">152dp</dimen>
- <dimen name="grid_item_height">176dp</dimen>
<dimen name="grid_section_separator_height">0dp</dimen>
<dimen name="grid_item_margin">6dp</dimen>
<dimen name="grid_padding_horiz">4dp</dimen>
diff --git a/res/values/drawables.xml b/res/values/drawables.xml
index 3cb6c76..2d2acbb 100644
--- a/res/values/drawables.xml
+++ b/res/values/drawables.xml
@@ -16,6 +16,6 @@
<resources>
<item name="app_icon" type="drawable">@mipmap/ic_app_icon</item>
- <item name="files_icon" type="drawable">@mipmap/ic_launcher_downloads</item>
+ <item name="files_icon" type="drawable">@mipmap/ic_app_icon</item>
<item name="picker_icon" type="drawable">@drawable/ic_doc_text</item>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1fbf564..ebb92f3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -18,9 +18,6 @@
<!-- Title of the documents application [CHAR LIMIT=32] -->
<string name="app_label">Files</string>
- <!-- Title of the standalone downloads activity. [CHAR LIMIT=32] -->
- <string name="downloads_label">Downloads</string>
-
<!-- Action bar title prompting user to choose a location to open a document from [CHAR LIMIT=32] -->
<string name="title_open">Open from</string>
<!-- Action bar title prompting user to choose a location to save a document to [CHAR LIMIT=32] -->
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index f24085d..e9360ab 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -303,7 +303,7 @@
Shared.mustShowDeviceRoot(intent) || getScopedPreferences().getShowDeviceRoot();
// Only show the toggle if advanced isn't forced enabled.
- state.showAdvancedOption = !Shared.mustShowDeviceRoot(intent);
+ state.showDeviceStorageOption = !Shared.mustShowDeviceRoot(intent);
if (DEBUG) Log.d(mTag, "Created new state object: " + state);
@@ -431,6 +431,12 @@
}
mNavigator.update();
+ // Causes talkback to announce the activity's new title
+ if (mState.stack.isRecents()) {
+ setTitle(mRoots.getRecentsRoot().title);
+ } else {
+ setTitle(mState.stack.getTitle());
+ }
invalidateOptionsMenu();
}
@@ -732,7 +738,7 @@
});
}
- public final class RetainedState {
+ public static final class RetainedState {
public @Nullable Selection selection;
public boolean hasSelection() {
diff --git a/src/com/android/documentsui/DirectoryReloadLock.java b/src/com/android/documentsui/DirectoryReloadLock.java
index 8033bb7..b44a963 100644
--- a/src/com/android/documentsui/DirectoryReloadLock.java
+++ b/src/com/android/documentsui/DirectoryReloadLock.java
@@ -16,8 +16,11 @@
package com.android.documentsui;
+import static com.android.documentsui.base.Shared.DEBUG;
+
import android.annotation.MainThread;
import android.annotation.Nullable;
+import android.util.Log;
import com.android.documentsui.base.Shared;
import com.android.documentsui.selection.BandController;
@@ -27,6 +30,8 @@
* while Band Selection is active.
*/
public final class DirectoryReloadLock {
+ private static final String TAG = "DirectoryReloadLock";
+
private int mPauseCount = 0;
private @Nullable Runnable mCallback;
@@ -37,6 +42,7 @@
public void block() {
Shared.checkMainLoop();
mPauseCount++;
+ if (DEBUG) Log.v(TAG, "Block count increments to " + mPauseCount + ".");
}
/**
@@ -46,7 +52,9 @@
@MainThread
public void unblock() {
Shared.checkMainLoop();
+ assert(mPauseCount > 0);
mPauseCount--;
+ if (DEBUG) Log.v(TAG, "Block count decrements to " + mPauseCount + ".");
if (mPauseCount == 0 && mCallback != null) {
mCallback.run();
mCallback = null;
diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java
index e261c9f..0d9b188 100644
--- a/src/com/android/documentsui/MenuManager.java
+++ b/src/com/android/documentsui/MenuManager.java
@@ -203,8 +203,8 @@
}
protected void updateAdvanced(MenuItem advanced) {
- advanced.setVisible(mState.showAdvancedOption);
- advanced.setTitle(mState.showAdvancedOption && mState.showAdvanced
+ advanced.setVisible(mState.showDeviceStorageOption);
+ advanced.setTitle(mState.showDeviceStorageOption && mState.showAdvanced
? R.string.menu_advanced_hide : R.string.menu_advanced_show);
}
@@ -303,10 +303,6 @@
mActivity = activity;
}
- public boolean shouldShowFancyFeatures() {
- return Shared.shouldShowFancyFeatures(mActivity);
- }
-
public boolean hasRootSettings() {
return mActivity.getCurrentRoot().hasSettings();
}
diff --git a/src/com/android/documentsui/archives/ArchiveId.java b/src/com/android/documentsui/archives/ArchiveId.java
index d136de1..ae23bce 100644
--- a/src/com/android/documentsui/archives/ArchiveId.java
+++ b/src/com/android/documentsui/archives/ArchiveId.java
@@ -18,7 +18,7 @@
import android.net.Uri;
-class ArchiveId {
+public class ArchiveId {
private final static char DELIMITER = '#';
public final Uri mArchiveUri;
diff --git a/src/com/android/documentsui/archives/ArchivesProvider.java b/src/com/android/documentsui/archives/ArchivesProvider.java
index 1ebe427..fb96337 100644
--- a/src/com/android/documentsui/archives/ArchivesProvider.java
+++ b/src/com/android/documentsui/archives/ArchivesProvider.java
@@ -21,10 +21,11 @@
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.database.Cursor;
-import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
+import android.database.MatrixCursor;
import android.graphics.Point;
import android.net.Uri;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract.Document;
@@ -40,13 +41,9 @@
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
-import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.Callable;
import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Provides basic implementation for creating, extracting and accessing
@@ -95,9 +92,23 @@
public Cursor queryChildDocuments(String documentId, @Nullable String[] projection,
@Nullable String sortOrder)
throws FileNotFoundException {
+ final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
Loader loader = null;
try {
loader = obtainInstance(documentId);
+ if (loader.mArchive == null) {
+ final MatrixCursor cursor = new MatrixCursor(
+ projection != null ? projection : Archive.DEFAULT_PROJECTION);
+ // Return an empty cursor with EXTRA_LOADING, which shows spinner
+ // in DocumentsUI. Once the archive is loaded, the notification will
+ // be sent, and the directory reloaded.
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean(DocumentsContract.EXTRA_LOADING, true);
+ cursor.setExtras(bundle);
+ cursor.setNotificationUri(getContext().getContentResolver(),
+ buildUriForArchive(archiveId.mArchiveUri));
+ return cursor;
+ }
return loader.get().queryChildDocuments(documentId, projection, sortOrder);
} finally {
releaseInstance(loader);
@@ -138,19 +149,26 @@
throws FileNotFoundException {
final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
if (archiveId.mPath.equals("/")) {
- // For the archive's root directory return hard-coded cursor, so clients know that
- // it's actually a directory and queryChildDocuments() can be called on it.
- //
- // TODO: Move this code to the Archive class, once opening archives is moved to
- // background.
- final MatrixCursor cursor = new MatrixCursor(
- projection != null ? projection : Archive.DEFAULT_PROJECTION);
- final RowBuilder row = cursor.newRow();
- row.add(Document.COLUMN_DOCUMENT_ID, documentId);
- row.add(Document.COLUMN_DISPLAY_NAME, "Archive"); // TODO: Fix.
- row.add(Document.COLUMN_SIZE, 0);
- row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
- return cursor;
+ try (final Cursor archiveCursor = getContext().getContentResolver().query(
+ archiveId.mArchiveUri,
+ new String[] { Document.COLUMN_DISPLAY_NAME },
+ null, null, null, null)) {
+ if (archiveCursor == null || !archiveCursor.moveToFirst()) {
+ throw new FileNotFoundException(
+ "Cannot resolve display name of the archive.");
+ }
+ final String displayName = archiveCursor.getString(
+ archiveCursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME));
+
+ final MatrixCursor cursor = new MatrixCursor(
+ projection != null ? projection : Archive.DEFAULT_PROJECTION);
+ final RowBuilder row = cursor.newRow();
+ row.add(Document.COLUMN_DOCUMENT_ID, documentId);
+ row.add(Document.COLUMN_DISPLAY_NAME, displayName);
+ row.add(Document.COLUMN_SIZE, 0);
+ row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
+ return cursor;
+ }
}
Loader loader = null;
@@ -269,47 +287,4 @@
mArchives.put(id.mArchiveUri, loader);
return loader;
}
-
- /**
- * Loads an instance of Archive lazily.
- */
- private static final class Loader {
- private final Context mContext;
- private final Uri mArchiveUri;
- private final Uri mNotificationUri;
- private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
- private Archive mArchive = null;
-
- Loader(Context context, Uri archiveUri, Uri notificationUri) {
- this.mContext = context;
- this.mArchiveUri = archiveUri;
- this.mNotificationUri = notificationUri;
- }
-
- synchronized Archive get() throws FileNotFoundException {
- if (mArchive != null) {
- return mArchive;
- }
-
- try {
- mArchive = Archive.createForParcelFileDescriptor(
- mContext,
- mContext.getContentResolver().openFileDescriptor(
- mArchiveUri, "r", null /* signal */),
- mArchiveUri, mNotificationUri);
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
-
- return mArchive;
- }
-
- Lock getReadLock() {
- return mLock.readLock();
- }
-
- Lock getWriteLock() {
- return mLock.writeLock();
- }
- }
}
diff --git a/src/com/android/documentsui/archives/Loader.java b/src/com/android/documentsui/archives/Loader.java
new file mode 100644
index 0000000..1acdc15
--- /dev/null
+++ b/src/com/android/documentsui/archives/Loader.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.archives;
+
+import android.content.Context;
+import android.net.Uri;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Loads an instance of Archive lazily.
+ */
+public class Loader {
+ private final Context mContext;
+ private final Uri mArchiveUri;
+ private final Uri mNotificationUri;
+ private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+ private Exception mFailureException = null;
+ public Archive mArchive = null;
+
+ Loader(Context context, Uri archiveUri, Uri notificationUri) {
+ this.mContext = context;
+ this.mArchiveUri = archiveUri;
+ this.mNotificationUri = notificationUri;
+
+ // Start loading the archive immediately in the background.
+ mExecutor.submit(this::get);
+ }
+
+ synchronized Archive get() throws FileNotFoundException {
+ if (mArchive != null) {
+ return mArchive;
+ }
+
+ // Once loading the archive failed, do not to retry opening it until the
+ // archive file has changed (the loader is deleted once we receive
+ // a notification about the archive file being changed).
+ if (mFailureException != null) {
+ throw new IllegalStateException(
+ "Trying to perform an operation on an archive which failed to load.",
+ mFailureException);
+ }
+
+ try {
+ mArchive = Archive.createForParcelFileDescriptor(
+ mContext,
+ mContext.getContentResolver().openFileDescriptor(
+ mArchiveUri, "r", null /* signal */),
+ mArchiveUri, mNotificationUri);
+ } catch (IOException e) {
+ mFailureException = e;
+ throw new IllegalStateException(e);
+ } catch (RuntimeException e) {
+ mFailureException = e;
+ throw e;
+ } finally {
+ // Notify observers that the root directory is loaded (or failed)
+ // so clients reload it.
+ mContext.getContentResolver().notifyChange(
+ ArchivesProvider.buildUriForArchive(mArchiveUri),
+ null /* observer */, false /* syncToNetwork */);
+ }
+ return mArchive;
+ }
+
+ Lock getReadLock() {
+ return mLock.readLock();
+ }
+
+ Lock getWriteLock() {
+ return mLock.writeLock();
+ }
+}
diff --git a/src/com/android/documentsui/base/DebugFlags.java b/src/com/android/documentsui/base/DebugFlags.java
new file mode 100644
index 0000000..09638bb
--- /dev/null
+++ b/src/com/android/documentsui/base/DebugFlags.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.documentsui.base;
+
+import javax.annotation.Nullable;
+
+/**
+ * Shared values that may be set by {@link DebugCommandProcessor}.
+ */
+public final class DebugFlags {
+
+ private DebugFlags() {}
+
+ private static String mQvPackage;
+ private static boolean sGestureScaleEnabled;
+ private static boolean sDocumentDetailsEnabled;
+
+ public static void setQuickViewer(@Nullable String qvPackage) {
+ mQvPackage = qvPackage;
+ }
+
+ public static @Nullable String getQuickViewer() {
+ return mQvPackage;
+ }
+
+ public static void setDocumentDetailsEnabled(boolean enabled) {
+ sDocumentDetailsEnabled = enabled;
+ }
+
+ public static boolean getDocumentDetailsEnabled() {
+ return sDocumentDetailsEnabled;
+ }
+
+ public static void setGestureScaleEnabled(boolean enabled) {
+ sGestureScaleEnabled = enabled;
+ }
+
+ public static boolean getGestureScaleEnabled() {
+ return sGestureScaleEnabled;
+ }
+}
diff --git a/src/com/android/documentsui/base/DocumentInfo.java b/src/com/android/documentsui/base/DocumentInfo.java
index f9e9b5c..afdaccc 100644
--- a/src/com/android/documentsui/base/DocumentInfo.java
+++ b/src/com/android/documentsui/base/DocumentInfo.java
@@ -31,6 +31,8 @@
import com.android.documentsui.archives.ArchivesProvider;
import com.android.documentsui.roots.RootCursorWrapper;
+import libcore.io.IoUtils;
+
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
@@ -42,8 +44,6 @@
import javax.annotation.Nullable;
-import libcore.io.IoUtils;
-
/**
* Representation of a {@link Document}.
*/
@@ -210,14 +210,15 @@
@Override
public String toString() {
- return "Document{"
+ return "DocumentInfo{"
+ "docId=" + documentId
+ ", name=" + displayName
+ + ", mimeType=" + mimeType
+ ", isContainer=" + isContainer()
+ ", isDirectory=" + isDirectory()
+ ", isArchive=" + isArchive()
+ ", isPartial=" + isPartial()
- + ", isVirtualDocument=" + isVirtualDocument()
+ + ", isVirtual=" + isVirtual()
+ ", isDeleteSupported=" + isDeleteSupported()
+ ", isCreateSupported=" + isCreateSupported()
+ ", isRenameSupported=" + isRenameSupported()
@@ -228,18 +229,10 @@
return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0;
}
- public boolean isThumbnailSupported() {
- return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
- }
-
public boolean isDirectory() {
return Document.MIME_TYPE_DIR.equals(mimeType);
}
- public boolean isGridPreferred() {
- return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0;
- }
-
public boolean isWriteSupported() {
return (flags & Document.FLAG_SUPPORTS_WRITE) != 0;
}
@@ -268,7 +261,7 @@
return isDirectory() || isArchive();
}
- public boolean isVirtualDocument() {
+ public boolean isVirtual() {
return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
}
diff --git a/src/com/android/documentsui/base/DocumentStack.java b/src/com/android/documentsui/base/DocumentStack.java
index 9e0c580..855f55f 100644
--- a/src/com/android/documentsui/base/DocumentStack.java
+++ b/src/com/android/documentsui/base/DocumentStack.java
@@ -33,6 +33,8 @@
import java.util.LinkedList;
import java.util.List;
+import javax.annotation.Nullable;
+
/**
* Representation of a stack of {@link DocumentInfo}, usually the result of a
* user-driven traversal.
@@ -45,7 +47,7 @@
private static final int VERSION_ADD_ROOT = 2;
private LinkedList<DocumentInfo> mList;
- private RootInfo mRoot;
+ private @Nullable RootInfo mRoot;
private boolean mInitialRootChanged;
private boolean mInitialDocChanged;
@@ -78,11 +80,11 @@
}
/**
- * Makes a new shallow copy, and pushes all docs to the new copy in the same order as they're
+ * Makes a new copy, and pushes all docs to the new copy in the same order as they're
* passed as parameters, i.e. the last document will be at the top of the stack.
*/
public DocumentStack(DocumentStack src, DocumentInfo... docs) {
- mList = src.mList;
+ mList = new LinkedList<>(src.mList);
for (DocumentInfo doc : docs) {
mList.addLast(doc);
}
@@ -90,7 +92,7 @@
mRoot = src.mRoot;
}
- public RootInfo getRoot() {
+ public @Nullable RootInfo getRoot() {
return mRoot;
}
diff --git a/src/com/android/documentsui/base/ScopedPreferences.java b/src/com/android/documentsui/base/ScopedPreferences.java
index e933ad0..92cca49 100644
--- a/src/com/android/documentsui/base/ScopedPreferences.java
+++ b/src/com/android/documentsui/base/ScopedPreferences.java
@@ -18,12 +18,23 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
+import android.text.TextUtils;
+/**
+ * Provides an interface (and runtime implementation) for preferences that are
+ * scoped (presumably to an activity). This eliminates the need to pass
+ * scoping values into {@link LocalPreferences}, as well as eliminates
+ * the static-coupling to {@link LocalPreferences} increasing testability.
+ */
public interface ScopedPreferences {
boolean getShowDeviceRoot();
void setShowDeviceRoot(boolean display);
+ /**
+ * @param scope An arbitrary string representitive of the scope
+ * for prefs that are set using this object.
+ */
public static ScopedPreferences create(Context context, String scope) {
return new RuntimeScopedPreferences(
PreferenceManager.getDefaultSharedPreferences(context), scope);
@@ -37,6 +48,8 @@
private String mScope;
private RuntimeScopedPreferences(SharedPreferences sharedPrefs, String scope) {
+ assert(!TextUtils.isEmpty(scope));
+
mSharedPrefs = sharedPrefs;
mScope = scope;
}
diff --git a/src/com/android/documentsui/base/Shared.java b/src/com/android/documentsui/base/Shared.java
index 14bb081..8e93587 100644
--- a/src/com/android/documentsui/base/Shared.java
+++ b/src/com/android/documentsui/base/Shared.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Looper;
@@ -54,13 +55,6 @@
"com.android.documentsui.PICK_COPY_DESTINATION";
/**
- * Extra flag allowing app to be opened in productivity mode (less downloadsy).
- * Useful developers and the likes. When set to true overrides the default
- * config value of productivity_device.
- */
- public static final String EXTRA_PRODUCTIVITY_MODE = "com.android.documentsui.PRODUCTIVITY";
-
- /**
* Extra boolean flag for {@link #ACTION_PICK_COPY_DESTINATION}, which
* specifies if the destination directory needs to create new directory or not.
*/
@@ -202,13 +196,17 @@
if (info.isSystemApp() || info.isUpdatedSystemApp()) {
final String extra = activity.getIntent().getStringExtra(
DocumentsContract.EXTRA_PACKAGE_NAME);
- if (extra != null) {
+ if (extra != null && !TextUtils.isEmpty(extra)) {
callingPackage = extra;
}
}
- } finally {
- return callingPackage;
+ } catch (NameNotFoundException e) {
+ // Couldn't lookup calling package info. This isn't really
+ // gonna happen, given that we're getting the name of the
+ // calling package from trusty old Activity.getCallingPackage.
+ // For that reason, we ignore this exception.
}
+ return callingPackage;
}
/**
@@ -216,7 +214,7 @@
* Method can be overridden if the change of the behavior of the the child activity is needed.
*/
public static Uri getDefaultRootUri(Activity activity) {
- return shouldShowDocumentsRoot(activity, activity.getIntent())
+ return shouldShowDocumentsRoot(activity)
? DocumentsContract.buildHomeUri()
: DocumentsContract.buildRootUri(
"com.android.providers.downloads.documents", "downloads");
@@ -233,37 +231,20 @@
}
/*
- * Returns true if app is running in "productivity mode".
- */
- private static boolean isProductivityMode(Context context, Intent intent) {
- return intent.getBooleanExtra(
- Shared.EXTRA_PRODUCTIVITY_MODE,
- context.getResources().getBoolean(R.bool.productivity_device));
- }
-
- /*
* Returns true if "Documents" root should be shown.
*/
- public static boolean shouldShowDocumentsRoot(Context context, Intent intent) {
- return isProductivityMode(context, intent);
+ public static boolean shouldShowDocumentsRoot(Context context) {
+ return context.getResources().getBoolean(R.bool.productivity_device);
}
/*
- * Returns true if device root should be shown.
+ * Returns true if the local/device storage root must be visible (this also hides
+ * the option to toggle visibility in the menu.)
*/
public static boolean mustShowDeviceRoot(Intent intent) {
return intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false);
}
- /**
- * Returns true if device root should be shown.
- */
- public static boolean shouldShowFancyFeatures(Activity activity) {
- Intent intent = activity.getIntent();
- return isProductivityMode(activity, intent)
- || intent.getBooleanExtra(DocumentsContract.EXTRA_FANCY_FEATURES, false);
- }
-
public static void checkMainLoop() {
if (Looper.getMainLooper() != Looper.myLooper()) {
Log.e(TAG, "Calling from non-UI thread!");
diff --git a/src/com/android/documentsui/base/State.java b/src/com/android/documentsui/base/State.java
index fff50c9..32d3cc2 100644
--- a/src/com/android/documentsui/base/State.java
+++ b/src/com/android/documentsui/base/State.java
@@ -77,7 +77,7 @@
public boolean allowMultiple;
public boolean localOnly;
- public boolean showAdvancedOption;
+ public boolean showDeviceStorageOption;
public boolean showAdvanced;
public boolean restored;
/*
@@ -127,7 +127,7 @@
out.writeStringArray(acceptMimes);
out.writeInt(allowMultiple ? 1 : 0);
out.writeInt(localOnly ? 1 : 0);
- out.writeInt(showAdvancedOption ? 1 : 0);
+ out.writeInt(showDeviceStorageOption ? 1 : 0);
out.writeInt(showAdvanced ? 1 : 0);
out.writeInt(restored ? 1 : 0);
out.writeInt(external ? 1 : 0);
@@ -151,7 +151,7 @@
state.acceptMimes = in.readStringArray();
state.allowMultiple = in.readInt() != 0;
state.localOnly = in.readInt() != 0;
- state.showAdvancedOption = in.readInt() != 0;
+ state.showDeviceStorageOption = in.readInt() != 0;
state.showAdvanced = in.readInt() != 0;
state.restored = in.readInt() != 0;
state.external = in.readInt() != 0;
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index a202049..59b58f1 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -22,6 +22,8 @@
import static com.android.documentsui.base.State.MODE_GRID;
import static com.android.documentsui.base.State.MODE_LIST;
+import android.annotation.DimenRes;
+import android.annotation.FractionRes;
import android.annotation.IntDef;
import android.annotation.StringRes;
import android.app.Activity;
@@ -36,6 +38,7 @@
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
@@ -66,8 +69,8 @@
import com.android.documentsui.BaseActivity;
import com.android.documentsui.BaseActivity.RetainedState;
import com.android.documentsui.DirectoryLoader;
-import com.android.documentsui.DirectoryResult;
import com.android.documentsui.DirectoryReloadLock;
+import com.android.documentsui.DirectoryResult;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.FocusManager;
import com.android.documentsui.ItemDragListener;
@@ -183,6 +186,9 @@
private GridLayoutManager mLayout;
private int mColumnCount = 1; // This will get updated when layout changes.
+ private float mLiveScale = 1.0f;
+ private @ViewMode int mMode;
+
private MessageBar mMessageBar;
private View mProgressBar;
@@ -204,7 +210,7 @@
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- BaseActivity activity = (BaseActivity<?>) getActivity();
+ BaseActivity<?> activity = (BaseActivity<?>) getActivity();
final View view = inflater.inflate(R.layout.fragment_directory, container, false);
mMessageBar = MessageBar.create(getChildFragmentManager());
@@ -342,6 +348,7 @@
EventHandler<InputEvent> gestureHandler = mState.allowMultiple
? gestureSel::start
: EventHandler.createStub(false);
+
mInputHandler = new UserInputHandler<>(
mActions,
mFocusManager,
@@ -359,7 +366,8 @@
mDragStartListener::onMouseDragEvent,
gestureSel,
mInputHandler,
- mBandController);
+ mBandController,
+ this::scaleLayout);
mMenuManager = mActivity.getMenuManager();
@@ -497,6 +505,7 @@
* @param mode The new view mode.
*/
private void updateLayout(@ViewMode int mode) {
+ mMode = mode;
mColumnCount = calculateColumnCount(mode);
if (mLayout != null) {
mLayout.setSpanCount(mColumnCount);
@@ -511,22 +520,65 @@
mIconHelper.setViewMode(mode);
}
+ /**
+ * Updates the layout after the view mode switches.
+ * @param mode The new view mode.
+ */
+ private void scaleLayout(float scale) {
+ assert(Build.IS_DEBUGGABLE);
+ if (DEBUG) Log.v(TAG, "Handling scale event: " + scale + ", existing scale: " + mLiveScale);
+
+ if (mMode == MODE_GRID) {
+ float minScale = getFraction(R.fraction.grid_scale_min);
+ float maxScale = getFraction(R.fraction.grid_scale_max);
+ float nextScale = mLiveScale * scale;
+
+ if (DEBUG) Log.v(TAG,
+ "Next scale " + nextScale + ", Min/max scale " + minScale + "/" + maxScale);
+
+ if (nextScale > minScale && nextScale < maxScale) {
+ if (DEBUG) Log.d(TAG, "Updating grid scale: " + scale);
+ mLiveScale = nextScale;
+ updateLayout(mMode);
+ }
+
+ } else {
+ if (DEBUG) Log.d(TAG, "List mode, ignoring scale: " + scale);
+ mLiveScale = 1.0f;
+ }
+ }
+
private int calculateColumnCount(@ViewMode int mode) {
if (mode == MODE_LIST) {
// List mode is a "grid" with 1 column.
return 1;
}
- int cellWidth = getResources().getDimensionPixelSize(R.dimen.grid_width);
- int cellMargin = 2 * getResources().getDimensionPixelSize(R.dimen.grid_item_margin);
- int viewPadding = mRecView.getPaddingLeft() + mRecView.getPaddingRight();
+ int cellWidth = getScaledSize(R.dimen.grid_width);
+ int cellMargin = 2 * getScaledSize(R.dimen.grid_item_margin);
+ int viewPadding =
+ (int) ((mRecView.getPaddingLeft() + mRecView.getPaddingRight()) * mLiveScale);
- // RecyclerView sometimes gets a width of 0 (see b/27150284). Clamp so that we always lay
- // out the grid with at least 2 columns.
+ // RecyclerView sometimes gets a width of 0 (see b/27150284).
+ // Clamp so that we always lay out the grid with at least 2 columns by default.
int columnCount = Math.max(2,
(mRecView.getWidth() - viewPadding) / (cellWidth + cellMargin));
- return columnCount;
+ // Finally with our grid count logic firmly in place, we apply any live scaling
+ // captured by the scale gesture detector.
+ return Math.max(1, Math.round(columnCount / mLiveScale));
+ }
+
+
+ /**
+ * Moderately abuse the "fraction" resource type for our purposes.
+ */
+ private float getFraction(@FractionRes int id) {
+ return getResources().getFraction(id, 1, 0);
+ }
+
+ private int getScaledSize(@DimenRes int id) {
+ return (int) (getResources().getDimensionPixelSize(id) * mLiveScale);
}
private int getDirectoryPadding(@ViewMode int mode) {
@@ -685,14 +737,6 @@
.withSrcs(srcs)
.build();
- // Relay any config overrides bits present in the original intent.
- Intent original = getActivity().getIntent();
- if (original != null && original.hasExtra(Shared.EXTRA_PRODUCTIVITY_MODE)) {
- intent.putExtra(
- Shared.EXTRA_PRODUCTIVITY_MODE,
- original.getBooleanExtra(Shared.EXTRA_PRODUCTIVITY_MODE, false));
- }
-
// Set an appropriate title on the drawer when it is shown in the picker.
// Coupled with the fact that we auto-open the drawer for copy/move operations
// it should basically be the thing people see first.
diff --git a/src/com/android/documentsui/dirlist/DocumentHolder.java b/src/com/android/documentsui/dirlist/DocumentHolder.java
index 48a05d8..b5c1174 100644
--- a/src/com/android/documentsui/dirlist/DocumentHolder.java
+++ b/src/com/android/documentsui/dirlist/DocumentHolder.java
@@ -20,7 +20,7 @@
import android.content.Context;
import android.database.Cursor;
import android.graphics.Rect;
-import android.support.annotation.Nullable;
+import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.KeyEvent;
@@ -28,28 +28,36 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
+import android.widget.FrameLayout;
import android.widget.ImageView;
-import com.android.documentsui.base.Shared;
-import com.android.documentsui.base.State;
-import com.android.documentsui.base.Events.InputEvent;
import com.android.documentsui.R;
+import com.android.documentsui.base.DebugFlags;
+import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.base.Events.InputEvent;
+import com.android.documentsui.base.Shared;
+import com.android.documentsui.ui.DocumentDebugInfo;
+
+import javax.annotation.Nullable;
public abstract class DocumentHolder
- extends RecyclerView.ViewHolder implements View.OnKeyListener, DocumentDetails {
+ extends RecyclerView.ViewHolder
+ implements View.OnKeyListener, DocumentDetails {
static final float DISABLED_ALPHA = 0.3f;
- protected @Nullable String modelId;
+ protected final Context mContext;
+ protected final @ColorInt int mDefaultBgColor;
+ protected final @ColorInt int mSelectedBgColor;
- final Context mContext;
- final @ColorInt int mDefaultBgColor;
- final @ColorInt int mSelectedBgColor;
+ protected @Nullable String mModelId;
+
+ private final View mSelectionHotspot;
+ private final @Nullable FrameLayout mDebugContainer;
+ private @Nullable DocumentDebugInfo mDebugInfo;
// See #addKeyEventListener for details on the need for this field.
- KeyboardEventListener mKeyEventListener;
-
- private View mSelectionHotspot;
+ private KeyboardEventListener mKeyEventListener;
public DocumentHolder(Context context, ViewGroup parent, int layout) {
this(context, inflateLayout(context, parent, layout));
@@ -64,8 +72,9 @@
mDefaultBgColor = context.getColor(R.color.item_doc_background);
mSelectedBgColor = context.getColor(R.color.item_doc_background_selected);
-
mSelectionHotspot = itemView.findViewById(R.id.icon_check);
+
+ mDebugContainer = (FrameLayout) itemView.findViewById(R.id.debug_info);
}
/**
@@ -74,16 +83,16 @@
* @param modelId
* @param state
*/
- public abstract void bind(Cursor cursor, String modelId, State state);
+ public abstract void bind(Cursor cursor, String modelId);
@Override
public boolean hasModelId() {
- return !TextUtils.isEmpty(modelId);
+ return !TextUtils.isEmpty(mModelId);
}
@Override
public String getModelId() {
- return modelId;
+ return mModelId;
}
/**
@@ -158,6 +167,24 @@
return false;
}
+ protected void includeDebugInfo(DocumentInfo doc) {
+ if (mDebugContainer == null) {
+ return;
+ }
+ if (DebugFlags.getDocumentDetailsEnabled()) {
+ assert(Build.IS_DEBUGGABLE);
+ if (mDebugInfo == null) {
+ assert(mDebugContainer.getChildAt(0) == null);
+ mDebugInfo = inflateLayout(mContext, mDebugContainer, R.layout.document_debug_info);
+ mDebugContainer.addView(mDebugInfo);
+ }
+ mDebugInfo.update(doc);
+ mDebugContainer.setVisibility(View.VISIBLE);
+ } else {
+ mDebugContainer.setVisibility(View.GONE);
+ }
+ }
+
static void setEnabledRecursive(View itemView, boolean enabled) {
if (itemView == null || itemView.isEnabled() == enabled) {
return;
@@ -172,9 +199,9 @@
}
}
- private static View inflateLayout(Context context, ViewGroup parent, int layout) {
+ private static <V extends View> V inflateLayout(Context context, ViewGroup parent, int layout) {
final LayoutInflater inflater = LayoutInflater.from(context);
- return inflater.inflate(layout, parent, false);
+ return (V) inflater.inflate(layout, parent, false);
}
static ViewPropertyAnimator fade(ImageView view, float alpha) {
diff --git a/src/com/android/documentsui/dirlist/GridDirectoryHolder.java b/src/com/android/documentsui/dirlist/GridDirectoryHolder.java
index 962f6bc..fb6c3c3 100644
--- a/src/com/android/documentsui/dirlist/GridDirectoryHolder.java
+++ b/src/com/android/documentsui/dirlist/GridDirectoryHolder.java
@@ -26,8 +26,10 @@
import android.widget.TextView;
import com.android.documentsui.R;
-import com.android.documentsui.base.State;
+import com.android.documentsui.base.DebugFlags;
+import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Events.InputEvent;
+import com.android.documentsui.roots.RootCursorWrapper;
final class GridDirectoryHolder extends DocumentHolder {
@@ -71,13 +73,18 @@
* @param state Current display state.
*/
@Override
- public void bind(Cursor cursor, String modelId, State state) {
+ public void bind(Cursor cursor, String modelId) {
assert(cursor != null);
- this.modelId = modelId;
+ this.mModelId = modelId;
- final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
- mTitle.setText(docDisplayName, TextView.BufferType.SPANNABLE);
+ mTitle.setText(
+ getCursorString(cursor, Document.COLUMN_DISPLAY_NAME),
+ TextView.BufferType.SPANNABLE);
+ if (DebugFlags.getDocumentDetailsEnabled()) {
+ String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
+ includeDebugInfo(DocumentInfo.fromCursor(cursor, authority));
+ }
}
}
diff --git a/src/com/android/documentsui/dirlist/GridDocumentHolder.java b/src/com/android/documentsui/dirlist/GridDocumentHolder.java
index 051114d..f4f7d7c 100644
--- a/src/com/android/documentsui/dirlist/GridDocumentHolder.java
+++ b/src/com/android/documentsui/dirlist/GridDocumentHolder.java
@@ -16,15 +16,12 @@
package com.android.documentsui.dirlist;
-import static com.android.documentsui.base.DocumentInfo.getCursorInt;
import static com.android.documentsui.base.DocumentInfo.getCursorLong;
import static com.android.documentsui.base.DocumentInfo.getCursorString;
import android.annotation.ColorInt;
import android.content.Context;
import android.database.Cursor;
-import android.net.Uri;
-import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.text.format.Formatter;
import android.view.View;
@@ -33,15 +30,14 @@
import android.widget.TextView;
import com.android.documentsui.R;
-import com.android.documentsui.base.Shared;
-import com.android.documentsui.base.State;
+import com.android.documentsui.base.DebugFlags;
+import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Events.InputEvent;
+import com.android.documentsui.base.Shared;
import com.android.documentsui.roots.RootCursorWrapper;
final class GridDocumentHolder extends DocumentHolder {
- private static boolean mHideTitles;
-
final TextView mTitle;
final TextView mDate;
final TextView mDetails;
@@ -52,6 +48,8 @@
final IconHelper mIconHelper;
private final @ColorInt int mDisabledBgColor;
+ // This is used in as a convenience in our bind method.
+ private final DocumentInfo mDoc = new DocumentInfo();
public GridDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper) {
super(context, parent, R.layout.item_doc_grid);
@@ -123,18 +121,12 @@
* @param state Current display state.
*/
@Override
- public void bind(Cursor cursor, String modelId, State state) {
+ public void bind(Cursor cursor, String modelId) {
assert(cursor != null);
- this.modelId = modelId;
+ mModelId = modelId;
- final String docAuthority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
- final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
- final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
- final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
- final long docLastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
- final int docIcon = getCursorInt(cursor, Document.COLUMN_ICON);
- final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+ mDoc.updateFromCursor(cursor, getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY));
mIconHelper.stopLoading(mIconThumb);
@@ -143,38 +135,36 @@
mIconThumb.animate().cancel();
mIconThumb.setAlpha(0f);
- final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
- mIconHelper.load(uri, docMimeType, docFlags, docIcon, docLastModified, mIconThumb,
- mIconMimeLg, mIconMimeSm);
+ mIconHelper.load(mDoc, mIconThumb, mIconMimeLg, mIconMimeSm);
- if (mHideTitles) {
- mTitle.setVisibility(View.GONE);
- } else {
- mTitle.setText(docDisplayName, TextView.BufferType.SPANNABLE);
- mTitle.setVisibility(View.VISIBLE);
- }
+ mTitle.setText(mDoc.displayName, TextView.BufferType.SPANNABLE);
+ mTitle.setVisibility(View.VISIBLE);
// If file is partial, we want to show summary field as that's more relevant than fileSize
// and date
- if ((docFlags & Document.FLAG_PARTIAL) != 0) {
+ if (mDoc.isPartial()) {
final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
mDetails.setVisibility(View.VISIBLE);
mDate.setText(null);
mDetails.setText(docSummary);
} else {
- if (docLastModified == -1) {
+ if (mDoc.lastModified == -1) {
mDate.setText(null);
} else {
- mDate.setText(Shared.formatTime(mContext, docLastModified));
+ mDate.setText(Shared.formatTime(mContext, mDoc.lastModified));
}
final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
- if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
+ if (mDoc.isDirectory() || docSize == -1) {
mDetails.setVisibility(View.GONE);
} else {
mDetails.setVisibility(View.VISIBLE);
mDetails.setText(Formatter.formatFileSize(mContext, docSize));
}
}
+
+ if (DebugFlags.getDocumentDetailsEnabled()) {
+ includeDebugInfo(mDoc);
+ }
}
}
diff --git a/src/com/android/documentsui/dirlist/IconHelper.java b/src/com/android/documentsui/dirlist/IconHelper.java
index 6f6327b..fa1c121 100644
--- a/src/com/android/documentsui/dirlist/IconHelper.java
+++ b/src/com/android/documentsui/dirlist/IconHelper.java
@@ -215,6 +215,24 @@
/**
* Load thumbnails for a directory list item.
*
+ * @param doc The document
+ * @param iconThumb The itemview's thumbnail icon.
+ * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown.
+ * @param subIconMime The second itemview's mime icon. Always visible.
+ * @return
+ */
+ public void load(
+ DocumentInfo doc,
+ ImageView iconThumb,
+ ImageView iconMime,
+ @Nullable ImageView subIconMime) {
+ load(doc.derivedUri, doc.mimeType, doc.flags, doc.icon, doc.lastModified,
+ iconThumb, iconMime, subIconMime);
+ }
+
+ /**
+ * Load thumbnails for a directory list item.
+ *
* @param uri The URI for the file being represented.
* @param mimeType The mime type of the file being represented.
* @param docFlags Flags for the file being represented.
diff --git a/src/com/android/documentsui/dirlist/ListDocumentHolder.java b/src/com/android/documentsui/dirlist/ListDocumentHolder.java
index 8921489..c65839f 100644
--- a/src/com/android/documentsui/dirlist/ListDocumentHolder.java
+++ b/src/com/android/documentsui/dirlist/ListDocumentHolder.java
@@ -16,16 +16,11 @@
package com.android.documentsui.dirlist;
-import static com.android.documentsui.base.DocumentInfo.getCursorInt;
-import static com.android.documentsui.base.DocumentInfo.getCursorLong;
import static com.android.documentsui.base.DocumentInfo.getCursorString;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Rect;
-import android.net.Uri;
-import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Document;
import android.text.format.Formatter;
import android.view.View;
import android.view.ViewGroup;
@@ -34,22 +29,25 @@
import android.widget.TextView;
import com.android.documentsui.R;
-import com.android.documentsui.base.Shared;
-import com.android.documentsui.base.State;
+import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Events.InputEvent;
+import com.android.documentsui.base.Shared;
import com.android.documentsui.roots.RootCursorWrapper;
final class ListDocumentHolder extends DocumentHolder {
- final TextView mTitle;
- final LinearLayout mDetails; // Container of date/size/summary
- final TextView mDate;
- final TextView mSize;
- final TextView mSummary;
- final ImageView mIconMime;
- final ImageView mIconThumb;
- final ImageView mIconCheck;
- final IconHelper mIconHelper;
- final View mIconLayout;
+
+ private final TextView mTitle;
+ private final LinearLayout mDetails; // Container of date/size/summary
+ private final TextView mDate;
+ private final TextView mSize;
+ private final TextView mSummary;
+ private final ImageView mIconMime;
+ private final ImageView mIconThumb;
+ private final ImageView mIconCheck;
+ private final IconHelper mIconHelper;
+ private final View mIconLayout;
+ // This is used in as a convenience in our bind method.
+ private final DocumentInfo mDoc;
public ListDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper) {
super(context, parent, R.layout.item_doc_list);
@@ -66,6 +64,7 @@
mDetails = (LinearLayout) itemView.findViewById(R.id.line2);
mIconHelper = iconHelper;
+ mDoc = new DocumentInfo();
}
@Override
@@ -138,21 +137,12 @@
* @param state Current display state.
*/
@Override
- public void bind(Cursor cursor, String modelId, State state) {
+ public void bind(Cursor cursor, String modelId) {
assert(cursor != null);
- this.modelId = modelId;
+ mModelId = modelId;
- final String docAuthority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
- final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
- final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
- final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
- final long docLastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
- final int docIcon = getCursorInt(cursor, Document.COLUMN_ICON);
- final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
- final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
- final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
- final boolean isDirectory = Document.MIME_TYPE_DIR.equals(docMimeType);
+ mDoc.updateFromCursor(cursor, getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY));
mIconHelper.stopLoading(mIconThumb);
@@ -161,38 +151,36 @@
mIconThumb.animate().cancel();
mIconThumb.setAlpha(0f);
- final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
- mIconHelper.load(uri, docMimeType, docFlags, docIcon, docLastModified, mIconThumb,
- mIconMime, null);
+ mIconHelper.load(mDoc, mIconThumb, mIconMime, null);
- mTitle.setText(docDisplayName, TextView.BufferType.SPANNABLE);
+ mTitle.setText(mDoc.displayName, TextView.BufferType.SPANNABLE);
mTitle.setVisibility(View.VISIBLE);
boolean hasDetails = false;
- if (isDirectory) {
+ if (mDoc.isDirectory()) {
// Note, we don't show any details for any directory...ever.
hasDetails = false;
} else {
- if (docSummary != null) {
+ if (mDoc.summary != null) {
hasDetails = true;
- mSummary.setText(docSummary);
+ mSummary.setText(mDoc.summary);
mSummary.setVisibility(View.VISIBLE);
} else {
mSummary.setVisibility(View.INVISIBLE);
}
- if (docLastModified > 0) {
+ if (mDoc.lastModified > 0) {
hasDetails = true;
- mDate.setText(Shared.formatTime(mContext, docLastModified));
+ mDate.setText(Shared.formatTime(mContext, mDoc.lastModified));
} else {
mDate.setText(null);
}
- if (docSize > -1) {
+ if (mDoc.size > -1) {
hasDetails = true;
mSize.setVisibility(View.VISIBLE);
- mSize.setText(Formatter.formatFileSize(mContext, docSize));
+ mSize.setText(Formatter.formatFileSize(mContext, mDoc.size));
} else {
mSize.setVisibility(View.GONE);
}
@@ -202,5 +190,8 @@
if (mDetails != null) {
mDetails.setVisibility(hasDetails ? View.VISIBLE : View.GONE);
}
+
+ // TODO: Add document debug info
+ // Call includeDebugInfo
}
}
diff --git a/src/com/android/documentsui/dirlist/ListeningGestureDetector.java b/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
index 296fa70..17ac867 100644
--- a/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
+++ b/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
@@ -16,15 +16,21 @@
package com.android.documentsui.dirlist;
+import static com.android.documentsui.base.Shared.DEBUG;
+
import android.annotation.Nullable;
import android.content.Context;
+import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.View.OnTouchListener;
+import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.Events;
import com.android.documentsui.base.Events.InputEvent;
@@ -32,18 +38,25 @@
import com.android.documentsui.selection.BandController;
import com.android.documentsui.selection.GestureSelector;
+import java.util.function.Consumer;
+
//Receives event meant for both directory and empty view, and either pass them to
//{@link UserInputHandler} for simple gestures (Single Tap, Long-Press), or intercept them for
//other types of gestures (drag n' drop)
final class ListeningGestureDetector extends GestureDetector
implements OnItemTouchListener, OnTouchListener {
+ private static final String TAG = "ListeningGestureDetector";
+
private final GestureSelector mGestureSelector;
private final EventHandler<InputEvent> mMouseDragListener;
private final BandController mBandController;
private final MouseDelegate mMouseDelegate = new MouseDelegate();
private final TouchDelegate mTouchDelegate = new TouchDelegate();
+ // Currently only initialized on IS_DEBUGGABLE builds.
+ private final @Nullable ScaleGestureDetector mScaleDetector;
+
public ListeningGestureDetector(
Context context,
RecyclerView recView,
@@ -51,19 +64,45 @@
EventHandler<InputEvent> mouseDragListener,
GestureSelector gestureSelector,
UserInputHandler<? extends InputEvent> handler,
- @Nullable BandController bandController) {
+ @Nullable BandController bandController,
+ Consumer<Float> scaleHandler) {
+
super(context, handler);
+
mMouseDragListener = mouseDragListener;
mGestureSelector = gestureSelector;
mBandController = bandController;
recView.addOnItemTouchListener(this);
emptyView.setOnTouchListener(this);
+
+ mScaleDetector = !Build.IS_DEBUGGABLE
+ ? null
+ : new ScaleGestureDetector(
+ context,
+ new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ if (DEBUG) Log.v(TAG,
+ "Received scale event: " + detector.getScaleFactor());
+ scaleHandler.accept(detector.getScaleFactor());
+ return true;
+ }
+ });
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
boolean handled = false;
+ // This is an in-development feature.
+ // TODO: Re-wire event handling so that we're not dispatching
+ // events to to scaledetector's #onTouchEvent from this
+ // #onInterceptTouchEvent touch event.
+ if (DebugFlags.getGestureScaleEnabled()
+ && mScaleDetector != null) {
+ mScaleDetector.onTouchEvent(e);
+ }
+
try (InputEvent event = MotionInputEvent.obtain(e, rv)) {
if (event.isMouseEvent()) {
handled |= mMouseDelegate.onInterceptTouchEvent(event);
diff --git a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
index adc4b04..99f9371 100644
--- a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
+++ b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
@@ -129,7 +129,7 @@
public void onBindViewHolder(DocumentHolder holder, int position) {
String modelId = mModelIds.get(position);
Cursor cursor = mEnv.getModel().getItem(modelId);
- holder.bind(cursor, modelId, mEnv.getDisplayState());
+ holder.bind(cursor, modelId);
final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
diff --git a/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java b/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
index 69f9566..4cb55b3 100644
--- a/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
+++ b/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
@@ -240,6 +240,7 @@
*/
private static final class EmptyDocumentHolder extends DocumentHolder {
final int mVisibleHeight;
+ private State mState;
public EmptyDocumentHolder(Context context) {
super(context, new Space(context));
@@ -249,12 +250,13 @@
}
public void bind(State state) {
- bind(null, null, state);
+ mState = state;
+ bind(null, null);
}
@Override
- public void bind(Cursor cursor, String modelId, State state) {
- if (state.derivedMode == State.MODE_GRID) {
+ public void bind(Cursor cursor, String modelId) {
+ if (mState.derivedMode == State.MODE_GRID) {
itemView.setMinimumHeight(mVisibleHeight);
} else {
itemView.setMinimumHeight(0);
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 7a2284f..0c67b20 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -217,7 +217,7 @@
Intent intent = getIntent();
return (intent != null && intent.hasExtra(Intent.EXTRA_TITLE))
? intent.getStringExtra(Intent.EXTRA_TITLE)
- : getTitle().toString();
+ : getString(R.string.app_label);
}
@Override
diff --git a/src/com/android/documentsui/files/LauncherActivity.java b/src/com/android/documentsui/files/LauncherActivity.java
index bb25c35..a73d439 100644
--- a/src/com/android/documentsui/files/LauncherActivity.java
+++ b/src/com/android/documentsui/files/LauncherActivity.java
@@ -29,8 +29,6 @@
import android.support.annotation.Nullable;
import android.util.Log;
-import com.android.documentsui.base.Shared;
-
import java.util.List;
/**
@@ -50,25 +48,34 @@
// Array of boolean extras that should be copied when creating new launch intents.
// Missing intents will be ignored.
private static final String[] PERSISTENT_BOOLEAN_EXTRAS = {
- DocumentsContract.EXTRA_SHOW_ADVANCED,
- DocumentsContract.EXTRA_FANCY_FEATURES,
- Shared.EXTRA_PRODUCTIVITY_MODE
+ DocumentsContract.EXTRA_SHOW_ADVANCED
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ launch();
+
+ finish();
+ }
+
+ private void launch() {
ActivityManager activities = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
Intent intent = findTask(activities);
if (intent != null) {
- restoreTask(intent);
- } else {
- startTask();
+ if (restoreTask(intent)) {
+ return;
+ } else {
+ // We failed to restore the task. It may happen when system was just updated and we
+ // moved the location of the targeted activity. Chances is that the rest of tasks
+ // can't be restored either, so clean those tasks and start a new one.
+ clearTask(activities);
+ }
}
- finish();
+ startTask();
}
private @Nullable Intent findTask(ActivityManager activities) {
@@ -92,10 +99,26 @@
startActivity(intent);
}
- private void restoreTask(Intent intent) {
+ private boolean restoreTask(Intent intent) {
if (DEBUG) Log.d(TAG, "Restoring existing task > " + intent.getData());
- // TODO: This doesn't appear to restore a task once it has stopped running.
- startActivity(intent);
+ try {
+ // TODO: This doesn't appear to restore a task once it has stopped running.
+ startActivity(intent);
+
+ return true;
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to restore task > " + intent.getData() +
+ ". Clear all existing tasks and start a new one.", e);
+ }
+
+ return false;
+ }
+
+ private void clearTask(ActivityManager activities) {
+ List<AppTask> tasks = activities.getAppTasks();
+ for (AppTask task : tasks) {
+ task.finishAndRemoveTask();
+ }
}
public static final Intent createLaunchIntent(Activity activity) {
diff --git a/src/com/android/documentsui/files/MenuManager.java b/src/com/android/documentsui/files/MenuManager.java
index 839c245..522f448 100644
--- a/src/com/android/documentsui/files/MenuManager.java
+++ b/src/com/android/documentsui/files/MenuManager.java
@@ -135,7 +135,7 @@
@Override
protected void updateNewWindow(MenuItem newWindow) {
- newWindow.setVisible(mDirDetails.shouldShowFancyFeatures());
+ newWindow.setVisible(true);
}
@Override
diff --git a/src/com/android/documentsui/files/QuickViewIntentBuilder.java b/src/com/android/documentsui/files/QuickViewIntentBuilder.java
index 3304cc1..c85af8a 100644
--- a/src/com/android/documentsui/files/QuickViewIntentBuilder.java
+++ b/src/com/android/documentsui/files/QuickViewIntentBuilder.java
@@ -36,9 +36,9 @@
import android.util.Range;
import com.android.documentsui.R;
+import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.queries.SetQuickViewerCommand;
import com.android.documentsui.roots.RootCursorWrapper;
import java.util.ArrayList;
@@ -142,8 +142,9 @@
// Allow users of debug devices to override default quick viewer
// for the purposes of testing.
if (Build.IS_DEBUGGABLE) {
- if (SetQuickViewerCommand.sQuickViewer != null) {
- return SetQuickViewerCommand.sQuickViewer;
+ String quickViewer = DebugFlags.getQuickViewer();
+ if (quickViewer != null) {
+ return quickViewer;
}
return android.os.SystemProperties.get("debug.quick_viewer", resValue);
}
diff --git a/src/com/android/documentsui/queries/DebugCommandProcessor.java b/src/com/android/documentsui/queries/DebugCommandProcessor.java
index 70d9d64..7a12a18 100644
--- a/src/com/android/documentsui/queries/DebugCommandProcessor.java
+++ b/src/com/android/documentsui/queries/DebugCommandProcessor.java
@@ -17,8 +17,10 @@
import android.os.Build;
import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
import android.util.Log;
+import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.EventHandler;
import java.util.ArrayList;
@@ -26,11 +28,18 @@
final class DebugCommandProcessor implements EventHandler<String> {
+ @VisibleForTesting
+ static final String COMMAND_PREFIX = "dbg:";
+
+ private static final String TAG = "DebugCommandProcessor";
+
private final List<EventHandler<String[]>> mCommands = new ArrayList<>();
public DebugCommandProcessor() {
if (Build.IS_DEBUGGABLE) {
- mCommands.add(new SetQuickViewerCommand());
+ mCommands.add(DebugCommandProcessor::quickViewer);
+ mCommands.add(DebugCommandProcessor::gestureScale);
+ mCommands.add(DebugCommandProcessor::docDetails);
}
}
@@ -43,8 +52,8 @@
@Override
public boolean accept(String query) {
- if (query.length() > 6 && query.substring(0, 6).equals("#debug")) {
- String[] tokens = query.substring(7).split("\\s+");
+ if (query.length() > COMMAND_PREFIX.length() && query.startsWith(COMMAND_PREFIX)) {
+ String[] tokens = query.substring(COMMAND_PREFIX.length()).split("\\s+");
for (EventHandler<String[]> command : mCommands) {
if (command.accept(tokens)) {
return true;
@@ -54,4 +63,57 @@
}
return false;
}
+
+ private static boolean quickViewer(String[] tokens) {
+ if ("qv".equals(tokens[0])) {
+ if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
+ DebugFlags.setQuickViewer(tokens[1]);
+ Log.i(TAG, "Set quick viewer to: " + tokens[1]);
+ return true;
+ } else {
+ Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
+ }
+ } else if ("deqv".equals(tokens[0])) {
+ Log.i(TAG, "Unset quick viewer");
+ DebugFlags.setQuickViewer(null);
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean gestureScale(String[] tokens) {
+ if ("gs".equals(tokens[0])) {
+ if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
+ boolean enabled = asBool(tokens[1]);
+ DebugFlags.setGestureScaleEnabled(enabled);
+ Log.i(TAG, "Set gesture scale enabled to: " + enabled);
+ return true;
+ }
+ Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
+ }
+ return false;
+ }
+
+ private static boolean docDetails(String[] tokens) {
+ if ("docinfo".equals(tokens[0])) {
+ if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
+ boolean enabled = asBool(tokens[1]);
+ DebugFlags.setDocumentDetailsEnabled(enabled);
+ Log.i(TAG, "Set gesture scale enabled to: " + enabled);
+ return true;
+ }
+ Log.w(TAG, "Invalid command structure: " + TextUtils.join(" ", tokens));
+ }
+ return false;
+ }
+
+ private static final boolean asBool(String val) {
+ if (val == null || val.equals("0")) {
+ return false;
+ }
+ if (val.equals("1")) {
+ return true;
+ }
+ return Boolean.valueOf(val);
+ }
}
diff --git a/src/com/android/documentsui/queries/SetQuickViewerCommand.java b/src/com/android/documentsui/queries/SetQuickViewerCommand.java
deleted file mode 100644
index 37fe0db..0000000
--- a/src/com/android/documentsui/queries/SetQuickViewerCommand.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.documentsui.queries;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.documentsui.base.EventHandler;
-
-public class SetQuickViewerCommand implements EventHandler<String[]> {
-
- // This is a quick/easy shortcut to sharing quick viewer debug settings
- // with QuickViewIntent builder. Tried setting at a system property
- // but got a native error. This being quick and easy, didn't investigate that err.
- public static String sQuickViewer;
- private static final String TAG = "SetQuickViewerCommand";
-
- @Override
- public boolean accept(String[] tokens) {
- if ("setqv".equals(tokens[0])) {
- if (tokens.length == 2 && !TextUtils.isEmpty(tokens[1])) {
- sQuickViewer = tokens[1];
- Log.i(TAG, "Set quick viewer to: " + sQuickViewer);
- return true;
- } else {
- Log.w(TAG, "Invalid command structure: " + tokens);
- }
- } else if ("unsetqv".equals(tokens[0])) {
- Log.i(TAG, "Unset quick viewer");
- sQuickViewer = null;
- return true;
- }
- return false;
- }
-}
diff --git a/src/com/android/documentsui/roots/RootsAccess.java b/src/com/android/documentsui/roots/RootsAccess.java
index 0d0a975..8259337 100644
--- a/src/com/android/documentsui/roots/RootsAccess.java
+++ b/src/com/android/documentsui/roots/RootsAccess.java
@@ -25,6 +25,7 @@
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.State;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -112,7 +113,7 @@
if (!overlap) {
if (DEBUG) Log.v(
tag, "Excluding root because: unsupported content types > "
- + state.acceptMimes);
+ + Arrays.toString(state.acceptMimes));
continue;
}
diff --git a/src/com/android/documentsui/selection/GestureSelector.java b/src/com/android/documentsui/selection/GestureSelector.java
index 959080c..ff77bd0 100644
--- a/src/com/android/documentsui/selection/GestureSelector.java
+++ b/src/com/android/documentsui/selection/GestureSelector.java
@@ -128,10 +128,6 @@
handled = handleInterceptedDownEvent(e);
}
- if (e.isActionUp()) {
- handled = handleUpEvent(e);
- }
-
if (e.isActionMove()) {
handled = handleInterceptedMoveEvent(e);
}
@@ -148,6 +144,10 @@
handleUpEvent(e);
}
+ if (e.isActionCancel()) {
+ handleCancelEvent(e);
+ }
+
if (e.isActionMove()) {
handleOnTouchMoveEvent(rv, e);
}
@@ -157,9 +157,9 @@
// If down event happens on a file/doc, we mark that item's position as last started.
private boolean handleInterceptedDownEvent(InputEvent e) {
View itemView = mViewFinder.findView(e.getX(), e.getY());
- if (itemView != null) {
- mLastStartedItemPos = e.getItemPosition();
- }
+ if (itemView != null) {
+ mLastStartedItemPos = e.getItemPosition();
+ }
return false;
}
@@ -175,14 +175,27 @@
return false;
}
- // Called when ACTION_UP event is intercepted.
- // Essentially, since this means all gesture movement is over, reset everything.
- private boolean handleUpEvent(InputEvent e) {
+ // Called when ACTION_UP event is to be handled.
+ // Essentially, since this means all gesture movement is over, reset everything and apply
+ // provisional selection.
+ private void handleUpEvent(InputEvent e) {
+ mSelectionMgr.getSelection().applyProvisionalSelection();
+ endSelection();
+ }
+
+ // Called when ACTION_CANCEL event is to be handled.
+ // This means this gesture selection is aborted, so reset everything and abandon provisional
+ // selection.
+ private void handleCancelEvent(InputEvent e) {
+ mSelectionMgr.cancelProvisionalSelection();
+ endSelection();
+ }
+
+ private void endSelection() {
+ assert(mStarted);
mLastStartedItemPos = -1;
mStarted = false;
- mSelectionMgr.getSelection().applyProvisionalSelection();
mLock.unblock();
- return false;
}
// Call when an intercepted ACTION_MOVE event is passed down.
diff --git a/src/com/android/documentsui/selection/SelectionManager.java b/src/com/android/documentsui/selection/SelectionManager.java
index 98d1cda..103ef8f 100644
--- a/src/com/android/documentsui/selection/SelectionManager.java
+++ b/src/com/android/documentsui/selection/SelectionManager.java
@@ -322,6 +322,13 @@
notifySelectionChanged();
}
+ void cancelProvisionalSelection() {
+ for (String id : mSelection.mProvisionalSelection) {
+ notifyItemStateChanged(id, false);
+ }
+ mSelection.cancelProvisionalSelection();
+ }
+
/**
* Stops an in-progress range selection. All selection done with
* {@link #snapRangeSelection(int, int)} with type RANGE_PROVISIONAL will be lost if
@@ -330,7 +337,7 @@
public void endRangeSelection() {
mRanger = null;
// Clean up in case there was any leftover provisional selection
- mSelection.cancelProvisionalSelection();
+ cancelProvisionalSelection();
}
/**
diff --git a/src/com/android/documentsui/services/CopyJob.java b/src/com/android/documentsui/services/CopyJob.java
index b9bc651..30bbbf1 100644
--- a/src/com/android/documentsui/services/CopyJob.java
+++ b/src/com/android/documentsui/services/CopyJob.java
@@ -401,7 +401,7 @@
if (DEBUG) Log.d(TAG, "Doing byte copy of document: " + src);
// If the file is virtual, but can be converted to another format, then try to copy it
// as such format. Also, append an extension for the target mime type (if known).
- if (src.isVirtualDocument()) {
+ if (src.isVirtual()) {
String[] streamTypes = null;
try {
streamTypes = getContentResolver().getStreamTypes(src.derivedUri, "*/*");
@@ -538,7 +538,7 @@
try {
// If the file is virtual, but can be converted to another format, then try to copy it
// as such format.
- if (src.isVirtualDocument()) {
+ if (src.isVirtual()) {
try {
srcFileAsAsset = getClient(src).openTypedAssetFileDescriptor(
src.derivedUri, mimeType, null, canceller);
@@ -592,7 +592,7 @@
src.derivedUri, dest.derivedUri, e);
}
- if (src.isVirtualDocument()) {
+ if (src.isVirtual()) {
convertedFiles.add(src);
}
diff --git a/src/com/android/documentsui/services/FileOperationService.java b/src/com/android/documentsui/services/FileOperationService.java
index 3fa23c9..d28a578 100644
--- a/src/com/android/documentsui/services/FileOperationService.java
+++ b/src/com/android/documentsui/services/FileOperationService.java
@@ -88,13 +88,13 @@
// Use a handler to schedule monitor tasks.
@VisibleForTesting Handler handler;
+ @GuardedBy("mRunning")
+ private final Map<String, JobRecord> mRunning = new HashMap<>();
+
private PowerManager mPowerManager;
private PowerManager.WakeLock mWakeLock; // the wake lock, if held.
private NotificationManager mNotificationManager;
- @GuardedBy("mRunning")
- private Map<String, JobRecord> mRunning = new HashMap<>();
-
private int mLastServiceId;
@Override
diff --git a/src/com/android/documentsui/services/MoveJob.java b/src/com/android/documentsui/services/MoveJob.java
index 16bda15..7e66cb0 100644
--- a/src/com/android/documentsui/services/MoveJob.java
+++ b/src/com/android/documentsui/services/MoveJob.java
@@ -154,7 +154,7 @@
// Moving virtual files by bytes is not supported. This is because, it would involve
// conversion, and the source file should not be deleted in such case (as it's a different
// file).
- if (src.isVirtualDocument()) {
+ if (src.isVirtual()) {
throw new ResourceException("Cannot move virtual file %s byte by byte.",
src.derivedUri);
}
diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java
index 4e9287e..88c98a4 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -259,7 +259,7 @@
final RootItem item = new RootItem(root, mActionHandler);
Activity activity = getActivity();
- if (root.isHome() && !Shared.shouldShowDocumentsRoot(activity, activity.getIntent())) {
+ if (root.isHome() && !Shared.shouldShowDocumentsRoot(activity)) {
continue;
} else if (root.isLibrary()) {
libraries.add(item);
diff --git a/src/com/android/documentsui/sorting/SortDimension.java b/src/com/android/documentsui/sorting/SortDimension.java
index 9d235a1..6411b98 100644
--- a/src/com/android/documentsui/sorting/SortDimension.java
+++ b/src/com/android/documentsui/sorting/SortDimension.java
@@ -124,6 +124,11 @@
}
@Override
+ public int hashCode() {
+ return mId;
+ }
+
+ @Override
public boolean equals(Object o) {
if (o == null || !(o instanceof SortDimension)) {
return false;
diff --git a/src/com/android/documentsui/sorting/SortingCursorWrapper.java b/src/com/android/documentsui/sorting/SortingCursorWrapper.java
index 89869d9..691c1d7 100644
--- a/src/com/android/documentsui/sorting/SortingCursorWrapper.java
+++ b/src/com/android/documentsui/sorting/SortingCursorWrapper.java
@@ -21,6 +21,7 @@
import android.database.AbstractCursor;
import android.database.Cursor;
+import android.os.Bundle;
import android.provider.DocumentsContract.Document;
import com.android.documentsui.base.Shared;
@@ -156,6 +157,11 @@
return mCursor.isNull(column);
}
+ @Override
+ public Bundle getExtras() {
+ return mCursor.getExtras();
+ }
+
/**
* @return Timestamp for the given document. Some docs (e.g. active downloads) have a null
* timestamp - these will be replaced with MAX_LONG so that such files get sorted to the top
diff --git a/src/com/android/documentsui/ui/DocumentDebugInfo.java b/src/com/android/documentsui/ui/DocumentDebugInfo.java
new file mode 100644
index 0000000..18d66f2
--- /dev/null
+++ b/src/com/android/documentsui/ui/DocumentDebugInfo.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.ui;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.documentsui.base.DocumentInfo;
+
+/**
+ * Document debug info view.
+ */
+public class DocumentDebugInfo extends TextView {
+ public DocumentDebugInfo(Context context) {
+ super(context);
+
+ }
+
+ public DocumentDebugInfo(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void update(DocumentInfo doc) {
+
+ String dbgInfo = new StringBuilder()
+ .append("** PROPERTIES **\n\n")
+ .append("docid: " + doc.documentId).append("\n")
+ .append("name: " + doc.displayName).append("\n")
+ .append("mimetype: " + doc.mimeType).append("\n")
+ .append("container: " + doc.isContainer()).append("\n")
+ .append("virtual: " + doc.isVirtual()).append("\n")
+ .append("\n")
+ .append("** OPERATIONS **\n\n")
+ .append("create: " + doc.isCreateSupported()).append("\n")
+ .append("delete: " + doc.isDeleteSupported()).append("\n")
+ .append("rename: " + doc.isRenameSupported()).append("\n")
+ .toString();
+
+ setText(dbgInfo);
+ }
+}
diff --git a/tests/common/com/android/documentsui/StubProvider.java b/tests/common/com/android/documentsui/StubProvider.java
index f71ce5d..dbd948c 100644
--- a/tests/common/com/android/documentsui/StubProvider.java
+++ b/tests/common/com/android/documentsui/StubProvider.java
@@ -766,7 +766,7 @@
this.file = file;
this.documentId = getDocumentIdForFile(file);
this.mimeType = Document.MIME_TYPE_DIR;
- this.streamTypes = new ArrayList<String>();
+ this.streamTypes = new ArrayList<>();
this.flags = Document.FLAG_DIR_SUPPORTS_CREATE | Document.FLAG_SUPPORTS_RENAME;
this.parentId = null;
this.rootInfo = rootInfo;
diff --git a/tests/common/com/android/documentsui/testing/Parcelables.java b/tests/common/com/android/documentsui/testing/Parcelables.java
index cd44a9a..5267065 100644
--- a/tests/common/com/android/documentsui/testing/Parcelables.java
+++ b/tests/common/com/android/documentsui/testing/Parcelables.java
@@ -17,26 +17,28 @@
package com.android.documentsui.testing;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Comparator;
+import java.util.function.BiPredicate;
public class Parcelables {
private Parcelables() {}
- public static <T extends Parcelable> void testParceling(T p, int flags) {
+ public static <T extends Parcelable> void assertParcelable(T p, int flags) {
final T restored = parcel(p, flags);
assertEquals(p, restored);
}
- public static <T extends Parcelable> void testParceling(T p, int flags, Comparator<T> comp) {
+ public static <T extends Parcelable> void assertParcelable(
+ T p, int flags, BiPredicate<T, T> pred) {
T restored = parcel(p, flags);
- assertEquals(0, comp.compare(p, restored));
+ assertTrue(pred.test(p, restored));
}
private static <T extends Parcelable> T parcel(T p, int flags) {
diff --git a/tests/common/com/android/documentsui/testing/TestDirectoryDetails.java b/tests/common/com/android/documentsui/testing/TestDirectoryDetails.java
index 82bd047..a1b29cd 100644
--- a/tests/common/com/android/documentsui/testing/TestDirectoryDetails.java
+++ b/tests/common/com/android/documentsui/testing/TestDirectoryDetails.java
@@ -23,7 +23,6 @@
*/
public class TestDirectoryDetails extends DirectoryDetails {
- public boolean shouldShowFancyFeatures;
public boolean isInRecents;
public boolean hasRootSettings;
public boolean hasItemsToPaste;
@@ -35,11 +34,6 @@
}
@Override
- public boolean shouldShowFancyFeatures() {
- return shouldShowFancyFeatures;
- }
-
- @Override
public boolean hasRootSettings() {
return hasRootSettings;
}
diff --git a/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java b/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java
index ff86a7f..aaace3e 100644
--- a/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java
+++ b/tests/functional/com/android/documentsui/FilesActivityDefaultsUiTest.java
@@ -72,6 +72,6 @@
}
private boolean docsRootEnabled() {
- return Shared.shouldShowDocumentsRoot(context, new Intent(DocumentsContract.ACTION_BROWSE));
+ return Shared.shouldShowDocumentsRoot(context);
}
}
diff --git a/tests/unit/com/android/documentsui/base/DocumentStackTest.java b/tests/unit/com/android/documentsui/base/DocumentStackTest.java
index b7e3cc7..b007583 100644
--- a/tests/unit/com/android/documentsui/base/DocumentStackTest.java
+++ b/tests/unit/com/android/documentsui/base/DocumentStackTest.java
@@ -18,28 +18,50 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
+import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
+import android.provider.DocumentsContract;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import com.android.documentsui.testing.Parcelables;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Objects;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DocumentStackTest {
+ private static final RootInfo ROOT_1;
+ private static final RootInfo ROOT_2;
+
private static final DocumentInfo DIR_1;
private static final DocumentInfo DIR_2;
private DocumentStack mStack;
static {
- DIR_1 = new DocumentInfo();
- DIR_1.displayName = "firstDirectory";
- DIR_2 = new DocumentInfo();
- DIR_2.displayName = "secondDirectory";
+ ROOT_1 = new RootInfo();
+ ROOT_1.rootId = "home";
+ ROOT_2 = new RootInfo();
+ ROOT_2.rootId = "downloads";
+
+ DIR_1 = createDir("first");
+ DIR_2 = createDir("second");
+ }
+
+ private static DocumentInfo createDir(String docId) {
+ DocumentInfo info = new DocumentInfo();
+ info.authority = "authority";
+ info.documentId = docId;
+ info.displayName = docId;
+ info.mimeType = DocumentsContract.Document.MIME_TYPE_DIR;
+ info.deriveFields();
+ return info;
}
@Before
@@ -50,13 +72,10 @@
@Test
public void testInitialStateEmpty() {
assertFalse(mStack.hasLocationChanged());
- }
-
- @Test
- public void testPushDocument_ChangesLocation() {
- mStack.push(DIR_1);
- mStack.push(DIR_2);
- assertTrue(mStack.hasLocationChanged());
+ assertFalse(mStack.hasInitialLocationChanged());
+ assertTrue(mStack.isEmpty());
+ assertEquals(0, mStack.size());
+ assertNull(mStack.getRoot());
}
@Test
@@ -73,4 +92,116 @@
mStack.pop();
assertEquals(DIR_1, mStack.peek());
}
+
+ @Test
+ public void testGetDocument() {
+ mStack.push(DIR_1);
+ mStack.push(DIR_2);
+
+ assertEquals(DIR_1, mStack.get(0));
+ assertEquals(DIR_2, mStack.get(1));
+ }
+
+ @Test
+ public void testChangeRoot() {
+ mStack.changeRoot(ROOT_1);
+
+ assertEquals(ROOT_1, mStack.getRoot());
+ }
+
+ @Test
+ public void testChangeRoot_ClearsStack() {
+ mStack.push(DIR_1);
+
+ mStack.changeRoot(ROOT_1);
+
+ assertTrue(mStack.isEmpty());
+ assertEquals(0, mStack.size());
+ }
+
+ @Test
+ public void testReset() {
+ mStack.changeRoot(ROOT_1);
+ mStack.push(DIR_1);
+
+ mStack.reset();
+
+ assertNull(mStack.getRoot());
+ assertTrue(mStack.isEmpty());
+ assertEquals(0, mStack.size());
+ }
+
+ @Test
+ public void testCopyConstructor() {
+ mStack.changeRoot(ROOT_1);
+ mStack.push(DIR_1);
+ mStack.push(DIR_2);
+
+ DocumentStack stack = new DocumentStack(mStack);
+
+ assertEquals(2, stack.size());
+ assertEquals(DIR_1, stack.get(0));
+ assertEquals(DIR_2, stack.get(1));
+ assertEquals(ROOT_1, stack.getRoot());
+ }
+
+ @Test
+ public void testCopyConstructor_MakesDeepCopy() {
+ mStack.changeRoot(ROOT_1);
+ mStack.push(DIR_1);
+ mStack.push(DIR_2);
+
+ DocumentStack stack = new DocumentStack(mStack);
+
+ mStack.changeRoot(ROOT_2);
+
+ assertEquals(2, stack.size());
+ assertEquals(DIR_1, stack.get(0));
+ assertEquals(DIR_2, stack.get(1));
+ assertEquals(ROOT_1, stack.getRoot());
+ }
+
+ @Test
+ public void testPushDocument_ChangesLocation() {
+ mStack.push(DIR_1);
+
+ assertTrue(mStack.hasLocationChanged());
+ }
+
+ @Test
+ public void testPushDocument_ChangesInitialLocation() {
+ mStack.push(DIR_1);
+ mStack.push(DIR_2);
+
+ assertTrue(mStack.hasInitialLocationChanged());
+ }
+
+ @Test
+ public void testChangeRoot_ChangesInitialLocation() {
+ mStack.changeRoot(ROOT_1);
+ mStack.changeRoot(ROOT_2);
+
+ assertTrue(mStack.hasInitialLocationChanged());
+ }
+
+ @Test
+ public void testParceling() {
+ mStack.changeRoot(ROOT_1);
+ mStack.push(DIR_1);
+ mStack.push(DIR_2);
+
+ Parcelables.assertParcelable(mStack, 0, (DocumentStack left, DocumentStack right) -> {
+ if (!Objects.equals(left.getRoot(), right.getRoot()) || left.size() != right.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < left.size(); ++i) {
+ if (!Objects.equals(left.get(i), right.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ });
+ }
}
diff --git a/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java b/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java
index 9f9663f..9d9256d 100644
--- a/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java
@@ -30,7 +30,6 @@
import android.view.MotionEvent.PointerProperties;
import com.android.documentsui.R;
-import com.android.documentsui.base.State;
@SmallTest
public class DocumentHolderTest extends AndroidTestCase {
@@ -44,7 +43,7 @@
LayoutInflater inflater = LayoutInflater.from(context);
mHolder = new DocumentHolder(getContext(), inflater.inflate(R.layout.item_doc_list, null)) {
@Override
- public void bind(Cursor cursor, String modelId, State state) {}
+ public void bind(Cursor cursor, String modelId) {}
};
mListener = new TestListener();
diff --git a/tests/unit/com/android/documentsui/files/MenuManagerTest.java b/tests/unit/com/android/documentsui/files/MenuManagerTest.java
index 5d1fb7d..473f3a8 100644
--- a/tests/unit/com/android/documentsui/files/MenuManagerTest.java
+++ b/tests/unit/com/android/documentsui/files/MenuManagerTest.java
@@ -173,7 +173,7 @@
@Test
public void testOptionMenu_showAdvanced() {
state.showAdvanced = true;
- state.showAdvancedOption = true;
+ state.showDeviceStorageOption = true;
mgr.updateOptionMenu(testMenu);
advanced.assertVisible();
@@ -197,14 +197,6 @@
}
@Test
- public void testOptionMenu_shouldShowFancyFeatures() {
- dirDetails.shouldShowFancyFeatures = true;
- mgr.updateOptionMenu(testMenu);
-
- newWindow.assertVisible();
- }
-
- @Test
public void testInflateContextMenu_Files() {
TestMenuInflater inflater = new TestMenuInflater();
diff --git a/tests/unit/com/android/documentsui/picker/MenuManagerTest.java b/tests/unit/com/android/documentsui/picker/MenuManagerTest.java
index f282bd7..0e6a53a 100644
--- a/tests/unit/com/android/documentsui/picker/MenuManagerTest.java
+++ b/tests/unit/com/android/documentsui/picker/MenuManagerTest.java
@@ -164,7 +164,7 @@
@Test
public void testOptionMenu_showAdvanced() {
state.showAdvanced = true;
- state.showAdvancedOption = true;
+ state.showDeviceStorageOption = true;
mgr.updateOptionMenu(testMenu);
advanced.assertVisible();
diff --git a/tests/unit/com/android/documentsui/queries/DebugCommandProcessorTest.java b/tests/unit/com/android/documentsui/queries/DebugCommandProcessorTest.java
index 0f6e996..2398bb2 100644
--- a/tests/unit/com/android/documentsui/queries/DebugCommandProcessorTest.java
+++ b/tests/unit/com/android/documentsui/queries/DebugCommandProcessorTest.java
@@ -16,11 +16,14 @@
package com.android.documentsui.queries;
+import static com.android.documentsui.queries.DebugCommandProcessor.COMMAND_PREFIX;
+
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.documentsui.testing.TestEventHandler;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,7 +45,7 @@
@Test
public void testTriesAllCommands() {
- mProcessor.accept("#debug poodles");
+ mProcessor.accept(COMMAND_PREFIX + "poodles");
mCommand0.assertCalled();
mCommand1.assertCalled();
}
@@ -50,21 +53,23 @@
@Test
public void testStopsAfterCommandHandled() {
mCommand0.nextReturn(true);
- mProcessor.accept("#debug poodles");
+ mProcessor.accept("dbg:poodles");
mCommand0.assertCalled();
mCommand1.assertNotCalled();
}
@Test
- public void testMissingCommand() {
- mProcessor.accept("#debug");
- mCommand0.assertNotCalled();
- mCommand1.assertNotCalled();
+ public void testConveysArguments() {
+ mCommand0.nextReturn(true);
+ mProcessor.accept(COMMAND_PREFIX + "cheese doodles");
+
+ String[] expected = {"cheese", "doodles"};
+ Assert.assertArrayEquals(expected, mCommand0.getLastValue());
}
@Test
- public void testEmptyInput() {
- mProcessor.accept("#debug");
+ public void testMissingCommand() {
+ mProcessor.accept(COMMAND_PREFIX);
mCommand0.assertNotCalled();
mCommand1.assertNotCalled();
}
diff --git a/tests/unit/com/android/documentsui/sorting/SortDimensionTest.java b/tests/unit/com/android/documentsui/sorting/SortDimensionTest.java
index 9468b84..c7abb9a 100644
--- a/tests/unit/com/android/documentsui/sorting/SortDimensionTest.java
+++ b/tests/unit/com/android/documentsui/sorting/SortDimensionTest.java
@@ -84,6 +84,6 @@
}
@Test
public void testParceling() {
- Parcelables.testParceling(mDimension, 0);
+ Parcelables.assertParcelable(mDimension, 0);
}
}
diff --git a/tests/unit/com/android/documentsui/sorting/SortModelTest.java b/tests/unit/com/android/documentsui/sorting/SortModelTest.java
index aa50fb0..c0acdb6 100644
--- a/tests/unit/com/android/documentsui/sorting/SortModelTest.java
+++ b/tests/unit/com/android/documentsui/sorting/SortModelTest.java
@@ -211,12 +211,12 @@
mModel.sortByUser(DIMENSION_2.getId(), SortDimension.SORT_DIRECTION_DESCENDING);
mModel.setDimensionVisibility(DIMENSION_3.getId(), View.GONE);
- Parcelables.testParceling(mModel, 0);
+ Parcelables.assertParcelable(mModel, 0);
}
@Test
public void testParceling_NoSortedDimension() {
- Parcelables.testParceling(mModel, 0);
+ Parcelables.assertParcelable(mModel, 0);
}
private @Nullable SortDimension getSortedDimension() {
diff --git a/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java b/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java
index 1d6a09c..eb1c54f 100644
--- a/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java
+++ b/tests/unit/com/android/documentsui/sorting/SortingCursorWrapperTest.java
@@ -17,13 +17,14 @@
package com.android.documentsui.sorting;
import static com.android.documentsui.base.DocumentInfo.getCursorString;
-
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.os.Bundle;
+import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.support.test.runner.AndroidJUnit4;
@@ -366,6 +367,27 @@
}
}
+ @Test
+ public void testReturnsWrappedExtras() {
+ MatrixCursor c = new MatrixCursor(COLUMNS);
+ Bundle extras = new Bundle();
+ extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
+ extras.putString(DocumentsContract.EXTRA_INFO, "cheddar");
+ extras.putString(DocumentsContract.EXTRA_ERROR, "flop");
+ c.setExtras(extras);
+
+ // set sorting to avoid an NPE.
+ sortModel.sortByUser(
+ SortModel.SORT_DIMENSION_ID_DATE,
+ SortDimension.SORT_DIRECTION_DESCENDING);
+
+ Bundle actual = createSortingCursorWrapper(c).getExtras();
+
+ assertTrue(actual.getBoolean(DocumentsContract.EXTRA_LOADING, false));
+ assertEquals("cheddar", actual.getString(DocumentsContract.EXTRA_INFO));
+ assertEquals("flop", actual.getString(DocumentsContract.EXTRA_ERROR));
+ }
+
private Cursor createSortingCursorWrapper() {
return createSortingCursorWrapper(cursor);
}