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.TextUtils;
26 import android.text.format.Formatter;
27 import android.util.Log;
28 import android.view.MotionEvent;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.ImageView;
32 import android.widget.LinearLayout;
33 import android.widget.TextView;
34 
35 import androidx.annotation.Nullable;
36 
37 import com.android.documentsui.R;
38 import com.android.documentsui.base.DocumentInfo;
39 import com.android.documentsui.base.Lookup;
40 import com.android.documentsui.base.Shared;
41 import com.android.documentsui.base.State;
42 import com.android.documentsui.base.UserId;
43 import com.android.documentsui.roots.RootCursorWrapper;
44 import com.android.documentsui.ui.Views;
45 
46 import java.util.ArrayList;
47 import java.util.function.Function;
48 
49 final class ListDocumentHolder extends DocumentHolder {
50     private static final String TAG = "ListDocumentHolder";
51 
52     private final TextView mTitle;
53     private final @Nullable TextView mDate; // Non-null for tablets/sw720dp, null for other devices.
54     private final @Nullable TextView mSize; // Non-null for tablets/sw720dp, null for other devices.
55     private final @Nullable TextView mType; // Non-null for tablets/sw720dp, null for other devices.
56     // Container for date + size + summary, null only for tablets/sw720dp
57     private final @Nullable LinearLayout mDetails;
58     // TextView for date + size + summary, null only for tablets/sw720dp
59     private final @Nullable TextView mMetadataView;
60     private final ImageView mIconMime;
61     private final ImageView mIconThumb;
62     private final ImageView mIconCheck;
63     private final ImageView mIconBriefcase;
64     private final View mIconLayout;
65     final View mPreviewIcon;
66 
67     private final IconHelper mIconHelper;
68     private final Lookup<String, String> mFileTypeLookup;
69     // This is used in as a convenience in our bind method.
70     private final DocumentInfo mDoc;
71 
ListDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper, Lookup<String, String> fileTypeLookup)72     public ListDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper,
73             Lookup<String, String> fileTypeLookup) {
74         super(context, parent, R.layout.item_doc_list);
75 
76         mIconLayout = itemView.findViewById(R.id.icon);
77         mIconMime = (ImageView) itemView.findViewById(R.id.icon_mime);
78         mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
79         mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check);
80         mIconBriefcase = (ImageView) itemView.findViewById(R.id.icon_briefcase);
81         mTitle = (TextView) itemView.findViewById(android.R.id.title);
82         mSize = (TextView) itemView.findViewById(R.id.size);
83         mDate = (TextView) itemView.findViewById(R.id.date);
84         mType = (TextView) itemView.findViewById(R.id.file_type);
85         mMetadataView = (TextView) itemView.findViewById(R.id.metadata);
86         // Warning: mDetails view doesn't exists in layout-sw720dp-land layout
87         mDetails = (LinearLayout) itemView.findViewById(R.id.line2);
88         mPreviewIcon = itemView.findViewById(R.id.preview_icon);
89 
90         mIconHelper = iconHelper;
91         mFileTypeLookup = fileTypeLookup;
92         mDoc = new DocumentInfo();
93     }
94 
95     @Override
setSelected(boolean selected, boolean animate)96     public void setSelected(boolean selected, boolean animate) {
97         // We always want to make sure our check box disappears if we're not selected,
98         // even if the item is disabled. But it should be an error (see assert below)
99         // to be set to selected && be disabled.
100         float checkAlpha = selected ? 1f : 0f;
101         if (animate) {
102             fade(mIconCheck, checkAlpha).start();
103         } else {
104             mIconCheck.setAlpha(checkAlpha);
105         }
106 
107         if (!itemView.isEnabled()) {
108             assert (!selected);
109         }
110 
111         super.setSelected(selected, animate);
112 
113         if (animate) {
114             fade(mIconMime, 1f - checkAlpha).start();
115             fade(mIconThumb, 1f - checkAlpha).start();
116         } else {
117             mIconMime.setAlpha(1f - checkAlpha);
118             mIconThumb.setAlpha(1f - checkAlpha);
119         }
120     }
121 
122     @Override
setEnabled(boolean enabled)123     public void setEnabled(boolean enabled) {
124         super.setEnabled(enabled);
125 
126         // Text colors enabled/disabled is handle via a color set.
127         final float imgAlpha = enabled ? 1f : DISABLED_ALPHA;
128         mIconMime.setAlpha(imgAlpha);
129         mIconThumb.setAlpha(imgAlpha);
130     }
131 
132     @Override
bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback)133     public void bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback) {
134         if (mDoc.isDirectory()) {
135             mPreviewIcon.setVisibility(View.GONE);
136         } else {
137             mPreviewIcon.setVisibility(show ? View.VISIBLE : View.GONE);
138             if (show) {
139                 mPreviewIcon.setContentDescription(
140                         itemView.getResources().getString(
141                                 mIconHelper.shouldShowBadge(mDoc.userId.getIdentifier())
142                                         ? R.string.preview_work_file
143                                         : R.string.preview_file, mDoc.displayName));
144                 mPreviewIcon.setAccessibilityDelegate(
145                         new PreviewAccessibilityDelegate(clickCallback));
146             }
147         }
148     }
149 
150     @Override
bindBriefcaseIcon(boolean show)151     public void bindBriefcaseIcon(boolean show) {
152         mIconBriefcase.setVisibility(show ? View.VISIBLE : View.GONE);
153     }
154 
155     @Override
inDragRegion(MotionEvent event)156     public boolean inDragRegion(MotionEvent event) {
157         // If itemView is activated = selected, then whole region is interactive
158         if (itemView.isActivated()) {
159             return true;
160         }
161 
162         // Do everything in global coordinates - it makes things simpler.
163         int[] coords = new int[2];
164         mIconLayout.getLocationOnScreen(coords);
165 
166         Rect textBounds = new Rect();
167         mTitle.getPaint().getTextBounds(
168                 mTitle.getText().toString(), 0, mTitle.getText().length(), textBounds);
169 
170         Rect rect = new Rect(
171                 coords[0],
172                 coords[1],
173                 coords[0] + mIconLayout.getWidth() + textBounds.width(),
174                 coords[1] + Math.max(mIconLayout.getHeight(), textBounds.height()));
175 
176         // If the tap occurred inside icon or the text, these are interactive spots.
177         return rect.contains((int) event.getRawX(), (int) event.getRawY());
178     }
179 
180     @Override
inSelectRegion(MotionEvent event)181     public boolean inSelectRegion(MotionEvent event) {
182         return (mDoc.isDirectory() && !(mAction == State.ACTION_BROWSE)) ?
183                 false : Views.isEventOver(event, mIconLayout);
184     }
185 
186     @Override
inPreviewIconRegion(MotionEvent event)187     public boolean inPreviewIconRegion(MotionEvent event) {
188         return Views.isEventOver(event, mPreviewIcon);
189     }
190 
191     /**
192      * Bind this view to the given document for display.
193      *
194      * @param cursor Pointing to the item to be bound.
195      * @param modelId The model ID of the item.
196      */
197     @Override
bind(Cursor cursor, String modelId)198     public void bind(Cursor cursor, String modelId) {
199         assert (cursor != null);
200 
201         mModelId = modelId;
202 
203         mDoc.updateFromCursor(cursor,
204                 UserId.of(getCursorInt(cursor, RootCursorWrapper.COLUMN_USER_ID)),
205                 getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY));
206 
207         mIconHelper.stopLoading(mIconThumb);
208 
209         mIconMime.animate().cancel();
210         mIconMime.setAlpha(1f);
211         mIconThumb.animate().cancel();
212         mIconThumb.setAlpha(0f);
213 
214         mIconHelper.load(mDoc, mIconThumb, mIconMime, null);
215 
216         mTitle.setText(mDoc.displayName, TextView.BufferType.SPANNABLE);
217         mTitle.setVisibility(View.VISIBLE);
218 
219         if (mDoc.isDirectory()) {
220             // Note, we don't show any details for any directory...ever.
221             if (mDetails != null) {
222                 // Non-tablets
223                 mDetails.setVisibility(View.GONE);
224             }
225         } else {
226             // For tablets metadata is provided in columns mDate, mSize, mType.
227             // For other devices mMetadataView consolidates the metadata info.
228             if (mMetadataView != null) {
229                 // Non-tablets
230                 boolean hasDetails = false;
231                 ArrayList<String> metadataList = new ArrayList<>();
232                 if (mDoc.lastModified > 0) {
233                     hasDetails = true;
234                     metadataList.add(Shared.formatTime(mContext, mDoc.lastModified));
235                 }
236                 if (mDoc.size > -1) {
237                     hasDetails = true;
238                     metadataList.add(Formatter.formatFileSize(mContext, mDoc.size));
239                 }
240                 metadataList.add(mFileTypeLookup.lookup(mDoc.mimeType));
241                 mMetadataView.setText(TextUtils.join(", ", metadataList));
242                 if (mDetails != null) {
243                     mDetails.setVisibility(hasDetails ? View.VISIBLE : View.GONE);
244                 } else {
245                     Log.w(TAG, "mDetails is unexpectedly null for non-tablet devices!");
246                 }
247             } else {
248                 // Tablets
249                 if (mDoc.lastModified > 0) {
250                     mDate.setVisibility(View.VISIBLE);
251                     mDate.setText(Shared.formatTime(mContext, mDoc.lastModified));
252                 } else {
253                     mDate.setVisibility(View.INVISIBLE);
254                 }
255                 if (mDoc.size > -1) {
256                     mSize.setVisibility(View.VISIBLE);
257                     mSize.setText(Formatter.formatFileSize(mContext, mDoc.size));
258                 } else {
259                     mSize.setVisibility(View.INVISIBLE);
260                 }
261                 mType.setText(mFileTypeLookup.lookup(mDoc.mimeType));
262             }
263         }
264 
265         // TODO: Add document debug info
266         // Call includeDebugInfo
267     }
268 }
269