Bug 965425 - expose ISO setting, r=dhylands,jst
authorMike Habicher <mikeh@mozilla.com>
Fri, 07 Mar 2014 01:00:43 -0500
changeset 172435 26779ad44626f8566f970247758465a4fabded16
parent 172434 f9e1959b21c68003a4cdb373afe2d97664e6414f
child 172436 4fdc4bd13f0ddd1c1f89dbcb06654c9c32ac3963
push id26359
push usercbook@mozilla.com
push dateFri, 07 Mar 2014 11:50:11 +0000
treeherdermozilla-central@ff96e428da76 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdhylands, jst
bugs965425
milestone30.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 965425 - expose ISO setting, r=dhylands,jst
dom/camera/DOMCameraCapabilities.cpp
dom/camera/DOMCameraCapabilities.h
dom/camera/DOMCameraControl.cpp
dom/camera/DOMCameraControl.h
dom/camera/GonkCameraParameters.cpp
dom/camera/GonkCameraParameters.h
dom/camera/ICameraControl.h
dom/camera/test/test_camera.html
dom/camera/test/test_camera_fake_parameters.html
dom/webidl/CameraCapabilities.webidl
dom/webidl/CameraControl.webidl
--- a/dom/camera/DOMCameraCapabilities.cpp
+++ b/dom/camera/DOMCameraCapabilities.cpp
@@ -129,16 +129,19 @@ CameraCapabilities::Populate(ICameraCont
   LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_EFFECTS);
 
   rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_FLASHMODES, mFlashModes);
   LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FLASHMODES);
 
   rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_FOCUSMODES, mFocusModes);
   LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_FOCUSMODES);
 
+  rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_ISOMODES, mIsoModes);
+  LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ISOMODES);
+
   rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, mZoomRatios);
   LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_ZOOMRATIOS);
 
   int32_t areas;
   rv = aCameraControl->Get(CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS, areas);
   LOG_IF_ERROR(rv, CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS);
   mMaxFocusAreas = areas < 0 ? 0 : areas;
 
@@ -273,10 +276,16 @@ CameraCapabilities::ExposureCompensation
 }
 
 JS::Value
 CameraCapabilities::RecorderProfiles(JSContext* aCx) const
 {
   return mRecorderProfiles;
 }
 
