Chromium Code Reviews| 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 |