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