1 /* 2 * Copyright (C) 2013 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.camera.data; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.graphics.Bitmap; 22 import android.graphics.Point; 23 import android.graphics.drawable.BitmapDrawable; 24 import android.net.Uri; 25 import android.provider.MediaStore; 26 import android.view.View; 27 import android.widget.ImageView; 28 29 import com.android.camera.Storage; 30 import com.android.camera.data.FilmstripItemAttributes.Attributes; 31 import com.android.camera.debug.Log; 32 import com.android.camera.util.CameraUtil; 33 import com.android.camera.util.Size; 34 import com.android.camera2.R; 35 import com.bumptech.glide.DrawableRequestBuilder; 36 import com.bumptech.glide.GenericRequestBuilder; 37 import com.bumptech.glide.Glide; 38 import com.bumptech.glide.load.resource.drawable.GlideDrawable; 39 import com.google.common.base.Optional; 40 41 import java.io.FileInputStream; 42 import java.io.FileNotFoundException; 43 44 import javax.annotation.Nonnull; 45 46 /** 47 * Backing data for a single photo displayed in the filmstrip. 48 */ 49 public class PhotoItem extends FilmstripItemBase<FilmstripItemData> { 50 private static final Log.Tag TAG = new Log.Tag("PhotoItem"); 51 private static final int MAX_PEEK_BITMAP_PIXELS = 1600000; // 1.6 * 4 MBs. 52 53 private static final FilmstripItemAttributes PHOTO_ITEM_ATTRIBUTES = 54 new FilmstripItemAttributes.Builder() 55 .with(Attributes.CAN_SHARE) 56 .with(Attributes.CAN_EDIT) 57 .with(Attributes.CAN_DELETE) 58 .with(Attributes.CAN_SWIPE_AWAY) 59 .with(Attributes.CAN_ZOOM_IN_PLACE) 60 .with(Attributes.HAS_DETAILED_CAPTURE_INFO) 61 .with(Attributes.IS_IMAGE) 62 .build(); 63 64 private final PhotoItemFactory mPhotoItemFactory; 65 66 private Optional<Bitmap> mSessionPlaceholderBitmap = Optional.absent(); 67 PhotoItem(Context context, GlideFilmstripManager manager, FilmstripItemData data, PhotoItemFactory photoItemFactory)68 public PhotoItem(Context context, GlideFilmstripManager manager, FilmstripItemData data, 69 PhotoItemFactory photoItemFactory) { 70 super(context, manager, data, PHOTO_ITEM_ATTRIBUTES); 71 mPhotoItemFactory = photoItemFactory; 72 } 73 74 /** 75 * A bitmap that if present, is a high resolution bitmap from a temporary 76 * session, that should be used as a placeholder in place of placeholder/ 77 * thumbnail loading. 78 * 79 * @param sessionPlaceholderBitmap a Bitmap to set as a placeholder 80 */ setSessionPlaceholderBitmap(Optional<Bitmap> sessionPlaceholderBitmap)81 public void setSessionPlaceholderBitmap(Optional<Bitmap> sessionPlaceholderBitmap) { 82 mSessionPlaceholderBitmap = sessionPlaceholderBitmap; 83 } 84 85 @Override toString()86 public String toString() { 87 return "PhotoItem: " + mData.toString(); 88 } 89 90 @Override delete()91 public boolean delete() { 92 ContentResolver cr = mContext.getContentResolver(); 93 cr.delete(PhotoDataQuery.CONTENT_URI, 94 MediaStore.Images.ImageColumns._ID + "=" + mData.getContentId(), null); 95 return super.delete(); 96 } 97 98 @Override getMediaDetails()99 public Optional<MediaDetails> getMediaDetails() { 100 Optional<MediaDetails> optionalDetails = super.getMediaDetails(); 101 if (optionalDetails.isPresent()) { 102 MediaDetails mediaDetails = optionalDetails.get(); 103 MediaDetails.extractExifInfo(mediaDetails, mData.getFilePath()); 104 mediaDetails.addDetail(MediaDetails.INDEX_ORIENTATION, mData.getOrientation()); 105 } 106 return optionalDetails; 107 } 108 109 @Override refresh()110 public FilmstripItem refresh() { 111 // TODO: Consider simply replacing the data inline 112 return mPhotoItemFactory.get(mData.getUri()); 113 } 114 115 @Override getView(Optional<View> optionalView, LocalFilmstripDataAdapter adapter, boolean isInProgress, VideoClickedCallback videoClickedCallback)116 public View getView(Optional<View> optionalView, LocalFilmstripDataAdapter adapter, 117 boolean isInProgress, VideoClickedCallback videoClickedCallback) { 118 ImageView imageView; 119 120 if (optionalView.isPresent()) { 121 imageView = (ImageView) optionalView.get(); 122 } else { 123 imageView = new ImageView(mContext); 124 imageView.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal()); 125 } 126 127 fillImageView(imageView); 128 129 return imageView; 130 } 131 fillImageView(final ImageView imageView)132 protected void fillImageView(final ImageView imageView) { 133 renderTinySize(mData.getUri()).into(imageView); 134 135 // TODO consider having metadata have a "get description" string 136 // or some other way of selecting rendering details based on metadata. 137 int stringId = R.string.photo_date_content_description; 138 if (getMetadata().isPanorama() || 139 getMetadata().isPanorama360()) { 140 stringId = R.string.panorama_date_content_description; 141 } else if (getMetadata().isUsePanoramaViewer()) { 142 // assume it's a PhotoSphere 143 stringId = R.string.photosphere_date_content_description; 144 } else if (this.getMetadata().isHasRgbzData()) { 145 stringId = R.string.refocus_date_content_description; 146 } 147 148 imageView.setContentDescription(mContext.getResources().getString( 149 stringId, 150 mDateFormatter.format(mData.getLastModifiedDate()))); 151 } 152 153 @Override recycle(@onnull View view)154 public void recycle(@Nonnull View view) { 155 Glide.clear(view); 156 mSessionPlaceholderBitmap = Optional.absent(); 157 } 158 159 @Override getItemViewType()160 public FilmstripItemType getItemViewType() { 161 return FilmstripItemType.PHOTO; 162 } 163 164 @Override renderTiny(@onnull View view)165 public void renderTiny(@Nonnull View view) { 166 if (view instanceof ImageView) { 167 renderTinySize(mData.getUri()).into((ImageView) view); 168 } else { 169 Log.w(TAG, "renderTiny was called with an object that is not an ImageView!"); 170 } 171 } 172 173 @Override renderThumbnail(@onnull View view)174 public void renderThumbnail(@Nonnull View view) { 175 if (view instanceof ImageView) { 176 renderScreenSize(mData.getUri()).into((ImageView) view); 177 } else { 178 Log.w(TAG, "renderThumbnail was called with an object that is not an ImageView!"); 179 } 180 } 181 182 @Override renderFullRes(@onnull View view)183 public void renderFullRes(@Nonnull View view) { 184 if (view instanceof ImageView) { 185 renderFullSize(mData.getUri()).into((ImageView) view); 186 } else { 187 Log.w(TAG, "renderFullRes was called with an object that is not an ImageView!"); 188 } 189 } 190 renderTinySize(Uri uri)191 private GenericRequestBuilder<Uri, ?, ?, GlideDrawable> renderTinySize(Uri uri) { 192 return mGlideManager.loadTinyThumb(uri, generateSignature(mData)); 193 } 194 renderScreenSize(Uri uri)195 private DrawableRequestBuilder<Uri> renderScreenSize(Uri uri) { 196 DrawableRequestBuilder<Uri> request = 197 mGlideManager.loadScreen(uri, generateSignature(mData), mSuggestedSize); 198 199 // If we have a non-null placeholder, use that and do NOT ever render a 200 // tiny thumbnail to prevent un-intended "flash of low resolution image" 201 if (mSessionPlaceholderBitmap.isPresent()) { 202 Log.v(TAG, "using session bitmap as placeholder"); 203 return request.placeholder(new BitmapDrawable(mContext.getResources(), 204 mSessionPlaceholderBitmap.get())); 205 } 206 207 // If we do not have a placeholder bitmap, render a thumbnail with 208 // the default placeholder resource like normal. 209 return request 210 .thumbnail(renderTinySize(uri)); 211 } 212 renderFullSize(Uri uri)213 private DrawableRequestBuilder<Uri> renderFullSize(Uri uri) { 214 Size size = mData.getDimensions(); 215 return mGlideManager.loadFull(uri, generateSignature(mData), size) 216 .thumbnail(renderScreenSize(uri)); 217 } 218 219 @Override generateThumbnail(int boundingWidthPx, int boundingHeightPx)220 public Optional<Bitmap> generateThumbnail(int boundingWidthPx, int boundingHeightPx) { 221 FilmstripItemData data = getData(); 222 final Bitmap bitmap; 223 224 if (getAttributes().isRendering()) { 225 return Storage.instance().getPlaceholderForSession(data.getUri()); 226 } else { 227 228 FileInputStream stream; 229 230 try { 231 stream = new FileInputStream(data.getFilePath()); 232 } catch (FileNotFoundException e) { 233 Log.e(TAG, "File not found:" + data.getFilePath()); 234 return Optional.absent(); 235 } 236 int width = data.getDimensions().getWidth(); 237 int height = data.getDimensions().getHeight(); 238 int orientation = data.getOrientation(); 239 240 Point dim = CameraUtil.resizeToFill( 241 width, 242 height, 243 orientation, 244 boundingWidthPx, 245 boundingHeightPx); 246 247 // If the orientation is not vertical 248 if (orientation % 180 != 0) { 249 int temp = dim.x; 250 dim.x = dim.y; 251 dim.y = temp; 252 } 253 254 bitmap = FilmstripItemUtils 255 .loadImageThumbnailFromStream( 256 stream, 257 data.getDimensions().getWidth(), 258 data.getDimensions().getHeight(), 259 (int) (dim.x * 0.7f), (int) (dim.y * 0.7), 260 data.getOrientation(), MAX_PEEK_BITMAP_PIXELS); 261 262 return Optional.fromNullable(bitmap); 263 } 264 } 265 } 266