Bug 1036637 - disable HDR on low-memory devices, r=dhylands
authorMike Habicher <mikeh@mozilla.com>
Sun, 13 Jul 2014 11:50:48 -0400
changeset 193659 88a2ebd864b079b08092a47ad7ddc9ef7882bea1
parent 193658 4480407ee78c773f8ccb738c8f14f4d00a4c7a06
child 193660 723764c6d5abb487485c38fc0263b86be6365d22
push id8843
push usermhabicher@mozilla.com
push dateSun, 13 Jul 2014 16:00:24 +0000
treeherderb2g-inbound@88a2ebd864b0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdhylands
bugs1036637
milestone33.0a1
Bug 1036637 - disable HDR on low-memory devices, r=dhylands
dom/camera/CameraPreferences.cpp
dom/camera/CameraPreferences.h
dom/camera/GonkCameraParameters.cpp
dom/camera/GonkCameraParameters.h
dom/camera/test/camera_common.js
dom/camera/test/test_camera_fake_parameters.html
modules/libpref/src/init/all.js
--- a/dom/camera/CameraPreferences.cpp
+++ b/dom/camera/CameraPreferences.cpp
@@ -17,41 +17,71 @@ static StaticAutoPtr<Monitor> sPrefMonit
 
 StaticAutoPtr<nsCString> CameraPreferences::sPrefTestEnabled;
 StaticAutoPtr<nsCString> CameraPreferences::sPrefHardwareTest;
 StaticAutoPtr<nsCString> CameraPreferences::sPrefGonkParameters;
 
 nsresult CameraPreferences::sPrefCameraControlMethodErrorOverride = NS_OK;
 nsresult CameraPreferences::sPrefCameraControlAsyncErrorOverride = NS_OK;
 
+uint32_t CameraPreferences::sPrefCameraControlLowMemoryThresholdMB = 0;
+
+bool CameraPreferences::sPrefCameraParametersIsLowMemory = false;
+
+#ifdef CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
 /* static */
 nsresult
 CameraPreferences::UpdatePref(const char* aPref, nsresult& aVal)
 {
   uint32_t val;
   nsresult rv = Preferences::GetUint(aPref, &val);
   if (NS_SUCCEEDED(rv)) {
     aVal = static_cast<nsresult>(val);
   }
   return rv;
 }
+#endif
+
+/* static */
+nsresult
+CameraPreferences::UpdatePref(const char* aPref, uint32_t& aVal)
+{
+  uint32_t val;
+  nsresult rv = Preferences::GetUint(aPref, &val);
+  if (NS_SUCCEEDED(rv)) {
+    aVal = val;
+  }
+  return rv;
+}
 
 /* static */
 nsresult
 CameraPreferences::UpdatePref(const char* aPref, nsACString& aVal)
 {
   nsCString val;
   nsresult rv = Preferences::GetCString(aPref, &val);
   if (NS_SUCCEEDED(rv)) {
     aVal = val;
   }
   return rv;
 }
 
 /* static */
