1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.documentsui.dirlist;
18 
19 import static com.android.documentsui.base.DocumentInfo.getCursorInt;
20 import static com.android.documentsui.base.DocumentInfo.getCursorString;
21 
22 import android.content.Context;
23 import android.database.Cursor;
24 import android.graphics.Rect;
25 import android.text.format.Formatter;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.ImageView;
30 import android.widget.LinearLayout;
31 import android.widget.TextView;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.documentsui.R;
36 import com.android.documentsui.base.DocumentInfo;
37 import com.android.documentsui.base.Lookup;
38 import com.android.documentsui.base.Shared;
39 import com.android.documentsui.base.State;
40 import com.android.documentsui.base.UserId;
41 import com.android.documentsui.roots.RootCursorWrapper;
42 import com.android.documentsui.ui.Views;
43 
44 import java.util.function.Function;
45 
46 final class ListDocumentHolder extends DocumentHolder {
47 
48     private final TextView mTitle;
49     private final @Nullable LinearLayout mDetails;  // Container of date/size/summary
50     private final TextView mDate;
51     private final TextView mSize;
52     private final TextView mType;
53     private final ImageView mIconMime;
54     private final ImageView mIconThumb;
55     private final ImageView mIconCheck;
56     private final ImageView mIconBriefcase;
57     private final View mIconLayout;
58     final View mPreviewIcon;
59 
60     private final IconHelper mIconHelper;
61     private final Lookup<String, String> mFileTypeLookup;
62     // This is used in as a convenience in our bind method.
63     private final DocumentInfo mDoc;
64 
ListDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper, Lookup<String, String> fileTypeLookup)65     public ListDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper,
66             Lookup<String, String> fileTypeLookup) {
67         super(context, parent, R.layout.item_doc_list);
68 
69         mIconLayout = itemView.findViewById(R.id.icon);
70         mIconMime = (ImageView) itemView.findViewById(R.id.icon_mime);
71         mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
72         mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check);
73         mIconBriefcase = (ImageView) itemView.findViewById(R.id.icon_briefcase);
74         mTitle = (TextView) itemView.findViewById(android.R.id.title);
75         mSize = (TextView) itemView.findViewById(R.id.size);
76         mDate = (TextView) itemView.findViewById(R.id.date);
77         mType = (TextView) itemView.findViewById(R.id.file_type);
78         // Warning: mDetails view doesn't exists in layout-sw720dp-land layout
79         mDetails = (LinearLayout) itemView.findViewById(R.id.line2);
80         mPreviewIcon = itemView.findViewById(R.id.preview_icon);
81 
82         mIconHelper = iconHelper;
83         mFileTypeLookup = fileTypeLookup;
84         mDoc = new DocumentInfo();
85     }
86 
87     @Override
setSelected(boolean selected, boolean animate)88     public void setSelected(boolean selected, boolean animate) {
89         // We always want to make sure our check box disappears if we're not selected,
90         // even if the item is disabled. But it should be an error (see assert below)
91         // to be set to selected && be disabled.
92         float checkAlpha = selected ? 1f : 0f;
93         if (animate) {
94             fade(mIconCheck, checkAlpha).start();
95         } else {
96             mIconCheck.setAlpha(checkAlpha);
97         }
98 
99         if (!itemView.isEnabled()) {
100             assert(!selected);
101             return;
102         }
103 
104         super.setSelected(selected, animate);
105 
106         if (animate) {
107             fade(mIconMime, 1f - checkAlpha).start();
108             fade(mIconThumb, 1f - checkAlpha).start();
109         } else {
110             mIconMime.setAlpha(1f - checkAlpha);
111             mIconThumb.setAlpha(1f - checkAlpha);
112         }
113     }
114 
115     @Override
setEnabled(boolean enabled)116     public void setEnabled(boolean enabled) {
117         super.setEnabled(enabled);
118 
119         // Text colors enabled/disabled is handle via a color set.
120         final float imgAlpha = enabled ? 1f : DISABLED_ALPHA;
121         mIconMime.setAlpha(imgAlpha);
122         mIconThumb.setAlpha(imgAlpha);
123     }
124 
125     @Override
bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback)126     public void bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback) {
127         if (mDoc.isDirectory()) {
128             mPreviewIcon.setVisibility(View.GONE);
129         } else {
130             mPreviewIcon.setVisibility(show ? View.VISIBLE : View.GONE);
131             if (show) {
132                 mPreviewIcon.setContentDescription(
133                         itemView.getResources().getString(
134                                 mIconHelper.shouldShowBadge(mDoc.userId.getIdentifier())
135                                         ? R.string.preview_work_file
136                                         : R.string.preview_file, mDoc.displayName));
137                 mPreviewIcon.setAccessibilityDelegate(
138                         new PreviewAccessibilityDelegate(clickCallback));
139             }
140         }
141     }
142 
143     @Override
bindBriefcaseIcon(boolean show)144     public void bindBriefcaseIcon(boolean show) {
145         mIconBriefcase.setVisibility(show ? View.VISIBLE : View.GONE);
146     }
147 
148     @Override
inDragRegion(MotionEvent event)149     public boolean inDragRegion(MotionEvent event) {
150         // If itemView is activated = selected, then whole region is interactive
151         if (itemView.isActivated()) {
152             return true;
153         }
154 
155         // Do everything in global coordinates - it makes things simpler.
156         int[] coords = new int[2];
157         mIconLayout.getLocationOnScreen(coords);
158 
159         Rect textBounds = new Rect();
160         mTitle.getPaint().getTextBounds(
161                 mTitle.getText().toString(), 0, mTitle.getText().length(), textBounds);
162 
163         Rect rect = new Rect(
164                 coords[0],
165                 coords[1],
166                 coords[0] + mIconLayout.getWidth() + textBounds.width(),
167                 coords[1] + Math.max(mIconLayout.getHeight(), textBounds.height()));
168 
169         // If the tap occurred inside icon or the text, these are interactive spots.
170         return rect.contains((int) event.getRawX(), (int) event.getRawY());
171     }
172 
173     @Override
inSelectRegion(MotionEvent event)174     public boolean inSelectRegion(MotionEvent event) {
175         return (mDoc.isDirectory() && !(mAction == State.ACTION_BROWSE)) ?
176                 false : Views.isEventOver(event, mIconLayout);
177     }
178 
179     @Override
inPreviewIconRegion(MotionEvent event)180     public boolean inPreviewIconRegion(MotionEvent event) {
181         return Views.isEventOver(event, mPreviewIcon);
182     }
183 
184     /**
185      * Bind this view to the given document for display.
186      * @param cursor Pointing to the item to be bound.
187      * @param modelId The model ID of the item.
188      */
189     @Override
bind(Cursor cursor, String modelId)190     public void bind(Cursor cursor, String modelId) {
191         assert(cursor != null);
192 
193         mModelId = modelId;
194 
195         mDoc.updateFromCursor(cursor,
196                 UserId.of(getCursorInt(cursor, RootCursorWrapper.COLUMN_USER_ID)),
197                 getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY));
198 
199         mIconHelper.stopLoading(mIconThumb);
200 
201         mIconMime.animate().cancel();
202         mIconMime.setAlpha(1f);
203         mIconThumb.animate().cancel();
204         mIconThumb.setAlpha(0f);
205 
206         mIconHelper.load(mDoc, mIconThumb, mIconMime, null);
207 
208         mTitle.setText(mDoc.displayName, TextView.BufferType.SPANNABLE);
209         mTitle.setVisibility(View.VISIBLE);
210 
211 
212         boolean hasDetails = false;
213         if (mDoc.isDirectory()) {
214             // Note, we don't show any details for any directory...ever.
215             hasDetails = false;
216         } else {
217             if (mDoc.lastModified > 0) {
218                 hasDetails = true;
219                 mDate.setText(Shared.formatTime(mContext, mDoc.lastModified));
220             } else {
221                 mDate.setText(null);
222             }
223 
224             if (mDoc.size > -1) {
225                 hasDetails = true;
226                 mSize.setVisibility(View.VISIBLE);
227                 mSize.setText(Formatter.formatFileSize(mContext, mDoc.size));
228             } else {
229                 mSize.setVisibility(View.INVISIBLE);
230             }
231 
232             mType.setText(mFileTypeLookup.lookup(mDoc.mimeType));
233         }
234 
235         // mDetails view doesn't exists in layout-sw720dp-land layout
236         if (mDetails != null) {
237             mDetails.setVisibility(hasDetails ? View.VISIBLE : View.GONE);
238         }
239 
240         // TODO: Add document debug info
241         // Call includeDebugInfo
242     }
243 }
244