Implement SkAshmemDiscardableMemory

-   Implement ashmem-backed SkDiscardableMemory subclass:
    This class in only accesible via the SkDiscardableMemory::Create()
    function, which replaces the mock implementation in
    SkDiscardableMemory_none.cpp

-   Added SkDiscardableMemory_ashmem.cpp to the Android port of Skia
    Removed SkDiscardableMemory_none.cpp from the Android port.

-   Added DiscardableMemoryTest.
    Still needs work.

-   SkDiscardablePixelRef Bugfix:
    onLockPixels() now calls SkDELETE on the SkDiscardableMemory pointer
    when it fails to unlock.

-   Improved documentation inside ashmem.h

BUG=
R=scroggo@google.com

Review URL: https://codereview.chromium.org/83563002

git-svn-id: http://skia.googlecode.com/svn/trunk@12608 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gyp/ports.gyp b/gyp/ports.gyp
index 068d2e1..26003de 100644
--- a/gyp/ports.gyp
+++ b/gyp/ports.gyp
@@ -153,10 +153,12 @@
         [ 'skia_os == "android"', {
           'sources!': [
             '../src/ports/SkDebug_stdio.cpp',
+            '../src/ports/SkDiscardableMemory_none.cpp',
             '../src/ports/SkPurgeableMemoryBlock_none.cpp',
           ],
           'sources': [
             '../src/ports/SkDebug_android.cpp',
+            '../src/ports/SkDiscardableMemory_ashmem.cpp',
             '../src/ports/SkFontConfigInterface_android.cpp',
             '../src/ports/SkFontConfigParser_android.cpp',
             '../src/ports/SkFontHost_fontconfig.cpp',
diff --git a/gyp/tests.gyp b/gyp/tests.gyp
index ed1d077..4c8053e 100644
--- a/gyp/tests.gyp
+++ b/gyp/tests.gyp
@@ -56,6 +56,7 @@
         '../tests/DequeTest.cpp',
         '../tests/DeviceLooperTest.cpp',
         '../tests/DiscardableMemoryPool.cpp',
+        '../tests/DiscardableMemoryTest.cpp',
         '../tests/DocumentTest.cpp',
         '../tests/DrawBitmapRectTest.cpp',
         '../tests/DrawPathTest.cpp',
diff --git a/src/ports/SkDiscardableMemory_ashmem.cpp b/src/ports/SkDiscardableMemory_ashmem.cpp
new file mode 100644
index 0000000..6f8684e
--- /dev/null
+++ b/src/ports/SkDiscardableMemory_ashmem.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <unistd.h>
+#include <sys/mman.h>
+#include "SkDiscardableMemory.h"
+#include "SkTypes.h"
+#include "android/ashmem.h"
+
+////////////////////////////////////////////////////////////////////////////////
+namespace {
+/**
+ *  DiscardableMemory implementation that uses the Android kernel's
+ *  ashmem (Android shared memory).
+ */
+class SkAshmemDiscardableMemory : public SkDiscardableMemory {
+public:
+    SkAshmemDiscardableMemory(int fd, void* address, size_t size);
+    virtual ~SkAshmemDiscardableMemory();
+    virtual bool lock() SK_OVERRIDE;
+    virtual void* data() SK_OVERRIDE;
+    virtual void unlock() SK_OVERRIDE;
+private:
+    bool         fLocked;
+    int          fFd;
+    void*        fMemory;
+    const size_t fSize;
+};
+
+SkAshmemDiscardableMemory::SkAshmemDiscardableMemory(int fd,
+                                                     void* address,
+                                                     size_t size)
+    : fLocked(true)  // Ashmem pages are pinned by default.
+    , fFd(fd)
+    , fMemory(address)
+    , fSize(size) {
+    SkASSERT(fFd >= 0);
+    SkASSERT(fMemory != NULL);
+    SkASSERT(fSize > 0);
+}
+
+SkAshmemDiscardableMemory::~SkAshmemDiscardableMemory() {
+    SkASSERT(!fLocked);
+    if (NULL != fMemory) {
+        munmap(fMemory, fSize);
+    }
+    if (fFd != -1) {
+        close(fFd);
+    }
+}
+
+bool SkAshmemDiscardableMemory::lock() {
+    SkASSERT(!fLocked);
+    if (-1 == fFd) {
+        fLocked = false;
+        return false;
+    }
+    SkASSERT(fMemory != NULL);
+    if (fLocked || (ASHMEM_NOT_PURGED == ashmem_pin_region(fFd, 0, 0))) {
+        fLocked = true;
+        return true;
+    } else {
+        munmap(fMemory, fSize);
+        fMemory = NULL;
+
+        close(fFd);
+        fFd = -1;
+        fLocked = false;
+        return false;
+    }
+}
+
+void* SkAshmemDiscardableMemory::data() {
+    SkASSERT(fLocked);
+    return fLocked ? fMemory : NULL;
+}
+
+void SkAshmemDiscardableMemory::unlock() {
+    SkASSERT(fLocked);
+    if (fLocked && (fFd != -1)) {
+        ashmem_unpin_region(fFd, 0, 0);
+    }
+    fLocked = false;
+}
+}  // namespace
+////////////////////////////////////////////////////////////////////////////////
+
+SkDiscardableMemory* SkDiscardableMemory::Create(size_t bytes) {
+    // ashmem likes lengths on page boundaries.
+    const size_t mask = getpagesize() - 1;
+    size_t size = (bytes + mask) & ~mask;
+
+    static const char name[] = "Skia_Ashmem_Discardable_Memory";
+    int fd = ashmem_create_region(name, size);
+    if (fd < 0) {
+        return NULL;
+    }
+    if (0 != ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE)) {
+        close(fd);
+        return NULL;
+    }
+    void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+    if ((MAP_FAILED == addr) || (NULL == addr)) {
+        close(fd);
+        return NULL;
+    }
+
+    return SkNEW_ARGS(SkAshmemDiscardableMemory, (fd, addr, size));
+}
+
diff --git a/src/utils/android/ashmem.h b/src/utils/android/ashmem.h
index 278192b..94ffe1a 100644
--- a/src/utils/android/ashmem.h
+++ b/src/utils/android/ashmem.h
@@ -16,6 +16,12 @@
 
 int ashmem_create_region(const char *name, size_t size);
 int ashmem_set_prot_region(int fd, int prot);
+
+/**
+ *   @return ASHMEM_NOT_PURGED if the memory was not purged.
+ *           ASHMEM_WAS_PURGED if the memory was purged.
+ *           -1 on error.
+ */
 int ashmem_pin_region(int fd, size_t offset, size_t len);
 int ashmem_unpin_region(int fd, size_t offset, size_t len);
 int ashmem_get_size_region(int fd);
diff --git a/tests/CachedDecodingPixelRefTest.cpp b/tests/CachedDecodingPixelRefTest.cpp
index 6abcf3d..1e4ab3f 100644
--- a/tests/CachedDecodingPixelRefTest.cpp
+++ b/tests/CachedDecodingPixelRefTest.cpp
@@ -300,12 +300,12 @@
     REPORTER_ASSERT(reporter, 0 == pool->getRAMUsed());
 
     SkDiscardableMemoryPool* globalPool = SkGetGlobalDiscardableMemoryPool();
+    // Only acts differently from NULL on a platform that has a
+    // default discardable memory implementation that differs from the
+    // global DM pool.
     CheckPixelRef(TestImageGenerator::kFailGetPixels_TestType,
                   reporter, kSkDiscardable_PixelRefType, globalPool);
     CheckPixelRef(TestImageGenerator::kSucceedGetPixels_TestType,
                   reporter, kSkDiscardable_PixelRefType, globalPool);
-
-    // TODO(halcanary): When ashmem-backed SkDiscardableMemory lands,
-    // test that here (on platforms where it is availible).
 }
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/tests/DiscardableMemoryTest.cpp b/tests/DiscardableMemoryTest.cpp
new file mode 100644
index 0000000..1fbc284
--- /dev/null
+++ b/tests/DiscardableMemoryTest.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDiscardableMemory.h"
+
+#include "Test.h"
+#include "TestClassDef.h"
+
+DEF_TEST(DiscardableMemory, reporter) {
+    const char testString[] = "HELLO, WORLD!";
+    const size_t len = sizeof(testString);
+    SkAutoTDelete<SkDiscardableMemory> dm(SkDiscardableMemory::Create(len));
+    REPORTER_ASSERT(reporter, dm.get() != NULL);
+    if (NULL == dm.get()) {
+        return;
+    }
+    void* ptr = dm->data();
+    REPORTER_ASSERT(reporter, ptr != NULL);
+    memcpy(ptr, testString, sizeof(testString));
+    dm->unlock();
+    bool success = dm->lock();
+    REPORTER_ASSERT(reporter, success);
+    if (!success) {
+        return;
+    }
+    ptr = dm->data();
+    REPORTER_ASSERT(reporter, 0 == memcmp(ptr, testString, len));
+    dm->unlock();
+}
+
diff --git a/tools/LazyDecodeBitmap.cpp b/tools/LazyDecodeBitmap.cpp
index 9e850e5..f5ff147 100644
--- a/tools/LazyDecodeBitmap.cpp
+++ b/tools/LazyDecodeBitmap.cpp
@@ -17,7 +17,6 @@
 
 __SK_FORCE_IMAGE_DECODER_LINKING;
 
-// TODO(halcanary) Use this flag when ashmem-backed discardable memory lands.
 DEFINE_bool(useVolatileCache, false, "Use a volatile cache for deferred image decoding pixels. "
             "Only meaningful if --deferImageDecoding is set to true and the platform has an "
             "implementation.");
@@ -39,9 +38,11 @@
         return false;
     }
     SkDiscardableMemory::Factory* pool = NULL;
-    if (info.fWidth * info.fHeight > 32 * 1024) {
+    if ((!FLAGS_useVolatileCache) || (info.fWidth * info.fHeight < 32 * 1024)) {
         // how to do switching with SkDiscardableMemory.
         pool = SkGetGlobalDiscardableMemoryPool();
+        // Only meaningful if platform has a default discardable
+        // memory implementation that differs from the global DM pool.
     }
     return SkDiscardablePixelRef::Install(gen.detach(), dst, pool);
 }