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 
init()61   private void init() {
62     showPlaceholder();
63   }
64 
MtpImageView(Context context)65   public MtpImageView(Context context) {
66     super(context);
67     init();
68   }
69 
MtpImageView(Context context, AttributeSet attrs)70   public MtpImageView(Context context, AttributeSet attrs) {
71     super(context, attrs);
72     init();
73   }
74 
MtpImageView(Context context, AttributeSet attrs, int defStyle)75   public MtpImageView(Context context, AttributeSet attrs, int defStyle) {
76     super(context, attrs, defStyle);
77     init();
78   }
79 
showPlaceholder()80   private void showPlaceholder() {
81     setImageResource(android.R.color.transparent);
82   }
83 
setMtpDeviceAndObjectInfo(MtpDevice device, IngestObjectInfo object, int gen)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 
fetchMtpImageDataFromDevice(MtpDevice device, IngestObjectInfo info)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 
updateDrawMatrix()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 
updateOverlayIconBounds()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
onLayout(boolean changed, int left, int top, int right, int bottom)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
onDraw(Canvas canvas)196   protected void onDraw(Canvas canvas) {
197     super.onDraw(canvas);
198     if (mShowOverlayIcon && mOverlayIcon != null) {
199       mOverlayIcon.draw(canvas);
200     }
201   }
202 
onMtpImageDataFetchedFromDevice(Object result)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 
cancelLoadingAndClear()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
onDetachedFromWindow()229   public void onDetachedFromWindow() {
230     cancelLoadingAndClear();
231     super.onDetachedFromWindow();
232   }
233 
234   private static class FetchImageHandler extends Handler {
FetchImageHandler(Looper l)235     public FetchImageHandler(Looper l) {
236       super(l);
237     }
238 
createOnNewThread()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
handleMessage(Message msg)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
handleMessage(Message msg)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