blob: 6b164b8e6838753bb79211f24478c6c8532bed0d [file] [log] [blame]
package org.robolectric.shadows;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.robolectric.shadow.api.Shadow.directlyOn;
import static org.robolectric.shadows.ImageUtil.getImageSizeFromStream;
import android.content.res.AssetManager.AssetInputStream;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.util.TypedValue;
import java.io.ByteArrayInputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.Join;
import org.robolectric.util.NamedStream;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
@SuppressWarnings({"UnusedDeclaration"})
@Implements(BitmapFactory.class)
public class ShadowBitmapFactory {
private static Map<String, Point> widthAndHeightMap = new HashMap<>();
@Implementation
protected static Bitmap decodeResourceStream(
Resources res, TypedValue value, InputStream is, Rect pad, BitmapFactory.Options opts) {
Bitmap bitmap = directlyOn(BitmapFactory.class, "decodeResourceStream",
ClassParameter.from(Resources.class, res),
ClassParameter.from(TypedValue.class, value),
ClassParameter.from(InputStream.class, is),
ClassParameter.from(Rect.class, pad),
ClassParameter.from(BitmapFactory.Options.class, opts));
if (value != null && value.string != null && value.string.toString().contains(".9.")) {
// todo: better support for nine-patches
ReflectionHelpers.callInstanceMethod(bitmap, "setNinePatchChunk", ClassParameter.from(byte[].class, new byte[0]));
}
return bitmap;
}
@Implementation
protected static Bitmap decodeResource(Resources res, int id, BitmapFactory.Options options) {
if (id == 0) {
return null;
}
final TypedValue value = new TypedValue();
InputStream is = res.openRawResource(id, value);
Point imageSizeFromStream = getImageSizeFromStream(is);
Bitmap bitmap = create("resource:" + res.getResourceName(id), options, imageSizeFromStream);
ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.createdFromResId = id;
return bitmap;
}
@Implementation
protected static Bitmap decodeFile(String pathName) {
return decodeFile(pathName, null);
}
@Implementation
protected static Bitmap decodeFile(String pathName, BitmapFactory.Options options) {
Bitmap bitmap = create("file:" + pathName, options);
ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.createdFromPath = pathName;
return bitmap;
}
@SuppressWarnings("ObjectToString")
@Implementation
protected static Bitmap decodeFileDescriptor(
FileDescriptor fd, Rect outPadding, BitmapFactory.Options opts) {
Bitmap bitmap = create("fd:" + fd, opts);
ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.createdFromFileDescriptor = fd;
return bitmap;
}
@Implementation
protected static Bitmap decodeStream(InputStream is) {
return decodeStream(is, null, null);
}
@Implementation
protected static Bitmap decodeStream(
InputStream is, Rect outPadding, BitmapFactory.Options opts) {
byte[] ninePatchChunk = null;
if (is instanceof AssetInputStream) {
ShadowAssetInputStream sais = Shadow.extract(is);
if (sais.isNinePatch()) {
ninePatchChunk = new byte[0];
}
if (sais.getDelegate() != null) {
is = sais.getDelegate();
}
}
try {
if (is != null) {
is.reset();
}
} catch (IOException e) {
// ignore
}
String name = (is instanceof NamedStream)
? is.toString().replace("stream for ", "")
: null;
Point imageSize = (is instanceof NamedStream) ? null : getImageSizeFromStream(is);
Bitmap bitmap = create(name, opts, imageSize);
bitmap.setNinePatchChunk(ninePatchChunk);
ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.createdFromStream = is;
return bitmap;
}
@Implementation
protected static Bitmap decodeByteArray(byte[] data, int offset, int length) {
Bitmap bitmap = decodeByteArray(data, offset, length, new BitmapFactory.Options());
ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.createdFromBytes = data;
return bitmap;
}
@Implementation
protected static Bitmap decodeByteArray(
byte[] data, int offset, int length, BitmapFactory.Options opts) {
String desc = new String(data, UTF_8);
if (offset != 0 || length != data.length) {
desc += " bytes " + offset + ".." + length;
}
Point imageSize = getImageSizeFromStream(new ByteArrayInputStream(data, offset, length));
return create(desc, opts, imageSize);
}
static Bitmap create(String name) {
return create(name, null);
}
public static Bitmap create(String name, BitmapFactory.Options options) {
return create(name, options, null);
}
public static Bitmap create(final String name, final BitmapFactory.Options options, final Point widthAndHeight) {
Bitmap bitmap = Shadow.newInstanceOf(Bitmap.class);
ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.appendDescription(name == null ? "Bitmap" : "Bitmap for " + name);
Bitmap.Config config;
if (options != null && options.inPreferredConfig != null) {
config = options.inPreferredConfig;
} else {
config = Bitmap.Config.ARGB_8888;
}
shadowBitmap.setConfig(config);
String optionsString = stringify(options);
if (!optionsString.isEmpty()) {
shadowBitmap.appendDescription(" with options ");
shadowBitmap.appendDescription(optionsString);
}
Point p = new Point(selectWidthAndHeight(name, widthAndHeight));
if (options != null && options.inSampleSize > 1) {
p.x = p.x / options.inSampleSize;
p.y = p.y / options.inSampleSize;
p.x = p.x == 0 ? 1 : p.x;
p.y = p.y == 0 ? 1 : p.y;
}
shadowBitmap.setWidth(p.x);
shadowBitmap.setHeight(p.y);
shadowBitmap.setPixels(new int[p.x * p.y], 0, 0, 0, 0, p.x, p.y);
if (options != null) {
options.outWidth = p.x;
options.outHeight = p.y;
}
return bitmap;
}
public static void provideWidthAndHeightHints(Uri uri, int width, int height) {
widthAndHeightMap.put(uri.toString(), new Point(width, height));
}
public static void provideWidthAndHeightHints(int resourceId, int width, int height) {
widthAndHeightMap.put("resource:" + RuntimeEnvironment.application.getResources().getResourceName(resourceId), new Point(width, height));
}
public static void provideWidthAndHeightHints(String file, int width, int height) {
widthAndHeightMap.put("file:" + file, new Point(width, height));
}
@SuppressWarnings("ObjectToString")
public static void provideWidthAndHeightHints(FileDescriptor fd, int width, int height) {
widthAndHeightMap.put("fd:" + fd, new Point(width, height));
}
private static String stringify(BitmapFactory.Options options) {
if (options == null) return "";
List<String> opts = new ArrayList<>();
if (options.inJustDecodeBounds) opts.add("inJustDecodeBounds");
if (options.inSampleSize > 1) opts.add("inSampleSize=" + options.inSampleSize);
return Join.join(", ", opts);
}
@Resetter
public static void reset() {
widthAndHeightMap.clear();
}
private static Point selectWidthAndHeight(final String name, final Point widthAndHeight) {
final Point widthAndHeightFromMap = widthAndHeightMap.get(name);
if (widthAndHeightFromMap != null) {
return widthAndHeightFromMap;
}
if (widthAndHeight != null) {
return widthAndHeight;
}
return new Point(100, 100);
}
}