/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.gallery3d.ingest.ui; import android.content.Context; import android.graphics.Matrix; import android.mtp.MtpDevice; import android.mtp.MtpObjectInfo; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.AttributeSet; import android.widget.ImageView; import com.android.gallery3d.ingest.data.BitmapWithMetadata; import com.android.gallery3d.ingest.data.MtpBitmapFetch; import java.lang.ref.WeakReference; public class MtpImageView extends ImageView { private int mObjectHandle; private int mGeneration; private WeakReference<MtpImageView> mWeakReference = new WeakReference<MtpImageView>(this); private Object mFetchLock = new Object(); private boolean mFetchPending = false; private MtpObjectInfo mFetchObjectInfo; private MtpDevice mFetchDevice; private Object mFetchResult; private static final FetchImageHandler sFetchHandler = FetchImageHandler.createOnNewThread(); private static final ShowImageHandler sFetchCompleteHandler = new ShowImageHandler(); private void init() { showPlaceholder(); } public MtpImageView(Context context) { super(context); init(); } public MtpImageView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public MtpImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void showPlaceholder() { setImageResource(android.R.color.transparent); } public void setMtpDeviceAndObjectInfo(MtpDevice device, MtpObjectInfo object, int gen) { int handle = object.getObjectHandle(); if (handle == mObjectHandle && gen == mGeneration) { return; } cancelLoadingAndClear(); showPlaceholder(); mGeneration = gen; mObjectHandle = handle; synchronized (mFetchLock) { mFetchObjectInfo = object; mFetchDevice = device; if (mFetchPending) return; mFetchPending = true; sFetchHandler.sendMessage( sFetchHandler.obtainMessage(0, mWeakReference)); } } protected Object fetchMtpImageDataFromDevice(MtpDevice device, MtpObjectInfo info) { return MtpBitmapFetch.getFullsize(device, info); } private float mLastBitmapWidth; private float mLastBitmapHeight; private int mLastRotationDegrees; private Matrix mDrawMatrix = new Matrix(); private void updateDrawMatrix() { mDrawMatrix.reset(); float dwidth; float dheight; float vheight = getHeight(); float vwidth = getWidth(); float scale; boolean rotated90 = (mLastRotationDegrees % 180 != 0); if (rotated90) { dwidth = mLastBitmapHeight; dheight = mLastBitmapWidth; } else { dwidth = mLastBitmapWidth; dheight = mLastBitmapHeight; } if (dwidth <= vwidth && dheight <= vheight) { scale = 1.0f; } else { scale = Math.min(vwidth / dwidth, vheight / dheight); } mDrawMatrix.setScale(scale, scale); if (rotated90) { mDrawMatrix.postTranslate(-dheight * scale * 0.5f, -dwidth * scale * 0.5f); mDrawMatrix.postRotate(mLastRotationDegrees); mDrawMatrix.postTranslate(dwidth * scale * 0.5f, dheight * scale * 0.5f); } mDrawMatrix.postTranslate((vwidth - dwidth * scale) * 0.5f, (vheight - dheight * scale) * 0.5f); if (!rotated90 && mLastRotationDegrees > 0) { // rotated by a multiple of 180 mDrawMatrix.postRotate(mLastRotationDegrees, vwidth / 2, vheight / 2); } setImageMatrix(mDrawMatrix); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed && getScaleType() == ScaleType.MATRIX) { updateDrawMatrix(); } } protected void onMtpImageDataFetchedFromDevice(Object result) { BitmapWithMetadata bitmapWithMetadata = (BitmapWithMetadata)result; if (getScaleType() == ScaleType.MATRIX) { mLastBitmapHeight = bitmapWithMetadata.bitmap.getHeight(); mLastBitmapWidth = bitmapWithMetadata.bitmap.getWidth(); mLastRotationDegrees = bitmapWithMetadata.rotationDegrees; updateDrawMatrix(); } else { setRotation(bitmapWithMetadata.rotationDegrees); } setAlpha(0f); setImageBitmap(bitmapWithMetadata.bitmap); animate().alpha(1f); } protected void cancelLoadingAndClear() { synchronized (mFetchLock) { mFetchDevice = null; mFetchObjectInfo = null; mFetchResult = null; } animate().cancel(); setImageResource(android.R.color.transparent); } @Override public void onDetachedFromWindow() { cancelLoadingAndClear(); super.onDetachedFromWindow(); } private static class FetchImageHandler extends Handler { public FetchImageHandler(Looper l) { super(l); } public static FetchImageHandler createOnNewThread() { HandlerThread t = new HandlerThread("MtpImageView Fetch"); t.start(); return new FetchImageHandler(t.getLooper()); } @Override public void handleMessage(Message msg) { @SuppressWarnings("unchecked") MtpImageView parent = ((WeakReference<MtpImageView>) msg.obj).get(); if (parent == null) return; MtpObjectInfo objectInfo; MtpDevice device; synchronized (parent.mFetchLock) { parent.mFetchPending = false; device = parent.mFetchDevice; objectInfo = parent.mFetchObjectInfo; } if (device == null) return; Object result = parent.fetchMtpImageDataFromDevice(device, objectInfo); if (result == null) return; synchronized (parent.mFetchLock) { if (parent.mFetchObjectInfo != objectInfo) return; parent.mFetchResult = result; parent.mFetchDevice = null; parent.mFetchObjectInfo = null; sFetchCompleteHandler.sendMessage( sFetchCompleteHandler.obtainMessage(0, parent.mWeakReference)); } } } private static class ShowImageHandler extends Handler { @Override public void handleMessage(Message msg) { @SuppressWarnings("unchecked") MtpImageView parent = ((WeakReference<MtpImageView>) msg.obj).get(); if (parent == null) return; Object result; synchronized (parent.mFetchLock) { result = parent.mFetchResult; } if (result == null) return; parent.onMtpImageDataFetchedFromDevice(result); } } }