• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
20 import static com.android.documentsui.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
21 import static com.android.documentsui.base.DocumentInfo.getCursorInt;
22 import static com.android.documentsui.base.DocumentInfo.getCursorLong;
23 import static com.android.documentsui.base.DocumentInfo.getCursorString;
24 import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled;
25 
26 import android.app.admin.DevicePolicyManager;
27 import android.content.Context;
28 import android.database.Cursor;
29 import android.graphics.drawable.Drawable;
30 import android.os.Build;
31 import android.provider.DocumentsContract.Document;
32 import android.text.format.Formatter;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.ImageView;
37 import android.widget.TextView;
38 
39 import androidx.annotation.Nullable;
40 import androidx.annotation.RequiresApi;
41 
42 import com.android.documentsui.ConfigStore;
43 import com.android.documentsui.DocumentsApplication;
44 import com.android.documentsui.IconUtils;
45 import com.android.documentsui.R;
46 import com.android.documentsui.base.DocumentInfo;
47 import com.android.documentsui.base.Shared;
48 import com.android.documentsui.base.State;
49 import com.android.documentsui.base.UserId;
50 import com.android.documentsui.roots.RootCursorWrapper;
51 import com.android.documentsui.ui.Views;
52 import com.android.modules.utils.build.SdkLevel;
53 
54 import com.google.android.material.card.MaterialCardView;
55 
56 import java.util.Map;
57 import java.util.function.Function;
58 
59 final class GridDocumentHolder extends DocumentHolder {
60 
61     final TextView mTitle;
62     final TextView mDate;
63     final TextView mDetails;
64     // Non-null only when useMaterial3 flag is ON.
65     final @Nullable TextView mBullet;
66     final ImageView mIconMimeLg;
67     // Null when useMaterial3 flag is ON.
68     final @Nullable ImageView mIconMimeSm;
69     final ImageView mIconThumb;
70     // Null when useMaterial3 flag is ON.
71     final @Nullable ImageView mIconCheck;
72     final ImageView mIconBadge;
73     final IconHelper mIconHelper;
74     // Null when useMaterial3 flag is ON.
75     final @Nullable View mIconLayout;
76     final View mPreviewIcon;
77 
78     // This is used in as a convenience in our bind method.
79     private final DocumentInfo mDoc = new DocumentInfo();
80 
81     // Non-null only when useMaterial3 flag is ON.
82     private final @Nullable MaterialCardView mIconWrapper;
83     // It will be 0 when use_material flag is OFF.
84     private final int mThumbnailStrokeWidth;
85 
GridDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper, ConfigStore configStore)86     GridDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper,
87             ConfigStore configStore) {
88         super(context, parent, R.layout.item_doc_grid, configStore);
89 
90         if (isUseMaterial3FlagEnabled()) {
91             mBullet = itemView.findViewById(R.id.bullet);
92             mIconWrapper = itemView.findViewById(R.id.icon_wrapper);
93             mIconLayout = null;
94             mIconMimeSm = null;
95             mIconCheck = null;
96             mThumbnailStrokeWidth =
97                     context.getResources()
98                             .getDimensionPixelSize(R.dimen.thumbnail_border_width);
99         } else {
100             mBullet = null;
101             mIconWrapper = null;
102             mIconLayout = itemView.findViewById(R.id.icon);
103             mIconMimeSm = (ImageView) itemView.findViewById(R.id.icon_mime_sm);
104             mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check);
105             mThumbnailStrokeWidth = 0;
106         }
107 
108         mTitle = (TextView) itemView.findViewById(android.R.id.title);
109         mDate = (TextView) itemView.findViewById(R.id.date);
110         mDetails = (TextView) itemView.findViewById(R.id.details);
111         mIconMimeLg = (ImageView) itemView.findViewById(R.id.icon_mime_lg);
112         mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
113         mIconBadge = (ImageView) itemView.findViewById(R.id.icon_profile_badge);
114         mPreviewIcon = itemView.findViewById(R.id.preview_icon);
115 
116         if (isUseMaterial3FlagEnabled()) {
117             int clipCornerRadius = context.getResources()
118                     .getDimensionPixelSize(R.dimen.thumbnail_clip_corner_radius);
119             IconUtils.applyThumbnailClipOutline(
120                     mIconThumb, mThumbnailStrokeWidth, clipCornerRadius);
121         }
122 
123         mIconHelper = iconHelper;
124 
125         if (SdkLevel.isAtLeastT() && !mConfigStore.isPrivateSpaceInDocsUIEnabled()) {
126             setUpdatableWorkProfileIcon(context);
127         }
128     }
129 
130     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
setUpdatableWorkProfileIcon(Context context)131     private void setUpdatableWorkProfileIcon(Context context) {
132         DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
133         Drawable drawable = dpm.getResources().getDrawable(WORK_PROFILE_ICON, SOLID_COLORED, () ->
134                 context.getDrawable(R.drawable.ic_briefcase));
135         mIconBadge.setImageDrawable(drawable);
136     }
137 
138     @Override
setSelected(boolean selected, boolean animate)139     public void setSelected(boolean selected, boolean animate) {
140         float checkAlpha = selected ? 1f : 0f;
141         if (!isUseMaterial3FlagEnabled()) {
142             // We always want to make sure our check box disappears if we're not selected,
143             // even if the item is disabled. This is because this object can be reused
144             // and this method will be called to setup initial state.
145             if (animate) {
146                 fade(mIconMimeSm, checkAlpha).start();
147                 fade(mIconCheck, checkAlpha).start();
148             } else {
149                 mIconCheck.setAlpha(checkAlpha);
150             }
151         }
152 
153 
154         // But it should be an error to be set to selected && be disabled.
155         if (!itemView.isEnabled()) {
156             assert (!selected);
157         }
158 
159         super.setSelected(selected, animate);
160 
161         if (!isUseMaterial3FlagEnabled()) {
162             if (animate) {
163                 fade(mIconMimeSm, 1f - checkAlpha).start();
164             } else {
165                 mIconMimeSm.setAlpha(1f - checkAlpha);
166             }
167         }
168 
169         // Do not show stroke when selected, only show stroke when not selected if it has thumbnail.
170         if (mIconWrapper != null) {
171             if (selected) {
172                 mIconWrapper.setStrokeWidth(0);
173             } else if (mIconThumb.getDrawable() != null) {
174                 mIconWrapper.setStrokeWidth(mThumbnailStrokeWidth);
175             }
176         }
177     }
178 
179     @Override
setEnabled(boolean enabled)180     public void setEnabled(boolean enabled) {
181         super.setEnabled(enabled);
182 
183         float imgAlpha = enabled ? 1f : DISABLED_ALPHA;
184 
185         mIconMimeLg.setAlpha(imgAlpha);
186         if (!isUseMaterial3FlagEnabled()) {
187             mIconMimeSm.setAlpha(imgAlpha);
188         }
189         mIconThumb.setAlpha(imgAlpha);
190     }
191 
192     @Override
bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback)193     public void bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback) {
194         if (isUseMaterial3FlagEnabled() && mDoc.isDirectory()) {
195             mPreviewIcon.setVisibility(View.GONE);
196             return;
197         }
198         mPreviewIcon.setVisibility(show ? View.VISIBLE : View.GONE);
199         if (show) {
200             mPreviewIcon.setContentDescription(
201                     getPreviewIconContentDescription(
202                             mIconHelper.shouldShowBadge(mDoc.userId.getIdentifier()),
203                             mDoc.displayName, mDoc.userId));
204             mPreviewIcon.setAccessibilityDelegate(
205                     new PreviewAccessibilityDelegate(clickCallback));
206         }
207     }
208 
209     @Override
bindBriefcaseIcon(boolean show)210     public void bindBriefcaseIcon(boolean show) {
211         mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE);
212     }
213 
214     @Override
215     @RequiresApi(Build.VERSION_CODES.S)
bindProfileIcon(boolean show, int userIdIdentifier)216     public void bindProfileIcon(boolean show, int userIdIdentifier) {
217         Map<UserId, Drawable> userIdToBadgeMap = DocumentsApplication.getUserManagerState(
218                 mContext).getUserIdToBadgeMap();
219         Drawable drawable = userIdToBadgeMap.get(UserId.of(userIdIdentifier));
220         mIconBadge.setImageDrawable(drawable);
221         mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE);
222         mIconBadge.setContentDescription(mIconHelper.getProfileLabel(userIdIdentifier));
223     }
224 
225     @Override
inDragRegion(MotionEvent event)226     public boolean inDragRegion(MotionEvent event) {
227         // Entire grid box should be draggable
228         return true;
229     }
230 
231     @Override
inSelectRegion(MotionEvent event)232     public boolean inSelectRegion(MotionEvent event) {
233         if (isUseMaterial3FlagEnabled()) {
234             return (mDoc.isDirectory() && !(mAction == State.ACTION_BROWSE)) ? false
235                     : Views.isEventOver(event, itemView.getParent(), mIconWrapper);
236         }
237         return Views.isEventOver(event, itemView.getParent(), mIconLayout);
238     }
239 
240     @Override
inPreviewIconRegion(MotionEvent event)241     public boolean inPreviewIconRegion(MotionEvent event) {
242         return Views.isEventOver(event, itemView.getParent(), mPreviewIcon);
243     }
244 
245     /**
246      * Bind this view to the given document for display.
247      *
248      * @param cursor  Pointing to the item to be bound.
249      * @param modelId The model ID of the item.
250      */
251     @Override
bind(Cursor cursor, String modelId)252     public void bind(Cursor cursor, String modelId) {
253         assert (cursor != null);
254 
255         mModelId = modelId;
256 
257         mDoc.updateFromCursor(cursor,
258                 UserId.of(getCursorInt(cursor, RootCursorWrapper.COLUMN_USER_ID)),
259                 getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY));
260 
261         mIconHelper.stopLoading(mIconThumb);
262 
263         mIconMimeLg.animate().cancel();
264         mIconMimeLg.setAlpha(1f);
265         mIconThumb.animate().cancel();
266         mIconThumb.setAlpha(0f);
267 
268         if (isUseMaterial3FlagEnabled()) {
269             mIconHelper.load(
270                     mDoc, mIconThumb, mIconMimeLg, /* subIconMime= */ null,
271                     thumbnailLoaded -> {
272                         // Show stroke when thumbnail is loaded.
273                         if (mIconWrapper != null) {
274                             mIconWrapper.setStrokeWidth(
275                                     thumbnailLoaded ? mThumbnailStrokeWidth : 0);
276                         }
277                     });
278         } else {
279             mIconHelper.load(
280                     mDoc, mIconThumb, mIconMimeLg, mIconMimeSm, /* thumbnailLoadedCallback= */
281                     null);
282         }
283 
284         mTitle.setText(mDoc.displayName, TextView.BufferType.SPANNABLE);
285         mTitle.setVisibility(View.VISIBLE);
286 
287         // If file is partial, we want to show summary field as that's more relevant than fileSize
288         // and date
289         if (mDoc.isPartial()) {
290             final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
291             mDetails.setVisibility(View.VISIBLE);
292             mDate.setText(null);
293             mDetails.setText(docSummary);
294         } else {
295             if (mDoc.lastModified == -1) {
296                 mDate.setText(null);
297             } else {
298                 mDate.setText(Shared.formatTime(mContext, mDoc.lastModified));
299             }
300 
301             final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
302             if (mDoc.isDirectory() || docSize == -1) {
303                 mDetails.setVisibility(View.GONE);
304             } else {
305                 mDetails.setVisibility(View.VISIBLE);
306                 mDetails.setText(Formatter.formatFileSize(mContext, docSize));
307             }
308         }
309 
310         if (mBullet != null && (mDetails.getText() == null || mDetails.getText().length() == 0
311                 || mDate.getText() == null || mDate.getText().length() == 0)) {
312             // There is no need for the bullet separating the details and date.
313             mBullet.setVisibility(View.GONE);
314         }
315     }
316 }
317