Initial checkin of AOSP Messaging app.
b/23110861 Change-Id: I9aa980d7569247d6b2ca78f5dcb4502e1eaadb8a
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.messaging.datamodel.media;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import com.android.messaging.datamodel.data.MessagePartData;
|
||||
import com.android.messaging.datamodel.media.PoolableImageCache.ReusableImageResourcePool;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.ImageUtils;
|
||||
import com.android.messaging.util.exif.ExifInterface;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base class that serves an image request for resolving, retrieving and decoding bitmap resources.
|
||||
*
|
||||
* Subclasses may choose to load images from different medium, such as from the file system or
|
||||
* from the local content resolver, by overriding the abstract getInputStreamForResource() method.
|
||||
*/
|
||||
public abstract class ImageRequest<D extends ImageRequestDescriptor>
|
||||
implements MediaRequest<ImageResource> {
|
||||
public static final int UNSPECIFIED_SIZE = MessagePartData.UNSPECIFIED_SIZE;
|
||||
|
||||
protected final Context mContext;
|
||||
protected final D mDescriptor;
|
||||
protected int mOrientation;
|
||||
|
||||
/**
|
||||
* Creates a new image request with the given descriptor.
|
||||
*/
|
||||
public ImageRequest(final Context context, final D descriptor) {
|
||||
mContext = context;
|
||||
mDescriptor = descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a key that uniquely identify the underlying image resource to be loaded (e.g. Uri or
|
||||
* file path).
|
||||
*/
|
||||
@Override
|
||||
public String getKey() {
|
||||
return mDescriptor.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the image request descriptor attached to this request.
|
||||
*/
|
||||
@Override
|
||||
public D getDescriptor() {
|
||||
return mDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequestType() {
|
||||
return MediaRequest.REQUEST_LOAD_MEDIA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows sub classes to specify that they want us to call getBitmapForResource rather than
|
||||
* getInputStreamForResource
|
||||
*/
|
||||
protected boolean hasBitmapObject() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Bitmap getBitmapForResource() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an input stream from which image resource could be loaded.
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
protected abstract InputStream getInputStreamForResource() throws FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Loads the image resource. This method is final; to override the media loading behavior
|
||||
* the subclass should override {@link #loadMediaInternal(List)}
|
||||
*/
|
||||
@Override
|
||||
public final ImageResource loadMediaBlocking(List<MediaRequest<ImageResource>> chainedTask)
|
||||
throws IOException {
|
||||
Assert.isNotMainThread();
|
||||
final ImageResource loadedResource = loadMediaInternal(chainedTask);
|
||||
return postProcessOnBitmapResourceLoaded(loadedResource);
|
||||
}
|
||||
|
||||
protected ImageResource loadMediaInternal(List<MediaRequest<ImageResource>> chainedTask)
|
||||
throws IOException {
|
||||
if (!mDescriptor.isStatic() && isGif()) {
|
||||
final GifImageResource gifImageResource =
|
||||
GifImageResource.createGifImageResource(getKey(), getInputStreamForResource());
|
||||
if (gifImageResource == null) {
|
||||
throw new RuntimeException("Error decoding gif");
|
||||
}
|
||||
return gifImageResource;
|
||||
} else {
|
||||
final Bitmap loadedBitmap = loadBitmapInternal();
|
||||
if (loadedBitmap == null) {
|
||||
throw new RuntimeException("failed decoding bitmap");
|
||||
}
|
||||
return new DecodedImageResource(getKey(), loadedBitmap, mOrientation);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isGif() throws FileNotFoundException {
|
||||
return ImageUtils.isGif(getInputStreamForResource());
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal routine for loading the image. The caller may optionally provide the width
|
||||
* and height of the source image if known so that we don't need to manually decode those.
|
||||
*/
|
||||
protected Bitmap loadBitmapInternal() throws IOException {
|
||||
|
||||
final boolean unknownSize = mDescriptor.sourceWidth == UNSPECIFIED_SIZE ||
|
||||
mDescriptor.sourceHeight == UNSPECIFIED_SIZE;
|
||||
|
||||
// If the ImageRequest has a Bitmap object rather than a stream, there's little to do here
|
||||
if (hasBitmapObject()) {
|
||||
final Bitmap bitmap = getBitmapForResource();
|
||||
if (bitmap != null && unknownSize) {
|
||||
mDescriptor.updateSourceDimensions(bitmap.getWidth(), bitmap.getHeight());
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
mOrientation = ImageUtils.getOrientation(getInputStreamForResource());
|
||||
|
||||
final BitmapFactory.Options options = PoolableImageCache.getBitmapOptionsForPool(
|
||||
false /* scaled */, 0 /* inputDensity */, 0 /* targetDensity */);
|
||||
// First, check dimensions of the bitmap if not already known.
|
||||
if (unknownSize) {
|
||||
final InputStream inputStream = getInputStreamForResource();
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(inputStream, null, options);
|
||||
// This is called when dimensions of image were unknown to allow db update
|
||||
if (ExifInterface.getOrientationParams(mOrientation).invertDimensions) {
|
||||
mDescriptor.updateSourceDimensions(options.outHeight, options.outWidth);
|
||||
} else {
|
||||
mDescriptor.updateSourceDimensions(options.outWidth, options.outHeight);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
} else {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
} else {
|
||||
options.outWidth = mDescriptor.sourceWidth;
|
||||
options.outHeight = mDescriptor.sourceHeight;
|
||||
}
|
||||
|
||||
// Calculate inSampleSize
|
||||
options.inSampleSize = ImageUtils.get().calculateInSampleSize(options,
|
||||
mDescriptor.desiredWidth, mDescriptor.desiredHeight);
|
||||
Assert.isTrue(options.inSampleSize > 0);
|
||||
|
||||
// Reopen the input stream and actually decode the bitmap. The initial
|
||||
// BitmapFactory.decodeStream() reads the header portion of the bitmap stream and leave
|
||||
// the input stream at the last read position. Since this input stream doesn't support
|
||||
// mark() and reset(), the only viable way to reload the input stream is to re-open it.
|
||||
// Alternatively, we could decode the bitmap into a byte array first and act on the byte
|
||||
// array, but that also means the entire bitmap (for example a 10MB image from the gallery)
|
||||
// without downsampling will have to be loaded into memory up front, which we don't want
|
||||
// as it gives a much bigger possibility of OOM when handling big images. Therefore, the
|
||||
// solution here is to close and reopen the bitmap input stream.
|
||||
// For inline images the size is cached in DB and this hit is only taken once per image
|
||||
final InputStream inputStream = getInputStreamForResource();
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
options.inJustDecodeBounds = false;
|
||||
|
||||
// Actually decode the bitmap, optionally using the bitmap pool.
|
||||
final ReusableImageResourcePool bitmapPool = getBitmapPool();
|
||||
if (bitmapPool == null) {
|
||||
return BitmapFactory.decodeStream(inputStream, null, options);
|
||||
} else {
|
||||
final int sampledWidth = (options.outWidth + options.inSampleSize - 1) /
|
||||
options.inSampleSize;
|
||||
final int sampledHeight = (options.outHeight + options.inSampleSize - 1) /
|
||||
options.inSampleSize;
|
||||
return bitmapPool.decodeSampledBitmapFromInputStream(
|
||||
inputStream, options, sampledWidth, sampledHeight);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
} else {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
private ImageResource postProcessOnBitmapResourceLoaded(final ImageResource loadedResource) {
|
||||
if (mDescriptor.cropToCircle && loadedResource instanceof DecodedImageResource) {
|
||||
final int width = mDescriptor.desiredWidth;
|
||||
final int height = mDescriptor.desiredHeight;
|
||||
final Bitmap sourceBitmap = loadedResource.getBitmap();
|
||||
final Bitmap targetBitmap = getBitmapPool().createOrReuseBitmap(width, height);
|
||||
final RectF dest = new RectF(0, 0, width, height);
|
||||
final RectF source = new RectF(0, 0, sourceBitmap.getWidth(), sourceBitmap.getHeight());
|
||||
final int backgroundColor = mDescriptor.circleBackgroundColor;
|
||||
final int strokeColor = mDescriptor.circleStrokeColor;
|
||||
ImageUtils.drawBitmapWithCircleOnCanvas(sourceBitmap, new Canvas(targetBitmap), source,
|
||||
dest, null, backgroundColor == 0 ? false : true /* fillBackground */,
|
||||
backgroundColor, strokeColor);
|
||||
return new DecodedImageResource(getKey(), targetBitmap,
|
||||
loadedResource.getOrientation());
|
||||
}
|
||||
return loadedResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bitmap pool for this image request.
|
||||
*/
|
||||
protected ReusableImageResourcePool getBitmapPool() {
|
||||
return MediaCacheManager.get().getOrCreateBitmapPoolForCache(getCacheId());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MediaCache<ImageResource> getMediaCache() {
|
||||
return (MediaCache<ImageResource>) MediaCacheManager.get().getOrCreateMediaCacheById(
|
||||
getCacheId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache id. Subclasses may override this to use a different cache.
|
||||
*/
|
||||
@Override
|
||||
public int getCacheId() {
|
||||
return BugleMediaCacheManager.DEFAULT_IMAGE_CACHE;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user