OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.chrome.browser.photo_picker; | |
6 | |
7 import android.app.Activity; | |
8 import android.content.Context; | |
9 import android.graphics.Bitmap; | |
10 import android.graphics.BitmapFactory; | |
11 import android.graphics.Canvas; | |
12 import android.graphics.Paint; | |
13 import android.graphics.PorterDuff; | |
14 import android.graphics.PorterDuffColorFilter; | |
15 import android.graphics.Rect; | |
16 import android.support.v4.content.ContextCompat; | |
17 import android.support.v7.widget.DefaultItemAnimator; | |
18 import android.support.v7.widget.GridLayoutManager; | |
19 import android.support.v7.widget.RecyclerView; | |
20 import android.support.v7.widget.Toolbar.OnMenuItemClickListener; | |
21 import android.util.AttributeSet; | |
22 import android.util.LruCache; | |
23 import android.view.LayoutInflater; | |
24 import android.view.MenuItem; | |
25 import android.view.View; | |
26 import android.widget.RelativeLayout; | |
27 | |
28 import org.chromium.chrome.R; | |
29 import org.chromium.chrome.browser.widget.selection.SelectableListLayout; | |
30 import org.chromium.chrome.browser.widget.selection.SelectionDelegate; | |
31 import org.chromium.ui.PhotoPickerListener; | |
32 | |
33 import java.util.List; | |
34 | |
35 /** | |
36 * A class for keeping track of common data associated with showing photos in | |
37 * the photo picker, for example the RecyclerView and the bitmap caches. | |
38 */ | |
39 public class PickerCategoryView extends RelativeLayout | |
40 implements FileEnumWorkerTask.FilesEnumeratedCallback, OnMenuItemClickLi stener { | |
41 // The dialog that owns us. | |
42 private PhotoPickerDialog mDialog; | |
43 | |
44 // The view containing the recycler view and the toolbar, etc. | |
45 private SelectableListLayout<PickerBitmap> mSelectableListLayout; | |
46 | |
47 // The toolbar at the top of the dialog. | |
48 private PhotoPickerToolbar mToolbar; | |
49 | |
50 // Our context. | |
51 private Context mContext; | |
52 | |
53 // The list of images on disk, sorted by last-modified first. | |
54 private List<PickerBitmap> mPickerBitmaps; | |
55 | |
56 // True if multi-selection is allowed in the picker. | |
57 private boolean mMultiSelection; | |
Theresa
2017/03/28 20:40:29
nit: mIsMultiSelectionAllowed? or mIsMultiSelectio
Finnur
2017/03/31 14:26:50
Done.
| |
58 | |
59 // The callback to notify the listener of decisions reached in the picker. | |
60 private PhotoPickerListener mListener; | |
61 | |
62 // The recycler view showing the images. | |
63 private RecyclerView mRecyclerView; | |
64 | |
65 // The picker adapter for the RecyclerView. | |
66 private PickerAdapter mPickerAdapter; | |
67 | |
68 // The selection delegate keeping track of which images are selected. | |
69 private SelectionDelegate<PickerBitmap> mSelectionDelegate; | |
70 | |
71 // A low-resolution cache for images. Helpful for cache misses from the high -resolution cache | |
72 // to avoid showing gray squares (show pixelated versions instead until imag e can be loaded off | |
73 // disk). | |
74 private LruCache<String, Bitmap> mLowResBitmaps; | |
75 | |
76 // A high-resolution cache for images. | |
77 private LruCache<String, Bitmap> mHighResBitmaps; | |
78 | |
79 // The number of columns to show. Note: mColumns and mPadding (see below) sh ould both be even | |
80 // numbers or both odd, not a mix (the column padding will not be of uniform thickness if they | |
81 // are a mix). | |
Theresa
2017/03/28 20:40:29
nit: Should these be JavaDoc /** */ comments so th
Finnur
2017/03/31 14:26:50
Done. Unless you mean all of them?
| |
82 private int mColumns; | |
83 | |
84 // The padding between columns. See also comment for mColumns. | |
85 private int mPadding; | |
86 | |
87 // The size of the bitmaps (equal length for width and height). | |
88 private int mImageSize; | |
89 | |
90 // The control to show for when an image is selected. | |
91 private Bitmap mBitmapSelected; | |
92 | |
93 // The control to show for when an image is not selected. | |
94 private Bitmap mBitmapUnselected; | |
95 | |
96 // A worker task for asynchronously enumerating files off the main thread. | |
97 private FileEnumWorkerTask mWorkerTask; | |
98 | |
99 public PickerCategoryView(Context context) { | |
100 super(context); | |
101 init(context); | |
102 } | |
103 | |
104 public PickerCategoryView(Context context, AttributeSet attrs) { | |
105 super(context, attrs); | |
106 init(context); | |
107 } | |
108 | |
109 public PickerCategoryView(Context context, AttributeSet attrs, int defStyle) { | |
110 super(context, attrs, defStyle); | |
111 init(context); | |
112 } | |
113 | |
114 /** | |
115 * A helper function for initializing the PickerCategoryView. | |
116 * @param context The context to use. | |
117 */ | |
118 @SuppressWarnings("unchecked") // mSelectableListLayout | |
119 private void init(Context context) { | |
120 mContext = context; | |
121 | |
122 mSelectionDelegate = new SelectionDelegate<PickerBitmap>(); | |
123 | |
124 View root = LayoutInflater.from(context).inflate(R.layout.photo_picker_d ialog, this); | |
125 mSelectableListLayout = | |
126 (SelectableListLayout<PickerBitmap>) root.findViewById(R.id.sele ctable_list); | |
127 | |
128 mPickerAdapter = new PickerAdapter(this); | |
129 mRecyclerView = mSelectableListLayout.initializeRecyclerView(mPickerAdap ter); | |
130 mToolbar = (PhotoPickerToolbar) mSelectableListLayout.initializeToolbar( | |
131 R.layout.photo_picker_toolbar, mSelectionDelegate, R.string.menu _history, null, | |
132 R.id.file_picker_normal_menu_group, R.id.file_picker_selection_m ode_menu_group, | |
133 R.color.default_primary_color, false, this); | |
134 | |
135 View view = ((Activity) context).getWindow().getDecorView(); | |
136 int width = view.getWidth() - view.getPaddingLeft() - view.getPaddingRig ht(); | |
137 | |
138 calculateGridMetrics(width); | |
139 | |
140 RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(mConte xt, mColumns); | |
141 mRecyclerView.setHasFixedSize(true); | |
142 mRecyclerView.setLayoutManager(mLayoutManager); | |
143 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); | |
Theresa
2017/03/28 20:40:29
This shouldn't be necessary. By default, RecyclerV
Finnur
2017/03/31 14:26:50
Done.
| |
144 mRecyclerView.addItemDecoration(new GridSpacingItemDecoration(mColumns, mPadding)); | |
145 | |
146 mBitmapSelected = BitmapFactory.decodeResource( | |
147 mContext.getResources(), R.drawable.ic_check_circle_black_24dp); | |
148 mBitmapUnselected = BitmapFactory.decodeResource( | |
149 mContext.getResources(), R.drawable.ic_radio_button_unchecked_bl ack_24dp); | |
150 | |
151 // Apply color to the bitmaps. | |
152 int prefAccentColor = ContextCompat.getColor(mContext, R.color.pref_acce nt_color); | |
153 mBitmapSelected = colorBitmap(mBitmapSelected, prefAccentColor); | |
154 int unselectedColor = ContextCompat.getColor(mContext, R.color.white_mod e_tint); | |
155 mBitmapUnselected = colorBitmap(mBitmapUnselected, unselectedColor); | |
Theresa
2017/03/28 20:40:29
Typically we would set the src for an ImageView in
Finnur
2017/03/31 14:26:50
Neat. Done.
| |
156 | |
157 final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); | |
158 final int cacheSizeLarge = maxMemory / 2; // 1/2 of the available memory . | |
159 final int cacheSizeSmall = maxMemory / 8; // 1/8th of the available memo ry. | |
160 mLowResBitmaps = new LruCache<String, Bitmap>(cacheSizeSmall) { | |
161 @Override | |
162 protected int sizeOf(String key, Bitmap bitmap) { | |
163 return bitmap.getByteCount() / 1024; | |
Theresa
2017/03/28 20:40:29
nit: define a constant for 1024
Finnur
2017/03/31 14:26:50
Done.
| |
164 } | |
165 }; | |
166 mHighResBitmaps = new LruCache<String, Bitmap>(cacheSizeLarge) { | |
167 @Override | |
168 protected int sizeOf(String key, Bitmap bitmap) { | |
169 return bitmap.getByteCount() / 1024; | |
170 } | |
171 }; | |
172 | |
173 // TODO(finnur): Remove this once the decoder service is in place. | |
174 prepareBitmaps(); | |
175 } | |
176 | |
177 /** | |
178 * Sets the starting state for the PickerCategoryView object. | |
179 * @param selectionDelegate The selection delegate to use. | |
180 * @param listener The listener who should be notified of actions. | |
181 * @param multiSelection Whether to allow the user to select more than one i mage. | |
182 */ | |
183 public void setStartingState( | |
184 PhotoPickerDialog dialog, PhotoPickerListener listener, boolean mult iSelection) { | |
185 if (!multiSelection) mSelectionDelegate.setSingleSelectionMode(); | |
186 | |
187 mDialog = dialog; | |
188 mMultiSelection = multiSelection; | |
189 mListener = listener; | |
190 } | |
191 | |
192 // FileEnumWorkerTask.FilesEnumeratedCallback: | |
193 | |
194 @Override | |
195 public void filesEnumeratedCallback(List<PickerBitmap> files) { | |
196 mPickerBitmaps = files; | |
197 if (files != null && files.size() > 0) { | |
198 mPickerAdapter.notifyDataSetChanged(); | |
199 } else { | |
200 setVisibility(View.GONE); | |
Theresa
2017/03/28 20:40:29
Can this ever be empty, since there's the camera a
Finnur
2017/03/31 14:26:50
Removed. I believe this is from back when the Came
| |
201 } | |
202 } | |
203 | |
204 // OnMenuItemClickListener: | |
205 | |
206 @Override | |
207 public boolean onMenuItemClick(MenuItem item) { | |
208 if (item.getItemId() == R.id.close_menu_id | |
209 || item.getItemId() == R.id.selection_mode_done_menu_id) { | |
210 tryNotifyPhotoSet(); | |
211 mDialog.dismiss(); | |
212 return true; | |
213 } | |
214 return false; | |
215 } | |
216 | |
217 // Simple accessors: | |
218 | |
219 public int getImageSize() { | |
220 return mImageSize; | |
221 } | |
222 public SelectionDelegate<PickerBitmap> getSelectionDelegate() { | |
223 return mSelectionDelegate; | |
224 } | |
225 public List<PickerBitmap> getPickerBitmaps() { | |
226 return mPickerBitmaps; | |
227 } | |
228 public LruCache<String, Bitmap> getLowResBitmaps() { | |
229 return mLowResBitmaps; | |
230 } | |
231 public LruCache<String, Bitmap> getHighResBitmaps() { | |
232 return mHighResBitmaps; | |
233 } | |
234 public boolean isMultiSelect() { | |
235 return mMultiSelection; | |
236 } | |
237 | |
238 /** | |
239 * Notifies the caller that the user selected to launch the gallery. | |
240 */ | |
241 public void showGallery() { | |
242 mListener.onPickerUserAction(PhotoPickerListener.Action.LAUNCH_GALLERY, null); | |
243 } | |
244 | |
245 /** | |
246 * Notifies the caller that the user selected to launch the camera intent. | |
247 */ | |
248 public void showCamera() { | |
249 mListener.onPickerUserAction(PhotoPickerListener.Action.LAUNCH_CAMERA, n ull); | |
250 } | |
251 | |
252 /** | |
253 * Returns the selection bitmaps (control indicating whether the image is se lected or not). | |
254 * @param selected See return value. | |
255 * @return If |selected| is true, the selection bitmap is returned. Otherwis e the unselection | |
256 * bitmap is returned. | |
257 */ | |
258 public Bitmap getSelectionBitmap(boolean selected) { | |
259 if (selected) { | |
260 return mBitmapSelected; | |
261 } else { | |
262 return mBitmapUnselected; | |
263 } | |
264 } | |
265 | |
266 /** | |
267 * Applies a color filter to a bitmap. | |
268 * @param original The bitmap to color. | |
269 * @param color The color to apply. | |
270 * @return A colored bitmap. | |
271 */ | |
272 private Bitmap colorBitmap(Bitmap original, int color) { | |
273 Bitmap mutable = original.copy(Bitmap.Config.ARGB_8888, true); | |
274 Canvas canvas = new Canvas(mutable); | |
275 Paint paint = new Paint(); | |
276 paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SR C_IN)); | |
277 canvas.drawBitmap(mutable, 0.f, 0.f, paint); | |
278 return mutable; | |
279 } | |
280 | |
281 /** | |
282 * Calculates image size and how many columns can fit on-screen. | |
283 * @param width The total width of the boundary to show the images in. | |
284 */ | |
285 private void calculateGridMetrics(int width) { | |
286 int minSize = | |
287 mContext.getResources().getDimensionPixelSize(R.dimen.file_picke r_tile_min_size); | |
288 mPadding = mContext.getResources().getDimensionPixelSize(R.dimen.file_pi cker_tile_gap); | |
289 mColumns = Math.max(1, (width - mPadding) / (minSize + mPadding)); | |
290 mImageSize = (width - mPadding * (mColumns + 1)) / (mColumns); | |
291 | |
292 // Make sure columns and padding are either both even or both odd. | |
293 if (((mColumns % 2) == 0) != ((mPadding % 2) == 0)) { | |
294 mPadding++; | |
295 } | |
296 } | |
297 | |
298 /** | |
299 * Prepares bitmaps for loading. | |
300 */ | |
301 private void prepareBitmaps() { | |
302 if (mWorkerTask != null) { | |
303 mWorkerTask.cancel(true); | |
304 } | |
305 | |
306 mWorkerTask = new FileEnumWorkerTask(this, new AttrAcceptFileFilter("ima ge/*,video/*")); | |
307 mWorkerTask.execute(); | |
308 } | |
309 | |
310 /** | |
311 * Tries to notify any listeners that one or more photos have been selected. | |
312 */ | |
313 private void tryNotifyPhotoSet() { | |
314 List<PickerBitmap> selectedFiles = mSelectionDelegate.getSelectedItems() ; | |
315 String[] photos = new String[selectedFiles.size()]; | |
316 int i = 0; | |
317 for (PickerBitmap bitmap : selectedFiles) { | |
318 photos[i++] = bitmap.getFilePath(); | |
319 } | |
320 | |
321 mListener.onPickerUserAction(PhotoPickerListener.Action.PHOTOS_SELECTED, photos); | |
322 } | |
323 | |
324 /** | |
325 * A class for implementing grid spacing between items. | |
326 */ | |
327 private class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { | |
328 // The number of spans to account for. | |
329 private int mSpanCount; | |
330 | |
331 // The amount of spacing to use. | |
332 private int mSpacing; | |
333 | |
334 public GridSpacingItemDecoration(int spanCount, int spacing) { | |
335 mSpanCount = spanCount; | |
336 mSpacing = spacing; | |
337 } | |
338 | |
339 @Override | |
340 public void getItemOffsets( | |
341 Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { | |
342 int left = 0, right = 0, top = 0, bottom = 0; | |
343 int position = parent.getChildAdapterPosition(view); | |
344 | |
345 if (position >= 0) { | |
346 int column = position % mSpanCount; | |
347 | |
348 left = mSpacing - column * mSpacing / mSpanCount; | |
349 right = (column + 1) * mSpacing / mSpanCount; | |
350 | |
351 if (position < mSpanCount) { | |
352 top = mSpacing; | |
353 } | |
354 bottom = mSpacing; | |
355 } | |
356 | |
357 outRect.set(left, top, right, bottom); | |
358 } | |
359 } | |
360 } | |
OLD | NEW |