Merge "Using Activity#setTitle to have TalkBack announce root traversal." into nyc-andromeda-dev
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/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/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 1a26bcc..86093f9 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -38,6 +38,7 @@
import com.android.documentsui.dirlist.AnimationView.AnimationType;
import com.android.documentsui.dirlist.AnimationView;
import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.dirlist.FocusHandler;
import com.android.documentsui.files.LauncherActivity;
import com.android.documentsui.queries.SearchViewManager;
import com.android.documentsui.roots.LoadRootTask;
@@ -62,15 +63,18 @@
protected final State mState;
protected final RootsAccess mRoots;
protected final DocumentsAccess mDocs;
+ protected final FocusHandler mFocusHandler;
protected final SelectionManager mSelectionMgr;
protected final SearchViewManager mSearchMgr;
protected final Lookup<String, Executor> mExecutors;
+
public AbstractActionHandler(
T activity,
State state,
RootsAccess roots,
DocumentsAccess docs,
+ FocusHandler focusHandler,
SelectionManager selectionMgr,
SearchViewManager searchMgr,
Lookup<String, Executor> executors) {
@@ -78,6 +82,7 @@
assert(activity != null);
assert(state != null);
assert(roots != null);
+ assert(focusHandler != null);
assert(selectionMgr != null);
assert(searchMgr != null);
assert(docs != null);
@@ -86,6 +91,7 @@
mState = state;
mRoots = roots;
mDocs = docs;
+ mFocusHandler = focusHandler;
mSelectionMgr = selectionMgr;
mSearchMgr = searchMgr;
mExecutors = executors;
@@ -232,6 +238,16 @@
}
@Override
+ public void cutToClipboard() {
+ throw new UnsupportedOperationException("Cut not supported!");
+ }
+
+ @Override
+ public void copyToClipboard() {
+ throw new UnsupportedOperationException("Copy not supported!");
+ }
+
+ @Override
public void deleteSelectedDocuments() {
throw new UnsupportedOperationException("Delete not supported!");
}
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index 7f6f923..d92ebbb 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -65,6 +65,13 @@
void openContainerDocument(DocumentInfo doc);
+ void cutToClipboard();
+
+ void copyToClipboard();
+
+ /**
+ * In general, selected = selection or single focused item
+ */
void deleteSelectedDocuments();
void shareSelectedDocuments();
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index f01b1a2..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);
@@ -672,20 +672,21 @@
* locked, open/close it as appropriate.
*/
void toggleNavDrawerFocus() {
+ boolean toogleHappened = false;
if (mNavDrawerHasFocus) {
mDrawer.setOpen(false);
DirectoryFragment df = DirectoryFragment.get(getFragmentManager());
- if (df != null) {
- df.requestFocus();
- }
+ assert (df != null);
+ toogleHappened = df.requestFocus();
} else {
mDrawer.setOpen(true);
RootsFragment rf = RootsFragment.get(getFragmentManager());
- if (rf != null) {
- rf.requestFocus();
- }
+ assert (rf != null);
+ toogleHappened = rf.requestFocus();
}
- mNavDrawerHasFocus = !mNavDrawerHasFocus;
+ if (toogleHappened) {
+ mNavDrawerHasFocus = !mNavDrawerHasFocus;
+ }
}
/**
@@ -737,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/FocusManager.java b/src/com/android/documentsui/FocusManager.java
index 2371b7f..8f5d388 100644
--- a/src/com/android/documentsui/FocusManager.java
+++ b/src/com/android/documentsui/FocusManager.java
@@ -146,6 +146,16 @@
return mScope.lastFocusPosition;
}
+ @Override
+ public @Nullable String getFocusModelId() {
+ if (mScope.lastFocusPosition != RecyclerView.NO_POSITION) {
+ DocumentHolder holder = (DocumentHolder) mScope.view
+ .findViewHolderForAdapterPosition(mScope.lastFocusPosition);
+ return holder.getModelId();
+ }
+ return null;
+ }
+
/**
* Finds the destination position where the focus should land for a given navigation event.
*
diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java
index e261c9f..55d2b0e 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);
}
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..34728a7 100644
--- a/src/com/android/documentsui/base/DocumentStack.java
+++ b/src/com/android/documentsui/base/DocumentStack.java
@@ -78,11 +78,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);
}
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..3fab225 100644
--- a/src/com/android/documentsui/base/Shared.java
+++ b/src/com/android/documentsui/base/Shared.java
@@ -249,7 +249,8 @@
}
/*
- * 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);
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 b7f24f5..cc6a548 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) {
@@ -581,11 +633,11 @@
return true;
case R.id.menu_cut_to_clipboard:
- cutSelectedToClipboard();
+ mActions.cutToClipboard();
return true;
case R.id.menu_copy_to_clipboard:
- copySelectedToClipboard();
+ mActions.copyToClipboard();
return true;
case R.id.menu_paste_from_clipboard:
@@ -786,34 +838,6 @@
mRecView.requestFocus();
}
- public void copySelectedToClipboard() {
- Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_CLIPBOARD);
-
- Selection selection = mSelectionMgr.getSelection(new Selection());
- if (selection.isEmpty()) {
- return;
- }
- mSelectionMgr.clearSelection();
-
- mClipper.clipDocumentsForCopy(mModel::getItemUri, selection);
-
- Snackbars.showDocumentsClipped(getActivity(), selection.size());
- }
-
- public void cutSelectedToClipboard() {
- Metrics.logUserAction(getContext(), Metrics.USER_ACTION_CUT_CLIPBOARD);
-
- Selection selection = mSelectionMgr.getSelection(new Selection());
- if (selection.isEmpty()) {
- return;
- }
- mSelectionMgr.clearSelection();
-
- mClipper.clipDocumentsForCut(mModel::getItemUri, selection, mState.stack.peek());
-
- Snackbars.showDocumentsClipped(getActivity(), selection.size());
- }
-
public void pasteFromClipboard() {
Metrics.logUserAction(getContext(), Metrics.USER_ACTION_PASTE_CLIPBOARD);
@@ -868,8 +892,12 @@
/**
* Attempts to restore focus on the directory listing.
*/
- public void requestFocus() {
+ public boolean requestFocus() {
+ if (mSelectionMgr.hasSelection()) {
+ return false;
+ }
mFocusManager.restoreLastFocus();
+ return true;
}
private void setupDragAndDropOnDocumentView(View view, Cursor cursor) {
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/FocusHandler.java b/src/com/android/documentsui/dirlist/FocusHandler.java
index 0f43ac0..1cbb8a9 100644
--- a/src/com/android/documentsui/dirlist/FocusHandler.java
+++ b/src/com/android/documentsui/dirlist/FocusHandler.java
@@ -16,6 +16,7 @@
package com.android.documentsui.dirlist;
+import android.annotation.Nullable;
import android.view.KeyEvent;
import android.view.View;
@@ -52,4 +53,8 @@
*/
int getFocusPosition();
+ /**
+ * @return The modelId of the last focused item. If no item is focused, this should return null.
+ */
+ @Nullable String getFocusModelId();
}
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/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index afea4cd..09f5ce1 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -48,6 +48,7 @@
import com.android.documentsui.clipping.UrisSupplier;
import com.android.documentsui.dirlist.AnimationView;
import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.dirlist.FocusHandler;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.dirlist.Model.Update;
import com.android.documentsui.files.ActionHandler.Addons;
@@ -87,6 +88,7 @@
State state,
RootsAccess roots,
DocumentsAccess docs,
+ FocusHandler focusHandler,
SelectionManager selectionMgr,
SearchViewManager searchMgr,
Lookup<String, Executor> executors,
@@ -96,7 +98,7 @@
DocumentClipper clipper,
ClipStore clipStore) {
- super(activity, state, roots, docs, selectionMgr, searchMgr, executors);
+ super(activity, state, roots, docs, focusHandler, selectionMgr, searchMgr, executors);
mActionModeAddons = actionModeAddons;
mDialogs = dialogs;
@@ -190,14 +192,57 @@
return previewDocument(doc);
}
+ private Selection getSelectedOrFocused() {
+ final Selection selection = this.getStableSelection();
+ if (selection.isEmpty()) {
+ String focusModelId = mFocusHandler.getFocusModelId();
+ if (focusModelId != null) {
+ selection.add(focusModelId);
+ }
+ }
+
+ return selection;
+ }
+
+ @Override
+ public void cutToClipboard() {
+ Metrics.logUserAction(mActivity, Metrics.USER_ACTION_CUT_CLIPBOARD);
+ Selection selection = getSelectedOrFocused();
+
+ if (selection.isEmpty()) {
+ return;
+ }
+ mSelectionMgr.clearSelection();
+
+ mClipper.clipDocumentsForCut(mScope.model::getItemUri, selection, mState.stack.peek());
+
+ mDialogs.showDocumentsClipped(selection.size());
+ }
+
+ @Override
+ public void copyToClipboard() {
+ Metrics.logUserAction(mActivity, Metrics.USER_ACTION_COPY_CLIPBOARD);
+ Selection selection = getSelectedOrFocused();
+
+ if (selection.isEmpty()) {
+ return;
+ }
+ mSelectionMgr.clearSelection();
+
+ mClipper.clipDocumentsForCopy(mScope.model::getItemUri, selection);
+
+ mDialogs.showDocumentsClipped(selection.size());
+ }
+
+
@Override
public void deleteSelectedDocuments() {
- assert(mSelectionMgr.hasSelection());
-
Metrics.logUserAction(mActivity, Metrics.USER_ACTION_DELETE);
+ Selection selection = getSelectedOrFocused();
- Selection selection = getStableSelection();
- assert(!selection.isEmpty());
+ if (selection.isEmpty()) {
+ return;
+ }
final DocumentInfo srcParent = mState.stack.peek();
assert(srcParent != null);
diff --git a/src/com/android/documentsui/files/ActivityInputHandler.java b/src/com/android/documentsui/files/ActivityInputHandler.java
index a7b0e2c..35653d2 100644
--- a/src/com/android/documentsui/files/ActivityInputHandler.java
+++ b/src/com/android/documentsui/files/ActivityInputHandler.java
@@ -18,31 +18,22 @@
import android.view.KeyEvent;
-import com.android.documentsui.selection.SelectionManager;
-import com.android.documentsui.ActionHandler;
-
/**
* Used by {@link FilesActivity} to manage global keyboard shortcuts tied to file actions
*/
final class ActivityInputHandler {
- private final SelectionManager mSelectionMgr;
- private final ActionHandler mActions;
+ private final Runnable mDeleteHandler;
- ActivityInputHandler(SelectionManager selectionMgr, ActionHandler actionHandler) {
- mSelectionMgr = selectionMgr;
- mActions = actionHandler;
+ ActivityInputHandler(Runnable deleteHandler) {
+ mDeleteHandler = deleteHandler;
}
boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_DEL && event.isAltPressed())
|| keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
- if (mSelectionMgr.hasSelection()) {
- mActions.deleteSelectedDocuments();
- return true;
- } else {
- return false;
- }
+ mDeleteHandler.run();
+ return true;
}
return false;
}
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 3eecdca..a62e6af 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -56,6 +56,7 @@
import com.android.documentsui.dirlist.DirectoryFragment;
import com.android.documentsui.dirlist.DocumentsAdapter;
import com.android.documentsui.dirlist.Model;
+import com.android.documentsui.selection.Selection;
import com.android.documentsui.selection.SelectionManager;
import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
import com.android.documentsui.services.FileOperationService;
@@ -124,6 +125,7 @@
mState,
mRoots,
mDocs,
+ mFocusManager,
mSelectionMgr,
mSearchManager,
ProviderExecutor::forAuthority,
@@ -133,7 +135,7 @@
mClipper,
DocumentsApplication.getClipStore(this));
- mActivityInputHandler = new ActivityInputHandler(mSelectionMgr, mActions);
+ mActivityInputHandler = new ActivityInputHandler(mActions::deleteSelectedDocuments);
RootsFragment.show(getFragmentManager(), null);
@@ -324,16 +326,10 @@
}
return true;
case KeyEvent.KEYCODE_X:
- dir = getDirectoryFragment();
- if (dir != null) {
- dir.cutSelectedToClipboard();
- }
+ mActions.cutToClipboard();
return true;
case KeyEvent.KEYCODE_C:
- dir = getDirectoryFragment();
- if (dir != null) {
- dir.copySelectedToClipboard();
- }
+ mActions.copyToClipboard();
return true;
case KeyEvent.KEYCODE_V:
dir = getDirectoryFragment();
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/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index 6e1b9d4..1999381 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -40,6 +40,7 @@
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
import com.android.documentsui.dirlist.DocumentDetails;
+import com.android.documentsui.dirlist.FocusHandler;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.dirlist.Model.Update;
import com.android.documentsui.picker.ActionHandler.Addons;
@@ -66,12 +67,13 @@
State state,
RootsAccess roots,
DocumentsAccess docs,
+ FocusHandler focusHandler,
SelectionManager selectionMgr,
SearchViewManager searchMgr,
Lookup<String, Executor> executors,
ActivityConfig activityConfig) {
- super(activity, state, roots, docs, selectionMgr, searchMgr, executors);
+ super(activity, state, roots, docs, focusHandler, selectionMgr, searchMgr, executors);
mConfig = activityConfig;
mScope = new ContentScope(this::onModelLoaded);
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index 268d801..eee92e5 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -110,6 +110,7 @@
mState,
mRoots,
mDocs,
+ mFocusManager,
mSelectionMgr,
mSearchManager,
ProviderExecutor::forAuthority,
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/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/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 8890fb3..4e9287e 100644
--- a/src/com/android/documentsui/sidebar/RootsFragment.java
+++ b/src/com/android/documentsui/sidebar/RootsFragment.java
@@ -357,8 +357,8 @@
/**
* Attempts to shift focus back to the navigation drawer.
*/
- public void requestFocus() {
- mList.requestFocus();
+ public boolean requestFocus() {
+ return mList.requestFocus();
}
private BaseActivity<?> getBaseActivity() {
diff --git a/src/com/android/documentsui/ui/DialogController.java b/src/com/android/documentsui/ui/DialogController.java
index 6c648a2..39bebfe 100644
--- a/src/com/android/documentsui/ui/DialogController.java
+++ b/src/com/android/documentsui/ui/DialogController.java
@@ -50,11 +50,17 @@
public void showNoApplicationFound() {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public void showDocumentsClipped(int size) {
+ throw new UnsupportedOperationException();
+ }
};
void confirmDelete(List<DocumentInfo> docs, ConfirmationCallback callback);
void showFileOperationFailures(int status, int opType, int docCount);
void showNoApplicationFound();
+ void showDocumentsClipped(int size);
// Should be private, but Java doesn't like me treating an interface like a mini-package.
public static final class RuntimeDialogController implements DialogController {
@@ -137,6 +143,11 @@
Snackbars.makeSnackbar(
mActivity, R.string.toast_no_application, Snackbar.LENGTH_SHORT).show();
}
+
+ @Override
+ public void showDocumentsClipped(int size) {
+ Snackbars.showDocumentsClipped(mActivity, size);
+ }
}
static DialogController create(Activity activity, MessageBuilder messages) {
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/dirlist/TestFocusHandler.java b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java
index 45e0655..76db4e1 100644
--- a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java
+++ b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java
@@ -45,6 +45,11 @@
}
@Override
+ public String getFocusModelId() {
+ return null;
+ }
+
+ @Override
public void focusDocument(String modelId) {
}
diff --git a/tests/common/com/android/documentsui/testing/TestActionHandler.java b/tests/common/com/android/documentsui/testing/TestActionHandler.java
index 69f716c..bd8adac 100644
--- a/tests/common/com/android/documentsui/testing/TestActionHandler.java
+++ b/tests/common/com/android/documentsui/testing/TestActionHandler.java
@@ -40,6 +40,7 @@
env.state,
env.roots,
env.docs,
+ env.focusHandler,
env.selectionMgr,
env.searchViewManager,
(String authority) -> null);
diff --git a/tests/common/com/android/documentsui/testing/TestConfirmationCallback.java b/tests/common/com/android/documentsui/testing/TestConfirmationCallback.java
index 6ffdf70..f94f33f 100644
--- a/tests/common/com/android/documentsui/testing/TestConfirmationCallback.java
+++ b/tests/common/com/android/documentsui/testing/TestConfirmationCallback.java
@@ -48,4 +48,8 @@
public void assertCalled() {
Assert.assertTrue(mCalled);
}
+
+ public void assertNeverCalled() {
+ Assert.assertFalse(mCalled);
+ }
}
diff --git a/tests/common/com/android/documentsui/testing/TestEnv.java b/tests/common/com/android/documentsui/testing/TestEnv.java
index e0af394..076e621 100644
--- a/tests/common/com/android/documentsui/testing/TestEnv.java
+++ b/tests/common/com/android/documentsui/testing/TestEnv.java
@@ -21,6 +21,7 @@
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.State;
+import com.android.documentsui.dirlist.TestFocusHandler;
import com.android.documentsui.dirlist.TestModel;
import com.android.documentsui.selection.SelectionManager;
@@ -50,6 +51,7 @@
public final State state = new State();
public final TestRootsAccess roots = new TestRootsAccess();
public final TestDocumentsAccess docs = new TestDocumentsAccess();
+ public final TestFocusHandler focusHandler = new TestFocusHandler();
public final TestModel model;
public final SelectionManager selectionMgr;
public final TestSearchViewManager searchViewManager;
diff --git a/tests/common/com/android/documentsui/ui/TestDialogController.java b/tests/common/com/android/documentsui/ui/TestDialogController.java
index dbac0f5..e3135be 100644
--- a/tests/common/com/android/documentsui/ui/TestDialogController.java
+++ b/tests/common/com/android/documentsui/ui/TestDialogController.java
@@ -28,6 +28,7 @@
public int mNextConfirmationCode;
private boolean mFileOpFailed;
private boolean mNoApplicationFound;
+ private boolean mDocumentsClipped;
public TestDialogController() {
// by default, always confirm
@@ -51,6 +52,11 @@
mNoApplicationFound = true;
}
+ @Override
+ public void showDocumentsClipped(int size) {
+ mDocumentsClipped = true;
+ }
+
public void assertNoFileFailures() {
Assert.assertFalse(mFileOpFailed);
}
@@ -59,6 +65,10 @@
Assert.assertFalse(mNoApplicationFound);
}
+ public void assertDocumentsClippedNotShown() {
+ Assert.assertFalse(mDocumentsClipped);
+ }
+
public void confirmNext() {
mNextConfirmationCode = ConfirmationCallback.CONFIRM;
}
diff --git a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
index 5ac2741..b3379bf 100644
--- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java
@@ -59,6 +59,7 @@
mEnv.state,
mEnv.roots,
mEnv.docs,
+ mEnv.focusHandler,
mEnv.selectionMgr,
mEnv.searchViewManager,
mEnv::lookupExecutor) {
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/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
index 271639b..c332bfd 100644
--- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
@@ -36,6 +36,7 @@
import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
+import com.android.documentsui.selection.Selection;
import com.android.documentsui.testing.Roots;
import com.android.documentsui.testing.TestConfirmationCallback;
import com.android.documentsui.testing.TestEnv;
@@ -71,6 +72,7 @@
mEnv.state,
mEnv.roots,
mEnv.docs,
+ mEnv.focusHandler,
mEnv.selectionMgr,
mEnv.searchViewManager,
mEnv::lookupExecutor,
@@ -112,13 +114,32 @@
}
@Test
- public void testDeleteSelectedDocuments() {
+ public void testCutSelectedDocuments_NoGivenSelection() {
mEnv.populateStack();
+ mEnv.selectionMgr.clearSelection();
+ mHandler.cutToClipboard();
+ mDialogs.assertDocumentsClippedNotShown();
+ }
+
+ @Test
+ public void testCopySelectedDocuments_NoGivenSelection() {
+ mEnv.populateStack();
+
+ mEnv.selectionMgr.clearSelection();
+ mHandler.copyToClipboard();
+ mDialogs.assertDocumentsClippedNotShown();
+ }
+
+ @Test
+ public void testDeleteSelectedDocuments_NoSelection() {
+ mEnv.populateStack();
+
+ mEnv.selectionMgr.clearSelection();
mHandler.deleteSelectedDocuments();
mDialogs.assertNoFileFailures();
- mActivity.startService.assertCalled();
- mActionModeAddons.finishOnConfirmed.assertConfirmed();
+ mActivity.startService.assertNotCalled();
+ mActionModeAddons.finishOnConfirmed.assertNeverCalled();
}
@Test
diff --git a/tests/unit/com/android/documentsui/files/ActivityInputHandlerTest.java b/tests/unit/com/android/documentsui/files/ActivityInputHandlerTest.java
index 37a696f..b579dbe 100644
--- a/tests/unit/com/android/documentsui/files/ActivityInputHandlerTest.java
+++ b/tests/unit/com/android/documentsui/files/ActivityInputHandlerTest.java
@@ -16,7 +16,6 @@
package com.android.documentsui.files;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.support.test.filters.MediumTest;
@@ -24,50 +23,30 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
-import com.android.documentsui.dirlist.TestData;
-import com.android.documentsui.selection.SelectionManager;
-import com.android.documentsui.selection.SelectionProbe;
-import com.android.documentsui.testing.SelectionManagers;
-import com.android.documentsui.testing.TestActionHandler;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.List;
-
@RunWith(AndroidJUnit4.class)
@MediumTest
public class ActivityInputHandlerTest {
- private static final List<String> ITEMS = TestData.create(100);
-
- private SelectionProbe mSelection;
- private TestActionHandler mActionHandler;
private ActivityInputHandler mActivityInputHandler;
+ private boolean mDeleteHappened;
@Before
public void setUp() {
- SelectionManager selectionMgr = SelectionManagers.createTestInstance(ITEMS);
- mSelection = new SelectionProbe(selectionMgr);
- mActionHandler = new TestActionHandler();
- mActivityInputHandler = new ActivityInputHandler(selectionMgr, mActionHandler);
+ mDeleteHappened = false;
+ mActivityInputHandler = new ActivityInputHandler(() -> {
+ mDeleteHappened = true;
+ });
}
@Test
- public void testDelete_noSelection() {
- KeyEvent event = new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, 0,
- KeyEvent.META_ALT_ON);
- assertFalse(mActivityInputHandler.onKeyDown(event.getKeyCode(), event));
- assertFalse(mActionHandler.mDeleteHappened);
- }
-
- @Test
- public void testDelete_hasSelection() {
- mSelection.select(1);
+ public void testDelete() {
KeyEvent event = new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, 0,
KeyEvent.META_ALT_ON);
assertTrue(mActivityInputHandler.onKeyDown(event.getKeyCode(), event));
- assertTrue(mActionHandler.mDeleteHappened);
+ assertTrue(mDeleteHappened);
}
}
diff --git a/tests/unit/com/android/documentsui/files/MenuManagerTest.java b/tests/unit/com/android/documentsui/files/MenuManagerTest.java
index 5d1fb7d..bad3e66 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();
diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
index 73cb123..5e26cd8 100644
--- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
@@ -65,6 +65,7 @@
mEnv.state,
mEnv.roots,
mEnv.docs,
+ mEnv.focusHandler,
mEnv.selectionMgr,
mEnv.searchViewManager,
mEnv::lookupExecutor,
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();
}