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