+void
+CameraCapabilities::GetIsoModes(nsTArray<nsString>& retval) const
+{
+  retval = mIsoModes;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/camera/DOMCameraCapabilities.h
+++ b/dom/camera/DOMCameraCapabilities.h
@@ -54,32 +54,34 @@ public:
   void GetFocusModes(nsTArray<nsString>& aRetVal) const;
   void GetZoomRatios(nsTArray<double>& aRetVal) const;
   uint32_t MaxFocusAreas() const;
   uint32_t MaxMeteringAreas() const;
   double MinExposureCompensation() const;
   double MaxExposureCompensation() const;
   double ExposureCompensationStep() const;
   JS::Value RecorderProfiles(JSContext* cx) const;
+  void GetIsoModes(nsTArray<nsString>& aRetVal) const;
 
 protected:
   nsresult TranslateToDictionary(ICameraControl* aCameraControl,
                                  uint32_t aKey, nsTArray<CameraSize>& aSizes);
 
   nsTArray<CameraSize> mPreviewSizes;
   nsTArray<CameraSize> mPictureSizes;
   nsTArray<CameraSize> mThumbnailSizes;
   nsTArray<CameraSize> mVideoSizes;
 
   nsTArray<nsString> mFileFormats;
   nsTArray<nsString> mWhiteBalanceModes;
   nsTArray<nsString> mSceneModes;
   nsTArray<nsString> mEffects;
   nsTArray<nsString> mFlashModes;
   nsTArray<nsString> mFocusModes;
+  nsTArray<nsString> mIsoModes;
 
   nsTArray<double> mZoomRatios;
 
   uint32_t mMaxFocusAreas;
   uint32_t mMaxMeteringAreas;
 
   double mMinExposureCompensation;
   double mMaxExposureCompensation;
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -394,16 +394,29 @@ nsDOMCameraControl::GetFocusMode(nsStrin
 }
 void
 nsDOMCameraControl::SetFocusMode(const nsAString& aFocusMode, ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
   aRv = mCameraControl->Set(CAMERA_PARAM_FOCUSMODE, aFocusMode);
 }
 
+void
+nsDOMCameraControl::GetIsoMode(nsString& aIsoMode, ErrorResult& aRv)
+{
+  MOZ_ASSERT(mCameraControl);
+  aRv = mCameraControl->Get(CAMERA_PARAM_ISOMODE, aIsoMode);
+}
+void
+nsDOMCameraControl::SetIsoMode(const nsAString& aIsoMode, ErrorResult& aRv)
+{
+  MOZ_ASSERT(mCameraControl);
+  aRv = mCameraControl->Set(CAMERA_PARAM_ISOMODE, aIsoMode);
+}
+
 double
 nsDOMCameraControl::GetZoom(ErrorResult& aRv)
 {
   MOZ_ASSERT(mCameraControl);
 
   double zoom;
   aRv = mCameraControl->Get(CAMERA_PARAM_ZOOM, zoom);
   return zoom;
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -74,16 +74,18 @@ public:
   double GetFocalLength(ErrorResult& aRv);
   double GetFocusDistanceNear(ErrorResult& aRv);
   double GetFocusDistanceOptimum(ErrorResult& aRv);
   double GetFocusDistanceFar(ErrorResult& aRv);
   void SetExposureCompensation(const dom::Optional<double>& aCompensation, ErrorResult& aRv);
   double GetExposureCompensation(ErrorResult& aRv);
   int32_t SensorAngle();
   already_AddRefed<dom::CameraCapabilities> Capabilities();
+  void GetIsoMode(nsString& aMode, ErrorResult& aRv);
+  void SetIsoMode(const nsAString& aMode, ErrorResult& aRv);
 
   // Unsolicited event handlers.
   already_AddRefed<dom::CameraShutterCallback> GetOnShutter();
   void SetOnShutter(dom::CameraShutterCallback* aCb);
   already_AddRefed<dom::CameraClosedCallback> GetOnClosed();
   void SetOnClosed(dom::CameraClosedCallback* aCb);
   already_AddRefed<dom::CameraRecorderStateChange> GetOnRecorderStateChange();
   void SetOnRecorderStateChange(dom::CameraRecorderStateChange* aCb);
--- a/dom/camera/GonkCameraParameters.cpp
+++ b/dom/camera/GonkCameraParameters.cpp
@@ -72,16 +72,20 @@ GonkCameraParameters::Parameters::GetTex
       // Not every platform defines a KEY_EXIF_DATETIME;
       // for those that don't, we use the raw string key, and if the platform
       // doesn't support it, it will be ignored.
       //
       // See bug 832494.
       return "exif-datetime";
     case CAMERA_PARAM_VIDEOSIZE:
       return KEY_VIDEO_SIZE;
+    case CAMERA_PARAM_ISOMODE:
+      // Not every platform defines KEY_ISO_MODE;
+      // for those that don't, we use the raw string key.
+      return "iso";
 
     case CAMERA_PARAM_SUPPORTED_PREVIEWSIZES:
       return KEY_SUPPORTED_PREVIEW_SIZES;
     case CAMERA_PARAM_SUPPORTED_PICTURESIZES:
       return KEY_SUPPORTED_PICTURE_SIZES;
     case CAMERA_PARAM_SUPPORTED_VIDEOSIZES:
       return KEY_SUPPORTED_VIDEO_SIZES;
     case CAMERA_PARAM_SUPPORTED_PICTUREFORMATS:
@@ -107,16 +111,20 @@ GonkCameraParameters::Parameters::GetTex
     case CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP:
       return KEY_EXPOSURE_COMPENSATION_STEP;
     case CAMERA_PARAM_SUPPORTED_ZOOM:
       return KEY_ZOOM_SUPPORTED;
     case CAMERA_PARAM_SUPPORTED_ZOOMRATIOS:
       return KEY_ZOOM_RATIOS;
     case CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES:
       return KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES;
+    case CAMERA_PARAM_SUPPORTED_ISOMODES:
+      // Not every platform defines KEY_SUPPORTED_ISO_MODES;
+      // for those that don't, we use the raw string key.
+      return "iso-values";
     default:
       DOM_CAMERA_LOGE("Unhandled camera parameter value %u\n", aKey);
       return nullptr;
   }
 }
 
 GonkCameraParameters::GonkCameraParameters()
   : mLock(PR_NewRWLock(PR_RWLOCK_RANK_NONE, "GonkCameraParameters.Lock"))
