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