1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.KITKAT; 4 5 import android.content.res.Resources; 6 import android.graphics.Bitmap; 7 import android.graphics.BitmapFactory; 8 import android.graphics.NinePatch; 9 import android.graphics.Rect; 10 import android.graphics.drawable.BitmapDrawable; 11 import android.graphics.drawable.Drawable; 12 import android.graphics.drawable.NinePatchDrawable; 13 import android.util.DisplayMetrics; 14 import android.util.TypedValue; 15 import java.io.InputStream; 16 import java.util.ArrayList; 17 import java.util.List; 18 import org.robolectric.annotation.Implementation; 19 import org.robolectric.annotation.Implements; 20 import org.robolectric.annotation.RealObject; 21 import org.robolectric.annotation.Resetter; 22 import org.robolectric.shadow.api.Shadow; 23 import org.robolectric.util.ReflectionHelpers; 24 import org.robolectric.util.ReflectionHelpers.ClassParameter; 25 26 @SuppressWarnings({"UnusedDeclaration"}) 27 @Implements(Drawable.class) 28 public class ShadowDrawable { 29 private static int defaultIntrinsicWidth = -1; 30 private static int defaultIntrinsicHeight = -1; 31 static final List<String> corruptStreamSources = new ArrayList<>(); 32 33 @RealObject Drawable realDrawable; 34 35 int createdFromResId = -1; 36 InputStream createdFromInputStream; 37 38 private int intrinsicWidth = defaultIntrinsicWidth; 39 private int intrinsicHeight = defaultIntrinsicHeight; 40 private int alpha; 41 private boolean wasInvalidated; 42 43 @Implementation 44 protected static Drawable createFromStream(InputStream is, String srcName) { 45 if (corruptStreamSources.contains(srcName)) { 46 return null; 47 } 48 BitmapDrawable drawable = new BitmapDrawable(ReflectionHelpers.callConstructor(Bitmap.class)); 49 ShadowBitmapDrawable shadowBitmapDrawable = Shadow.extract(drawable); 50 shadowBitmapDrawable.createdFromInputStream = is; 51 shadowBitmapDrawable.drawableCreateFromStreamSource = srcName; 52 shadowBitmapDrawable.validate(); // start off not invalidated 53 return drawable; 54 } 55 56 @Implementation // todo: this sucks, it's all just so we can detect 9-patches 57 protected static Drawable createFromResourceStream( 58 Resources res, TypedValue value, InputStream is, String srcName, BitmapFactory.Options opts) { 59 if (is == null) { 60 return null; 61 } 62 Rect pad = new Rect(); 63 if (opts == null) opts = new BitmapFactory.Options(); 64 opts.inScreenDensity = DisplayMetrics.DENSITY_DEFAULT; 65 66 Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts); 67 if (bm != null) { 68 boolean isNinePatch = srcName != null && srcName.contains(".9."); 69 if (isNinePatch) { 70 ReflectionHelpers.callInstanceMethod(bm, "setNinePatchChunk", ClassParameter.from(byte[].class, new byte[0])); 71 } 72 byte[] np = bm.getNinePatchChunk(); 73 if (np == null || !NinePatch.isNinePatchChunk(np)) { 74 np = null; 75 pad = null; 76 } 77 78 if (np != null) { 79 // todo: wrong 80 return new NinePatchDrawable(res, bm, np, pad, srcName); 81 } 82 83 return new BitmapDrawable(res, bm); 84 } 85 return null; 86 } 87 88 @Implementation 89 protected static Drawable createFromPath(String pathName) { 90 BitmapDrawable drawable = new BitmapDrawable(ReflectionHelpers.callConstructor(Bitmap.class)); 91 ShadowBitmapDrawable shadowBitmapDrawable = Shadow.extract(drawable); 92 shadowBitmapDrawable.drawableCreateFromPath = pathName; 93 shadowBitmapDrawable.validate(); // start off not invalidated 94 return drawable; 95 } 96 97 public static Drawable createFromResourceId(int resourceId) { 98 Bitmap bitmap = ReflectionHelpers.callConstructor(Bitmap.class); 99 ShadowBitmap shadowBitmap = Shadow.extract(bitmap); 100 shadowBitmap.createdFromResId = resourceId; 101 BitmapDrawable drawable = new BitmapDrawable(bitmap); 102 ShadowBitmapDrawable shadowBitmapDrawable = Shadow.extract(drawable); 103 shadowBitmapDrawable.validate(); // start off not invalidated 104 shadowBitmapDrawable.createdFromResId = resourceId; 105 return drawable; 106 } 107 108 @Implementation 109 protected int getIntrinsicWidth() { 110 return intrinsicWidth; 111 } 112 113 @Implementation 114 protected int getIntrinsicHeight() { 115 return intrinsicHeight; 116 } 117 118 public static void addCorruptStreamSource(String src) { 119 corruptStreamSources.add(src); 120 } 121 122 @Resetter 123 public static void clearCorruptStreamSources() { 124 corruptStreamSources.clear(); 125 } 126 127 public static void setDefaultIntrinsicWidth(int defaultIntrinsicWidth) { 128 ShadowDrawable.defaultIntrinsicWidth = defaultIntrinsicWidth; 129 } 130 131 public static void setDefaultIntrinsicHeight(int defaultIntrinsicHeight) { 132 ShadowDrawable.defaultIntrinsicHeight = defaultIntrinsicHeight; 133 } 134 135 public void setIntrinsicWidth(int intrinsicWidth) { 136 this.intrinsicWidth = intrinsicWidth; 137 } 138 139 public void setIntrinsicHeight(int intrinsicHeight) { 140 this.intrinsicHeight = intrinsicHeight; 141 } 142 143 public InputStream getInputStream() { 144 return createdFromInputStream; 145 } 146 147 @Implementation 148 protected void setAlpha(int alpha) { 149 this.alpha = alpha; 150 Shadow.directlyOn(realDrawable, Drawable.class).setAlpha(alpha); 151 } 152 153 @Implementation 154 protected void invalidateSelf() { 155 wasInvalidated = true; 156 Shadow.directlyOn(realDrawable, Drawable.class, "invalidateSelf"); 157 } 158 159 @Implementation(minSdk = KITKAT) 160 protected int getAlpha() { 161 return alpha; 162 } 163 164 public int getCreatedFromResId() { 165 return createdFromResId; 166 } 167 168 public boolean wasInvalidated() { 169 return wasInvalidated; 170 } 171 172 public void validate() { 173 wasInvalidated = false; 174 } 175 } 176