diff options
author | 2018-11-06 17:16:47 +0800 | |
---|---|---|
committer | 2019-01-03 14:42:51 +0800 | |
commit | 7a7e7df7f310c6e46e347f1273755a0eb469df59 (patch) | |
tree | 29a3040d98c92921102acc5557ca8c8f9b8b37c5 | |
parent | eb43e960649c674c8f0c8c0386c8c48e12574435 (diff) |
Implement files preview on pick mode specific action
1. Add preview icon and show on specific action state such as
ACTION_OPEN, ACTION_GET_CONTENT and ACTION_OPEN_TREE.
if file is not enabled, the icon will be hidden.
2. Fix list item background color bug on tablet in night mode.
3. Ensure sort UI update after restoreRootAndDirectory.
4. Hide "Summary" column in tablet list mode because it seems not
display any content.
TODO: Delete "Summary" related code if ensure it not work.
Bug: 112470012
Bug: 120961136
Test: manual
Test: atest DocumentsUITests
Change-Id: Id07848ed17933b2938e300d50503afeb1df8c902
29 files changed, 549 insertions, 28 deletions
diff --git a/res/drawable/circle_button_background.xml b/res/drawable/circle_button_background.xml new file mode 100644 index 000000000..d5b3c50d1 --- /dev/null +++ b/res/drawable/circle_button_background.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid + android:color="#66000000"/> +</shape>
\ No newline at end of file diff --git a/res/drawable/ic_zoom_out.xml b/res/drawable/ic_zoom_out.xml new file mode 100644 index 000000000..295054a35 --- /dev/null +++ b/res/drawable/ic_zoom_out.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="@android:color/white" + android:pathData="M15,3l2.3,2.3 -2.89,2.87 1.42,1.42L18.7,6.7 21,9L21,3zM3,9l2.3,-2.3 2.87,2.89 1.42,-1.42L6.7,5.3 9,3L3,3zM9,21l-2.3,-2.3 2.89,-2.87 -1.42,-1.42L5.3,17.3 3,15v6zM21,15l-2.3,2.3 -2.87,-2.89 -1.42,1.42 2.89,2.87L15,21h6z"/> +</vector> diff --git a/res/layout-sw720dp/column_headers.xml b/res/layout-sw720dp/column_headers.xml index d5bcb766f..60be5dc10 100644 --- a/res/layout-sw720dp/column_headers.xml +++ b/res/layout-sw720dp/column_headers.xml @@ -31,7 +31,7 @@ android:gravity="center_vertical" android:minHeight="@dimen/list_item_height" android:paddingStart="@dimen/list_item_padding" - android:paddingEnd="@dimen/list_item_padding" + android:paddingEnd="@dimen/list_item_width" android:orientation="horizontal"> <!-- Placeholder for icon --> <View @@ -52,7 +52,7 @@ android:id="@android:id/title" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="0.375" + android:layout_weight="0.4" android:layout_marginEnd="12dp" android:focusable="true" android:gravity="center_vertical" @@ -66,8 +66,8 @@ android:id="@android:id/summary" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="0.25" - android:layout_marginEnd="12dp" + android:layout_weight="0" + android:layout_marginEnd="0dp" android:focusable="true" android:gravity="center_vertical" android:orientation="horizontal" @@ -80,7 +80,7 @@ android:id="@+id/file_type" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="0.125" + android:layout_weight="0.2" android:layout_marginEnd="12dp" android:focusable="true" android:gravity="center_vertical" @@ -94,7 +94,7 @@ android:id="@+id/size" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="0.125" + android:layout_weight="0.2" android:layout_marginEnd="12dp" android:focusable="true" android:gravity="center_vertical" @@ -108,7 +108,7 @@ android:id="@+id/date" android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="0.125" + android:layout_weight="0.2" android:layout_marginEnd="12dp" android:focusable="true" android:gravity="center_vertical" diff --git a/res/layout-sw720dp/item_doc_list.xml b/res/layout-sw720dp/item_doc_list.xml index 98e19f0d1..8f9eccd45 100644 --- a/res/layout-sw720dp/item_doc_list.xml +++ b/res/layout-sw720dp/item_doc_list.xml @@ -18,7 +18,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@drawable/list_item_background" + android:background="?android:attr/selectableItemBackground" android:focusable="true" android:orientation="horizontal" > @@ -74,15 +74,14 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:orientation="horizontal" - android:paddingEnd="@dimen/list_item_padding" > + android:orientation="horizontal" > <TextView android:id="@android:id/title" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="12dp" - android:layout_weight="0.375" + android:layout_weight="0.4" android:ellipsize="middle" android:singleLine="true" android:textAlignment="viewStart" @@ -93,8 +92,8 @@ android:id="@android:id/summary" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginEnd="12dp" - android:layout_weight="0.25" + android:layout_marginEnd="0dp" + android:layout_weight="0" android:ellipsize="end" android:singleLine="true" android:textAlignment="viewStart" @@ -106,7 +105,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="12dp" - android:layout_weight="0.125" + android:layout_weight="0.2" android:ellipsize="end" android:singleLine="true" android:textAlignment="viewStart" @@ -118,7 +117,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="12dp" - android:layout_weight="0.125" + android:layout_weight="0.2" android:ellipsize="end" android:minWidth="70dp" android:singleLine="true" @@ -131,7 +130,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="12dp" - android:layout_weight="0.125" + android:layout_weight="0.2" android:ellipsize="end" android:minWidth="70dp" android:singleLine="true" @@ -139,6 +138,34 @@ android:textAppearance="@android:style/TextAppearance.Material.Body1" android:textColor="?android:attr/textColorSecondary" /> </LinearLayout> + + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <FrameLayout + android:id="@+id/preview_icon" + android:layout_width="@dimen/list_item_width" + android:layout_height="@dimen/list_item_height" + android:padding="@dimen/list_item_icon_padding" + android:focusable="true"> + + <ImageView + android:layout_width="@dimen/check_icon_size" + android:layout_height="@dimen/check_icon_size" + android:layout_gravity="center" + android:scaleType="fitCenter" + android:tint="?android:attr/textColorPrimary" + android:src="@drawable/ic_zoom_out"/> + + </FrameLayout> + + <android.widget.Space + android:layout_width="@dimen/list_item_width" + android:layout_height="@dimen/list_item_height"/> + + </FrameLayout> + </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/res/layout/item_dir_grid.xml b/res/layout/item_dir_grid.xml index 4b6f65967..000790e12 100644 --- a/res/layout/item_dir_grid.xml +++ b/res/layout/item_dir_grid.xml @@ -23,6 +23,7 @@ decide to rip these out, please be sure to check out focus and keyboards. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/item_root" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" diff --git a/res/layout/item_doc_grid.xml b/res/layout/item_doc_grid.xml index ddd3acba4..ed369ef8d 100644 --- a/res/layout/item_doc_grid.xml +++ b/res/layout/item_doc_grid.xml @@ -22,6 +22,7 @@ decide to rip these out, please be sure to check out focus and keyboards. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/item_root" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" @@ -68,6 +69,28 @@ </FrameLayout> + <FrameLayout + android:id="@+id/preview_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="4dp" + android:layout_alignParentTop="true" + android:layout_alignParentEnd="true" + android:background="@drawable/circle_button_background" + android:pointerIcon="hand" + android:focusable="true" + android:clickable="true"> + + <ImageView + android:layout_width="@dimen/zoom_icon_size" + android:layout_height="@dimen/zoom_icon_size" + android:layout_margin="2dp" + android:layout_gravity="center" + android:scaleType="fitCenter" + android:src="@drawable/ic_zoom_out"/> + + </FrameLayout> + <!-- Item nameplate. Has a mime-type icon and some text fields (title, size, mod-time, etc). --> diff --git a/res/layout/item_doc_list.xml b/res/layout/item_doc_list.xml index 6b929b293..d8f3d3425 100644 --- a/res/layout/item_doc_list.xml +++ b/res/layout/item_doc_list.xml @@ -15,7 +15,9 @@ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/item_root" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?android:attr/selectableItemBackground" @@ -96,7 +98,7 @@ <TextView android:id="@+id/date" - android:layout_width="90dp" + android:layout_width="70dp" android:layout_height="wrap_content" android:ellipsize="end" android:singleLine="true" @@ -105,7 +107,7 @@ <TextView android:id="@+id/size" - android:layout_width="90dp" + android:layout_width="70dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:ellipsize="end" @@ -115,7 +117,7 @@ <TextView android:id="@+id/file_type" - android:layout_width="90dp" + android:layout_width="70dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:ellipsize="end" @@ -135,6 +137,25 @@ android:textAppearance="@android:style/TextAppearance.Material.Caption"/> </LinearLayout> </LinearLayout> + + <FrameLayout + android:id="@+id/preview_icon" + android:layout_width="@dimen/list_item_width" + android:layout_height="@dimen/list_item_height" + android:padding="@dimen/list_item_icon_padding" + android:focusable="true" + android:clickable="true"> + + <ImageView + android:layout_width="@dimen/check_icon_size" + android:layout_height="@dimen/check_icon_size" + android:layout_gravity="center" + android:scaleType="fitCenter" + android:tint="?android:attr/textColorPrimary" + android:src="@drawable/ic_zoom_out"/> + + </FrameLayout> + </LinearLayout> </LinearLayout> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index bc3ab5f65..b3dfd1a6b 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -22,6 +22,7 @@ <dimen name="root_icon_margin">0dp</dimen> <dimen name="root_spacer_padding">0dp</dimen> <dimen name="check_icon_size">30dp</dimen> + <dimen name="zoom_icon_size">22dp</dimen> <dimen name="list_item_thumbnail_size">40dp</dimen> <dimen name="grid_item_icon_size">30dp</dimen> <dimen name="progress_bar_height">4dp</dimen> diff --git a/res/values/strings.xml b/res/values/strings.xml index 5247753f3..5083f51c8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -427,4 +427,6 @@ <!-- Hint on text input field for rename the file or the folder. [CHAR_LIMIT=48] --> <string name="input_hint_rename">New name</string> + <!-- Content description for preivew function. --> + <string name="preview_file">Preview the file <xliff:g id="fileName" example="example.jpg">%1$s</xliff:g></string> </resources> diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java index b6ea20e6b..0eef2d76d 100644 --- a/src/com/android/documentsui/AbstractActionHandler.java +++ b/src/com/android/documentsui/AbstractActionHandler.java @@ -349,6 +349,11 @@ public abstract class AbstractActionHandler<T extends FragmentActivity & CommonA } } + @Override + public boolean previewItem(ItemDetails<String> doc) { + throw new UnsupportedOperationException("Can't handle preview."); + } + private void openFolderInSearchResult(@Nullable DocumentStack stack, DocumentInfo doc) { if (stack == null) { mState.stack.popToRootDocument(); diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java index 0ff6e34f9..5f74b583e 100644 --- a/src/com/android/documentsui/ActionHandler.java +++ b/src/com/android/documentsui/ActionHandler.java @@ -130,6 +130,8 @@ public interface ActionHandler { void openContainerDocument(DocumentInfo doc); + boolean previewItem(ItemDetails<String> doc); + void cutToClipboard(); void copyToClipboard(); diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java index 20d8f0d6f..87438a8db 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -455,6 +455,7 @@ public abstract class BaseActivity setTitle(mState.stack.getTitle()); invalidateOptionsMenu(); + mSortController.onViewModeChanged(mState.derivedMode); } private final List<String> getExcludedAuthorities() { diff --git a/src/com/android/documentsui/base/State.java b/src/com/android/documentsui/base/State.java index a2e5ba3a2..bd4d86273 100644 --- a/src/com/android/documentsui/base/State.java +++ b/src/com/android/documentsui/base/State.java @@ -115,6 +115,16 @@ public class State implements android.os.Parcelable { } } + /** + * Check current action should have preview function or not. + * @return True, if the action should have preview. + */ + public boolean shouldShowPreview() { + return action == ACTION_GET_CONTENT + || action == ACTION_OPEN_TREE + || action == ACTION_OPEN; + } + @Override public int describeContents() { return 0; diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java index 2171f8601..064cb35be 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -489,6 +489,10 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On } private boolean onItemActivated(ItemDetails<String> item, MotionEvent e) { + if (((DocumentItemDetails) item).inPreviewIconHotspot(e)) { + return mActions.previewItem(item); + } + return mActions.openItem( item, ActionHandler.VIEW_TYPE_PREVIEW, diff --git a/src/com/android/documentsui/dirlist/DocumentHolder.java b/src/com/android/documentsui/dirlist/DocumentHolder.java index c1c5c1ee3..41257cce9 100644 --- a/src/com/android/documentsui/dirlist/DocumentHolder.java +++ b/src/com/android/documentsui/dirlist/DocumentHolder.java @@ -18,6 +18,7 @@ package com.android.documentsui.dirlist; import android.content.Context; import android.database.Cursor; +import android.os.Bundle; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -26,10 +27,14 @@ import android.view.ViewGroup; import android.view.ViewPropertyAnimator; import android.widget.ImageView; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.recyclerview.widget.RecyclerView; import com.android.documentsui.base.Shared; +import java.util.function.Consumer; +import java.util.function.Function; + import javax.annotation.Nullable; /** @@ -94,6 +99,8 @@ public abstract class DocumentHolder setEnabledRecursive(itemView, enabled); } + public void bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback) {} + @Override public boolean onKey(View v, int keyCode, KeyEvent event) { assert(mKeyEventListener != null); @@ -124,6 +131,10 @@ public abstract class DocumentHolder return false; } + public boolean inPreviewIconRegion(MotionEvent event) { + return false; + } + public DocumentItemDetails getItemDetails() { return mDetails; } @@ -151,4 +162,21 @@ public abstract class DocumentHolder static ViewPropertyAnimator fade(ImageView view, float alpha) { return view.animate().setDuration(Shared.CHECK_ANIMATION_DURATION).alpha(alpha); } + + protected static class PreviewAccessibilityDelegate extends View.AccessibilityDelegate { + private Function<View, Boolean> mCallback; + + public PreviewAccessibilityDelegate(Function<View, Boolean> clickCallback) { + super(); + mCallback = clickCallback; + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) { + return mCallback.apply(host); + } + return super.performAccessibilityAction(host, action, args); + } + } } diff --git a/src/com/android/documentsui/dirlist/DocumentItemDetails.java b/src/com/android/documentsui/dirlist/DocumentItemDetails.java index bf6d7ddd5..ab8644836 100644 --- a/src/com/android/documentsui/dirlist/DocumentItemDetails.java +++ b/src/com/android/documentsui/dirlist/DocumentItemDetails.java @@ -56,4 +56,8 @@ public final class DocumentItemDetails extends ItemDetails<String> { public boolean inSelectionHotspot(MotionEvent e) { return mDocumentHolder.inSelectRegion(e); } + + public boolean inPreviewIconHotspot(MotionEvent e) { + return mDocumentHolder.inPreviewIconRegion(e); + } } diff --git a/src/com/android/documentsui/dirlist/GridDocumentHolder.java b/src/com/android/documentsui/dirlist/GridDocumentHolder.java index e89e6963e..e27d21097 100644 --- a/src/com/android/documentsui/dirlist/GridDocumentHolder.java +++ b/src/com/android/documentsui/dirlist/GridDocumentHolder.java @@ -37,6 +37,8 @@ import com.android.documentsui.base.Shared; import com.android.documentsui.roots.RootCursorWrapper; import com.android.documentsui.ui.Views; +import java.util.function.Function; + final class GridDocumentHolder extends DocumentHolder { final TextView mTitle; @@ -48,6 +50,7 @@ final class GridDocumentHolder extends DocumentHolder { final ImageView mIconCheck; final IconHelper mIconHelper; final View mIconLayout; + final View mPreviewIcon; // This is used in as a convenience in our bind method. private final DocumentInfo mDoc = new DocumentInfo(); @@ -63,6 +66,7 @@ final class GridDocumentHolder extends DocumentHolder { mIconMimeSm = (ImageView) itemView.findViewById(R.id.icon_mime_sm); mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb); mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check); + mPreviewIcon = itemView.findViewById(R.id.preview_icon); mIconHelper = iconHelper; } @@ -107,6 +111,16 @@ final class GridDocumentHolder extends DocumentHolder { } @Override + public void bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback) { + mPreviewIcon.setVisibility(show ? View.VISIBLE : View.GONE); + if (show) { + mPreviewIcon.setContentDescription( + itemView.getResources().getString(R.string.preview_file, mDoc.displayName)); + mPreviewIcon.setAccessibilityDelegate(new PreviewAccessibilityDelegate(clickCallback)); + } + } + + @Override public boolean inDragRegion(MotionEvent event) { // Entire grid box should be draggable return true; @@ -117,6 +131,11 @@ final class GridDocumentHolder extends DocumentHolder { return Views.isEventOver(event, mIconLayout); } + @Override + public boolean inPreviewIconRegion(MotionEvent event) { + return Views.isEventOver(event, mPreviewIcon); + } + /** * Bind this view to the given document for display. * @param cursor Pointing to the item to be bound. diff --git a/src/com/android/documentsui/dirlist/ListDocumentHolder.java b/src/com/android/documentsui/dirlist/ListDocumentHolder.java index a33aa9a6e..5232f26f7 100644 --- a/src/com/android/documentsui/dirlist/ListDocumentHolder.java +++ b/src/com/android/documentsui/dirlist/ListDocumentHolder.java @@ -37,6 +37,8 @@ import com.android.documentsui.base.Shared; import com.android.documentsui.roots.RootCursorWrapper; import com.android.documentsui.ui.Views; +import java.util.function.Function; + final class ListDocumentHolder extends DocumentHolder { private final TextView mTitle; @@ -49,6 +51,7 @@ final class ListDocumentHolder extends DocumentHolder { private final ImageView mIconThumb; private final ImageView mIconCheck; private final View mIconLayout; + final View mPreviewIcon; private final IconHelper mIconHelper; private final Lookup<String, String> mFileTypeLookup; @@ -70,6 +73,7 @@ final class ListDocumentHolder extends DocumentHolder { mType = (TextView) itemView.findViewById(R.id.file_type); // Warning: mDetails view doesn't exists in layout-sw720dp-land layout mDetails = (LinearLayout) itemView.findViewById(R.id.line2); + mPreviewIcon = itemView.findViewById(R.id.preview_icon); mIconHelper = iconHelper; mFileTypeLookup = fileTypeLookup; @@ -115,6 +119,21 @@ final class ListDocumentHolder extends DocumentHolder { } @Override + public void bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback) { + if (mDoc.isDirectory()) { + mPreviewIcon.setVisibility(View.GONE); + } else { + mPreviewIcon.setVisibility(show ? View.VISIBLE : View.GONE); + if (show) { + mPreviewIcon.setContentDescription( + itemView.getResources().getString(R.string.preview_file, mDoc.displayName)); + mPreviewIcon.setAccessibilityDelegate( + new PreviewAccessibilityDelegate(clickCallback)); + } + } + } + + @Override public boolean inDragRegion(MotionEvent event) { // If itemView is activated = selected, then whole region is interactive if (itemView.isActivated()) { @@ -144,6 +163,11 @@ final class ListDocumentHolder extends DocumentHolder { return Views.isEventOver(event, mIconLayout); } + @Override + public boolean inPreviewIconRegion(MotionEvent event) { + return Views.isEventOver(event, mPreviewIcon); + } + /** * Bind this view to the given document for display. * @param cursor Pointing to the item to be bound. diff --git a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java index 604badf4f..08f15221c 100644 --- a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java +++ b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java @@ -136,6 +136,8 @@ final class ModelBackedDocumentsAdapter extends DocumentsAdapter { } holder.setEnabled(enabled); holder.setSelected(mEnv.isSelected(modelId), false); + holder.bindPreviewIcon(mEnv.getDisplayState().shouldShowPreview() && enabled, + view -> mEnv.getActionHandler().previewItem(holder.getItemDetails())); mEnv.onBindDocumentHolder(holder, cursor); } diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java index 5ade6b1ea..af48af069 100644 --- a/src/com/android/documentsui/files/ActionHandler.java +++ b/src/com/android/documentsui/files/ActionHandler.java @@ -611,7 +611,8 @@ public class ActionHandler<T extends FragmentActivity & Addons> extends Abstract mActivity.getPackageManager(), mActivity.getResources(), doc, - mModel).build(); + mModel, + false /* fromPicker */).build(); if (intent != null) { // TODO: un-work around issue b/24963914. Should be fixed soon. diff --git a/src/com/android/documentsui/files/QuickViewIntentBuilder.java b/src/com/android/documentsui/files/QuickViewIntentBuilder.java index 7a7245d10..f7d757117 100644 --- a/src/com/android/documentsui/files/QuickViewIntentBuilder.java +++ b/src/com/android/documentsui/files/QuickViewIntentBuilder.java @@ -67,6 +67,9 @@ public final class QuickViewIntentBuilder { QuickViewConstants.FEATURE_DOWNLOAD, QuickViewConstants.FEATURE_PRINT }; + private static final String[] PICKER_FEATURES = { + QuickViewConstants.FEATURE_VIEW + }; private final DocumentInfo mDocument; private final Model mModel; @@ -74,11 +77,14 @@ public final class QuickViewIntentBuilder { private final PackageManager mPackageMgr; private final Resources mResources; + private final boolean mFromPicker; + public QuickViewIntentBuilder( PackageManager packageMgr, Resources resources, DocumentInfo doc, - Model model) { + Model model, + boolean fromPicker) { assert(packageMgr != null); assert(resources != null); @@ -89,13 +95,14 @@ public final class QuickViewIntentBuilder { mResources = resources; mDocument = doc; mModel = model; + mFromPicker = fromPicker; } /** * Builds the intent for quick viewing. Short circuits building if a handler cannot * be resolved; in this case {@code null} is returned. */ - @Nullable Intent build() { + @Nullable public Intent build() { if (DEBUG) Log.d(TAG, "Preparing intent for doc:" + mDocument.documentId); String trustedPkg = getQuickViewPackage(); @@ -107,7 +114,7 @@ public final class QuickViewIntentBuilder { | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.setPackage(trustedPkg); if (hasRegisteredHandler(intent)) { - includeQuickViewFeaturesFlag(intent, mDocument); + includeQuickViewFeaturesFlag(intent, mDocument, mFromPicker); final ArrayList<Uri> uris = new ArrayList<>(); final int documentLocation = collectViewableUris(uris); @@ -213,10 +220,12 @@ public final class QuickViewIntentBuilder { return intent.resolveActivity(mPackageMgr) != null; } - private static void includeQuickViewFeaturesFlag(Intent intent, DocumentInfo doc) { + private static void includeQuickViewFeaturesFlag(Intent intent, DocumentInfo doc, + boolean fromPicker) { intent.putExtra( Intent.EXTRA_QUICK_VIEW_FEATURES, - doc.isInArchive() ? IN_ARCHIVE_FEATURES : FULL_FEATURES); + doc.isInArchive() ? IN_ARCHIVE_FEATURES + : fromPicker ? PICKER_FEATURES : FULL_FEATURES); } private static Range<Integer> computeSiblingsRange(List<Uri> uris, int documentLocation) { diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java index 1a1bcb057..4438e12d9 100644 --- a/src/com/android/documentsui/picker/ActionHandler.java +++ b/src/com/android/documentsui/picker/ActionHandler.java @@ -26,6 +26,7 @@ import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION; import android.content.ClipData; import android.content.ComponentName; import android.content.Intent; +import android.content.QuickViewConstants; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.AsyncTask; @@ -53,6 +54,7 @@ import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.Shared; import com.android.documentsui.base.State; import com.android.documentsui.dirlist.AnimationView; +import com.android.documentsui.files.QuickViewIntentBuilder; import com.android.documentsui.picker.ActionHandler.Addons; import com.android.documentsui.queries.SearchViewManager; import com.android.documentsui.roots.ProvidersAccess; @@ -70,6 +72,9 @@ import javax.annotation.Nullable; class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionHandler<T> { private static final String TAG = "PickerActionHandler"; + private static final String[] PREVIEW_FEATURES = { + QuickViewConstants.FEATURE_VIEW + }; private final Features mFeatures; private final ActivityConfig mConfig; @@ -269,6 +274,40 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH return false; } + @Override + public boolean previewItem(ItemDetails<String> details) { + final DocumentInfo doc = mModel.getDocument(details.getSelectionKey()); + if (doc == null) { + Log.w(TAG, "Can't view item. No Document available for modeId: " + + details.getSelectionKey()); + return false; + } + return priviewDocument(doc); + + } + + @VisibleForTesting + boolean priviewDocument(DocumentInfo doc) { + Intent intent = new QuickViewIntentBuilder( + mActivity.getPackageManager(), + mActivity.getResources(), + doc, + mModel, + true /* fromPicker */).build(); + + if (intent != null) { + try { + mActivity.startActivity(intent); + return true; + } catch (SecurityException e) { + Log.e(TAG, "Caught security error: " + e.getLocalizedMessage()); + } + } else { + Log.e(TAG, "Quick view intetn is null"); + } + return false; + } + void pickDocument(DocumentInfo pickTarget) { assert(pickTarget != null); Uri result; diff --git a/tests/common/com/android/documentsui/bots/DirectoryListBot.java b/tests/common/com/android/documentsui/bots/DirectoryListBot.java index cb287dbff..665c0ac18 100644 --- a/tests/common/com/android/documentsui/bots/DirectoryListBot.java +++ b/tests/common/com/android/documentsui/bots/DirectoryListBot.java @@ -54,6 +54,10 @@ import java.util.regex.Pattern; public class DirectoryListBot extends Bots.BaseBot { private static final String DIR_CONTAINER_ID = "com.android.documentsui:id/container_directory"; private static final String DIR_LIST_ID = "com.android.documentsui:id/dir_list"; + private static final String ITEM_ROOT_ID = "com.android.documentsui:id/item_root"; + private static final String PREVIEW_ID = "com.android.documentsui:id/preview_icon"; + + private static final int MAX_LAYOUT_LEVEL = 10; private static final BySelector SNACK_DELETE = By.text(Pattern.compile("^Deleting [0-9]+ item.+")); @@ -273,6 +277,21 @@ public class DirectoryListBot extends Bots.BaseBot { return true; } + public boolean hasDocumentPreview(String label) { + final BySelector list = By.res(DIR_LIST_ID); + final UiObject2 text = mDevice.findObject(list).findObject(By.text(label)); + + UiObject2 parent = text; + for (int i = 1; i <= MAX_LAYOUT_LEVEL; i++) { + parent = parent.getParent(); + if (ITEM_ROOT_ID.equals(parent.getResourceName())) { + break; + } + } + + return parent.hasObject(By.res(PREVIEW_ID)); + } + public void assertFirstDocumentHasFocus() throws UiObjectNotFoundException { final UiSelector docList = new UiSelector().resourceId( DIR_CONTAINER_ID).childSelector( diff --git a/tests/functional/com/android/documentsui/PickerPreviewAllTypeUiTest.java b/tests/functional/com/android/documentsui/PickerPreviewAllTypeUiTest.java new file mode 100644 index 000000000..fdd9cc55f --- /dev/null +++ b/tests/functional/com/android/documentsui/PickerPreviewAllTypeUiTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 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; + +import android.content.Intent; +import android.provider.DocumentsContract; + +import androidx.test.filters.LargeTest; + +import com.android.documentsui.bots.UiBot; +import com.android.documentsui.picker.PickActivity; + +@LargeTest +public class PickerPreviewAllTypeUiTest extends ActivityTest<PickActivity> { + + public PickerPreviewAllTypeUiTest() { + super(PickActivity.class); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + initTestFiles(); + } + + @Override + protected void launchActivity() { + final Intent intent = context.getPackageManager().getLaunchIntentForPackage( + UiBot.TARGET_PKG); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction(Intent.ACTION_GET_CONTENT); + if (getInitialRoot() != null) { + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, getInitialRoot().getUri()); + } + intent.setType("*/*"); + setActivityIntent(intent); + getActivity(); // Launch the activity. + } + + public void testPreviewInvisible_directory_gridMode() throws Exception { + bots.main.switchToGridMode(); + assertTrue(bots.directory.findDocument(dirName1).isEnabled()); + assertFalse(bots.directory.hasDocumentPreview(dirName1)); + } + + public void testPreviewInvisible_directory_listMode() throws Exception { + bots.main.switchToListMode(); + assertTrue(bots.directory.findDocument(dirName1).isEnabled()); + assertFalse(bots.directory.hasDocumentPreview(dirName1)); + } + + public void testPreviewVisible_allType_girdMode() throws Exception { + bots.main.switchToGridMode(); + assertTrue(bots.directory.findDocument(fileName1).isEnabled()); + assertTrue(bots.directory.hasDocumentPreview(fileName1)); + assertTrue(bots.directory.findDocument(fileName2).isEnabled()); + assertTrue(bots.directory.hasDocumentPreview(fileName2)); + } + + public void testPreviewVisible_allType_listMode() throws Exception { + bots.main.switchToListMode(); + assertTrue(bots.directory.findDocument(fileName1).isEnabled()); + assertTrue(bots.directory.hasDocumentPreview(fileName1)); + assertTrue(bots.directory.findDocument(fileName2).isEnabled()); + assertTrue(bots.directory.hasDocumentPreview(fileName2)); + } +} diff --git a/tests/functional/com/android/documentsui/PickerPreviewTextUiTest.java b/tests/functional/com/android/documentsui/PickerPreviewTextUiTest.java new file mode 100644 index 000000000..501a9283a --- /dev/null +++ b/tests/functional/com/android/documentsui/PickerPreviewTextUiTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2018 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; + +import android.content.Intent; +import android.provider.DocumentsContract; + +import androidx.test.filters.LargeTest; + +import com.android.documentsui.bots.UiBot; +import com.android.documentsui.picker.PickActivity; + +@LargeTest +public class PickerPreviewTextUiTest extends ActivityTest<PickActivity>{ + + public PickerPreviewTextUiTest() { + super(PickActivity.class); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + initTestFiles(); + } + + @Override + protected void launchActivity() { + final Intent intent = context.getPackageManager().getLaunchIntentForPackage( + UiBot.TARGET_PKG); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction(Intent.ACTION_GET_CONTENT); + if (getInitialRoot() != null) { + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, getInitialRoot().getUri()); + } + intent.setType("text/*"); + setActivityIntent(intent); + getActivity(); // Launch the activity. + } + + public void testPreviewInvisible_disabled_gridMode() throws Exception { + bots.main.switchToGridMode(); + assertFalse(bots.directory.findDocument(fileName2).isEnabled()); + assertFalse(bots.directory.hasDocumentPreview(fileName2)); + } + + public void testPreviewInvisible_disabled_listMode() throws Exception { + bots.main.switchToListMode(); + assertFalse(bots.directory.findDocument(fileName2).isEnabled()); + assertFalse(bots.directory.hasDocumentPreview(fileName2)); + } + + public void testPreviewInvisible_directory_gridMode() throws Exception { + bots.main.switchToGridMode(); + assertTrue(bots.directory.findDocument(dirName1).isEnabled()); + assertFalse(bots.directory.hasDocumentPreview(dirName1)); + } + + public void testPreviewInvisible_directory_listMode() throws Exception { + bots.main.switchToListMode(); + assertTrue(bots.directory.findDocument(dirName1).isEnabled()); + assertFalse(bots.directory.hasDocumentPreview(dirName1)); + } + + public void testPreviewVisible_enabled_gridMode() throws Exception { + bots.main.switchToGridMode(); + assertTrue(bots.directory.findDocument(fileName1).isEnabled()); + assertTrue(bots.directory.hasDocumentPreview(fileName1)); + } + + public void testPreviewVisible_enabled_listMode() throws Exception { + bots.main.switchToListMode(); + assertTrue(bots.directory.findDocument(fileName1).isEnabled()); + assertTrue(bots.directory.hasDocumentPreview(fileName1)); + } +} diff --git a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java index 4012bdd0c..3d1e0dbf9 100644 --- a/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/AbstractActionHandlerTest.java @@ -285,4 +285,13 @@ public class AbstractActionHandlerTest { assertTrue(listener.getLastValue().hasException()); } + + @Test + public void testPreviewItem_throwException() throws Exception { + try { + mHandler.previewItem(null); + fail("Should have thrown UnsupportedOperationException."); + } catch (UnsupportedOperationException expected) { + } + } } diff --git a/tests/unit/com/android/documentsui/base/StateTest.java b/tests/unit/com/android/documentsui/base/StateTest.java index 9631ada2d..06fdfeb1d 100644 --- a/tests/unit/com/android/documentsui/base/StateTest.java +++ b/tests/unit/com/android/documentsui/base/StateTest.java @@ -18,6 +18,8 @@ package com.android.documentsui.base; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import android.content.Intent; @@ -58,4 +60,32 @@ public class StateTest { assertArrayEquals(new String[] { MIME_TYPES[0] }, mState.acceptMimes); } + + @Test + public void testShouldShowPreview_actionBrowse() { + mState.action = State.ACTION_BROWSE; + + assertFalse(mState.shouldShowPreview()); + } + + @Test + public void testShouldShowPreview_actionOpen() { + mState.action = State.ACTION_OPEN; + + assertTrue(mState.shouldShowPreview()); + } + + @Test + public void testShouldShowPreview_actionGetContent() { + mState.action = State.ACTION_GET_CONTENT; + + assertTrue(mState.shouldShowPreview()); + } + + @Test + public void testShouldShowPreview_actionOpenTree() { + mState.action = State.ACTION_OPEN_TREE; + + assertTrue(mState.shouldShowPreview()); + } } diff --git a/tests/unit/com/android/documentsui/files/QuickViewIntentBuilderTest.java b/tests/unit/com/android/documentsui/files/QuickViewIntentBuilderTest.java index 95485276d..26355e6e3 100644 --- a/tests/unit/com/android/documentsui/files/QuickViewIntentBuilderTest.java +++ b/tests/unit/com/android/documentsui/files/QuickViewIntentBuilderTest.java @@ -42,7 +42,8 @@ public class QuickViewIntentBuilderTest { @Test public void testSetsNoFeatures_InArchiveDocument() { QuickViewIntentBuilder builder = - new QuickViewIntentBuilder(mPm, mRes, TestEnv.FILE_IN_ARCHIVE, mEnv.archiveModel); + new QuickViewIntentBuilder( + mPm, mRes, TestEnv.FILE_IN_ARCHIVE, mEnv.archiveModel, false); Intent intent = builder.build(); @@ -53,7 +54,7 @@ public class QuickViewIntentBuilderTest { @Test public void testSetsFullFeatures_RegularDocument() { QuickViewIntentBuilder builder = - new QuickViewIntentBuilder(mPm, mRes, TestEnv.FILE_JPG, mEnv.model); + new QuickViewIntentBuilder(mPm, mRes, TestEnv.FILE_JPG, mEnv.model, false); Intent intent = builder.build(); @@ -68,4 +69,19 @@ public class QuickViewIntentBuilderTest { assertTrue(features.contains(QuickViewConstants.FEATURE_DOWNLOAD)); assertTrue(features.contains(QuickViewConstants.FEATURE_PRINT)); } + + @Test + public void testPickerFeatures_RegularDocument() { + + QuickViewIntentBuilder builder = + new QuickViewIntentBuilder(mPm, mRes, TestEnv.FILE_JPG, mEnv.model, true); + + Intent intent = builder.build(); + + Set<String> features = new HashSet<>( + Arrays.asList(intent.getStringArrayExtra(Intent.EXTRA_QUICK_VIEW_FEATURES))); + + assertEquals("Unexpected features set: " + features, 1, features.size()); + assertTrue(features.contains(QuickViewConstants.FEATURE_VIEW)); + } } diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java index 69cfa3eb2..ba62e2fa4 100644 --- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java @@ -465,6 +465,15 @@ public class ActionHandlerTest { assertNotNull(mActivity.startActivityForResult.getLastValue().first); } + @Test + public void testPreviewItem() throws Exception { + mActivity.resources.setQuickViewerPackage("corptropolis.viewer"); + mActivity.currentRoot = TestProvidersAccess.HOME; + + mHandler.priviewDocument(TestEnv.FILE_GIF); + mActivity.assertActivityStarted(Intent.ACTION_QUICK_VIEW); + } + private void testInitLocationDefaultToRecentsOnAction(@ActionType int action) throws Exception { mEnv.state.action = action; |