+nsresult
+CameraPreferences::UpdatePref(const char* aPref, bool& aVal)
+{
+  bool val;
+  nsresult rv = Preferences::GetBool(aPref, &val);
+  if (NS_SUCCEEDED(rv)) {
+    aVal = val;
+  }
+  return rv;
+}
+
+/* static */
 CameraPreferences::Pref CameraPreferences::sPrefs[] = {
   {
     "camera.control.test.enabled",
     kPrefValueIsCString,
     { &sPrefTestEnabled }
   },
   {
     "camera.control.test.hardware",
@@ -62,24 +92,34 @@ CameraPreferences::Pref CameraPreference
   {
     "camera.control.test.hardware.gonk.parameters",
     kPrefValueIsCString,
     { &sPrefGonkParameters }
   },
 #endif
   {
     "camera.control.test.method.error",
-    kPrefValueIsNSResult,
+    kPrefValueIsNsResult,
     { &sPrefCameraControlMethodErrorOverride }
   },
   {
     "camera.control.test.async.error",
-    kPrefValueIsNSResult,
+    kPrefValueIsNsResult,
     { &sPrefCameraControlAsyncErrorOverride }
   },
+  {
+    "camera.control.test.is_low_memory",
+    kPrefValueIsBoolean,
+    { &sPrefCameraParametersIsLowMemory }
+  },
+  {
+    "camera.control.low_memory_thresholdMB",
+    kPrefValueIsUint32,
+    { &sPrefCameraControlLowMemoryThresholdMB }
+  },
 };
 
 /* static */
 uint32_t
 CameraPreferences::PrefToIndex(const char* aPref)
 {
   for (uint32_t i = 0; i < ArrayLength(sPrefs); ++i) {
     if (strcmp(aPref, sPrefs[i].mPref) == 0) {
@@ -99,36 +139,59 @@ CameraPreferences::PreferenceChanged(con
   if (i == kPrefNotFound) {
     DOM_CAMERA_LOGE("Preference '%s' is not tracked by CameraPreferences\n", aPref);
     return;
   }
 
   Pref& p = sPrefs[i];
   nsresult rv;
   switch (p.mValueType) {
-    case kPrefValueIsNSResult:
+    case kPrefValueIsNsResult:
+    #ifdef CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
       {
         nsresult& v = *p.mValue.mAsNsResult;
         rv = UpdatePref(aPref, v);
         if (NS_SUCCEEDED(rv)) {
           DOM_CAMERA_LOGI("Preference '%s' has changed, 0x%x\n", aPref, v);
         }
       }
       break;
+    #endif
+
+    case kPrefValueIsUint32:
+      {
+        uint32_t& v = *p.mValue.mAsUint32;
+        rv = UpdatePref(aPref, v);
+        if (NS_SUCCEEDED(rv)) {
+          DOM_CAMERA_LOGI("Preference '%s' has changed, %u\n", aPref, v);
+        }
+      }
+      break;
 
     case kPrefValueIsCString:
       {
         nsCString& v = **p.mValue.mAsCString;
         rv = UpdatePref(aPref, v);
         if (NS_SUCCEEDED(rv)) {
           DOM_CAMERA_LOGI("Preference '%s' has changed, '%s'\n", aPref, v.get());
         }
       }
       break;
 
+    case kPrefValueIsBoolean:
+      {
+        bool& v = *p.mValue.mAsBoolean;
+        rv = UpdatePref(aPref, v);
+        if (NS_SUCCEEDED(rv)) {
+          DOM_CAMERA_LOGI("Preference '%s' has changed, %s\n",
+            aPref, v ? "true" : "false");
+        }
+      }
+      break;
+
     default:
       MOZ_ASSERT_UNREACHABLE("Unhandled preference value type!");
       return;
   }
 
   if (NS_FAILED(rv)) {
     DOM_CAMERA_LOGE("Failed to get pref '%s' (0x%x)\n", aPref, rv);
   }
@@ -206,35 +269,83 @@ CameraPreferences::GetPref(const char* a
     return false;
   }
 
   DOM_CAMERA_LOGI("Preference '%s', got '%s'\n", aPref, (*s)->get());
   aVal = **s;
   return true;
 }
 
+#ifdef CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
 /* static */
 bool
 CameraPreferences::GetPref(const char* aPref, nsresult& aVal)
 {
   MOZ_ASSERT(sPrefMonitor, "sPrefMonitor missing in CameraPreferences::GetPref()");
   MonitorAutoLock mon(*sPrefMonitor);
 
   uint32_t i = PrefToIndex(aPref);
   if (i == kPrefNotFound || i >= ArrayLength(sPrefs)) {
     DOM_CAMERA_LOGW("Preference '%s' is not tracked by CameraPreferences\n", aPref);
     return false;
   }
-  if (sPrefs[i].mValueType != kPrefValueIsNSResult) {
+  if (sPrefs[i].mValueType != kPrefValueIsNsResult) {
     DOM_CAMERA_LOGW("Preference '%s' is not an nsresult type\n", aPref);
     return false;
   }
 
   nsresult v = *sPrefs[i].mValue.mAsNsResult;
   if (v == NS_OK) {
-    DOM_CAMERA_LOGI("Preference '%s' is not set\n", aPref);
+    DOM_CAMERA_LOGW("Preference '%s' is not set\n", aPref);
     return false;
   }
 
   DOM_CAMERA_LOGI("Preference '%s', got 0x%x\n", aPref, v);
   aVal = v;
   return true;
 }
+#endif
+
+/* static */
+bool
+CameraPreferences::GetPref(const char* aPref, uint32_t& aVal)
+{
+  MOZ_ASSERT(sPrefMonitor, "sPrefMonitor missing in CameraPreferences::GetPref()");
+  MonitorAutoLock mon(*sPrefMonitor);
+
+  uint32_t i = PrefToIndex(aPref);
+  if (i == kPrefNotFound || i >= ArrayLength(sPrefs)) {
+    DOM_CAMERA_LOGW("Preference '%s' is not tracked by CameraPreferences\n", aPref);
+    return false;
+  }
+  if (sPrefs[i].mValueType != kPrefValueIsUint32) {
+    DOM_CAMERA_LOGW("Preference '%s' is not a uint32_t type\n", aPref);
+    return false;
+  }
+
+  uint32_t v = *sPrefs[i].mValue.mAsUint32;
+  DOM_CAMERA_LOGI("Preference '%s', got %u\n", aPref, v);
+  aVal = v;
+  return true;
+}
+
+/* static */
+bool
+CameraPreferences::GetPref(const char* aPref, bool& aVal)
+{
+  MOZ_ASSERT(sPrefMonitor, "sPrefMonitor missing in CameraPreferences::GetPref()");
+  MonitorAutoLock mon(*sPrefMonitor);
+
+  uint32_t i = PrefToIndex(aPref);
+  if (i == kPrefNotFound || i >= ArrayLength(sPrefs)) {
+    DOM_CAMERA_LOGW("Preference '%s' is not tracked by CameraPreferences\n", aPref);
+    return false;
+  }
+  if (sPrefs[i].mValueType != kPrefValueIsBoolean) {
+    DOM_CAMERA_LOGW("Preference '%s' is not a boolean type\n", aPref);
+    return false;
+  }
+
+  bool v = *sPrefs[i].mValue.mAsBoolean;
+  DOM_CAMERA_LOGI("Preference '%s', got %s\n", aPref, v ? "true" : "false");
+  aVal = v;
+  return true;
+}
--- a/dom/camera/CameraPreferences.h
+++ b/dom/camera/CameraPreferences.h
@@ -3,64 +3,87 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DOM_CAMERA_CAMERAPREFERENCES_H
 #define DOM_CAMERA_CAMERAPREFERENCES_H
 
 #include "nsString.h"
 
+#if defined(MOZ_HAVE_CXX11_STRONG_ENUMS) || defined(MOZ_HAVE_CXX11_ENUM_TYPE)
+// Older compilers that don't support strongly-typed enums
+// just typedef uint32_t to nsresult, which results in conflicting
+// overloaded members in CameraPreferences.
+#define CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
+#endif
+
 namespace mozilla {
 
 template<class T> class StaticAutoPtr;
 
 class CameraPreferences
 {
 public:
   static bool Initialize();
   static void Shutdown();
 
   static bool GetPref(const char* aPref, nsACString& aVal);
+#ifdef CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
   static bool GetPref(const char* aPref, nsresult& aVal);
+#endif
+  static bool GetPref(const char* aPref, uint32_t& aVal);
+  static bool GetPref(const char* aPref, bool& aVal);
 
 protected:
   static const uint32_t kPrefNotFound = UINT32_MAX;
   static uint32_t PrefToIndex(const char* aPref);
 
   static void PreferenceChanged(const char* aPref, void* aClosure);
+#ifdef CAMERAPREFERENCES_HAVE_SEPARATE_UINT32_AND_NSRESULT
   static nsresult UpdatePref(const char* aPref, nsresult& aVar);
+#endif
+  static nsresult UpdatePref(const char* aPref, uint32_t& aVar);
   static nsresult UpdatePref(const char* aPref, nsACString& aVar);
+  static nsresult UpdatePref(const char* aPref, bool& aVar);
 
   enum PrefValueType {
-    kPrefValueIsNSResult,
-    kPrefValueIsCString
+    kPrefValueIsNsResult,
+    kPrefValueIsUint32,
+    kPrefValueIsCString,
+    kPrefValueIsBoolean
   };
   struct Pref {
     const char* const           mPref;
     PrefValueType               mValueType;
     union {
       // The 'mAsVoid' member must be first and is required to allow 'mValue'
       // to be initialized with any pointer type, as not all of our platforms
       // support the use of designated initializers; in their absence, only
       // the first element of a union can be statically initialized, and
       // 'void*' lets us stuff any pointer type into it.
       void*                     mAsVoid;
       StaticAutoPtr<nsCString>* mAsCString;
       nsresult*                 mAsNsResult;
+      uint32_t*                 mAsUint32;
+      bool*                     mAsBoolean;
     } mValue;
   };
   static Pref sPrefs[];
 
   static StaticAutoPtr<nsCString> sPrefTestEnabled;
   static StaticAutoPtr<nsCString> sPrefHardwareTest;
   static StaticAutoPtr<nsCString> sPrefGonkParameters;
 
   static nsresult sPrefCameraControlMethodErrorOverride;
   static nsresult sPrefCameraControlAsyncErrorOverride;
 
+  static uint32_t sPrefCameraControlLowMemoryThresholdMB;
+
+  static bool sPrefCameraParametersIsLowMemory;
+
 private:
   // static class only
   CameraPreferences();
   ~CameraPreferences();
 };
 
 } // namespace mozilla
 
--- a/dom/camera/GonkCameraParameters.cpp
+++ b/dom/camera/GonkCameraParameters.cpp
@@ -11,22 +11,50 @@
  * 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 "GonkCameraParameters.h"
 #include "camera/CameraParameters.h"
+#include "CameraPreferences.h"
 #include "ICameraControl.h"
 #include "CameraCommon.h"
+#include "mozilla/Hal.h"
 
 using namespace mozilla;
 using namespace android;
 
+/* static */ bool
+GonkCameraParameters::IsLowMemoryPlatform()
+{
+  bool testIsLowMem = false;
+  CameraPreferences::GetPref("camera.control.test.is_low_memory", testIsLowMem);
+  if (testIsLowMem) {
+    NS_WARNING("Forcing low-memory platform camera preferences");
+    return true;
+  }
+
+  uint32_t lowMemoryThresholdBytes = 0;
+  CameraPreferences::GetPref("camera.control.low_memory_thresholdMB",
+                             lowMemoryThresholdBytes);
+  lowMemoryThresholdBytes *= 1024 * 1024;
+  if (lowMemoryThresholdBytes) {
+    uint32_t totalMemoryBytes = hal::GetTotalSystemMemory();
+    if (totalMemoryBytes < lowMemoryThresholdBytes) {
+      DOM_CAMERA_LOGI("Low-memory platform with %d bytes of RAM (threshold: <%d bytes)\n",
+        totalMemoryBytes, lowMemoryThresholdBytes);
+      return true;
+    }
+  }
+
+  return false;
+}
+
 /* static */ const char*
 GonkCameraParameters::Parameters::GetTextKey(uint32_t aKey)
 {
   switch (aKey) {
     case CAMERA_PARAM_PREVIEWSIZE:
       return KEY_PREVIEW_SIZE;
     case CAMERA_PARAM_PREVIEWFORMAT:
       return KEY_PREVIEW_FORMAT;
@@ -241,44 +269,65 @@ GonkCameraParameters::Initialize()
   }
 
   // The return code from GetListAsArray() doesn't matter. If it fails,
   // the isoModes array will be empty, and the subsequent loop won't
   // execute.
   nsString s;
   nsTArray<nsCString> isoModes;
   GetListAsArray(CAMERA_PARAM_SUPPORTED_ISOMODES, isoModes);
-  for (uint32_t i = 0; i < isoModes.Length(); ++i) {
+  for (nsTArray<nsCString>::size_type i = 0; i < isoModes.Length(); ++i) {
     rv = MapIsoFromGonk(isoModes[i].get(), s);
     if (NS_FAILED(rv)) {
       DOM_CAMERA_LOGW("Unrecognized ISO mode value '%s'\n", isoModes[i].get());
       continue;
     }
     *mIsoModes.AppendElement() = s;
     mIsoModeMap.Put(s, new nsCString(isoModes[i]));
   }
 
+  GetListAsArray(CAMERA_PARAM_SUPPORTED_SCENEMODES, mSceneModes);
+  if (IsLowMemoryPlatform()) {
+    bool hdrRemoved = false;
+    while (mSceneModes.RemoveElement(NS_LITERAL_STRING("hdr"))) {
+      hdrRemoved = true;
+    }
+    if (hdrRemoved) {
+      DOM_CAMERA_LOGI("Disabling HDR support due to low memory\n");
+    }
+  }
+
   mInitialized = true;
   return NS_OK;
 }
 
 // Handle nsAStrings
 nsresult
 GonkCameraParameters::SetTranslated(uint32_t aKey, const nsAString& aValue)
 {
-  if (aKey == CAMERA_PARAM_ISOMODE) {
-    nsAutoCString v;
-    nsresult rv = MapIsoToGonk(aValue, v);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    return SetImpl(aKey, v.get());
+  switch (aKey) {
+    case CAMERA_PARAM_ISOMODE:
+      {
+        nsAutoCString v;
+        nsresult rv = MapIsoToGonk(aValue, v);
+        if (NS_FAILED(rv)) {
+          return rv;
+        }
+        return SetImpl(aKey, v.get());
+      }
+
+    case CAMERA_PARAM_SCENEMODE:
+      if (mSceneModes.IndexOf(aValue) == nsTArray<nsString>::NoIndex) {
+        return NS_ERROR_INVALID_ARG;
+      }
+      // fallthrough
+
+    default:
+      return SetImpl(aKey, NS_ConvertUTF16toUTF8(aValue).get());
   }
-
-  return SetImpl(aKey, NS_ConvertUTF16toUTF8(aValue).get());
 }
 
 nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, nsAString& aValue)
 {
   const char* val;
   nsresult rv = GetImpl(aKey, val);
   if (NS_FAILED(rv)) {
@@ -615,17 +664,17 @@ GonkCameraParameters::SetTranslated(uint
   }
 
   return SetImpl(aKey, aValue);
 }
 
 nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, double& aValue)
 {
-  double val;
+  double val = 0.0; // initialize to keep the compiler happy [-Wmaybe-uninitialized]
   int index = 0;
   double focusDistance[3];
   const char* s;
   nsresult rv;
 
   switch (aKey) {
     case CAMERA_PARAM_ZOOM:
       rv = GetImpl(aKey, index);
@@ -835,22 +884,28 @@ GonkCameraParameters::GetListAsArray(uin
   }
 
   return NS_OK;
 }
 
 nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, nsTArray<nsString>& aValues)
 {
-  if (aKey == CAMERA_PARAM_SUPPORTED_ISOMODES) {
-    aValues = mIsoModes;
-    return NS_OK;
+  switch (aKey) {
+    case CAMERA_PARAM_SUPPORTED_ISOMODES:
+      aValues = mIsoModes;
+      return NS_OK;
+
+    case CAMERA_PARAM_SUPPORTED_SCENEMODES:
+      aValues = mSceneModes;
+      return NS_OK;
+
+    default:
+      return GetListAsArray(aKey, aValues);
   }
-
-  return GetListAsArray(aKey, aValues);
 }
 
 nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, nsTArray<double>& aValues)
 {
   if (aKey == CAMERA_PARAM_SUPPORTED_ZOOMRATIOS) {
     aValues.Clear();
     for (uint32_t i = 0; i < mZoomRatios.Length(); ++i) {
--- a/dom/camera/GonkCameraParameters.h
+++ b/dom/camera/GonkCameraParameters.h
@@ -96,16 +96,17 @@ protected:
   bool mInitialized;
 
   // Required internal properties
   double mExposureCompensationStep;
   int32_t mExposureCompensationMinIndex;
   int32_t mExposureCompensationMaxIndex;
   nsTArray<int> mZoomRatios;
   nsTArray<nsString> mIsoModes;
+  nsTArray<nsString> mSceneModes;
   nsClassHashtable<nsStringHashKey, nsCString> mIsoModeMap;
 
   // This subclass of android::CameraParameters just gives
   // all of the AOSP getters and setters the same signature.
   class Parameters : public android::CameraParameters
   {
   public:
     using android::CameraParameters::set;
@@ -219,13 +220,17 @@ protected:
   //  - NS_OK on success;
   //  - NS_ERROR_INVALID_ARG if the 'aIso' argument is not a valid form.
   nsresult MapIsoToGonk(const nsAString& aIso, nsACString& aIsoOut);
   nsresult MapIsoFromGonk(const char* aIso, nsAString& aIsoOut);
 
   // Call once to initialize local cached values used in translating other
   // arguments between Gecko and Gonk. Always returns NS_OK.
   nsresult Initialize();
+
+  // Returns true if we're a memory-constrained platform that requires
+  // certain features to be disabled; returns false otherwise.
+  static bool IsLowMemoryPlatform();
 };
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_GONKCAMERAPARAMETERS_H
--- a/dom/camera/test/camera_common.js
+++ b/dom/camera/test/camera_common.js
@@ -30,16 +30,17 @@ var CameraTest = (function() {
    *
    * This means (of course) that neither the key not the value tokens can
    * contain either equals signs or semicolons. The test shim doesn't enforce
    * this so that we can test getting junk from the camera library as well.
    */
   const PREF_TEST_ENABLED = "camera.control.test.enabled";
   const PREF_TEST_HARDWARE = "camera.control.test.hardware";
   const PREF_TEST_EXTRA_PARAMETERS = "camera.control.test.hardware.gonk.parameters";
+  const PREF_TEST_FAKE_LOW_MEMORY = "camera.control.test.is_low_memory";
   var oldTestEnabled;
   var oldTestHw;
   var testMode;
 
   function testHardwareSetFakeParameters(parameters, callback) {
     SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_EXTRA_PARAMETERS, parameters]]}, function() {
       var setParams = SpecialPowers.getCharPref(PREF_TEST_EXTRA_PARAMETERS);
       ise(setParams, parameters, "Extra test parameters '" + setParams + "'");
@@ -48,16 +49,30 @@ var CameraTest = (function() {
       }
     });
   }
 
   function testHardwareClearFakeParameters(callback) {
     SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_EXTRA_PARAMETERS]]}, callback);
   }
 
+  function testHardwareSetFakeLowMemoryPlatform(callback) {
+    SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_FAKE_LOW_MEMORY, true]]}, function() {
+      var setParams = SpecialPowers.getBoolPref(PREF_TEST_FAKE_LOW_MEMORY);
+      ise(setParams, true, "Fake low memory platform");
+      if (callback) {
+        callback(setParams);
+      }
+    });
+  }
+
+  function testHardwareClearFakeLowMemoryPlatform(callback) {
+    SpecialPowers.pushPrefEnv({'clear': [[PREF_TEST_FAKE_LOW_MEMORY]]}, callback);
+  }
+
   function testHardwareSet(test, callback) {
     SpecialPowers.pushPrefEnv({'set': [[PREF_TEST_HARDWARE, test]]}, function() {
       var setTest = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
       ise(setTest, test, "Test subtype set to " + setTest);
       if (callback) {
         callback(setTest);
       }
     });
@@ -83,16 +98,18 @@ var CameraTest = (function() {
       if (setMode === "hardware") {
         try {
           oldTestHw = SpecialPowers.getCharPref(PREF_TEST_HARDWARE);
         } catch(e) { }
         testMode = {
           set: testHardwareSet,
           setFakeParameters: testHardwareSetFakeParameters,
           clearFakeParameters: testHardwareClearFakeParameters,
+          setFakeLowMemoryPlatform: testHardwareSetFakeLowMemoryPlatform,
+          clearFakeLowMemoryPlatform: testHardwareClearFakeLowMemoryPlatform,
           done: testHardwareDone
         };
         if (callback) {
           callback(testMode);
         }
       }
     });
   }
