Home | History | Annotate | Download | only in data
      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 
     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      */
     81     public void setSessionPlaceholderBitmap(Optional<Bitmap> sessionPlaceholderBitmap) {
     82         mSessionPlaceholderBitmap = sessionPlaceholderBitmap;
     83     }
     84 
     85     @Override
     86     public String toString() {
     87         return "PhotoItem: " + mData.toString();
     88     }
     89 
     90     @Override
     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
     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
    110     public FilmstripItem refresh() {
    111         // TODO: Consider simply replacing the data inline
    112         return mPhotoItemFactory.get(mData.getUri());
    113     }
    114 
    115     @Override
    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 
    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
    154     public void recycle(@Nonnull View view) {
    155         Glide.clear(view);
    156         mSessionPlaceholderBitmap = Optional.absent();
    157     }
    158 
    159     @Override
    160     public FilmstripItemType getItemViewType() {
    161         return FilmstripItemType.PHOTO;
    162     }
    163 
    164     @Override
    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
    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
    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 
    191     private GenericRequestBuilder<Uri, ?, ?, GlideDrawable> renderTinySize(Uri uri) {
    192         return mGlideManager.loadTinyThumb(uri, generateSignature(mData));
    193     }
    194 
    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 
    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
    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.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 dummy = dim.x;
    250                 dim.x = dim.y;
    251                 dim.y = dummy;
    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