Add TaskManager API

This API can be used to run arbitrary tasks on a pool of worker
threads. The number of threads is calculated based on the number
of CPU cores available.

The API is made of 3 classes:

TaskManager
      Creates and manages the worker threads.

Task
      Describes the work to be done and the type of the output.
      A task contains a future used to wait for the worker thread
      to be done computing the result of the task.

TaskProcessor
      The processor dispatches tasks to the TaskManager and is
      responsible for performing the computation required by
      each task. A processor will only be asked to process tasks
      sent to the manager through the processor.

A typical use case:

class MyTask: Task<MyType>

class MyProcessor: TaskProcessor<MyType>

TaskManager m = new TaskManager();
MyProcessor p = new MyProcessor(m);
MyTask t = new MyTask();
p.add(t);

// Waits until the result is available
MyType result = t->getResult();

Change-Id: I1fe845ba4c49bb0e1b0627ab147f9a861c8e0749
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 85b2052..1618110 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -7,6 +7,7 @@
 	LOCAL_SRC_FILES:= \
 		utils/Blur.cpp \
 		utils/SortedListImpl.cpp \
+		thread/TaskManager.cpp \
 		font/CacheTexture.cpp \
 		font/Font.cpp \
 		FontRenderer.cpp \
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index ca699d5..dc32a7e 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -25,6 +25,9 @@
 
 #include <cutils/compiler.h>
 
+#include "thread/TaskProcessor.h"
+#include "thread/TaskManager.h"
+
 #include "FontRenderer.h"
 #include "GammaFontRenderer.h"
 #include "TextureCache.h"
@@ -278,6 +281,8 @@
 
     GammaFontRenderer* fontRenderer;
 
+    TaskManager tasks;
+
     Dither dither;
     Stencil stencil;
 
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 9e6ec84..afdc2c9 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -31,69 +31,32 @@
 // Path precaching
 ///////////////////////////////////////////////////////////////////////////////
 