@@ -133,16 +141,53 @@ GonkCameraParameters::~GonkCameraParamet
 {
   MOZ_COUNT_DTOR(GonkCameraParameters);
   if (mLock) {
     PR_DestroyRWLock(mLock);
     mLock = nullptr;
   }
 }
 
+nsresult
+GonkCameraParameters::MapIsoToGonk(const nsAString& aIso, nsACString& aIsoOut)
+{
+  if (aIso.EqualsASCII("hjr")) {
+    aIsoOut = "ISO_HJR";
+  } else if (aIso.EqualsASCII("auto")) {
+    aIsoOut = "auto";
+  } else {
+    nsAutoCString v = NS_LossyConvertUTF16toASCII(aIso);
+    unsigned int iso;
+    if (sscanf(v.get(), "%u", &iso) != 1) {
+      return NS_ERROR_FAILURE;
+    }
+    aIsoOut = nsPrintfCString("ISO%u", iso);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GonkCameraParameters::MapIsoFromGonk(const char* aIso, nsAString& aIsoOut)
+{
+  if (strcmp(aIso, "ISO_HJR") == 0) {
+    aIsoOut.AssignASCII("hjr");
+  } else if (strcmp(aIso, "auto") == 0) {
+    aIsoOut.AssignASCII("auto");
+  } else {
+    unsigned int iso;
+    if (sscanf(aIso, "ISO%u", &iso) != 1) {
+      return NS_ERROR_FAILURE;
+    }
+    aIsoOut.AppendInt(iso);
+  }
+
+  return NS_OK;
+}
+
 // Any members that need to be initialized on the first parameter pull
 // need to get handled in here.
 nsresult
 GonkCameraParameters::Initialize()
 {
   nsresult rv;
 
   rv = GetImpl(CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION, mExposureCompensationMin);
@@ -156,37 +201,63 @@ GonkCameraParameters::Initialize()
     mExposureCompensationStep = 0;
   }
   rv = GetListAsArray(CAMERA_PARAM_SUPPORTED_ZOOMRATIOS, mZoomRatios);
   if (NS_FAILED(rv)) {
     // zoom is not supported
     mZoomRatios.Clear();
   }
 
+  // The return code from GetListAsArray() doesn't matter. If it fails,
+  // the isoModes array will be empty, and the subsequent loop won't
+  // execute.
+  nsTArray<nsCString> isoModes;
+  GetListAsArray(CAMERA_PARAM_SUPPORTED_ISOMODES, isoModes);
+  for (uint32_t i = 0; i < isoModes.Length(); ++i) {
+    nsString v;
+    rv = MapIsoFromGonk(isoModes[i].get(), v);
+    if (NS_SUCCEEDED(rv)) {
+      *mIsoModes.AppendElement() = v;
+    }
+  }
+
   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());
+  }
+
   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)) {
     return rv;
   }
