1 package org.robolectric.shadows; 2 3 import android.graphics.Bitmap; 4 import android.graphics.Canvas; 5 import android.graphics.ColorFilter; 6 import android.graphics.ColorMatrix; 7 import android.graphics.ColorMatrixColorFilter; 8 import android.graphics.Matrix; 9 import android.graphics.Paint; 10 import android.graphics.Path; 11 import android.graphics.Rect; 12 import android.graphics.RectF; 13 import java.util.ArrayList; 14 import java.util.List; 15 import org.robolectric.annotation.Implementation; 16 import org.robolectric.annotation.Implements; 17 import org.robolectric.shadow.api.Shadow; 18 import org.robolectric.util.Join; 19 import org.robolectric.util.ReflectionHelpers; 20 21 /** 22 * Broken. This implementation is very specific to the application for which it was developed. 23 * Todo: Reimplement. Consider using the same strategy of collecting a history of draw events 24 * and providing methods for writing queries based on type, number, and order of events. 25 */ 26 @SuppressWarnings({"UnusedDeclaration"}) 27 @Implements(Canvas.class) 28 public class ShadowCanvas { 29 private List<PathPaintHistoryEvent> pathPaintEvents = new ArrayList<>(); 30 private List<CirclePaintHistoryEvent> circlePaintEvents = new ArrayList<>(); 31 private List<ArcPaintHistoryEvent> arcPaintEvents = new ArrayList<>(); 32 private List<RectPaintHistoryEvent> rectPaintEvents = new ArrayList<>(); 33 private List<LinePaintHistoryEvent> linePaintEvents = new ArrayList<>(); 34 private List<OvalPaintHistoryEvent> ovalPaintEvents = new ArrayList<>(); 35 private List<TextHistoryEvent> drawnTextEventHistory = new ArrayList<>(); 36 private Paint drawnPaint; 37 private Bitmap targetBitmap = ReflectionHelpers.callConstructor(Bitmap.class); 38 private float translateX; 39 private float translateY; 40 private float scaleX = 1; 41 private float scaleY = 1; 42 private int height; 43 private int width; 44 45 /** 46 * Returns a textual representation of the appearance of the object. 47 * 48 * @param canvas the canvas to visualize 49 * @return The textual representation of the appearance of the object. 50 */ visualize(Canvas canvas)51 public static String visualize(Canvas canvas) { 52 ShadowCanvas shadowCanvas = Shadow.extract(canvas); 53 return shadowCanvas.getDescription(); 54 } 55 56 @Implementation __constructor__(Bitmap bitmap)57 protected void __constructor__(Bitmap bitmap) { 58 this.targetBitmap = bitmap; 59 } 60 appendDescription(String s)61 public void appendDescription(String s) { 62 ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap); 63 shadowBitmap.appendDescription(s); 64 } 65 getDescription()66 public String getDescription() { 67 ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap); 68 return shadowBitmap.getDescription(); 69 } 70 71 @Implementation setBitmap(Bitmap bitmap)72 protected void setBitmap(Bitmap bitmap) { 73 targetBitmap = bitmap; 74 } 75 76 @Implementation drawText(String text, float x, float y, Paint paint)77 protected void drawText(String text, float x, float y, Paint paint) { 78 drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, text)); 79 } 80 81 @Implementation drawText(CharSequence text, int start, int end, float x, float y, Paint paint)82 protected void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { 83 drawnTextEventHistory.add( 84 new TextHistoryEvent(x, y, paint, text.subSequence(start, end).toString())); 85 } 86 87 @Implementation drawText(char[] text, int index, int count, float x, float y, Paint paint)88 protected void drawText(char[] text, int index, int count, float x, float y, Paint paint) { 89 drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, new String(text, index, count))); 90 } 91 92 @Implementation drawText(String text, int start, int end, float x, float y, Paint paint)93 protected void drawText(String text, int start, int end, float x, float y, Paint paint) { 94 drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, text.substring(start, end))); 95 } 96 97 @Implementation translate(float x, float y)98 protected void translate(float x, float y) { 99 this.translateX = x; 100 this.translateY = y; 101 } 102 103 @Implementation scale(float sx, float sy)104 protected void scale(float sx, float sy) { 105 this.scaleX = sx; 106 this.scaleY = sy; 107 } 108 109 @Implementation scale(float sx, float sy, float px, float py)110 protected void scale(float sx, float sy, float px, float py) { 111 this.scaleX = sx; 112 this.scaleY = sy; 113 } 114 115 @Implementation drawPaint(Paint paint)116 protected void drawPaint(Paint paint) { 117 drawnPaint = paint; 118 } 119 120 @Implementation drawColor(int color)121 protected void drawColor(int color) { 122 appendDescription("draw color " + color); 123 } 124 125 @Implementation drawBitmap(Bitmap bitmap, float left, float top, Paint paint)126 protected void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { 127 describeBitmap(bitmap, paint); 128 129 int x = (int) (left + translateX); 130 int y = (int) (top + translateY); 131 if (x != 0 || y != 0) { 132 appendDescription(" at (" + x + "," + y + ")"); 133 } 134 135 if (scaleX != 1 && scaleY != 1) { 136 appendDescription(" scaled by (" + scaleX + "," + scaleY + ")"); 137 } 138 } 139 140 @Implementation drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)141 protected void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { 142 describeBitmap(bitmap, paint); 143 144 StringBuilder descriptionBuilder = new StringBuilder(); 145 if (dst != null) { 146 descriptionBuilder.append(" at (").append(dst.left).append(",").append(dst.top) 147 .append(") with height=").append(dst.height()).append(" and width=").append(dst.width()); 148 } 149 150 if (src != null) { 151 descriptionBuilder.append( " taken from ").append(src.toString()); 152 } 153 appendDescription(descriptionBuilder.toString()); 154 } 155 156 @Implementation drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)157 protected void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { 158 describeBitmap(bitmap, paint); 159 160 StringBuilder descriptionBuilder = new StringBuilder(); 161 if (dst != null) { 162 descriptionBuilder.append(" at (").append(dst.left).append(",").append(dst.top) 163 .append(") with height=").append(dst.height()).append(" and width=").append(dst.width()); 164 } 165 166 if (src != null) { 167 descriptionBuilder.append( " taken from ").append(src.toString()); 168 } 169 appendDescription(descriptionBuilder.toString()); 170 } 171 172 @Implementation drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)173 protected void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { 174 describeBitmap(bitmap, paint); 175 176 ShadowMatrix shadowMatrix = Shadow.extract(matrix); 177 appendDescription(" transformed by " + shadowMatrix.getDescription()); 178 } 179 180 @Implementation drawPath(Path path, Paint paint)181 protected void drawPath(Path path, Paint paint) { 182 pathPaintEvents.add(new PathPaintHistoryEvent(new Path(path), new Paint(paint))); 183 184 separateLines(); 185 ShadowPath shadowPath = Shadow.extract(path); 186 appendDescription("Path " + shadowPath.getPoints().toString()); 187 } 188 189 @Implementation drawCircle(float cx, float cy, float radius, Paint paint)190 protected void drawCircle(float cx, float cy, float radius, Paint paint) { 191 circlePaintEvents.add(new CirclePaintHistoryEvent(cx, cy, radius, paint)); 192 } 193 194 @Implementation drawArc( RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)195 protected void drawArc( 196 RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) { 197 arcPaintEvents.add(new ArcPaintHistoryEvent(oval, startAngle, sweepAngle, useCenter, paint)); 198 } 199 200 @Implementation drawRect(float left, float top, float right, float bottom, Paint paint)201 protected void drawRect(float left, float top, float right, float bottom, Paint paint) { 202 rectPaintEvents.add(new RectPaintHistoryEvent(left, top, right, bottom, paint)); 203 } 204 205 @Implementation drawLine(float startX, float startY, float stopX, float stopY, Paint paint)206 protected void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { 207 linePaintEvents.add(new LinePaintHistoryEvent(startX, startY, stopX, stopY, paint)); 208 } 209 210 @Implementation drawOval(RectF oval, Paint paint)211 protected void drawOval(RectF oval, Paint paint) { 212 ovalPaintEvents.add(new OvalPaintHistoryEvent(oval, paint)); 213 } 214 215 @Implementation save()216 protected int save() { 217 return 1; 218 } 219 220 @Implementation restore()221 protected void restore() {} 222 describeBitmap(Bitmap bitmap, Paint paint)223 private void describeBitmap(Bitmap bitmap, Paint paint) { 224 separateLines(); 225 226 ShadowBitmap shadowBitmap = Shadow.extract(bitmap); 227 appendDescription(shadowBitmap.getDescription()); 228 229 if (paint != null) { 230 ColorFilter colorFilter = paint.getColorFilter(); 231 if (colorFilter != null) { 232 if (colorFilter instanceof ColorMatrixColorFilter) { 233 ColorMatrixColorFilter colorMatrixColorFilter = (ColorMatrixColorFilter) colorFilter; 234 ShadowColorMatrixColorFilter shadowColorMatrixColorFilter = 235 Shadow.extract(colorMatrixColorFilter); 236 ColorMatrix colorMatrix = shadowColorMatrixColorFilter.getMatrix(); 237 appendDescription(" with ColorMatrixColorFilter<" + formatColorMatric(colorMatrix) + ">"); 238 } else { 239 appendDescription(" with " + colorFilter); 240 } 241 } 242 } 243 } 244 formatColorMatric(ColorMatrix colorMatrix)245 private String formatColorMatric(ColorMatrix colorMatrix) { 246 List<String> floats = new ArrayList<>(); 247 for (float f : colorMatrix.getArray()) { 248 String format = String.format("%.2f", f); 249 format = format.replace(".00", ""); 250 floats.add(format); 251 } 252 return Join.join(",", floats); 253 } 254 separateLines()255 private void separateLines() { 256 if (getDescription().length() != 0) { 257 appendDescription("\n"); 258 } 259 } 260 getPathPaintHistoryCount()261 public int getPathPaintHistoryCount() { 262 return pathPaintEvents.size(); 263 } 264 getCirclePaintHistoryCount()265 public int getCirclePaintHistoryCount() { 266 return circlePaintEvents.size(); 267 } 268 getArcPaintHistoryCount()269 public int getArcPaintHistoryCount() { 270 return arcPaintEvents.size(); 271 } 272 hasDrawnPath()273 public boolean hasDrawnPath() { 274 return getPathPaintHistoryCount() > 0; 275 } 276 hasDrawnCircle()277 public boolean hasDrawnCircle() { 278 return circlePaintEvents.size() > 0; 279 } 280 getDrawnPathPaint(int i)281 public Paint getDrawnPathPaint(int i) { 282 return pathPaintEvents.get(i).pathPaint; 283 } 284 getDrawnPath(int i)285 public Path getDrawnPath(int i) { 286 return pathPaintEvents.get(i).drawnPath; 287 } 288 getDrawnCircle(int i)289 public CirclePaintHistoryEvent getDrawnCircle(int i) { 290 return circlePaintEvents.get(i); 291 } 292 getDrawnArc(int i)293 public ArcPaintHistoryEvent getDrawnArc(int i) { 294 return arcPaintEvents.get(i); 295 } 296 resetCanvasHistory()297 public void resetCanvasHistory() { 298 drawnTextEventHistory.clear(); 299 pathPaintEvents.clear(); 300 circlePaintEvents.clear(); 301 rectPaintEvents.clear(); 302 linePaintEvents.clear(); 303 ovalPaintEvents.clear(); 304 ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap); 305 shadowBitmap.setDescription(""); 306 } 307 getDrawnPaint()308 public Paint getDrawnPaint() { 309 return drawnPaint; 310 } 311 setHeight(int height)312 public void setHeight(int height) { 313 this.height = height; 314 } 315 setWidth(int width)316 public void setWidth(int width) { 317 this.width = width; 318 } 319 320 @Implementation getWidth()321 protected int getWidth() { 322 return width; 323 } 324 325 @Implementation getHeight()326 protected int getHeight() { 327 return height; 328 } 329 getDrawnTextEvent(int i)330 public TextHistoryEvent getDrawnTextEvent(int i) { 331 return drawnTextEventHistory.get(i); 332 } 333 getTextHistoryCount()334 public int getTextHistoryCount() { 335 return drawnTextEventHistory.size(); 336 } 337 getDrawnRect(int i)338 public RectPaintHistoryEvent getDrawnRect(int i) { 339 return rectPaintEvents.get(i); 340 } 341 getLastDrawnRect()342 public RectPaintHistoryEvent getLastDrawnRect() { 343 return rectPaintEvents.get(rectPaintEvents.size() - 1); 344 } 345 getRectPaintHistoryCount()346 public int getRectPaintHistoryCount() { 347 return rectPaintEvents.size(); 348 } 349 getDrawnLine(int i)350 public LinePaintHistoryEvent getDrawnLine(int i) { 351 return linePaintEvents.get(i); 352 } 353 getLinePaintHistoryCount()354 public int getLinePaintHistoryCount() { 355 return linePaintEvents.size(); 356 } 357 getOvalPaintHistoryCount()358 public int getOvalPaintHistoryCount() { 359 return ovalPaintEvents.size(); 360 } 361 getDrawnOval(int i)362 public OvalPaintHistoryEvent getDrawnOval(int i) { 363 return ovalPaintEvents.get(i); 364 } 365 366 public static class LinePaintHistoryEvent { 367 public Paint paint; 368 public float startX; 369 public float startY; 370 public float stopX; 371 public float stopY; 372 LinePaintHistoryEvent( float startX, float startY, float stopX, float stopY, Paint paint)373 private LinePaintHistoryEvent( 374 float startX, float startY, float stopX, float stopY, Paint paint) { 375 this.paint = new Paint(paint); 376 this.paint.setColor(paint.getColor()); 377 this.paint.setStrokeWidth(paint.getStrokeWidth()); 378 this.startX = startX; 379 this.startY = startY; 380 this.stopX = stopX; 381 this.stopY = stopY; 382 } 383 } 384 385 public static class OvalPaintHistoryEvent { 386 public final RectF oval; 387 public final Paint paint; 388 OvalPaintHistoryEvent(RectF oval, Paint paint)389 private OvalPaintHistoryEvent(RectF oval, Paint paint) { 390 this.oval = new RectF(oval); 391 this.paint = new Paint(paint); 392 this.paint.setColor(paint.getColor()); 393 this.paint.setStrokeWidth(paint.getStrokeWidth()); 394 } 395 } 396 397 public static class RectPaintHistoryEvent { 398 public final Paint paint; 399 public final RectF rect; 400 public final float left; 401 public final float top; 402 public final float right; 403 public final float bottom; 404 RectPaintHistoryEvent( float left, float top, float right, float bottom, Paint paint)405 private RectPaintHistoryEvent( 406 float left, float top, float right, float bottom, Paint paint){ 407 this.rect = new RectF(left, top, right, bottom); 408 this.paint = new Paint(paint); 409 this.paint.setColor(paint.getColor()); 410 this.paint.setStrokeWidth(paint.getStrokeWidth()); 411 this.paint.setTextSize(paint.getTextSize()); 412 this.paint.setStyle(paint.getStyle()); 413 this.left = left; 414 this.top = top; 415 this.right = right; 416 this.bottom = bottom; 417 } 418 } 419 420 private static class PathPaintHistoryEvent { 421 private final Path drawnPath; 422 private final Paint pathPaint; 423 PathPaintHistoryEvent(Path drawnPath, Paint pathPaint)424 PathPaintHistoryEvent(Path drawnPath, Paint pathPaint) { 425 this.drawnPath = drawnPath; 426 this.pathPaint = pathPaint; 427 } 428 } 429 430 public static class CirclePaintHistoryEvent { 431 public final float centerX; 432 public final float centerY; 433 public final float radius; 434 public final Paint paint; 435 CirclePaintHistoryEvent(float centerX, float centerY, float radius, Paint paint)436 private CirclePaintHistoryEvent(float centerX, float centerY, float radius, Paint paint) { 437 this.centerX = centerX; 438 this.centerY = centerY; 439 this.radius = radius; 440 this.paint = paint; 441 } 442 } 443 444 public static class ArcPaintHistoryEvent { 445 public final RectF oval; 446 public final float startAngle; 447 public final float sweepAngle; 448 public final boolean useCenter; 449 public final Paint paint; 450 ArcPaintHistoryEvent(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)451 public ArcPaintHistoryEvent(RectF oval, float startAngle, float sweepAngle, boolean useCenter, 452 Paint paint) { 453 this.oval = oval; 454 this.startAngle = startAngle; 455 this.sweepAngle = sweepAngle; 456 this.useCenter = useCenter; 457 this.paint = paint; 458 } 459 } 460 461 public static class TextHistoryEvent { 462 public final float x; 463 public final float y; 464 public final Paint paint; 465 public final String text; 466 TextHistoryEvent(float x, float y, Paint paint, String text)467 private TextHistoryEvent(float x, float y, Paint paint, String text) { 468 this.x = x; 469 this.y = y; 470 this.paint = paint; 471 this.text = text; 472 } 473 } 474 } 475