1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; 4 import static android.os.Build.VERSION_CODES.M; 5 import static android.os.Build.VERSION_CODES.N_MR1; 6 import static android.os.Build.VERSION_CODES.O; 7 import static android.os.Build.VERSION_CODES.P; 8 import static android.os.Build.VERSION_CODES.Q; 9 import static android.os.Build.VERSION_CODES.R; 10 import static android.os.Build.VERSION_CODES.S; 11 12 import android.graphics.Bitmap; 13 import android.graphics.Canvas; 14 import android.graphics.ColorFilter; 15 import android.graphics.Matrix; 16 import android.graphics.Paint; 17 import android.graphics.Path; 18 import android.graphics.Rect; 19 import android.graphics.RectF; 20 import com.google.common.base.Preconditions; 21 import java.util.ArrayList; 22 import java.util.List; 23 import org.robolectric.annotation.Implementation; 24 import org.robolectric.annotation.Implements; 25 import org.robolectric.annotation.RealObject; 26 import org.robolectric.annotation.ReflectorObject; 27 import org.robolectric.annotation.Resetter; 28 import org.robolectric.res.android.NativeObjRegistry; 29 import org.robolectric.shadow.api.Shadow; 30 import org.robolectric.util.ReflectionHelpers; 31 import org.robolectric.util.reflector.Direct; 32 import org.robolectric.util.reflector.ForType; 33 34 /** 35 * Broken. This implementation is very specific to the application for which it was developed. Todo: 36 * Reimplement. Consider using the same strategy of collecting a history of draw events and 37 * providing methods for writing queries based on type, number, and order of events. 38 */ 39 @SuppressWarnings({"UnusedDeclaration"}) 40 @Implements(value = Canvas.class, isInAndroidSdk = false) 41 public class ShadowLegacyCanvas extends ShadowCanvas { 42 private static final NativeObjRegistry<NativeCanvas> nativeObjectRegistry = 43 new NativeObjRegistry<>(NativeCanvas.class); 44 45 @RealObject protected Canvas realCanvas; 46 @ReflectorObject protected CanvasReflector canvasReflector; 47 48 private final List<RoundRectPaintHistoryEvent> roundRectPaintEvents = new ArrayList<>(); 49 private List<PathPaintHistoryEvent> pathPaintEvents = new ArrayList<>(); 50 private List<CirclePaintHistoryEvent> circlePaintEvents = new ArrayList<>(); 51 private List<ArcPaintHistoryEvent> arcPaintEvents = new ArrayList<>(); 52 private List<RectPaintHistoryEvent> rectPaintEvents = new ArrayList<>(); 53 private List<LinePaintHistoryEvent> linePaintEvents = new ArrayList<>(); 54 private List<OvalPaintHistoryEvent> ovalPaintEvents = new ArrayList<>(); 55 private List<TextHistoryEvent> drawnTextEventHistory = new ArrayList<>(); 56 private Paint drawnPaint; 57 private Bitmap targetBitmap = ReflectionHelpers.callConstructor(Bitmap.class); 58 private float translateX; 59 private float translateY; 60 private float scaleX = 1; 61 private float scaleY = 1; 62 private int height; 63 private int width; 64 65 @Implementation __constructor__(Bitmap bitmap)66 protected void __constructor__(Bitmap bitmap) { 67 canvasReflector.__constructor__(bitmap); 68 this.targetBitmap = bitmap; 69 } 70 getNativeId()71 private long getNativeId() { 72 return realCanvas.getNativeCanvasWrapper(); 73 } 74 getNativeCanvas()75 private NativeCanvas getNativeCanvas() { 76 return nativeObjectRegistry.getNativeObject(getNativeId()); 77 } 78 79 @Override appendDescription(String s)80 public void appendDescription(String s) { 81 ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap); 82 shadowBitmap.appendDescription(s); 83 } 84 85 @Override getDescription()86 public String getDescription() { 87 ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap); 88 return shadowBitmap.getDescription(); 89 } 90 91 @Implementation setBitmap(Bitmap bitmap)92 protected void setBitmap(Bitmap bitmap) { 93 targetBitmap = bitmap; 94 } 95 96 @Implementation drawText(String text, float x, float y, Paint paint)97 protected void drawText(String text, float x, float y, Paint paint) { 98 drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, text)); 99 } 100 101 @Implementation drawText(CharSequence text, int start, int end, float x, float y, Paint paint)102 protected void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { 103 drawnTextEventHistory.add( 104 new TextHistoryEvent(x, y, paint, text.subSequence(start, end).toString())); 105 } 106 107 @Implementation drawText(char[] text, int index, int count, float x, float y, Paint paint)108 protected void drawText(char[] text, int index, int count, float x, float y, Paint paint) { 109 drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, new String(text, index, count))); 110 } 111 112 @Implementation drawText(String text, int start, int end, float x, float y, Paint paint)113 protected void drawText(String text, int start, int end, float x, float y, Paint paint) { 114 drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, text.substring(start, end))); 115 } 116 117 @Implementation translate(float x, float y)118 protected void translate(float x, float y) { 119 this.translateX = x; 120 this.translateY = y; 121 } 122 123 @Implementation scale(float sx, float sy)124 protected void scale(float sx, float sy) { 125 this.scaleX = sx; 126 this.scaleY = sy; 127 } 128 129 @Implementation scale(float sx, float sy, float px, float py)130 protected void scale(float sx, float sy, float px, float py) { 131 this.scaleX = sx; 132 this.scaleY = sy; 133 } 134 135 @Implementation drawPaint(Paint paint)136 protected void drawPaint(Paint paint) { 137 drawnPaint = paint; 138 } 139 140 @Implementation drawColor(int color)141 protected void drawColor(int color) { 142 appendDescription("draw color " + color); 143 } 144 145 @Implementation drawBitmap(Bitmap bitmap, float left, float top, Paint paint)146 protected void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { 147 describeBitmap(bitmap, paint); 148 149 int x = (int) (left + translateX); 150 int y = (int) (top + translateY); 151 if (x != 0 || y != 0) { 152 appendDescription(" at (" + x + "," + y + ")"); 153 } 154 155 if (scaleX != 1 && scaleY != 1) { 156 appendDescription(" scaled by (" + scaleX + "," + scaleY + ")"); 157 } 158 159 if (bitmap != null && targetBitmap != null) { 160 ShadowLegacyBitmap shadowTargetBitmap = Shadow.extract(targetBitmap); 161 shadowTargetBitmap.drawBitmap(bitmap, (int) left, (int) top); 162 } 163 } 164 165 @Implementation drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)166 protected void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { 167 describeBitmap(bitmap, paint); 168 169 StringBuilder descriptionBuilder = new StringBuilder(); 170 if (dst != null) { 171 descriptionBuilder 172 .append(" at (") 173 .append(dst.left) 174 .append(",") 175 .append(dst.top) 176 .append(") with height=") 177 .append(dst.height()) 178 .append(" and width=") 179 .append(dst.width()); 180 } 181 182 if (src != null) { 183 descriptionBuilder.append(" taken from ").append(src.toString()); 184 } 185 appendDescription(descriptionBuilder.toString()); 186 } 187 188 @Implementation drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)189 protected void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { 190 describeBitmap(bitmap, paint); 191 192 StringBuilder descriptionBuilder = new StringBuilder(); 193 if (dst != null) { 194 descriptionBuilder 195 .append(" at (") 196 .append(dst.left) 197 .append(",") 198 .append(dst.top) 199 .append(") with height=") 200 .append(dst.height()) 201 .append(" and width=") 202 .append(dst.width()); 203 } 204 205 if (src != null) { 206 descriptionBuilder.append(" taken from ").append(src.toString()); 207 } 208 appendDescription(descriptionBuilder.toString()); 209 } 210 211 @Implementation drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)212 protected void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { 213 describeBitmap(bitmap, paint); 214 215 ShadowMatrix shadowMatrix = Shadow.extract(matrix); 216 appendDescription(" transformed by " + shadowMatrix.getDescription()); 217 } 218 219 @Implementation drawPath(Path path, Paint paint)220 protected void drawPath(Path path, Paint paint) { 221 pathPaintEvents.add(new PathPaintHistoryEvent(new Path(path), new Paint(paint))); 222 223 separateLines(); 224 ShadowPath shadowPath = Shadow.extract(path); 225 appendDescription("Path " + shadowPath.getPoints().toString()); 226 } 227 228 @Implementation drawCircle(float cx, float cy, float radius, Paint paint)229 protected void drawCircle(float cx, float cy, float radius, Paint paint) { 230 circlePaintEvents.add(new CirclePaintHistoryEvent(cx, cy, radius, paint)); 231 } 232 233 @Implementation drawArc( RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)234 protected void drawArc( 235 RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) { 236 arcPaintEvents.add(new ArcPaintHistoryEvent(oval, startAngle, sweepAngle, useCenter, paint)); 237 } 238 239 @Implementation drawRect(float left, float top, float right, float bottom, Paint paint)240 protected void drawRect(float left, float top, float right, float bottom, Paint paint) { 241 rectPaintEvents.add(new RectPaintHistoryEvent(left, top, right, bottom, paint)); 242 243 if (targetBitmap != null) { 244 ShadowLegacyBitmap shadowTargetBitmap = Shadow.extract(targetBitmap); 245 shadowTargetBitmap.drawRect(new RectF(left, top, right, bottom), paint); 246 } 247 } 248 249 @Implementation drawRect(Rect r, Paint paint)250 protected void drawRect(Rect r, Paint paint) { 251 rectPaintEvents.add(new RectPaintHistoryEvent(r.left, r.top, r.right, r.bottom, paint)); 252 253 if (targetBitmap != null) { 254 ShadowLegacyBitmap shadowTargetBitmap = Shadow.extract(targetBitmap); 255 shadowTargetBitmap.drawRect(r, paint); 256 } 257 } 258 259 @Implementation drawRoundRect(RectF rect, float rx, float ry, Paint paint)260 protected void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { 261 roundRectPaintEvents.add( 262 new RoundRectPaintHistoryEvent( 263 rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint)); 264 } 265 266 @Implementation drawLine(float startX, float startY, float stopX, float stopY, Paint paint)267 protected void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { 268 linePaintEvents.add(new LinePaintHistoryEvent(startX, startY, stopX, stopY, paint)); 269 } 270 271 @Implementation drawOval(RectF oval, Paint paint)272 protected void drawOval(RectF oval, Paint paint) { 273 ovalPaintEvents.add(new OvalPaintHistoryEvent(oval, paint)); 274 } 275 describeBitmap(Bitmap bitmap, Paint paint)276 private void describeBitmap(Bitmap bitmap, Paint paint) { 277 separateLines(); 278 279 ShadowBitmap shadowBitmap = Shadow.extract(bitmap); 280 appendDescription(shadowBitmap.getDescription()); 281 282 if (paint != null) { 283 ColorFilter colorFilter = paint.getColorFilter(); 284 if (colorFilter != null) { 285 appendDescription(" with " + colorFilter.getClass().getSimpleName()); 286 } 287 } 288 } 289 separateLines()290 private void separateLines() { 291 if (getDescription().length() != 0) { 292 appendDescription("\n"); 293 } 294 } 295 296 @Override getPathPaintHistoryCount()297 public int getPathPaintHistoryCount() { 298 return pathPaintEvents.size(); 299 } 300 301 @Override getCirclePaintHistoryCount()302 public int getCirclePaintHistoryCount() { 303 return circlePaintEvents.size(); 304 } 305 306 @Override getArcPaintHistoryCount()307 public int getArcPaintHistoryCount() { 308 return arcPaintEvents.size(); 309 } 310 311 @Override hasDrawnPath()312 public boolean hasDrawnPath() { 313 return getPathPaintHistoryCount() > 0; 314 } 315 316 @Override hasDrawnCircle()317 public boolean hasDrawnCircle() { 318 return circlePaintEvents.size() > 0; 319 } 320 321 @Override getDrawnPathPaint(int i)322 public Paint getDrawnPathPaint(int i) { 323 return pathPaintEvents.get(i).pathPaint; 324 } 325 326 @Override getDrawnPath(int i)327 public Path getDrawnPath(int i) { 328 return pathPaintEvents.get(i).drawnPath; 329 } 330 331 @Override getDrawnCircle(int i)332 public CirclePaintHistoryEvent getDrawnCircle(int i) { 333 return circlePaintEvents.get(i); 334 } 335 336 @Override getDrawnArc(int i)337 public ArcPaintHistoryEvent getDrawnArc(int i) { 338 return arcPaintEvents.get(i); 339 } 340 341 @Override resetCanvasHistory()342 public void resetCanvasHistory() { 343 drawnTextEventHistory.clear(); 344 pathPaintEvents.clear(); 345 circlePaintEvents.clear(); 346 rectPaintEvents.clear(); 347 roundRectPaintEvents.clear(); 348 linePaintEvents.clear(); 349 ovalPaintEvents.clear(); 350 ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap); 351 shadowBitmap.setDescription(""); 352 } 353 354 @Override getDrawnPaint()355 public Paint getDrawnPaint() { 356 return drawnPaint; 357 } 358 359 @Override setHeight(int height)360 public void setHeight(int height) { 361 this.height = height; 362 } 363 364 @Override setWidth(int width)365 public void setWidth(int width) { 366 this.width = width; 367 } 368 369 @Implementation getWidth()370 protected int getWidth() { 371 if (width == 0) { 372 return targetBitmap.getWidth(); 373 } 374 return width; 375 } 376 377 @Implementation getHeight()378 protected int getHeight() { 379 if (height == 0) { 380 return targetBitmap.getHeight(); 381 } 382 return height; 383 } 384 385 @Implementation getClipBounds(Rect bounds)386 protected boolean getClipBounds(Rect bounds) { 387 Preconditions.checkNotNull(bounds); 388 if (targetBitmap == null) { 389 return false; 390 } 391 bounds.set(0, 0, targetBitmap.getWidth(), targetBitmap.getHeight()); 392 return !bounds.isEmpty(); 393 } 394 395 @Override getDrawnTextEvent(int i)396 public TextHistoryEvent getDrawnTextEvent(int i) { 397 return drawnTextEventHistory.get(i); 398 } 399 400 @Override getTextHistoryCount()401 public int getTextHistoryCount() { 402 return drawnTextEventHistory.size(); 403 } 404 405 @Override getDrawnRect(int i)406 public RectPaintHistoryEvent getDrawnRect(int i) { 407 return rectPaintEvents.get(i); 408 } 409 410 @Override getLastDrawnRect()411 public RectPaintHistoryEvent getLastDrawnRect() { 412 return rectPaintEvents.get(rectPaintEvents.size() - 1); 413 } 414 415 @Override getRectPaintHistoryCount()416 public int getRectPaintHistoryCount() { 417 return rectPaintEvents.size(); 418 } 419 420 @Override getDrawnRoundRect(int i)421 public RoundRectPaintHistoryEvent getDrawnRoundRect(int i) { 422 return roundRectPaintEvents.get(i); 423 } 424 425 @Override getLastDrawnRoundRect()426 public RoundRectPaintHistoryEvent getLastDrawnRoundRect() { 427 return roundRectPaintEvents.get(roundRectPaintEvents.size() - 1); 428 } 429 430 @Override getRoundRectPaintHistoryCount()431 public int getRoundRectPaintHistoryCount() { 432 return roundRectPaintEvents.size(); 433 } 434 435 @Override getDrawnLine(int i)436 public LinePaintHistoryEvent getDrawnLine(int i) { 437 return linePaintEvents.get(i); 438 } 439 440 @Override getLinePaintHistoryCount()441 public int getLinePaintHistoryCount() { 442 return linePaintEvents.size(); 443 } 444 445 @Override getOvalPaintHistoryCount()446 public int getOvalPaintHistoryCount() { 447 return ovalPaintEvents.size(); 448 } 449 450 @Override getDrawnOval(int i)451 public OvalPaintHistoryEvent getDrawnOval(int i) { 452 return ovalPaintEvents.get(i); 453 } 454 455 @Implementation(maxSdk = N_MR1) save()456 protected int save() { 457 return getNativeCanvas().save(); 458 } 459 460 @Implementation(maxSdk = N_MR1) restore()461 protected void restore() { 462 getNativeCanvas().restore(); 463 } 464 465 @Implementation(maxSdk = N_MR1) getSaveCount()466 protected int getSaveCount() { 467 return getNativeCanvas().getSaveCount(); 468 } 469 470 @Implementation(maxSdk = N_MR1) restoreToCount(int saveCount)471 protected void restoreToCount(int saveCount) { 472 getNativeCanvas().restoreToCount(saveCount); 473 } 474 475 @Implementation release()476 protected void release() { 477 nativeObjectRegistry.unregister(getNativeId()); 478 canvasReflector.release(); 479 } 480 481 @Implementation(maxSdk = LOLLIPOP_MR1) initRaster(long bitmapHandle)482 protected static long initRaster(long bitmapHandle) { 483 return nativeObjectRegistry.register(new NativeCanvas()); 484 } 485 486 @Implementation(minSdk = M, maxSdk = N_MR1) initRaster(Bitmap bitmap)487 protected static long initRaster(Bitmap bitmap) { 488 return nativeObjectRegistry.register(new NativeCanvas()); 489 } 490 491 @Implementation(minSdk = O, maxSdk = P) nInitRaster(Bitmap bitmap)492 protected static long nInitRaster(Bitmap bitmap) { 493 return nativeObjectRegistry.register(new NativeCanvas()); 494 } 495 496 @Implementation(minSdk = Q) nInitRaster(long bitmapHandle)497 protected static long nInitRaster(long bitmapHandle) { 498 return nativeObjectRegistry.register(new NativeCanvas()); 499 } 500 501 @Implementation(minSdk = O) nGetSaveCount(long canvasHandle)502 protected static int nGetSaveCount(long canvasHandle) { 503 return nativeObjectRegistry.getNativeObject(canvasHandle).getSaveCount(); 504 } 505 506 @Implementation(minSdk = O) nSave(long canvasHandle, int saveFlags)507 protected static int nSave(long canvasHandle, int saveFlags) { 508 return nativeObjectRegistry.getNativeObject(canvasHandle).save(); 509 } 510 511 @Implementation(maxSdk = N_MR1) native_saveLayer( long nativeCanvas, float l, float t, float r, float b, long nativePaint, int layerFlags)512 protected static int native_saveLayer( 513 long nativeCanvas, float l, float t, float r, float b, long nativePaint, int layerFlags) { 514 return nativeObjectRegistry.getNativeObject(nativeCanvas).save(); 515 } 516 517 @Implementation(minSdk = O, maxSdk = R) nSaveLayer( long nativeCanvas, float l, float t, float r, float b, long nativePaint, int layerFlags)518 protected static int nSaveLayer( 519 long nativeCanvas, float l, float t, float r, float b, long nativePaint, int layerFlags) { 520 return nativeObjectRegistry.getNativeObject(nativeCanvas).save(); 521 } 522 523 @Implementation(minSdk = S) nSaveLayer( long nativeCanvas, float l, float t, float r, float b, long nativePaint)524 protected static int nSaveLayer( 525 long nativeCanvas, float l, float t, float r, float b, long nativePaint) { 526 return nativeObjectRegistry.getNativeObject(nativeCanvas).save(); 527 } 528 529 @Implementation(maxSdk = N_MR1) native_saveLayerAlpha( long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags)530 protected static int native_saveLayerAlpha( 531 long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) { 532 return nativeObjectRegistry.getNativeObject(nativeCanvas).save(); 533 } 534 535 @Implementation(minSdk = O, maxSdk = R) nSaveLayerAlpha( long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags)536 protected static int nSaveLayerAlpha( 537 long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) { 538 return nativeObjectRegistry.getNativeObject(nativeCanvas).save(); 539 } 540 541 @Implementation(minSdk = S) nSaveLayerAlpha( long nativeCanvas, float l, float t, float r, float b, int alpha)542 protected static int nSaveLayerAlpha( 543 long nativeCanvas, float l, float t, float r, float b, int alpha) { 544 return nativeObjectRegistry.getNativeObject(nativeCanvas).save(); 545 } 546 547 @Implementation(minSdk = O) nRestore(long canvasHandle)548 protected static boolean nRestore(long canvasHandle) { 549 return nativeObjectRegistry.getNativeObject(canvasHandle).restore(); 550 } 551 552 @Implementation(minSdk = O) nRestoreToCount(long canvasHandle, int saveCount)553 protected static void nRestoreToCount(long canvasHandle, int saveCount) { 554 nativeObjectRegistry.getNativeObject(canvasHandle).restoreToCount(saveCount); 555 } 556 557 private static class PathPaintHistoryEvent { 558 private final Path drawnPath; 559 private final Paint pathPaint; 560 PathPaintHistoryEvent(Path drawnPath, Paint pathPaint)561 PathPaintHistoryEvent(Path drawnPath, Paint pathPaint) { 562 this.drawnPath = drawnPath; 563 this.pathPaint = pathPaint; 564 } 565 } 566 567 @Resetter reset()568 public static void reset() { 569 nativeObjectRegistry.clear(); 570 } 571 572 @SuppressWarnings("MemberName") 573 @ForType(Canvas.class) 574 private interface CanvasReflector { 575 @Direct __constructor__(Bitmap bitmap)576 void __constructor__(Bitmap bitmap); 577 578 @Direct release()579 void release(); 580 } 581 582 private static class NativeCanvas { 583 private int saveCount = 1; 584 save()585 int save() { 586 return saveCount++; 587 } 588 restore()589 boolean restore() { 590 if (saveCount > 1) { 591 saveCount--; 592 return true; 593 } else { 594 return false; 595 } 596 } 597 getSaveCount()598 int getSaveCount() { 599 return saveCount; 600 } 601 restoreToCount(int saveCount)602 void restoreToCount(int saveCount) { 603 if (saveCount > 0) { 604 this.saveCount = saveCount; 605 } 606 } 607 } 608 } 609