Home | History | Annotate | Download | only in ui
      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.gallery3d.ingest.ui;
     18 
     19 import com.android.gallery3d.R;
     20 import com.android.gallery3d.ingest.data.BitmapWithMetadata;
     21 import com.android.gallery3d.ingest.data.IngestObjectInfo;
     22 import com.android.gallery3d.ingest.data.MtpBitmapFetch;
     23 import com.android.gallery3d.ingest.data.MtpDeviceIndex;
     24 
     25 import android.content.Context;
     26 import android.graphics.Canvas;
     27 import android.graphics.Matrix;
     28 import android.graphics.drawable.Drawable;
     29 import android.mtp.MtpDevice;
     30 import android.os.Handler;
     31 import android.os.HandlerThread;
     32 import android.os.Looper;
     33 import android.os.Message;
     34 import android.util.AttributeSet;
     35 import android.widget.ImageView;
     36 
     37 import java.lang.ref.WeakReference;
     38 
     39 /**
     40  * View for images from an MTP devices
     41  */
     42 public class MtpImageView extends ImageView {
     43   // We will use the thumbnail for images larger than this threshold
     44   private static final int MAX_FULLSIZE_PREVIEW_SIZE = 8388608; // 8 megabytes
     45 
     46   private int mObjectHandle;
     47   private int mGeneration;
     48 
     49   private WeakReference<MtpImageView> mWeakReference = new WeakReference<MtpImageView>(this);
     50   private Object mFetchLock = new Object();
     51   private boolean mFetchPending = false;
     52   private IngestObjectInfo mFetchObjectInfo;
     53   private MtpDevice mFetchDevice;
     54   private Object mFetchResult;
     55   private Drawable mOverlayIcon;
     56   private boolean mShowOverlayIcon;
     57 
     58   private static final FetchImageHandler sFetchHandler = FetchImageHandler.createOnNewThread();
     59   private static final ShowImageHandler sFetchCompleteHandler = new ShowImageHandler();
     60 
     61   private void init() {
     62     showPlaceholder();
     63   }
     64 
     65   public MtpImageView(Context context) {
     66     super(context);
     67     init();
     68   }
     69 
     70   public MtpImageView(Context context, AttributeSet attrs) {
     71     super(context, attrs);
     72     init();
     73   }
     74 
     75   public MtpImageView(Context context, AttributeSet attrs, int defStyle) {
     76     super(context, attrs, defStyle);
     77     init();
     78   }
     79 
     80   private void showPlaceholder() {
     81     setImageResource(android.R.color.transparent);
     82   }
     83 
     84   public void setMtpDeviceAndObjectInfo(MtpDevice device, IngestObjectInfo object, int gen) {
     85     int handle = object.getObjectHandle();
     86     if (handle == mObjectHandle && gen == mGeneration) {
     87       return;
     88     }
     89     cancelLoadingAndClear();
     90     showPlaceholder();
     91     mGeneration = gen;
     92     mObjectHandle = handle;
     93     mShowOverlayIcon = MtpDeviceIndex.SUPPORTED_VIDEO_FORMATS.contains(object.getFormat());
     94     if (mShowOverlayIcon && mOverlayIcon == null) {
     95       mOverlayIcon = getResources().getDrawable(R.drawable.ic_control_play);
     96       updateOverlayIconBounds();
     97     }
     98     synchronized (mFetchLock) {
     99       mFetchObjectInfo = object;
    100       mFetchDevice = device;
    101       if (mFetchPending) {
    102         return;
    103       }
    104       mFetchPending = true;
    105       sFetchHandler.sendMessage(
    106           sFetchHandler.obtainMessage(0, mWeakReference));
    107     }
    108   }
    109 
    110   protected Object fetchMtpImageDataFromDevice(MtpDevice device, IngestObjectInfo info) {
    111     if (info.getCompressedSize() <= MAX_FULLSIZE_PREVIEW_SIZE
    112         && MtpDeviceIndex.SUPPORTED_IMAGE_FORMATS.contains(info.getFormat())) {
    113       return MtpBitmapFetch.getFullsize(device, info);
    114     } else {
    115       return new BitmapWithMetadata(MtpBitmapFetch.getThumbnail(device, info), 0);
    116     }
    117   }
    118 
    119   private float mLastBitmapWidth;
    120   private float mLastBitmapHeight;
    121   private int mLastRotationDegrees;
    122   private Matrix mDrawMatrix = new Matrix();
    123 
    124   private void updateDrawMatrix() {
    125     mDrawMatrix.reset();
    126     float dwidth;
    127     float dheight;
    128     float vheight = getHeight();
    129     float vwidth = getWidth();
    130     float scale;
    131     boolean rotated90 = (mLastRotationDegrees % 180 != 0);
    132     if (rotated90) {
    133       dwidth = mLastBitmapHeight;
    134       dheight = mLastBitmapWidth;
    135     } else {
    136       dwidth = mLastBitmapWidth;
    137       dheight = mLastBitmapHeight;
    138     }
    139     if (dwidth <= vwidth && dheight <= vheight) {
    140       scale = 1.0f;
    141     } else {
    142       scale = Math.min(vwidth / dwidth, vheight / dheight);
    143     }
    144     mDrawMatrix.setScale(scale, scale);
    145     if (rotated90) {
    146       mDrawMatrix.postTranslate(-dheight * scale * 0.5f,
    147           -dwidth * scale * 0.5f);
    148       mDrawMatrix.postRotate(mLastRotationDegrees);
    149       mDrawMatrix.postTranslate(dwidth * scale * 0.5f,
    150           dheight * scale * 0.5f);
    151     }
    152     mDrawMatrix.postTranslate((vwidth - dwidth * scale) * 0.5f,
    153         (vheight - dheight * scale) * 0.5f);
    154     if (!rotated90 && mLastRotationDegrees > 0) {
    155       // rotated by a multiple of 180
    156       mDrawMatrix.postRotate(mLastRotationDegrees, vwidth / 2, vheight / 2);
    157     }
    158     setImageMatrix(mDrawMatrix);
    159   }
    160 
    161   private static final int OVERLAY_ICON_SIZE_DENOMINATOR = 4;
    162 
    163   private void updateOverlayIconBounds() {
    164     int iheight = mOverlayIcon.getIntrinsicHeight();
    165     int iwidth = mOverlayIcon.getIntrinsicWidth();
    166     int vheight = getHeight();
    167     int vwidth = getWidth();
    168     float scaleHeight = ((float) vheight) / (iheight * OVERLAY_ICON_SIZE_DENOMINATOR);
    169     float scaleWidth = ((float) vwidth) / (iwidth * OVERLAY_ICON_SIZE_DENOMINATOR);
    170     if (scaleHeight >= 1f && scaleWidth >= 1f) {
    171       mOverlayIcon.setBounds((vwidth - iwidth) / 2,
    172           (vheight - iheight) / 2,
    173           (vwidth + iwidth) / 2,
    174           (vheight + iheight) / 2);
    175     } else {
    176       float scale = Math.min(scaleHeight, scaleWidth);
    177       mOverlayIcon.setBounds((int) (vwidth - scale * iwidth) / 2,
    178           (int) (vheight - scale * iheight) / 2,
    179           (int) (vwidth + scale * iwidth) / 2,
    180           (int) (vheight + scale * iheight) / 2);
    181     }
    182   }
    183 
    184   @Override
    185   protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    186     super.onLayout(changed, left, top, right, bottom);
    187     if (changed && getScaleType() == ScaleType.MATRIX) {
    188       updateDrawMatrix();
    189     }
    190     if (mShowOverlayIcon && changed && mOverlayIcon != null) {
    191       updateOverlayIconBounds();
    192     }
    193   }
    194 
    195   @Override
    196   protected void onDraw(Canvas canvas) {
    197     super.onDraw(canvas);
    198     if (mShowOverlayIcon && mOverlayIcon != null) {
    199       mOverlayIcon.draw(canvas);
    200     }
    201   }
    202 
    203   protected void onMtpImageDataFetchedFromDevice(Object result) {
    204     BitmapWithMetadata bitmapWithMetadata = (BitmapWithMetadata) result;
    205     if (getScaleType() == ScaleType.MATRIX) {
    206       mLastBitmapHeight = bitmapWithMetadata.bitmap.getHeight();
    207       mLastBitmapWidth = bitmapWithMetadata.bitmap.getWidth();
    208       mLastRotationDegrees = bitmapWithMetadata.rotationDegrees;
    209       updateDrawMatrix();
    210     } else {
    211       setRotation(bitmapWithMetadata.rotationDegrees);
    212     }
    213     setAlpha(0f);
    214     setImageBitmap(bitmapWithMetadata.bitmap);
    215     animate().alpha(1f);
    216   }
    217 
    218   protected void cancelLoadingAndClear() {
    219     synchronized (mFetchLock) {
    220       mFetchDevice = null;
    221       mFetchObjectInfo = null;
    222       mFetchResult = null;
    223     }
    224     animate().cancel();
    225     setImageResource(android.R.color.transparent);
    226   }
    227 
    228   @Override
    229   public void onDetachedFromWindow() {
    230     cancelLoadingAndClear();
    231     super.onDetachedFromWindow();
    232   }
    233 
    234   private static class FetchImageHandler extends Handler {
    235     public FetchImageHandler(Looper l) {
    236       super(l);
    237     }
    238 
    239     public static FetchImageHandler createOnNewThread() {
    240       HandlerThread t = new HandlerThread("MtpImageView Fetch");
    241       t.start();
    242       return new FetchImageHandler(t.getLooper());
    243     }
    244 
    245     @Override
    246     public void handleMessage(Message msg) {
    247       @SuppressWarnings("unchecked")
    248       MtpImageView parent = ((WeakReference<MtpImageView>) msg.obj).get();
    249       if (parent == null) {
    250         return;
    251       }
    252       IngestObjectInfo objectInfo;
    253       MtpDevice device;
    254       synchronized (parent.mFetchLock) {
    255         parent.mFetchPending = false;
    256         device = parent.mFetchDevice;
    257         objectInfo = parent.mFetchObjectInfo;
    258       }
    259       if (device == null) {
    260         return;
    261       }
    262       Object result = parent.fetchMtpImageDataFromDevice(device, objectInfo);
    263       if (result == null) {
    264         return;
    265       }
    266       synchronized (parent.mFetchLock) {
    267         if (parent.mFetchObjectInfo != objectInfo) {
    268           return;
    269         }
    270         parent.mFetchResult = result;
    271         parent.mFetchDevice = null;
    272         parent.mFetchObjectInfo = null;
    273         sFetchCompleteHandler.sendMessage(
    274             sFetchCompleteHandler.obtainMessage(0, parent.mWeakReference));
    275       }
    276     }
    277   }
    278 
    279   private static class ShowImageHandler extends Handler {
    280     @Override
    281     public void handleMessage(Message msg) {
    282       @SuppressWarnings("unchecked")
    283       MtpImageView parent = ((WeakReference<MtpImageView>) msg.obj).get();
    284       if (parent == null) {
    285         return;
    286       }
    287       Object result;
    288       synchronized (parent.mFetchLock) {
    289         result = parent.mFetchResult;
    290       }
    291       if (result == null) {
    292         return;
    293       }
    294       parent.onMtpImageDataFetchedFromDevice(result);
    295     }
    296   }
    297 }
    298