Bug 1036637 - disable HDR on low-memory devices, r=dhylands
authorMike Habicher <mikeh@mozilla.com>
Sun, 13 Jul 2014 11:50:48 -0400
changeset 215580 88a2ebd864b079b08092a47ad7ddc9ef7882bea1
parent 215579 4480407ee78c773f8ccb738c8f14f4d00a4c7a06
child 215581 723764c6d5abb487485c38fc0263b86be6365d22
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdhylands
bugs1036637
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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