-bool PathCache::PrecacheThread::threadLoop() {
-    mSignal.wait();
-    Vector<Task> tasks;
-    {
-        Mutex::Autolock l(mLock);
-        tasks = mTasks;
-        mTasks.clear();
-    }
-
-    Caches& caches = Caches::getInstance();
-    uint32_t maxSize = caches.maxTextureSize;
-
-    ATRACE_BEGIN("pathPrecache");
-    for (size_t i = 0; i < tasks.size(); i++) {
-        const Task& task = tasks.itemAt(i);
-
-        float left, top, offset;
-        uint32_t width, height;
-        PathCache::computePathBounds(task.path, task.paint, left, top, offset, width, height);
-
-        if (width <= maxSize && height <= maxSize) {
-            SkBitmap* bitmap = new SkBitmap();
-
-            PathTexture* texture = task.texture;
-            texture->left = left;
-            texture->top = top;
-            texture->offset = offset;
-            texture->width = width;
-            texture->height = height;
-
-            PathCache::drawPath(task.path, task.paint, *bitmap, left, top, offset, width, height);
-
-            texture->future()->produce(bitmap);
-        } else {
-            task.texture->future()->produce(NULL);
-        }
-    }
-    ATRACE_END();
-    return true;
+PathCache::PathProcessor::PathProcessor(Caches& caches):
+        TaskProcessor<SkBitmap*>(&caches.tasks), mMaxTextureSize(caches.maxTextureSize) {
 }
 
-void PathCache::PrecacheThread::addTask(PathTexture* texture, SkPath* path, SkPaint* paint) {
-    if (!isRunning()) {
-        run("libhwui:pathPrecache", PRIORITY_DEFAULT);
+void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
+    sp<PathTask> t = static_cast<PathTask* >(task.get());
+    ATRACE_NAME("pathPrecache");
+
+    float left, top, offset;
+    uint32_t width, height;
+    PathCache::computePathBounds(t->path, t->paint, left, top, offset, width, height);
+
+    PathTexture* texture = t->texture;
+    texture->left = left;
+    texture->top = top;
+    texture->offset = offset;
+    texture->width = width;
+    texture->height = height;
+
+    if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
+        SkBitmap* bitmap = new SkBitmap();
+        PathCache::drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height);
+        t->setResult(bitmap);
+    } else {
+        t->setResult(NULL);
     }
-
-    Task task;
-    task.texture = texture;
-    task.path = path;
-    task.paint = paint;
-
-    Mutex::Autolock l(mLock);
-    mTasks.add(task);
-    mSignal.signal();
-}
-
-void PathCache::PrecacheThread::exit() {
-    {
-        Mutex::Autolock l(mLock);
-        mTasks.clear();
-    }
-    requestExit();
-    mSignal.signal();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -101,11 +64,10 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 PathCache::PathCache(): ShapeCache<PathCacheEntry>("path",
-        PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE), mThread(new PrecacheThread()) {
+        PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE) {
 }
 
 PathCache::~PathCache() {
-    mThread->exit();
 }
 
 void PathCache::remove(SkPath* path) {
@@ -165,17 +127,18 @@
     } else {
         // A bitmap is attached to the texture, this means we need to
         // upload it as a GL texture
-        if (texture->future() != NULL) {
+        const sp<Task<SkBitmap*> >& task = texture->task();
+        if (task != NULL) {
             // But we must first wait for the worker thread to be done
             // producing the bitmap, so let's wait
-            SkBitmap* bitmap = texture->future()->get();
+            SkBitmap* bitmap = task->getResult();
             if (bitmap) {
                 addTexture(entry, bitmap, texture);
-                texture->clearFuture();
+                texture->clearTask();
             } else {
                 ALOGW("Path too large to be rendered into a texture (%dx%d)",
                         texture->width, texture->height);
-                texture->clearFuture();
+                texture->clearTask();
                 texture = NULL;
                 mCache.remove(entry);
             }
@@ -189,6 +152,10 @@
 }
 
 void PathCache::precache(SkPath* path, SkPaint* paint) {
+    if (!Caches::getInstance().tasks.canRunTasks()) {
+        return;
+    }
+
     path = getSourcePath(path);
 
     PathCacheEntry entry(path, paint);
@@ -205,7 +172,9 @@
     if (generate) {
         // It is important to specify the generation ID so we do not
         // attempt to precache the same path several times
-        texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID(), true);
+        texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID());
+        sp<PathTask> task = new PathTask(path, paint, texture);
+        texture->setTask(task);
 
         // During the precaching phase we insert path texture objects into
         // the cache that do not point to any GL texture. They are instead
@@ -215,7 +184,11 @@
         // asks for a path texture. This is also when the cache limit will
         // be enforced.
         mCache.put(entry, texture);
-        mThread->addTask(texture, path, paint);
+
+        if (mProcessor == NULL) {
+            mProcessor = new PathProcessor(Caches::getInstance());
+        }
+        mProcessor->add(task);
     }
 }
 
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 1d28ecb..27031a5 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -23,6 +23,8 @@
 #include "Debug.h"
 #include "ShapeCache.h"
 #include "thread/Signal.h"
+#include "thread/Task.h"
+#include "thread/TaskProcessor.h"
 
 class SkPaint;
 class SkPath;
@@ -100,32 +102,33 @@
     void precache(SkPath* path, SkPaint* paint);
 
 private:
-    class PrecacheThread: public Thread {
+    class PathTask: public Task<SkBitmap*> {
     public:
-        PrecacheThread(): mSignal(Condition::WAKE_UP_ONE) { }
+        PathTask(SkPath* path, SkPaint* paint, PathTexture* texture):
+            path(path), paint(paint), texture(texture) {
+        }
 
-        void addTask(PathTexture* texture, SkPath* path, SkPaint* paint);
-        void exit();
+        ~PathTask() {
+            delete future()->get();
+        }
 
-    private:
-        struct Task {
-            PathTexture* texture;
-            SkPath* path;
-            SkPaint* paint;
-        };
-
-        virtual bool threadLoop();
-
-        // Lock for the list of tasks
-        Mutex mLock;
-        Vector<Task> mTasks;
-
-        // Signal used to wake up the thread when a new
-        // task is available in the list
-        mutable Signal mSignal;
+        SkPath* path;
+        SkPaint* paint;
+        PathTexture* texture;
     };
 
-    sp<PrecacheThread> mThread;
+    class PathProcessor: public TaskProcessor<SkBitmap*> {
+    public:
+        PathProcessor(Caches& caches);
+        ~PathProcessor() { }
+
+        virtual void onProcess(const sp<Task<SkBitmap*> >& task);
+
+    private:
+        uint32_t mMaxTextureSize;
+    };
+
+    sp<PathProcessor> mProcessor;
     Vector<SkPath*> mGarbage;
     mutable Mutex mLock;
 }; // class PathCache
diff --git a/libs/hwui/ShapeCache.h b/libs/hwui/ShapeCache.h
index 67ae85b..58fea08 100644
--- a/libs/hwui/ShapeCache.h
+++ b/libs/hwui/ShapeCache.h
@@ -30,12 +30,11 @@
 #include <utils/JenkinsHash.h>
 #include <utils/LruCache.h>
 #include <utils/Trace.h>
-#include <utils/CallStack.h>
 
 #include "Debug.h"
 #include "Properties.h"
 #include "Texture.h"
-#include "thread/Future.h"
+#include "thread/Task.h"
 
 namespace android {
 namespace uirenderer {
@@ -62,14 +61,8 @@
     PathTexture(): Texture() {
     }
 
-    PathTexture(bool hasFuture): Texture() {
-        if (hasFuture) {
-            mFuture = new Future<SkBitmap*>();
-        }
-    }
-
     ~PathTexture() {
-        clearFuture();
+        clearTask();
     }
 
     /**
@@ -85,19 +78,22 @@
      */
     float offset;
 
-    sp<Future<SkBitmap*> > future() const {
-        return mFuture;
+    sp<Task<SkBitmap*> > task() const {
+        return mTask;
     }
 
-    void clearFuture() {
-        if (mFuture != NULL) {
-            delete mFuture->get();
-            mFuture.clear();
+    void setTask(const sp<Task<SkBitmap*> >& task) {
+        mTask = task;
+    }
+
+    void clearTask() {
+        if (mTask != NULL) {
+            mTask.clear();
         }
     }
 
 private:
-    sp<Future<SkBitmap*> > mFuture;
+    sp<Task<SkBitmap*> > mTask;
 }; // struct PathTexture
 
 /**
@@ -551,8 +547,8 @@
     }
 
     static PathTexture* createTexture(float left, float top, float offset,
-            uint32_t width, uint32_t height, uint32_t id, bool hasFuture = false) {
-        PathTexture* texture = new PathTexture(hasFuture);
+            uint32_t width, uint32_t height, uint32_t id) {
+        PathTexture* texture = new PathTexture();
         texture->left = left;
         texture->top = top;
         texture->offset = offset;
diff --git a/libs/hwui/thread/Future.h b/libs/hwui/thread/Future.h
index 340fec7..a3ff3bc 100644
--- a/libs/hwui/thread/Future.h
+++ b/libs/hwui/thread/Future.h
@@ -24,7 +24,7 @@
 namespace android {
 namespace uirenderer {
 
-template<class T>
+template<typename T>
 class Future: public LightRefBase<Future<T> > {
 public:
     Future(Condition::WakeUpType type = Condition::WAKE_UP_ONE): mBarrier(type), mResult() { }
diff --git a/libs/hwui/thread/Task.h b/libs/hwui/thread/Task.h
new file mode 100644
index 0000000..9a211a2
--- /dev/null
+++ b/libs/hwui/thread/Task.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_TASK_H
+#define ANDROID_HWUI_TASK_H
+
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include <utils/RefBase.h>
+#include <utils/Trace.h>
+
+#include "Future.h"
+
+namespace android {
+namespace uirenderer {
+
+class TaskBase: public RefBase {
+public:
+    TaskBase() { }
+    virtual ~TaskBase() { }
+};
+
+template<typename T>
+class Task: public TaskBase {
+public:
+    Task(): mFuture(new Future<T>()) { }
+    virtual ~Task() { }
+
+    T getResult() const {
+        ATRACE_NAME("waitForTask");
+        return mFuture->get();
+    }
+
+    void setResult(T result) {
+        mFuture->produce(result);
+    }
+
+protected:
+    const sp<Future<T> >& future() const {
+        return mFuture;
+    }
+
+private:
+    sp<Future<T> > mFuture;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_H
diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp
new file mode 100644
index 0000000..ce6c8c0
--- /dev/null
+++ b/libs/hwui/thread/TaskManager.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/sysinfo.h>
+
+#include "Task.h"
+#include "TaskProcessor.h"
+#include "TaskManager.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Manager
+///////////////////////////////////////////////////////////////////////////////
+
+TaskManager::TaskManager() {
+    // Get the number of available CPUs. This value does not change over time.
+    int cpuCount = sysconf(_SC_NPROCESSORS_ONLN);
+
+    for (int i = 0; i < cpuCount / 2; i++) {
+        String8 name;
+        name.appendFormat("hwuiTask%d", i + 1);
+        mThreads.add(new WorkerThread(name));
+    }
+}
+
+TaskManager::~TaskManager() {
+    for (size_t i = 0; i < mThreads.size(); i++) {
+        mThreads[i]->exit();
+    }
+}
+
+bool TaskManager::canRunTasks() const {
+    return mThreads.size() > 0;
+}
+
+bool TaskManager::addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor) {
+    if (mThreads.size() > 0) {
+        TaskWrapper wrapper(task, processor);
+
+        size_t minQueueSize = INT_MAX;
+        sp<WorkerThread> thread;
+
+        for (size_t i = 0; i < mThreads.size(); i++) {
+            if (mThreads[i]->getTaskCount() < minQueueSize) {
+                thread = mThreads[i];
+                minQueueSize = mThreads[i]->getTaskCount();
+            }
+        }
+
+        return thread->addTask(wrapper);
+    }
+    return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Thread
+///////////////////////////////////////////////////////////////////////////////
+
+bool TaskManager::WorkerThread::threadLoop() {
+    mSignal.wait();
+    Vector<TaskWrapper> tasks;
+    {
+        Mutex::Autolock l(mLock);
+        tasks = mTasks;
+        mTasks.clear();
+    }
+
+    for (size_t i = 0; i < tasks.size(); i++) {
+        const TaskWrapper& task = tasks.itemAt(i);
+        task.mProcessor->process(task.mTask);
+    }
+
+    return true;
+}
+
+bool TaskManager::WorkerThread::addTask(TaskWrapper task) {
+    if (!isRunning()) {
+        run(mName.string(), PRIORITY_DEFAULT);
+    }
+
+    Mutex::Autolock l(mLock);
+    ssize_t index = mTasks.add(task);
+    mSignal.signal();
+
+    return index >= 0;
+}
+
+size_t TaskManager::WorkerThread::getTaskCount() const {
+    Mutex::Autolock l(mLock);
+    return mTasks.size();
+}
+
+void TaskManager::WorkerThread::exit() {
+    {
+        Mutex::Autolock l(mLock);
+        mTasks.clear();
+    }
+    requestExit();
+    mSignal.signal();
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h
new file mode 100644
index 0000000..bc86062
--- /dev/null
+++ b/libs/hwui/thread/TaskManager.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_TASK_MANAGER_H
+#define ANDROID_HWUI_TASK_MANAGER_H
+
+#include <utils/Mutex.h>
+#include <utils/String8.h>
+#include <utils/Thread.h>
+#include <utils/Vector.h>
+
+#include "Signal.h"
+
+namespace android {
+namespace uirenderer {
+
+template <typename T>
+class Task;
+class TaskBase;
+
+template <typename T>
+class TaskProcessor;
+class TaskProcessorBase;
+
+class TaskManager {
+public:
+    TaskManager();
+    ~TaskManager();
+
+    /**
+     * Returns true if this task  manager can run tasks,
+     * false otherwise. This method will typically return
+     * true on a single CPU core device.
+     */
+    bool canRunTasks() const;
+
+private:
+    template <typename T>
+    friend class TaskProcessor;
+
+    template<typename T>
+    bool addTask(const sp<Task<T> >& task, const sp<TaskProcessor<T> >& processor) {
+        return addTaskBase(sp<TaskBase>(task), sp<TaskProcessorBase>(processor));
+    }
+
+    bool addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor);
+
+    struct TaskWrapper {
+        TaskWrapper(): mTask(), mProcessor() { }
+
+        TaskWrapper(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor):
+            mTask(task), mProcessor(processor) {
+        }
+
+        sp<TaskBase> mTask;
+        sp<TaskProcessorBase> mProcessor;
+    };
+
+    class WorkerThread: public Thread {
+    public:
+        WorkerThread(const String8 name): mSignal(Condition::WAKE_UP_ONE), mName(name) { }
+
+        bool addTask(TaskWrapper task);
+        size_t getTaskCount() const;
+        void exit();
+
+    private:
+        virtual bool threadLoop();
+
+        // Lock for the list of tasks
+        mutable Mutex mLock;
+        Vector<TaskWrapper> mTasks;
+
+        // Signal used to wake up the thread when a new
+        // task is available in the list
+        mutable Signal mSignal;
+
+        const String8 mName;
+    };
+
+    Vector<sp<WorkerThread> > mThreads;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_MANAGER_H
diff --git a/libs/hwui/thread/TaskProcessor.h b/libs/hwui/thread/TaskProcessor.h
new file mode 100644
index 0000000..d1269f0
--- /dev/null
+++ b/libs/hwui/thread/TaskProcessor.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_TASK_PROCESSOR_H
+#define ANDROID_HWUI_TASK_PROCESSOR_H
+
+#include <utils/RefBase.h>
+
+#include "Task.h"
+#include "TaskManager.h"
+
+namespace android {
+namespace uirenderer {
+
+class TaskProcessorBase: public RefBase {
+public:
+    TaskProcessorBase() { }
+    virtual ~TaskProcessorBase() { };
+
+private:
+    friend class TaskManager;
+
+    virtual void process(const sp<TaskBase>& task) = 0;
+};
+
+template<typename T>
+class TaskProcessor: public TaskProcessorBase {
+public:
+    TaskProcessor(TaskManager* manager): mManager(manager) { }
+    virtual ~TaskProcessor() { }
+
+    bool add(const sp<Task<T> >& task);
+
+    virtual void onProcess(const sp<Task<T> >& task) = 0;
+
+private:
+    virtual void process(const sp<TaskBase>& task) {
+        sp<Task<T> > realTask = static_cast<Task<T>* >(task.get());
+        // This is the right way to do it but sp<> doesn't play nice
+        // sp<Task<T> > realTask = static_cast<sp<Task<T> > >(task);
+        onProcess(realTask);
+    }
+
+    TaskManager* mManager;
+};
+
+template<typename T>
+bool TaskProcessor<T>::add(const sp<Task<T> >& task) {
+    if (mManager) {
+        sp<TaskProcessor<T> > self(this);
+        return mManager->addTask(task, self);
+    }
+    return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_TASK_PROCESSOR_H
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
index ac8ab1f..9f97311 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java
@@ -33,6 +33,7 @@
     private Path mPath;
 
     private final Random mRandom = new Random();
+    @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
     private final ArrayList<Path> mPathList = new ArrayList<Path>();
 
     @Override
@@ -58,6 +59,19 @@
         path.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f);
     }
 
+    private static Path makeLargePath() {
+        Path path = new Path();
+        buildLargePath(path);
+        return path;
+    }
+
+    private static void buildLargePath(Path path) {
+        path.moveTo(0.0f, 0.0f);
+        path.cubicTo(0.0f, 0.0f, 10000.0f, 15000.0f, 10000.0f, 20000.0f);
+        path.cubicTo(10000.0f, 20000.0f, 5000.0f, 30000.0f, -8000.0f, 20000.0f);
+        path.cubicTo(-8000.0f, 20000.0f, 10000.0f, 20000.0f, 20000.0f, 0.0f);
+    }
+
     public class PathsView extends View {
         private final Paint mMediumPaint;
 
@@ -97,6 +111,9 @@
                 int r = mRandom.nextInt(10);
                 if (r == 5 || r == 3) {
                     mPathList.add(path);
+                } else if (r == 7) {
+                    path = makeLargePath();
+                    mPathList.add(path);
                 }
     
                 canvas.save();