1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <gtest/gtest.h>
18#include <VectorDrawable.h>
19
20#include "AnimationContext.h"
21#include "DamageAccumulator.h"
22#include "IContextFactory.h"
23#include "pipeline/skia/SkiaDisplayList.h"
24#include "pipeline/skia/SkiaPipeline.h"
25#include "pipeline/skia/SkiaRecordingCanvas.h"
26#include "renderthread/CanvasContext.h"
27#include "tests/common/TestUtils.h"
28#include "SkiaCanvas.h"
29#include <SkSurface_Base.h>
30#include <SkLiteRecorder.h>
31#include <SkClipStack.h>
32#include "FatalTestCanvas.h"
33#include <string.h>
34
35
36using namespace android;
37using namespace android::uirenderer;
38using namespace android::uirenderer::renderthread;
39using namespace android::uirenderer::skiapipeline;
40
41TEST(RenderNodeDrawable, create) {
42    auto rootNode = TestUtils::createNode(0, 0, 200, 400,
43            [](RenderProperties& props, Canvas& canvas) {
44                canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver);
45            });
46
47    SkLiteDL skLiteDL;
48    SkLiteRecorder canvas;
49    canvas.reset(&skLiteDL, SkIRect::MakeWH(1, 1));
50    canvas.translate(100, 100);
51    RenderNodeDrawable drawable(rootNode.get(), &canvas);
52
53    ASSERT_EQ(drawable.getRenderNode(), rootNode.get());
54    ASSERT_EQ(&drawable.getNodeProperties(), &rootNode->properties());
55    ASSERT_EQ(drawable.getRecordedMatrix(), canvas.getTotalMatrix());
56}
57
58namespace {
59
60static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
61    SkPaint paint;
62    // order put in blue channel, transparent so overlapped content doesn't get rejected
63    paint.setColor(SkColorSetARGB(1, 0, 0, expectedDrawOrder));
64    canvas->drawRect(0, 0, 100, 100, paint);
65}
66
67static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder, float z) {
68    auto node = TestUtils::createSkiaNode(0, 0, 100, 100,
69            [expectedDrawOrder, z](RenderProperties& props, SkiaRecordingCanvas& canvas) {
70        drawOrderedRect(&canvas, expectedDrawOrder);
71        props.setTranslationZ(z);
72    });
73    canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
74}
75
76static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder,
77        std::function<void(RenderProperties& props, SkiaRecordingCanvas& canvas)> setup) {
78    auto node = TestUtils::createSkiaNode(0, 0, 100, 100,
79            [expectedDrawOrder, setup](RenderProperties& props, SkiaRecordingCanvas& canvas) {
80        drawOrderedRect(&canvas, expectedDrawOrder);
81        if (setup) {
82             setup(props, canvas);
83        }
84    });
85    canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
86}
87
88class ZReorderCanvas : public SkCanvas {
89public:
90    ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
91    void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
92        int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
93        EXPECT_EQ(expectedOrder, mDrawCounter++) << "An op was drawn out of order";
94    }
95    int getIndex() { return mDrawCounter; }
96protected:
97    int mDrawCounter = 0;
98};
99
100} // end anonymous namespace
101
102TEST(RenderNodeDrawable, zReorder) {
103
104    auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
105            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
106        canvas.insertReorderBarrier(true);
107        canvas.insertReorderBarrier(false);
108        drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
109        drawOrderedRect(&canvas, 1);
110        canvas.insertReorderBarrier(true);
111        drawOrderedNode(&canvas, 6, 2.0f);
112        drawOrderedRect(&canvas, 3);
113        drawOrderedNode(&canvas, 4, 0.0f);
114        drawOrderedRect(&canvas, 5);
115        drawOrderedNode(&canvas, 2, -2.0f);
116        drawOrderedNode(&canvas, 7, 2.0f);
117        canvas.insertReorderBarrier(false);
118        drawOrderedRect(&canvas, 8);
119        drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
120        canvas.insertReorderBarrier(true); //reorder a node ahead of drawrect op
121        drawOrderedRect(&canvas, 11);
122        drawOrderedNode(&canvas, 10, -1.0f);
123        canvas.insertReorderBarrier(false);
124        canvas.insertReorderBarrier(true); //test with two empty reorder sections
125        canvas.insertReorderBarrier(true);
126        canvas.insertReorderBarrier(false);
127        drawOrderedRect(&canvas, 12);
128    });
129
130    //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
131    ZReorderCanvas canvas(100, 100);
132    RenderNodeDrawable drawable(parent.get(), &canvas, false);
133    canvas.drawDrawable(&drawable);
134    EXPECT_EQ(13, canvas.getIndex());
135}
136
137TEST(RenderNodeDrawable, composeOnLayer)
138{
139    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
140    SkCanvas& canvas = *surface->getCanvas();
141    canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
142    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
143
144    auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
145        [](RenderProperties& props, SkiaRecordingCanvas& recorder) {
146            recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
147        });
148
149    //attach a layer to the render node
150    auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1);
151    auto canvas2 = surfaceLayer->getCanvas();
152    canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
153    rootNode->setLayerSurface(surfaceLayer);
154
155    RenderNodeDrawable drawable1(rootNode.get(), &canvas, false);
156    canvas.drawDrawable(&drawable1);
157    ASSERT_EQ(SK_ColorRED, TestUtils::getColor(surface, 0, 0));
158
159    RenderNodeDrawable drawable2(rootNode.get(), &canvas, true);
160    canvas.drawDrawable(&drawable2);
161    ASSERT_EQ(SK_ColorWHITE, TestUtils::getColor(surface, 0, 0));
162
163    RenderNodeDrawable drawable3(rootNode.get(), &canvas, false);
164    canvas.drawDrawable(&drawable3);
165    ASSERT_EQ(SK_ColorRED, TestUtils::getColor(surface, 0, 0));
166
167    rootNode->setLayerSurface(sk_sp<SkSurface>());
168}
169
170namespace {
171static SkRect getRecorderClipBounds(const SkiaRecordingCanvas& recorder) {
172    SkRect clipBounds;
173    recorder.getClipBounds(&clipBounds);
174    return clipBounds;
175}
176
177static SkMatrix getRecorderMatrix(const SkiaRecordingCanvas& recorder) {
178    SkMatrix matrix;
179    recorder.getMatrix(&matrix);
180    return matrix;
181}
182}
183
184TEST(RenderNodeDrawable, saveLayerClipAndMatrixRestore)
185{
186    auto surface = SkSurface::MakeRasterN32Premul(400, 800);
187    SkCanvas& canvas = *surface->getCanvas();
188    canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
189    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
190
191    auto rootNode = TestUtils::createSkiaNode(0, 0, 400, 800,
192        [](RenderProperties& props, SkiaRecordingCanvas& recorder) {
193            SkPaint layerPaint;
194            ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 800), getRecorderClipBounds(recorder));
195            EXPECT_TRUE(getRecorderMatrix(recorder).isIdentity());
196
197            //note we don't pass SaveFlags::MatrixClip, but matrix and clip will be saved
198            recorder.saveLayer(0, 0, 400, 400, &layerPaint, SaveFlags::ClipToLayer);
199            ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 400), getRecorderClipBounds(recorder));
200            EXPECT_TRUE(getRecorderMatrix(recorder).isIdentity());
201
202            recorder.clipRect(50, 50, 350, 350, SkClipOp::kIntersect);
203            ASSERT_EQ(SkRect::MakeLTRB(50, 50, 350, 350), getRecorderClipBounds(recorder));
204
205            recorder.translate(300.0f, 400.0f);
206            EXPECT_EQ(SkMatrix::MakeTrans(300.0f, 400.0f), getRecorderMatrix(recorder));
207
208            recorder.restore();
209            ASSERT_EQ(SkRect::MakeLTRB(0, 0, 400, 800), getRecorderClipBounds(recorder));
210            EXPECT_TRUE(getRecorderMatrix(recorder).isIdentity());
211
212            SkPaint paint;
213            paint.setAntiAlias(true);
214            paint.setColor(SK_ColorGREEN);
215            recorder.drawRect(0.0f, 400.0f, 400.0f, 800.0f, paint);
216        });
217
218    RenderNodeDrawable drawable(rootNode.get(), &canvas, true);
219    canvas.drawDrawable(&drawable);
220    ASSERT_EQ(SK_ColorGREEN, TestUtils::getColor(surface, 200, 600));
221}
222
223namespace {
224class ContextFactory : public IContextFactory {
225public:
226    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
227        return new AnimationContext(clock);
228    }
229};
230} // end anonymous namespace
231
232RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) {
233    static const int SCROLL_X = 5;
234    static const int SCROLL_Y = 10;
235    class ProjectionTestCanvas : public SkCanvas {
236    public:
237        ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
238        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
239            const int index = mDrawCounter++;
240            SkMatrix expectedMatrix;;
241            switch (index) {
242            case 0:  //this is node "B"
243                EXPECT_EQ(SkRect::MakeWH(100, 100), rect);
244                EXPECT_EQ(SK_ColorWHITE, paint.getColor());
245                expectedMatrix.reset();
246                EXPECT_EQ(SkRect::MakeLTRB(0, 0, 100, 100), TestUtils::getClipBounds(this));
247                break;
248            case 1:  //this is node "P"
249                EXPECT_EQ(SkRect::MakeLTRB(-10, -10, 60, 60), rect);
250                EXPECT_EQ(SK_ColorDKGRAY, paint.getColor());
251                expectedMatrix.setTranslate(50 - SCROLL_X, 50 - SCROLL_Y);
252                EXPECT_EQ(SkRect::MakeLTRB(-35, -30, 45, 50), TestUtils::getLocalClipBounds(this));
253                break;
254            case 2:  //this is node "C"
255                EXPECT_EQ(SkRect::MakeWH(100, 50), rect);
256                EXPECT_EQ(SK_ColorBLUE, paint.getColor());
257                expectedMatrix.setTranslate(-SCROLL_X, 50 - SCROLL_Y);
258                EXPECT_EQ(SkRect::MakeLTRB(0, 40, 95, 90), TestUtils::getClipBounds(this));
259                break;
260            default:
261                ADD_FAILURE();
262            }
263            EXPECT_EQ(expectedMatrix, getTotalMatrix());
264        }
265
266        int getIndex() { return mDrawCounter; }
267    protected:
268        int mDrawCounter = 0;
269    };
270
271    /**
272     * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C)
273     * with a projecting child (P) of its own. P would normally draw between B and C's "background"
274     * draw, but because it is projected backwards, it's drawn in between B and C.
275     *
276     * The parent is scrolled by SCROLL_X/SCROLL_Y, but this does not affect the background
277     * (which isn't affected by scroll).
278     */
279    auto receiverBackground = TestUtils::createSkiaNode(0, 0, 100, 100,
280            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
281        properties.setProjectionReceiver(true);
282        // scroll doesn't apply to background, so undone via translationX/Y
283        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
284        properties.setTranslationX(SCROLL_X);
285        properties.setTranslationY(SCROLL_Y);
286
287        SkPaint paint;
288        paint.setColor(SK_ColorWHITE);
289        canvas.drawRect(0, 0, 100, 100, paint);
290    }, "B");
291
292    auto projectingRipple = TestUtils::createSkiaNode(50, 0, 100, 50,
293            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
294        properties.setProjectBackwards(true);
295        properties.setClipToBounds(false);
296        SkPaint paint;
297        paint.setColor(SK_ColorDKGRAY);
298        canvas.drawRect(-10, -10, 60, 60, paint);
299    }, "P");
300    auto child = TestUtils::createSkiaNode(0, 50, 100, 100,
301            [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
302        SkPaint paint;
303        paint.setColor(SK_ColorBLUE);
304        canvas.drawRect(0, 0, 100, 50, paint);
305        canvas.drawRenderNode(projectingRipple.get());
306    }, "C");
307    auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
308            [&receiverBackground, &child](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
309        // Set a rect outline for the projecting ripple to be masked against.
310        properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f);
311
312        canvas.save(SaveFlags::MatrixClip);
313        canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
314        canvas.drawRenderNode(receiverBackground.get());
315        canvas.drawRenderNode(child.get());
316        canvas.restore();
317    }, "A");
318    ContextFactory contextFactory;
319    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
320            renderThread, false, parent.get(), &contextFactory));
321    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
322    DamageAccumulator damageAccumulator;
323    info.damageAccumulator = &damageAccumulator;
324    parent->prepareTree(info);
325
326    //parent(A)             -> (receiverBackground, child)
327    //child(C)              -> (rect[0, 0, 100, 50], projectingRipple)
328    //projectingRipple(P)   -> (rect[-10, -10, 60, 60]) -> projects backwards
329    //receiverBackground(B) -> (rect[0, 0, 100, 100]) -> projection receiver
330
331    //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
332    ProjectionTestCanvas canvas(100, 100);
333    RenderNodeDrawable drawable(parent.get(), &canvas, true);
334    canvas.drawDrawable(&drawable);
335    EXPECT_EQ(3, canvas.getIndex());
336}
337
338RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
339    /* R is backward projected on B and C is a layer.
340                A
341               / \
342              B   C
343                  |
344                  R
345    */
346    static const int SCROLL_X = 5;
347    static const int SCROLL_Y = 10;
348    static const int CANVAS_WIDTH = 400;
349    static const int CANVAS_HEIGHT = 400;
350    static const int LAYER_WIDTH = 200;
351    static const int LAYER_HEIGHT = 200;
352    class ProjectionTestCanvas : public SkCanvas {
353    public:
354        ProjectionTestCanvas(int* drawCounter)
355            : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT)
356            , mDrawCounter(drawCounter)
357        {}
358        void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
359                const SkPaint&) override {
360            EXPECT_EQ(0, (*mDrawCounter)++); //part of painting the layer
361            EXPECT_EQ(SkRect::MakeLTRB(0, 0, LAYER_WIDTH, LAYER_HEIGHT), TestUtils::getClipBounds(this));
362        }
363        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
364            EXPECT_EQ(1, (*mDrawCounter)++);
365            EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), TestUtils::getClipBounds(this));
366        }
367        void onDrawOval(const SkRect&, const SkPaint&) override {
368            EXPECT_EQ(2, (*mDrawCounter)++);
369            SkMatrix expectedMatrix;
370            expectedMatrix.setTranslate(100 - SCROLL_X, 100 - SCROLL_Y);
371            EXPECT_EQ(expectedMatrix, getTotalMatrix());
372            EXPECT_EQ(SkRect::MakeLTRB(-85, -80, 295, 300), TestUtils::getLocalClipBounds(this));
373        }
374        int* mDrawCounter;
375    };
376
377    class ProjectionLayer : public SkSurface_Base {
378    public:
379        ProjectionLayer(int* drawCounter)
380            : SkSurface_Base(SkImageInfo::MakeN32Premul(LAYER_WIDTH, LAYER_HEIGHT), nullptr)
381            , mDrawCounter(drawCounter) {
382        }
383        void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override {
384            EXPECT_EQ(3, (*mDrawCounter)++);
385            EXPECT_EQ(SkRect::MakeLTRB(100 - SCROLL_X, 100 - SCROLL_Y, 300 - SCROLL_X,
386                   300 - SCROLL_Y), TestUtils::getClipBounds(this->getCanvas()));
387        }
388        SkCanvas* onNewCanvas() override {
389            return new ProjectionTestCanvas(mDrawCounter);
390        }
391        sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
392            return nullptr;
393        }
394        sk_sp<SkImage> onNewImageSnapshot() override {
395            return nullptr;
396        }
397        void onCopyOnWrite(ContentChangeMode) override {}
398        int* mDrawCounter;
399    };
400
401    auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
402            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
403        properties.setProjectionReceiver(true);
404        // scroll doesn't apply to background, so undone via translationX/Y
405        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
406        properties.setTranslationX(SCROLL_X);
407        properties.setTranslationY(SCROLL_Y);
408
409        canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
410    }, "B"); //B
411    auto projectingRipple = TestUtils::createSkiaNode(0, 0, LAYER_WIDTH, LAYER_HEIGHT,
412            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
413        properties.setProjectBackwards(true);
414        properties.setClipToBounds(false);
415        canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds
416    }, "R"); //R
417    auto child = TestUtils::createSkiaNode(100, 100, 300, 300,
418            [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
419        canvas.drawRenderNode(projectingRipple.get());
420        canvas.drawArc(0, 0, LAYER_WIDTH, LAYER_HEIGHT, 0.0f, 280.0f, true, SkPaint());
421    }, "C"); //C
422    auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
423            [&receiverBackground, &child](RenderProperties& properties,
424            SkiaRecordingCanvas& canvas) {
425        // Set a rect outline for the projecting ripple to be masked against.
426        properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f);
427        canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
428        canvas.drawRenderNode(receiverBackground.get());
429        canvas.drawRenderNode(child.get());
430    }, "A"); //A
431
432    //prepareTree is required to find, which receivers have backward projected nodes
433    ContextFactory contextFactory;
434    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
435            renderThread, false, parent.get(), &contextFactory));
436    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
437    DamageAccumulator damageAccumulator;
438    info.damageAccumulator = &damageAccumulator;
439    parent->prepareTree(info);
440
441    int drawCounter = 0;
442    //set a layer after prepareTree to avoid layer logic there
443    child->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
444    sk_sp<SkSurface> surfaceLayer1(new ProjectionLayer(&drawCounter));
445    child->setLayerSurface(surfaceLayer1);
446    Matrix4 windowTransform;
447    windowTransform.loadTranslate(100, 100, 0);
448    child->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
449
450    LayerUpdateQueue layerUpdateQueue;
451    layerUpdateQueue.enqueueLayerWithDamage(child.get(),
452            android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
453    SkiaPipeline::renderLayersImpl(layerUpdateQueue, true);
454    EXPECT_EQ(1, drawCounter);  //assert index 0 is drawn on the layer
455
456    RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true);
457    surfaceLayer1->getCanvas()->drawDrawable(&drawable);
458    EXPECT_EQ(4, drawCounter);
459
460    // clean up layer pointer, so we can safely destruct RenderNode
461    child->setLayerSurface(nullptr);
462}
463
464RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) {
465    /* R is backward projected on B.
466                A
467               / \
468              B   C
469                  |
470                  R
471    */
472    static const int SCROLL_X = 500000;
473    static const int SCROLL_Y = 0;
474    static const int CANVAS_WIDTH = 400;
475    static const int CANVAS_HEIGHT = 400;
476    class ProjectionChildScrollTestCanvas : public SkCanvas {
477    public:
478        ProjectionChildScrollTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
479        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
480            EXPECT_EQ(0, mDrawCounter++);
481            EXPECT_TRUE(getTotalMatrix().isIdentity());
482        }
483        void onDrawOval(const SkRect&, const SkPaint&) override {
484            EXPECT_EQ(1, mDrawCounter++);
485            EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), TestUtils::getClipBounds(this));
486            EXPECT_TRUE(getTotalMatrix().isIdentity());
487        }
488        int mDrawCounter = 0;
489    };
490
491    auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
492            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
493        properties.setProjectionReceiver(true);
494        canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
495    }, "B"); //B
496    auto projectingRipple = TestUtils::createSkiaNode(0, 0, 200, 200,
497            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
498        // scroll doesn't apply to background, so undone via translationX/Y
499        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
500        properties.setTranslationX(SCROLL_X);
501        properties.setTranslationY(SCROLL_Y);
502        properties.setProjectBackwards(true);
503        properties.setClipToBounds(false);
504        canvas.drawOval(0, 0, 200, 200, SkPaint());
505    }, "R"); //R
506    auto child = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
507            [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
508        // Record time clip will be ignored by projectee
509        canvas.clipRect(100, 100, 300, 300, SkClipOp::kIntersect);
510
511        canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
512        canvas.drawRenderNode(projectingRipple.get());
513    }, "C"); //C
514    auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
515            [&receiverBackground, &child](RenderProperties& properties,
516            SkiaRecordingCanvas& canvas) {
517        canvas.drawRenderNode(receiverBackground.get());
518        canvas.drawRenderNode(child.get());
519    }, "A"); //A
520
521    //prepareTree is required to find, which receivers have backward projected nodes
522    ContextFactory contextFactory;
523    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
524            renderThread, false, parent.get(), &contextFactory));
525    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
526    DamageAccumulator damageAccumulator;
527    info.damageAccumulator = &damageAccumulator;
528    parent->prepareTree(info);
529
530    std::unique_ptr<ProjectionChildScrollTestCanvas> canvas(new ProjectionChildScrollTestCanvas());
531    RenderNodeDrawable drawable(parent.get(), canvas.get(), true);
532    canvas->drawDrawable(&drawable);
533    EXPECT_EQ(2, canvas->mDrawCounter);
534}
535
536namespace {
537static int drawNode(RenderThread& renderThread, const sp<RenderNode>& renderNode)
538{
539    ContextFactory contextFactory;
540    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
541            renderThread, false, renderNode.get(), &contextFactory));
542    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
543    DamageAccumulator damageAccumulator;
544    info.damageAccumulator = &damageAccumulator;
545    renderNode->prepareTree(info);
546
547    //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
548    ZReorderCanvas canvas(100, 100);
549    RenderNodeDrawable drawable(renderNode.get(), &canvas, false);
550    canvas.drawDrawable(&drawable);
551    return canvas.getIndex();
552}
553}
554
555RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedInMiddle) {
556    /* R is backward projected on B
557                A
558               / \
559              B   C
560                  |
561                  R
562    */
563    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
564            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
565        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
566            props.setProjectionReceiver(true);
567        } ); //nodeB
568        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
569            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
570                props.setProjectBackwards(true);
571                props.setClipToBounds(false);
572            } ); //nodeR
573        } ); //nodeC
574    }); //nodeA
575    EXPECT_EQ(3, drawNode(renderThread, nodeA));
576}
577
578RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectLast) {
579    /* R is backward projected on E
580                  A
581                / | \
582               /  |  \
583              B   C   E
584                  |
585                  R
586    */
587    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
588            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
589        drawOrderedNode(&canvas, 0, nullptr); //nodeB
590        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
591            drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //drawn as 2
592                props.setProjectBackwards(true);
593                props.setClipToBounds(false);
594            } ); //nodeR
595        } ); //nodeC
596        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //drawn as 3
597            props.setProjectionReceiver(true);
598        } ); //nodeE
599    }); //nodeA
600    EXPECT_EQ(4, drawNode(renderThread, nodeA));
601}
602
603RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderNoReceivable) {
604    /* R is backward projected without receiver
605                A
606               / \
607              B   C
608                  |
609                  R
610    */
611     auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
612            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
613        drawOrderedNode(&canvas, 0, nullptr); //nodeB
614        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
615            drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
616                //not having a projection receiver is an undefined behavior
617                props.setProjectBackwards(true);
618                props.setClipToBounds(false);
619            } ); //nodeR
620        } ); //nodeC
621    }); //nodeA
622    EXPECT_EQ(2, drawNode(renderThread, nodeA));
623}
624
625RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderParentReceivable) {
626    /* R is backward projected on C
627                A
628               / \
629              B   C
630                  |
631                  R
632    */
633     auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
634            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
635        drawOrderedNode(&canvas, 0, nullptr); //nodeB
636        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
637            props.setProjectionReceiver(true);
638            drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
639                props.setProjectBackwards(true);
640                props.setClipToBounds(false);
641            } ); //nodeR
642        } ); //nodeC
643    }); //nodeA
644    EXPECT_EQ(3, drawNode(renderThread, nodeA));
645}
646
647RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderSameNodeReceivable) {
648    /* R is backward projected on R
649                A
650               / \
651              B   C
652                  |
653                  R
654    */
655     auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
656            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
657        drawOrderedNode(&canvas, 0, nullptr); //nodeB
658        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
659            drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
660                //having a node that is projected on itself is an undefined/unexpected behavior
661                props.setProjectionReceiver(true);
662                props.setProjectBackwards(true);
663                props.setClipToBounds(false);
664            } ); //nodeR
665        } ); //nodeC
666    }); //nodeA
667    EXPECT_EQ(2, drawNode(renderThread, nodeA));
668}
669
670//Note: the outcome for this test is different in HWUI
671RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedSibling) {
672    /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
673                A
674               /|\
675              / | \
676             B  C  R
677    */
678    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
679            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
680        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
681            props.setProjectionReceiver(true);
682        } ); //nodeB
683        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
684        } ); //nodeC
685        drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
686            props.setProjectBackwards(true);
687            props.setClipToBounds(false);
688        } ); //nodeR
689    }); //nodeA
690    EXPECT_EQ(2, drawNode(renderThread, nodeA));
691}
692
693RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedSibling2) {
694    /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
695                A
696                |
697                G
698               /|\
699              / | \
700             B  C  R
701    */
702    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
703            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
704        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
705            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
706                props.setProjectionReceiver(true);
707            } ); //nodeB
708            drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
709            } ); //nodeC
710            drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
711                props.setProjectBackwards(true);
712                props.setClipToBounds(false);
713            } ); //nodeR
714        } ); //nodeG
715    }); //nodeA
716    EXPECT_EQ(3, drawNode(renderThread, nodeA));
717}
718
719RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderGrandparentReceivable) {
720    /* R is backward projected on B
721                A
722                |
723                B
724                |
725                C
726                |
727                R
728    */
729    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
730            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
731        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
732            props.setProjectionReceiver(true);
733            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
734                drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
735                    props.setProjectBackwards(true);
736                    props.setClipToBounds(false);
737                } ); //nodeR
738            } ); //nodeC
739        } ); //nodeB
740    }); //nodeA
741    EXPECT_EQ(3, drawNode(renderThread, nodeA));
742}
743
744RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivables) {
745    /* B and G are receivables, R is backward projected
746                A
747               / \
748              B   C
749                 / \
750                G   R
751    */
752    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
753            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
754        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
755            props.setProjectionReceiver(true);
756        } ); //nodeB
757        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
758            drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
759                props.setProjectionReceiver(true);
760            } ); //nodeG
761            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
762                props.setProjectBackwards(true);
763                props.setClipToBounds(false);
764            } ); //nodeR
765        } ); //nodeC
766    }); //nodeA
767    EXPECT_EQ(4, drawNode(renderThread, nodeA));
768}
769
770RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesLikelyScenario) {
771    /* B and G are receivables, G is backward projected
772                A
773               / \
774              B   C
775                 / \
776                G   R
777    */
778    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
779            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
780        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
781            props.setProjectionReceiver(true);
782        } ); //nodeB
783        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
784            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
785                props.setProjectionReceiver(true);
786                props.setProjectBackwards(true);
787                props.setClipToBounds(false);
788            } ); //nodeG
789            drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
790            } ); //nodeR
791        } ); //nodeC
792    }); //nodeA
793    EXPECT_EQ(4, drawNode(renderThread, nodeA));
794}
795
796RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesDeeper) {
797    /* B and G are receivables, R is backward projected
798                A
799               / \
800              B   C
801                 / \
802                G   D
803                    |
804                    R
805    */
806    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
807            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
808        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
809            props.setProjectionReceiver(true);
810        } ); //nodeB
811        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
812            drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
813                props.setProjectionReceiver(true);
814            } ); //nodeG
815            drawOrderedNode(&canvas, 4, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //D
816                drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
817                    props.setProjectBackwards(true);
818                    props.setClipToBounds(false);
819                } ); //nodeR
820            } ); //nodeD
821        } ); //nodeC
822    }); //nodeA
823    EXPECT_EQ(5, drawNode(renderThread, nodeA));
824}
825
826RENDERTHREAD_TEST(RenderNodeDrawable, simple) {
827    static const int CANVAS_WIDTH = 100;
828    static const int CANVAS_HEIGHT = 200;
829    class SimpleTestCanvas : public TestCanvasBase {
830    public:
831        SimpleTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {
832        }
833        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
834            EXPECT_EQ(0, mDrawCounter++);
835        }
836        void onDrawImage(const SkImage*, SkScalar dx, SkScalar dy, const SkPaint*) override {
837            EXPECT_EQ(1, mDrawCounter++);
838        }
839    };
840
841    auto node = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
842            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
843        sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25));
844        canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
845        canvas.drawBitmap(*bitmap, 10, 10, nullptr);
846    });
847
848    SimpleTestCanvas canvas;
849    RenderNodeDrawable drawable(node.get(), &canvas, true);
850    canvas.drawDrawable(&drawable);
851    EXPECT_EQ(2, canvas.mDrawCounter);
852}
853
854RENDERTHREAD_TEST(RenderNodeDrawable, colorOp_unbounded) {
855    static const int CANVAS_WIDTH = 200;
856    static const int CANVAS_HEIGHT = 200;
857    class ColorTestCanvas : public TestCanvasBase {
858    public:
859        ColorTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {
860        }
861        void onDrawPaint(const SkPaint&) {
862            switch (mDrawCounter++) {
863            case 0:
864                EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT),
865                        TestUtils::getClipBounds(this));
866                break;
867            case 1:
868                EXPECT_EQ(SkRect::MakeWH(10, 10), TestUtils::getClipBounds(this));
869                break;
870            default:
871                ADD_FAILURE();
872            }
873        }
874    };
875
876    auto unclippedColorView = TestUtils::createSkiaNode(0, 0, 10, 10,
877            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
878        props.setClipToBounds(false);
879        canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
880    });
881
882    auto clippedColorView = TestUtils::createSkiaNode(0, 0, 10, 10,
883            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
884        canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
885    });
886
887    ColorTestCanvas canvas;
888    RenderNodeDrawable drawable(unclippedColorView.get(), &canvas, true);
889    canvas.drawDrawable(&drawable);
890    EXPECT_EQ(1, canvas.mDrawCounter);
891    RenderNodeDrawable drawable2(clippedColorView.get(), &canvas, true);
892    canvas.drawDrawable(&drawable2);
893    EXPECT_EQ(2, canvas.mDrawCounter);
894}
895
896TEST(RenderNodeDrawable, renderNode) {
897    static const int CANVAS_WIDTH = 200;
898    static const int CANVAS_HEIGHT = 200;
899    class RenderNodeTestCanvas : public TestCanvasBase {
900    public:
901        RenderNodeTestCanvas() : TestCanvasBase(CANVAS_WIDTH, CANVAS_HEIGHT) {
902        }
903        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
904            switch(mDrawCounter++) {
905            case 0:
906                EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), TestUtils::getClipBounds(this));
907                EXPECT_EQ(SK_ColorDKGRAY, paint.getColor());
908                break;
909            case 1:
910                EXPECT_EQ(SkRect::MakeLTRB(50, 50, 150, 150), TestUtils::getClipBounds(this));
911                EXPECT_EQ(SK_ColorWHITE, paint.getColor());
912                break;
913            default:
914                ADD_FAILURE();
915            }
916        }
917    };
918
919    auto child = TestUtils::createSkiaNode(10, 10, 110, 110,
920            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
921        SkPaint paint;
922        paint.setColor(SK_ColorWHITE);
923        canvas.drawRect(0, 0, 100, 100, paint);
924    });
925
926    auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
927            [&child](RenderProperties& props, SkiaRecordingCanvas& canvas) {
928        SkPaint paint;
929        paint.setColor(SK_ColorDKGRAY);
930        canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, paint);
931
932        canvas.save(SaveFlags::MatrixClip);
933        canvas.translate(40, 40);
934        canvas.drawRenderNode(child.get());
935        canvas.restore();
936    });
937
938    RenderNodeTestCanvas canvas;
939    RenderNodeDrawable drawable(parent.get(), &canvas, true);
940    canvas.drawDrawable(&drawable);
941    EXPECT_EQ(2, canvas.mDrawCounter);
942}
943
944
945TEST(ReorderBarrierDrawable, testShadowMatrix) {
946    static const int CANVAS_WIDTH = 100;
947    static const int CANVAS_HEIGHT = 100;
948    static const float TRANSLATE_X = 11.0f;
949    static const float TRANSLATE_Y = 22.0f;
950    static const float CASTER_X = 40.0f;
951    static const float CASTER_Y = 40.0f;
952    static const float CASTER_WIDTH = 20.0f;
953    static const float CASTER_HEIGHT = 20.0f;
954
955
956    class ShadowTestCanvas : public SkCanvas {
957    public:
958        ShadowTestCanvas(int width, int height) : SkCanvas(width, height) {}
959        int getIndex() { return mDrawCounter; }
960
961        virtual void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
962            // expect to draw 2 RenderNodeDrawable, 1 StartReorderBarrierDrawable,
963            // 1 EndReorderBarrierDrawable
964            mDrawCounter++;
965            SkCanvas::onDrawDrawable(drawable, matrix);
966        }
967
968        virtual void didTranslate(SkScalar dx, SkScalar dy) override {
969            mDrawCounter++;
970            EXPECT_EQ(dx, TRANSLATE_X);
971            EXPECT_EQ(dy, TRANSLATE_Y);
972        }
973
974        virtual void didConcat(const SkMatrix& matrix) override {
975            // This function is invoked by EndReorderBarrierDrawable::drawShadow to apply shadow
976            // matrix.
977            mDrawCounter++;
978            EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X, CASTER_Y), matrix);
979            EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X+TRANSLATE_X, CASTER_Y+TRANSLATE_Y),
980                    getTotalMatrix());
981        }
982    protected:
983        int mDrawCounter = 0;
984    };
985
986    auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
987            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
988        canvas.translate(TRANSLATE_X, TRANSLATE_Y);
989        canvas.insertReorderBarrier(true);
990
991        auto node = TestUtils::createSkiaNode(CASTER_X, CASTER_Y, CASTER_X + CASTER_WIDTH,
992                CASTER_Y + CASTER_HEIGHT,
993                [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
994                    props.setElevation(42);
995                    props.mutableOutline().setRoundRect(0, 0, 20, 20, 5, 1);
996                    props.mutableOutline().setShouldClip(true);
997                });
998        canvas.drawRenderNode(node.get());
999        canvas.insertReorderBarrier(false);
1000    });
1001
1002    //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
1003    ShadowTestCanvas canvas(CANVAS_WIDTH, CANVAS_HEIGHT);
1004    RenderNodeDrawable drawable(parent.get(), &canvas, false);
1005    canvas.drawDrawable(&drawable);
1006    EXPECT_EQ(6, canvas.getIndex());
1007}