@@ -117,18 +134,26 @@ var CameraTest = (function() {
       var next = cleanUpTestEnabled;
       if (testMode) {
         testMode.done(next);
         testMode = null;
       } else {
         next();
       }
     }
+    function cleanUpLowMemoryPlatform() {
+      var next = cleanUpTest;
+      if (testMode) {
+        testMode.clearFakeLowMemoryPlatform(next);
+      } else {
+        next();
+      }
+    }
     function cleanUpExtraParameters() {
-      var next = cleanUpTest;
+      var next = cleanUpLowMemoryPlatform;
       if (testMode) {
         testMode.clearFakeParameters(next);
       } else {
         next();
       }
     }
 
     cleanUpExtraParameters();
--- a/dom/camera/test/test_camera_fake_parameters.html
+++ b/dom/camera/test/test_camera_fake_parameters.html
@@ -110,44 +110,93 @@ var tests = [
     },
     test: function testFakeZoomOutOfOrder(cam, cap) {
       ok(cap.zoomRatios.length == 1, "zoom ratios length = " + cap.zoomRatios.length);
       ok(cap.zoomRatios[0] == 1.0, "only supported zoom = " + cap.zoomRatios[0] + "x");
       next();
     }
   },
   {
+    key: "fake-high-memory-platform",
+    prep: function setupFakeHighMemoryPlatform(test) {
+      test.setFakeParameters("scene-mode-values=none,snow,beach,hdr,nothdr", function () {
+        run();
+      });
+    },
+    test: function testFakeHighMemoryPlatform(cam, cap) {
+      ok(cap.sceneModes.length == 5, "scene modes length = " + cap.zoomRatios.length);
+
+      // make sure expected values are present and can be set
+      [ "none", "snow", "beach", "hdr", "nothdr" ].forEach(function(mode) {
+        ok(cap.sceneModes.indexOf(mode) != -1, "Scene mode '" + mode + "' is present");
+        cam.sceneMode = mode;
+        ok(cam.sceneMode == mode, "Scene mode '" + cam.sceneMode + "' is set");
+      });
+
+      next();
+    }
+  },
+  {
+    key: "fake-low-memory-platform",
+    prep: function setupFakeLowMemoryPlatform(test) {
+      test.setFakeLowMemoryPlatform(function() {
+        test.setFakeParameters("scene-mode-values=none,hdr,snow,beach,hdr,nothdr", function () {
+          run();
+        });
+      });
+    },
+    test: function testFakeLowMemoryPlatform(cam, cap) {
+      ok(cap.sceneModes.length == 4, "scene modes length = " + cap.zoomRatios.length);
+
+      // make sure expected values are present and can be set
+      [ "none", "snow", "beach", "nothdr" ].forEach(function(mode) {
+        ok(cap.sceneModes.indexOf(mode) != -1, "Scene mode '" + mode + "' is present");
+        cam.sceneMode = mode;
+        ok(cam.sceneMode == mode, "Scene mode '" + cam.sceneMode + "' is set");
+      });
+
+      // make sure unsupported values have been removed, and can't be set
+      var sceneMode = cam.sceneMode;
+      [ "hdr" ].forEach(function(mode) {
+        ok(cap.sceneModes.indexOf(mode) == -1, "Scene mode '" + mode + "' is not present");
+        try {
+          cam.sceneMode = mode;
+        } catch(e) {
+        }
+        ok(cam.sceneMode != mode, "Scene mode '" + cam.sceneMode + "' is still set, '"
+          + mode + "' rejected");
+      });
+      ok(cam.sceneMode == sceneMode, "Scene mode '" + cam.sceneMode + "' is still set");
+
+      next();
+    }
+  },
+  {
     key: "fake-iso",
     prep: function setupFakeIso(test) {
       // we should recognize 'auto', 'hjr', and numeric modes; anything else
       // from the driver is ignored, which this test also verifies.
       test.setFakeParameters(
         "iso=auto;iso-values=auto,ISO_HJR,ISO100,foo,ISObar,ISO150moz,ISO200,400,ISO800,1600",
         function () {
         run();
       });
     },
     test: function testFakeIso(cam, cap) {
       ok(cap.isoModes.length == 7, "ISO modes length = " + cap.isoModes.length);
 
       // make sure we're not leaking any unexpected values formats
-      ok(cap.isoModes.indexOf("ISO_HJR") == -1, "ISO mode 'ISO_HJR' does not appear");
-      ok(cap.isoModes.indexOf("_HJR") == -1, "ISO mode '_HJR' does not appear");
-      ok(cap.isoModes.indexOf("HJR") == -1, "ISO mode 'HJR' does not appear");
-      ok(cap.isoModes.indexOf("ISO100") == -1, "ISO mode 'ISO100' does not appear");
-      ok(cap.isoModes.indexOf("ISO200") == -1, "ISO mode 'ISO200' does not appear");
-      ok(cap.isoModes.indexOf("ISO800") == -1, "ISO mode 'ISO800' does not appear");
+      [ "ISO_HJR", "_HJR", "HJR", "ISO100", "ISO200", "ISO800" ].forEach(function(iso) {
+        ok(cap.isoModes.indexOf(iso) == -1, "ISO mode '" + iso + "' does not appear");
+      });
 
       // make sure any weird values are dropped entirely
-      ok(cap.isoModes.indexOf("foo") == -1, "Unknown ISO mode 'foo' is ignored");
-      ok(cap.isoModes.indexOf("ISObar") == -1, "Unknown ISO mode 'ISObar' is ignored");
-      ok(cap.isoModes.indexOf("bar") == -1, "Unknown ISO mode 'bar' is ignored");
-      ok(cap.isoModes.indexOf("ISO150moz") == -1, "Unknown ISO mode 'ISO150moz' is ignored");
-      ok(cap.isoModes.indexOf("150moz") == -1, "Unknown ISO mode '150moz' is ignored");
-      ok(cap.isoModes.indexOf("150") == -1, "Unknown ISO mode '150' is ignored");
+      [ "foo", "ISObar", "bar", "ISO150moz", "150moz", "150" ].forEach(function(iso) {
+        ok(cap.isoModes.indexOf(iso) == -1, "Unknown ISO mode '" + iso + "' is ignored");
+      });
 
       // make sure expected values are present
       [ "auto", "hjr", "100", "200", "400", "800", "1600" ].forEach(function(iso) {
         ok(cap.isoModes.indexOf(iso) != -1, "ISO mode '" + iso + "' is present");
       });
 
       // test setters/getters for individual ISO modes
       cap.isoModes.forEach(function(iso, index) {
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4268,8 +4268,16 @@ pref("image.mozsamplesize.enabled", fals
 // play nicely with Firefox OS apps yet.
 #ifndef MOZ_WIDGET_GONK
 pref("beacon.enabled", true);
 #endif
 
 // Camera prefs
 pref("camera.control.autofocus_moving_callback.enabled", true);
 pref("camera.control.face_detection.enabled", true);
+#ifdef MOZ_WIDGET_GONK
+// Empirically, this is the value returned by hal::GetTotalSystemMemory()
+// when Flame's memory is limited to 512MiB. If the camera stack determines
+// it is running on a low memory platform, features that can be reliably
+// supported will be disabled. This threshold can be adjusted to suit other
+// platforms; and set to 0 to disable the low-memory check altogether.
+pref("camera.control.low_memory_thresholdMB", 404);
+#endif