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 
25 import android.app.admin.DevicePolicyManager;
26 import android.content.Context;
27 import android.database.Cursor;
28 import android.graphics.drawable.Drawable;
29 import android.os.Build;
30 import android.provider.DocumentsContract.Document;
31 import android.text.format.Formatter;
32 import android.view.MotionEvent;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.ImageView;
36 import android.widget.TextView;
37 
38 import androidx.annotation.RequiresApi;
39 
40 import com.android.documentsui.ConfigStore;
41 import com.android.documentsui.DocumentsApplication;
42 import com.android.documentsui.R;
43 import com.android.documentsui.base.DocumentInfo;
44 import com.android.documentsui.base.Shared;
45 import com.android.documentsui.base.UserId;
46 import com.android.documentsui.roots.RootCursorWrapper;
47 import com.android.documentsui.ui.Views;
48 import com.android.modules.utils.build.SdkLevel;
49 
50 import java.util.Map;
51 import java.util.function.Function;
52 
53 final class GridDocumentHolder extends DocumentHolder {
54 
55     final TextView mTitle;
56     final TextView mDate;
57     final TextView mDetails;
58     final ImageView mIconMimeLg;
59     final ImageView mIconMimeSm;
60     final ImageView mIconThumb;
61     final ImageView mIconCheck;
62     final ImageView mIconBadge;
63     final IconHelper mIconHelper;
64     final View mIconLayout;
65     final View mPreviewIcon;
66 
67     // This is used in as a convenience in our bind method.
68     private final DocumentInfo mDoc = new DocumentInfo();
69 
GridDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper, ConfigStore configStore)70     GridDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper,
71             ConfigStore configStore) {
72         super(context, parent, R.layout.item_doc_grid, configStore);
73 
74         mIconLayout = itemView.findViewById(R.id.icon);
75         mTitle = (TextView) itemView.findViewById(android.R.id.title);
76         mDate = (TextView) itemView.findViewById(R.id.date);
77         mDetails = (TextView) itemView.findViewById(R.id.details);
78         mIconMimeLg = (ImageView) itemView.findViewById(R.id.icon_mime_lg);
79         mIconMimeSm = (ImageView) itemView.findViewById(R.id.icon_mime_sm);
80         mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
81         mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check);
82         mIconBadge = (ImageView) itemView.findViewById(R.id.icon_profile_badge);
83         mPreviewIcon = itemView.findViewById(R.id.preview_icon);
84 
85         mIconHelper = iconHelper;
86 
87         if (SdkLevel.isAtLeastT() && !mConfigStore.isPrivateSpaceInDocsUIEnabled()) {
88             setUpdatableWorkProfileIcon(context);
89         }
90     }
91 
92     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
setUpdatableWorkProfileIcon(Context context)93     private void setUpdatableWorkProfileIcon(Context context) {
94         DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
95         Drawable drawable = dpm.getResources().getDrawable(WORK_PROFILE_ICON, SOLID_COLORED, () ->
96                 context.getDrawable(R.drawable.ic_briefcase));
97         mIconBadge.setImageDrawable(drawable);
98     }
99 
100     @Override
setSelected(boolean selected, boolean animate)101     public void setSelected(boolean selected, boolean animate) {
102         // We always want to make sure our check box disappears if we're not selected,
103         // even if the item is disabled. This is because this object can be reused
104         // and this method will be called to setup initial state.
105         float checkAlpha = selected ? 1f : 0f;
106         if (animate) {
107             fade(mIconMimeSm, checkAlpha).start();
108             fade(mIconCheck, checkAlpha).start();
109         } else {
110             mIconCheck.setAlpha(checkAlpha);
111         }
112 
113         // But it should be an error to be set to selected && be disabled.
114         if (!itemView.isEnabled()) {
115             assert (!selected);
116         }
117 
118         super.setSelected(selected, animate);
119 
120         if (animate) {
121             fade(mIconMimeSm, 1f - checkAlpha).start();
122         } else {
123             mIconMimeSm.setAlpha(1f - checkAlpha);
124         }
125     }
126 
127     @Override
setEnabled(boolean enabled)128     public void setEnabled(boolean enabled) {
129         super.setEnabled(enabled);
130 
131         float imgAlpha = enabled ? 1f : DISABLED_ALPHA;
132 
133         mIconMimeLg.setAlpha(imgAlpha);
134         mIconMimeSm.setAlpha(imgAlpha);
135         mIconThumb.setAlpha(imgAlpha);
136     }
137 
138     @Override
bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback)139     public void bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback) {
140         mPreviewIcon.setVisibility(show ? View.VISIBLE : View.GONE);
141         if (show) {
142             mPreviewIcon.setContentDescription(
143                     getPreviewIconContentDescription(
144                             mIconHelper.shouldShowBadge(mDoc.userId.getIdentifier()),
145                             mDoc.displayName, mDoc.userId));
146             mPreviewIcon.setAccessibilityDelegate(new PreviewAccessibilityDelegate(clickCallback));
147         }
148     }
149 
150     @Override
bindBriefcaseIcon(boolean show)151     public void bindBriefcaseIcon(boolean show) {
152         mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE);
153     }
154 
155     @Override
156     @RequiresApi(Build.VERSION_CODES.S)
bindProfileIcon(boolean show, int userIdIdentifier)157     public void bindProfileIcon(boolean show, int userIdIdentifier) {
158         Map<UserId, Drawable> userIdToBadgeMap = DocumentsApplication.getUserManagerState(
159                 mContext).getUserIdToBadgeMap();
160         Drawable drawable = userIdToBadgeMap.get(UserId.of(userIdIdentifier));
161         mIconBadge.setImageDrawable(drawable);
162         mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE);
163         mIconBadge.setContentDescription(mIconHelper.getProfileLabel(userIdIdentifier));
164     }
165 
166     @Override
inDragRegion(MotionEvent event)167     public boolean inDragRegion(MotionEvent event) {
168         // Entire grid box should be draggable
169         return true;
170     }
171 
172     @Override
inSelectRegion(MotionEvent event)173     public boolean inSelectRegion(MotionEvent event) {
174         return Views.isEventOver(event, itemView.getParent(), mIconLayout);
175     }
176 
177     @Override
inPreviewIconRegion(MotionEvent event)178     public boolean inPreviewIconRegion(MotionEvent event) {
179         return Views.isEventOver(event, itemView.getParent(), mPreviewIcon);
180     }
181 
182     /**
183      * Bind this view to the given document for display.
184      *
185      * @param cursor  Pointing to the item to be bound.
186      * @param modelId The model ID of the item.
187      */
188     @Override
bind(Cursor cursor, String modelId)189     public void bind(Cursor cursor, String modelId) {
190         assert (cursor != null);
191 
192         mModelId = modelId;
193 
194         mDoc.updateFromCursor(cursor,
195                 UserId.of(getCursorInt(cursor, RootCursorWrapper.COLUMN_USER_ID)),
196                 getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY));
197 
198         mIconHelper.stopLoading(mIconThumb);
199 
200         mIconMimeLg.animate().cancel();
201         mIconMimeLg.setAlpha(1f);
202         mIconThumb.animate().cancel();
203         mIconThumb.setAlpha(0f);
204 
205         mIconHelper.load(mDoc, mIconThumb, mIconMimeLg, mIconMimeSm);
206 
207         mTitle.setText(mDoc.displayName, TextView.BufferType.SPANNABLE);
208         mTitle.setVisibility(View.VISIBLE);
209 
210         // If file is partial, we want to show summary field as that's more relevant than fileSize
211         // and date
212         if (mDoc.isPartial()) {
213             final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
214             mDetails.setVisibility(View.VISIBLE);
215             mDate.setText(null);
216             mDetails.setText(docSummary);
217         } else {
218             if (mDoc.lastModified == -1) {
219                 mDate.setText(null);
220             } else {
221                 mDate.setText(Shared.formatTime(mContext, mDoc.lastModified));
222             }
223 
224             final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
225             if (mDoc.isDirectory() || docSize == -1) {
226                 mDetails.setVisibility(View.GONE);
227             } else {
228                 mDetails.setVisibility(View.VISIBLE);
229                 mDetails.setText(Formatter.formatFileSize(mContext, docSize));
230             }
231         }
232     }
233 }
234