-  aValue.AssignASCII(val);
-  return NS_OK;
+  if (aKey == CAMERA_PARAM_ISOMODE) {
+    rv = MapIsoFromGonk(val, aValue);
+  } else {
+    aValue.AssignASCII(val);
+  }
+  return rv;
 }
 
 // Handle ICameraControl::Sizes
 nsresult
 GonkCameraParameters::SetTranslated(uint32_t aKey, const ICameraControl::Size& aSize)
 {
   if (aSize.width > INT_MAX || aSize.height > INT_MAX) {
     // AOSP can only handle signed ints.
@@ -351,42 +422,57 @@ GonkCameraParameters::SetTranslated(uint
   }
   return NS_OK;
 }
 
 // Handle int64_ts
 nsresult
 GonkCameraParameters::SetTranslated(uint32_t aKey, const int64_t& aValue)
 {
-  if (aKey == CAMERA_PARAM_PICTURE_DATETIME) {
-    // Add the non-GPS timestamp.  The EXIF date/time field is formatted as
-    // "YYYY:MM:DD HH:MM:SS", without room for a time-zone; as such, the time
-    // is meant to be stored as a local time.  Since we are given seconds from
-    // Epoch GMT, we use localtime_r() to handle the conversion.
-    time_t time = aValue;
-    if (time != aValue) {
-      DOM_CAMERA_LOGE("picture date/time '%llu' is too far in the future\n", aValue);
-      return NS_ERROR_INVALID_ARG;
-    }
+  switch (aKey) {
+    case CAMERA_PARAM_PICTURE_DATETIME:
+      {
+        // Add the non-GPS timestamp.  The EXIF date/time field is formatted as
+        // "YYYY:MM:DD HH:MM:SS", without room for a time-zone; as such, the time
+        // is meant to be stored as a local time.  Since we are given seconds from
+        // Epoch GMT, we use localtime_r() to handle the conversion.
+        time_t time = aValue;
+        if (time != aValue) {
+          DOM_CAMERA_LOGE("picture date/time '%llu' is too far in the future\n", aValue);
+          return NS_ERROR_INVALID_ARG;
+        }
+
+        struct tm t;
+        if (!localtime_r(&time, &t)) {
+          DOM_CAMERA_LOGE("picture date/time couldn't be converted to local time: (%d) %s\n", errno, strerror(errno));
+          return NS_ERROR_FAILURE;
+        }
 
-    struct tm t;
-    if (!localtime_r(&time, &t)) {
-      DOM_CAMERA_LOGE("picture date/time couldn't be converted to local time: (%d) %s\n", errno, strerror(errno));
-      return NS_ERROR_FAILURE;
-    }
+        char dateTime[20];
+        if (!strftime(dateTime, sizeof(dateTime), "%Y:%m:%d %T", &t)) {
+          DOM_CAMERA_LOGE("picture date/time couldn't be converted to string\n");
+          return NS_ERROR_FAILURE;
+        }
+
+        DOM_CAMERA_LOGI("setting picture date/time to %s\n", dateTime);
+
+        return SetImpl(CAMERA_PARAM_PICTURE_DATETIME, dateTime);
+      }
 
-    char dateTime[20];
-    if (!strftime(dateTime, sizeof(dateTime), "%Y:%m:%d %T", &t)) {
-      DOM_CAMERA_LOGE("picture date/time couldn't be converted to string\n");
-      return NS_ERROR_FAILURE;
-    }
+    case CAMERA_PARAM_ISOMODE:
+      {
+        if (aValue > INT32_MAX) {
+          DOM_CAMERA_LOGW("Can't set ISO mode = %lld, too big\n", aValue);
+          return NS_ERROR_INVALID_ARG;
+        }
 
-    DOM_CAMERA_LOGI("setting picture date/time to %s\n", dateTime);
-
-    return SetImpl(CAMERA_PARAM_PICTURE_DATETIME, dateTime);
+        nsString s;
+        s.AppendInt(aValue);
+        return SetTranslated(CAMERA_PARAM_ISOMODE, s);
+      }
   }
 
   // You can't actually pass 64-bit parameters to Gonk. :(
   int32_t v = static_cast<int32_t>(aValue);
   if (static_cast<int64_t>(v) != aValue) {
     return NS_ERROR_INVALID_ARG;;
   }
   return SetImpl(aKey, v);
@@ -604,16 +690,27 @@ ParseItem(const char* aStart, const char
     aItem->AssignASCII(aStart, aEnd - aStart);
   } else {
     aItem->AssignASCII(aStart);
   }
   return NS_OK;
 }
 
 nsresult
+ParseItem(const char* aStart, const char* aEnd, nsACString* aItem)
+{
+  if (aEnd) {
+    aItem->AssignASCII(aStart, aEnd - aStart);
+  } else {
+    aItem->AssignASCII(aStart);
+  }
+  return NS_OK;
+}
+
+nsresult
 ParseItem(const char* aStart, const char* aEnd, double* aItem)
 {
   if (sscanf(aStart, "%lf", aItem) == 1) {
     return NS_OK;
   }
 
   return NS_ERROR_FAILURE;
 }
@@ -672,25 +769,30 @@ 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;
+  }
+
   return GetListAsArray(aKey, aValues);
 }
 
 nsresult
 GonkCameraParameters::GetTranslated(uint32_t aKey, nsTArray<double>& aValues)
 {
   if (aKey == CAMERA_PARAM_SUPPORTED_ZOOMRATIOS) {
     aValues.Clear();
-    for (int i = 0; i < mZoomRatios.Length(); ++i) {
+    for (uint32_t i = 0; i < mZoomRatios.Length(); ++i) {
       *aValues.AppendElement() = mZoomRatios[i] / 100.0;
     }
     return NS_OK;
   }
 
   return GetListAsArray(aKey, aValues);
 }
 
--- a/dom/camera/GonkCameraParameters.h
+++ b/dom/camera/GonkCameraParameters.h
@@ -1,10 +1,10 @@
 /*
- * Copyright (C) 2013 Mozilla Foundation
+ * Copyright (C) 2013-2014 Mozilla Foundation
  *
  * 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
@@ -90,16 +90,17 @@ protected:
   PRRWLock* mLock;
   bool mDirty;
   bool mInitialized;
 
   // Required internal properties
   double mExposureCompensationMin;
   double mExposureCompensationStep;
   nsTArray<int> mZoomRatios;
+  nsTArray<nsString> mIsoModes;
 
   // 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;
 
@@ -171,15 +172,17 @@ protected:
   nsresult SetTranslated(uint32_t aKey, const int& aValue);
   nsresult GetTranslated(uint32_t aKey, int& aValue);
   nsresult SetTranslated(uint32_t aKey, const uint32_t& aValue);
   nsresult GetTranslated(uint32_t aKey, uint32_t& aValue);
   nsresult GetTranslated(uint32_t aKey, nsTArray<nsString>& aValues);
   nsresult GetTranslated(uint32_t aKey, nsTArray<double>& aValues);
 
   template<class T> nsresult GetListAsArray(uint32_t aKey, nsTArray<T>& aArray);
+  nsresult MapIsoToGonk(const nsAString& aIso, nsACString& aIsoOut);
+  nsresult MapIsoFromGonk(const char* aIso, nsAString& aIsoOut);
 
   nsresult Initialize();
 };
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_GONKCAMERAPARAMETERS_H
--- a/dom/camera/ICameraControl.h
+++ b/dom/camera/ICameraControl.h
@@ -45,16 +45,17 @@ enum {
   CAMERA_PARAM_FOCUSDISTANCENEAR,
   CAMERA_PARAM_FOCUSDISTANCEOPTIMUM,
   CAMERA_PARAM_FOCUSDISTANCEFAR,
   CAMERA_PARAM_EXPOSURECOMPENSATION,
   CAMERA_PARAM_PICTURESIZE,
   CAMERA_PARAM_THUMBNAILSIZE,
   CAMERA_PARAM_THUMBNAILQUALITY,
   CAMERA_PARAM_SENSORANGLE,
+  CAMERA_PARAM_ISOMODE,
 
   // supported features
   CAMERA_PARAM_SUPPORTED_PREVIEWSIZES,
   CAMERA_PARAM_SUPPORTED_PICTURESIZES,
   CAMERA_PARAM_SUPPORTED_VIDEOSIZES,
   CAMERA_PARAM_SUPPORTED_PICTUREFORMATS,
   CAMERA_PARAM_SUPPORTED_WHITEBALANCES,
   CAMERA_PARAM_SUPPORTED_SCENEMODES,
@@ -63,17 +64,18 @@ enum {
   CAMERA_PARAM_SUPPORTED_FOCUSMODES,
   CAMERA_PARAM_SUPPORTED_MAXFOCUSAREAS,
   CAMERA_PARAM_SUPPORTED_MAXMETERINGAREAS,
   CAMERA_PARAM_SUPPORTED_MINEXPOSURECOMPENSATION,
   CAMERA_PARAM_SUPPORTED_MAXEXPOSURECOMPENSATION,
   CAMERA_PARAM_SUPPORTED_EXPOSURECOMPENSATIONSTEP,
   CAMERA_PARAM_SUPPORTED_ZOOM,
   CAMERA_PARAM_SUPPORTED_ZOOMRATIOS,
-  CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES
+  CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES,
+  CAMERA_PARAM_SUPPORTED_ISOMODES
 };
 
 class ICameraControl
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ICameraControl)
 
   static nsresult GetNumberOfCameras(int32_t& aDeviceCount);
--- a/dom/camera/test/test_camera.html
+++ b/dom/camera/test/test_camera.html
@@ -29,17 +29,17 @@ var config = {
 };
 
 function onError(e) {
   ok(false, "Error" + JSON.stringify(e));
 }
 
 var capabilities = [ 'previewSizes', 'pictureSizes', 'fileFormats', 'maxFocusAreas', 'minExposureCompensation',
                      'maxExposureCompensation', 'stepExposureCompensation', 'maxMeteringAreas', 'videoSizes',
-                     'recorderProfiles', 'zoomRatios'];
+                     'recorderProfiles', 'zoomRatios', 'isoModes'];
 
 var Camera = {
   cameraObj: null,
   _recording: false,
   _currentTest: null,
   _autoFocusSupported: 0,
   _manuallyFocused: false,
   _flashmodes: null,
@@ -146,18 +146,20 @@ var Camera = {
     Camera.getPictureSizes();
     Camera.getPreviewSizes();
     Camera.getFileFormats();
     Camera.getFocusModes();
     Camera.getZoomRatios();
     ok(Camera._previewSizes.length > 0, "previewSizes length = " + Camera._previewSizes.length);
     ok(Camera._pictureSizes.length > 0, "picturesizes length = " + Camera._pictureSizes.length);
     ok(Camera._fileFormats.length > 0, "file formats length = " + Camera._fileFormats.length);
-    info("zoom ratios length = " + Camera._zoomRatios.length);
+    ok(camcap.isoModes.length == 0, "ISO modes length = " + camcap.isoModes.length);
 
+    // The emulator doesn't support zoom, so these parameters will be very constrained
+    ok(Camera._zoomRatios.length == 1, "zoom ratios length = " + Camera._zoomRatios.length);
     if (Camera._zoomRatios.length > 0) {
       Camera._zoomRatios.forEach(function(element, index) {
         info("zoom[" + index + "] = " + element + "x");
         Camera.setZoom(element);
         ok(Camera.getZoom() === element, "zoom[" + index + "] = " + element + "x");
       });
 
       var zoom = Camera._zoomRatios[0] - 0.1;
--- a/dom/camera/test/test_camera_fake_parameters.html
+++ b/dom/camera/test/test_camera_fake_parameters.html
@@ -22,30 +22,40 @@ var initialConfig = {
     width: 352,
     height: 288
   }
 };
 
 var cameraObj = null;
 
 // Shorthand functions
+function onError(e) {
+  ok(false, "Error" + JSON.stringify(e));
+}
+
 function end() {
   CameraTest.end();
 }
 function next() {
-  CameraTest.next();
+  if (cameraObj) {
+    cameraObj.release(
+      function success() {
+        CameraTest.next();
+      },
+      onError
+    );
+    cameraObj = null;
+  } else {
+    CameraTest.next();
+  }
 }
 function run() {
   CameraTest.run();
 }
 
-function onError(e) {
-  ok(false, "Error" + JSON.stringify(e));
-}
-
 // The array of tests
 var tests = [
   {
     key: "fake-zoom",
     prep: function setupFakeZoom(test) {
       test.setFakeParameters("zoom-ratios=100,150,200,300,400;max-zoom=4", function() {
         run();
       });
@@ -81,28 +91,58 @@ var tests = [
         ok(cam.zoom === cap.zoomRatios[0],
           zoom + "x zoom rounded down to: " + cap.zoomRatios[0] +
           "x, cam.zoom = " + cam.zoom + "x");
       }
 
       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,ISO200,ISO400,ISO800,ISO1600",
+        function () {
+        run();
+      });
+    },
+    test: function testFakeIso(cam, cap) {
+      // value 'foo' should be missing
+      ok(cap.isoModes.length == 7, "ISO modes length = " + cap.isoModes.length);
+      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");
+
+      // test individual ISO modes
+      cap.isoModes.forEach(function(iso, index) {
+        cam.iso = iso;
+        ok(cam.iso === iso,
+          "ISO[" + index + "] = " + iso + ", cam.iso = " + cam.iso);
+      });
+
+      next();
+    }
+  },
 ];
 
 var testGenerator = function() {
   for (var i = 0; i < tests.length; ++i ) {
     yield tests[i];
   }
 }();
 
 window.addEventListener('beforeunload', function() {
   document.getElementById('viewfinder').mozSrcObject = null;
-  cameraObj.release();
-  cameraObj = null;
+  if (cameraObj) {
+    cameraObj.release();
+    cameraObj = null;
+  }
 });
 
 CameraTest.begin("hardware", function(test) {
   function onError(error) {
     ok(false, "getCamera() failed with: " + error);
     end();
   }
 
--- a/dom/webidl/CameraCapabilities.webidl
+++ b/dom/webidl/CameraCapabilities.webidl
@@ -25,9 +25,11 @@ interface CameraCapabilities
   [Constant, Cached] readonly attribute unsigned long maxFocusAreas;
   [Constant, Cached] readonly attribute unsigned long maxMeteringAreas;
 
   [Constant, Cached] readonly attribute double minExposureCompensation;
   [Constant, Cached] readonly attribute double maxExposureCompensation;
   [Constant, Cached] readonly attribute double exposureCompensationStep;
 
   [Constant, Cached] readonly attribute any recorderProfiles;
+
+  [Constant, Cached] readonly attribute sequence<DOMString> isoModes;
 };
--- a/dom/webidl/CameraControl.webidl
+++ b/dom/webidl/CameraControl.webidl
@@ -226,16 +226,21 @@ interface CameraControl : MediaStream
      acceptable values must range from minExposureCompensation
      to maxExposureCompensation in steps of stepExposureCompensation;
      invalid values will be rounded to the nearest valid value. */
   [Throws]
   void setExposureCompensation(optional double compensation);
   [Throws]
   readonly attribute unrestricted double exposureCompensation;
 
+  /* one of the values chosen from capabilities.isoModes; default
+     value is "auto" if supported. */
+  [Throws]
+  attribute DOMString       isoMode;
+
   /* the function to call on the camera's shutter event, to trigger
      a shutter sound and/or a visual shutter indicator. */
   attribute CameraShutterCallback? onShutter;
 
   /* the function to call when the camera hardware is closed
      by the underlying framework, e.g. when another app makes a more
      recent call to get the camera. */
   attribute CameraClosedCallback? onClosed;