diff --git a/browser/locales/en-US/searchplugins/google.xml b/browser/locales/en-US/searchplugins/google.xml index 4cd56d30fba..a285056fbae 100644 --- a/browser/locales/en-US/searchplugins/google.xml +++ b/browser/locales/en-US/searchplugins/google.xml @@ -16,7 +16,7 @@ Google Google Search UTF-8 -data:image/png;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA +data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABUUlEQVR42pWTzUsCYRCH9y9zu3SooCCkjhIRRLeIykXokiWCJ7PvDpZRlz6si1lIQZ3SQxQdOhREpgSm0JeQvfu0+i6I7LKLh4F5h5nnnRl+o6jTdHn8omAYbVqhXqvYFXcEBKFDwcoZZB8B4LkEB9cwGGmFKHb01A1EU9JXzfdvDYZi1lwLwBcVAIwsNWPesIwls7gDtB2Z7N9ujVe+IX2LO2AgItB1OL9vJqsmILDrOoK02IkBAdYy4FsQJC5h+VQCHQDWTqYSgo8fuHuRxS4Ae3stQ7UGE5ttAHqCUgfxC7m4ryrowOyeO6CxqHwZxtYFqtYc5+kNan/gDTsAeueEIRj7n/rmRQMwueUAGF0VAAT3rQBTC0Y3DoDOGbm00icML4oWHYSTgo0MFqjlmPpDgqMcFCuQf4erBzjOwXjcriu9qHg0uutO2+es6fl67T9ptebvFRjBVgAAAABJRU5ErkJggg== #expand __GOOGLE_PARAMS__ diff --git a/configure.in b/configure.in index 66c69296b0a..9b26859bd0b 100644 --- a/configure.in +++ b/configure.in @@ -1,3 +1,4 @@ + dnl -*- Mode: Autoconf; tab-width: 4; indent-tabs-mode: nil; -*- dnl vi: set tabstop=4 shiftwidth=4 expandtab syntax=m4: dnl This Source Code Form is subject to the terms of the Mozilla Public @@ -197,7 +198,7 @@ if test -n "$gonkdir" ; then ;; esac - CPPFLAGS="-DANDROID -isystem $gonkdir/bionic/libc/$ARCH_DIR/include -isystem $gonkdir/bionic/libc/include/ -isystem $gonkdir/bionic/libc/kernel/common -isystem $gonkdir/bionic/libc/kernel/$ARCH_DIR -isystem $gonkdir/bionic/libm/include -I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/hardware/libhardware/include -I$gonkdir/hardware/libhardware_legacy/include -I$gonkdir/system -I$gonkdir/system/core/include -isystem $gonkdir/bionic -I$gonkdir/frameworks/base/include -I$gonkdir/external/dbus -I$gonkdir/external/bluetooth/bluez/lib $CPPFLAGS -I$gonkdir/frameworks/base/services/sensorservice -I$gonkdir/frameworks/base/services/camera -I$gonkdir/system/media/wilhelm/include" + CPPFLAGS="-DANDROID -isystem $gonkdir/bionic/libc/$ARCH_DIR/include -isystem $gonkdir/bionic/libc/include/ -isystem $gonkdir/bionic/libc/kernel/common -isystem $gonkdir/bionic/libc/kernel/$ARCH_DIR -isystem $gonkdir/bionic/libm/include -I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/hardware/libhardware/include -I$gonkdir/hardware/libhardware_legacy/include -I$gonkdir/system -I$gonkdir/system/core/include -isystem $gonkdir/bionic -I$gonkdir/frameworks/base/include -I$gonkdir/external/dbus -I$gonkdir/external/bluetooth/bluez/lib $CPPFLAGS -I$gonkdir/frameworks/base/services/sensorservice -I$gonkdir/frameworks/base/services/camera -I$gonkdir/system/media/wilhelm/include -I$gonkdir/frameworks/base/include/media/stagefright -I$gonkdir/frameworks/base/include/media/stagefright/openmax -I$gonkdir/frameworks/base/media/libstagefright/rtsp -I$gonkdir/frameworks/base/media/libstagefright/include -I$gonkdir/dalvik/libnativehelper/include/nativehelper" CFLAGS="-mandroid -fno-short-enums -fno-exceptions $CFLAGS" CXXFLAGS="-mandroid -fno-short-enums -fno-exceptions -Wno-psabi $CXXFLAGS $STLPORT_CPPFLAGS" dnl Add -llog by default, since we use it all over the place. diff --git a/docshell/test/file_bug590573_1.html b/docshell/test/file_bug590573_1.html index 40da53f1a55..850d418bde1 100644 --- a/docshell/test/file_bug590573_1.html +++ b/docshell/test/file_bug590573_1.html @@ -2,7 +2,7 @@ -
This is a very tall div.
+
This is a very tall div.
diff --git a/dom/browser-element/BrowserElementScrolling.js b/dom/browser-element/BrowserElementScrolling.js index 06ac4f3c9c7..0061aeb2e9e 100644 --- a/dom/browser-element/BrowserElementScrolling.js +++ b/dom/browser-element/BrowserElementScrolling.js @@ -206,42 +206,20 @@ const ContentPanning = { let metrics = data.json; let displayPort = metrics.displayPort; - let screenWidth = metrics.screenSize.width; - let screenHeight = metrics.screenSize.height; + let compositionWidth = metrics.compositionBounds.width; + let compositionHeight = metrics.compositionBounds.height; let x = metrics.x; let y = metrics.y; this._zoom = metrics.zoom; this._viewport = new Rect(x, y, - screenWidth / metrics.zoom, - screenHeight / metrics.zoom); + compositionWidth / metrics.zoom, + compositionHeight / metrics.zoom); this._cssPageRect = new Rect(metrics.cssPageRect.x, metrics.cssPageRect.y, metrics.cssPageRect.width, metrics.cssPageRect.height); - - let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - if (this._screenWidth != screenWidth || this._screenHeight != screenHeight) { - cwu.setCSSViewport(screenWidth, screenHeight); - this._screenWidth = screenWidth; - this._screenHeight = screenHeight; - } - - // Set scroll position - cwu.setScrollPositionClampingScrollPortSize( - screenWidth / metrics.zoom, screenHeight / metrics.zoom); - content.scrollTo(x, y); - cwu.setResolution(displayPort.resolution, displayPort.resolution); - - let element = null; - if (content.document && (element = content.document.documentElement)) { - cwu.setDisplayPortForElement(displayPort.left, - displayPort.top, - displayPort.width, - displayPort.height, - element); - } }, _recvDoubleTap: function(data) { @@ -272,7 +250,7 @@ const ContentPanning = { let cssPageRect = this._cssPageRect; let viewport = this._viewport; - let bRect = new Rect(Math.max(cssPageRect.left, rect.x - margin), + let bRect = new Rect(Math.max(cssPageRect.x, rect.x - margin), rect.y, rect.w + 2 * margin, rect.h); diff --git a/dom/camera/AudioParameter.cpp b/dom/camera/AudioParameter.cpp new file mode 100644 index 00000000000..59ccfd00f3c --- /dev/null +++ b/dom/camera/AudioParameter.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2006-2011 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. + */ + +#define LOG_TAG "AudioParameter" +//#define LOG_NDEBUG 0 + +#include + +#include + +namespace android { + +const char *AudioParameter::keyRouting = "routing"; +const char *AudioParameter::keySamplingRate = "sampling_rate"; +const char *AudioParameter::keyFormat = "format"; +const char *AudioParameter::keyChannels = "channels"; +const char *AudioParameter::keyFrameCount = "frame_count"; +const char *AudioParameter::keyInputSource = "input_source"; + +AudioParameter::AudioParameter(const String8& keyValuePairs) +{ + char *str = new char[keyValuePairs.length()+1]; + mKeyValuePairs = keyValuePairs; + + strcpy(str, keyValuePairs.string()); + char *pair = strtok(str, ";"); + while (pair != NULL) { + if (strlen(pair) != 0) { + size_t eqIdx = strcspn(pair, "="); + String8 key = String8(pair, eqIdx); + String8 value; + if (eqIdx == strlen(pair)) { + value = String8(""); + } else { + value = String8(pair + eqIdx + 1); + } + if (mParameters.indexOfKey(key) < 0) { + mParameters.add(key, value); + } else { + mParameters.replaceValueFor(key, value); + } + } else { + LOGV("AudioParameter() cstor empty key value pair"); + } + pair = strtok(NULL, ";"); + } + + delete[] str; +} + +AudioParameter::~AudioParameter() +{ + mParameters.clear(); +} + +String8 AudioParameter::toString() +{ + String8 str = String8(""); + + size_t size = mParameters.size(); + for (size_t i = 0; i < size; i++) { + str += mParameters.keyAt(i); + str += "="; + str += mParameters.valueAt(i); + if (i < (size - 1)) str += ";"; + } + return str; +} + +status_t AudioParameter::add(const String8& key, const String8& value) +{ + if (mParameters.indexOfKey(key) < 0) { + mParameters.add(key, value); + return NO_ERROR; + } else { + mParameters.replaceValueFor(key, value); + return ALREADY_EXISTS; + } +} + +status_t AudioParameter::addInt(const String8& key, const int value) +{ + char str[12]; + if (snprintf(str, 12, "%d", value) > 0) { + String8 str8 = String8(str); + return add(key, str8); + } else { + return BAD_VALUE; + } +} + +status_t AudioParameter::addFloat(const String8& key, const float value) +{ + char str[23]; + if (snprintf(str, 23, "%.10f", value) > 0) { + String8 str8 = String8(str); + return add(key, str8); + } else { + return BAD_VALUE; + } +} + +status_t AudioParameter::remove(const String8& key) +{ + if (mParameters.indexOfKey(key) >= 0) { + mParameters.removeItem(key); + return NO_ERROR; + } else { + return BAD_VALUE; + } +} + +status_t AudioParameter::get(const String8& key, String8& value) +{ + if (mParameters.indexOfKey(key) >= 0) { + value = mParameters.valueFor(key); + return NO_ERROR; + } else { + return BAD_VALUE; + } +} + +status_t AudioParameter::getInt(const String8& key, int& value) +{ + String8 str8; + status_t result = get(key, str8); + value = 0; + if (result == NO_ERROR) { + int val; + if (sscanf(str8.string(), "%d", &val) == 1) { + value = val; + } else { + result = INVALID_OPERATION; + } + } + return result; +} + +status_t AudioParameter::getFloat(const String8& key, float& value) +{ + String8 str8; + status_t result = get(key, str8); + value = 0; + if (result == NO_ERROR) { + float val; + if (sscanf(str8.string(), "%f", &val) == 1) { + value = val; + } else { + result = INVALID_OPERATION; + } + } + return result; +} + +status_t AudioParameter::getAt(size_t index, String8& key, String8& value) +{ + if (mParameters.size() > index) { + key = mParameters.keyAt(index); + value = mParameters.valueAt(index); + return NO_ERROR; + } else { + return BAD_VALUE; + } +} + +}; // namespace android diff --git a/dom/camera/CameraControlImpl.cpp b/dom/camera/CameraControlImpl.cpp index 8d974b1aaf0..df4a8843fd8 100644 --- a/dom/camera/CameraControlImpl.cpp +++ b/dom/camera/CameraControlImpl.cpp @@ -196,9 +196,9 @@ CameraControlImpl::TakePicture(CameraSize aSize, int32_t aRotation, const nsAStr } nsresult -CameraControlImpl::StartRecording(CameraSize aSize, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError) +CameraControlImpl::StartRecording(nsIDOMDeviceStorage* aStorageArea, const nsAString& aFilename, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError) { - nsCOMPtr startRecordingTask = new StartRecordingTask(this, aSize, onSuccess, onError); + nsCOMPtr startRecordingTask = new StartRecordingTask(this, aStorageArea, aFilename, onSuccess, onError); return mCameraThread->Dispatch(startRecordingTask, NS_DISPATCH_NORMAL); } @@ -223,6 +223,13 @@ CameraControlImpl::StopPreview() mCameraThread->Dispatch(stopPreviewTask, NS_DISPATCH_NORMAL); } +nsresult +CameraControlImpl::GetPreviewStreamVideoMode(CameraRecordingOptions* aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError) +{ + nsCOMPtr getPreviewStreamVideoModeTask = new GetPreviewStreamVideoModeTask(this, *aOptions, onSuccess, onError); + return mCameraThread->Dispatch(getPreviewStreamVideoModeTask, NS_DISPATCH_NORMAL); +} + bool CameraControlImpl::ReceiveFrame(void* aBuffer, ImageFormat aFormat, FrameBuilder aBuilder) { diff --git a/dom/camera/CameraControlImpl.h b/dom/camera/CameraControlImpl.h index c59b221e99e..b74845a9e53 100644 --- a/dom/camera/CameraControlImpl.h +++ b/dom/camera/CameraControlImpl.h @@ -8,6 +8,7 @@ #include "nsCOMPtr.h" #include "nsDOMFile.h" #include "DictionaryHelpers.h" +#include "nsIDOMDeviceStorage.h" #include "nsIDOMCameraManager.h" #include "ICameraControl.h" #include "CameraCommon.h" @@ -25,6 +26,7 @@ class StartRecordingTask; class StopRecordingTask; class SetParameterTask; class GetParameterTask; +class GetPreviewStreamVideoModeTask; class DOMCameraPreview; @@ -39,6 +41,7 @@ class CameraControlImpl : public ICameraControl friend class StopRecordingTask; friend class SetParameterTask; friend class GetParameterTask; + friend class GetPreviewStreamVideoModeTask; public: CameraControlImpl(uint32_t aCameraId, nsIThread* aCameraThread) @@ -64,8 +67,9 @@ public: void StopPreview(); nsresult AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError); nsresult TakePicture(CameraSize aSize, int32_t aRotation, const nsAString& aFileFormat, CameraPosition aPosition, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError); - nsresult StartRecording(CameraSize aSize, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError); + nsresult StartRecording(nsIDOMDeviceStorage* aStorageArea, const nsAString& aFilename, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError); nsresult StopRecording(); + nsresult GetPreviewStreamVideoMode(CameraRecordingOptions* aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError); nsresult Set(uint32_t aKey, const nsAString& aValue); nsresult Get(uint32_t aKey, nsAString& aValue); @@ -111,6 +115,7 @@ protected: virtual nsresult StopRecordingImpl(StopRecordingTask* aStopRecording) = 0; virtual nsresult PushParametersImpl() = 0; virtual nsresult PullParametersImpl() = 0; + virtual nsresult GetPreviewStreamVideoModeImpl(GetPreviewStreamVideoModeTask* aGetPreviewStreamVideoMode) = 0; uint32_t mCameraId; nsCOMPtr mCameraThread; @@ -347,9 +352,8 @@ public: class StartRecordingResult : public nsRunnable { public: - StartRecordingResult(nsIDOMMediaStream* aStream, nsICameraStartRecordingCallback* onSuccess) - : mStream(aStream) - , mOnSuccessCb(onSuccess) + StartRecordingResult(nsICameraStartRecordingCallback* onSuccess) + : mOnSuccessCb(onSuccess) { } virtual ~StartRecordingResult() { } @@ -359,13 +363,12 @@ public: MOZ_ASSERT(NS_IsMainThread()); if (mOnSuccessCb) { - mOnSuccessCb->HandleEvent(mStream); + mOnSuccessCb->HandleEvent(); } return NS_OK; } protected: - nsCOMPtr mStream; nsCOMPtr mOnSuccessCb; }; @@ -373,9 +376,10 @@ protected: class StartRecordingTask : public nsRunnable { public: - StartRecordingTask(CameraControlImpl* aCameraControl, CameraSize aSize, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError) - : mSize(aSize) - , mCameraControl(aCameraControl) + StartRecordingTask(CameraControlImpl* aCameraControl, nsIDOMDeviceStorage* aStorageArea, const nsAString& aFilename, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError) + : mCameraControl(aCameraControl) + , mStorageArea(aStorageArea) + , mFilename(aFilename) , mOnSuccessCb(onSuccess) , mOnErrorCb(onError) { @@ -391,17 +395,21 @@ public: { DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); nsresult rv = mCameraControl->StartRecordingImpl(this); - DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__); + DOM_CAMERA_LOGT("%s:%d : result %d\n", __func__, __LINE__, rv); - if (NS_FAILED(rv) && mOnErrorCb) { + if (NS_SUCCEEDED(rv)) { + if (mOnSuccessCb) { + rv = NS_DispatchToMainThread(new StartRecordingResult(mOnSuccessCb)); + } + } else if (mOnErrorCb) { rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"))); - NS_ENSURE_SUCCESS(rv, rv); } return rv; } - CameraSize mSize; nsRefPtr mCameraControl; + nsCOMPtr mStorageArea; + nsString mFilename; nsCOMPtr mOnSuccessCb; nsCOMPtr mOnErrorCb; }; @@ -491,6 +499,67 @@ public: nsRefPtr mCameraControl; }; +// Return the resulting preview stream to JS. Runs on the main thread. +class GetPreviewStreamVideoModeResult : public nsRunnable +{ +public: + GetPreviewStreamVideoModeResult(nsIDOMMediaStream* aStream, nsICameraPreviewStreamCallback* onSuccess) + : mStream(aStream) + , mOnSuccessCb(onSuccess) + { + DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); + } + + virtual ~GetPreviewStreamVideoModeResult() + { + DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this); + } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mOnSuccessCb) { + mOnSuccessCb->HandleEvent(mStream); + } + return NS_OK; + } + +protected: + nsCOMPtr mStream; + nsCOMPtr mOnSuccessCb; +}; + +// Get the video mode preview stream. +class GetPreviewStreamVideoModeTask : public nsRunnable +{ +public: + GetPreviewStreamVideoModeTask(CameraControlImpl* aCameraControl, CameraRecordingOptions aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError) + : mCameraControl(aCameraControl) + , mOptions(aOptions) + , mOnSuccessCb(onSuccess) + , mOnErrorCb(onError) + { } + + NS_IMETHOD Run() + { + DOM_CAMERA_LOGI("%s:%d -- BEFORE IMPL\n", __func__, __LINE__); + nsresult rv = mCameraControl->GetPreviewStreamVideoModeImpl(this); + DOM_CAMERA_LOGI("%s:%d -- AFTER IMPL : rv = %d\n", __func__, __LINE__, rv); + + if (NS_FAILED(rv) && mOnErrorCb) { + rv = NS_DispatchToMainThread(new CameraErrorResult(mOnErrorCb, NS_LITERAL_STRING("FAILURE"))); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; + } + + nsRefPtr mCameraControl; + CameraRecordingOptions mOptions; + nsCOMPtr mOnSuccessCb; + nsCOMPtr mOnErrorCb; +}; + } // namespace mozilla #endif // DOM_CAMERA_CAMERACONTROLIMPL_H diff --git a/dom/camera/DOMCameraControl.cpp b/dom/camera/DOMCameraControl.cpp index c05f10294b9..e4be8d22b49 100644 --- a/dom/camera/DOMCameraControl.cpp +++ b/dom/camera/DOMCameraControl.cpp @@ -9,6 +9,7 @@ #include "nsThread.h" #include "mozilla/Services.h" #include "nsIObserverService.h" +#include "nsIDOMDeviceStorage.h" #include "DOMCameraManager.h" #include "DOMCameraCapabilities.h" #include "DOMCameraControl.h" @@ -218,16 +219,12 @@ nsDOMCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter) return NS_ERROR_NOT_IMPLEMENTED; } -/* void startRecording (in jsval aOptions, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +/* [implicit_jscontext] void startRecording (in nsIDOMDeviceStorage storageArea, in DOMString filename, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ NS_IMETHODIMP -nsDOMCameraControl::StartRecording(const JS::Value& aOptions, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) +nsDOMCameraControl::StartRecording(nsIDOMDeviceStorage* storageArea, const nsAString& filename, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) { NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); - CameraSize size; - nsresult rv = size.Init(cx, &aOptions); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr obs = mozilla::services::GetObserverService(); if (!obs) { NS_WARNING("Could not get the Observer service for CameraControl::StartRecording."); @@ -238,7 +235,7 @@ nsDOMCameraControl::StartRecording(const JS::Value& aOptions, nsICameraStartReco "recording-device-events", NS_LITERAL_STRING("starting").get()); - return mCameraControl->StartRecording(size, onSuccess, onError); + return mCameraControl->StartRecording(storageArea, filename, onSuccess, onError); } /* void stopRecording (); */ @@ -316,6 +313,19 @@ nsDOMCameraControl::TakePicture(const JS::Value& aOptions, nsICameraTakePictureC return mCameraControl->TakePicture(size, options.rotation, options.fileFormat, pos, onSuccess, onError); } +/* [implicit_jscontext] void GetPreviewStreamVideoMode (in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError); */ +NS_IMETHODIMP +nsDOMCameraControl::GetPreviewStreamVideoMode(const JS::Value& aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx) +{ + NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG); + + CameraRecordingOptions options; + nsresult rv = options.Init(cx, &aOptions); + NS_ENSURE_SUCCESS(rv, rv); + + return mCameraControl->GetPreviewStreamVideoMode(&options, onSuccess, onError); +} + class GetCameraResult : public nsRunnable { public: diff --git a/dom/camera/GonkCameraControl.cpp b/dom/camera/GonkCameraControl.cpp index 115bc8d5a22..6b60351ca0f 100644 --- a/dom/camera/GonkCameraControl.cpp +++ b/dom/camera/GonkCameraControl.cpp @@ -15,6 +15,10 @@ */ #include +#include +#include +#include +#include #include "base/basictypes.h" #include "libcameraservice/CameraHardwareInterface.h" #include "camera/CameraParameters.h" @@ -23,6 +27,8 @@ #include "nsMemory.h" #include "jsapi.h" #include "nsThread.h" +#include +#include "nsDirectoryServiceDefs.h" // for NS_GetSpecialDirectory #include "nsPrintfCString.h" #include "DOMCameraManager.h" #include "GonkCameraHwMgr.h" @@ -547,19 +553,27 @@ nsGonkCameraControl::StartPreviewImpl(StartPreviewTask* aStartPreview) } nsresult -nsGonkCameraControl::StopPreviewImpl(StopPreviewTask* aStopPreview) +nsGonkCameraControl::StopPreviewInternal(bool aForced) { DOM_CAMERA_LOGI("%s: stopping preview\n", __func__); // StopPreview() is a synchronous call--it doesn't return // until the camera preview thread exits. - GonkCameraHardware::StopPreview(mHwHandle); - mDOMPreview->Stopped(); - mDOMPreview = nullptr; + if (mDOMPreview) { + GonkCameraHardware::StopPreview(mHwHandle); + mDOMPreview->Stopped(aForced); + mDOMPreview = nullptr; + } return NS_OK; } +nsresult +nsGonkCameraControl::StopPreviewImpl(StopPreviewTask* aStopPreview) +{ + return StopPreviewInternal(); +} + nsresult nsGonkCameraControl::AutoFocusImpl(AutoFocusTask* aAutoFocus) { @@ -691,13 +705,71 @@ nsGonkCameraControl::PullParametersImpl() nsresult nsGonkCameraControl::StartRecordingImpl(StartRecordingTask* aStartRecording) { - return NS_ERROR_NOT_IMPLEMENTED; + mStartRecordingOnSuccessCb = aStartRecording->mOnSuccessCb; + mStartRecordingOnErrorCb = aStartRecording->mOnErrorCb; + + /** + * We need to pull in the base path from aStartRecording->mStorageArea + * once that feature lands. See bug 795201. + * + * For now, we just assume /sdcard/Movies. + * + * Also, the camera app needs to provide the file extension '.3gp' for now. + * See bug 795202. + */ +#if 1 + nsCOMPtr filename; + aStartRecording->mStorageArea->GetRootDirectory(getter_AddRefs(filename)); + filename->Append(aStartRecording->mFilename); + + nsAutoCString pathname; + filename->GetNativePath(pathname); +#else + nsAutoCString pathname(NS_LITERAL_CSTRING("/sdcard/Movies/")); + nsAutoCString filename(NS_ConvertUTF16toUTF8(aStartRecording->mFilename)); + + // Make sure that the file name doesn't contain any directory components. + if (strcmp(filename.get(), basename(filename.get())) != 0) { + DOM_CAMERA_LOGE("Video filename '%s' is not valid\n", filename.get()); + return NS_ERROR_INVALID_ARG; + } + + pathname.Append(filename); +#endif + DOM_CAMERA_LOGI("Video pathname is '%s'\n", pathname.get()); + int fd = open(pathname.get(), O_RDWR | O_CREAT, 0644); + if (fd < 0) { + DOM_CAMERA_LOGE("Couldn't create file '%s' with error (%d) %s\n", pathname.get(), errno, strerror(errno)); + return NS_ERROR_FAILURE; + } + + if (SetupRecording(fd) != NS_OK) { + DOM_CAMERA_LOGE("SetupRecording() failed\n"); + close(fd); + return NS_ERROR_FAILURE; + } + if (mRecorder->start() != OK) { + DOM_CAMERA_LOGE("mRecorder->start() failed\n"); + close(fd); + return NS_ERROR_FAILURE; + } + + // dispatch the callback + nsCOMPtr startRecordingResult = new StartRecordingResult(mStartRecordingOnSuccessCb); + nsresult rv = NS_DispatchToMainThread(startRecordingResult); + if (NS_FAILED(rv)) { + DOM_CAMERA_LOGE("Failed to dispatch start recording result to main thread (%d)!", rv); + } + return NS_OK; } nsresult nsGonkCameraControl::StopRecordingImpl(StopRecordingTask* aStopRecording) { - return NS_ERROR_NOT_IMPLEMENTED; + mRecorder->stop(); + delete mRecorder; + mRecorder = nullptr; + return NS_OK; } void @@ -809,6 +881,152 @@ nsGonkCameraControl::SetPreviewSize(uint32_t aWidth, uint32_t aHeight) PushParameters(); } +nsresult +nsGonkCameraControl::SetupVideoMode() +{ + // read preferences for camcorder + mMediaProfiles = MediaProfiles::getInstance(); + + /** + * Right now default to profile 3, which is 352x288 on Otoro. In the + * future, allow the application to select a recording quality and + * configuration. + * + * See bug 795379. + */ + int quality = 3; // cif:352x288 + camcorder_quality q = static_cast(quality); + mDuration = mMediaProfiles->getCamcorderProfileParamByName("duration", (int)mCameraId, q); + mVideoFileFormat = mMediaProfiles->getCamcorderProfileParamByName("file.format", (int)mCameraId, q); + mVideoCodec = mMediaProfiles->getCamcorderProfileParamByName("vid.codec", (int)mCameraId, q); + mVideoBitRate = mMediaProfiles->getCamcorderProfileParamByName("vid.bps", (int)mCameraId, q); + mVideoFrameRate = mMediaProfiles->getCamcorderProfileParamByName("vid.fps", (int)mCameraId, q); + mVideoFrameWidth = mMediaProfiles->getCamcorderProfileParamByName("vid.width", (int)mCameraId, q); + mVideoFrameHeight = mMediaProfiles->getCamcorderProfileParamByName("vid.height", (int)mCameraId, q); + mAudioCodec = mMediaProfiles->getCamcorderProfileParamByName("aud.codec", (int)mCameraId, q); + mAudioBitRate = mMediaProfiles->getCamcorderProfileParamByName("aud.bps", (int)mCameraId, q); + mAudioSampleRate = mMediaProfiles->getCamcorderProfileParamByName("aud.hz", (int)mCameraId, q); + mAudioChannels = mMediaProfiles->getCamcorderProfileParamByName("aud.ch", (int)mCameraId, q); + + if (mVideoFrameRate == -1) { + DOM_CAMERA_LOGE("Failed to get a valid frame rate!\n"); + DOM_CAMERA_LOGE("Also got width=%d, height=%d\n", mVideoFrameWidth, mVideoFrameHeight); + return NS_ERROR_FAILURE; + } + + PullParametersImpl(); + + // Configure camera video recording parameters. + const size_t SIZE = 256; + char buffer[SIZE]; + + /** + * Ignore the width and height settings from app, just use the one in profile. + * Eventually, will try to choose a profile which respects the settings from app. + * See bug 795330. + */ + mParams.setPreviewSize(mVideoFrameWidth, mVideoFrameHeight); + mParams.setPreviewFrameRate(mVideoFrameRate); + snprintf(buffer, SIZE, "%dx%d", mVideoFrameWidth, mVideoFrameHeight); + + /** + * "record-size" is probably deprecated in later ICS; + * might need to set "video-size" instead of "record-size". + * See bug 795332. + */ + mParams.set("record-size", buffer); + + /** + * If we want to enable picture-taking _while_ recording video, this sets the + * size of the captured picture. For now, just set it to the same dimensions + * as the video we're recording; ideally, we should probably make sure it + * matches one of the supported picture sizes. + */ + mParams.setPictureSize(mVideoFrameWidth, mVideoFrameHeight); + + PushParametersImpl(); + return NS_OK; +} + +#ifndef CHECK_SETARG +#define CHECK_SETARG(x) \ + do { \ + if (x) { \ + DOM_CAMERA_LOGE(#x " failed\n"); \ + return NS_ERROR_INVALID_ARG; \ + } \ + } while(0) +#endif + +nsresult +nsGonkCameraControl::SetupRecording(int aFd) +{ + // choosing a size big enough to hold the params + const size_t SIZE = 256; + char buffer[SIZE]; + + mRecorder = new GonkRecorder(); + CHECK_SETARG(mRecorder->init()); + + // set all the params + CHECK_SETARG(mRecorder->setCameraHandle((int32_t)mHwHandle)); + CHECK_SETARG(mRecorder->setAudioSource(AUDIO_SOURCE_CAMCORDER)); + CHECK_SETARG(mRecorder->setVideoSource(VIDEO_SOURCE_CAMERA)); + CHECK_SETARG(mRecorder->setOutputFormat((output_format)mVideoFileFormat)); + CHECK_SETARG(mRecorder->setVideoFrameRate(mVideoFrameRate)); + CHECK_SETARG(mRecorder->setVideoSize(mVideoFrameWidth, mVideoFrameHeight)); + snprintf(buffer, SIZE, "video-param-encoding-bitrate=%d", mVideoBitRate); + CHECK_SETARG(mRecorder->setParameters(String8(buffer))); + CHECK_SETARG(mRecorder->setVideoEncoder((video_encoder)mVideoCodec)); + snprintf(buffer, SIZE, "audio-param-encoding-bitrate=%d", mAudioBitRate); + CHECK_SETARG(mRecorder->setParameters(String8(buffer))); + snprintf(buffer, SIZE, "audio-param-number-of-channels=%d", mAudioChannels); + CHECK_SETARG(mRecorder->setParameters(String8(buffer))); + snprintf(buffer, SIZE, "audio-param-sampling-rate=%d", mAudioSampleRate); + CHECK_SETARG(mRecorder->setParameters(String8(buffer))); + CHECK_SETARG(mRecorder->setAudioEncoder((audio_encoder)mAudioCodec)); + // TODO: For now there is no limit on recording duration (See bug 795090) + CHECK_SETARG(mRecorder->setParameters(String8("max-duration=-1"))); + // TODO: For now there is no limit on file size (See bug 795090) + CHECK_SETARG(mRecorder->setParameters(String8("max-filesize=-1"))); + snprintf(buffer, SIZE, "video-param-rotation-angle-degrees=%d", mVideoRotation); + CHECK_SETARG(mRecorder->setParameters(String8(buffer))); + + // recording API needs file descriptor of output file + CHECK_SETARG(mRecorder->setOutputFile(aFd, 0, 0)); + CHECK_SETARG(mRecorder->prepare()); + return NS_OK; +} + +nsresult +nsGonkCameraControl::GetPreviewStreamVideoModeImpl(GetPreviewStreamVideoModeTask* aGetPreviewStreamVideoMode) +{ + nsCOMPtr getPreviewStreamResult = nullptr; + + // stop any currently running preview + StopPreviewInternal(true /* forced */); + + // copy the recording preview options + mVideoRotation = aGetPreviewStreamVideoMode->mOptions.rotation; + mVideoWidth = aGetPreviewStreamVideoMode->mOptions.width; + mVideoHeight = aGetPreviewStreamVideoMode->mOptions.height; + DOM_CAMERA_LOGI("recording preview format: %d x %d (w x h) (rotated %d degrees)\n", mVideoWidth, mVideoHeight, mVideoRotation); + + // setup the video mode + nsresult rv = SetupVideoMode(); + NS_ENSURE_SUCCESS(rv, rv); + + // create and return new preview stream object + getPreviewStreamResult = new GetPreviewStreamResult(this, mVideoWidth, mVideoHeight, mVideoFrameRate, aGetPreviewStreamVideoMode->mOnSuccessCb); + rv = NS_DispatchToMainThread(getPreviewStreamResult); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch GetPreviewStreamVideoMode() onSuccess callback to main thread!"); + return rv; + } + + return NS_OK; +} + // Gonk callback handlers. namespace mozilla { diff --git a/dom/camera/GonkCameraControl.h b/dom/camera/GonkCameraControl.h index db82b7b9a25..0e9bec15800 100644 --- a/dom/camera/GonkCameraControl.h +++ b/dom/camera/GonkCameraControl.h @@ -24,6 +24,7 @@ #include "DOMCameraControl.h" #include "CameraControlImpl.h" #include "CameraCommon.h" +#include "GonkRecorder.h" namespace mozilla { @@ -47,6 +48,9 @@ public: void SetParameter(uint32_t aKey, const nsTArray& aRegions); nsresult PushParameters(); + nsresult SetupRecording(int aFd); + nsresult SetupVideoMode(); + void AutoFocusComplete(bool aSuccess); void TakePictureComplete(uint8_t* aData, uint32_t aLength); @@ -56,12 +60,14 @@ protected: nsresult GetPreviewStreamImpl(GetPreviewStreamTask* aGetPreviewStream); nsresult StartPreviewImpl(StartPreviewTask* aStartPreview); nsresult StopPreviewImpl(StopPreviewTask* aStopPreview); + nsresult StopPreviewInternal(bool aForced = false); nsresult AutoFocusImpl(AutoFocusTask* aAutoFocus); nsresult TakePictureImpl(TakePictureTask* aTakePicture); nsresult StartRecordingImpl(StartRecordingTask* aStartRecording); nsresult StopRecordingImpl(StopRecordingTask* aStopRecording); nsresult PushParametersImpl(); nsresult PullParametersImpl(); + nsresult GetPreviewStreamVideoModeImpl(GetPreviewStreamVideoModeTask* aGetPreviewStreamVideoMode); void SetPreviewSize(uint32_t aWidth, uint32_t aHeight); @@ -84,6 +90,27 @@ protected: uint32_t mFps; uint32_t mDiscardedFrameCount; + android::MediaProfiles* mMediaProfiles; + android::GonkRecorder* mRecorder; + + PRUint32 mVideoRotation; + PRUint32 mVideoWidth; + PRUint32 mVideoHeight; + nsString mVideoFile; + + // camcorder profile settings for the desired quality level + int mDuration; // max recording duration (ignored) + int mVideoFileFormat; // output file format + int mVideoCodec; // video encoder + int mVideoBitRate; // video bit rate + int mVideoFrameRate; // video frame rate + int mVideoFrameWidth; // video frame width + int mVideoFrameHeight;// video frame height + int mAudioCodec; // audio encoder + int mAudioBitRate; // audio bit rate + int mAudioSampleRate; // audio sample rate + int mAudioChannels; // number of audio channels + private: nsGonkCameraControl(const nsGonkCameraControl&) MOZ_DELETE; nsGonkCameraControl& operator=(const nsGonkCameraControl&) MOZ_DELETE; diff --git a/dom/camera/GonkCameraHwMgr.cpp b/dom/camera/GonkCameraHwMgr.cpp index 9b6dfd41533..d22471b803c 100644 --- a/dom/camera/GonkCameraHwMgr.cpp +++ b/dom/camera/GonkCameraHwMgr.cpp @@ -143,6 +143,34 @@ GonkCameraHardware::NotifyCallback(int32_t aMsgType, int32_t ext1, int32_t ext2, } } +void +GonkCameraHardware::DataCallbackTimestamp(nsecs_t aTimestamp, int32_t aMsgType, const sp &aDataPtr, void* aUser) +{ + DOM_CAMERA_LOGI("%s",__func__); + GonkCameraHardware* hw = GetHardware((uint32_t)aUser); + if (!hw) { + DOM_CAMERA_LOGE("%s:aUser = %d resolved to no camera hw\n", __func__, (uint32_t)aUser); + return; + } + if (hw->mClosing) { + return; + } + + sp listener; + { + //TODO + //Mutex::Autolock _l(hw->mLock); + listener = hw->mListener; + } + if (listener.get()) { + DOM_CAMERA_LOGI("Listener registered, posting recording frame!"); + listener->postDataTimestamp(aTimestamp, aMsgType, aDataPtr); + } else { + DOM_CAMERA_LOGW("No listener was set. Drop a recording frame."); + hw->mHardware->releaseRecordingFrame(aDataPtr); + } +} + void GonkCameraHardware::Init() { @@ -162,7 +190,7 @@ GonkCameraHardware::Init() if (sHwHandle == 0) { sHwHandle = 1; // don't use 0 } - mHardware->setCallbacks(GonkCameraHardware::NotifyCallback, GonkCameraHardware::DataCallback, NULL, (void*)sHwHandle); + mHardware->setCallbacks(GonkCameraHardware::NotifyCallback, GonkCameraHardware::DataCallback, GonkCameraHardware::DataCallbackTimestamp, (void*)sHwHandle); mInitialized = true; } @@ -314,3 +342,81 @@ GonkCameraHardware::StopPreview(uint32_t aHwHandle) hw->mHardware->stopPreview(); } } + +int +GonkCameraHardware::StartRecording(uint32_t aHwHandle) +{ + DOM_CAMERA_LOGI("%s: aHwHandle = %d\n", __func__, aHwHandle); + int rv = OK; + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return DEAD_OBJECT; + } + + if (hw->mHardware->recordingEnabled()) { + return OK; + } + + if (!hw->mHardware->previewEnabled()) { + DOM_CAMERA_LOGW("Preview was not enabled, enabling now!\n"); + rv = StartPreview(aHwHandle); + if (rv != OK) { + return rv; + } + } + + // start recording mode + hw->mHardware->enableMsgType(CAMERA_MSG_VIDEO_FRAME); + DOM_CAMERA_LOGI("Calling hw->startRecording\n"); + rv = hw->mHardware->startRecording(); + if (rv != OK) { + DOM_CAMERA_LOGE("mHardware->startRecording() failed with status %d", rv); + } + return rv; +} + +int +GonkCameraHardware::StopRecording(uint32_t aHwHandle) +{ + DOM_CAMERA_LOGI("%s: aHwHandle = %d\n", __func__, aHwHandle); + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return DEAD_OBJECT; + } + + hw->mHardware->disableMsgType(CAMERA_MSG_VIDEO_FRAME); + hw->mHardware->stopRecording(); + return OK; +} + +int +GonkCameraHardware::SetListener(uint32_t aHwHandle, const sp& aListener) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return DEAD_OBJECT; + } + + hw->mListener = aListener; + return OK; +} + +void +GonkCameraHardware::ReleaseRecordingFrame(uint32_t aHwHandle, const sp& aFrame) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (hw) { + hw->mHardware->releaseRecordingFrame(aFrame); + } +} + +int +GonkCameraHardware::StoreMetaDataInBuffers(uint32_t aHwHandle, bool aEnabled) +{ + GonkCameraHardware* hw = GetHardware(aHwHandle); + if (!hw) { + return DEAD_OBJECT; + } + + return hw->mHardware->storeMetaDataInBuffers(aEnabled); +} diff --git a/dom/camera/GonkCameraHwMgr.h b/dom/camera/GonkCameraHwMgr.h index 25833b3c55a..56f28ebd46c 100644 --- a/dom/camera/GonkCameraHwMgr.h +++ b/dom/camera/GonkCameraHwMgr.h @@ -20,6 +20,8 @@ #include "libcameraservice/CameraHardwareInterface.h" #include "binder/IMemory.h" #include "mozilla/ReentrantMonitor.h" +#include "GonkCameraListener.h" +#include #include "GonkCameraControl.h" #include "CameraCommon.h" @@ -46,6 +48,7 @@ protected: static void DataCallback(int32_t aMsgType, const sp &aDataPtr, camera_frame_metadata_t* aMetadata, void* aUser); static void NotifyCallback(int32_t aMsgType, int32_t ext1, int32_t ext2, void* aUser); + static void DataCallbackTimestamp(nsecs_t aTimestamp, int32_t aMsgType, const sp& aDataPtr, void* aUser); public: virtual void OnNewFrame() MOZ_OVERRIDE; @@ -60,6 +63,11 @@ public: static void StopPreview(uint32_t aHwHandle); static int PushParameters(uint32_t aHwHandle, const CameraParameters& aParams); static void PullParameters(uint32_t aHwHandle, CameraParameters& aParams); + static int StartRecording(uint32_t aHwHandle); + static int StopRecording(uint32_t aHwHandle); + static int SetListener(uint32_t aHwHandle, const sp& aListener); + static void ReleaseRecordingFrame(uint32_t aHwHandle, const sp& aFrame); + static int StoreMetaDataInBuffers(uint32_t aHwHandle, bool aEnabled); protected: static GonkCameraHardware* sHw; @@ -93,6 +101,7 @@ protected: struct timespec mStart; struct timespec mAutoFocusStart; #endif + sp mListener; bool mInitialized; bool IsInitialized() diff --git a/dom/camera/GonkCameraListener.h b/dom/camera/GonkCameraListener.h new file mode 100644 index 00000000000..243264c2e9d --- /dev/null +++ b/dom/camera/GonkCameraListener.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2008 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 GONK_CAMERA_LISTENER_H +#define GONK_CAMERA_LISTENER_H + +#include +#include "libcameraservice/CameraHardwareInterface.h" + +namespace android { + +// ref-counted object for callbacks +class GonkCameraListener: virtual public RefBase +{ +public: + virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2) = 0; + virtual void postData(int32_t msgType, const sp& dataPtr, + camera_frame_metadata_t *metadata) = 0; + virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp& dataPtr) = 0; +}; + +}; // namespace android + +#endif diff --git a/dom/camera/GonkCameraSource.cpp b/dom/camera/GonkCameraSource.cpp new file mode 100644 index 00000000000..73331eda9b8 --- /dev/null +++ b/dom/camera/GonkCameraSource.cpp @@ -0,0 +1,733 @@ +/* + * Copyright (C) 2009 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 +#include "nsDebug.h" +#define DOM_CAMERA_LOG_LEVEL 3 +#include "CameraCommon.h" +#define LOGD DOM_CAMERA_LOGA +#define LOGV DOM_CAMERA_LOGI +#define LOGI DOM_CAMERA_LOGI +#define LOGW DOM_CAMERA_LOGW +#define LOGE DOM_CAMERA_LOGE + +#include +#include "GonkCameraSource.h" +#include "GonkCameraListener.h" +#include "GonkCameraHwMgr.h" +#include +#include +#include +#include +#include +#include + +using namespace mozilla; +namespace android { + +static const int64_t CAMERA_SOURCE_TIMEOUT_NS = 3000000000LL; + +struct GonkCameraSourceListener : public GonkCameraListener { + GonkCameraSourceListener(const sp &source); + + virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2); + virtual void postData(int32_t msgType, const sp &dataPtr, + camera_frame_metadata_t *metadata); + + virtual void postDataTimestamp( + nsecs_t timestamp, int32_t msgType, const sp& dataPtr); + +protected: + virtual ~GonkCameraSourceListener(); + +private: + wp mSource; + + GonkCameraSourceListener(const GonkCameraSourceListener &); + GonkCameraSourceListener &operator=(const GonkCameraSourceListener &); +}; + +GonkCameraSourceListener::GonkCameraSourceListener(const sp &source) + : mSource(source) { +} + +GonkCameraSourceListener::~GonkCameraSourceListener() { +} + +void GonkCameraSourceListener::notify(int32_t msgType, int32_t ext1, int32_t ext2) { + LOGV("notify(%d, %d, %d)", msgType, ext1, ext2); +} + +void GonkCameraSourceListener::postData(int32_t msgType, const sp &dataPtr, + camera_frame_metadata_t *metadata) { + LOGV("postData(%d, ptr:%p, size:%d)", + msgType, dataPtr->pointer(), dataPtr->size()); + + sp source = mSource.promote(); + if (source.get() != NULL) { + source->dataCallback(msgType, dataPtr); + } +} + +void GonkCameraSourceListener::postDataTimestamp( + nsecs_t timestamp, int32_t msgType, const sp& dataPtr) { + + sp source = mSource.promote(); + if (source.get() != NULL) { + source->dataCallbackTimestamp(timestamp/1000, msgType, dataPtr); + } +} + +static int32_t getColorFormat(const char* colorFormat) { + return OMX_COLOR_FormatYUV420SemiPlanar; + + if (!strcmp(colorFormat, CameraParameters::PIXEL_FORMAT_YUV420P)) { + return OMX_COLOR_FormatYUV420Planar; + } + + if (!strcmp(colorFormat, CameraParameters::PIXEL_FORMAT_YUV422SP)) { + return OMX_COLOR_FormatYUV422SemiPlanar; + } + + if (!strcmp(colorFormat, CameraParameters::PIXEL_FORMAT_YUV420SP)) { + return OMX_COLOR_FormatYUV420SemiPlanar; + } + + if (!strcmp(colorFormat, CameraParameters::PIXEL_FORMAT_YUV422I)) { + return OMX_COLOR_FormatYCbYCr; + } + + if (!strcmp(colorFormat, CameraParameters::PIXEL_FORMAT_RGB565)) { + return OMX_COLOR_Format16bitRGB565; + } + + if (!strcmp(colorFormat, "OMX_TI_COLOR_FormatYUV420PackedSemiPlanar")) { + return OMX_TI_COLOR_FormatYUV420PackedSemiPlanar; + } + + LOGE("Uknown color format (%s), please add it to " + "GonkCameraSource::getColorFormat", colorFormat); + + CHECK_EQ(0, "Unknown color format"); +} + +GonkCameraSource *GonkCameraSource::Create( + int32_t cameraHandle, + Size videoSize, + int32_t frameRate, + bool storeMetaDataInVideoBuffers) { + + GonkCameraSource *source = new GonkCameraSource(cameraHandle, + videoSize, frameRate, + storeMetaDataInVideoBuffers); + return source; +} + +GonkCameraSource::GonkCameraSource( + int32_t cameraHandle, + Size videoSize, + int32_t frameRate, + bool storeMetaDataInVideoBuffers) + : mCameraFlags(0), + mVideoFrameRate(-1), + mNumFramesReceived(0), + mLastFrameTimestampUs(0), + mStarted(false), + mNumFramesEncoded(0), + mTimeBetweenFrameCaptureUs(0), + mFirstFrameTimeUs(0), + mNumFramesDropped(0), + mNumGlitches(0), + mGlitchDurationThresholdUs(200000), + mCollectStats(false) { + mVideoSize.width = -1; + mVideoSize.height = -1; + + mCameraHandle = cameraHandle; + + mInitCheck = init( + videoSize, frameRate, + storeMetaDataInVideoBuffers); + if (mInitCheck != OK) releaseCamera(); +} + +status_t GonkCameraSource::initCheck() const { + return mInitCheck; +} + +//TODO: Do we need to reimplement isCameraAvailable? + +/* + * Check to see whether the requested video width and height is one + * of the supported sizes. + * @param width the video frame width in pixels + * @param height the video frame height in pixels + * @param suppportedSizes the vector of sizes that we check against + * @return true if the dimension (width and height) is supported. + */ +static bool isVideoSizeSupported( + int32_t width, int32_t height, + const Vector& supportedSizes) { + + LOGV("isVideoSizeSupported"); + for (size_t i = 0; i < supportedSizes.size(); ++i) { + if (width == supportedSizes[i].width && + height == supportedSizes[i].height) { + return true; + } + } + return false; +} + +/* + * If the preview and video output is separate, we only set the + * the video size, and applications should set the preview size + * to some proper value, and the recording framework will not + * change the preview size; otherwise, if the video and preview + * output is the same, we need to set the preview to be the same + * as the requested video size. + * + */ +/* + * Query the camera to retrieve the supported video frame sizes + * and also to see whether CameraParameters::setVideoSize() + * is supported or not. + * @param params CameraParameters to retrieve the information + * @@param isSetVideoSizeSupported retunrs whether method + * CameraParameters::setVideoSize() is supported or not. + * @param sizes returns the vector of Size objects for the + * supported video frame sizes advertised by the camera. + */ +static void getSupportedVideoSizes( + const CameraParameters& params, + bool *isSetVideoSizeSupported, + Vector& sizes) { + + *isSetVideoSizeSupported = true; + params.getSupportedVideoSizes(sizes); + if (sizes.size() == 0) { + LOGD("Camera does not support setVideoSize()"); + params.getSupportedPreviewSizes(sizes); + *isSetVideoSizeSupported = false; + } +} + +/* + * Check whether the camera has the supported color format + * @param params CameraParameters to retrieve the information + * @return OK if no error. + */ +status_t GonkCameraSource::isCameraColorFormatSupported( + const CameraParameters& params) { + mColorFormat = getColorFormat(params.get( + CameraParameters::KEY_VIDEO_FRAME_FORMAT)); + if (mColorFormat == -1) { + return BAD_VALUE; + } + return OK; +} + +/* + * Configure the camera to use the requested video size + * (width and height) and/or frame rate. If both width and + * height are -1, configuration on the video size is skipped. + * if frameRate is -1, configuration on the frame rate + * is skipped. Skipping the configuration allows one to + * use the current camera setting without the need to + * actually know the specific values (see Create() method). + * + * @param params the CameraParameters to be configured + * @param width the target video frame width in pixels + * @param height the target video frame height in pixels + * @param frameRate the target frame rate in frames per second. + * @return OK if no error. + */ +status_t GonkCameraSource::configureCamera( + CameraParameters* params, + int32_t width, int32_t height, + int32_t frameRate) { + LOGV("configureCamera"); + Vector sizes; + bool isSetVideoSizeSupportedByCamera = true; + getSupportedVideoSizes(*params, &isSetVideoSizeSupportedByCamera, sizes); + bool isCameraParamChanged = false; + if (width != -1 && height != -1) { + if (!isVideoSizeSupported(width, height, sizes)) { + LOGE("Video dimension (%dx%d) is unsupported", width, height); + return BAD_VALUE; + } + if (isSetVideoSizeSupportedByCamera) { + params->setVideoSize(width, height); + } else { + params->setPreviewSize(width, height); + } + isCameraParamChanged = true; + } else if ((width == -1 && height != -1) || + (width != -1 && height == -1)) { + // If one and only one of the width and height is -1 + // we reject such a request. + LOGE("Requested video size (%dx%d) is not supported", width, height); + return BAD_VALUE; + } else { // width == -1 && height == -1 + // Do not configure the camera. + // Use the current width and height value setting from the camera. + } + + if (frameRate != -1) { + CHECK(frameRate > 0 && frameRate <= 120); + const char* supportedFrameRates = + params->get(CameraParameters::KEY_SUPPORTED_PREVIEW_FRAME_RATES); + CHECK(supportedFrameRates != NULL); + LOGV("Supported frame rates: %s", supportedFrameRates); + char buf[4]; + snprintf(buf, 4, "%d", frameRate); + if (strstr(supportedFrameRates, buf) == NULL) { + LOGE("Requested frame rate (%d) is not supported: %s", + frameRate, supportedFrameRates); + return BAD_VALUE; + } + + // The frame rate is supported, set the camera to the requested value. + params->setPreviewFrameRate(frameRate); + isCameraParamChanged = true; + } else { // frameRate == -1 + // Do not configure the camera. + // Use the current frame rate value setting from the camera + } + + if (isCameraParamChanged) { + // Either frame rate or frame size needs to be changed. + if (OK != GonkCameraHardware::PushParameters(mCameraHandle,*params)) { + LOGE("Could not change settings." + " Someone else is using camera ?"); + return -EBUSY; + } + } + return OK; +} + +/* + * Check whether the requested video frame size + * has been successfully configured or not. If both width and height + * are -1, check on the current width and height value setting + * is performed. + * + * @param params CameraParameters to retrieve the information + * @param the target video frame width in pixels to check against + * @param the target video frame height in pixels to check against + * @return OK if no error + */ +status_t GonkCameraSource::checkVideoSize( + const CameraParameters& params, + int32_t width, int32_t height) { + + LOGV("checkVideoSize"); + // The actual video size is the same as the preview size + // if the camera hal does not support separate video and + // preview output. In this case, we retrieve the video + // size from preview. + int32_t frameWidthActual = -1; + int32_t frameHeightActual = -1; + Vector sizes; + params.getSupportedVideoSizes(sizes); + if (sizes.size() == 0) { + // video size is the same as preview size + params.getPreviewSize(&frameWidthActual, &frameHeightActual); + } else { + // video size may not be the same as preview + params.getVideoSize(&frameWidthActual, &frameHeightActual); + } + if (frameWidthActual < 0 || frameHeightActual < 0) { + LOGE("Failed to retrieve video frame size (%dx%d)", + frameWidthActual, frameHeightActual); + return UNKNOWN_ERROR; + } + + // Check the actual video frame size against the target/requested + // video frame size. + if (width != -1 && height != -1) { + if (frameWidthActual != width || frameHeightActual != height) { + LOGE("Failed to set video frame size to %dx%d. " + "The actual video size is %dx%d ", width, height, + frameWidthActual, frameHeightActual); + return UNKNOWN_ERROR; + } + } + + // Good now. + mVideoSize.width = frameWidthActual; + mVideoSize.height = frameHeightActual; + return OK; +} + +/* + * Check the requested frame rate has been successfully configured or not. + * If the target frameRate is -1, check on the current frame rate value + * setting is performed. + * + * @param params CameraParameters to retrieve the information + * @param the target video frame rate to check against + * @return OK if no error. + */ +status_t GonkCameraSource::checkFrameRate( + const CameraParameters& params, + int32_t frameRate) { + + LOGV("checkFrameRate"); + int32_t frameRateActual = params.getPreviewFrameRate(); + if (frameRateActual < 0) { + LOGE("Failed to retrieve preview frame rate (%d)", frameRateActual); + return UNKNOWN_ERROR; + } + + // Check the actual video frame rate against the target/requested + // video frame rate. + if (frameRate != -1 && (frameRateActual - frameRate) != 0) { + LOGE("Failed to set preview frame rate to %d fps. The actual " + "frame rate is %d", frameRate, frameRateActual); + return UNKNOWN_ERROR; + } + + // Good now. + mVideoFrameRate = frameRateActual; + return OK; +} + +/* + * Initialize the CameraSource to so that it becomes + * ready for providing the video input streams as requested. + * @param camera the camera object used for the video source + * @param cameraId if camera == 0, use camera with this id + * as the video source + * @param videoSize the target video frame size. If both + * width and height in videoSize is -1, use the current + * width and heigth settings by the camera + * @param frameRate the target frame rate in frames per second. + * if it is -1, use the current camera frame rate setting. + * @param storeMetaDataInVideoBuffers request to store meta + * data or real YUV data in video buffers. Request to + * store meta data in video buffers may not be honored + * if the source does not support this feature. + * + * @return OK if no error. + */ +status_t GonkCameraSource::init( + Size videoSize, + int32_t frameRate, + bool storeMetaDataInVideoBuffers) { + + LOGV("init"); + status_t err = OK; + //TODO: need to do something here to check the sanity of camera + + CameraParameters params; + GonkCameraHardware::PullParameters(mCameraHandle, params); + if ((err = isCameraColorFormatSupported(params)) != OK) { + return err; + } + + // Set the camera to use the requested video frame size + // and/or frame rate. + if ((err = configureCamera(¶ms, + videoSize.width, videoSize.height, + frameRate))) { + return err; + } + + // Check on video frame size and frame rate. + CameraParameters newCameraParams; + GonkCameraHardware::PullParameters(mCameraHandle, newCameraParams); + if ((err = checkVideoSize(newCameraParams, + videoSize.width, videoSize.height)) != OK) { + return err; + } + if ((err = checkFrameRate(newCameraParams, frameRate)) != OK) { + return err; + } + + // By default, do not store metadata in video buffers + mIsMetaDataStoredInVideoBuffers = false; + GonkCameraHardware::StoreMetaDataInBuffers(mCameraHandle, false); + if (storeMetaDataInVideoBuffers) { + if (OK == GonkCameraHardware::StoreMetaDataInBuffers(mCameraHandle, true)) { + mIsMetaDataStoredInVideoBuffers = true; + } + } + + const char *hfr_str = params.get("video-hfr"); + int32_t hfr = -1; + if ( hfr_str != NULL ) { + hfr = atoi(hfr_str); + } + if(hfr < 0) { + LOGW("Invalid hfr value(%d) set from app. Disabling HFR.", hfr); + hfr = 0; + } + + int64_t glitchDurationUs = (1000000LL / mVideoFrameRate); + if (glitchDurationUs > mGlitchDurationThresholdUs) { + mGlitchDurationThresholdUs = glitchDurationUs; + } + + const char * k3dFrameArrangement = "3d-frame-format"; + const char * arrangement = params.get(k3dFrameArrangement); + // XXX: just assume left/right for now since that's all the camera supports + bool want3D = (arrangement != NULL && !strcmp("left-right", arrangement)); + + // XXX: query camera for the stride and slice height + // when the capability becomes available. + mMeta = new MetaData; + mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW); + mMeta->setInt32(kKeyColorFormat, mColorFormat); + mMeta->setInt32(kKeyWidth, mVideoSize.width); + mMeta->setInt32(kKeyHeight, mVideoSize.height); + mMeta->setInt32(kKeyStride, mVideoSize.width); + mMeta->setInt32(kKeySliceHeight, mVideoSize.height); + mMeta->setInt32(kKeyFrameRate, mVideoFrameRate); + + return OK; +} + +GonkCameraSource::~GonkCameraSource() { + if (mStarted) { + stop(); + } else if (mInitCheck == OK) { + // Camera is initialized but because start() is never called, + // the lock on Camera is never released(). This makes sure + // Camera's lock is released in this case. + // TODO: Don't think I need to do this + releaseCamera(); + } +} + +void GonkCameraSource::startCameraRecording() { + LOGV("startCameraRecording"); + CHECK_EQ(OK, GonkCameraHardware::StartRecording(mCameraHandle)); +} + +status_t GonkCameraSource::start(MetaData *meta) { + LOGV("start"); + CHECK(!mStarted); + if (mInitCheck != OK) { + LOGE("GonkCameraSource is not initialized yet"); + return mInitCheck; + } + + char value[PROPERTY_VALUE_MAX]; + if (property_get("media.stagefright.record-stats", value, NULL) + && (!strcmp(value, "1") || !strcasecmp(value, "true"))) { + mCollectStats = true; + } + + mStartTimeUs = 0; + int64_t startTimeUs; + if (meta && meta->findInt64(kKeyTime, &startTimeUs)) { + LOGV("Metadata enabled, startime: %lld us", startTimeUs); + mStartTimeUs = startTimeUs; + } + + // Register a listener with GonkCameraHardware so that we can get callbacks + GonkCameraHardware::SetListener(mCameraHandle, new GonkCameraSourceListener(this)); + + startCameraRecording(); + + mStarted = true; + return OK; +} + +void GonkCameraSource::stopCameraRecording() { + LOGV("stopCameraRecording"); + GonkCameraHardware::StopRecording(mCameraHandle); +} + +void GonkCameraSource::releaseCamera() { + LOGV("releaseCamera"); +} + +status_t GonkCameraSource::stop() { + LOGV("stop: E"); + Mutex::Autolock autoLock(mLock); + mStarted = false; + mFrameAvailableCondition.signal(); + + releaseQueuedFrames(); + while (!mFramesBeingEncoded.empty()) { + if (NO_ERROR != + mFrameCompleteCondition.waitRelative(mLock, + mTimeBetweenFrameCaptureUs * 1000LL + CAMERA_SOURCE_TIMEOUT_NS)) { + LOGW("Timed out waiting for outstanding frames being encoded: %d", + mFramesBeingEncoded.size()); + } + } + LOGV("Calling stopCameraRecording"); + stopCameraRecording(); + releaseCamera(); + + if (mCollectStats) { + LOGI("Frames received/encoded/dropped: %d/%d/%d in %lld us", + mNumFramesReceived, mNumFramesEncoded, mNumFramesDropped, + mLastFrameTimestampUs - mFirstFrameTimeUs); + } + + if (mNumGlitches > 0) { + LOGW("%d long delays between neighboring video frames", mNumGlitches); + } + + CHECK_EQ(mNumFramesReceived, mNumFramesEncoded + mNumFramesDropped); + LOGV("stop: X"); + return OK; +} + +void GonkCameraSource::releaseRecordingFrame(const sp& frame) { + LOGV("releaseRecordingFrame"); + GonkCameraHardware::ReleaseRecordingFrame(mCameraHandle, frame); +} + +void GonkCameraSource::releaseQueuedFrames() { + List >::iterator it; + while (!mFramesReceived.empty()) { + it = mFramesReceived.begin(); + releaseRecordingFrame(*it); + mFramesReceived.erase(it); + ++mNumFramesDropped; + } +} + +sp GonkCameraSource::getFormat() { + return mMeta; +} + +void GonkCameraSource::releaseOneRecordingFrame(const sp& frame) { + releaseRecordingFrame(frame); +} + +void GonkCameraSource::signalBufferReturned(MediaBuffer *buffer) { + LOGV("signalBufferReturned: %p", buffer->data()); + Mutex::Autolock autoLock(mLock); + for (List >::iterator it = mFramesBeingEncoded.begin(); + it != mFramesBeingEncoded.end(); ++it) { + if ((*it)->pointer() == buffer->data()) { + releaseOneRecordingFrame((*it)); + mFramesBeingEncoded.erase(it); + ++mNumFramesEncoded; + buffer->setObserver(0); + buffer->release(); + mFrameCompleteCondition.signal(); + return; + } + } + CHECK_EQ(0, "signalBufferReturned: bogus buffer"); +} + +status_t GonkCameraSource::read( + MediaBuffer **buffer, const ReadOptions *options) { + LOGV("read"); + + *buffer = NULL; + + int64_t seekTimeUs; + ReadOptions::SeekMode mode; + if (options && options->getSeekTo(&seekTimeUs, &mode)) { + return ERROR_UNSUPPORTED; + } + + sp frame; + int64_t frameTime; + + { + Mutex::Autolock autoLock(mLock); + while (mStarted && mFramesReceived.empty()) { + if (NO_ERROR != + mFrameAvailableCondition.waitRelative(mLock, + mTimeBetweenFrameCaptureUs * 1000LL + CAMERA_SOURCE_TIMEOUT_NS)) { + //TODO: check sanity of camera? + LOGW("Timed out waiting for incoming camera video frames: %lld us", + mLastFrameTimestampUs); + } + } + if (!mStarted) { + return OK; + } + frame = *mFramesReceived.begin(); + mFramesReceived.erase(mFramesReceived.begin()); + + frameTime = *mFrameTimes.begin(); + mFrameTimes.erase(mFrameTimes.begin()); + mFramesBeingEncoded.push_back(frame); + *buffer = new MediaBuffer(frame->pointer(), frame->size()); + (*buffer)->setObserver(this); + (*buffer)->add_ref(); + (*buffer)->meta_data()->setInt64(kKeyTime, frameTime); + } + return OK; +} + +void GonkCameraSource::dataCallbackTimestamp(int64_t timestampUs, + int32_t msgType, const sp &data) { + LOGV("dataCallbackTimestamp: timestamp %lld us", timestampUs); + //LOGV("dataCallbackTimestamp: data %x size %d", data->pointer(), data->size()); + Mutex::Autolock autoLock(mLock); + if (!mStarted || (mNumFramesReceived == 0 && timestampUs < mStartTimeUs)) { + LOGV("Drop frame at %lld/%lld us", timestampUs, mStartTimeUs); + releaseOneRecordingFrame(data); + return; + } + + if (mNumFramesReceived > 0) { + CHECK(timestampUs > mLastFrameTimestampUs); + if (timestampUs - mLastFrameTimestampUs > mGlitchDurationThresholdUs) { + ++mNumGlitches; + } + } + + // May need to skip frame or modify timestamp. Currently implemented + // by the subclass GonkCameraSourceTimeLapse. + if (skipCurrentFrame(timestampUs)) { + releaseOneRecordingFrame(data); + return; + } + + mLastFrameTimestampUs = timestampUs; + if (mNumFramesReceived == 0) { + mFirstFrameTimeUs = timestampUs; + // Initial delay + if (mStartTimeUs > 0) { + if (timestampUs < mStartTimeUs) { + // Frame was captured before recording was started + // Drop it without updating the statistical data. + releaseOneRecordingFrame(data); + return; + } + mStartTimeUs = timestampUs - mStartTimeUs; + } + } + ++mNumFramesReceived; + + CHECK(data != NULL && data->size() > 0); + mFramesReceived.push_back(data); + int64_t timeUs = mStartTimeUs + (timestampUs - mFirstFrameTimeUs); + mFrameTimes.push_back(timeUs); + LOGV("initial delay: %lld, current time stamp: %lld", + mStartTimeUs, timeUs); + mFrameAvailableCondition.signal(); +} + +bool GonkCameraSource::isMetaDataStoredInVideoBuffers() const { + LOGV("isMetaDataStoredInVideoBuffers"); + return mIsMetaDataStoredInVideoBuffers; +} + +} // namespace android diff --git a/dom/camera/GonkCameraSource.h b/dom/camera/GonkCameraSource.h new file mode 100644 index 00000000000..fe58f96770c --- /dev/null +++ b/dom/camera/GonkCameraSource.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2009 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 GONK_CAMERA_SOURCE_H_ + +#define GONK_CAMERA_SOURCE_H_ + +#include +#include +#include +#include +#include +#include + +namespace android { + +class IMemory; +class GonkCameraSourceListener; + +class GonkCameraSource : public MediaSource, public MediaBufferObserver { +public: + + static GonkCameraSource *Create(int32_t cameraHandle, + Size videoSize, + int32_t frameRate, + bool storeMetaDataInVideoBuffers = false); + + virtual ~GonkCameraSource(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + + /** + * Check whether a GonkCameraSource object is properly initialized. + * Must call this method before stop(). + * @return OK if initialization has successfully completed. + */ + virtual status_t initCheck() const; + + /** + * Returns the MetaData associated with the GonkCameraSource, + * including: + * kKeyColorFormat: YUV color format of the video frames + * kKeyWidth, kKeyHeight: dimension (in pixels) of the video frames + * kKeySampleRate: frame rate in frames per second + * kKeyMIMEType: always fixed to be MEDIA_MIMETYPE_VIDEO_RAW + */ + virtual sp getFormat(); + + /** + * Tell whether this camera source stores meta data or real YUV + * frame data in video buffers. + * + * @return true if meta data is stored in the video + * buffers; false if real YUV data is stored in + * the video buffers. + */ + bool isMetaDataStoredInVideoBuffers() const; + + virtual void signalBufferReturned(MediaBuffer* buffer); + +protected: + + enum CameraFlags { + FLAGS_SET_CAMERA = 1L << 0, + FLAGS_HOT_CAMERA = 1L << 1, + }; + + int32_t mCameraFlags; + Size mVideoSize; + int32_t mVideoFrameRate; + int32_t mColorFormat; + status_t mInitCheck; + + sp mMeta; + + int64_t mStartTimeUs; + int32_t mNumFramesReceived; + int64_t mLastFrameTimestampUs; + bool mStarted; + int32_t mNumFramesEncoded; + + // Time between capture of two frames. + int64_t mTimeBetweenFrameCaptureUs; + + GonkCameraSource(int32_t cameraHandle, + Size videoSize, int32_t frameRate, + bool storeMetaDataInVideoBuffers = false); + + virtual void startCameraRecording(); + virtual void stopCameraRecording(); + virtual void releaseRecordingFrame(const sp& frame); + + // Returns true if need to skip the current frame. + // Called from dataCallbackTimestamp. + virtual bool skipCurrentFrame(int64_t timestampUs) {return false;} + + friend class GonkCameraSourceListener; + // Callback called when still camera raw data is available. + virtual void dataCallback(int32_t msgType, const sp &data) {} + + virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType, + const sp &data); + +private: + + Mutex mLock; + Condition mFrameAvailableCondition; + Condition mFrameCompleteCondition; + List > mFramesReceived; + List > mFramesBeingEncoded; + List mFrameTimes; + + int64_t mFirstFrameTimeUs; + int32_t mNumFramesDropped; + int32_t mNumGlitches; + int64_t mGlitchDurationThresholdUs; + bool mCollectStats; + bool mIsMetaDataStoredInVideoBuffers; + int32_t mCameraHandle; + + void releaseQueuedFrames(); + void releaseOneRecordingFrame(const sp& frame); + + status_t init(Size videoSize, int32_t frameRate, + bool storeMetaDataInVideoBuffers); + status_t isCameraColorFormatSupported(const CameraParameters& params); + status_t configureCamera(CameraParameters* params, + int32_t width, int32_t height, + int32_t frameRate); + + status_t checkVideoSize(const CameraParameters& params, + int32_t width, int32_t height); + + status_t checkFrameRate(const CameraParameters& params, + int32_t frameRate); + + void releaseCamera(); + + GonkCameraSource(const GonkCameraSource &); + GonkCameraSource &operator=(const GonkCameraSource &); +}; + +} // namespace android + +#endif // GONK_CAMERA_SOURCE_H_ diff --git a/dom/camera/GonkRecorder.cpp b/dom/camera/GonkRecorder.cpp new file mode 100644 index 00000000000..7ad4a0a46bd --- /dev/null +++ b/dom/camera/GonkRecorder.cpp @@ -0,0 +1,1629 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "GonkRecorder" + +#include +#include +#include "GonkRecorder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "ARTPWriter.h" + +#include +#include "GonkCameraSource.h" + +namespace android { + +static sp sOMX = NULL; +static sp GetOMX() { + if(sOMX.get() == NULL) { + sOMX = new OMX; + } + return sOMX; +} + +GonkRecorder::GonkRecorder() + : mWriter(NULL), + mOutputFd(-1), + mAudioSource(AUDIO_SOURCE_CNT), + mVideoSource(VIDEO_SOURCE_LIST_END), + mStarted(false), + mDisableAudio(false) { + + LOGV("Constructor"); + reset(); +} + +GonkRecorder::~GonkRecorder() { + LOGV("Destructor"); + stop(); +} + +status_t GonkRecorder::init() { + LOGV("init"); + return OK; +} + +status_t GonkRecorder::setAudioSource(audio_source_t as) { + LOGV("setAudioSource: %d", as); + if (as < AUDIO_SOURCE_DEFAULT || + as >= AUDIO_SOURCE_CNT) { + LOGE("Invalid audio source: %d", as); + return BAD_VALUE; + } + + if (mDisableAudio) { + return OK; + } + + if (as == AUDIO_SOURCE_DEFAULT) { + mAudioSource = AUDIO_SOURCE_MIC; + } else { + mAudioSource = as; + } + + return OK; +} + +status_t GonkRecorder::setVideoSource(video_source vs) { + LOGV("setVideoSource: %d", vs); + if (vs < VIDEO_SOURCE_DEFAULT || + vs >= VIDEO_SOURCE_LIST_END) { + LOGE("Invalid video source: %d", vs); + return BAD_VALUE; + } + + if (vs == VIDEO_SOURCE_DEFAULT) { + mVideoSource = VIDEO_SOURCE_CAMERA; + } else { + mVideoSource = vs; + } + + return OK; +} + +status_t GonkRecorder::setOutputFormat(output_format of) { + LOGV("setOutputFormat: %d", of); + if (of < OUTPUT_FORMAT_DEFAULT || + of >= OUTPUT_FORMAT_LIST_END) { + LOGE("Invalid output format: %d", of); + return BAD_VALUE; + } + + if (of == OUTPUT_FORMAT_DEFAULT) { + mOutputFormat = OUTPUT_FORMAT_THREE_GPP; + } else { + mOutputFormat = of; + } + + return OK; +} + +status_t GonkRecorder::setAudioEncoder(audio_encoder ae) { + LOGV("setAudioEncoder: %d", ae); + if (ae < AUDIO_ENCODER_DEFAULT || + ae >= AUDIO_ENCODER_LIST_END) { + LOGE("Invalid audio encoder: %d", ae); + return BAD_VALUE; + } + + if (mDisableAudio) { + return OK; + } + + if (ae == AUDIO_ENCODER_DEFAULT) { + mAudioEncoder = AUDIO_ENCODER_AMR_NB; + } else { + mAudioEncoder = ae; + } + + return OK; +} + +status_t GonkRecorder::setVideoEncoder(video_encoder ve) { + LOGV("setVideoEncoder: %d", ve); + if (ve < VIDEO_ENCODER_DEFAULT || + ve >= VIDEO_ENCODER_LIST_END) { + LOGE("Invalid video encoder: %d", ve); + return BAD_VALUE; + } + + if (ve == VIDEO_ENCODER_DEFAULT) { + mVideoEncoder = VIDEO_ENCODER_H263; + } else { + mVideoEncoder = ve; + } + + return OK; +} + +status_t GonkRecorder::setVideoSize(int width, int height) { + LOGV("setVideoSize: %dx%d", width, height); + if (width <= 0 || height <= 0) { + LOGE("Invalid video size: %dx%d", width, height); + return BAD_VALUE; + } + + // Additional check on the dimension will be performed later + mVideoWidth = width; + mVideoHeight = height; + + return OK; +} + +status_t GonkRecorder::setVideoFrameRate(int frames_per_second) { + LOGV("setVideoFrameRate: %d", frames_per_second); + if ((frames_per_second <= 0 && frames_per_second != -1) || + frames_per_second > 120) { + LOGE("Invalid video frame rate: %d", frames_per_second); + return BAD_VALUE; + } + + // Additional check on the frame rate will be performed later + mFrameRate = frames_per_second; + + return OK; +} + +status_t GonkRecorder::setOutputFile(const char *path) { + LOGE("setOutputFile(const char*) must not be called"); + // We don't actually support this at all, as the media_server process + // no longer has permissions to create files. + + return -EPERM; +} + +status_t GonkRecorder::setOutputFile(int fd, int64_t offset, int64_t length) { + LOGV("setOutputFile: %d, %lld, %lld", fd, offset, length); + // These don't make any sense, do they? + CHECK_EQ(offset, 0); + CHECK_EQ(length, 0); + + if (fd < 0) { + LOGE("Invalid file descriptor: %d", fd); + return -EBADF; + } + + if (mOutputFd >= 0) { + ::close(mOutputFd); + } + mOutputFd = dup(fd); + + return OK; +} + +// Attempt to parse an int64 literal optionally surrounded by whitespace, +// returns true on success, false otherwise. +static bool safe_strtoi64(const char *s, int64_t *val) { + char *end; + + // It is lame, but according to man page, we have to set errno to 0 + // before calling strtoll(). + errno = 0; + *val = strtoll(s, &end, 10); + + if (end == s || errno == ERANGE) { + return false; + } + + // Skip trailing whitespace + while (isspace(*end)) { + ++end; + } + + // For a successful return, the string must contain nothing but a valid + // int64 literal optionally surrounded by whitespace. + + return *end == '\0'; +} + +// Return true if the value is in [0, 0x007FFFFFFF] +static bool safe_strtoi32(const char *s, int32_t *val) { + int64_t temp; + if (safe_strtoi64(s, &temp)) { + if (temp >= 0 && temp <= 0x007FFFFFFF) { + *val = static_cast(temp); + return true; + } + } + return false; +} + +// Trim both leading and trailing whitespace from the given string. +static void TrimString(String8 *s) { + size_t num_bytes = s->bytes(); + const char *data = s->string(); + + size_t leading_space = 0; + while (leading_space < num_bytes && isspace(data[leading_space])) { + ++leading_space; + } + + size_t i = num_bytes; + while (i > leading_space && isspace(data[i - 1])) { + --i; + } + + s->setTo(String8(&data[leading_space], i - leading_space)); +} + +status_t GonkRecorder::setParamAudioSamplingRate(int32_t sampleRate) { + LOGV("setParamAudioSamplingRate: %d", sampleRate); + if (sampleRate <= 0) { + LOGE("Invalid audio sampling rate: %d", sampleRate); + return BAD_VALUE; + } + + // Additional check on the sample rate will be performed later. + mSampleRate = sampleRate; + return OK; +} + +status_t GonkRecorder::setParamAudioNumberOfChannels(int32_t channels) { + LOGV("setParamAudioNumberOfChannels: %d", channels); + if (channels <= 0 || channels >= 3) { + LOGE("Invalid number of audio channels: %d", channels); + return BAD_VALUE; + } + + // Additional check on the number of channels will be performed later. + mAudioChannels = channels; + return OK; +} + +status_t GonkRecorder::setParamAudioEncodingBitRate(int32_t bitRate) { + LOGV("setParamAudioEncodingBitRate: %d", bitRate); + if (bitRate <= 0) { + LOGE("Invalid audio encoding bit rate: %d", bitRate); + return BAD_VALUE; + } + + // The target bit rate may not be exactly the same as the requested. + // It depends on many factors, such as rate control, and the bit rate + // range that a specific encoder supports. The mismatch between the + // the target and requested bit rate will NOT be treated as an error. + mAudioBitRate = bitRate; + return OK; +} + +status_t GonkRecorder::setParamVideoEncodingBitRate(int32_t bitRate) { + LOGV("setParamVideoEncodingBitRate: %d", bitRate); + if (bitRate <= 0) { + LOGE("Invalid video encoding bit rate: %d", bitRate); + return BAD_VALUE; + } + + // The target bit rate may not be exactly the same as the requested. + // It depends on many factors, such as rate control, and the bit rate + // range that a specific encoder supports. The mismatch between the + // the target and requested bit rate will NOT be treated as an error. + mVideoBitRate = bitRate; + return OK; +} + +// Always rotate clockwise, and only support 0, 90, 180 and 270 for now. +status_t GonkRecorder::setParamVideoRotation(int32_t degrees) { + LOGV("setParamVideoRotation: %d", degrees); + if (degrees < 0 || degrees % 90 != 0) { + LOGE("Unsupported video rotation angle: %d", degrees); + return BAD_VALUE; + } + mRotationDegrees = degrees % 360; + return OK; +} + +status_t GonkRecorder::setParamMaxFileDurationUs(int64_t timeUs) { + LOGV("setParamMaxFileDurationUs: %lld us", timeUs); + + // This is meant for backward compatibility for MediaRecorder.java + if (timeUs <= 0) { + LOGW("Max file duration is not positive: %lld us. Disabling duration limit.", timeUs); + timeUs = 0; // Disable the duration limit for zero or negative values. + } else if (timeUs <= 100000LL) { // XXX: 100 milli-seconds + LOGE("Max file duration is too short: %lld us", timeUs); + return BAD_VALUE; + } + + if (timeUs <= 15 * 1000000LL) { + LOGW("Target duration (%lld us) too short to be respected", timeUs); + } + mMaxFileDurationUs = timeUs; + return OK; +} + +status_t GonkRecorder::setParamMaxFileSizeBytes(int64_t bytes) { + LOGV("setParamMaxFileSizeBytes: %lld bytes", bytes); + + // This is meant for backward compatibility for MediaRecorder.java + if (bytes <= 0) { + LOGW("Max file size is not positive: %lld bytes. " + "Disabling file size limit.", bytes); + bytes = 0; // Disable the file size limit for zero or negative values. + } else if (bytes <= 1024) { // XXX: 1 kB + LOGE("Max file size is too small: %lld bytes", bytes); + return BAD_VALUE; + } + + if (bytes <= 100 * 1024) { + LOGW("Target file size (%lld bytes) is too small to be respected", bytes); + } + + if (bytes >= 0xffffffffLL) { + LOGW("Target file size (%lld bytes) too larger than supported, clip to 4GB", bytes); + bytes = 0xffffffffLL; + } + + mMaxFileSizeBytes = bytes; + return OK; +} + +status_t GonkRecorder::setParamInterleaveDuration(int32_t durationUs) { + LOGV("setParamInterleaveDuration: %d", durationUs); + if (durationUs <= 500000) { // 500 ms + // If interleave duration is too small, it is very inefficient to do + // interleaving since the metadata overhead will count for a significant + // portion of the saved contents + LOGE("Audio/video interleave duration is too small: %d us", durationUs); + return BAD_VALUE; + } else if (durationUs >= 10000000) { // 10 seconds + // If interleaving duration is too large, it can cause the recording + // session to use too much memory since we have to save the output + // data before we write them out + LOGE("Audio/video interleave duration is too large: %d us", durationUs); + return BAD_VALUE; + } + mInterleaveDurationUs = durationUs; + return OK; +} + +// If seconds < 0, only the first frame is I frame, and rest are all P frames +// If seconds == 0, all frames are encoded as I frames. No P frames +// If seconds > 0, it is the time spacing (seconds) between 2 neighboring I frames +status_t GonkRecorder::setParamVideoIFramesInterval(int32_t seconds) { + LOGV("setParamVideoIFramesInterval: %d seconds", seconds); + mIFramesIntervalSec = seconds; + return OK; +} + +status_t GonkRecorder::setParam64BitFileOffset(bool use64Bit) { + LOGV("setParam64BitFileOffset: %s", + use64Bit? "use 64 bit file offset": "use 32 bit file offset"); + mUse64BitFileOffset = use64Bit; + return OK; +} + +status_t GonkRecorder::setParamVideoCameraId(int32_t cameraId) { + LOGV("setParamVideoCameraId: %d", cameraId); + if (cameraId < 0) { + return BAD_VALUE; + } + mCameraId = cameraId; + return OK; +} + +status_t GonkRecorder::setParamTrackTimeStatus(int64_t timeDurationUs) { + LOGV("setParamTrackTimeStatus: %lld", timeDurationUs); + if (timeDurationUs < 20000) { // Infeasible if shorter than 20 ms? + LOGE("Tracking time duration too short: %lld us", timeDurationUs); + return BAD_VALUE; + } + mTrackEveryTimeDurationUs = timeDurationUs; + return OK; +} + +status_t GonkRecorder::setParamVideoEncoderProfile(int32_t profile) { + LOGV("setParamVideoEncoderProfile: %d", profile); + + // Additional check will be done later when we load the encoder. + // For now, we are accepting values defined in OpenMAX IL. + mVideoEncoderProfile = profile; + return OK; +} + +status_t GonkRecorder::setParamVideoEncoderLevel(int32_t level) { + LOGV("setParamVideoEncoderLevel: %d", level); + + // Additional check will be done later when we load the encoder. + // For now, we are accepting values defined in OpenMAX IL. + mVideoEncoderLevel = level; + return OK; +} + +status_t GonkRecorder::setParamMovieTimeScale(int32_t timeScale) { + LOGV("setParamMovieTimeScale: %d", timeScale); + + // The range is set to be the same as the audio's time scale range + // since audio's time scale has a wider range. + if (timeScale < 600 || timeScale > 96000) { + LOGE("Time scale (%d) for movie is out of range [600, 96000]", timeScale); + return BAD_VALUE; + } + mMovieTimeScale = timeScale; + return OK; +} + +status_t GonkRecorder::setParamVideoTimeScale(int32_t timeScale) { + LOGV("setParamVideoTimeScale: %d", timeScale); + + // 60000 is chosen to make sure that each video frame from a 60-fps + // video has 1000 ticks. + if (timeScale < 600 || timeScale > 60000) { + LOGE("Time scale (%d) for video is out of range [600, 60000]", timeScale); + return BAD_VALUE; + } + mVideoTimeScale = timeScale; + return OK; +} + +status_t GonkRecorder::setParamAudioTimeScale(int32_t timeScale) { + LOGV("setParamAudioTimeScale: %d", timeScale); + + // 96000 Hz is the highest sampling rate support in AAC. + if (timeScale < 600 || timeScale > 96000) { + LOGE("Time scale (%d) for audio is out of range [600, 96000]", timeScale); + return BAD_VALUE; + } + mAudioTimeScale = timeScale; + return OK; +} + +status_t GonkRecorder::setParamGeoDataLongitude( + int64_t longitudex10000) { + + if (longitudex10000 > 1800000 || longitudex10000 < -1800000) { + return BAD_VALUE; + } + mLongitudex10000 = longitudex10000; + return OK; +} + +status_t GonkRecorder::setParamGeoDataLatitude( + int64_t latitudex10000) { + + if (latitudex10000 > 900000 || latitudex10000 < -900000) { + return BAD_VALUE; + } + mLatitudex10000 = latitudex10000; + return OK; +} + +status_t GonkRecorder::setParameter( + const String8 &key, const String8 &value) { + LOGV("setParameter: key (%s) => value (%s)", key.string(), value.string()); + if (key == "max-duration") { + int64_t max_duration_ms; + if (safe_strtoi64(value.string(), &max_duration_ms)) { + return setParamMaxFileDurationUs(1000LL * max_duration_ms); + } + } else if (key == "max-filesize") { + int64_t max_filesize_bytes; + if (safe_strtoi64(value.string(), &max_filesize_bytes)) { + return setParamMaxFileSizeBytes(max_filesize_bytes); + } + } else if (key == "interleave-duration-us") { + int32_t durationUs; + if (safe_strtoi32(value.string(), &durationUs)) { + return setParamInterleaveDuration(durationUs); + } + } else if (key == "param-movie-time-scale") { + int32_t timeScale; + if (safe_strtoi32(value.string(), &timeScale)) { + return setParamMovieTimeScale(timeScale); + } + } else if (key == "param-use-64bit-offset") { + int32_t use64BitOffset; + if (safe_strtoi32(value.string(), &use64BitOffset)) { + return setParam64BitFileOffset(use64BitOffset != 0); + } + } else if (key == "param-geotag-longitude") { + int64_t longitudex10000; + if (safe_strtoi64(value.string(), &longitudex10000)) { + return setParamGeoDataLongitude(longitudex10000); + } + } else if (key == "param-geotag-latitude") { + int64_t latitudex10000; + if (safe_strtoi64(value.string(), &latitudex10000)) { + return setParamGeoDataLatitude(latitudex10000); + } + } else if (key == "param-track-time-status") { + int64_t timeDurationUs; + if (safe_strtoi64(value.string(), &timeDurationUs)) { + return setParamTrackTimeStatus(timeDurationUs); + } + } else if (key == "audio-param-sampling-rate") { + int32_t sampling_rate; + if (safe_strtoi32(value.string(), &sampling_rate)) { + return setParamAudioSamplingRate(sampling_rate); + } + } else if (key == "audio-param-number-of-channels") { + int32_t number_of_channels; + if (safe_strtoi32(value.string(), &number_of_channels)) { + return setParamAudioNumberOfChannels(number_of_channels); + } + } else if (key == "audio-param-encoding-bitrate") { + int32_t audio_bitrate; + if (safe_strtoi32(value.string(), &audio_bitrate)) { + return setParamAudioEncodingBitRate(audio_bitrate); + } + } else if (key == "audio-param-time-scale") { + int32_t timeScale; + if (safe_strtoi32(value.string(), &timeScale)) { + return setParamAudioTimeScale(timeScale); + } + } else if (key == "video-param-encoding-bitrate") { + int32_t video_bitrate; + if (safe_strtoi32(value.string(), &video_bitrate)) { + return setParamVideoEncodingBitRate(video_bitrate); + } + } else if (key == "video-param-rotation-angle-degrees") { + int32_t degrees; + if (safe_strtoi32(value.string(), °rees)) { + return setParamVideoRotation(degrees); + } + } else if (key == "video-param-i-frames-interval") { + int32_t seconds; + if (safe_strtoi32(value.string(), &seconds)) { + return setParamVideoIFramesInterval(seconds); + } + } else if (key == "video-param-encoder-profile") { + int32_t profile; + if (safe_strtoi32(value.string(), &profile)) { + return setParamVideoEncoderProfile(profile); + } + } else if (key == "video-param-encoder-level") { + int32_t level; + if (safe_strtoi32(value.string(), &level)) { + return setParamVideoEncoderLevel(level); + } + } else if (key == "video-param-camera-id") { + int32_t cameraId; + if (safe_strtoi32(value.string(), &cameraId)) { + return setParamVideoCameraId(cameraId); + } + } else if (key == "video-param-time-scale") { + int32_t timeScale; + if (safe_strtoi32(value.string(), &timeScale)) { + return setParamVideoTimeScale(timeScale); + } + } else { + LOGE("setParameter: failed to find key %s", key.string()); + } + return BAD_VALUE; +} + +status_t GonkRecorder::setParameters(const String8 ¶ms) { + LOGV("setParameters: %s", params.string()); + const char *cparams = params.string(); + const char *key_start = cparams; + for (;;) { + const char *equal_pos = strchr(key_start, '='); + if (equal_pos == NULL) { + LOGE("Parameters %s miss a value", cparams); + return BAD_VALUE; + } + String8 key(key_start, equal_pos - key_start); + TrimString(&key); + if (key.length() == 0) { + LOGE("Parameters %s contains an empty key", cparams); + return BAD_VALUE; + } + const char *value_start = equal_pos + 1; + const char *semicolon_pos = strchr(value_start, ';'); + String8 value; + if (semicolon_pos == NULL) { + value.setTo(value_start); + } else { + value.setTo(value_start, semicolon_pos - value_start); + } + if (setParameter(key, value) != OK) { + return BAD_VALUE; + } + if (semicolon_pos == NULL) { + break; // Reaches the end + } + key_start = semicolon_pos + 1; + } + return OK; +} + +status_t GonkRecorder::setListener(const sp &listener) { + mListener = listener; + + return OK; +} + +status_t GonkRecorder::prepare() { + LOGV(" %s E", __func__ ); + + if(mVideoSource != VIDEO_SOURCE_LIST_END && mVideoEncoder != VIDEO_ENCODER_LIST_END && mVideoHeight && mVideoWidth && /*Video recording*/ + (mMaxFileDurationUs <=0 || /*Max duration is not set*/ + (mVideoHeight * mVideoWidth < 720 * 1280 && mMaxFileDurationUs > 30*60*1000*1000) || + (mVideoHeight * mVideoWidth >= 720 * 1280 && mMaxFileDurationUs > 10*60*1000*1000))) { + /*Above Check can be further optimized for lower resolutions to reduce file size*/ + LOGV("File is huge so setting 64 bit file offsets"); + setParam64BitFileOffset(true); + } + LOGV(" %s X", __func__ ); + return OK; +} + +status_t GonkRecorder::start() { + CHECK(mOutputFd >= 0); + + if (mWriter != NULL) { + LOGE("File writer is not available"); + return UNKNOWN_ERROR; + } + + status_t status = OK; + + switch (mOutputFormat) { + case OUTPUT_FORMAT_DEFAULT: + case OUTPUT_FORMAT_THREE_GPP: + case OUTPUT_FORMAT_MPEG_4: + status = startMPEG4Recording(); + break; + + case OUTPUT_FORMAT_AMR_NB: + case OUTPUT_FORMAT_AMR_WB: + status = startAMRRecording(); + break; + + case OUTPUT_FORMAT_MPEG2TS: + status = startMPEG2TSRecording(); + break; + default: + LOGE("Unsupported output file format: %d", mOutputFormat); + status = UNKNOWN_ERROR; + break; + } + + if ((status == OK) && (!mStarted)) { + mStarted = true; + } + + return status; +} + +sp GonkRecorder::createAudioSource() { + + sp audioSource = + new AudioSource( + mAudioSource, + mSampleRate, + mAudioChannels); + + status_t err = audioSource->initCheck(); + + if (err != OK) { + LOGE("audio source is not initialized"); + return NULL; + } + + sp encMeta = new MetaData; + const char *mime; + switch (mAudioEncoder) { + case AUDIO_ENCODER_AMR_NB: + case AUDIO_ENCODER_DEFAULT: + mime = MEDIA_MIMETYPE_AUDIO_AMR_NB; + break; + case AUDIO_ENCODER_AMR_WB: + mime = MEDIA_MIMETYPE_AUDIO_AMR_WB; + break; + case AUDIO_ENCODER_AAC: + mime = MEDIA_MIMETYPE_AUDIO_AAC; + break; + default: + LOGE("Unknown audio encoder: %d", mAudioEncoder); + return NULL; + } + encMeta->setCString(kKeyMIMEType, mime); + + int32_t maxInputSize; + CHECK(audioSource->getFormat()->findInt32( + kKeyMaxInputSize, &maxInputSize)); + + encMeta->setInt32(kKeyMaxInputSize, maxInputSize); + encMeta->setInt32(kKeyChannelCount, mAudioChannels); + encMeta->setInt32(kKeySampleRate, mSampleRate); + encMeta->setInt32(kKeyBitRate, mAudioBitRate); + if (mAudioTimeScale > 0) { + encMeta->setInt32(kKeyTimeScale, mAudioTimeScale); + } + + // use direct OMX interface instead of connecting to + // mediaserver over binder calls + sp audioEncoder = + OMXCodec::Create(GetOMX(), encMeta, + true /* createEncoder */, audioSource); + mAudioSourceNode = audioSource; + + return audioEncoder; +} + +status_t GonkRecorder::startAMRRecording() { + CHECK(mOutputFormat == OUTPUT_FORMAT_AMR_NB || + mOutputFormat == OUTPUT_FORMAT_AMR_WB); + + if (mOutputFormat == OUTPUT_FORMAT_AMR_NB) { + if (mAudioEncoder != AUDIO_ENCODER_DEFAULT && + mAudioEncoder != AUDIO_ENCODER_AMR_NB) { + LOGE("Invalid encoder %d used for AMRNB recording", + mAudioEncoder); + return BAD_VALUE; + } + } else { // mOutputFormat must be OUTPUT_FORMAT_AMR_WB + if (mAudioEncoder != AUDIO_ENCODER_AMR_WB) { + LOGE("Invlaid encoder %d used for AMRWB recording", + mAudioEncoder); + return BAD_VALUE; + } + } + + mWriter = new AMRWriter(mOutputFd); + status_t status = startRawAudioRecording(); + if (status != OK) { + mWriter.clear(); + mWriter = NULL; + } + return status; +} + +status_t GonkRecorder::startRawAudioRecording() { + if (mAudioSource >= AUDIO_SOURCE_CNT) { + LOGE("Invalid audio source: %d", mAudioSource); + return BAD_VALUE; + } + + status_t status = BAD_VALUE; + if (OK != (status = checkAudioEncoderCapabilities())) { + return status; + } + + sp audioEncoder = createAudioSource(); + if (audioEncoder == NULL) { + return UNKNOWN_ERROR; + } + + CHECK(mWriter != 0); + mWriter->addSource(audioEncoder); + + if (mMaxFileDurationUs != 0) { + mWriter->setMaxFileDuration(mMaxFileDurationUs); + } + if (mMaxFileSizeBytes != 0) { + mWriter->setMaxFileSize(mMaxFileSizeBytes); + } + mWriter->setListener(mListener); + mWriter->start(); + + return OK; +} + +status_t GonkRecorder::startMPEG2TSRecording() { + CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_MPEG2TS); + + sp writer = new MPEG2TSWriter(mOutputFd); + + if (mAudioSource != AUDIO_SOURCE_CNT) { + if (mAudioEncoder != AUDIO_ENCODER_AAC) { + return ERROR_UNSUPPORTED; + } + + status_t err = setupAudioEncoder(writer); + + if (err != OK) { + return err; + } + } + + if (mVideoSource < VIDEO_SOURCE_LIST_END) { + if (mVideoEncoder != VIDEO_ENCODER_H264) { + return ERROR_UNSUPPORTED; + } + + sp mediaSource; + status_t err = setupMediaSource(&mediaSource); + if (err != OK) { + return err; + } + + sp encoder; + err = setupVideoEncoder(mediaSource, mVideoBitRate, &encoder); + + if (err != OK) { + return err; + } + + writer->addSource(encoder); + } + + if (mMaxFileDurationUs != 0) { + writer->setMaxFileDuration(mMaxFileDurationUs); + } + + if (mMaxFileSizeBytes != 0) { + writer->setMaxFileSize(mMaxFileSizeBytes); + } + + mWriter = writer; + + return mWriter->start(); +} + +void GonkRecorder::clipVideoFrameRate() { + LOGV("clipVideoFrameRate: encoder %d", mVideoEncoder); + int minFrameRate = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.fps.min", mVideoEncoder); + int maxFrameRate = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.fps.max", mVideoEncoder); + if (mFrameRate < minFrameRate && mFrameRate != -1) { + LOGW("Intended video encoding frame rate (%d fps) is too small" + " and will be set to (%d fps)", mFrameRate, minFrameRate); + mFrameRate = minFrameRate; + } else if (mFrameRate > maxFrameRate) { + LOGW("Intended video encoding frame rate (%d fps) is too large" + " and will be set to (%d fps)", mFrameRate, maxFrameRate); + mFrameRate = maxFrameRate; + } +} + +void GonkRecorder::clipVideoBitRate() { + LOGV("clipVideoBitRate: encoder %d", mVideoEncoder); + int minBitRate = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.bps.min", mVideoEncoder); + int maxBitRate = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.bps.max", mVideoEncoder); + if (mVideoBitRate < minBitRate) { + LOGW("Intended video encoding bit rate (%d bps) is too small" + " and will be set to (%d bps)", mVideoBitRate, minBitRate); + mVideoBitRate = minBitRate; + } else if (mVideoBitRate > maxBitRate) { + LOGW("Intended video encoding bit rate (%d bps) is too large" + " and will be set to (%d bps)", mVideoBitRate, maxBitRate); + mVideoBitRate = maxBitRate; + } +} + +void GonkRecorder::clipVideoFrameWidth() { + LOGV("clipVideoFrameWidth: encoder %d", mVideoEncoder); + int minFrameWidth = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.width.min", mVideoEncoder); + int maxFrameWidth = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.width.max", mVideoEncoder); + if (mVideoWidth < minFrameWidth) { + LOGW("Intended video encoding frame width (%d) is too small" + " and will be set to (%d)", mVideoWidth, minFrameWidth); + mVideoWidth = minFrameWidth; + } else if (mVideoWidth > maxFrameWidth) { + LOGW("Intended video encoding frame width (%d) is too large" + " and will be set to (%d)", mVideoWidth, maxFrameWidth); + mVideoWidth = maxFrameWidth; + } +} + +status_t GonkRecorder::checkVideoEncoderCapabilities() { + // Dont clip for time lapse capture as encoder will have enough + // time to encode because of slow capture rate of time lapse. + clipVideoBitRate(); + clipVideoFrameRate(); + clipVideoFrameWidth(); + clipVideoFrameHeight(); + setDefaultProfileIfNecessary(); + return OK; +} + +// Set to use AVC baseline profile if the encoding parameters matches +// CAMCORDER_QUALITY_LOW profile; this is for the sake of MMS service. +void GonkRecorder::setDefaultProfileIfNecessary() { + LOGV("setDefaultProfileIfNecessary"); + + camcorder_quality quality = CAMCORDER_QUALITY_LOW; + + int64_t durationUs = mEncoderProfiles->getCamcorderProfileParamByName( + "duration", mCameraId, quality) * 1000000LL; + + int fileFormat = mEncoderProfiles->getCamcorderProfileParamByName( + "file.format", mCameraId, quality); + + int videoCodec = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.codec", mCameraId, quality); + + int videoBitRate = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.bps", mCameraId, quality); + + int videoFrameRate = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.fps", mCameraId, quality); + + int videoFrameWidth = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.width", mCameraId, quality); + + int videoFrameHeight = mEncoderProfiles->getCamcorderProfileParamByName( + "vid.height", mCameraId, quality); + + int audioCodec = mEncoderProfiles->getCamcorderProfileParamByName( + "aud.codec", mCameraId, quality); + + int audioBitRate = mEncoderProfiles->getCamcorderProfileParamByName( + "aud.bps", mCameraId, quality); + + int audioSampleRate = mEncoderProfiles->getCamcorderProfileParamByName( + "aud.hz", mCameraId, quality); + + int audioChannels = mEncoderProfiles->getCamcorderProfileParamByName( + "aud.ch", mCameraId, quality); + + if (durationUs == mMaxFileDurationUs && + fileFormat == mOutputFormat && + videoCodec == mVideoEncoder && + videoBitRate == mVideoBitRate && + videoFrameRate == mFrameRate && + videoFrameWidth == mVideoWidth && + videoFrameHeight == mVideoHeight && + audioCodec == mAudioEncoder && + audioBitRate == mAudioBitRate && + audioSampleRate == mSampleRate && + audioChannels == mAudioChannels) { + if (videoCodec == VIDEO_ENCODER_H264) { + LOGI("Force to use AVC baseline profile"); + setParamVideoEncoderProfile(OMX_VIDEO_AVCProfileBaseline); + } + } +} + +status_t GonkRecorder::checkAudioEncoderCapabilities() { + clipAudioBitRate(); + clipAudioSampleRate(); + clipNumberOfAudioChannels(); + return OK; +} + +void GonkRecorder::clipAudioBitRate() { + LOGV("clipAudioBitRate: encoder %d", mAudioEncoder); + + int minAudioBitRate = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.bps.min", mAudioEncoder); + if (mAudioBitRate < minAudioBitRate) { + LOGW("Intended audio encoding bit rate (%d) is too small" + " and will be set to (%d)", mAudioBitRate, minAudioBitRate); + mAudioBitRate = minAudioBitRate; + } + + int maxAudioBitRate = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.bps.max", mAudioEncoder); + if (mAudioBitRate > maxAudioBitRate) { + LOGW("Intended audio encoding bit rate (%d) is too large" + " and will be set to (%d)", mAudioBitRate, maxAudioBitRate); + mAudioBitRate = maxAudioBitRate; + } +} + +void GonkRecorder::clipAudioSampleRate() { + LOGV("clipAudioSampleRate: encoder %d", mAudioEncoder); + + int minSampleRate = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.hz.min", mAudioEncoder); + if (mSampleRate < minSampleRate) { + LOGW("Intended audio sample rate (%d) is too small" + " and will be set to (%d)", mSampleRate, minSampleRate); + mSampleRate = minSampleRate; + } + + int maxSampleRate = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.hz.max", mAudioEncoder); + if (mSampleRate > maxSampleRate) { + LOGW("Intended audio sample rate (%d) is too large" + " and will be set to (%d)", mSampleRate, maxSampleRate); + mSampleRate = maxSampleRate; + } +} + +void GonkRecorder::clipNumberOfAudioChannels() { + LOGV("clipNumberOfAudioChannels: encoder %d", mAudioEncoder); + + int minChannels = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.ch.min", mAudioEncoder); + if (mAudioChannels < minChannels) { + LOGW("Intended number of audio channels (%d) is too small" + " and will be set to (%d)", mAudioChannels, minChannels); + mAudioChannels = minChannels; + } + + int maxChannels = + mEncoderProfiles->getAudioEncoderParamByName( + "enc.aud.ch.max", mAudioEncoder); + if (mAudioChannels > maxChannels) { + LOGW("Intended number of audio channels (%d) is too large" + " and will be set to (%d)", mAudioChannels, maxChannels); + mAudioChannels = maxChannels; + } +} + +void GonkRecorder::clipVideoFrameHeight() { + LOGV("clipVideoFrameHeight: encoder %d", mVideoEncoder); + int minFrameHeight = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.height.min", mVideoEncoder); + int maxFrameHeight = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.height.max", mVideoEncoder); + if (mVideoHeight < minFrameHeight) { + LOGW("Intended video encoding frame height (%d) is too small" + " and will be set to (%d)", mVideoHeight, minFrameHeight); + mVideoHeight = minFrameHeight; + } else if (mVideoHeight > maxFrameHeight) { + LOGW("Intended video encoding frame height (%d) is too large" + " and will be set to (%d)", mVideoHeight, maxFrameHeight); + mVideoHeight = maxFrameHeight; + } +} + +// Set up the appropriate MediaSource depending on the chosen option +status_t GonkRecorder::setupMediaSource( + sp *mediaSource) { + if (mVideoSource == VIDEO_SOURCE_DEFAULT + || mVideoSource == VIDEO_SOURCE_CAMERA) { + sp cameraSource; + status_t err = setupCameraSource(&cameraSource); + if (err != OK) { + return err; + } + *mediaSource = cameraSource; + } else if (mVideoSource == VIDEO_SOURCE_GRALLOC_BUFFER) { + return BAD_VALUE; + } else { + return INVALID_OPERATION; + } + return OK; +} + +status_t GonkRecorder::setupCameraSource( + sp *cameraSource) { + status_t err = OK; + if ((err = checkVideoEncoderCapabilities()) != OK) { + return err; + } + Size videoSize; + videoSize.width = mVideoWidth; + videoSize.height = mVideoHeight; + bool useMeta = true; + char value[PROPERTY_VALUE_MAX]; + if (property_get("debug.camcorder.disablemeta", value, NULL) && + atoi(value)) { + useMeta = false; + } + + *cameraSource = GonkCameraSource::Create( + mCameraHandle, videoSize, mFrameRate, useMeta); + if (*cameraSource == NULL) { + return UNKNOWN_ERROR; + } + + if ((*cameraSource)->initCheck() != OK) { + (*cameraSource).clear(); + *cameraSource = NULL; + return NO_INIT; + } + + // When frame rate is not set, the actual frame rate will be set to + // the current frame rate being used. + if (mFrameRate == -1) { + int32_t frameRate = 0; + CHECK ((*cameraSource)->getFormat()->findInt32( + kKeyFrameRate, &frameRate)); + LOGI("Frame rate is not explicitly set. Use the current frame " + "rate (%d fps)", frameRate); + mFrameRate = frameRate; + } + + CHECK(mFrameRate != -1); + + mIsMetaDataStoredInVideoBuffers = + (*cameraSource)->isMetaDataStoredInVideoBuffers(); + + return OK; +} + +status_t GonkRecorder::setupVideoEncoder( + sp cameraSource, + int32_t videoBitRate, + sp *source) { + source->clear(); + + sp enc_meta = new MetaData; + enc_meta->setInt32(kKeyBitRate, videoBitRate); + enc_meta->setInt32(kKeyFrameRate, mFrameRate); + + switch (mVideoEncoder) { + case VIDEO_ENCODER_H263: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263); + break; + + case VIDEO_ENCODER_MPEG_4_SP: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); + break; + + case VIDEO_ENCODER_H264: + enc_meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); + break; + + default: + CHECK(!"Should not be here, unsupported video encoding."); + break; + } + + sp meta = cameraSource->getFormat(); + + int32_t width, height, stride, sliceHeight, colorFormat; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + CHECK(meta->findInt32(kKeyStride, &stride)); + CHECK(meta->findInt32(kKeySliceHeight, &sliceHeight)); + CHECK(meta->findInt32(kKeyColorFormat, &colorFormat)); + + enc_meta->setInt32(kKeyWidth, width); + enc_meta->setInt32(kKeyHeight, height); + enc_meta->setInt32(kKeyIFramesInterval, mIFramesIntervalSec); + enc_meta->setInt32(kKeyStride, stride); + enc_meta->setInt32(kKeySliceHeight, sliceHeight); + enc_meta->setInt32(kKeyColorFormat, colorFormat); + if (mVideoTimeScale > 0) { + enc_meta->setInt32(kKeyTimeScale, mVideoTimeScale); + } + + /* + * can set profile from the app as a parameter. + * For the mean time, set from shell + */ + + char value[PROPERTY_VALUE_MAX]; + bool customProfile = false; + + if (property_get("encoder.video.profile", value, NULL) > 0) { + customProfile = true; + } + + if (customProfile) { + switch ( mVideoEncoder ) { + case VIDEO_ENCODER_H264: + if (strncmp("base", value, 4) == 0) { + mVideoEncoderProfile = OMX_VIDEO_AVCProfileBaseline; + LOGI("H264 Baseline Profile"); + } + else if (strncmp("main", value, 4) == 0) { + mVideoEncoderProfile = OMX_VIDEO_AVCProfileMain; + LOGI("H264 Main Profile"); + } + else if (strncmp("high", value, 4) == 0) { + mVideoEncoderProfile = OMX_VIDEO_AVCProfileHigh; + LOGI("H264 High Profile"); + } + else { + LOGW("Unsupported H264 Profile"); + } + break; + case VIDEO_ENCODER_MPEG_4_SP: + if (strncmp("simple", value, 5) == 0 ) { + mVideoEncoderProfile = OMX_VIDEO_MPEG4ProfileSimple; + LOGI("MPEG4 Simple profile"); + } + else if (strncmp("asp", value, 3) == 0 ) { + mVideoEncoderProfile = OMX_VIDEO_MPEG4ProfileAdvancedSimple; + LOGI("MPEG4 Advanced Simple Profile"); + } + else { + LOGW("Unsupported MPEG4 Profile"); + } + break; + default: + LOGW("No custom profile support for other codecs"); + break; + } + } + + if (mVideoEncoderProfile != -1) { + enc_meta->setInt32(kKeyVideoProfile, mVideoEncoderProfile); + } + if (mVideoEncoderLevel != -1) { + enc_meta->setInt32(kKeyVideoLevel, mVideoEncoderLevel); + } + + uint32_t encoder_flags = 0; + if (mIsMetaDataStoredInVideoBuffers) { + LOGW("Camera source supports metadata mode, create OMXCodec for metadata"); + encoder_flags |= OMXCodec::kHardwareCodecsOnly; + encoder_flags |= OMXCodec::kStoreMetaDataInVideoBuffers; + encoder_flags |= OMXCodec::kOnlySubmitOneInputBufferAtOneTime; + } + + sp encoder = OMXCodec::Create( + GetOMX(), + enc_meta, + true /* createEncoder */, cameraSource, + NULL, encoder_flags); + if (encoder == NULL) { + LOGW("Failed to create the encoder"); + // When the encoder fails to be created, we need + // release the camera source due to the camera's lock + // and unlock mechanism. + cameraSource->stop(); + return UNKNOWN_ERROR; + } + + *source = encoder; + + return OK; +} + +status_t GonkRecorder::setupAudioEncoder(const sp& writer) { + status_t status = BAD_VALUE; + if (OK != (status = checkAudioEncoderCapabilities())) { + return status; + } + + switch(mAudioEncoder) { + case AUDIO_ENCODER_AMR_NB: + case AUDIO_ENCODER_AMR_WB: + case AUDIO_ENCODER_AAC: + break; + + default: + LOGE("Unsupported audio encoder: %d", mAudioEncoder); + return UNKNOWN_ERROR; + } + + sp audioEncoder = createAudioSource(); + if (audioEncoder == NULL) { + return UNKNOWN_ERROR; + } + + writer->addSource(audioEncoder); + return OK; +} + +status_t GonkRecorder::setupMPEG4Recording( + int outputFd, + int32_t videoWidth, int32_t videoHeight, + int32_t videoBitRate, + int32_t *totalBitRate, + sp *mediaWriter) { + mediaWriter->clear(); + *totalBitRate = 0; + status_t err = OK; + sp writer = new MPEG4Writer(outputFd); + + if (mVideoSource < VIDEO_SOURCE_LIST_END) { + + sp mediaSource; + err = setupMediaSource(&mediaSource); + if (err != OK) { + return err; + } + + sp encoder; + err = setupVideoEncoder(mediaSource, videoBitRate, &encoder); + if (err != OK) { + return err; + } + + writer->addSource(encoder); + *totalBitRate += videoBitRate; + } + + // Audio source is added at the end if it exists. + // This help make sure that the "recoding" sound is suppressed for + // camcorder applications in the recorded files. + if (mAudioSource != AUDIO_SOURCE_CNT) { + err = setupAudioEncoder(writer); + if (err != OK) return err; + *totalBitRate += mAudioBitRate; + } + + if (mInterleaveDurationUs > 0) { + reinterpret_cast(writer.get())-> + setInterleaveDuration(mInterleaveDurationUs); + } + if (mLongitudex10000 > -3600000 && mLatitudex10000 > -3600000) { + reinterpret_cast(writer.get())-> + setGeoData(mLatitudex10000, mLongitudex10000); + } + if (mMaxFileDurationUs != 0) { + writer->setMaxFileDuration(mMaxFileDurationUs); + } + if (mMaxFileSizeBytes != 0) { + writer->setMaxFileSize(mMaxFileSizeBytes); + } + + mStartTimeOffsetMs = mEncoderProfiles->getStartTimeOffsetMs(mCameraId); + if (mStartTimeOffsetMs > 0) { + reinterpret_cast(writer.get())-> + setStartTimeOffsetMs(mStartTimeOffsetMs); + } + + writer->setListener(mListener); + *mediaWriter = writer; + return OK; +} + +void GonkRecorder::setupMPEG4MetaData(int64_t startTimeUs, int32_t totalBitRate, + sp *meta) { + (*meta)->setInt64(kKeyTime, startTimeUs); + (*meta)->setInt32(kKeyFileType, mOutputFormat); + (*meta)->setInt32(kKeyBitRate, totalBitRate); + (*meta)->setInt32(kKey64BitFileOffset, mUse64BitFileOffset); + if (mMovieTimeScale > 0) { + (*meta)->setInt32(kKeyTimeScale, mMovieTimeScale); + } + if (mTrackEveryTimeDurationUs > 0) { + (*meta)->setInt64(kKeyTrackTimeStatus, mTrackEveryTimeDurationUs); + } + + char value[PROPERTY_VALUE_MAX]; + if (property_get("debug.camcorder.rotation", value, 0) > 0 && atoi(value) >= 0) { + mRotationDegrees = atoi(value); + LOGI("Setting rotation to %d", mRotationDegrees ); + } + + if (mRotationDegrees != 0) { + (*meta)->setInt32(kKeyRotation, mRotationDegrees); + } +} + +status_t GonkRecorder::startMPEG4Recording() { + int32_t totalBitRate; + status_t err = setupMPEG4Recording( + mOutputFd, mVideoWidth, mVideoHeight, + mVideoBitRate, &totalBitRate, &mWriter); + if (err != OK) { + return err; + } + + //systemTime() doesn't give correct time because + //HAVE_POSIX_CLOCKS is not defined for utils/Timers.cpp + //so, using clock_gettime directly +#include + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + int64_t startTimeUs = int64_t(t.tv_sec)*1000000000LL + t.tv_nsec; + startTimeUs = startTimeUs / 1000; + sp meta = new MetaData; + setupMPEG4MetaData(startTimeUs, totalBitRate, &meta); + + err = mWriter->start(meta.get()); + if (err != OK) { + return err; + } + + return OK; +} + +status_t GonkRecorder::pause() { + LOGV("pause"); + if (mWriter == NULL) { + return UNKNOWN_ERROR; + } + mWriter->pause(); + + if (mStarted) { + mStarted = false; + } + + + return OK; +} + +status_t GonkRecorder::stop() { + LOGV("stop"); + status_t err = OK; + + if (mWriter != NULL) { + err = mWriter->stop(); + mWriter.clear(); + } + + if (mOutputFd >= 0) { + ::close(mOutputFd); + mOutputFd = -1; + } + + if (mStarted) { + mStarted = false; + } + + + return err; +} + +status_t GonkRecorder::close() { + LOGV("close"); + stop(); + + return OK; +} + +status_t GonkRecorder::reset() { + LOGV("reset"); + stop(); + + // No audio or video source by default + mAudioSource = AUDIO_SOURCE_CNT; + mVideoSource = VIDEO_SOURCE_LIST_END; + + // Default parameters + mOutputFormat = OUTPUT_FORMAT_THREE_GPP; + mAudioEncoder = AUDIO_ENCODER_AMR_NB; + mVideoEncoder = VIDEO_ENCODER_H263; + mVideoWidth = 176; + mVideoHeight = 144; + mFrameRate = -1; + mVideoBitRate = 192000; + mSampleRate = 8000; + mAudioChannels = 1; + mAudioBitRate = 12200; + mInterleaveDurationUs = 0; + mIFramesIntervalSec = 2; + mAudioSourceNode = 0; + mUse64BitFileOffset = false; + mMovieTimeScale = -1; + mAudioTimeScale = -1; + mVideoTimeScale = -1; + mCameraId = 0; + mStartTimeOffsetMs = -1; + mVideoEncoderProfile = -1; + mVideoEncoderLevel = -1; + mMaxFileDurationUs = 0; + mMaxFileSizeBytes = 0; + mTrackEveryTimeDurationUs = 0; + mIsMetaDataStoredInVideoBuffers = false; + mEncoderProfiles = MediaProfiles::getInstance(); + mRotationDegrees = 0; + mLatitudex10000 = -3600000; + mLongitudex10000 = -3600000; + + mOutputFd = -1; + mCameraHandle = -1; + //TODO: May need to register a listener eventually + //if someone is interested in recorder events for now + //default to no listener registered + mListener = NULL; + + // Disable Audio Encoding + char value[PROPERTY_VALUE_MAX]; + property_get("camcorder.debug.disableaudio", value, "0"); + if(atoi(value)) mDisableAudio = true; + + return OK; +} + +status_t GonkRecorder::getMaxAmplitude(int *max) { + LOGV("getMaxAmplitude"); + + if (max == NULL) { + LOGE("Null pointer argument"); + return BAD_VALUE; + } + + if (mAudioSourceNode != 0) { + *max = mAudioSourceNode->getMaxAmplitude(); + } else { + *max = 0; + } + + return OK; +} + +status_t GonkRecorder::dump( + int fd, const Vector& args) const { + LOGV("dump"); + const size_t SIZE = 256; + char buffer[SIZE]; + String8 result; + if (mWriter != 0) { + mWriter->dump(fd, args); + } else { + snprintf(buffer, SIZE, " No file writer\n"); + result.append(buffer); + } + snprintf(buffer, SIZE, " Recorder: %p\n", this); + snprintf(buffer, SIZE, " Output file (fd %d):\n", mOutputFd); + result.append(buffer); + snprintf(buffer, SIZE, " File format: %d\n", mOutputFormat); + result.append(buffer); + snprintf(buffer, SIZE, " Max file size (bytes): %lld\n", mMaxFileSizeBytes); + result.append(buffer); + snprintf(buffer, SIZE, " Max file duration (us): %lld\n", mMaxFileDurationUs); + result.append(buffer); + snprintf(buffer, SIZE, " File offset length (bits): %d\n", mUse64BitFileOffset? 64: 32); + result.append(buffer); + snprintf(buffer, SIZE, " Interleave duration (us): %d\n", mInterleaveDurationUs); + result.append(buffer); + snprintf(buffer, SIZE, " Progress notification: %lld us\n", mTrackEveryTimeDurationUs); + result.append(buffer); + snprintf(buffer, SIZE, " Audio\n"); + result.append(buffer); + snprintf(buffer, SIZE, " Source: %d\n", mAudioSource); + result.append(buffer); + snprintf(buffer, SIZE, " Encoder: %d\n", mAudioEncoder); + result.append(buffer); + snprintf(buffer, SIZE, " Bit rate (bps): %d\n", mAudioBitRate); + result.append(buffer); + snprintf(buffer, SIZE, " Sampling rate (hz): %d\n", mSampleRate); + result.append(buffer); + snprintf(buffer, SIZE, " Number of channels: %d\n", mAudioChannels); + result.append(buffer); + snprintf(buffer, SIZE, " Max amplitude: %d\n", mAudioSourceNode == 0? 0: mAudioSourceNode->getMaxAmplitude()); + result.append(buffer); + snprintf(buffer, SIZE, " Video\n"); + result.append(buffer); + snprintf(buffer, SIZE, " Source: %d\n", mVideoSource); + result.append(buffer); + snprintf(buffer, SIZE, " Camera Id: %d\n", mCameraId); + result.append(buffer); + snprintf(buffer, SIZE, " Camera Handle: %d\n", mCameraHandle); + result.append(buffer); + snprintf(buffer, SIZE, " Start time offset (ms): %d\n", mStartTimeOffsetMs); + result.append(buffer); + snprintf(buffer, SIZE, " Encoder: %d\n", mVideoEncoder); + result.append(buffer); + snprintf(buffer, SIZE, " Encoder profile: %d\n", mVideoEncoderProfile); + result.append(buffer); + snprintf(buffer, SIZE, " Encoder level: %d\n", mVideoEncoderLevel); + result.append(buffer); + snprintf(buffer, SIZE, " I frames interval (s): %d\n", mIFramesIntervalSec); + result.append(buffer); + snprintf(buffer, SIZE, " Frame size (pixels): %dx%d\n", mVideoWidth, mVideoHeight); + result.append(buffer); + snprintf(buffer, SIZE, " Frame rate (fps): %d\n", mFrameRate); + result.append(buffer); + snprintf(buffer, SIZE, " Bit rate (bps): %d\n", mVideoBitRate); + result.append(buffer); + ::write(fd, result.string(), result.size()); + return OK; +} + +status_t GonkRecorder::setCameraHandle(int32_t handle) { + if (handle < 0) { + return BAD_VALUE; + } + mCameraHandle = handle; + return OK; +} + +} // namespace android diff --git a/dom/camera/GonkRecorder.h b/dom/camera/GonkRecorder.h new file mode 100644 index 00000000000..be05ef74585 --- /dev/null +++ b/dom/camera/GonkRecorder.h @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2009 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 GONK_RECORDER_H_ + +#define GONK_RECORDER_H_ + +#include +#include +#include + +#include + +namespace android { + +class GonkCameraSource; +struct MediaSource; +struct MediaWriter; +class MetaData; +struct AudioSource; +class MediaProfiles; + +struct GonkRecorder { + GonkRecorder(); + virtual ~GonkRecorder(); + + virtual status_t init(); + virtual status_t setAudioSource(audio_source_t as); + virtual status_t setVideoSource(video_source vs); + virtual status_t setOutputFormat(output_format of); + virtual status_t setAudioEncoder(audio_encoder ae); + virtual status_t setVideoEncoder(video_encoder ve); + virtual status_t setVideoSize(int width, int height); + virtual status_t setVideoFrameRate(int frames_per_second); + virtual status_t setOutputFile(const char *path); + virtual status_t setOutputFile(int fd, int64_t offset, int64_t length); + virtual status_t setParameters(const String8& params); + virtual status_t setCameraHandle(int32_t handle); + virtual status_t setListener(const sp& listener); + virtual status_t prepare(); + virtual status_t start(); + virtual status_t pause(); + virtual status_t stop(); + virtual status_t close(); + virtual status_t reset(); + virtual status_t getMaxAmplitude(int *max); + virtual status_t dump(int fd, const Vector& args) const; + // Querying a SurfaceMediaSourcer + +private: + sp mListener; + sp mWriter; + int mOutputFd; + sp mAudioSourceNode; + + audio_source_t mAudioSource; + video_source mVideoSource; + output_format mOutputFormat; + audio_encoder mAudioEncoder; + video_encoder mVideoEncoder; + bool mUse64BitFileOffset; + int32_t mVideoWidth, mVideoHeight; + int32_t mFrameRate; + int32_t mVideoBitRate; + int32_t mAudioBitRate; + int32_t mAudioChannels; + int32_t mSampleRate; + int32_t mInterleaveDurationUs; + int32_t mIFramesIntervalSec; + int32_t mCameraId; + int32_t mVideoEncoderProfile; + int32_t mVideoEncoderLevel; + int32_t mMovieTimeScale; + int32_t mVideoTimeScale; + int32_t mAudioTimeScale; + int64_t mMaxFileSizeBytes; + int64_t mMaxFileDurationUs; + int64_t mTrackEveryTimeDurationUs; + int32_t mRotationDegrees; // Clockwise + int32_t mLatitudex10000; + int32_t mLongitudex10000; + int32_t mStartTimeOffsetMs; + + String8 mParams; + + bool mIsMetaDataStoredInVideoBuffers; + MediaProfiles *mEncoderProfiles; + + bool mStarted; + // Needed when GLFrames are encoded. + // An pointer + // will be sent to the client side using which the + // frame buffers will be queued and dequeued + bool mDisableAudio; + int32_t mCameraHandle; + + status_t setupMPEG4Recording( + int outputFd, + int32_t videoWidth, int32_t videoHeight, + int32_t videoBitRate, + int32_t *totalBitRate, + sp *mediaWriter); + void setupMPEG4MetaData(int64_t startTimeUs, int32_t totalBitRate, + sp *meta); + status_t startMPEG4Recording(); + status_t startAMRRecording(); + status_t startRawAudioRecording(); + status_t startMPEG2TSRecording(); + sp createAudioSource(); + status_t checkVideoEncoderCapabilities(); + status_t checkAudioEncoderCapabilities(); + // Generic MediaSource set-up. Returns the appropriate + // source (CameraSource or SurfaceMediaSource) + // depending on the videosource type + status_t setupMediaSource(sp *mediaSource); + status_t setupCameraSource(sp *cameraSource); + // setup the surfacemediasource for the encoder + + status_t setupAudioEncoder(const sp& writer); + status_t setupVideoEncoder( + sp cameraSource, + int32_t videoBitRate, + sp *source); + + // Encoding parameter handling utilities + status_t setParameter(const String8 &key, const String8 &value); + status_t setParamAudioEncodingBitRate(int32_t bitRate); + status_t setParamAudioNumberOfChannels(int32_t channles); + status_t setParamAudioSamplingRate(int32_t sampleRate); + status_t setParamAudioTimeScale(int32_t timeScale); + status_t setParamVideoEncodingBitRate(int32_t bitRate); + status_t setParamVideoIFramesInterval(int32_t seconds); + status_t setParamVideoEncoderProfile(int32_t profile); + status_t setParamVideoEncoderLevel(int32_t level); + status_t setParamVideoCameraId(int32_t cameraId); + status_t setParamVideoTimeScale(int32_t timeScale); + status_t setParamVideoRotation(int32_t degrees); + status_t setParamTrackTimeStatus(int64_t timeDurationUs); + status_t setParamInterleaveDuration(int32_t durationUs); + status_t setParam64BitFileOffset(bool use64BitFileOffset); + status_t setParamMaxFileDurationUs(int64_t timeUs); + status_t setParamMaxFileSizeBytes(int64_t bytes); + status_t setParamMovieTimeScale(int32_t timeScale); + status_t setParamGeoDataLongitude(int64_t longitudex10000); + status_t setParamGeoDataLatitude(int64_t latitudex10000); + void clipVideoBitRate(); + void clipVideoFrameRate(); + void clipVideoFrameWidth(); + void clipVideoFrameHeight(); + void clipAudioBitRate(); + void clipAudioSampleRate(); + void clipNumberOfAudioChannels(); + void setDefaultProfileIfNecessary(); + + GonkRecorder(const GonkRecorder &); + GonkRecorder &operator=(const GonkRecorder &); +}; + +} // namespace android + +#endif // GONK_RECORDER_H_ diff --git a/dom/camera/ICameraControl.h b/dom/camera/ICameraControl.h index 6fb565e3dcc..2f5a88b74f3 100644 --- a/dom/camera/ICameraControl.h +++ b/dom/camera/ICameraControl.h @@ -6,6 +6,7 @@ #define DOM_CAMERA_ICAMERACONTROL_H #include "jsapi.h" +#include "nsIDOMDeviceStorage.h" #include "nsIDOMCameraManager.h" #include "DictionaryHelpers.h" #include "CameraCommon.h" @@ -26,8 +27,9 @@ public: virtual void StopPreview() = 0; virtual nsresult AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError) = 0; virtual nsresult TakePicture(CameraSize aSize, int32_t aRotation, const nsAString& aFileFormat, CameraPosition aPosition, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError) = 0; - virtual nsresult StartRecording(CameraSize aSize, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError) = 0; + virtual nsresult StartRecording(nsIDOMDeviceStorage* aStorageArea, const nsAString& aFilename, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError) = 0; virtual nsresult StopRecording() = 0; + virtual nsresult GetPreviewStreamVideoMode(CameraRecordingOptions* aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError) = 0; virtual nsresult Set(uint32_t aKey, const nsAString& aValue) = 0; virtual nsresult Get(uint32_t aKey, nsAString& aValue) = 0; diff --git a/dom/camera/Makefile.in b/dom/camera/Makefile.in index ff2fdbcfbcb..d7d6f24e4af 100644 --- a/dom/camera/Makefile.in +++ b/dom/camera/Makefile.in @@ -32,6 +32,9 @@ CPPSRCS += \ GonkCameraControl.cpp \ GonkCameraHwMgr.cpp \ GonkNativeWindow.cpp \ + GonkRecorder.cpp \ + GonkCameraSource.cpp \ + AudioParameter.cpp \ $(NULL) else ifeq (gonk,$(MOZ_WIDGET_TOOLKIT)) CPPSRCS += \ diff --git a/dom/camera/README b/dom/camera/README new file mode 100644 index 00000000000..cd7a49205ae --- /dev/null +++ b/dom/camera/README @@ -0,0 +1,28 @@ +This README file details from where some of the camcorder source files were derived from and how to apply the provided patch file to get the updated files for B2G. +--------------------------------- + +Following is the list of B2G files which were derived from an android ics_chocolate build. It also shows the corresponding locations where the original source files can be found: + +GonkRecoder.cpp: +https://www.codeaurora.org/gitweb/quic/la/?p=platform/frameworks/base.git;a=blob;f=media/libmediaplayerservice/StagefrightRecorder.cpp;hb=ef1672482a9c2b88d8017927df68144fee42626c + +GonkRecorder.h: +https://www.codeaurora.org/gitweb/quic/la/?p=platform/frameworks/base.git;a=blob;f=media/libmediaplayerservice/StagefrightRecorder.h;hb=e3682213bcd3fe43b059e00f0fe4dbebc3f3c35d + +GonkCameraSource.cpp: +https://www.codeaurora.org/gitweb/quic/la/?p=platform/frameworks/base.git;a=blob;f=media/libstagefright/CameraSource.cpp;hb=7fa677babfee9c241a131b22c9c1c5ab512ef2d2 + +GonkCameraSource.h: +https://www.codeaurora.org/gitweb/quic/la/?p=platform/frameworks/base.git;a=blob;f=include/media/stagefright/CameraSource.h;hb=96af14d9b013496accf40a85a66fefcba3ac0111 + +AudioParameter.cpp: +https://www.codeaurora.org/gitweb/quic/la/?p=platform/frameworks/base.git;a=blob;f=media/libmedia/AudioParameter.cpp;hb=4dc22e77cfd2a1c3671e5646ee87c5e4c15596a0 + +GonkCameraListener.h: +https://www.codeaurora.org/gitweb/quic/la/?p=platform/frameworks/base.git;a=blob;f=include/camera/Camera.h;hb=796f35e408d9dca386f90d8fbde80471ac011fa6 + +There were quite a few changes done to the above listed sources to support camcorder on B2G platform. +update.patch lists the changes on top of the original files. +update.sh shell script copies the files from an android tree and applies the patch to get updated files for B2G. + + diff --git a/dom/camera/nsIDOMCameraManager.idl b/dom/camera/nsIDOMCameraManager.idl index 95787f46189..aaf0fe8f999 100644 --- a/dom/camera/nsIDOMCameraManager.idl +++ b/dom/camera/nsIDOMCameraManager.idl @@ -5,6 +5,7 @@ interface nsIDOMBlob; +interface nsIDOMDeviceStorage; /* Used to set the dimensions of a captured picture, a preview stream, a video capture stream, etc. */ @@ -108,10 +109,8 @@ interface nsICameraCapabilities : nsISupports readonly attribute jsval videoSizes; }; -/* - These properties only affect the captured image; - invalid property settings are ignored. -*/ +/* These properties only affect the captured image; + invalid property settings are ignored. */ dictionary CameraPictureOptions { /* an object with a combination of 'height' and 'width' properties @@ -147,6 +146,14 @@ dictionary CameraPictureOptions jsval position; }; +/* These properties affect video recording. */ +dictionary CameraRecordingOptions +{ + long width; + long height; + long rotation; +}; + [scriptable, function, uuid(0444a687-4bc9-462c-8246-5423f0fe46a4)] interface nsICameraPreviewStreamCallback : nsISupports { @@ -165,10 +172,10 @@ interface nsICameraTakePictureCallback : nsISupports void handleEvent(in nsIDOMBlob picture); }; -[scriptable, function, uuid(ac43f123-529c-48d3-84dd-ad206b7aca9b)] +[scriptable, function, uuid(89a762f8-581b-410a-ad86-e2bd2113ad82)] interface nsICameraStartRecordingCallback : nsISupports { - void handleEvent(in nsIDOMMediaStream stream); + void handleEvent(); }; [scriptable, function, uuid(fb80db71-e315-42f0-9ea9-dd3dd312ed70)] @@ -187,7 +194,7 @@ interface nsICameraErrorCallback : nsISupports attributes here affect the preview, any pictures taken, and/or any video recorded by the camera. */ -[scriptable, uuid(b8949e5c-55b0-49dd-99a9-68d11342915a)] +[scriptable, uuid(469e0462-59e4-4ed5-afa9-aecd1256ee30)] interface nsICameraControl : nsISupports { readonly attribute nsICameraCapabilities capabilities; @@ -290,15 +297,21 @@ interface nsICameraControl : nsISupports [implicit_jscontext] void takePicture(in jsval aOptions, in nsICameraTakePictureCallback onSuccess, [optional] in nsICameraErrorCallback onError); - /* start recording video; 'aOptions' define the frame size of to - capture, chosen from capabilities.videoSizes, e.g.: + /* get a media stream to be used as a camera viewfinder in video mode; 'aOptions' + define the frame size of the video capture, chosen from capabilities.videoSizes, e.g.: { width: 640, - height: 480 + height: 480, + rotation: 90 } */ [implicit_jscontext] - void startRecording(in jsval aOptions, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); + void getPreviewStreamVideoMode(in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError); + + /* start recording video; + */ + [implicit_jscontext] + void startRecording(in nsIDOMDeviceStorage storageArea, in DOMString filename, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); /* stop precording video. */ void stopRecording(); diff --git a/dom/camera/update.patch b/dom/camera/update.patch new file mode 100644 index 00000000000..96f73452ffa --- /dev/null +++ b/dom/camera/update.patch @@ -0,0 +1,2296 @@ +diff --git a/GonkCameraListener.h b/GonkCameraListener.h +index 67eeef3..243264c 100644 +--- a/GonkCameraListener.h ++++ b/GonkCameraListener.h +@@ -14,49 +14,16 @@ + * limitations under the License. + */ + +-#ifndef ANDROID_HARDWARE_CAMERA_H +-#define ANDROID_HARDWARE_CAMERA_H ++#ifndef GONK_CAMERA_LISTENER_H ++#define GONK_CAMERA_LISTENER_H + + #include +-#include +-#include +-#include +-#include +-#include ++#include "libcameraservice/CameraHardwareInterface.h" + + namespace android { + +-struct CameraInfo { +- /** +- * The direction that the camera faces to. It should be CAMERA_FACING_BACK +- * or CAMERA_FACING_FRONT. +- */ +- int facing; +- +- /** +- * The orientation of the camera image. The value is the angle that the +- * camera image needs to be rotated clockwise so it shows correctly on the +- * display in its natural orientation. It should be 0, 90, 180, or 270. +- * +- * For example, suppose a device has a naturally tall screen. The +- * back-facing camera sensor is mounted in landscape. You are looking at +- * the screen. If the top side of the camera sensor is aligned with the +- * right edge of the screen in natural orientation, the value should be +- * 90. If the top side of a front-facing camera sensor is aligned with the +- * right of the screen, the value should be 270. +- */ +- int orientation; +- int mode; +-}; +- +-class ICameraService; +-class ICamera; +-class Surface; +-class Mutex; +-class String8; +- + // ref-counted object for callbacks +-class CameraListener: virtual public RefBase ++class GonkCameraListener: virtual public RefBase + { + public: + virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2) = 0; +@@ -65,133 +32,6 @@ public: + virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp& dataPtr) = 0; + }; + +-class Camera : public BnCameraClient, public IBinder::DeathRecipient +-{ +-public: +- // construct a camera client from an existing remote +- static sp create(const sp& camera); +- static int32_t getNumberOfCameras(); +- static status_t getCameraInfo(int cameraId, +- struct CameraInfo* cameraInfo); +- static sp connect(int cameraId); +- virtual ~Camera(); +- void init(); +- +- status_t reconnect(); +- void disconnect(); +- status_t lock(); +- status_t unlock(); +- +- status_t getStatus() { return mStatus; } +- +- // pass the buffered Surface to the camera service +- status_t setPreviewDisplay(const sp& surface); +- +- // pass the buffered ISurfaceTexture to the camera service +- status_t setPreviewTexture(const sp& surfaceTexture); +- +- // start preview mode, must call setPreviewDisplay first +- status_t startPreview(); +- +- // stop preview mode +- void stopPreview(); +- +- // get preview state +- bool previewEnabled(); +- +- // start recording mode, must call setPreviewDisplay first +- status_t startRecording(); +- +- // stop recording mode +- void stopRecording(); +- +- // get recording state +- bool recordingEnabled(); +- +- // release a recording frame +- void releaseRecordingFrame(const sp& mem); +- +- // autoFocus - status returned from callback +- status_t autoFocus(); +- +- // cancel auto focus +- status_t cancelAutoFocus(); +- +- // take a picture - picture returned from callback +- status_t takePicture(int msgType); +- +- // set preview/capture parameters - key/value pairs +- status_t setParameters(const String8& params); +- +- // get preview/capture parameters - key/value pairs +- String8 getParameters() const; +- +- // send command to camera driver +- status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2); +- +- // tell camera hal to store meta data or real YUV in video buffers. +- status_t storeMetaDataInBuffers(bool enabled); +- +- void setListener(const sp& listener); +- void setRecordingProxyListener(const sp& listener); +- void setPreviewCallbackFlags(int preview_callback_flag); +- +- sp getRecordingProxy(); +- +- // ICameraClient interface +- virtual void notifyCallback(int32_t msgType, int32_t ext, int32_t ext2); +- virtual void dataCallback(int32_t msgType, const sp& dataPtr, +- camera_frame_metadata_t *metadata); +- virtual void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp& dataPtr); +- +- sp remote(); +- +- class RecordingProxy : public BnCameraRecordingProxy +- { +- public: +- RecordingProxy(const sp& camera); +- +- // ICameraRecordingProxy interface +- virtual status_t startRecording(const sp& listener); +- virtual void stopRecording(); +- virtual void releaseRecordingFrame(const sp& mem); +- +- private: +- sp mCamera; +- }; +- +-private: +- Camera(); +- Camera(const Camera&); +- Camera& operator=(const Camera); +- virtual void binderDied(const wp& who); +- +- class DeathNotifier: public IBinder::DeathRecipient +- { +- public: +- DeathNotifier() { +- } +- +- virtual void binderDied(const wp& who); +- }; +- +- static sp mDeathNotifier; +- +- // helper function to obtain camera service handle +- static const sp& getCameraService(); +- +- sp mCamera; +- status_t mStatus; +- +- sp mListener; +- sp mRecordingProxyListener; +- +- friend class DeathNotifier; +- +- static Mutex mLock; +- static sp mCameraService; +-}; +- + }; // namespace android + + #endif +diff --git a/GonkCameraSource.cpp b/GonkCameraSource.cpp +index af6b340..9dba596 100644 +--- a/GonkCameraSource.cpp ++++ b/GonkCameraSource.cpp +@@ -14,29 +14,34 @@ + * limitations under the License. + */ + +-//#define LOG_NDEBUG 0 +-#define LOG_TAG "CameraSource" +-#include ++#include ++#include "nsDebug.h" ++#define DOM_CAMERA_LOG_LEVEL 3 ++#include "CameraCommon.h" ++#define LOGD DOM_CAMERA_LOGA ++#define LOGV DOM_CAMERA_LOGI ++#define LOGI DOM_CAMERA_LOGI ++#define LOGW DOM_CAMERA_LOGW ++#define LOGE DOM_CAMERA_LOGE + + #include +-#include +-#include ++#include "GonkCameraSource.h" ++#include "GonkCameraListener.h" ++#include "GonkCameraHwMgr.h" + #include + #include + #include + #include +-#include +-#include +-#include + #include + #include + ++using namespace mozilla; + namespace android { + + static const int64_t CAMERA_SOURCE_TIMEOUT_NS = 3000000000LL; + +-struct CameraSourceListener : public CameraListener { +- CameraSourceListener(const sp &source); ++struct GonkCameraSourceListener : public GonkCameraListener { ++ GonkCameraSourceListener(const sp &source); + + virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2); + virtual void postData(int32_t msgType, const sp &dataPtr, +@@ -46,41 +51,41 @@ struct CameraSourceListener : public CameraListener { + nsecs_t timestamp, int32_t msgType, const sp& dataPtr); + + protected: +- virtual ~CameraSourceListener(); ++ virtual ~GonkCameraSourceListener(); + + private: +- wp mSource; ++ wp mSource; + +- CameraSourceListener(const CameraSourceListener &); +- CameraSourceListener &operator=(const CameraSourceListener &); ++ GonkCameraSourceListener(const GonkCameraSourceListener &); ++ GonkCameraSourceListener &operator=(const GonkCameraSourceListener &); + }; + +-CameraSourceListener::CameraSourceListener(const sp &source) ++GonkCameraSourceListener::GonkCameraSourceListener(const sp &source) + : mSource(source) { + } + +-CameraSourceListener::~CameraSourceListener() { ++GonkCameraSourceListener::~GonkCameraSourceListener() { + } + +-void CameraSourceListener::notify(int32_t msgType, int32_t ext1, int32_t ext2) { ++void GonkCameraSourceListener::notify(int32_t msgType, int32_t ext1, int32_t ext2) { + LOGV("notify(%d, %d, %d)", msgType, ext1, ext2); + } + +-void CameraSourceListener::postData(int32_t msgType, const sp &dataPtr, ++void GonkCameraSourceListener::postData(int32_t msgType, const sp &dataPtr, + camera_frame_metadata_t *metadata) { + LOGV("postData(%d, ptr:%p, size:%d)", + msgType, dataPtr->pointer(), dataPtr->size()); + +- sp source = mSource.promote(); ++ sp source = mSource.promote(); + if (source.get() != NULL) { + source->dataCallback(msgType, dataPtr); + } + } + +-void CameraSourceListener::postDataTimestamp( ++void GonkCameraSourceListener::postDataTimestamp( + nsecs_t timestamp, int32_t msgType, const sp& dataPtr) { + +- sp source = mSource.promote(); ++ sp source = mSource.promote(); + if (source.get() != NULL) { + source->dataCallbackTimestamp(timestamp/1000, msgType, dataPtr); + } +@@ -114,48 +119,30 @@ static int32_t getColorFormat(const char* colorFormat) { + } + + LOGE("Uknown color format (%s), please add it to " +- "CameraSource::getColorFormat", colorFormat); ++ "GonkCameraSource::getColorFormat", colorFormat); + + CHECK_EQ(0, "Unknown color format"); + } + +-CameraSource *CameraSource::Create() { +- Size size; +- size.width = -1; +- size.height = -1; +- +- sp camera; +- return new CameraSource(camera, NULL, 0, size, -1, NULL, false); +-} +- +-// static +-CameraSource *CameraSource::CreateFromCamera( +- const sp& camera, +- const sp& proxy, +- int32_t cameraId, ++GonkCameraSource *GonkCameraSource::Create( ++ int32_t cameraHandle, + Size videoSize, + int32_t frameRate, +- const sp& surface, + bool storeMetaDataInVideoBuffers) { + +- CameraSource *source = new CameraSource(camera, proxy, cameraId, +- videoSize, frameRate, surface, ++ GonkCameraSource *source = new GonkCameraSource(cameraHandle, ++ videoSize, frameRate, + storeMetaDataInVideoBuffers); + return source; + } + +-CameraSource::CameraSource( +- const sp& camera, +- const sp& proxy, +- int32_t cameraId, ++GonkCameraSource::GonkCameraSource( ++ int32_t cameraHandle, + Size videoSize, + int32_t frameRate, +- const sp& surface, + bool storeMetaDataInVideoBuffers) + : mCameraFlags(0), + mVideoFrameRate(-1), +- mCamera(0), +- mSurface(surface), + mNumFramesReceived(0), + mLastFrameTimestampUs(0), + mStarted(false), +@@ -169,43 +156,19 @@ CameraSource::CameraSource( + mVideoSize.width = -1; + mVideoSize.height = -1; + +- mInitCheck = init(camera, proxy, cameraId, ++ mCameraHandle = cameraHandle; ++ ++ mInitCheck = init( + videoSize, frameRate, + storeMetaDataInVideoBuffers); + if (mInitCheck != OK) releaseCamera(); + } + +-status_t CameraSource::initCheck() const { ++status_t GonkCameraSource::initCheck() const { + return mInitCheck; + } + +-status_t CameraSource::isCameraAvailable( +- const sp& camera, const sp& proxy, +- int32_t cameraId) { +- +- if (camera == 0) { +- mCamera = Camera::connect(cameraId); +- if (mCamera == 0) return -EBUSY; +- mCameraFlags &= ~FLAGS_HOT_CAMERA; +- } else { +- // We get the proxy from Camera, not ICamera. We need to get the proxy +- // to the remote Camera owned by the application. Here mCamera is a +- // local Camera object created by us. We cannot use the proxy from +- // mCamera here. +- mCamera = Camera::create(camera); +- if (mCamera == 0) return -EBUSY; +- mCameraRecordingProxy = proxy; +- mCameraFlags |= FLAGS_HOT_CAMERA; +- mDeathNotifier = new DeathNotifier(); +- // isBinderAlive needs linkToDeath to work. +- mCameraRecordingProxy->asBinder()->linkToDeath(mDeathNotifier); +- } +- +- mCamera->lock(); +- +- return OK; +-} +- ++//TODO: Do we need to reimplement isCameraAvailable? + + /* + * Check to see whether the requested video width and height is one +@@ -267,7 +230,7 @@ static void getSupportedVideoSizes( + * @param params CameraParameters to retrieve the information + * @return OK if no error. + */ +-status_t CameraSource::isCameraColorFormatSupported( ++status_t GonkCameraSource::isCameraColorFormatSupported( + const CameraParameters& params) { + mColorFormat = getColorFormat(params.get( + CameraParameters::KEY_VIDEO_FRAME_FORMAT)); +@@ -292,7 +255,7 @@ status_t CameraSource::isCameraColorFormatSupported( + * @param frameRate the target frame rate in frames per second. + * @return OK if no error. + */ +-status_t CameraSource::configureCamera( ++status_t GonkCameraSource::configureCamera( + CameraParameters* params, + int32_t width, int32_t height, + int32_t frameRate) { +@@ -347,10 +310,9 @@ status_t CameraSource::configureCamera( + + if (isCameraParamChanged) { + // Either frame rate or frame size needs to be changed. +- String8 s = params->flatten(); +- if (OK != mCamera->setParameters(s)) { ++ if (OK != GonkCameraHardware::PushParameters(mCameraHandle,*params)) { + LOGE("Could not change settings." +- " Someone else is using camera %p?", mCamera.get()); ++ " Someone else is using camera ?"); + return -EBUSY; + } + } +@@ -368,7 +330,7 @@ status_t CameraSource::configureCamera( + * @param the target video frame height in pixels to check against + * @return OK if no error + */ +-status_t CameraSource::checkVideoSize( ++status_t GonkCameraSource::checkVideoSize( + const CameraParameters& params, + int32_t width, int32_t height) { + +@@ -420,7 +382,7 @@ status_t CameraSource::checkVideoSize( + * @param the target video frame rate to check against + * @return OK if no error. + */ +-status_t CameraSource::checkFrameRate( ++status_t GonkCameraSource::checkFrameRate( + const CameraParameters& params, + int32_t frameRate) { + +@@ -462,39 +424,17 @@ status_t CameraSource::checkFrameRate( + * + * @return OK if no error. + */ +-status_t CameraSource::init( +- const sp& camera, +- const sp& proxy, +- int32_t cameraId, ++status_t GonkCameraSource::init( + Size videoSize, + int32_t frameRate, + bool storeMetaDataInVideoBuffers) { + + LOGV("init"); + status_t err = OK; +- int64_t token = IPCThreadState::self()->clearCallingIdentity(); +- err = initWithCameraAccess(camera, proxy, cameraId, +- videoSize, frameRate, +- storeMetaDataInVideoBuffers); +- IPCThreadState::self()->restoreCallingIdentity(token); +- return err; +-} +- +-status_t CameraSource::initWithCameraAccess( +- const sp& camera, +- const sp& proxy, +- int32_t cameraId, +- Size videoSize, +- int32_t frameRate, +- bool storeMetaDataInVideoBuffers) { +- LOGV("initWithCameraAccess"); +- status_t err = OK; ++ //TODO: need to do something here to check the sanity of camera + +- if ((err = isCameraAvailable(camera, proxy, cameraId)) != OK) { +- LOGE("Camera connection could not be established."); +- return err; +- } +- CameraParameters params(mCamera->getParameters()); ++ CameraParameters params; ++ GonkCameraHardware::PullParameters(mCameraHandle, params); + if ((err = isCameraColorFormatSupported(params)) != OK) { + return err; + } +@@ -508,7 +448,8 @@ status_t CameraSource::initWithCameraAccess( + } + + // Check on video frame size and frame rate. +- CameraParameters newCameraParams(mCamera->getParameters()); ++ CameraParameters newCameraParams; ++ GonkCameraHardware::PullParameters(mCameraHandle, newCameraParams); + if ((err = checkVideoSize(newCameraParams, + videoSize.width, videoSize.height)) != OK) { + return err; +@@ -517,15 +458,11 @@ status_t CameraSource::initWithCameraAccess( + return err; + } + +- // This CHECK is good, since we just passed the lock/unlock +- // check earlier by calling mCamera->setParameters(). +- CHECK_EQ(OK, mCamera->setPreviewDisplay(mSurface)); +- + // By default, do not store metadata in video buffers + mIsMetaDataStoredInVideoBuffers = false; +- mCamera->storeMetaDataInBuffers(false); ++ GonkCameraHardware::StoreMetaDataInBuffers(mCameraHandle, false); + if (storeMetaDataInVideoBuffers) { +- if (OK == mCamera->storeMetaDataInBuffers(true)) { ++ if (OK == GonkCameraHardware::StoreMetaDataInBuffers(mCameraHandle, true)) { + mIsMetaDataStoredInVideoBuffers = true; + } + } +@@ -568,40 +505,28 @@ status_t CameraSource::initWithCameraAccess( + return OK; + } + +-CameraSource::~CameraSource() { ++GonkCameraSource::~GonkCameraSource() { + if (mStarted) { + stop(); + } else if (mInitCheck == OK) { + // Camera is initialized but because start() is never called, + // the lock on Camera is never released(). This makes sure + // Camera's lock is released in this case. ++ // TODO: Don't think I need to do this + releaseCamera(); + } + } + +-void CameraSource::startCameraRecording() { ++void GonkCameraSource::startCameraRecording() { + LOGV("startCameraRecording"); +- // Reset the identity to the current thread because media server owns the +- // camera and recording is started by the applications. The applications +- // will connect to the camera in ICameraRecordingProxy::startRecording. +- int64_t token = IPCThreadState::self()->clearCallingIdentity(); +- if (mCameraFlags & FLAGS_HOT_CAMERA) { +- mCamera->unlock(); +- mCamera.clear(); +- CHECK_EQ(OK, mCameraRecordingProxy->startRecording(new ProxyListener(this))); +- } else { +- mCamera->setListener(new CameraSourceListener(this)); +- mCamera->startRecording(); +- CHECK(mCamera->recordingEnabled()); +- } +- IPCThreadState::self()->restoreCallingIdentity(token); ++ CHECK_EQ(OK, GonkCameraHardware::StartRecording(mCameraHandle)); + } + +-status_t CameraSource::start(MetaData *meta) { ++status_t GonkCameraSource::start(MetaData *meta) { + LOGV("start"); + CHECK(!mStarted); + if (mInitCheck != OK) { +- LOGE("CameraSource is not initialized yet"); ++ LOGE("GonkCameraSource is not initialized yet"); + return mInitCheck; + } + +@@ -614,58 +539,34 @@ status_t CameraSource::start(MetaData *meta) { + mStartTimeUs = 0; + int64_t startTimeUs; + if (meta && meta->findInt64(kKeyTime, &startTimeUs)) { ++ LOGV("Metadata enabled, startime: %lld us", startTimeUs); + mStartTimeUs = startTimeUs; + } + ++ // Register a listener with GonkCameraHardware so that we can get callbacks ++ GonkCameraHardware::SetListener(mCameraHandle, new GonkCameraSourceListener(this)); ++ + startCameraRecording(); + + mStarted = true; + return OK; + } + +-void CameraSource::stopCameraRecording() { ++void GonkCameraSource::stopCameraRecording() { + LOGV("stopCameraRecording"); +- if (mCameraFlags & FLAGS_HOT_CAMERA) { +- mCameraRecordingProxy->stopRecording(); +- } else { +- mCamera->setListener(NULL); +- mCamera->stopRecording(); +- } ++ GonkCameraHardware::StopRecording(mCameraHandle); + } + +-void CameraSource::releaseCamera() { ++void GonkCameraSource::releaseCamera() { + LOGV("releaseCamera"); +- if (mCamera != 0) { +- int64_t token = IPCThreadState::self()->clearCallingIdentity(); +- if ((mCameraFlags & FLAGS_HOT_CAMERA) == 0) { +- LOGV("Camera was cold when we started, stopping preview"); +- mCamera->stopPreview(); +- mCamera->disconnect(); +- } +- mCamera->unlock(); +- mCamera.clear(); +- mCamera = 0; +- IPCThreadState::self()->restoreCallingIdentity(token); +- } +- if (mCameraRecordingProxy != 0) { +- mCameraRecordingProxy->asBinder()->unlinkToDeath(mDeathNotifier); +- mCameraRecordingProxy.clear(); +- } +- mCameraFlags = 0; + } + +-status_t CameraSource::stop() { +- LOGD("stop: E"); ++status_t GonkCameraSource::stop() { ++ LOGV("stop: E"); + Mutex::Autolock autoLock(mLock); + mStarted = false; + mFrameAvailableCondition.signal(); + +- int64_t token; +- bool isTokenValid = false; +- if (mCamera != 0) { +- token = IPCThreadState::self()->clearCallingIdentity(); +- isTokenValid = true; +- } + releaseQueuedFrames(); + while (!mFramesBeingEncoded.empty()) { + if (NO_ERROR != +@@ -675,11 +576,9 @@ status_t CameraSource::stop() { + mFramesBeingEncoded.size()); + } + } ++ LOGV("Calling stopCameraRecording"); + stopCameraRecording(); + releaseCamera(); +- if (isTokenValid) { +- IPCThreadState::self()->restoreCallingIdentity(token); +- } + + if (mCollectStats) { + LOGI("Frames received/encoded/dropped: %d/%d/%d in %lld us", +@@ -692,22 +591,16 @@ status_t CameraSource::stop() { + } + + CHECK_EQ(mNumFramesReceived, mNumFramesEncoded + mNumFramesDropped); +- LOGD("stop: X"); ++ LOGV("stop: X"); + return OK; + } + +-void CameraSource::releaseRecordingFrame(const sp& frame) { ++void GonkCameraSource::releaseRecordingFrame(const sp& frame) { + LOGV("releaseRecordingFrame"); +- if (mCameraRecordingProxy != NULL) { +- mCameraRecordingProxy->releaseRecordingFrame(frame); +- } else if (mCamera != NULL) { +- int64_t token = IPCThreadState::self()->clearCallingIdentity(); +- mCamera->releaseRecordingFrame(frame); +- IPCThreadState::self()->restoreCallingIdentity(token); +- } ++ GonkCameraHardware::ReleaseRecordingFrame(mCameraHandle, frame); + } + +-void CameraSource::releaseQueuedFrames() { ++void GonkCameraSource::releaseQueuedFrames() { + List >::iterator it; + while (!mFramesReceived.empty()) { + it = mFramesReceived.begin(); +@@ -717,15 +610,15 @@ void CameraSource::releaseQueuedFrames() { + } + } + +-sp CameraSource::getFormat() { ++sp GonkCameraSource::getFormat() { + return mMeta; + } + +-void CameraSource::releaseOneRecordingFrame(const sp& frame) { ++void GonkCameraSource::releaseOneRecordingFrame(const sp& frame) { + releaseRecordingFrame(frame); + } + +-void CameraSource::signalBufferReturned(MediaBuffer *buffer) { ++void GonkCameraSource::signalBufferReturned(MediaBuffer *buffer) { + LOGV("signalBufferReturned: %p", buffer->data()); + Mutex::Autolock autoLock(mLock); + for (List >::iterator it = mFramesBeingEncoded.begin(); +@@ -743,7 +636,7 @@ void CameraSource::signalBufferReturned(MediaBuffer *buffer) { + CHECK_EQ(0, "signalBufferReturned: bogus buffer"); + } + +-status_t CameraSource::read( ++status_t GonkCameraSource::read( + MediaBuffer **buffer, const ReadOptions *options) { + LOGV("read"); + +@@ -764,11 +657,7 @@ status_t CameraSource::read( + if (NO_ERROR != + mFrameAvailableCondition.waitRelative(mLock, + mTimeBetweenFrameCaptureUs * 1000LL + CAMERA_SOURCE_TIMEOUT_NS)) { +- if (mCameraRecordingProxy != 0 && +- !mCameraRecordingProxy->asBinder()->isBinderAlive()) { +- LOGW("camera recording proxy is gone"); +- return ERROR_END_OF_STREAM; +- } ++ //TODO: check sanity of camera? + LOGW("Timed out waiting for incoming camera video frames: %lld us", + mLastFrameTimestampUs); + } +@@ -790,9 +679,10 @@ status_t CameraSource::read( + return OK; + } + +-void CameraSource::dataCallbackTimestamp(int64_t timestampUs, ++void GonkCameraSource::dataCallbackTimestamp(int64_t timestampUs, + int32_t msgType, const sp &data) { + LOGV("dataCallbackTimestamp: timestamp %lld us", timestampUs); ++ //LOGV("dataCallbackTimestamp: data %x size %d", data->pointer(), data->size()); + Mutex::Autolock autoLock(mLock); + if (!mStarted || (mNumFramesReceived == 0 && timestampUs < mStartTimeUs)) { + LOGV("Drop frame at %lld/%lld us", timestampUs, mStartTimeUs); +@@ -808,7 +698,7 @@ void CameraSource::dataCallbackTimestamp(int64_t timestampUs, + } + + // May need to skip frame or modify timestamp. Currently implemented +- // by the subclass CameraSourceTimeLapse. ++ // by the subclass GonkCameraSourceTimeLapse. + if (skipCurrentFrame(timestampUs)) { + releaseOneRecordingFrame(data); + return; +@@ -839,22 +729,9 @@ void CameraSource::dataCallbackTimestamp(int64_t timestampUs, + mFrameAvailableCondition.signal(); + } + +-bool CameraSource::isMetaDataStoredInVideoBuffers() const { ++bool GonkCameraSource::isMetaDataStoredInVideoBuffers() const { + LOGV("isMetaDataStoredInVideoBuffers"); + return mIsMetaDataStoredInVideoBuffers; + } + +-CameraSource::ProxyListener::ProxyListener(const sp& source) { +- mSource = source; +-} +- +-void CameraSource::ProxyListener::dataCallbackTimestamp( +- nsecs_t timestamp, int32_t msgType, const sp& dataPtr) { +- mSource->dataCallbackTimestamp(timestamp / 1000, msgType, dataPtr); +-} +- +-void CameraSource::DeathNotifier::binderDied(const wp& who) { +- LOGI("Camera recording proxy died"); +-} +- +-} // namespace android ++} // namespace android +diff --git a/GonkCameraSource.h b/GonkCameraSource.h +index 446720b..fe58f96 100644 +--- a/GonkCameraSource.h ++++ b/GonkCameraSource.h +@@ -14,69 +14,31 @@ + * limitations under the License. + */ + +-#ifndef CAMERA_SOURCE_H_ ++#ifndef GONK_CAMERA_SOURCE_H_ + +-#define CAMERA_SOURCE_H_ ++#define GONK_CAMERA_SOURCE_H_ + + #include + #include +-#include +-#include + #include + #include + #include ++#include + + namespace android { + + class IMemory; +-class Camera; +-class Surface; ++class GonkCameraSourceListener; + +-class CameraSource : public MediaSource, public MediaBufferObserver { ++class GonkCameraSource : public MediaSource, public MediaBufferObserver { + public: +- /** +- * Factory method to create a new CameraSource using the current +- * settings (such as video size, frame rate, color format, etc) +- * from the default camera. +- * +- * @return NULL on error. +- */ +- static CameraSource *Create(); + +- /** +- * Factory method to create a new CameraSource. +- * +- * @param camera the video input frame data source. If it is NULL, +- * we will try to connect to the camera with the given +- * cameraId. +- * +- * @param cameraId the id of the camera that the source will connect +- * to if camera is NULL; otherwise ignored. +- * +- * @param videoSize the dimension (in pixels) of the video frame +- * @param frameRate the target frames per second +- * @param surface the preview surface for display where preview +- * frames are sent to +- * @param storeMetaDataInVideoBuffers true to request the camera +- * source to store meta data in video buffers; false to +- * request the camera source to store real YUV frame data +- * in the video buffers. The camera source may not support +- * storing meta data in video buffers, if so, a request +- * to do that will NOT be honored. To find out whether +- * meta data is actually being stored in video buffers +- * during recording, call isMetaDataStoredInVideoBuffers(). +- * +- * @return NULL on error. +- */ +- static CameraSource *CreateFromCamera(const sp &camera, +- const sp &proxy, +- int32_t cameraId, +- Size videoSize, +- int32_t frameRate, +- const sp& surface, +- bool storeMetaDataInVideoBuffers = false); ++ static GonkCameraSource *Create(int32_t cameraHandle, ++ Size videoSize, ++ int32_t frameRate, ++ bool storeMetaDataInVideoBuffers = false); + +- virtual ~CameraSource(); ++ virtual ~GonkCameraSource(); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); +@@ -84,14 +46,14 @@ public: + MediaBuffer **buffer, const ReadOptions *options = NULL); + + /** +- * Check whether a CameraSource object is properly initialized. ++ * Check whether a GonkCameraSource object is properly initialized. + * Must call this method before stop(). + * @return OK if initialization has successfully completed. + */ + virtual status_t initCheck() const; + + /** +- * Returns the MetaData associated with the CameraSource, ++ * Returns the MetaData associated with the GonkCameraSource, + * including: + * kKeyColorFormat: YUV color format of the video frames + * kKeyWidth, kKeyHeight: dimension (in pixels) of the video frames +@@ -113,22 +75,6 @@ public: + virtual void signalBufferReturned(MediaBuffer* buffer); + + protected: +- class ProxyListener: public BnCameraRecordingProxyListener { +- public: +- ProxyListener(const sp& source); +- virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType, +- const sp &data); +- +- private: +- sp mSource; +- }; +- +- // isBinderAlive needs linkToDeath to work. +- class DeathNotifier: public IBinder::DeathRecipient { +- public: +- DeathNotifier() {} +- virtual void binderDied(const wp& who); +- }; + + enum CameraFlags { + FLAGS_SET_CAMERA = 1L << 0, +@@ -141,10 +87,6 @@ protected: + int32_t mColorFormat; + status_t mInitCheck; + +- sp mCamera; +- sp mCameraRecordingProxy; +- sp mDeathNotifier; +- sp mSurface; + sp mMeta; + + int64_t mStartTimeUs; +@@ -156,11 +98,9 @@ protected: + // Time between capture of two frames. + int64_t mTimeBetweenFrameCaptureUs; + +- CameraSource(const sp& camera, const sp& proxy, +- int32_t cameraId, ++ GonkCameraSource(int32_t cameraHandle, + Size videoSize, int32_t frameRate, +- const sp& surface, +- bool storeMetaDataInVideoBuffers); ++ bool storeMetaDataInVideoBuffers = false); + + virtual void startCameraRecording(); + virtual void stopCameraRecording(); +@@ -170,6 +110,7 @@ protected: + // Called from dataCallbackTimestamp. + virtual bool skipCurrentFrame(int64_t timestampUs) {return false;} + ++ friend class GonkCameraSourceListener; + // Callback called when still camera raw data is available. + virtual void dataCallback(int32_t msgType, const sp &data) {} + +@@ -177,7 +118,6 @@ protected: + const sp &data); + + private: +- friend class CameraSourceListener; + + Mutex mLock; + Condition mFrameAvailableCondition; +@@ -192,23 +132,13 @@ private: + int64_t mGlitchDurationThresholdUs; + bool mCollectStats; + bool mIsMetaDataStoredInVideoBuffers; ++ int32_t mCameraHandle; + + void releaseQueuedFrames(); + void releaseOneRecordingFrame(const sp& frame); + +- +- status_t init(const sp& camera, const sp& proxy, +- int32_t cameraId, Size videoSize, int32_t frameRate, +- bool storeMetaDataInVideoBuffers); +- +- status_t initWithCameraAccess( +- const sp& camera, const sp& proxy, +- int32_t cameraId, Size videoSize, int32_t frameRate, ++ status_t init(Size videoSize, int32_t frameRate, + bool storeMetaDataInVideoBuffers); +- +- status_t isCameraAvailable(const sp& camera, +- const sp& proxy, +- int32_t cameraId); + status_t isCameraColorFormatSupported(const CameraParameters& params); + status_t configureCamera(CameraParameters* params, + int32_t width, int32_t height, +@@ -222,10 +152,10 @@ private: + + void releaseCamera(); + +- CameraSource(const CameraSource &); +- CameraSource &operator=(const CameraSource &); ++ GonkCameraSource(const GonkCameraSource &); ++ GonkCameraSource &operator=(const GonkCameraSource &); + }; + + } // namespace android + +-#endif // CAMERA_SOURCE_H_ ++#endif // GONK_CAMERA_SOURCE_H_ +diff --git a/GonkRecorder.cpp b/GonkRecorder.cpp +index b20ca9d..2dc625c 100644 +--- a/GonkRecorder.cpp ++++ b/GonkRecorder.cpp +@@ -16,35 +16,23 @@ + */ + + //#define LOG_NDEBUG 0 +-#define LOG_TAG "StagefrightRecorder" ++#define LOG_TAG "GonkRecorder" ++ + #include + #include +-#include "StagefrightRecorder.h" +- +-#include +-#include ++#include "GonkRecorder.h" + +-#include + #include + #include +-#include +-#include +-#include +-#include +-#include + #include + #include + #include + #include + #include + #include +-#include ++#include + #include +-#include + #include +-#include +-#include +-#include + #include + + #include +@@ -57,51 +45,41 @@ + #include "ARTPWriter.h" + + #include ++#include "GonkCameraSource.h" + + namespace android { + +-// To collect the encoder usage for the battery app +-static void addBatteryData(uint32_t params) { +- sp binder = +- defaultServiceManager()->getService(String16("media.player")); +- sp service = interface_cast(binder); +- CHECK(service.get() != NULL); +- +- service->addBatteryData(params); ++static sp sOMX = NULL; ++static sp GetOMX() { ++ if(sOMX.get() == NULL) { ++ sOMX = new OMX; ++ } ++ return sOMX; + } + +- +-StagefrightRecorder::StagefrightRecorder() ++GonkRecorder::GonkRecorder() + : mWriter(NULL), + mOutputFd(-1), + mAudioSource(AUDIO_SOURCE_CNT), + mVideoSource(VIDEO_SOURCE_LIST_END), +- mStarted(false), mSurfaceMediaSource(NULL), ++ mStarted(false), + mDisableAudio(false) { + + LOGV("Constructor"); + reset(); + } + +-StagefrightRecorder::~StagefrightRecorder() { ++GonkRecorder::~GonkRecorder() { + LOGV("Destructor"); + stop(); + } + +-status_t StagefrightRecorder::init() { ++status_t GonkRecorder::init() { + LOGV("init"); + return OK; + } + +-// The client side of mediaserver asks it to creat a SurfaceMediaSource +-// and return a interface reference. The client side will use that +-// while encoding GL Frames +-sp StagefrightRecorder::querySurfaceMediaSource() const { +- LOGV("Get SurfaceMediaSource"); +- return mSurfaceMediaSource; +-} +- +-status_t StagefrightRecorder::setAudioSource(audio_source_t as) { ++status_t GonkRecorder::setAudioSource(audio_source_t as) { + LOGV("setAudioSource: %d", as); + if (as < AUDIO_SOURCE_DEFAULT || + as >= AUDIO_SOURCE_CNT) { +@@ -122,7 +100,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setVideoSource(video_source vs) { ++status_t GonkRecorder::setVideoSource(video_source vs) { + LOGV("setVideoSource: %d", vs); + if (vs < VIDEO_SOURCE_DEFAULT || + vs >= VIDEO_SOURCE_LIST_END) { +@@ -139,7 +117,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setOutputFormat(output_format of) { ++status_t GonkRecorder::setOutputFormat(output_format of) { + LOGV("setOutputFormat: %d", of); + if (of < OUTPUT_FORMAT_DEFAULT || + of >= OUTPUT_FORMAT_LIST_END) { +@@ -156,7 +134,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setAudioEncoder(audio_encoder ae) { ++status_t GonkRecorder::setAudioEncoder(audio_encoder ae) { + LOGV("setAudioEncoder: %d", ae); + if (ae < AUDIO_ENCODER_DEFAULT || + ae >= AUDIO_ENCODER_LIST_END) { +@@ -174,21 +152,10 @@ + mAudioEncoder = ae; + } + +- // Use default values if appropriate setparam's weren't called. +- if(mAudioEncoder == AUDIO_ENCODER_AAC) { +- mSampleRate = mSampleRate ? mSampleRate : 48000; +- mAudioChannels = mAudioChannels ? mAudioChannels : 2; +- mAudioBitRate = mAudioBitRate ? mAudioBitRate : 156000; +- } +- else{ +- mSampleRate = mSampleRate ? mSampleRate : 8000; +- mAudioChannels = mAudioChannels ? mAudioChannels : 1; +- mAudioBitRate = mAudioBitRate ? mAudioBitRate : 12200; +- } + return OK; + } + +-status_t StagefrightRecorder::setVideoEncoder(video_encoder ve) { ++status_t GonkRecorder::setVideoEncoder(video_encoder ve) { + LOGV("setVideoEncoder: %d", ve); + if (ve < VIDEO_ENCODER_DEFAULT || + ve >= VIDEO_ENCODER_LIST_END) { +@@ -205,7 +172,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setVideoSize(int width, int height) { ++status_t GonkRecorder::setVideoSize(int width, int height) { + LOGV("setVideoSize: %dx%d", width, height); + if (width <= 0 || height <= 0) { + LOGE("Invalid video size: %dx%d", width, height); +@@ -219,7 +186,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setVideoFrameRate(int frames_per_second) { ++status_t GonkRecorder::setVideoFrameRate(int frames_per_second) { + LOGV("setVideoFrameRate: %d", frames_per_second); + if ((frames_per_second <= 0 && frames_per_second != -1) || + frames_per_second > 120) { +@@ -233,31 +200,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setCamera(const sp &camera, +- const sp &proxy) { +- LOGV("setCamera"); +- if (camera == 0) { +- LOGE("camera is NULL"); +- return BAD_VALUE; +- } +- if (proxy == 0) { +- LOGE("camera proxy is NULL"); +- return BAD_VALUE; +- } +- +- mCamera = camera; +- mCameraProxy = proxy; +- return OK; +-} +- +-status_t StagefrightRecorder::setPreviewSurface(const sp &surface) { +- LOGV("setPreviewSurface: %p", surface.get()); +- mPreviewSurface = surface; +- +- return OK; +-} +- +-status_t StagefrightRecorder::setOutputFile(const char *path) { ++status_t GonkRecorder::setOutputFile(const char *path) { + LOGE("setOutputFile(const char*) must not be called"); + // We don't actually support this at all, as the media_server process + // no longer has permissions to create files. +@@ -265,7 +208,7 @@ + return -EPERM; + } + +-status_t StagefrightRecorder::setOutputFile(int fd, int64_t offset, int64_t length) { ++status_t GonkRecorder::setOutputFile(int fd, int64_t offset, int64_t length) { + LOGV("setOutputFile: %d, %lld, %lld", fd, offset, length); + // These don't make any sense, do they? + CHECK_EQ(offset, 0); +@@ -339,7 +282,7 @@ + s->setTo(String8(&data[leading_space], i - leading_space)); + } + +-status_t StagefrightRecorder::setParamAudioSamplingRate(int32_t sampleRate) { ++status_t GonkRecorder::setParamAudioSamplingRate(int32_t sampleRate) { + LOGV("setParamAudioSamplingRate: %d", sampleRate); + if (sampleRate <= 0) { + LOGE("Invalid audio sampling rate: %d", sampleRate); +@@ -351,7 +294,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamAudioNumberOfChannels(int32_t channels) { ++status_t GonkRecorder::setParamAudioNumberOfChannels(int32_t channels) { + LOGV("setParamAudioNumberOfChannels: %d", channels); + if (channels <= 0 || channels >= 3) { + LOGE("Invalid number of audio channels: %d", channels); +@@ -363,7 +306,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamAudioEncodingBitRate(int32_t bitRate) { ++status_t GonkRecorder::setParamAudioEncodingBitRate(int32_t bitRate) { + LOGV("setParamAudioEncodingBitRate: %d", bitRate); + if (bitRate <= 0) { + LOGE("Invalid audio encoding bit rate: %d", bitRate); +@@ -378,7 +321,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamVideoEncodingBitRate(int32_t bitRate) { ++status_t GonkRecorder::setParamVideoEncodingBitRate(int32_t bitRate) { + LOGV("setParamVideoEncodingBitRate: %d", bitRate); + if (bitRate <= 0) { + LOGE("Invalid video encoding bit rate: %d", bitRate); +@@ -394,7 +337,7 @@ + } + + // Always rotate clockwise, and only support 0, 90, 180 and 270 for now. +-status_t StagefrightRecorder::setParamVideoRotation(int32_t degrees) { ++status_t GonkRecorder::setParamVideoRotation(int32_t degrees) { + LOGV("setParamVideoRotation: %d", degrees); + if (degrees < 0 || degrees % 90 != 0) { + LOGE("Unsupported video rotation angle: %d", degrees); +@@ -404,7 +347,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamMaxFileDurationUs(int64_t timeUs) { ++status_t GonkRecorder::setParamMaxFileDurationUs(int64_t timeUs) { + LOGV("setParamMaxFileDurationUs: %lld us", timeUs); + + // This is meant for backward compatibility for MediaRecorder.java +@@ -423,7 +366,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamMaxFileSizeBytes(int64_t bytes) { ++status_t GonkRecorder::setParamMaxFileSizeBytes(int64_t bytes) { + LOGV("setParamMaxFileSizeBytes: %lld bytes", bytes); + + // This is meant for backward compatibility for MediaRecorder.java +@@ -449,7 +392,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamInterleaveDuration(int32_t durationUs) { ++status_t GonkRecorder::setParamInterleaveDuration(int32_t durationUs) { + LOGV("setParamInterleaveDuration: %d", durationUs); + if (durationUs <= 500000) { // 500 ms + // If interleave duration is too small, it is very inefficient to do +@@ -471,20 +414,20 @@ + // If seconds < 0, only the first frame is I frame, and rest are all P frames + // If seconds == 0, all frames are encoded as I frames. No P frames + // If seconds > 0, it is the time spacing (seconds) between 2 neighboring I frames +-status_t StagefrightRecorder::setParamVideoIFramesInterval(int32_t seconds) { ++status_t GonkRecorder::setParamVideoIFramesInterval(int32_t seconds) { + LOGV("setParamVideoIFramesInterval: %d seconds", seconds); + mIFramesIntervalSec = seconds; + return OK; + } + +-status_t StagefrightRecorder::setParam64BitFileOffset(bool use64Bit) { ++status_t GonkRecorder::setParam64BitFileOffset(bool use64Bit) { + LOGV("setParam64BitFileOffset: %s", + use64Bit? "use 64 bit file offset": "use 32 bit file offset"); + mUse64BitFileOffset = use64Bit; + return OK; + } + +-status_t StagefrightRecorder::setParamVideoCameraId(int32_t cameraId) { ++status_t GonkRecorder::setParamVideoCameraId(int32_t cameraId) { + LOGV("setParamVideoCameraId: %d", cameraId); + if (cameraId < 0) { + return BAD_VALUE; +@@ -493,7 +436,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamTrackTimeStatus(int64_t timeDurationUs) { ++status_t GonkRecorder::setParamTrackTimeStatus(int64_t timeDurationUs) { + LOGV("setParamTrackTimeStatus: %lld", timeDurationUs); + if (timeDurationUs < 20000) { // Infeasible if shorter than 20 ms? + LOGE("Tracking time duration too short: %lld us", timeDurationUs); +@@ -503,7 +446,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamVideoEncoderProfile(int32_t profile) { ++status_t GonkRecorder::setParamVideoEncoderProfile(int32_t profile) { + LOGV("setParamVideoEncoderProfile: %d", profile); + + // Additional check will be done later when we load the encoder. +@@ -512,7 +455,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamVideoEncoderLevel(int32_t level) { ++status_t GonkRecorder::setParamVideoEncoderLevel(int32_t level) { + LOGV("setParamVideoEncoderLevel: %d", level); + + // Additional check will be done later when we load the encoder. +@@ -521,7 +464,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamMovieTimeScale(int32_t timeScale) { ++status_t GonkRecorder::setParamMovieTimeScale(int32_t timeScale) { + LOGV("setParamMovieTimeScale: %d", timeScale); + + // The range is set to be the same as the audio's time scale range +@@ -534,7 +477,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamVideoTimeScale(int32_t timeScale) { ++status_t GonkRecorder::setParamVideoTimeScale(int32_t timeScale) { + LOGV("setParamVideoTimeScale: %d", timeScale); + + // 60000 is chosen to make sure that each video frame from a 60-fps +@@ -547,7 +490,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamAudioTimeScale(int32_t timeScale) { ++status_t GonkRecorder::setParamAudioTimeScale(int32_t timeScale) { + LOGV("setParamAudioTimeScale: %d", timeScale); + + // 96000 Hz is the highest sampling rate support in AAC. +@@ -559,33 +502,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamTimeLapseEnable(int32_t timeLapseEnable) { +- LOGV("setParamTimeLapseEnable: %d", timeLapseEnable); +- +- if(timeLapseEnable == 0) { +- mCaptureTimeLapse = false; +- } else if (timeLapseEnable == 1) { +- mCaptureTimeLapse = true; +- } else { +- return BAD_VALUE; +- } +- return OK; +-} +- +-status_t StagefrightRecorder::setParamTimeBetweenTimeLapseFrameCapture(int64_t timeUs) { +- LOGV("setParamTimeBetweenTimeLapseFrameCapture: %lld us", timeUs); +- +- // Not allowing time more than a day +- if (timeUs <= 0 || timeUs > 86400*1E6) { +- LOGE("Time between time lapse frame capture (%lld) is out of range [0, 1 Day]", timeUs); +- return BAD_VALUE; +- } +- +- mTimeBetweenTimeLapseFrameCaptureUs = timeUs; +- return OK; +-} +- +-status_t StagefrightRecorder::setParamGeoDataLongitude( ++status_t GonkRecorder::setParamGeoDataLongitude( + int64_t longitudex10000) { + + if (longitudex10000 > 1800000 || longitudex10000 < -1800000) { +@@ -595,7 +512,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParamGeoDataLatitude( ++status_t GonkRecorder::setParamGeoDataLatitude( + int64_t latitudex10000) { + + if (latitudex10000 > 900000 || latitudex10000 < -900000) { +@@ -605,7 +522,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setParameter( ++status_t GonkRecorder::setParameter( + const String8 &key, const String8 &value) { + LOGV("setParameter: key (%s) => value (%s)", key.string(), value.string()); + if (key == "max-duration") { +@@ -703,24 +620,13 @@ + if (safe_strtoi32(value.string(), &timeScale)) { + return setParamVideoTimeScale(timeScale); + } +- } else if (key == "time-lapse-enable") { +- int32_t timeLapseEnable; +- if (safe_strtoi32(value.string(), &timeLapseEnable)) { +- return setParamTimeLapseEnable(timeLapseEnable); +- } +- } else if (key == "time-between-time-lapse-frame-capture") { +- int64_t timeBetweenTimeLapseFrameCaptureMs; +- if (safe_strtoi64(value.string(), &timeBetweenTimeLapseFrameCaptureMs)) { +- return setParamTimeBetweenTimeLapseFrameCapture( +- 1000LL * timeBetweenTimeLapseFrameCaptureMs); +- } + } else { + LOGE("setParameter: failed to find key %s", key.string()); + } + return BAD_VALUE; + } + +-status_t StagefrightRecorder::setParameters(const String8 ¶ms) { ++status_t GonkRecorder::setParameters(const String8 ¶ms) { + LOGV("setParameters: %s", params.string()); + const char *cparams = params.string(); + const char *key_start = cparams; +@@ -755,13 +661,13 @@ + return OK; + } + +-status_t StagefrightRecorder::setListener(const sp &listener) { ++status_t GonkRecorder::setListener(const sp &listener) { + mListener = listener; + + return OK; + } + +-status_t StagefrightRecorder::prepare() { ++status_t GonkRecorder::prepare() { + LOGV(" %s E", __func__ ); + + if(mVideoSource != VIDEO_SOURCE_LIST_END && mVideoEncoder != VIDEO_ENCODER_LIST_END && mVideoHeight && mVideoWidth && /*Video recording*/ +@@ -776,17 +682,15 @@ + return OK; + } + +-status_t StagefrightRecorder::start() { ++status_t GonkRecorder::start() { + CHECK(mOutputFd >= 0); + + if (mWriter != NULL) { +- LOGE("File writer is not avaialble"); ++ LOGE("File writer is not available"); + return UNKNOWN_ERROR; + } + + status_t status = OK; +- if(AUDIO_SOURCE_FM_RX_A2DP == mAudioSource) +- return startFMA2DPWriter(); + + switch (mOutputFormat) { + case OUTPUT_FORMAT_DEFAULT: +@@ -800,22 +704,9 @@ + status = startAMRRecording(); + break; + +- case OUTPUT_FORMAT_AAC_ADIF: +- case OUTPUT_FORMAT_AAC_ADTS: +- status = startAACRecording(); +- break; +- +- case OUTPUT_FORMAT_RTP_AVP: +- status = startRTPRecording(); +- break; +- + case OUTPUT_FORMAT_MPEG2TS: + status = startMPEG2TSRecording(); + break; +- +- case OUTPUT_FORMAT_QCP: +- status = startExtendedRecording( ); +- break; + default: + LOGE("Unsupported output file format: %d", mOutputFormat); + status = UNKNOWN_ERROR; +@@ -824,22 +715,12 @@ + + if ((status == OK) && (!mStarted)) { + mStarted = true; +- +- uint32_t params = IMediaPlayerService::kBatteryDataCodecStarted; +- if (mAudioSource != AUDIO_SOURCE_CNT) { +- params |= IMediaPlayerService::kBatteryDataTrackAudio; +- } +- if (mVideoSource != VIDEO_SOURCE_LIST_END) { +- params |= IMediaPlayerService::kBatteryDataTrackVideo; +- } +- +- addBatteryData(params); + } + + return status; + } + +-sp StagefrightRecorder::createAudioSource() { ++sp GonkRecorder::createAudioSource() { + + bool tunneledSource = false; + const char *tunnelMime; +@@ -907,12 +788,6 @@ + case AUDIO_ENCODER_AAC: + mime = MEDIA_MIMETYPE_AUDIO_AAC; + break; +- case AUDIO_ENCODER_EVRC: +- mime = MEDIA_MIMETYPE_AUDIO_EVRC; +- break; +- case AUDIO_ENCODER_QCELP: +- mime = MEDIA_MIMETYPE_AUDIO_QCELP; +- break; + default: + LOGE("Unknown audio encoder: %d", mAudioEncoder); + return NULL; +@@ -931,36 +806,17 @@ + encMeta->setInt32(kKeyTimeScale, mAudioTimeScale); + } + +- OMXClient client; +- CHECK_EQ(client.connect(), OK); +- ++ // use direct OMX interface instead of connecting to ++ // mediaserver over binder calls + sp audioEncoder = +- OMXCodec::Create(client.interface(), encMeta, ++ OMXCodec::Create(GetOMX(), encMeta, + true /* createEncoder */, audioSource); + mAudioSourceNode = audioSource; + + return audioEncoder; + } + +-status_t StagefrightRecorder::startAACRecording() { +- // FIXME: +- // Add support for OUTPUT_FORMAT_AAC_ADIF +- CHECK(mOutputFormat == OUTPUT_FORMAT_AAC_ADTS); +- +- CHECK(mAudioEncoder == AUDIO_ENCODER_AAC); +- CHECK(mAudioSource != AUDIO_SOURCE_CNT); +- +- mWriter = new AACWriter(mOutputFd); +- status_t status = startRawAudioRecording(); +- if (status != OK) { +- mWriter.clear(); +- mWriter = NULL; +- } +- +- return status; +-} +- +-status_t StagefrightRecorder::startAMRRecording() { ++status_t GonkRecorder::startAMRRecording() { + CHECK(mOutputFormat == OUTPUT_FORMAT_AMR_NB || + mOutputFormat == OUTPUT_FORMAT_AMR_WB); + +@@ -971,28 +827,12 @@ + mAudioEncoder); + return BAD_VALUE; + } +- if (mSampleRate != 8000) { +- LOGE("Invalid sampling rate %d used for AMRNB recording", +- mSampleRate); +- return BAD_VALUE; +- } + } else { // mOutputFormat must be OUTPUT_FORMAT_AMR_WB + if (mAudioEncoder != AUDIO_ENCODER_AMR_WB) { + LOGE("Invlaid encoder %d used for AMRWB recording", + mAudioEncoder); + return BAD_VALUE; + } +- if (mSampleRate != 16000) { +- LOGE("Invalid sample rate %d used for AMRWB recording", +- mSampleRate); +- return BAD_VALUE; +- } +- } +- +- if (mAudioChannels != 1) { +- LOGE("Invalid number of audio channels %d used for amr recording", +- mAudioChannels); +- return BAD_VALUE; + } + + mWriter = new AMRWriter(mOutputFd); +@@ -1004,7 +844,7 @@ + return status; + } + +-status_t StagefrightRecorder::startRawAudioRecording() { ++status_t GonkRecorder::startRawAudioRecording() { + if (mAudioSource >= AUDIO_SOURCE_CNT) { + LOGE("Invalid audio source: %d", mAudioSource); + return BAD_VALUE; +@@ -1035,62 +875,7 @@ + return OK; + } + +-status_t StagefrightRecorder::startFMA2DPWriter() { +- /* FM soc outputs at 48k */ +- mSampleRate = 48000; +- mAudioChannels = 2; +- +- sp meta = new MetaData; +- meta->setInt32(kKeyChannelCount, mAudioChannels); +- meta->setInt32(kKeySampleRate, mSampleRate); +- +- mWriter = new FMA2DPWriter(); +- mWriter->setListener(mListener); +- mWriter->start(meta.get()); +- return OK; +-} +- +-status_t StagefrightRecorder::startRTPRecording() { +- CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_RTP_AVP); +- +- if ((mAudioSource != AUDIO_SOURCE_CNT +- && mVideoSource != VIDEO_SOURCE_LIST_END) +- || (mAudioSource == AUDIO_SOURCE_CNT +- && mVideoSource == VIDEO_SOURCE_LIST_END)) { +- // Must have exactly one source. +- return BAD_VALUE; +- } +- +- if (mOutputFd < 0) { +- return BAD_VALUE; +- } +- +- sp source; +- +- if (mAudioSource != AUDIO_SOURCE_CNT) { +- source = createAudioSource(); +- } else { +- +- sp mediaSource; +- status_t err = setupMediaSource(&mediaSource); +- if (err != OK) { +- return err; +- } +- +- err = setupVideoEncoder(mediaSource, mVideoBitRate, &source); +- if (err != OK) { +- return err; +- } +- } +- +- mWriter = new ARTPWriter(mOutputFd); +- mWriter->addSource(source); +- mWriter->setListener(mListener); +- +- return mWriter->start(); +-} +- +-status_t StagefrightRecorder::startMPEG2TSRecording() { ++status_t GonkRecorder::startMPEG2TSRecording() { + CHECK_EQ(mOutputFormat, OUTPUT_FORMAT_MPEG2TS); + + sp writer = new MPEG2TSWriter(mOutputFd); +@@ -1141,7 +926,7 @@ + return mWriter->start(); + } + +-void StagefrightRecorder::clipVideoFrameRate() { ++void GonkRecorder::clipVideoFrameRate() { + LOGV("clipVideoFrameRate: encoder %d", mVideoEncoder); + int minFrameRate = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.fps.min", mVideoEncoder); +@@ -1158,7 +943,7 @@ + } + } + +-void StagefrightRecorder::clipVideoBitRate() { ++void GonkRecorder::clipVideoBitRate() { + LOGV("clipVideoBitRate: encoder %d", mVideoEncoder); + int minBitRate = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.bps.min", mVideoEncoder); +@@ -1175,7 +960,7 @@ + } + } + +-void StagefrightRecorder::clipVideoFrameWidth() { ++void GonkRecorder::clipVideoFrameWidth() { + LOGV("clipVideoFrameWidth: encoder %d", mVideoEncoder); + int minFrameWidth = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.width.min", mVideoEncoder); +@@ -1192,8 +977,7 @@ + } + } + +-status_t StagefrightRecorder::checkVideoEncoderCapabilities() { +- if (!mCaptureTimeLapse) { ++status_t GonkRecorder::checkVideoEncoderCapabilities() { + // Dont clip for time lapse capture as encoder will have enough + // time to encode because of slow capture rate of time lapse. + clipVideoBitRate(); +@@ -1201,13 +985,12 @@ + clipVideoFrameWidth(); + clipVideoFrameHeight(); + setDefaultProfileIfNecessary(); +- } + return OK; + } + + // Set to use AVC baseline profile if the encoding parameters matches + // CAMCORDER_QUALITY_LOW profile; this is for the sake of MMS service. +-void StagefrightRecorder::setDefaultProfileIfNecessary() { ++void GonkRecorder::setDefaultProfileIfNecessary() { + LOGV("setDefaultProfileIfNecessary"); + + camcorder_quality quality = CAMCORDER_QUALITY_LOW; +@@ -1263,14 +1046,14 @@ + } + } + +-status_t StagefrightRecorder::checkAudioEncoderCapabilities() { ++status_t GonkRecorder::checkAudioEncoderCapabilities() { + clipAudioBitRate(); + clipAudioSampleRate(); + clipNumberOfAudioChannels(); + return OK; + } + +-void StagefrightRecorder::clipAudioBitRate() { ++void GonkRecorder::clipAudioBitRate() { + LOGV("clipAudioBitRate: encoder %d", mAudioEncoder); + + int minAudioBitRate = +@@ -1292,7 +1075,7 @@ + } + } + +-void StagefrightRecorder::clipAudioSampleRate() { ++void GonkRecorder::clipAudioSampleRate() { + LOGV("clipAudioSampleRate: encoder %d", mAudioEncoder); + + int minSampleRate = +@@ -1314,7 +1097,7 @@ + } + } + +-void StagefrightRecorder::clipNumberOfAudioChannels() { ++void GonkRecorder::clipNumberOfAudioChannels() { + LOGV("clipNumberOfAudioChannels: encoder %d", mAudioEncoder); + + int minChannels = +@@ -1336,7 +1119,7 @@ + } + } + +-void StagefrightRecorder::clipVideoFrameHeight() { ++void GonkRecorder::clipVideoFrameHeight() { + LOGV("clipVideoFrameHeight: encoder %d", mVideoEncoder); + int minFrameHeight = mEncoderProfiles->getVideoEncoderParamByName( + "enc.vid.height.min", mVideoEncoder); +@@ -1354,61 +1137,26 @@ + } + + // Set up the appropriate MediaSource depending on the chosen option +-status_t StagefrightRecorder::setupMediaSource( ++status_t GonkRecorder::setupMediaSource( + sp *mediaSource) { + if (mVideoSource == VIDEO_SOURCE_DEFAULT + || mVideoSource == VIDEO_SOURCE_CAMERA) { +- sp cameraSource; ++ sp cameraSource; + status_t err = setupCameraSource(&cameraSource); + if (err != OK) { + return err; + } + *mediaSource = cameraSource; + } else if (mVideoSource == VIDEO_SOURCE_GRALLOC_BUFFER) { +- // If using GRAlloc buffers, setup surfacemediasource. +- // Later a handle to that will be passed +- // to the client side when queried +- status_t err = setupSurfaceMediaSource(); +- if (err != OK) { +- return err; +- } +- *mediaSource = mSurfaceMediaSource; ++ return BAD_VALUE; + } else { + return INVALID_OPERATION; + } + return OK; + } + +-// setupSurfaceMediaSource creates a source with the given +-// width and height and framerate. +-// TODO: This could go in a static function inside SurfaceMediaSource +-// similar to that in CameraSource +-status_t StagefrightRecorder::setupSurfaceMediaSource() { +- status_t err = OK; +- mSurfaceMediaSource = new SurfaceMediaSource(mVideoWidth, mVideoHeight); +- if (mSurfaceMediaSource == NULL) { +- return NO_INIT; +- } +- +- if (mFrameRate == -1) { +- int32_t frameRate = 0; +- CHECK (mSurfaceMediaSource->getFormat()->findInt32( +- kKeyFrameRate, &frameRate)); +- LOGI("Frame rate is not explicitly set. Use the current frame " +- "rate (%d fps)", frameRate); +- mFrameRate = frameRate; +- } else { +- err = mSurfaceMediaSource->setFrameRate(mFrameRate); +- } +- CHECK(mFrameRate != -1); +- +- mIsMetaDataStoredInVideoBuffers = +- mSurfaceMediaSource->isMetaDataStoredInVideoBuffers(); +- return err; +-} +- +-status_t StagefrightRecorder::setupCameraSource( +- sp *cameraSource) { ++status_t GonkRecorder::setupCameraSource( ++ sp *cameraSource) { + status_t err = OK; + if ((err = checkVideoEncoderCapabilities()) != OK) { + return err; +@@ -1416,26 +1164,15 @@ + Size videoSize; + videoSize.width = mVideoWidth; + videoSize.height = mVideoHeight; +- if (mCaptureTimeLapse) { +- mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera( +- mCamera, mCameraProxy, mCameraId, +- videoSize, mFrameRate, mPreviewSurface, +- mTimeBetweenTimeLapseFrameCaptureUs); +- *cameraSource = mCameraSourceTimeLapse; +- } else { +- +- bool useMeta = true; +- char value[PROPERTY_VALUE_MAX]; +- if (property_get("debug.camcorder.disablemeta", value, NULL) && ++ bool useMeta = true; ++ char value[PROPERTY_VALUE_MAX]; ++ if (property_get("debug.camcorder.disablemeta", value, NULL) && + atoi(value)) { +- useMeta = false; +- } +- *cameraSource = CameraSource::CreateFromCamera( +- mCamera, mCameraProxy, mCameraId, videoSize, mFrameRate, +- mPreviewSurface, useMeta); ++ useMeta = false; + } +- mCamera.clear(); +- mCameraProxy.clear(); ++ ++ *cameraSource = GonkCameraSource::Create( ++ mCameraHandle, videoSize, mFrameRate, useMeta); + if (*cameraSource == NULL) { + return UNKNOWN_ERROR; + } +@@ -1465,7 +1202,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setupVideoEncoder( ++status_t GonkRecorder::setupVideoEncoder( + sp cameraSource, + int32_t videoBitRate, + sp *source) { +@@ -1501,10 +1238,7 @@ + CHECK(meta->findInt32(kKeyStride, &stride)); + CHECK(meta->findInt32(kKeySliceHeight, &sliceHeight)); + CHECK(meta->findInt32(kKeyColorFormat, &colorFormat)); +- hfr = 0; +- if (!meta->findInt32(kKeyHFR, &hfr)) { +- LOGW("hfr not found, default to 0"); +- } ++ CHECK(meta->findInt32(kKeyHFR, &hfr)); + + if(hfr) { + mMaxFileDurationUs = mMaxFileDurationUs * (hfr/mFrameRate); +@@ -1598,30 +1332,17 @@ + enc_meta->setInt32(kKey3D, is3D); + } + +- OMXClient client; +- CHECK_EQ(client.connect(), OK); +- + uint32_t encoder_flags = 0; + if (mIsMetaDataStoredInVideoBuffers) { + LOGW("Camera source supports metadata mode, create OMXCodec for metadata"); + encoder_flags |= OMXCodec::kHardwareCodecsOnly; + encoder_flags |= OMXCodec::kStoreMetaDataInVideoBuffers; +- if (property_get("ro.board.platform", value, "0") +- && (!strncmp(value, "msm7627", sizeof("msm7627") - 1))) { +- LOGW("msm7627 family of chipsets supports, only one buffer at a time"); +- encoder_flags |= OMXCodec::kOnlySubmitOneInputBufferAtOneTime; +- } +- } +- +- // Do not wait for all the input buffers to become available. +- // This give timelapse video recording faster response in +- // receiving output from video encoder component. +- if (mCaptureTimeLapse) { + encoder_flags |= OMXCodec::kOnlySubmitOneInputBufferAtOneTime; + } + + sp encoder = OMXCodec::Create( +- client.interface(), enc_meta, ++ GetOMX(), ++ enc_meta, + true /* createEncoder */, cameraSource, + NULL, encoder_flags); + if (encoder == NULL) { +@@ -1638,7 +1359,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setupAudioEncoder(const sp& writer) { ++status_t GonkRecorder::setupAudioEncoder(const sp& writer) { + status_t status = BAD_VALUE; + if (OK != (status = checkAudioEncoderCapabilities())) { + return status; +@@ -1664,7 +1385,7 @@ + return OK; + } + +-status_t StagefrightRecorder::setupMPEG4Recording( ++status_t GonkRecorder::setupMPEG4Recording( + int outputFd, + int32_t videoWidth, int32_t videoHeight, + int32_t videoBitRate, +@@ -1696,7 +1417,7 @@ + // Audio source is added at the end if it exists. + // This help make sure that the "recoding" sound is suppressed for + // camcorder applications in the recorded files. +- if (!mCaptureTimeLapse && (mAudioSource != AUDIO_SOURCE_CNT)) { ++ if (mAudioSource != AUDIO_SOURCE_CNT) { + err = setupAudioEncoder(writer); + if (err != OK) return err; + *totalBitRate += mAudioBitRate; +@@ -1728,7 +1449,7 @@ + return OK; + } + +-void StagefrightRecorder::setupMPEG4MetaData(int64_t startTimeUs, int32_t totalBitRate, ++void GonkRecorder::setupMPEG4MetaData(int64_t startTimeUs, int32_t totalBitRate, + sp *meta) { + (*meta)->setInt64(kKeyTime, startTimeUs); + (*meta)->setInt32(kKeyFileType, mOutputFormat); +@@ -1752,7 +1473,7 @@ + } + } + +-status_t StagefrightRecorder::startMPEG4Recording() { ++status_t GonkRecorder::startMPEG4Recording() { + int32_t totalBitRate; + status_t err = setupMPEG4Recording( + mOutputFd, mVideoWidth, mVideoHeight, +@@ -1761,7 +1482,14 @@ + return err; + } + +- int64_t startTimeUs = systemTime() / 1000; ++ //systemTime() doesn't give correct time because ++ //HAVE_POSIX_CLOCKS is not defined for utils/Timers.cpp ++ //so, using clock_gettime directly ++#include ++ struct timespec t; ++ clock_gettime(CLOCK_MONOTONIC, &t); ++ int64_t startTimeUs = int64_t(t.tv_sec)*1000000000LL + t.tv_nsec; ++ startTimeUs = startTimeUs / 1000; + sp meta = new MetaData; + setupMPEG4MetaData(startTimeUs, totalBitRate, &meta); + +@@ -1773,7 +1501,7 @@ + return OK; + } + +-status_t StagefrightRecorder::pause() { ++status_t GonkRecorder::pause() { + LOGV("pause"); + if (mWriter == NULL) { + return UNKNOWN_ERROR; +@@ -1782,31 +1510,16 @@ + + if (mStarted) { + mStarted = false; +- +- uint32_t params = 0; +- if (mAudioSource != AUDIO_SOURCE_CNT) { +- params |= IMediaPlayerService::kBatteryDataTrackAudio; +- } +- if (mVideoSource != VIDEO_SOURCE_LIST_END) { +- params |= IMediaPlayerService::kBatteryDataTrackVideo; +- } +- +- addBatteryData(params); + } + + + return OK; + } + +-status_t StagefrightRecorder::stop() { ++status_t GonkRecorder::stop() { + LOGV("stop"); + status_t err = OK; + +- if (mCaptureTimeLapse && mCameraSourceTimeLapse != NULL) { +- mCameraSourceTimeLapse->startQuickReadReturns(); +- mCameraSourceTimeLapse = NULL; +- } +- + if (mWriter != NULL) { + err = mWriter->stop(); + mWriter.clear(); +@@ -1819,30 +1532,20 @@ + + if (mStarted) { + mStarted = false; +- +- uint32_t params = 0; +- if (mAudioSource != AUDIO_SOURCE_CNT) { +- params |= IMediaPlayerService::kBatteryDataTrackAudio; +- } +- if (mVideoSource != VIDEO_SOURCE_LIST_END) { +- params |= IMediaPlayerService::kBatteryDataTrackVideo; +- } +- +- addBatteryData(params); + } + + + return err; + } + +-status_t StagefrightRecorder::close() { ++status_t GonkRecorder::close() { + LOGV("close"); + stop(); + + return OK; + } + +-status_t StagefrightRecorder::reset() { ++status_t GonkRecorder::reset() { + LOGV("reset"); + stop(); + +@@ -1858,9 +1561,9 @@ + mVideoHeight = 144; + mFrameRate = -1; + mVideoBitRate = 192000; +- mSampleRate = 0; +- mAudioChannels = 0; +- mAudioBitRate = 0; ++ mSampleRate = 8000; ++ mAudioChannels = 1; ++ mAudioBitRate = 12200; + mInterleaveDurationUs = 0; + mIFramesIntervalSec = 2; + mAudioSourceNode = 0; +@@ -1875,9 +1578,6 @@ + mMaxFileDurationUs = 0; + mMaxFileSizeBytes = 0; + mTrackEveryTimeDurationUs = 0; +- mCaptureTimeLapse = false; +- mTimeBetweenTimeLapseFrameCaptureUs = -1; +- mCameraSourceTimeLapse = NULL; + mIsMetaDataStoredInVideoBuffers = false; + mEncoderProfiles = MediaProfiles::getInstance(); + mRotationDegrees = 0; +@@ -1885,6 +1585,11 @@ + mLongitudex10000 = -3600000; + + mOutputFd = -1; ++ mCameraHandle = -1; ++ //TODO: May need to register a listener eventually ++ //if someone is interested in recorder events for now ++ //default to no listener registered ++ mListener = NULL; + + // Disable Audio Encoding + char value[PROPERTY_VALUE_MAX]; +@@ -1894,7 +1599,7 @@ + return OK; + } + +-status_t StagefrightRecorder::getMaxAmplitude(int *max) { ++status_t GonkRecorder::getMaxAmplitude(int *max) { + LOGV("getMaxAmplitude"); + + if (max == NULL) { +@@ -1911,7 +1616,7 @@ + return OK; + } + +-status_t StagefrightRecorder::dump( ++status_t GonkRecorder::dump( + int fd, const Vector& args) const { + LOGV("dump"); + const size_t SIZE = 256; +@@ -1958,6 +1663,8 @@ + result.append(buffer); + snprintf(buffer, SIZE, " Camera Id: %d\n", mCameraId); + result.append(buffer); ++ snprintf(buffer, SIZE, " Camera Handle: %d\n", mCameraHandle); ++ result.append(buffer); + snprintf(buffer, SIZE, " Start time offset (ms): %d\n", mStartTimeOffsetMs); + result.append(buffer); + snprintf(buffer, SIZE, " Encoder: %d\n", mVideoEncoder); +@@ -1978,45 +1685,12 @@ + return OK; + } + +-status_t StagefrightRecorder::startExtendedRecording() { +- CHECK(mOutputFormat == OUTPUT_FORMAT_QCP); +- +- if (mSampleRate != 8000) { +- LOGE("Invalid sampling rate %d used for recording", +- mSampleRate); +- return BAD_VALUE; +- } +- if (mAudioChannels != 1) { +- LOGE("Invalid number of audio channels %d used for recording", +- mAudioChannels); +- return BAD_VALUE; +- } +- +- if (mAudioSource >= AUDIO_SOURCE_CNT) { +- LOGE("Invalid audio source: %d", mAudioSource); +- return BAD_VALUE; +- } +- +- sp audioEncoder = createAudioSource(); +- +- if (audioEncoder == NULL) { +- LOGE("AudioEncoder NULL"); +- return UNKNOWN_ERROR; +- } +- +- mWriter = new ExtendedWriter(dup(mOutputFd)); +- mWriter->addSource(audioEncoder); +- +- if (mMaxFileDurationUs != 0) { +- mWriter->setMaxFileDuration(mMaxFileDurationUs); +- } +- if (mMaxFileSizeBytes != 0) { +- mWriter->setMaxFileSize(mMaxFileSizeBytes); +- } +- mWriter->setListener(mListener); +- mWriter->start(); +- +- return OK; ++status_t GonkRecorder::setCameraHandle(int32_t handle) { ++ if (handle < 0) { ++ return BAD_VALUE; ++ } ++ mCameraHandle = handle; ++ return OK; + } + + } // namespace android +diff --git a/GonkRecorder.h b/GonkRecorder.h +index dba6110..fa948af 100644 +--- a/GonkRecorder.h ++++ b/GonkRecorder.h +@@ -14,11 +14,11 @@ + * limitations under the License. + */ + +-#ifndef STAGEFRIGHT_RECORDER_H_ ++#ifndef GONK_RECORDER_H_ + +-#define STAGEFRIGHT_RECORDER_H_ ++#define GONK_RECORDER_H_ + +-#include ++#include + #include + #include + +@@ -26,21 +26,16 @@ + + namespace android { + +-class Camera; +-class ICameraRecordingProxy; +-class CameraSource; +-class CameraSourceTimeLapse; ++class GonkCameraSource; + struct MediaSource; + struct MediaWriter; + class MetaData; + struct AudioSource; + class MediaProfiles; +-class ISurfaceTexture; +-class SurfaceMediaSource; + +-struct StagefrightRecorder : public MediaRecorderBase { +- StagefrightRecorder(); +- virtual ~StagefrightRecorder(); ++struct GonkRecorder { ++ GonkRecorder(); ++ virtual ~GonkRecorder(); + + virtual status_t init(); + virtual status_t setAudioSource(audio_source_t as); +@@ -50,11 +45,10 @@ + virtual status_t setVideoEncoder(video_encoder ve); + virtual status_t setVideoSize(int width, int height); + virtual status_t setVideoFrameRate(int frames_per_second); +- virtual status_t setCamera(const sp& camera, const sp& proxy); +- virtual status_t setPreviewSurface(const sp& surface); + virtual status_t setOutputFile(const char *path); + virtual status_t setOutputFile(int fd, int64_t offset, int64_t length); + virtual status_t setParameters(const String8& params); ++ virtual status_t setCameraHandle(int32_t handle); + virtual status_t setListener(const sp& listener); + virtual status_t prepare(); + virtual status_t start(); +@@ -65,12 +59,8 @@ + virtual status_t getMaxAmplitude(int *max); + virtual status_t dump(int fd, const Vector& args) const; + // Querying a SurfaceMediaSourcer +- virtual sp querySurfaceMediaSource() const; + + private: +- sp mCamera; +- sp mCameraProxy; +- sp mPreviewSurface; + sp mListener; + sp mWriter; + int mOutputFd; +@@ -104,11 +94,6 @@ + int32_t mLongitudex10000; + int32_t mStartTimeOffsetMs; + +- bool mCaptureTimeLapse; +- int64_t mTimeBetweenTimeLapseFrameCaptureUs; +- sp mCameraSourceTimeLapse; +- +- + String8 mParams; + + bool mIsMetaDataStoredInVideoBuffers; +@@ -119,8 +104,8 @@ + // An pointer + // will be sent to the client side using which the + // frame buffers will be queued and dequeued +- sp mSurfaceMediaSource; + bool mDisableAudio; ++ int32_t mCameraHandle; + + status_t setupMPEG4Recording( + int outputFd, +@@ -132,10 +117,7 @@ + sp *meta); + status_t startMPEG4Recording(); + status_t startAMRRecording(); +- status_t startFMA2DPWriter(); +- status_t startAACRecording(); + status_t startRawAudioRecording(); +- status_t startRTPRecording(); + status_t startMPEG2TSRecording(); + sp createAudioSource(); + status_t checkVideoEncoderCapabilities(); +@@ -144,9 +126,8 @@ + // source (CameraSource or SurfaceMediaSource) + // depending on the videosource type + status_t setupMediaSource(sp *mediaSource); +- status_t setupCameraSource(sp *cameraSource); ++ status_t setupCameraSource(sp *cameraSource); + // setup the surfacemediasource for the encoder +- status_t setupSurfaceMediaSource(); + + status_t setupAudioEncoder(const sp& writer); + status_t setupVideoEncoder( +@@ -160,8 +141,6 @@ + status_t setParamAudioNumberOfChannels(int32_t channles); + status_t setParamAudioSamplingRate(int32_t sampleRate); + status_t setParamAudioTimeScale(int32_t timeScale); +- status_t setParamTimeLapseEnable(int32_t timeLapseEnable); +- status_t setParamTimeBetweenTimeLapseFrameCapture(int64_t timeUs); + status_t setParamVideoEncodingBitRate(int32_t bitRate); + status_t setParamVideoIFramesInterval(int32_t seconds); + status_t setParamVideoEncoderProfile(int32_t profile); +@@ -186,14 +165,10 @@ + void clipNumberOfAudioChannels(); + void setDefaultProfileIfNecessary(); + +- +- StagefrightRecorder(const StagefrightRecorder &); +- StagefrightRecorder &operator=(const StagefrightRecorder &); +- +- /* extension */ +- status_t startExtendedRecording(); ++ GonkRecorder(const GonkRecorder &); ++ GonkRecorder &operator=(const GonkRecorder &); + }; + + } // namespace android + +-#endif // STAGEFRIGHT_RECORDER_H_ ++#endif // GONK_RECORDER_H_ diff --git a/dom/camera/update.sh b/dom/camera/update.sh new file mode 100644 index 00000000000..9b5a968cea2 --- /dev/null +++ b/dom/camera/update.sh @@ -0,0 +1,14 @@ +# Usage: ./update.sh +# +# Copies the needed files from the directory containing the original +# Android ICS OS source and applies the B2G specific changes for the +# camcorder functionality in B2G. +cp $1/frameworks/base/media/libmediaplayerservice/StagefrightRecorder.cpp ./GonkRecorder.cpp +cp $1/frameworks/base/media/libmediaplayerservice/StagefrightRecorder.h ./GonkRecorder.h +cp $1/frameworks/base/media/libstagefright/CameraSource.cpp ./GonkCameraSource.cpp +cp $1/frameworks/base/include/media/stagefright/CameraSource.h ./GonkCameraSource.h +cp $1/frameworks/base/media/libmedia/AudioParameter.cpp ./AudioParameter.cpp +cp $1/frameworks/base/include/camera/Camera.h ./GonkCameraListener.h +patch -p1 setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_RAW); + mMeta->setInt32(kKeyColorFormat, mColorFormat); + mMeta->setInt32(kKeyWidth, mVideoSize.width); + mMeta->setInt32(kKeyHeight, mVideoSize.height); + mMeta->setInt32(kKeyStride, mVideoSize.width); + mMeta->setInt32(kKeySliceHeight, mVideoSize.height); + mMeta->setInt32(kKeyFrameRate, mVideoFrameRate); +- mMeta->setInt32(kKeyHFR, hfr); + +- if (want3D) { +- mMeta->setInt32(kKey3D, !0); +- } + return OK; + } + + GonkCameraSource::~GonkCameraSource() { + if (mStarted) { + stop(); + } else if (mInitCheck == OK) { + // Camera is initialized but because start() is never called, +diff --git a/dom/camera/GonkRecorder.cpp b/dom/camera/GonkRecorder.cpp +--- a/dom/camera/GonkRecorder.cpp ++++ b/dom/camera/GonkRecorder.cpp +@@ -716,56 +716,16 @@ status_t GonkRecorder::start() { + mStarted = true; + } + + return status; + } + + sp GonkRecorder::createAudioSource() { + +- bool tunneledSource = false; +- const char *tunnelMime; +- { +- AudioParameter param; +- String8 key("tunneled-input-formats"); +- param.add( key, String8("get") ); +- String8 valueStr = AudioSystem::getParameters( 0, param.toString()); +- AudioParameter result(valueStr); +- int value; +- if ( mAudioEncoder == AUDIO_ENCODER_AMR_NB && +- result.getInt(String8("AMR"),value) == NO_ERROR ) { +- tunneledSource = true; +- tunnelMime = MEDIA_MIMETYPE_AUDIO_AMR_NB; +- } +- else if ( mAudioEncoder == AUDIO_ENCODER_QCELP && +- result.getInt(String8("QCELP"),value) == NO_ERROR ) { +- tunneledSource = true; +- tunnelMime = MEDIA_MIMETYPE_AUDIO_QCELP; +- } +- else if ( mAudioEncoder == AUDIO_ENCODER_EVRC && +- result.getInt(String8("EVRC"),value) == NO_ERROR ) { +- tunneledSource = true; +- tunnelMime = MEDIA_MIMETYPE_AUDIO_EVRC; +- } +- } +- +- if ( tunneledSource ) { +- sp audioSource = NULL; +- sp meta = new MetaData; +- meta->setInt32(kKeyChannelCount, mAudioChannels); +- meta->setInt32(kKeySampleRate, mSampleRate); +- meta->setInt32(kKeyBitRate, mAudioBitRate); +- if (mAudioTimeScale > 0) { +- meta->setInt32(kKeyTimeScale, mAudioTimeScale); +- } +- meta->setCString( kKeyMIMEType, tunnelMime ); +- audioSource = new AudioSource( mAudioSource, meta); +- return audioSource->initCheck( ) == OK ? audioSource : NULL; +- } +- + sp audioSource = + new AudioSource( + mAudioSource, + mSampleRate, + mAudioChannels); + + status_t err = audioSource->initCheck(); + +@@ -1226,56 +1186,33 @@ status_t GonkRecorder::setupVideoEncoder + + default: + CHECK(!"Should not be here, unsupported video encoding."); + break; + } + + sp meta = cameraSource->getFormat(); + +- int32_t width, height, stride, sliceHeight, colorFormat, hfr, is3D; ++ int32_t width, height, stride, sliceHeight, colorFormat; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + CHECK(meta->findInt32(kKeyStride, &stride)); + CHECK(meta->findInt32(kKeySliceHeight, &sliceHeight)); + CHECK(meta->findInt32(kKeyColorFormat, &colorFormat)); +- CHECK(meta->findInt32(kKeyHFR, &hfr)); +- +- if(hfr) { +- mMaxFileDurationUs = mMaxFileDurationUs * (hfr/mFrameRate); +- } +- + + enc_meta->setInt32(kKeyWidth, width); + enc_meta->setInt32(kKeyHeight, height); + enc_meta->setInt32(kKeyIFramesInterval, mIFramesIntervalSec); + enc_meta->setInt32(kKeyStride, stride); + enc_meta->setInt32(kKeySliceHeight, sliceHeight); + enc_meta->setInt32(kKeyColorFormat, colorFormat); +- enc_meta->setInt32(kKeyHFR, hfr); + if (mVideoTimeScale > 0) { + enc_meta->setInt32(kKeyTimeScale, mVideoTimeScale); + } + +- char mDeviceName[100]; +- property_get("ro.board.platform",mDeviceName,"0"); +- if(!strncmp(mDeviceName, "msm7627a", 8)) { +- if(hfr && (width * height > 432*240)) { +- LOGE("HFR mode is supported only upto WQVGA resolution"); +- return INVALID_OPERATION; +- } +- } +- else { +- if(hfr && ((mVideoEncoder != VIDEO_ENCODER_H264) || (width * height > 800*480))) { +- LOGE("HFR mode is supported only upto WVGA and H264 codec."); +- return INVALID_OPERATION; +- } +- } +- +- + /* + * can set profile from the app as a parameter. + * For the mean time, set from shell + */ + + char value[PROPERTY_VALUE_MAX]; + bool customProfile = false; + +@@ -1322,19 +1259,16 @@ status_t GonkRecorder::setupVideoEncoder + } + + if (mVideoEncoderProfile != -1) { + enc_meta->setInt32(kKeyVideoProfile, mVideoEncoderProfile); + } + if (mVideoEncoderLevel != -1) { + enc_meta->setInt32(kKeyVideoLevel, mVideoEncoderLevel); + } +- if (meta->findInt32(kKey3D, &is3D)) { +- enc_meta->setInt32(kKey3D, is3D); +- } + + uint32_t encoder_flags = 0; + if (mIsMetaDataStoredInVideoBuffers) { + LOGW("Camera source supports metadata mode, create OMXCodec for metadata"); + encoder_flags |= OMXCodec::kHardwareCodecsOnly; + encoder_flags |= OMXCodec::kStoreMetaDataInVideoBuffers; + encoder_flags |= OMXCodec::kOnlySubmitOneInputBufferAtOneTime; + } diff --git a/dom/devicestorage/DeviceStorageRequestChild.cpp b/dom/devicestorage/DeviceStorageRequestChild.cpp index 09241060357..d4b7854bfd6 100644 --- a/dom/devicestorage/DeviceStorageRequestChild.cpp +++ b/dom/devicestorage/DeviceStorageRequestChild.cpp @@ -55,7 +55,8 @@ DeviceStorageRequestChild::Recv__delete__(const DeviceStorageResponseValue& aVal BlobChild* actor = static_cast(r.blobChild()); nsCOMPtr blob = actor->GetBlob(); - jsval result = InterfaceToJsval(mRequest->GetOwner(), blob, &NS_GET_IID(nsIDOMBlob)); + nsCOMPtr file = do_QueryInterface(blob); + jsval result = InterfaceToJsval(mRequest->GetOwner(), file, &NS_GET_IID(nsIDOMFile)); mRequest->FireSuccess(result); break; } diff --git a/dom/devicestorage/nsDeviceStorage.cpp b/dom/devicestorage/nsDeviceStorage.cpp index d62af32a40a..d1f6aeb4c0c 100644 --- a/dom/devicestorage/nsDeviceStorage.cpp +++ b/dom/devicestorage/nsDeviceStorage.cpp @@ -1913,6 +1913,17 @@ nsDOMDeviceStorage::Stat(nsIDOMDOMRequest** aRetval) return NS_OK; } +NS_IMETHODIMP +nsDOMDeviceStorage::GetRootDirectory(nsIFile** aRootDirectory) +{ + if (!mRootDirectory) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr file; + return mRootDirectory->Clone(aRootDirectory); +} + NS_IMETHODIMP nsDOMDeviceStorage::Enumerate(const JS::Value & aName, const JS::Value & aOptions, diff --git a/dom/icc/interfaces/SimToolKit.idl b/dom/icc/interfaces/SimToolKit.idl index 12e84110216..dc826207078 100644 --- a/dom/icc/interfaces/SimToolKit.idl +++ b/dom/icc/interfaces/SimToolKit.idl @@ -255,6 +255,94 @@ dictionary MozStkSetUpEventList jsval eventList; // unsigned short [] }; +dictionary MozStkLocationInfo +{ + /** + * Mobile Country Code (MCC) of the current serving operator. + */ + unsigned short mcc; + + /** + * Mobile Network Code (MNC) of the current serving operator. + */ + unsigned short mnc; + + /** + * Mobile Location Area Code (LAC) for the current serving operator. + */ + unsigned short gsmLocationAreaCode; + + /** + * Mobile Cell ID for the current serving operator. + */ + unsigned long gsmCellId; +}; + +dictionary MozStkDuration +{ + /** + * Time unit used, should be one of STK_TIME_UNIT_*. + */ + unsigned short timeUnit; + + /** + * The length of time required, expressed in timeUnit. + */ + octet timeInterval; +}; + +dictionary MozStkPlayTone +{ + /** + * Text String. + */ + DOMString text; + + /** + * One of STK_TONE_TYPE_*. + */ + unsigned short tone; + + /** + * The length of time for which the ME shall generate the tone. + * + * @see MozStkDuration + */ + jsval duration; + + /** + * Need to vibrate or not. + * true: vibrate alert, if available, with the tone. + * false: use of vibrate alert is up to the ME. + */ + boolean isVibrate; +}; + +dictionary MozStkLocationEvent +{ + /** + * The type of this event. + * It shall be nsIDOMMozIccManager.STK_EVENT_TYPE_LOCATION_STATUS; + */ + unsigned short eventType; + + /** + * Indicate current service state of the MS with one of the values listed + * below: + * - nsIDOMMozIccManager.STK_SERVICE_STATE_NORMAL + * - nsIDOMMozIccManager.STK_SERVICE_STATE_LIMITED + * - nsIDOMMozIccManager.STK_SERVICE_STATE_UNAVAILABLE + */ + unsigned short locationStatus; + + /** + * See MozStkLocationInfo. + * This value shall only be provided if the locationStatus indicates + * 'STK_SERVICE_STATE_NORMAL'. + */ + jsval locationInfo; +}; + dictionary MozStkCommand { /** @@ -306,6 +394,22 @@ dictionary MozStkCommand * When typeOfCommand is * - STK_CMD_SET_UP_EVENT_LIST * options is MozStkSetUpEventList. + * + * When typeOfCommand is + * - STK_CMD_PLAY_TONE + * options is MozStkPlayTone. + * + * When typeOfCommand is + * - STK_CMD_POLL_INTERVAL + * options is MozStkDuration. + * + * When typeOfCommand is + * - STK_CMD_POLL_OFF + * options is null. + * + * When typeOfCommand is + * - STK_CMD_REFRESH + * options is null. */ jsval options; }; diff --git a/dom/icc/interfaces/nsIDOMIccManager.idl b/dom/icc/interfaces/nsIDOMIccManager.idl index b2a653eee65..f6333830b2f 100644 --- a/dom/icc/interfaces/nsIDOMIccManager.idl +++ b/dom/icc/interfaces/nsIDOMIccManager.idl @@ -7,7 +7,7 @@ interface nsIDOMEventListener; -[scriptable, builtinclass, uuid(2eace3f9-6aa4-491b-820e-7d69ce7b3f02)] +[scriptable, builtinclass, uuid(9d898c66-3485-4cd5-ab8d-92ef2988887b)] interface nsIDOMMozIccManager : nsIDOMEventTarget { /** @@ -30,6 +30,8 @@ interface nsIDOMMozIccManager : nsIDOMEventTarget * @see TS 11.14, clause 13.4 */ const unsigned short STK_CMD_REFRESH = 0x01; + const unsigned short STK_CMD_POLL_INTERVAL = 0x03; + const unsigned short STK_CMD_POLL_OFF = 0x04; const unsigned short STK_CMD_SET_UP_EVENT_LIST = 0x05; const unsigned short STK_CMD_SET_UP_CALL = 0x10; const unsigned short STK_CMD_SEND_SS = 0x11; @@ -37,6 +39,7 @@ interface nsIDOMMozIccManager : nsIDOMEventTarget const unsigned short STK_CMD_SEND_SMS = 0x13; const unsigned short STK_CMD_SEND_DTMF = 0x14; const unsigned short STK_CMD_LAUNCH_BROWSER = 0x15; + const unsigned short STK_CMD_PLAY_TONE = 0x20; const unsigned short STK_CMD_DISPLAY_TEXT = 0x21; const unsigned short STK_CMD_GET_INKEY = 0x22; const unsigned short STK_CMD_GET_INPUT = 0x23; @@ -162,6 +165,35 @@ interface nsIDOMMozIccManager : nsIDOMEventTarget const unsigned short STK_EVENT_TYPE_BROWSING_STATUS = 0x0f; const unsigned short STK_EVENT_TYPE_FRAMES_INFORMATION_CHANGED = 0x10; + /** + * The service state of STK Location Status. + */ + const unsigned short STK_SERVICE_STATE_NORMAL = 0x00; + const unsigned short STK_SERVICE_STATE_LIMITED = 0x01; + const unsigned short STK_SERVICE_STATE_UNAVAILABLE = 0x02; + + /** + * Tone type. + */ + const unsigned short STK_TONE_TYPE_DIAL_TONE = 0x01; + const unsigned short STK_TONE_TYPE_CALLED_SUBSCRIBER_BUSY = 0x02; + const unsigned short STK_TONE_TYPE_CONGESTION = 0x03; + const unsigned short STK_TONE_TYPE_RADIO_PATH_ACK = 0x04; + const unsigned short STK_TONE_TYPE_RADIO_PATH_NOT_AVAILABLE = 0x05; + const unsigned short STK_TONE_TYPE_ERROR = 0x06; + const unsigned short STK_TONE_TYPE_CALL_WAITING_TONE = 0x07; + const unsigned short STK_TONE_TYPE_RINGING_TONE = 0x08; + const unsigned short STK_TONE_TYPE_GENERAL_BEEP = 0x10; + const unsigned short STK_TONE_TYPE_POSITIVE_ACK_TONE = 0x11; + const unsigned short STK_TONE_TYPE_NEGATIVE_ACK_TONE = 0x12; + + /** + * Time unit + */ + const unsigned short STK_TIME_UNIT_MINUTE = 0x00; + const unsigned short STK_TIME_UNIT_SECOND = 0x01; + const unsigned short STK_TIME_UNIT_TENTH_SECOND = 0x02; + /** * Send the response back to ICC after an attempt to execute STK Proactive * Command. @@ -185,6 +217,16 @@ interface nsIDOMMozIccManager : nsIDOMEventTarget void sendStkMenuSelection(in unsigned short itemIdentifier, in boolean helpRequested); + /** + * Send "Event Download" Envelope command to ICC. + * ICC will not respond with any data for this command. + * + * @param event + * one of events below: + * - MozStkLocationEvent + */ + void sendStkEventDownload(in jsval event); + /** * The 'stkcommand' event is notified whenever STK Proactive Command is * issued from ICC. diff --git a/dom/icc/src/IccManager.cpp b/dom/icc/src/IccManager.cpp index 9cd3068ebb0..0ea9eb2dbc4 100644 --- a/dom/icc/src/IccManager.cpp +++ b/dom/icc/src/IccManager.cpp @@ -137,6 +137,17 @@ IccManager::SendStkMenuSelection(uint16_t aItemIdentifier, bool aHelpRequested) return NS_OK; } +NS_IMETHODIMP +IccManager::SendStkEventDownload(const JS::Value& aEvent) +{ + if (!mProvider) { + return NS_ERROR_FAILURE; + } + + mProvider->SendStkEventDownload(GetOwner(), aEvent); + return NS_OK; +} + nsresult IccManager::InternalDispatchEvent(const nsAString& aType) { diff --git a/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl b/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl index c3820d85230..aad7b8436d7 100644 --- a/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl +++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl @@ -9,13 +9,14 @@ interface nsIDOMDOMRequest; interface nsIDOMDeviceStorageCursor; interface nsIDOMDeviceStorageChangeEvent; interface nsIDOMEventListener; +interface nsIFile; dictionary DeviceStorageEnumerationParameters { jsval since; }; -[scriptable, uuid(7efbe025-3a8a-4151-9257-3e8c941dc099), builtinclass] +[scriptable, uuid(7f69936f-2948-4733-ba41-c7e1d657a88b), builtinclass] interface nsIDOMDeviceStorage : nsIDOMEventTarget { [implicit_jscontext] attribute jsval onchange; @@ -38,4 +39,6 @@ interface nsIDOMDeviceStorage : nsIDOMEventTarget nsIDOMDeviceStorageCursor enumerateEditable([optional] in jsval aName, /* DeviceStorageEnumerationParameters */ [optional] in jsval options); nsIDOMDOMRequest stat(); + + [noscript] readonly attribute nsIFile rootDirectory; }; diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index c76315d351e..0bd173bafb3 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -266,6 +266,12 @@ parent: */ ContentReceivedTouch(bool aPreventDefault); + /** + * Updates any zoom constraints on the parent and anything tied to it. This + * is useful for control logic that resides outside of the remote browser. + */ + UpdateZoomConstraints(bool aAllowZoom, float aMinZoom, float aMaxZoom); + __delete__(); child: @@ -282,9 +288,9 @@ child: LoadURL(nsCString uri); - UpdateDimensions(nsRect rect, nsIntSize size); + UpdateDimensions(nsRect rect, nsIntSize size) compress; - UpdateFrame(FrameMetrics frame); + UpdateFrame(FrameMetrics frame) compress; /** * Requests handling of a double tap. |point| is in CSS pixels, relative to diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index df0d4fa0ca0..527693617fb 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -18,6 +18,7 @@ #include "mozilla/dom/PContentChild.h" #include "mozilla/dom/PContentDialogChild.h" #include "mozilla/ipc/DocumentRendererChild.h" +#include "mozilla/layers/AsyncPanZoomController.h" #include "mozilla/layers/CompositorChild.h" #include "mozilla/layers/PLayersChild.h" #include "mozilla/layout/RenderFrameChild.h" @@ -29,10 +30,12 @@ #include "nsContentUtils.h" #include "nsEmbedCID.h" #include "nsEventListenerManager.h" +#include "nsGenericElement.h" #include "nsIAppsService.h" #include "nsIBaseWindow.h" #include "nsIComponentManager.h" #include "nsIDOMClassInfo.h" +#include "nsIDOMElement.h" #include "nsIDOMEvent.h" #include "nsIDOMWindow.h" #include "nsIDOMWindowUtils.h" @@ -50,6 +53,8 @@ #include "nsIServiceManager.h" #include "nsISupportsImpl.h" #include "nsIURI.h" +#include "nsIURIFixup.h" +#include "nsCDefaultURIFixup.h" #include "nsIView.h" #include "nsIWebBrowser.h" #include "nsIWebBrowserFocus.h" @@ -85,6 +90,12 @@ using namespace mozilla::widget; NS_IMPL_ISUPPORTS1(ContentListener, nsIDOMEventListener) +static const nsIntSize kDefaultViewportSize(980, 480); + +static const char CANCEL_DEFAULT_PAN_ZOOM[] = "cancel-default-pan-zoom"; +static const char BROWSER_ZOOM_TO_RECT[] = "browser-zoom-to-rect"; +static const char BEFORE_FIRST_PAINT[] = "before-first-paint"; + NS_IMETHODIMP ContentListener::HandleEvent(nsIDOMEvent* aEvent) { @@ -151,28 +162,45 @@ TabChild::TabChild(uint32_t aChromeFlags, bool aIsBrowserElement, , mTabChildGlobal(nullptr) , mChromeFlags(aChromeFlags) , mOuterRect(0, 0, 0, 0) + , mInnerSize(0, 0) + , mOldViewportWidth(0.0f) , mLastBackgroundColor(NS_RGB(255, 255, 255)) , mAppId(aAppId) , mDidFakeShow(false) , mIsBrowserElement(aIsBrowserElement) , mNotified(false) + , mContentDocumentIsDisplayed(false) , mTriedBrowserInit(false) { printf("creating %d!\n", NS_IsMainThread()); } -nsresult +NS_IMETHODIMP +TabChild::HandleEvent(nsIDOMEvent* aEvent) +{ + nsAutoString eventType; + aEvent->GetType(eventType); + if (eventType.EqualsLiteral("DOMMetaAdded")) { + // This meta data may or may not have been a meta viewport tag. If it was, + // we should handle it immediately. + HandlePossibleMetaViewportChange(); + } + + return NS_OK; +} + +NS_IMETHODIMP TabChild::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData) { - if (!strcmp(aTopic, "cancel-default-pan-zoom")) { + if (!strcmp(aTopic, CANCEL_DEFAULT_PAN_ZOOM)) { nsCOMPtr docShell(do_QueryInterface(aSubject)); nsCOMPtr tabChild(GetTabChildFrom(docShell)); if (tabChild == this) { mRemoteFrame->CancelDefaultPanZoom(); } - } else if (!strcmp(aTopic, "browser-zoom-to-rect")) { + } else if (!strcmp(aTopic, BROWSER_ZOOM_TO_RECT)) { nsCOMPtr docShell(do_QueryInterface(aSubject)); nsCOMPtr tabChild(GetTabChildFrom(docShell)); if (tabChild == this) { @@ -182,11 +210,264 @@ TabChild::Observe(nsISupports *aSubject, &rect.x, &rect.y, &rect.width, &rect.height); SendZoomToRect(rect); } + } else if (!strcmp(aTopic, BEFORE_FIRST_PAINT)) { + if (IsAsyncPanZoomEnabled()) { + nsCOMPtr subject(do_QueryInterface(aSubject)); + nsCOMPtr domDoc; + mWebNav->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr doc(do_QueryInterface(domDoc)); + + if (SameCOMIdentity(subject, doc)) { + nsCOMPtr utils(GetDOMWindowUtils()); + + mContentDocumentIsDisplayed = true; + + // Reset CSS viewport and zoom to default on new page, then calculate them + // properly using the actual metadata from the page. + SetCSSViewport(kDefaultViewportSize.width, kDefaultViewportSize.height); + + // Calculate a really simple resolution that we probably won't be + // keeping, as well as putting the scroll offset back to the top-left of + // the page. + float resolution = float(mInnerSize.width) / float(kDefaultViewportSize.width); + mLastMetrics.mZoom.width = mLastMetrics.mZoom.height = + mLastMetrics.mResolution.width = mLastMetrics.mResolution.height = + resolution; + mLastMetrics.mScrollOffset = gfx::Point(0, 0); + utils->SetResolution(resolution, resolution); + + HandlePossibleMetaViewportChange(); + } + } } return NS_OK; } +NS_IMETHODIMP +TabChild::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) +{ + NS_NOTREACHED("not implemented in TabChild"); + return NS_OK; +} + +NS_IMETHODIMP +TabChild::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + NS_NOTREACHED("not implemented in TabChild"); + return NS_OK; +} + +NS_IMETHODIMP +TabChild::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI *aLocation, + uint32_t aFlags) +{ + if (!IsAsyncPanZoomEnabled()) { + return NS_OK; + } + + nsCOMPtr window; + aWebProgress->GetDOMWindow(getter_AddRefs(window)); + if (!window) { + return NS_OK; + } + + nsCOMPtr progressDoc; + window->GetDocument(getter_AddRefs(progressDoc)); + if (!progressDoc) { + return NS_OK; + } + + nsCOMPtr domDoc; + mWebNav->GetDocument(getter_AddRefs(domDoc)); + if (!domDoc || !SameCOMIdentity(domDoc, progressDoc)) { + return NS_OK; + } + + nsCOMPtr urifixup(do_GetService(NS_URIFIXUP_CONTRACTID)); + if (!urifixup) { + return NS_OK; + } + + nsCOMPtr exposableURI; + urifixup->CreateExposableURI(aLocation, getter_AddRefs(exposableURI)); + if (!exposableURI) { + return NS_OK; + } + + if (!(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT)) { + mContentDocumentIsDisplayed = false; + } else if (mLastURI != nullptr) { + bool exposableEqualsLast, exposableEqualsNew; + exposableURI->Equals(mLastURI.get(), &exposableEqualsLast); + exposableURI->Equals(aLocation, &exposableEqualsNew); + if (exposableEqualsLast && !exposableEqualsNew) { + mContentDocumentIsDisplayed = false; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +TabChild::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const PRUnichar* aMessage) +{ + NS_NOTREACHED("not implemented in TabChild"); + return NS_OK; +} + +NS_IMETHODIMP +TabChild::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) +{ + NS_NOTREACHED("not implemented in TabChild"); + return NS_OK; +} + +void +TabChild::SetCSSViewport(float aWidth, float aHeight) +{ + mOldViewportWidth = aWidth; + + if (mContentDocumentIsDisplayed) { + nsCOMPtr utils(GetDOMWindowUtils()); + utils->SetCSSViewport(aWidth, aHeight); + } +} + +void +TabChild::HandlePossibleMetaViewportChange() +{ + if (!IsAsyncPanZoomEnabled()) { + return; + } + + nsCOMPtr domDoc; + mWebNav->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr document(do_QueryInterface(domDoc)); + + nsCOMPtr utils(GetDOMWindowUtils()); + + ViewportInfo viewportMetaData = + nsContentUtils::GetViewportInfo(document, mInnerSize.width, mInnerSize.height); + SendUpdateZoomConstraints(viewportMetaData.allowZoom, + viewportMetaData.minZoom, + viewportMetaData.maxZoom); + + float screenW = mInnerSize.width; + float screenH = mInnerSize.height; + float viewportW = viewportMetaData.width; + float viewportH = viewportMetaData.height; + + // We're not being displayed in any way; don't bother doing anything because + // that will just confuse future adjustments. + if (!screenW || !screenH) { + return; + } + + // Make sure the viewport height is not shorter than the window when the page + // is zoomed out to show its full width. Note that before we set the viewport + // width, the "full width" of the page isn't properly defined, so that's why + // we have to call SetCSSViewport twice - once to set the width, and the + // second time to figure out the height based on the layout at that width. + float oldBrowserWidth = mOldViewportWidth; + mLastMetrics.mViewport.width = viewportMetaData.width; + mLastMetrics.mViewport.height = viewportMetaData.height; + if (!oldBrowserWidth) { + oldBrowserWidth = kDefaultViewportSize.width; + } + SetCSSViewport(viewportW, viewportH); + + // If this page has not been painted yet, then this must be getting run + // because a meta-viewport element was added (via the DOMMetaAdded handler). + // in this case, we should not do anything that forces a reflow (see bug + // 759678) such as requesting the page size or sending a viewport update. this + // code will get run again in the before-first-paint handler and that point we + // will run though all of it. the reason we even bother executing up to this + // point on the DOMMetaAdded handler is so that scripts that use + // window.innerWidth before they are painted have a correct value (bug + // 771575). + if (!mContentDocumentIsDisplayed) { + return; + } + + float minScale = 1.0f; + + nsCOMPtr htmlDOMElement = do_QueryInterface(document->GetHtmlElement()); + nsCOMPtr bodyDOMElement = do_QueryInterface(document->GetBodyElement()); + + PRInt32 htmlWidth = 0, htmlHeight = 0; + if (htmlDOMElement) { + htmlDOMElement->GetScrollWidth(&htmlWidth); + htmlDOMElement->GetScrollHeight(&htmlHeight); + } + PRInt32 bodyWidth = 0, bodyHeight = 0; + if (bodyDOMElement) { + bodyDOMElement->GetScrollWidth(&bodyWidth); + bodyDOMElement->GetScrollHeight(&bodyHeight); + } + + float pageWidth = NS_MAX(htmlWidth, bodyWidth); + float pageHeight = NS_MAX(htmlHeight, bodyHeight); + + minScale = mInnerSize.width / pageWidth; + minScale = clamped((double)minScale, viewportMetaData.minZoom, viewportMetaData.maxZoom); + + viewportH = NS_MAX(viewportH, screenH / minScale); + SetCSSViewport(viewportW, viewportH); + + // This change to the zoom accounts for all types of changes I can conceive: + // 1. screen size changes, CSS viewport does not (pages with no meta viewport + // or a fixed size viewport) + // 2. screen size changes, CSS viewport also does (pages with a device-width + // viewport) + // 3. screen size remains constant, but CSS viewport changes (meta viewport + // tag is added or removed) + // 4. neither screen size nor CSS viewport changes + // + // In all of these cases, we maintain how much actual content is visible + // within the screen width. Note that "actual content" may be different with + // respect to CSS pixels because of the CSS viewport size changing. + int32_t oldScreenWidth = mLastMetrics.mCompositionBounds.width; + if (!oldScreenWidth) { + oldScreenWidth = mInnerSize.width; + } + float zoomScale = (screenW * oldBrowserWidth) / (oldScreenWidth * viewportW); + + float zoom = clamped(double(mLastMetrics.mZoom.width * zoomScale), + viewportMetaData.minZoom, viewportMetaData.maxZoom); + utils->SetResolution(zoom, zoom); + + FrameMetrics metrics(mLastMetrics); + metrics.mViewport = gfx::Rect(0.0f, 0.0f, viewportW, viewportH); + metrics.mScrollableRect = gfx::Rect(0.0f, 0.0f, pageWidth, pageHeight); + metrics.mCompositionBounds = nsIntRect(0, 0, mInnerSize.width, mInnerSize.height); + metrics.mZoom.width = metrics.mZoom.height = + metrics.mResolution.width = metrics.mResolution.height = zoom; + metrics.mDisplayPort = AsyncPanZoomController::CalculatePendingDisplayPort( + // The page must have been refreshed in some way such as a new document or + // new CSS viewport, so we know that there's no velocity, acceleration, and + // we have no idea how long painting will take. + metrics, gfx::Point(0.0f, 0.0f), gfx::Point(0.0f, 0.0f), 0.0); + // Force a repaint with these metrics. This, among other things, sets the + // displayport, so we start with async painting. + RecvUpdateFrame(metrics); +} + nsresult TabChild::Init() { @@ -238,6 +519,12 @@ TabChild::Init() "DNS prefetching enable step."); } + nsCOMPtr docShell = do_GetInterface(mWebNav); + MOZ_ASSERT(docShell); + nsCOMPtr webProgress = do_GetInterface(docShell); + NS_ENSURE_TRUE(webProgress, NS_ERROR_FAILURE); + webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_LOCATION); + return NS_OK; } @@ -266,8 +553,11 @@ NS_INTERFACE_MAP_BEGIN(TabChild) NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChromeFocus) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_ENTRY(nsIWindowProvider) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) NS_INTERFACE_MAP_ENTRY(nsITabChild) NS_INTERFACE_MAP_ENTRY(nsIDialogCreator) + NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsSupportsWeakReference) NS_INTERFACE_MAP_END @@ -799,6 +1089,7 @@ TabChild::RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size) mOuterRect.width = rect.width; mOuterRect.height = rect.height; + mInnerSize = size; mWidget->Resize(0, 0, size.width, size.height, true); @@ -806,6 +1097,8 @@ TabChild::RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size) baseWin->SetPositionAndSize(0, 0, size.width, size.height, true); + HandlePossibleMetaViewportChange(); + return true; } @@ -842,33 +1135,63 @@ TabChild::RecvUpdateFrame(const FrameMetrics& aFrameMetrics) return true; } + // The BrowserElementScrolling helper must know about these updated metrics + // for other functions it performs, such as double tap handling. nsCString data; - data += nsPrintfCString("{ \"x\" : %d", NS_lround(aFrameMetrics.mViewportScrollOffset.x)); - data += nsPrintfCString(", \"y\" : %d", NS_lround(aFrameMetrics.mViewportScrollOffset.y)); + data += nsPrintfCString("{ \"x\" : %d", NS_lround(aFrameMetrics.mScrollOffset.x)); + data += nsPrintfCString(", \"y\" : %d", NS_lround(aFrameMetrics.mScrollOffset.y)); // We don't treat the x and y scales any differently for this // semi-platform-specific code. - data += nsPrintfCString(", \"zoom\" : %f", aFrameMetrics.mResolution.width); + data += nsPrintfCString(", \"zoom\" : %f", aFrameMetrics.mZoom.width); data += nsPrintfCString(", \"displayPort\" : "); - data += nsPrintfCString("{ \"left\" : %d", aFrameMetrics.mDisplayPort.X()); - data += nsPrintfCString(", \"top\" : %d", aFrameMetrics.mDisplayPort.Y()); - data += nsPrintfCString(", \"width\" : %d", aFrameMetrics.mDisplayPort.Width()); - data += nsPrintfCString(", \"height\" : %d", aFrameMetrics.mDisplayPort.Height()); + data += nsPrintfCString("{ \"x\" : %f", aFrameMetrics.mDisplayPort.x); + data += nsPrintfCString(", \"y\" : %f", aFrameMetrics.mDisplayPort.y); + data += nsPrintfCString(", \"width\" : %f", aFrameMetrics.mDisplayPort.width); + data += nsPrintfCString(", \"height\" : %f", aFrameMetrics.mDisplayPort.height); data += nsPrintfCString(", \"resolution\" : %f", aFrameMetrics.mResolution.width); data += nsPrintfCString(" }"); - data += nsPrintfCString(", \"screenSize\" : "); - data += nsPrintfCString("{ \"width\" : %d", aFrameMetrics.mViewport.width); - data += nsPrintfCString(", \"height\" : %d", aFrameMetrics.mViewport.height); + data += nsPrintfCString(", \"compositionBounds\" : "); + data += nsPrintfCString("{ \"x\" : %d", aFrameMetrics.mCompositionBounds.x); + data += nsPrintfCString(", \"y\" : %d", aFrameMetrics.mCompositionBounds.y); + data += nsPrintfCString(", \"width\" : %d", aFrameMetrics.mCompositionBounds.width); + data += nsPrintfCString(", \"height\" : %d", aFrameMetrics.mCompositionBounds.height); data += nsPrintfCString(" }"); data += nsPrintfCString(", \"cssPageRect\" : "); - data += nsPrintfCString("{ \"x\" : %f", aFrameMetrics.mCSSContentRect.x); - data += nsPrintfCString(", \"y\" : %f", aFrameMetrics.mCSSContentRect.y); - data += nsPrintfCString(", \"width\" : %f", aFrameMetrics.mCSSContentRect.width); - data += nsPrintfCString(", \"height\" : %f", aFrameMetrics.mCSSContentRect.height); + data += nsPrintfCString("{ \"x\" : %f", aFrameMetrics.mScrollableRect.x); + data += nsPrintfCString(", \"y\" : %f", aFrameMetrics.mScrollableRect.y); + data += nsPrintfCString(", \"width\" : %f", aFrameMetrics.mScrollableRect.width); + data += nsPrintfCString(", \"height\" : %f", aFrameMetrics.mScrollableRect.height); data += nsPrintfCString(" }"); data += nsPrintfCString(" }"); DispatchMessageManagerMessage(NS_LITERAL_STRING("Viewport:Change"), data); + nsCOMPtr utils(GetDOMWindowUtils()); + nsCOMPtr window = do_GetInterface(mWebNav); + + utils->SetScrollPositionClampingScrollPortSize( + aFrameMetrics.mCompositionBounds.width / aFrameMetrics.mZoom.width, + aFrameMetrics.mCompositionBounds.height / aFrameMetrics.mZoom.width); + window->ScrollTo(aFrameMetrics.mScrollOffset.x, + aFrameMetrics.mScrollOffset.y); + utils->SetResolution(aFrameMetrics.mResolution.width, + aFrameMetrics.mResolution.width); + + nsCOMPtr domDoc; + nsCOMPtr docElement; + mWebNav->GetDocument(getter_AddRefs(domDoc)); + if (domDoc) { + domDoc->GetDocumentElement(getter_AddRefs(docElement)); + if (docElement) { + utils->SetDisplayPortForElement( + aFrameMetrics.mDisplayPort.x, aFrameMetrics.mDisplayPort.y, + aFrameMetrics.mDisplayPort.width, aFrameMetrics.mDisplayPort.height, + docElement); + } + } + + mLastMetrics = aFrameMetrics; + return true; } @@ -926,8 +1249,7 @@ TabChild::RecvMouseEvent(const nsString& aType, const int32_t& aModifiers, const bool& aIgnoreRootScrollFrame) { - nsCOMPtr window = do_GetInterface(mWebNav); - nsCOMPtr utils = do_GetInterface(window); + nsCOMPtr utils(GetDOMWindowUtils()); NS_ENSURE_TRUE(utils, true); utils->SendMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers, aIgnoreRootScrollFrame, 0, 0); @@ -1028,8 +1350,7 @@ TabChild::RecvKeyEvent(const nsString& aType, const int32_t& aModifiers, const bool& aPreventDefault) { - nsCOMPtr window = do_GetInterface(mWebNav); - nsCOMPtr utils = do_GetInterface(window); + nsCOMPtr utils(GetDOMWindowUtils()); NS_ENSURE_TRUE(utils, true); bool ignored = false; utils->SendKeyEvent(aType, aKeyCode, aCharCode, @@ -1279,6 +1600,13 @@ TabChild::RecvDestroy() ); } + nsCOMPtr observerService = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID); + + observerService->RemoveObserver(this, CANCEL_DEFAULT_PAN_ZOOM); + observerService->RemoveObserver(this, BROWSER_ZOOM_TO_RECT); + observerService->RemoveObserver(this, BEFORE_FIRST_PAINT); + // XXX what other code in ~TabChild() should we be running here? DestroyWindow(); @@ -1317,7 +1645,7 @@ TabChild::InitTabChildGlobal(FrameScriptLoading aScriptLoading) mTabChildGlobal = scope; nsISupports* scopeSupports = NS_ISUPPORTS_CAST(nsIDOMEventTarget*, scope); - + NS_ENSURE_TRUE(InitTabChildGlobalInternal(scopeSupports), false); scope->Init(); @@ -1325,6 +1653,8 @@ TabChild::InitTabChildGlobal(FrameScriptLoading aScriptLoading) nsCOMPtr root = do_QueryInterface(chromeHandler); NS_ENSURE_TRUE(root, false); root->SetParentTarget(scope); + + chromeHandler->AddEventListener(NS_LITERAL_STRING("DOMMetaAdded"), this, false); } if (aScriptLoading != DONT_LOAD_SCRIPTS && !mTriedBrowserInit) { @@ -1389,10 +1719,13 @@ TabChild::InitRenderingState() if (observerService) { observerService->AddObserver(this, - "cancel-default-pan-zoom", + CANCEL_DEFAULT_PAN_ZOOM, false); observerService->AddObserver(this, - "browser-zoom-to-rect", + BROWSER_ZOOM_TO_RECT, + false); + observerService->AddObserver(this, + BEFORE_FIRST_PAINT, false); } diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index 1e6a0b232ab..aeae753937a 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -36,9 +36,11 @@ #include "nsNetUtil.h" #include "nsFrameMessageManager.h" #include "nsIScriptContext.h" +#include "nsIWebProgressListener.h" #include "nsDOMEventTargetHelper.h" #include "nsIDialogCreator.h" #include "nsIDialogParamBlock.h" +#include "nsIDOMWindowUtils.h" #include "nsIPresShell.h" #include "nsIPrincipal.h" #include "nsIScriptObjectPrincipal.h" @@ -141,6 +143,8 @@ class TabChild : public PBrowserChild, public nsIWebBrowserChromeFocus, public nsIInterfaceRequestor, public nsIWindowProvider, + public nsIDOMEventListener, + public nsIWebProgressListener, public nsSupportsWeakReference, public nsIDialogCreator, public nsITabChild, @@ -175,6 +179,8 @@ public: NS_DECL_NSIWEBBROWSERCHROMEFOCUS NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIWINDOWPROVIDER + NS_DECL_NSIDOMEVENTLISTENER + NS_DECL_NSIWEBPROGRESSLISTENER NS_DECL_NSIDIALOGCREATOR NS_DECL_NSITABCHILD NS_DECL_NSIOBSERVER @@ -328,6 +334,16 @@ private: // Call RecvShow(nsIntSize(0, 0)) and block future calls to RecvShow(). void DoFakeShow(); + // Wrapper for nsIDOMWindowUtils.setCSSViewport(). This updates some state + // variables local to this class before setting it. + void SetCSSViewport(float aX, float aY); + + // Recalculates the display state, including the CSS viewport. This should + // be called whenever we believe the meta viewport data on a document may + // have changed. If it didn't change, this function doesn't do anything. + // However, it should not be called all the time as it is fairly expensive. + void HandlePossibleMetaViewportChange(); + // Wraps up a JSON object as a structured clone and sends it to the browser // chrome script. // @@ -347,18 +363,30 @@ private: bool* aWindowIsNew, nsIDOMWindow** aReturn); + nsIDOMWindowUtils* GetDOMWindowUtils() + { + nsCOMPtr window = do_GetInterface(mWebNav); + nsCOMPtr utils = do_GetInterface(window); + return utils; + } + nsCOMPtr mWebNav; nsCOMPtr mWidget; + nsCOMPtr mLastURI; + FrameMetrics mLastMetrics; RenderFrameChild* mRemoteFrame; nsRefPtr mTabChildGlobal; uint32_t mChromeFlags; nsIntRect mOuterRect; + nsIntSize mInnerSize; + float mOldViewportWidth; nscolor mLastBackgroundColor; ScrollingBehavior mScrolling; uint32_t mAppId; bool mDidFakeShow; bool mIsBrowserElement; bool mNotified; + bool mContentDocumentIsDisplayed; bool mTriedBrowserInit; DISALLOW_EVIL_CONSTRUCTORS(TabChild); diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 578566942e8..13a05addbe6 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -83,6 +83,7 @@ TabParent::TabParent(mozIApplication* aApp, bool aIsBrowserElement) , mIMECompositionStart(0) , mIMESeqno(0) , mEventCaptureDepth(0) + , mDimensions(0, 0) , mDPI(0) , mIsBrowserElement(aIsBrowserElement) , mShown(false) @@ -229,6 +230,7 @@ TabParent::Show(const nsIntSize& size) { // sigh mShown = true; + mDimensions = size; unused << SendShow(size); } @@ -239,6 +241,7 @@ TabParent::UpdateDimensions(const nsRect& rect, const nsIntSize& size) if (RenderFrameParent* rfp = GetRenderFrame()) { rfp->NotifyDimensionsChanged(size.width, size.height); } + mDimensions = size; } void @@ -1172,6 +1175,21 @@ TabParent::RecvBrowserFrameOpenWindow(PBrowserParent* aOpener, return true; } +bool +TabParent::RecvPRenderFrameConstructor(PRenderFrameParent* actor, + ScrollingBehavior* scrolling, + LayersBackend* backend, + int32_t* maxTextureSize, + uint64_t* layersId) +{ + RenderFrameParent* rfp = GetRenderFrame(); + if (mDimensions != nsIntSize() && rfp) { + rfp->NotifyDimensionsChanged(mDimensions.width, mDimensions.height); + } + + return true; +} + bool TabParent::RecvZoomToRect(const gfxRect& aRect) { @@ -1181,6 +1199,17 @@ TabParent::RecvZoomToRect(const gfxRect& aRect) return true; } +bool +TabParent::RecvUpdateZoomConstraints(const bool& aAllowZoom, + const float& aMinZoom, + const float& aMaxZoom) +{ + if (RenderFrameParent* rfp = GetRenderFrame()) { + rfp->UpdateZoomConstraints(aAllowZoom, aMinZoom, aMaxZoom); + } + return true; +} + bool TabParent::RecvContentReceivedTouch(const bool& aPreventDefault) { diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index b676620c0d7..285d145c020 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -94,6 +94,11 @@ public: virtual bool RecvMoveFocus(const bool& aForward); virtual bool RecvEvent(const RemoteDOMEvent& aEvent); + virtual bool RecvPRenderFrameConstructor(PRenderFrameParent* actor, + ScrollingBehavior* scrolling, + LayersBackend* backend, + int32_t* maxTextureSize, + uint64_t* layersId); virtual bool RecvBrowserFrameOpenWindow(PBrowserParent* aOpener, const nsString& aURL, const nsString& aName, @@ -131,6 +136,9 @@ public: virtual bool RecvGetDPI(float* aValue); virtual bool RecvGetWidgetNativeData(WindowsHandle* aValue); virtual bool RecvZoomToRect(const gfxRect& aRect); + virtual bool RecvUpdateZoomConstraints(const bool& aAllowZoom, + const float& aMinZoom, + const float& aMaxZoom); virtual bool RecvContentReceivedTouch(const bool& aPreventDefault); virtual PContentDialogParent* AllocPContentDialog(const uint32_t& aType, const nsCString& aName, @@ -269,6 +277,7 @@ protected: // The number of event series we're currently capturing. int32_t mEventCaptureDepth; + nsIntSize mDimensions; float mDPI; bool mIsBrowserElement; bool mShown; diff --git a/dom/network/interfaces/nsIMobileConnectionProvider.idl b/dom/network/interfaces/nsIMobileConnectionProvider.idl index 024b828c2d8..67f92e1578f 100644 --- a/dom/network/interfaces/nsIMobileConnectionProvider.idl +++ b/dom/network/interfaces/nsIMobileConnectionProvider.idl @@ -14,7 +14,7 @@ interface nsIDOMWindow; * XPCOM component (in the content process) that provides the mobile * network information. */ -[scriptable, uuid(63787ba1-5091-450b-8810-d321a8b4f77a)] +[scriptable, uuid(bc1a82aa-2a1f-4a27-a5e7-614d06b72e0a)] interface nsIMobileConnectionProvider : nsISupports { readonly attribute DOMString cardState; @@ -40,4 +40,6 @@ interface nsIMobileConnectionProvider : nsISupports void sendStkMenuSelection(in nsIDOMWindow window, in unsigned short itemIdentifier, in boolean helpRequested); + void sendStkEventDownload(in nsIDOMWindow window, + in jsval event); }; diff --git a/dom/system/gonk/RILContentHelper.js b/dom/system/gonk/RILContentHelper.js index 4aed9d0a31f..1f73c31084f 100644 --- a/dom/system/gonk/RILContentHelper.js +++ b/dom/system/gonk/RILContentHelper.js @@ -453,6 +453,15 @@ RILContentHelper.prototype = { helpRequested: helpRequested}); }, + sendStkEventDownload: function sendStkEventDownload(window, + event) { + if (window == null) { + throw Components.Exception("Can't get window object", + Cr.NS_ERROR_UNEXPECTED); + } + cpmm.sendAsyncMessage("RIL:SendStkEventDownload", {event: event}); + }, + _telephonyCallbacks: null, _voicemailCallbacks: null, _enumerateTelephonyCallbacks: null, diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js index bbfa06803d6..fd1e93a2b6d 100644 --- a/dom/system/gonk/RadioInterfaceLayer.js +++ b/dom/system/gonk/RadioInterfaceLayer.js @@ -73,6 +73,7 @@ const RIL_IPC_MOBILECONNECTION_MSG_NAMES = [ "RIL:CancelUSSD", "RIL:SendStkResponse", "RIL:SendStkMenuSelection", + "RIL:SendStkEventDownload", ]; XPCOMUtils.defineLazyServiceGetter(this, "gSmsService", @@ -384,6 +385,9 @@ RadioInterfaceLayer.prototype = { case "RIL:SendStkMenuSelection": this.sendStkMenuSelection(msg.json); break; + case "RIL:SendStkEventDownload": + this.sendStkEventDownload(msg.json); + break; } }, @@ -1530,6 +1534,11 @@ RadioInterfaceLayer.prototype = { this.worker.postMessage(message); }, + sendStkEventDownload: function sendStkEventDownload(message) { + message.rilMessageType = "sendStkEventDownload"; + this.worker.postMessage(message); + }, + get microphoneMuted() { return gAudioManager.microphoneMuted; }, diff --git a/dom/system/gonk/ril_consts.js b/dom/system/gonk/ril_consts.js index 01b749b6c1e..194af1e1b0a 100644 --- a/dom/system/gonk/ril_consts.js +++ b/dom/system/gonk/ril_consts.js @@ -537,11 +537,15 @@ const COMPREHENSIONTLV_TAG_ALPHA_ID = 0x05; const COMPREHENSIONTLV_TAG_ADDRESS = 0x06; const COMPREHENSIONTLV_TAG_SMS_TPDU = 0x0b; const COMPREHENSIONTLV_TAG_TEXT_STRING = 0x0d; +const COMPREHENSIONTLV_TAG_TONE = 0x0e; const COMPREHENSIONTLV_TAG_ITEM = 0x0f; const COMPREHENSIONTLV_TAG_ITEM_ID = 0x10; const COMPREHENSIONTLV_TAG_RESPONSE_LENGTH = 0x11; +const COMPREHENSIONTLV_TAG_FILE_LIST = 0x12; +const COMPREHENSIONTLV_TAG_LOCATION_INFO = 0x13; const COMPREHENSIONTLV_TAG_HELP_REQUEST = 0x15; const COMPREHENSIONTLV_TAG_DEFAULT_TEXT = 0x17; +const COMPREHENSIONTLV_TAG_LOCATION_STATUS = 0x1b; const COMPREHENSIONTLV_TAG_EVENT_LIST = 0x19; const COMPREHENSIONTLV_TAG_ICON_ID = 0x1e; const COMPREHENSIONTLV_TAG_ICON_ID_LIST = 0x1f; @@ -558,6 +562,8 @@ const STK_DEVICE_ID_NETWORK = 0x83; // STK Proactive commands. const STK_CMD_REFRESH = 0x01; +const STK_CMD_POLL_INTERVAL = 0x03; +const STK_CMD_POLL_OFF = 0x04; const STK_CMD_SET_UP_EVENT_LIST = 0x05; const STK_CMD_SET_UP_CALL = 0x10; const STK_CMD_SEND_SS = 0x11; @@ -565,6 +571,7 @@ const STK_CMD_SEND_USSD = 0x12; const STK_CMD_SEND_SMS = 0x13; const STK_CMD_SEND_DTMF = 0x14; const STK_CMD_LAUNCH_BROWSER = 0x15; +const STK_CMD_PLAY_TONE = 0x20; const STK_CMD_DISPLAY_TEXT = 0x21; const STK_CMD_GET_INKEY = 0x22; const STK_CMD_GET_INPUT = 0x23; @@ -725,6 +732,36 @@ const STK_EVENT_TYPE_LOCAL_CONNECTION = 0x0d; const STK_EVENT_TYPE_NETWORK_SEARCH_MODE_CHANGED = 0x0e; const STK_EVENT_TYPE_BROWSING_STATUS = 0x0f; +// STK Service state of Location Status. +const STK_SERVICE_STATE_NORMAL = 0x00; +const STK_SERVICE_STATE_LIMITED = 0x01; +const STK_SERVICE_STATE_UNAVAILABLE = 0x02; + +// Refresh mode. +const STK_REFRESH_NAA_INIT_AND_FULL_FILE_CHANGE = 0x00; +const STK_REFRESH_FILE_CHANGE = 0x01; +const STK_REFRESH_NAA_INIT_AND_FILE_CHANGE = 0x02; +const STK_REFRESH_NAA_INIT = 0x03; +const STK_REFRESH_UICC_RESET = 0x04; + +// Tone type. +const STK_TONE_TYPE_DIAL_TONE = 0x01; +const STK_TONE_TYPE_CALLED_SUBSCRIBER_BUSY = 0x02; +const STK_TONE_TYPE_CONGESTION = 0x03; +const STK_TONE_TYPE_RADIO_PATH_ACK = 0x04; +const STK_TONE_TYPE_RADIO_PATH_NOT_AVAILABLE = 0x05; +const STK_TONE_TYPE_ERROR = 0x06; +const STK_TONE_TYPE_CALL_WAITING_TONE = 0x07; +const STK_TONE_TYPE_RINGING_TONE = 0x08; +const STK_TONE_TYPE_GENERAL_BEEP = 0x10; +const STK_TONE_TYPE_POSITIVE_ACK_TONE = 0x11; +const STK_TONE_TYPE_NEGATIVE_ACK_TONE = 0x12; + +// Time unit. +const STK_TIME_UNIT_MINUTE = 0x00; +const STK_TIME_UNIT_SECOND = 0x01; +const STK_TIME_UNIT_TENTH_SECOND = 0x02; + /** * (U)SIM Services. * diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index c8323b7c610..88122b547ed 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -51,6 +51,16 @@ const PARCEL_SIZE_SIZE = UINT32_SIZE; const PDU_HEX_OCTET_SIZE = 4; +const TLV_COMMAND_DETAILS_SIZE = 5; +const TLV_DEVICE_ID_SIZE = 4; +const TLV_RESULT_SIZE = 3; +const TLV_ITEM_ID_SIZE = 3; +const TLV_HELP_REQUESTED_SIZE = 2; +const TLV_EVENT_LIST_SIZE = 3; +const TLV_LOCATION_STATUS_SIZE = 3; +const TLV_LOCATION_INFO_GSM_SIZE = 9; +const TLV_LOCATION_INFO_UMTS_SIZE = 11; + const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"]; let RILQUIRKS_CALLSTATE_EXTRA_UINT32 = libcutils.property_get("ro.moz.ril.callstate_extra_int"); @@ -2257,10 +2267,10 @@ let RIL = { } // 1 octets = 2 chars. - let size = (5 + /* Size of Command Details TLV */ - 4 + /* Size of Device Identifier TLV */ - 3 + /* Size of Result */ - (response.itemIdentifier ? 3 : 0) + + let size = (TLV_COMMAND_DETAILS_SIZE + + TLV_DEVICE_ID_SIZE + + TLV_RESULT_SIZE + + (response.itemIdentifier ? TLV_ITEM_ID_SIZE : 0) + (textLen ? textLen + 3 : 0)) * 2; Buf.writeUint32(size); @@ -2365,6 +2375,29 @@ let RIL = { this.sendICCEnvelopeCommand(command); }, + /** + * Send STK Envelope(Event Download) command. + * @param event + */ + sendStkEventDownload: function sendStkEventDownload(command) { + command.tag = BER_EVENT_DOWNLOAD_TAG; + command.eventList = command.event.eventType; + switch (command.eventList) { + case STK_EVENT_TYPE_LOCATION_STATUS: + command.deviceId = { + sourceId :STK_DEVICE_ID_ME, + destinationId: STK_DEVICE_ID_SIM + }; + command.locationStatus = command.event.locationStatus; + // Location info should only be provided when locationStatus is normal. + if (command.locationStatus == STK_SERVICE_STATE_NORMAL) { + command.locationInfo = command.event.locationInfo; + } + break; + } + this.sendICCEnvelopeCommand(command); + }, + /** * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC. * @@ -2372,12 +2405,25 @@ let RIL = { * @patam deviceId * @param [optioanl] itemIdentifier * @param [optional] helpRequested + * @param [optional] eventList + * @param [optional] locationStatus + * @param [optional] locationInfo */ sendICCEnvelopeCommand: function sendICCEnvelopeCommand(options) { + if (DEBUG) { + debug("Stk Envelope " + JSON.stringify(options)); + } let token = Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND); - let berLen = 4 + /* Size of Device Identifier TLV */ - (options.itemIdentifier ? 3 : 0) + - (options.helpRequested ? 2 : 0); + let berLen = TLV_DEVICE_ID_SIZE + /* Size of Device Identifier TLV */ + (options.itemIdentifier ? TLV_ITEM_ID_SIZE : 0) + + (options.helpRequested ? TLV_HELP_REQUESTED_SIZE : 0) + + (options.eventList ? TLV_EVENT_LIST_SIZE : 0) + + (options.locationStatus ? TLV_LOCATION_STATUS_SIZE : 0) + + (options.locationInfo ? + (options.locationInfo.gsmCellId > 0xffff ? + TLV_LOCATION_INFO_UMTS_SIZE : + TLV_LOCATION_INFO_GSM_SIZE) : + 0); let size = (2 + berLen) * 2; Buf.writeUint32(size); @@ -2409,6 +2455,28 @@ let RIL = { // Help Request doesn't have value } + // Event List + if (options.eventList) { + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST | + COMPREHENSIONTLV_FLAG_CR); + GsmPDUHelper.writeHexOctet(1); + GsmPDUHelper.writeHexOctet(options.eventList); + } + + // Location Status + if (options.locationStatus) { + let len = options.locationStatus.length; + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS | + COMPREHENSIONTLV_FLAG_CR); + GsmPDUHelper.writeHexOctet(1); + GsmPDUHelper.writeHexOctet(options.locationStatus); + } + + // Location Info + if (options.locationInfo) { + ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo); + } + Buf.writeUint32(0); Buf.sendParcel(); }, @@ -6006,6 +6074,15 @@ let StkCommandParamsFactory = { createParam: function createParam(cmdDetails, ctlvs) { let param; switch (cmdDetails.typeOfCommand) { + case STK_CMD_REFRESH: + param = this.processRefresh(cmdDetails, ctlvs); + break; + case STK_CMD_POLL_INTERVAL: + param = this.processPollInterval(cmdDetails, ctlvs); + break; + case STK_CMD_POLL_OFF: + param = this.processPollOff(cmdDetails, ctlvs); + break; case STK_CMD_SET_UP_EVENT_LIST: param = this.processSetUpEventList(cmdDetails, ctlvs); break; @@ -6034,9 +6111,12 @@ let StkCommandParamsFactory = { case STK_CMD_SET_UP_CALL: param = this.processSetupCall(cmdDetails, ctlvs); break; - case STK_LAUNCH_BROWSER: + case STK_CMD_LAUNCH_BROWSER: param = this.processLaunchBrowser(cmdDetails, ctlvs); break; + case STK_CMD_PLAY_TONE: + param = this.processPlayTone(cmdDetails, ctlvs); + break; default: debug("unknown proactive command"); break; @@ -6044,6 +6124,66 @@ let StkCommandParamsFactory = { return param; }, + /** + * Construct a param for Refresh. + * + * @param cmdDetails + * The value object of CommandDetails TLV. + * @param ctlvs + * The all TLVs in this proactive command. + */ + processRefresh: function processRefresh(cmdDetails, ctlvs) { + let refreshType = cmdDetails.commandQualifier; + switch (refreshType) { + case STK_REFRESH_FILE_CHANGE: + case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE: + let ctlv = StkProactiveCmdHelper.searchForTag( + COMPREHENSIONTLV_FILE_LIST, ctlvs); + if (ctlv) { + let list = ctlv.value.fileList; + if (DEBUG) { + debug("Refresh, list = " + list); + } + RIL.fetchICCRecords(); + } + break; + } + return {}; + }, + + /** + * Construct a param for Poll Interval. + * + * @param cmdDetails + * The value object of CommandDetails TLV. + * @param ctlvs + * The all TLVs in this proactive command. + */ + processPollInterval: function processPollInterval(cmdDetails, ctlvs) { + let ctlv = StkProactiveCmdHelper.searchForTag( + COMPREHENSIONTLV_TAG_DURATION, ctlvs); + if (!ctlv) { + RIL.sendStkTerminalResponse({ + command: cmdDetails, + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); + throw new Error("Stk Poll Interval: Required value missing : Duration"); + } + + return ctlv.value; + }, + + /** + * Construct a param for Poll Off. + * + * @param cmdDetails + * The value object of CommandDetails TLV. + * @param ctlvs + * The all TLVs in this proactive command. + */ + processPollOff: function processPollOff(cmdDetails, ctlvs) { + return {}; + }, + /** * Construct a param for Set Up Event list. * @@ -6309,6 +6449,32 @@ let StkCommandParamsFactory = { browser.mode = cmdDetails.commandQualifier & 0x03; return browser; + }, + + processPlayTone: function processPlayTone(cmdDetails, ctlvs) { + let playTone = {}; + + let ctlv = StkProactiveCmdHelper.searchForTag( + COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); + if (ctlv) { + playTone.text = ctlv.value.identifier; + } + + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TONE, ctlvs); + if (ctlv) { + playTone.tone = ctlv.value.tone; + } + + ctlv = StkProactiveCmdHelper.searchForTag( + COMPREHENSIONTLV_TAG_DURATION, ctlvs); + if (ctlv) { + playTone.duration = ctlv.value; + } + + // vibrate is only defined in TS 102.223 + playTone.isVibrate = (cmdDetails.commandQualifier & 0x01) != 0x00; + + return playTone; } }; @@ -6321,16 +6487,22 @@ let StkProactiveCmdHelper = { return this.retrieveDeviceId(length); case COMPREHENSIONTLV_TAG_ALPHA_ID: return this.retrieveAlphaId(length); + case COMPREHENSIONTLV_TAG_DURATION: + return this.retrieveDuration(length); case COMPREHENSIONTLV_TAG_ADDRESS: return this.retrieveAddress(length); case COMPREHENSIONTLV_TAG_TEXT_STRING: return this.retrieveTextString(length); + case COMPREHENSIONTLV_TAG_TONE: + return this.retrieveTone(length); case COMPREHENSIONTLV_TAG_ITEM: return this.retrieveItem(length); case COMPREHENSIONTLV_TAG_ITEM_ID: return this.retrieveItemId(length); case COMPREHENSIONTLV_TAG_RESPONSE_LENGTH: return this.retrieveResponseLength(length); + case COMPREHENSIONTLV_TAG_FILE_LIST: + return this.retrieveFileList(length); case COMPREHENSIONTLV_TAG_DEFAULT_TEXT: return this.retrieveDefaultText(length); case COMPREHENSIONTLV_TAG_EVENT_LIST: @@ -6398,6 +6570,23 @@ let StkProactiveCmdHelper = { return alphaId; }, + /** + * Duration. + * + * | Byte | Description | Length | + * | 1 | Response Length Tag | 1 | + * | 2 | Lenth = 02 | 1 | + * | 3 | Time unit | 1 | + * | 4 | Time interval | 1 | + */ + retrieveDuration: function retrieveDuration(length) { + let duration = { + timeUnit: GsmPDUHelper.readHexOctet(), + timeInterval: GsmPDUHelper.readHexOctet(), + }; + return duration; + }, + /** * Address. * @@ -6450,6 +6639,21 @@ let StkProactiveCmdHelper = { return text; }, + /** + * Tone. + * + * | Byte | Description | Length | + * | 1 | Tone Tag | 1 | + * | 2 | Lenth = 01 | 1 | + * | 3 | Tone | 1 | + */ + retrieveTone: function retrieveTone(length) { + let tone = { + tone: GsmPDUHelper.readHexOctet(), + }; + return tone; + }, + /** * Item. * @@ -6500,6 +6704,30 @@ let StkProactiveCmdHelper = { return rspLength; }, + /** + * File List. + * + * | Byte | Description | Length | + * | 1 | File List Tag | 1 | + * | 2 ~ (Y-1)+2 | Length (X) | Y | + * | (Y-1)+3 | Number of files | 1 | + * | (Y-1)+4 ~ | Files | X | + * | (Y-1)+X+2 | | | + */ + retrieveFileList: function retrieveFileList(length) { + let num = GsmPDUHelper.readHexOctet(); + let fileList = ""; + length--; // -1 for the num octet. + for (let i = 0; i < 2 * length; i++) { + // Didn't use readHexOctet here, + // otherwise 0x00 will be "0", not "00" + fileList += String.fromCharCode(Buf.readUint16()); + } + return { + fileList: fileList + }; + }, + /** * Default Text. * @@ -6669,7 +6897,68 @@ let ComprehensionTlvHelper = { index += tlv.hlen; } return chunks; - } + }, + + /** + * Write Location Info Comprehension TLV. + * + * @param loc location Information. + */ + writeLocationInfoTlv: function writeLocationInfoTlv(loc) { + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO | + COMPREHENSIONTLV_FLAG_CR); + GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7); + // From TS 11.14, clause 12.19 + // "The mobile country code (MCC), the mobile network code (MNC), + // the location area code (LAC) and the cell ID are + // coded as in TS 04.08." + // And from TS 04.08 and TS 24.008, + // the format is as follows: + // + // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3 + // + // 8 7 6 5 4 3 2 1 + // +-------------+-------------+ + // | MCC digit 2 | MCC digit 1 | octet 2 + // | MNC digit 3 | MCC digit 3 | octet 3 + // | MNC digit 2 | MNC digit 1 | octet 4 + // +-------------+-------------+ + // + // Also in TS 24.008 + // "However a network operator may decide to + // use only two digits in the MNC in the LAI over the + // radio interface. In this case, bits 5 to 8 of octet 3 + // shall be coded as '1111'". + + // MCC & MNC, 3 octets + let mcc = loc.mcc.toString(); + let mnc = loc.mnc.toString(); + if (mnc.length == 1) { + mnc = "F0" + mnc; + } else if (mnc.length == 2) { + mnc = "F" + mnc; + } else { + mnc = mnc[2] + mnc[0] + mnc[1]; + } + GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc); + + // LAC, 2 octets + GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff); + GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff); + + // Cell Id + if (loc.gsmCellId > 0xffff) { + // UMTS/WCDMA, gsmCellId is 28 bits. + GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff); + GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff); + GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff); + GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff); + } else { + // GSM, gsmCellId is 16 bits. + GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff); + GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff); + } + }, }; let BerTlvHelper = { diff --git a/dom/system/gonk/tests/test_ril_worker_icc.js b/dom/system/gonk/tests/test_ril_worker_icc.js index 411d548bf39..1dc135525c4 100644 --- a/dom/system/gonk/tests/test_ril_worker_icc.js +++ b/dom/system/gonk/tests/test_ril_worker_icc.js @@ -142,3 +142,202 @@ add_test(function test_write_dialling_number() { run_next_test(); }); +/** + * Verify ComprehensionTlvHelper.writeLocationInfoTlv + */ +add_test(function test_write_location_info_tlv() { + let worker = newUint8Worker(); + let pduHelper = worker.GsmPDUHelper; + let tlvHelper = worker.ComprehensionTlvHelper; + + // Test with 2-digit mnc, and gsmCellId obtained from UMTS network. + let loc = { + mcc: 466, + mnc: 92, + gsmLocationAreaCode : 10291, + gsmCellId: 19072823 + }; + tlvHelper.writeLocationInfoTlv(loc); + + let tag = pduHelper.readHexOctet(); + do_check_eq(tag, COMPREHENSIONTLV_TAG_LOCATION_INFO | + COMPREHENSIONTLV_FLAG_CR); + + let length = pduHelper.readHexOctet(); + do_check_eq(length, 9); + + let mcc_mnc = pduHelper.readSwappedNibbleBcdString(3); + do_check_eq(mcc_mnc, "46692"); + + let lac = (pduHelper.readHexOctet() << 8) | pduHelper.readHexOctet(); + do_check_eq(lac, 10291); + + let cellId = (pduHelper.readHexOctet() << 24) | + (pduHelper.readHexOctet() << 16) | + (pduHelper.readHexOctet() << 8) | + (pduHelper.readHexOctet()); + do_check_eq(cellId, 19072823); + + // Test with 1-digit mnc, and gsmCellId obtained from GSM network. + loc = { + mcc: 466, + mnc: 2, + gsmLocationAreaCode : 10291, + gsmCellId: 65534 + }; + tlvHelper.writeLocationInfoTlv(loc); + + tag = pduHelper.readHexOctet(); + do_check_eq(tag, COMPREHENSIONTLV_TAG_LOCATION_INFO | + COMPREHENSIONTLV_FLAG_CR); + + length = pduHelper.readHexOctet(); + do_check_eq(length, 7); + + mcc_mnc = pduHelper.readSwappedNibbleBcdString(3); + do_check_eq(mcc_mnc, "46602"); + + lac = (pduHelper.readHexOctet() << 8) | pduHelper.readHexOctet(); + do_check_eq(lac, 10291); + + cellId = (pduHelper.readHexOctet() << 8) | + (pduHelper.readHexOctet()); + do_check_eq(cellId, 65534); + + // Test with 3-digit mnc, and gsmCellId obtained from GSM network. + loc = { + mcc: 466, + mnc: 222, + gsmLocationAreaCode : 10291, + gsmCellId: 65534 + }; + tlvHelper.writeLocationInfoTlv(loc); + + tag = pduHelper.readHexOctet(); + do_check_eq(tag, COMPREHENSIONTLV_TAG_LOCATION_INFO | + COMPREHENSIONTLV_FLAG_CR); + + length = pduHelper.readHexOctet(); + do_check_eq(length, 7); + + mcc_mnc = pduHelper.readSwappedNibbleBcdString(3); + do_check_eq(mcc_mnc, "466222"); + + lac = (pduHelper.readHexOctet() << 8) | pduHelper.readHexOctet(); + do_check_eq(lac, 10291); + + cellId = (pduHelper.readHexOctet() << 8) | + (pduHelper.readHexOctet()); + do_check_eq(cellId, 65534); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Refresh + */ +add_test(function test_stk_proactive_command_refresh() { + let worker = newUint8Worker(); + let pduHelper = worker.GsmPDUHelper; + let berHelper = worker.BerTlvHelper; + let stkHelper = worker.StkProactiveCmdHelper; + + let refresh_1 = [ + 0xD0, + 0x10, + 0x81, 0x03, 0x01, 0x01, 0x01, + 0x82, 0x02, 0x81, 0x82, + 0x92, 0x05, 0x01, 0x3F, 0x00, 0x2F, 0xE2]; + + for (let i = 0; i < refresh_1.length; i++) { + pduHelper.writeHexOctet(refresh_1[i]); + } + + let berTlv = berHelper.decode(refresh_1.length); + let ctlvs = berTlv.value; + let tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs); + do_check_eq(tlv.value.commandNumber, 0x01); + do_check_eq(tlv.value.typeOfCommand, 0x01); + do_check_eq(tlv.value.commandQualifier, 0x01); + + tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs); + do_check_eq(tlv.value.fileList, "3F002FE2"); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Play Tone + */ +add_test(function test_stk_proactive_command_play_tone() { + let worker = newUint8Worker(); + let pduHelper = worker.GsmPDUHelper; + let berHelper = worker.BerTlvHelper; + let stkHelper = worker.StkProactiveCmdHelper; + + let tone_1 = [ + 0xD0, + 0x1B, + 0x81, 0x03, 0x01, 0x20, 0x00, + 0x82, 0x02, 0x81, 0x03, + 0x85, 0x09, 0x44, 0x69, 0x61, 0x6C, 0x20, 0x54, 0x6F, 0x6E, 0x65, + 0x8E, 0x01, 0x01, + 0x84, 0x02, 0x01, 0x05]; + + for (let i = 0; i < tone_1.length; i++) { + pduHelper.writeHexOctet(tone_1[i]); + } + + let berTlv = berHelper.decode(tone_1.length); + let ctlvs = berTlv.value; + let tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs); + do_check_eq(tlv.value.commandNumber, 0x01); + do_check_eq(tlv.value.typeOfCommand, 0x20); + do_check_eq(tlv.value.commandQualifier, 0x00); + + tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); + do_check_eq(tlv.value.identifier, "Dial Tone"); + + tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_TONE, ctlvs); + do_check_eq(tlv.value.tone, STK_TONE_TYPE_DIAL_TONE); + + tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs); + do_check_eq(tlv.value.timeUnit, STK_TIME_UNIT_SECOND); + do_check_eq(tlv.value.timeInterval, 5); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Poll Interval + */ +add_test(function test_stk_proactive_command_poll_interval() { + let worker = newUint8Worker(); + let pduHelper = worker.GsmPDUHelper; + let berHelper = worker.BerTlvHelper; + let stkHelper = worker.StkProactiveCmdHelper; + + let poll_1 = [ + 0xD0, + 0x0D, + 0x81, 0x03, 0x01, 0x03, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x84, 0x02, 0x01, 0x14]; + + for (let i = 0; i < poll_1.length; i++) { + pduHelper.writeHexOctet(poll_1[i]); + } + + let berTlv = berHelper.decode(poll_1.length); + let ctlvs = berTlv.value; + let tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs); + do_check_eq(tlv.value.commandNumber, 0x01); + do_check_eq(tlv.value.typeOfCommand, 0x03); + do_check_eq(tlv.value.commandQualifier, 0x00); + + tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs); + do_check_eq(tlv.value.timeUnit, STK_TIME_UNIT_SECOND); + do_check_eq(tlv.value.timeInterval, 0x14); + + run_next_test(); +}); diff --git a/gfx/2d/BaseRect.h b/gfx/2d/BaseRect.h index 7f88336a368..e6cce2dc659 100644 --- a/gfx/2d/BaseRect.h +++ b/gfx/2d/BaseRect.h @@ -403,6 +403,21 @@ struct BaseRect { width = right - x; height = bottom - y; } + // Scale 'this' by 1/aScale, converting coordinates to integers so that the result is + // the largest integer-coordinate rectangle contained by the unrounded result. + void ScaleInverseRoundIn(double aScale) { ScaleInverseRoundIn(aScale, aScale); } + // Scale 'this' by 1/aXScale and 1/aYScale, converting coordinates to integers so + // that the result is the largest integer-coordinate rectangle contained by the + // unrounded result. + void ScaleInverseRoundIn(double aXScale, double aYScale) + { + T right = static_cast(floor(double(XMost()) / aXScale)); + T bottom = static_cast(floor(double(YMost()) / aYScale)); + x = static_cast(ceil(double(x) / aXScale)); + y = static_cast(ceil(double(y) / aYScale)); + width = gfx_max(0, right - x); + height = gfx_max(0, bottom - y); + } /** * Clamp aPoint to this rectangle. It is allowed to end up on any diff --git a/gfx/layers/FrameMetrics.h b/gfx/layers/FrameMetrics.h index d5a09bfc8a6..2db60db9be7 100644 --- a/gfx/layers/FrameMetrics.h +++ b/gfx/layers/FrameMetrics.h @@ -30,12 +30,15 @@ public: // will begin at. FrameMetrics() - : mViewport(0, 0, 0, 0) + : mCompositionBounds(0, 0, 0, 0) , mContentRect(0, 0, 0, 0) - , mViewportScrollOffset(0, 0) + , mDisplayPort(0, 0, 0, 0) + , mViewport(0, 0, 0, 0) + , mScrollOffset(0, 0) , mScrollId(NULL_SCROLL_ID) - , mCSSContentRect(0, 0, 0, 0) + , mScrollableRect(0, 0, 0, 0) , mResolution(1, 1) + , mDevPixelsPerCSSPixel(1) , mMayHaveTouchListeners(false) {} @@ -44,7 +47,7 @@ public: bool operator==(const FrameMetrics& aOther) const { return (mViewport.IsEqualEdges(aOther.mViewport) && - mViewportScrollOffset == aOther.mViewportScrollOffset && + mScrollOffset == aOther.mScrollOffset && mDisplayPort.IsEqualEdges(aOther.mDisplayPort) && mScrollId == aOther.mScrollId); } @@ -68,21 +71,152 @@ public: return mScrollId != NULL_SCROLL_ID; } - // These are all in layer coordinate space. - nsIntRect mViewport; + gfxSize LayersPixelsPerCSSPixel() const + { + return mResolution * mDevPixelsPerCSSPixel; + } + + gfx::Point GetScrollOffsetInLayerPixels() const + { + return gfx::Point(mScrollOffset.x * LayersPixelsPerCSSPixel().width, + mScrollOffset.y * LayersPixelsPerCSSPixel().height); + } + + // --------------------------------------------------------------------------- + // The following metrics are all in widget space/device pixels. + // + + // This is the area within the widget that we're compositing to, which means + // that it is the visible region of this frame. It is not relative to + // anything. + // So { 0, 0, [compositeArea.width], [compositeArea.height] }. + // + // This is useful because, on mobile, the viewport and composition dimensions + // are not always the same. In this case, we calculate the displayport using + // an area bigger than the region we're compositing to. If we used the + // viewport dimensions to calculate the displayport, we'd run into situations + // where we're prerendering the wrong regions and the content may be clipped, + // or too much of it prerendered. If the displayport is the same as the + // viewport, there is no need for this and we can just use the viewport + // instead. + // + // This is only valid on the root layer. Nested iframes do not need this + // metric as they do not have a displayport set. See bug 775452. + nsIntRect mCompositionBounds; + + // |mScrollableRect|, stored in device pixels. DECPRECATED, DO NOT USE. + // + // This is valid on any layer where |mScrollableRect| is, though it may be + // more lazily maintained than |mScrollableRect|. That is, when + // |mScrollableRect| is updated, this may lag. For this reason, it's better to + // use |mScrollableRect| for any control logic. + // + // FIXME/bug 785929: Is this really necessary? Can it not be calculated from + // |mScrollableRect| whenever it's needed? nsIntRect mContentRect; - gfx::Point mViewportScrollOffset; - nsIntRect mDisplayPort; + + // --------------------------------------------------------------------------- + // The following metrics are all in CSS pixels. They are not in any uniform + // space, so each is explained separately. + // + + // The area of a frame's contents that has been painted, relative to the + // viewport. It is in the same coordinate space as |mViewport|. For example, + // if it is at 0,0, then it's at the same place at the viewport, which is at + // the top-left in the layer, and at the same place as the scroll offset of + // the document. + // + // Note that this is structured in such a way that it doesn't depend on the + // method layout uses to scroll content. + // + // May be larger or smaller than |mScrollableRect|. + // + // To pre-render a margin of 100 CSS pixels around the window, + // { x = -100, y = - 100, + // width = window.innerWidth + 100, height = window.innerHeight + 100 } + // + // This is only valid on the root layer. Nested iframes do not have a + // displayport set on them. See bug 775452. + gfx::Rect mDisplayPort; + + // The CSS viewport, which is the dimensions we're using to constrain the + // element of this frame, relative to the top-left of the layer. Note + // that its offset is structured in such a way that it doesn't depend on the + // method layout uses to scroll content. + // + // This is mainly useful on the root layer, however nested iframes can have + // their own viewport, which will just be the size of the window of the + // iframe. For layers that don't correspond to a document, this metric is + // meaningless and invalid. + gfx::Rect mViewport; + + // The position of the top-left of the CSS viewport, relative to the document + // (or the document relative to the viewport, if that helps understand it). + // + // Thus it is relative to the document. It is in the same coordinate space as + // |mScrollableRect|, but a different coordinate space than |mViewport| and + // |mDisplayPort|. + // + // It is required that the rect: + // { x = mScrollOffset.x, y = mScrollOffset.y, + // width = mCompositionBounds.x / mResolution.width, + // height = mCompositionBounds.y / mResolution.height } + // Be within |mScrollableRect|. + // + // This is valid for any layer, but is always relative to this frame and + // not any parents, regardless of parent transforms. + gfx::Point mScrollOffset; + + // A unique ID assigned to each scrollable frame (unless this is + // ROOT_SCROLL_ID, in which case it is not unique). ViewID mScrollId; - // Consumers often want to know the origin/size before scaling to pixels - // so we record this as well. - gfx::Rect mCSSContentRect; + // The scrollable bounds of a frame. This is determined by reflow. + // For the top-level |window|, + // { x = window.scrollX, y = window.scrollY, // could be 0, 0 + // width = window.innerWidth, height = window.innerHeight } + // + // This is relative to the document. It is in the same coordinate space as + // |mScrollOffset|, but a different coordinate space than |mViewport| and + // |mDisplayPort|. Note also that this coordinate system is understood by + // window.scrollTo(). + // + // This is valid on any layer unless it has no content. + gfx::Rect mScrollableRect; - // This represents the resolution at which the associated layer - // will been rendered. + // --------------------------------------------------------------------------- + // The following metrics are dimensionless. + // + + // The resolution, along both axes, that the current frame has been painted + // at. + // + // Every time this frame is composited and the compositor samples its + // transform, this metric is used to create a transform which is + // post-multiplied into the parent's transform. Since this only happens when + // we walk the layer tree, the resulting transform isn't stored here. Thus the + // resolution of parent layers is opaque to this metric. gfxSize mResolution; + // The amount we are currently zooming the frame. This is distinct from + // |mResolution| as we can paint a frame at a different resolution than we + // zoom it at. This is useful in situations where we want to zoom a frame + // without forcing a repaint. At steady state, this and |mResolution| will be + // equal. + // + // Every time this frame is composited and the compositor samples its + // transform, this metric is used to create a transform which is + // post-multiplied into the parent's transform. Since this only happens when + // we walk the layer tree, the resulting transform isn't stored here. Thus the + // zoom of parent layers is opaque to this metric. + gfxSize mZoom; + + // The conversion factor between CSS pixels and device pixels for this frame. + // This can vary based on a variety of things, such as reflowing-zoom. The + // conversion factor for device pixels to layers pixels is just the + // resolution. + float mDevPixelsPerCSSPixel; + // Whether or not this frame may have touch listeners. bool mMayHaveTouchListeners; }; diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 547475f3329..515ae8e991f 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -143,6 +143,17 @@ AppendToString(nsACString& s, const nsIntRect& r, return s += sfx; } +nsACString& +AppendToString(nsACString& s, const Rect& r, + const char* pfx="", const char* sfx="") +{ + s += pfx; + s.AppendPrintf( + "(x=%f, y=%f, w=%f, h=%f)", + r.x, r.y, r.width, r.height); + return s += sfx; +} + nsACString& AppendToString(nsACString& s, const nsIntRegion& r, const char* pfx="", const char* sfx="") @@ -173,7 +184,7 @@ AppendToString(nsACString& s, const FrameMetrics& m, { s += pfx; AppendToString(s, m.mViewport, "{ viewport="); - AppendToString(s, m.mViewportScrollOffset, " viewportScroll="); + AppendToString(s, m.mScrollOffset, " viewportScroll="); AppendToString(s, m.mDisplayPort, " displayport="); AppendToString(s, m.mScrollId, " scrollId=", " }"); return s += sfx; diff --git a/gfx/layers/basic/BasicTiledThebesLayer.cpp b/gfx/layers/basic/BasicTiledThebesLayer.cpp index d41380af96d..b786a140f2e 100644 --- a/gfx/layers/basic/BasicTiledThebesLayer.cpp +++ b/gfx/layers/basic/BasicTiledThebesLayer.cpp @@ -253,7 +253,7 @@ BasicTiledThebesLayer::PaintThebes(gfxContext* aContext, Layer* primaryScrollable = BasicManager()->GetPrimaryScrollableLayer(); if (primaryScrollable) { const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics(); - scrollOffset = metrics.mViewportScrollOffset; + scrollOffset = metrics.mScrollOffset; } int32_t scrollDiffX = scrollOffset.x - mLastScrollOffset.x; int32_t scrollDiffY = scrollOffset.y - mLastScrollOffset.y; diff --git a/gfx/layers/ipc/AsyncPanZoomController.cpp b/gfx/layers/ipc/AsyncPanZoomController.cpp index 216e5d5ae7a..ef461066207 100644 --- a/gfx/layers/ipc/AsyncPanZoomController.cpp +++ b/gfx/layers/ipc/AsyncPanZoomController.cpp @@ -44,7 +44,7 @@ static const int32_t FLING_REPAINT_INTERVAL = 75; * Minimum amount of speed along an axis before we begin painting far ahead by * adjusting the displayport. */ -static const float MIN_SKATE_SPEED = 0.5f; +static const float MIN_SKATE_SPEED = 0.7f; /** * Angle from axis within which we stay axis-locked. @@ -79,17 +79,26 @@ static const double MIN_ZOOM = 0.125; */ static const int TOUCH_LISTENER_TIMEOUT = 300; +/** + * Number of samples to store of how long it took to paint after the previous + * requests. + */ +static const int NUM_PAINT_DURATION_SAMPLES = 3; + AsyncPanZoomController::AsyncPanZoomController(GeckoContentController* aGeckoContentController, GestureBehavior aGestures) : mGeckoContentController(aGeckoContentController), mTouchListenerTimeoutTask(nullptr), mX(this), mY(this), + mAllowZoom(true), + mMinZoom(MIN_ZOOM), + mMaxZoom(MAX_ZOOM), mMonitor("AsyncPanZoomController"), mLastSampleTime(TimeStamp::Now()), mState(NOTHING), mDPI(72), - mContentPainterStatus(CONTENT_IDLE), + mWaitingForContentToPaint(false), mDisableNextTouchBatch(false), mHandlingTouchQueue(false) { @@ -137,11 +146,11 @@ AsyncPanZoomController::ReceiveInputEvent(const nsInputEvent& aEvent, gfx::Point currentScrollOffset, lastScrollOffset; { MonitorAutoLock monitor(mMonitor); - currentZoom = mFrameMetrics.mResolution.width; - currentScrollOffset = gfx::Point(mFrameMetrics.mViewportScrollOffset.x, - mFrameMetrics.mViewportScrollOffset.y); - lastScrollOffset = gfx::Point(mLastContentPaintMetrics.mViewportScrollOffset.x, - mLastContentPaintMetrics.mViewportScrollOffset.y); + currentZoom = mFrameMetrics.mZoom.width; + currentScrollOffset = gfx::Point(mFrameMetrics.mScrollOffset.x, + mFrameMetrics.mScrollOffset.y); + lastScrollOffset = gfx::Point(mLastContentPaintMetrics.mScrollOffset.x, + mLastContentPaintMetrics.mScrollOffset.y); } nsEventStatus status; @@ -284,8 +293,13 @@ nsEventStatus AsyncPanZoomController::OnTouchStart(const MultiTouchInput& aEvent case ANIMATING_ZOOM: // We just interrupted a double-tap animation, so force a redraw in case // this touchstart is just a tap that doesn't end up triggering a redraw. - RequestContentRepaint(); - ScheduleComposite(); + { + MonitorAutoLock monitor(mMonitor); + // Bring the resolution back in sync with the zoom. + SetZoomAndResolution(mFrameMetrics.mZoom.width); + RequestContentRepaint(); + ScheduleComposite(); + } // Fall through. case FLING: CancelAnimation(); @@ -404,6 +418,10 @@ nsEventStatus AsyncPanZoomController::OnTouchCancel(const MultiTouchInput& aEven } nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) { + if (!mAllowZoom) { + return nsEventStatus_eConsumeNoDefault; + } + SetState(PINCHING); mLastZoomFocus = aEvent.mFocusPoint; @@ -411,6 +429,10 @@ nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEve } nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) { + if (mState != PINCHING) { + return nsEventStatus_eConsumeNoDefault; + } + float prevSpan = aEvent.mPreviousSpan; if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) { // We're still handling it; we've just decided to throw this event away. @@ -422,7 +444,7 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) { { MonitorAutoLock monitor(mMonitor); - float scale = mFrameMetrics.mResolution.width; + float scale = mFrameMetrics.mZoom.width; nsIntPoint focusPoint = aEvent.mFocusPoint; float xFocusChange = (mLastZoomFocus.x - focusPoint.x) / scale, yFocusChange = (mLastZoomFocus.y - focusPoint.y) / scale; @@ -442,14 +464,14 @@ nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) { float neededDisplacementX = 0, neededDisplacementY = 0; // Only do the scaling if we won't go over 8x zoom in or out. - bool doScale = (scale < MAX_ZOOM && spanRatio > 1.0f) || (scale > MIN_ZOOM && spanRatio < 1.0f); + bool doScale = (scale < mMaxZoom && spanRatio > 1.0f) || (scale > mMinZoom && spanRatio < 1.0f); // If this zoom will take it over 8x zoom in either direction, but it's not // already there, then normalize it. - if (scale * spanRatio > MAX_ZOOM) { - spanRatio = scale / MAX_ZOOM; - } else if (scale * spanRatio < MIN_ZOOM) { - spanRatio = scale / MIN_ZOOM; + if (scale * spanRatio > mMaxZoom) { + spanRatio = scale / mMaxZoom; + } else if (scale * spanRatio < mMinZoom) { + spanRatio = scale / mMinZoom; } if (doScale) { @@ -529,7 +551,7 @@ nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEven gfx::Point point = WidgetSpaceToCompensatedViewportSpace( gfx::Point(aEvent.mPoint.x, aEvent.mPoint.y), - mFrameMetrics.mResolution.width); + mFrameMetrics.mZoom.width); mGeckoContentController->HandleSingleTap(nsIntPoint(NS_lround(point.x), NS_lround(point.y))); return nsEventStatus_eConsumeNoDefault; } @@ -545,10 +567,13 @@ nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) if (mGeckoContentController) { MonitorAutoLock monitor(mMonitor); - gfx::Point point = WidgetSpaceToCompensatedViewportSpace( - gfx::Point(aEvent.mPoint.x, aEvent.mPoint.y), - mFrameMetrics.mResolution.width); - mGeckoContentController->HandleDoubleTap(nsIntPoint(NS_lround(point.x), NS_lround(point.y))); + if (mAllowZoom) { + gfx::Point point = WidgetSpaceToCompensatedViewportSpace( + gfx::Point(aEvent.mPoint.x, aEvent.mPoint.y), + mFrameMetrics.mZoom.width); + mGeckoContentController->HandleDoubleTap(nsIntPoint(NS_lround(point.x), NS_lround(point.y))); + } + return nsEventStatus_eConsumeNoDefault; } return nsEventStatus_eIgnore; @@ -568,6 +593,10 @@ const gfx::Point AsyncPanZoomController::GetVelocityVector() { return gfx::Point(mX.GetVelocity(), mY.GetVelocity()); } +const gfx::Point AsyncPanZoomController::GetAccelerationVector() { + return gfx::Point(mX.GetAccelerationFactor(), mY.GetAccelerationFactor()); +} + void AsyncPanZoomController::StartPanning(const MultiTouchInput& aEvent) { float dx = mX.PanDistance(), dy = mY.PanDistance(); @@ -614,7 +643,7 @@ void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) { // We want to inversely scale it because when you're zoomed further in, a // larger swipe should move you a shorter distance. - float inverseScale = 1 / mFrameMetrics.mResolution.width; + float inverseScale = 1 / mFrameMetrics.mZoom.width; int32_t xDisplacement = mX.GetDisplacementForDuration(inverseScale, timeDelta); int32_t yDisplacement = mY.GetDisplacementForDuration(inverseScale, timeDelta); @@ -642,6 +671,9 @@ bool AsyncPanZoomController::DoFling(const TimeDuration& aDelta) { shouldContinueFlingY = mY.FlingApplyFrictionOrCancel(aDelta); // If we shouldn't continue the fling, let's just stop and repaint. if (!shouldContinueFlingX && !shouldContinueFlingY) { + // Bring the resolution back in sync with the zoom, in case we scaled down + // the zoom while accelerating. + SetZoomAndResolution(mFrameMetrics.mZoom.width); RequestContentRepaint(); mState = NOTHING; return false; @@ -649,7 +681,7 @@ bool AsyncPanZoomController::DoFling(const TimeDuration& aDelta) { // We want to inversely scale it because when you're zoomed further in, a // larger swipe should move you a shorter distance. - float inverseScale = 1 / mFrameMetrics.mResolution.width; + float inverseScale = 1 / mFrameMetrics.mZoom.width; ScrollBy(gfx::Point( mX.GetDisplacementForDuration(inverseScale, aDelta), @@ -669,132 +701,167 @@ void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorPa } void AsyncPanZoomController::ScrollBy(const gfx::Point& aOffset) { - gfx::Point newOffset(mFrameMetrics.mViewportScrollOffset.x + aOffset.x, - mFrameMetrics.mViewportScrollOffset.y + aOffset.y); + gfx::Point newOffset(mFrameMetrics.mScrollOffset.x + aOffset.x, + mFrameMetrics.mScrollOffset.y + aOffset.y); FrameMetrics metrics(mFrameMetrics); - metrics.mViewportScrollOffset = newOffset; + metrics.mScrollOffset = newOffset; mFrameMetrics = metrics; } void AsyncPanZoomController::SetPageRect(const gfx::Rect& aCSSPageRect) { FrameMetrics metrics = mFrameMetrics; gfx::Rect pageSize = aCSSPageRect; - float scale = mFrameMetrics.mResolution.width; + float scale = mFrameMetrics.mZoom.width; // The page rect is the css page rect scaled by the current zoom. - pageSize.ScaleRoundOut(1 / scale); + pageSize.ScaleInverseRoundOut(scale); // Round the page rect so we don't get any truncation, then get the nsIntRect // from this. metrics.mContentRect = nsIntRect(pageSize.x, pageSize.y, pageSize.width, pageSize.height); - metrics.mCSSContentRect = aCSSPageRect; + metrics.mScrollableRect = aCSSPageRect; mFrameMetrics = metrics; } void AsyncPanZoomController::ScaleWithFocus(float aScale, const nsIntPoint& aFocus) { - FrameMetrics metrics(mFrameMetrics); + float scaleFactor = aScale / mFrameMetrics.mZoom.width; - // Don't set the scale to the inputted value, but rather multiply it in. - float scaleFactor = aScale / metrics.mResolution.width, - oldScale = metrics.mResolution.width; - - metrics.mResolution.width = metrics.mResolution.height = aScale; + SetZoomAndResolution(aScale); // Force a recalculation of the page rect based on the new zoom and the // current CSS page rect (which is unchanged since it's not affected by zoom). - SetPageRect(mFrameMetrics.mCSSContentRect); + SetPageRect(mFrameMetrics.mScrollableRect); - gfx::Point scrollOffset = metrics.mViewportScrollOffset; - - scrollOffset.x += float(aFocus.x) * (scaleFactor - 1.0f) / oldScale; - scrollOffset.y += float(aFocus.y) * (scaleFactor - 1.0f) / oldScale; - - metrics.mViewportScrollOffset = scrollOffset; - - mFrameMetrics = metrics; + // If the new scale is very small, we risk multiplying in huge rounding + // errors, so don't bother adjusting the scroll offset. + if (aScale >= 0.01f) { + mFrameMetrics.mScrollOffset.x += float(aFocus.x) * (scaleFactor - 1.0f) / aScale; + mFrameMetrics.mScrollOffset.y += float(aFocus.y) * (scaleFactor - 1.0f) / aScale; + } } -bool AsyncPanZoomController::EnlargeDisplayPortAlongAxis(float aViewport, +bool AsyncPanZoomController::EnlargeDisplayPortAlongAxis(float aSkateSizeMultiplier, + double aEstimatedPaintDuration, + float aCompositionBounds, float aVelocity, + float aAcceleration, float* aDisplayPortOffset, float* aDisplayPortLength) { - const float MIN_SKATE_SIZE_MULTIPLIER = 2.0f; - const float MAX_SKATE_SIZE_MULTIPLIER = 4.0f; - if (fabsf(aVelocity) > MIN_SKATE_SPEED) { - *aDisplayPortLength = aViewport * clamped(fabsf(aVelocity), - MIN_SKATE_SIZE_MULTIPLIER, MAX_SKATE_SIZE_MULTIPLIER); - *aDisplayPortOffset = aVelocity > 0 ? 0 : aViewport - *aDisplayPortLength; + // Enlarge the area we paint. + *aDisplayPortLength = aCompositionBounds * aSkateSizeMultiplier; + // Position the area we paint such that all of the excess that extends past + // the screen is on the side towards the velocity. + *aDisplayPortOffset = aVelocity > 0 ? 0 : aCompositionBounds - *aDisplayPortLength; + + // Only compensate for acceleration when we actually have any. Otherwise + // we'll overcompensate when a user is just panning around without flinging. + if (aAcceleration > 1.01f) { + // Compensate for acceleration and how long we expect a paint to take. We + // try to predict where the viewport will be when painting has finished. + *aDisplayPortOffset += + fabsf(aAcceleration) * aVelocity * aCompositionBounds * aEstimatedPaintDuration; + // If our velocity is in the negative direction of the axis, we have to + // compensate for the fact that our scroll offset is the top-left position + // of the viewport. In this case, let's make it relative to the + // bottom-right. That way, we'll always be growing the displayport upwards + // and to the left when skating negatively. + *aDisplayPortOffset -= aVelocity < 0 ? aCompositionBounds : 0; + } return true; } return false; } -const nsIntRect AsyncPanZoomController::CalculatePendingDisplayPort() { - float scale = mFrameMetrics.mResolution.width; - nsIntRect viewport = mFrameMetrics.mViewport; - viewport.ScaleRoundIn(1 / scale); +const gfx::Rect AsyncPanZoomController::CalculatePendingDisplayPort( + const FrameMetrics& aFrameMetrics, + const gfx::Point& aVelocity, + const gfx::Point& aAcceleration, + double aEstimatedPaintDuration) +{ + // The multiplier we apply to a dimension's length if it is skating. That is, + // if it's going above MIN_SKATE_SPEED. We prefer to increase the size of the + // Y axis because it is more natural in the case that a user is reading a page + // that scrolls up/down. Note that one, both or neither of these may be used + // at any instant. + const float X_SKATE_SIZE_MULTIPLIER = 3.0f; + const float Y_SKATE_SIZE_MULTIPLIER = 3.5f; - gfx::Point scrollOffset = mFrameMetrics.mViewportScrollOffset; - gfx::Point velocity = GetVelocityVector(); + // The multiplier we apply to a dimension's length if it is stationary. We + // prefer to increase the size of the Y axis because it is more natural in the + // case that a user is reading a page that scrolls up/down. Note that one, + // both or neither of these may be used at any instant. + const float X_STATIONARY_SIZE_MULTIPLIER = 1.5f; + const float Y_STATIONARY_SIZE_MULTIPLIER = 2.5f; + + // If we don't get an estimated paint duration, we probably don't have any + // data. In this case, we're dealing with either a stationary frame or a first + // paint. In either of these cases, we can just assume it'll take 1 second to + // paint. Getting this correct is not important anyways since it's only really + // useful when accelerating, which can't be happening at this point. + double estimatedPaintDuration = + aEstimatedPaintDuration > EPSILON ? aEstimatedPaintDuration : 1.0; + + float scale = aFrameMetrics.mZoom.width; + nsIntRect compositionBounds = aFrameMetrics.mCompositionBounds; + compositionBounds.ScaleInverseRoundIn(scale); + const gfx::Rect& scrollableRect = aFrameMetrics.mScrollableRect; + + gfx::Point scrollOffset = aFrameMetrics.mScrollOffset; - // The displayport is relative to the current scroll offset. Here's a little - // diagram to make it easier to see: - // - // - - - - - // | | - // ************* - // * | | * - // - -*- @------ -*- - - // | * |=====| * | - // * |=====| * - // | * |=====| * | - // - -*- ------- -*- - - // * | | * - // ************* - // | | - // - - - - - // - // The full --- area with === inside it is the actual viewport rect, the *** area - // is the displayport, and the - - - area is an imaginary additional page on all 4 - // borders of the actual page. Notice that the displayport intersects half-way with - // each of the imaginary extra pages. The @ symbol at the top left of the - // viewport marks the current scroll offset. From the @ symbol to the far left - // and far top, it is clear that this distance is 1/4 of the displayport's - // height/width dimension. - const float STATIONARY_SIZE_MULTIPLIER = 2.0f; gfx::Rect displayPort(0, 0, - viewport.width * STATIONARY_SIZE_MULTIPLIER, - viewport.height * STATIONARY_SIZE_MULTIPLIER); + compositionBounds.width * X_STATIONARY_SIZE_MULTIPLIER, + compositionBounds.height * Y_STATIONARY_SIZE_MULTIPLIER); // If there's motion along an axis of movement, and it's above a threshold, // then we want to paint a larger area in the direction of that motion so that // it's less likely to checkerboard. bool enlargedX = EnlargeDisplayPortAlongAxis( - viewport.width, velocity.x, &displayPort.x, &displayPort.width); + X_SKATE_SIZE_MULTIPLIER, estimatedPaintDuration, + compositionBounds.width, aVelocity.x, aAcceleration.x, + &displayPort.x, &displayPort.width); bool enlargedY = EnlargeDisplayPortAlongAxis( - viewport.height, velocity.y, &displayPort.y, &displayPort.height); + Y_SKATE_SIZE_MULTIPLIER, estimatedPaintDuration, + compositionBounds.height, aVelocity.y, aAcceleration.y, + &displayPort.y, &displayPort.height); if (!enlargedX && !enlargedY) { - displayPort.x = -displayPort.width / 4; - displayPort.y = -displayPort.height / 4; + // Position the x and y such that the screen falls in the middle of the displayport. + displayPort.x = -(displayPort.width - compositionBounds.width) / 2; + displayPort.y = -(displayPort.height - compositionBounds.height) / 2; } else if (!enlargedX) { - displayPort.width = viewport.width; + displayPort.width = compositionBounds.width; } else if (!enlargedY) { - displayPort.height = viewport.height; + displayPort.height = compositionBounds.height; + } + + // If we go over the bounds when trying to predict where we will be when this + // paint finishes, move it back into the range of the CSS content rect. + // FIXME/bug 780395: Generalize this. This code is pretty hacky as it will + // probably not work at all for RTL content. This is not intended to be + // incredibly accurate; it'll just prevent the entire displayport from being + // outside the content rect (which causes bad things to happen). + if (enlargedX || enlargedY) { + if (scrollOffset.x + compositionBounds.width > scrollableRect.width) { + scrollOffset.x -= compositionBounds.width + scrollOffset.x - scrollableRect.width; + } else if (scrollOffset.x < scrollableRect.x) { + scrollOffset.x = scrollableRect.x; + } + if (scrollOffset.y + compositionBounds.height > scrollableRect.height) { + scrollOffset.y -= compositionBounds.height + scrollOffset.y - scrollableRect.height; + } else if (scrollOffset.y < scrollableRect.y) { + scrollOffset.y = scrollableRect.y; + } } gfx::Rect shiftedDisplayPort = displayPort; shiftedDisplayPort.MoveBy(scrollOffset.x, scrollOffset.y); - displayPort = shiftedDisplayPort.Intersect(mFrameMetrics.mCSSContentRect); + displayPort = shiftedDisplayPort.Intersect(aFrameMetrics.mScrollableRect); displayPort.MoveBy(-scrollOffset.x, -scrollOffset.y); - // Round the displayport so we don't get any truncation, then get the nsIntRect - // from this. - displayPort.Round(); - return nsIntRect(displayPort.x, displayPort.y, displayPort.width, displayPort.height); + return displayPort; } void AsyncPanZoomController::SetDPI(int aDPI) { @@ -812,24 +879,31 @@ void AsyncPanZoomController::ScheduleComposite() { } void AsyncPanZoomController::RequestContentRepaint() { - mFrameMetrics.mDisplayPort = CalculatePendingDisplayPort(); + mPreviousPaintStartTime = TimeStamp::Now(); - gfx::Point oldScrollOffset = mLastPaintRequestMetrics.mViewportScrollOffset, - newScrollOffset = mFrameMetrics.mViewportScrollOffset; + double estimatedPaintSum = 0.0; + for (uint32_t i = 0; i < mPreviousPaintDurations.Length(); i++) { + estimatedPaintSum += mPreviousPaintDurations[i].ToSeconds(); + } + + double estimatedPaintDuration = 0.0; + if (estimatedPaintSum > EPSILON) { + estimatedPaintDuration = estimatedPaintSum / mPreviousPaintDurations.Length(); + } + + mFrameMetrics.mDisplayPort = + CalculatePendingDisplayPort(mFrameMetrics, + GetVelocityVector(), + GetAccelerationVector(), + estimatedPaintDuration); + + gfx::Point oldScrollOffset = mLastPaintRequestMetrics.mScrollOffset, + newScrollOffset = mFrameMetrics.mScrollOffset; // If we're trying to paint what we already think is painted, discard this // request since it's a pointless paint. - nsRect oldDisplayPort = nsRect( - mLastPaintRequestMetrics.mDisplayPort.x, - mLastPaintRequestMetrics.mDisplayPort.y, - mLastPaintRequestMetrics.mDisplayPort.width, - mLastPaintRequestMetrics.mDisplayPort.height); - - gfx::Rect newDisplayPort = gfx::Rect( - mFrameMetrics.mDisplayPort.x, - mFrameMetrics.mDisplayPort.y, - mFrameMetrics.mDisplayPort.width, - mFrameMetrics.mDisplayPort.height); + gfx::Rect oldDisplayPort = mLastPaintRequestMetrics.mDisplayPort; + gfx::Rect newDisplayPort = mFrameMetrics.mDisplayPort; oldDisplayPort.MoveBy(oldScrollOffset.x, oldScrollOffset.y); newDisplayPort.MoveBy(newScrollOffset.x, newScrollOffset.y); @@ -842,13 +916,27 @@ void AsyncPanZoomController::RequestContentRepaint() { return; } - if (mContentPainterStatus == CONTENT_IDLE) { - mContentPainterStatus = CONTENT_PAINTING; - mLastPaintRequestMetrics = mFrameMetrics; - mGeckoContentController->RequestContentRepaint(mFrameMetrics); - } else { - mContentPainterStatus = CONTENT_PAINTING_AND_PAINT_PENDING; - } + // Cache the resolution since we're temporarily changing it to accomodate + // mixed resolution/zoom (normally we make them the same thing). + float actualResolution = mFrameMetrics.mResolution.width; + // Calculate the factor of acceleration based on the faster of the two axes. + float accelerationFactor = + clamped(NS_MAX(mX.GetAccelerationFactor(), mY.GetAccelerationFactor()), + float(MIN_ZOOM) / 2.0f, float(MAX_ZOOM)); + // Scale down the resolution a bit based on acceleration. + mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height = + actualResolution / accelerationFactor; + + // This message is compressed, so fire whether or not we already have a paint + // queued up. We need to know whether or not a paint was requested anyways, + // ofr the purposes of content calling window.scrollTo(). + mGeckoContentController->RequestContentRepaint(mFrameMetrics); + mLastPaintRequestMetrics = mFrameMetrics; + mWaitingForContentToPaint = true; + + // Set the resolution back to what it was for the purpose of logic control. + mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height = + actualResolution; } bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime, @@ -888,20 +976,22 @@ bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSa } double sampledPosition = gComputedTimingFunction->GetValue(animPosition); - mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height = - mEndZoomToMetrics.mResolution.width * sampledPosition + - mStartZoomToMetrics.mResolution.width * (1 - sampledPosition); + mFrameMetrics.mZoom.width = mFrameMetrics.mZoom.height = + mEndZoomToMetrics.mZoom.width * sampledPosition + + mStartZoomToMetrics.mZoom.width * (1 - sampledPosition); - mFrameMetrics.mViewportScrollOffset = gfx::Point( - mEndZoomToMetrics.mViewportScrollOffset.x * sampledPosition + - mStartZoomToMetrics.mViewportScrollOffset.x * (1 - sampledPosition), - mEndZoomToMetrics.mViewportScrollOffset.y * sampledPosition + - mStartZoomToMetrics.mViewportScrollOffset.y * (1 - sampledPosition) + mFrameMetrics.mScrollOffset = gfx::Point( + mEndZoomToMetrics.mScrollOffset.x * sampledPosition + + mStartZoomToMetrics.mScrollOffset.x * (1 - sampledPosition), + mEndZoomToMetrics.mScrollOffset.y * sampledPosition + + mStartZoomToMetrics.mScrollOffset.y * (1 - sampledPosition) ); requestAnimationFrame = true; if (aSampleTime - mAnimationStartTime >= ZOOM_TO_DURATION) { + // Bring the resolution in sync with the zoom. + SetZoomAndResolution(mFrameMetrics.mZoom.width); mState = NOTHING; RequestContentRepaint(); } @@ -916,14 +1006,14 @@ bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSa // transformed due to touches like panning or pinching. Eventually, the root // layer transform will become this during runtime, but we must wait for Gecko // to repaint. - localScaleX = mFrameMetrics.mResolution.width; - localScaleY = mFrameMetrics.mResolution.height; + localScaleX = mFrameMetrics.mZoom.width; + localScaleY = mFrameMetrics.mZoom.height; if (frame.IsScrollable()) { - metricsScrollOffset = frame.mViewportScrollOffset; + metricsScrollOffset = frame.GetScrollOffsetInLayerPixels(); } - scrollOffset = mFrameMetrics.mViewportScrollOffset; + scrollOffset = mFrameMetrics.mScrollOffset; } nsIntPoint scrollCompensation( @@ -953,13 +1043,15 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aViewportFr mLastContentPaintMetrics = aViewportFrame; - if (mContentPainterStatus != CONTENT_IDLE) { - if (mContentPainterStatus == CONTENT_PAINTING_AND_PAINT_PENDING) { - mContentPainterStatus = CONTENT_IDLE; - RequestContentRepaint(); - } else { - mContentPainterStatus = CONTENT_IDLE; + if (mWaitingForContentToPaint) { + // Remove the oldest sample we have if adding a new sample takes us over our + // desired number of samples. + if (mPreviousPaintDurations.Length() >= NUM_PAINT_DURATION_SAMPLES) { + mPreviousPaintDurations.RemoveElementAt(0); } + + mPreviousPaintDurations.AppendElement( + TimeStamp::Now() - mPreviousPaintStartTime); } else { // No paint was requested, but we got one anyways. One possible cause of this // is that content could have fired a scrollTo(). In this case, we should take @@ -972,10 +1064,7 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aViewportFr case FLING: case TOUCHING: case WAITING_LISTENERS: - // FIXME/bug 784908: Scroll offset is stored in layer pixels in the rest - // of the layers code, but we want it in CSS pixels. - mFrameMetrics.mViewportScrollOffset = - aViewportFrame.mViewportScrollOffset / aViewportFrame.mResolution.width; + mFrameMetrics.mScrollOffset = aViewportFrame.mScrollOffset; break; // Don't clobber if we're in other states. default: @@ -983,25 +1072,28 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aViewportFr } } + mWaitingForContentToPaint = false; + if (aIsFirstPaint || mFrameMetrics.IsDefault()) { - mContentPainterStatus = CONTENT_IDLE; + mPreviousPaintDurations.Clear(); mX.CancelTouch(); mY.CancelTouch(); - mFrameMetrics = aViewportFrame; - mFrameMetrics.mResolution.width = 1 / mFrameMetrics.mResolution.width; - mFrameMetrics.mResolution.height = 1 / mFrameMetrics.mResolution.height; - SetPageRect(mFrameMetrics.mCSSContentRect); - // Bug 776413/fixme: Request a repaint as soon as a page is loaded so that - // we get a larger displayport. This is very bad because we're wasting a - // paint and not initializating the displayport correctly. - RequestContentRepaint(); + // The composition bounds are not stored within the layers code, so we have + // to reset them back to what they were every time we overwrite them. + nsIntRect compositionBounds = mFrameMetrics.mCompositionBounds; + mFrameMetrics = aViewportFrame; + mFrameMetrics.mCompositionBounds = compositionBounds; + + // On first paint, we want to bring zoom back in sync with resolution. + mFrameMetrics.mZoom = mFrameMetrics.mResolution; + SetPageRect(mFrameMetrics.mScrollableRect); mState = NOTHING; - } else if (!mFrameMetrics.mCSSContentRect.IsEqualEdges(aViewportFrame.mCSSContentRect)) { - mFrameMetrics.mCSSContentRect = aViewportFrame.mCSSContentRect; - SetPageRect(mFrameMetrics.mCSSContentRect); + } else if (!mFrameMetrics.mScrollableRect.IsEqualEdges(aViewportFrame.mScrollableRect)) { + mFrameMetrics.mScrollableRect = aViewportFrame.mScrollableRect; + SetPageRect(mFrameMetrics.mScrollableRect); } } @@ -1010,11 +1102,27 @@ const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() { return mFrameMetrics; } -void AsyncPanZoomController::UpdateViewportSize(int aWidth, int aHeight) { +void AsyncPanZoomController::UpdateCompositionBounds(const nsIntRect& aCompositionBounds) { MonitorAutoLock mon(mMonitor); - FrameMetrics metrics = GetFrameMetrics(); - metrics.mViewport = nsIntRect(0, 0, aWidth, aHeight); - mFrameMetrics = metrics; + + nsIntRect oldCompositionBounds = mFrameMetrics.mCompositionBounds; + mFrameMetrics.mCompositionBounds = aCompositionBounds; + + // If the window had 0 dimensions before, or does now, we don't want to + // repaint or update the zoom since we'll run into rendering issues and/or + // divide-by-zero. This manifests itself as the screen flashing. If the page + // has gone out of view, the buffer will be cleared elsewhere anyways. + if (aCompositionBounds.width && aCompositionBounds.height && + oldCompositionBounds.width && oldCompositionBounds.height) { + // Alter the zoom such that we can see the same width of the page as we used + // to be able to. + SetZoomAndResolution(mFrameMetrics.mResolution.width * + aCompositionBounds.width / + oldCompositionBounds.width); + + // Repaint on a rotation so that our new resolution gets properly updated. + RequestContentRepaint(); + } } void AsyncPanZoomController::CancelDefaultPanZoom() { @@ -1032,27 +1140,29 @@ void AsyncPanZoomController::ZoomToRect(const gfxRect& aRect) { { MonitorAutoLock mon(mMonitor); - nsIntRect viewport = mFrameMetrics.mViewport; - gfx::Rect cssPageRect = mFrameMetrics.mCSSContentRect; - gfx::Point scrollOffset = mFrameMetrics.mViewportScrollOffset; + nsIntRect compositionBounds = mFrameMetrics.mCompositionBounds; + gfx::Rect cssPageRect = mFrameMetrics.mScrollableRect; + gfx::Point scrollOffset = mFrameMetrics.mScrollOffset; // If the rect is empty, treat it as a request to zoom out to the full page // size. if (zoomToRect.IsEmpty()) { - nsIntRect cssViewport = viewport; - cssViewport.ScaleRoundIn(1 / mFrameMetrics.mResolution.width); - cssViewport.MoveBy(nsIntPoint(NS_lround(scrollOffset.x), NS_lround(scrollOffset.y))); + // composition bounds in CSS coordinates + nsIntRect cssCompositionBounds = compositionBounds; + cssCompositionBounds.ScaleInverseRoundIn(mFrameMetrics.mZoom.width); + cssCompositionBounds.MoveBy(scrollOffset.x, scrollOffset.y); - float y = mFrameMetrics.mViewportScrollOffset.y; - float newHeight = cssViewport.height * cssPageRect.width / cssViewport.width; - float dh = cssViewport.height - newHeight; + float y = mFrameMetrics.mScrollOffset.y; + float newHeight = + cssCompositionBounds.height * cssPageRect.width / cssCompositionBounds.width; + float dh = cssCompositionBounds.height - newHeight; zoomToRect = gfx::Rect(0.0f, y + dh/2, cssPageRect.width, y + dh/2 + newHeight); } else { - float targetRatio = float(viewport.width) / float(viewport.height); + float targetRatio = float(compositionBounds.width) / float(compositionBounds.height); float rectRatio = zoomToRect.width / zoomToRect.height; if (fabsf(targetRatio - rectRatio) < EPSILON) { @@ -1072,25 +1182,27 @@ void AsyncPanZoomController::ZoomToRect(const gfxRect& aRect) { zoomToRect = zoomToRect.Intersect(cssPageRect); } - mEndZoomToMetrics.mResolution.width = mEndZoomToMetrics.mResolution.height = - NS_MIN(viewport.width / zoomToRect.width, viewport.height / zoomToRect.height); + mEndZoomToMetrics.mZoom.width = mEndZoomToMetrics.mZoom.height = + NS_MIN(compositionBounds.width / zoomToRect.width, compositionBounds.height / zoomToRect.height); - mEndZoomToMetrics.mResolution.width = mEndZoomToMetrics.mResolution.height = - clamped(mEndZoomToMetrics.mResolution.width, MIN_ZOOM, MAX_ZOOM); + mEndZoomToMetrics.mZoom.width = mEndZoomToMetrics.mZoom.height = + clamped(float(mEndZoomToMetrics.mZoom.width), + mMinZoom, + mMaxZoom); // Recalculate the zoom to rect using the new dimensions. - zoomToRect.width = viewport.width / mEndZoomToMetrics.mResolution.width; - zoomToRect.height = viewport.height / mEndZoomToMetrics.mResolution.height; + zoomToRect.width = compositionBounds.width / mEndZoomToMetrics.mZoom.width; + zoomToRect.height = compositionBounds.height / mEndZoomToMetrics.mZoom.height; // Clamp the zoom to rect to the CSS rect to make sure it fits. zoomToRect = zoomToRect.Intersect(cssPageRect); // Do one final recalculation to get the resolution. - mEndZoomToMetrics.mResolution.width = mEndZoomToMetrics.mResolution.height = - NS_MAX(viewport.width / zoomToRect.width, viewport.height / zoomToRect.height); + mEndZoomToMetrics.mZoom.width = mEndZoomToMetrics.mZoom.height = + NS_MAX(compositionBounds.width / zoomToRect.width, compositionBounds.height / zoomToRect.height); mStartZoomToMetrics = mFrameMetrics; - mEndZoomToMetrics.mViewportScrollOffset = + mEndZoomToMetrics.mScrollOffset = gfx::Point(zoomToRect.x, zoomToRect.y); mAnimationStartTime = TimeStamp::Now(); @@ -1144,5 +1256,19 @@ void AsyncPanZoomController::TimeoutTouchListeners() { ContentReceivedTouch(false); } +void AsyncPanZoomController::SetZoomAndResolution(float aScale) { + mMonitor.AssertCurrentThreadOwns(); + mFrameMetrics.mResolution.width = mFrameMetrics.mResolution.height = + mFrameMetrics.mZoom.width = mFrameMetrics.mZoom.height = aScale; +} + +void AsyncPanZoomController::UpdateZoomConstraints(bool aAllowZoom, + float aMinZoom, + float aMaxZoom) { + mAllowZoom = aAllowZoom; + mMinZoom = aMinZoom; + mMaxZoom = aMaxZoom; +} + } } diff --git a/gfx/layers/ipc/AsyncPanZoomController.h b/gfx/layers/ipc/AsyncPanZoomController.h index ab130771deb..8c817da483d 100644 --- a/gfx/layers/ipc/AsyncPanZoomController.h +++ b/gfx/layers/ipc/AsyncPanZoomController.h @@ -14,6 +14,7 @@ #include "mozilla/TimeStamp.h" #include "InputData.h" #include "Axis.h" +#include "nsContentUtils.h" #include "base/message_loop.h" @@ -97,17 +98,13 @@ public: nsInputEvent* aOutEvent); /** - * Updates the viewport size, i.e. the dimensions of the frame (not - * necessarily the screen) content will actually be rendered onto in device - * pixels for example, a subframe will not take the entire screen, but we - * still want to know how big it is in device pixels. Ideally we want to be - * using CSS pixels everywhere inside here, but in this case we need to know - * how large of a displayport to set so we use these dimensions plus some - * extra. - * - * XXX: Use nsIntRect instead. + * Updates the composition bounds, i.e. the dimensions of the final size of + * the frame this is tied to during composition onto, in device pixels. In + * general, this will just be: + * { x = 0, y = 0, width = surface.width, height = surface.height }, however + * there is no hard requirement for this. */ - void UpdateViewportSize(int aWidth, int aHeight); + void UpdateCompositionBounds(const nsIntRect& aCompositionBounds); /** * We have found a scrollable subframe, so disable our machinery until we hit @@ -134,6 +131,13 @@ public: */ void ContentReceivedTouch(bool aPreventDefault); + /** + * Updates any zoom constraints contained in the tag. + * We try to obey everything it asks us elsewhere, but here we only handle + * minimum-scale, maximum-scale, and user-scalable. + */ + void UpdateZoomConstraints(bool aAllowZoom, float aMinScale, float aMaxScale); + // -------------------------------------------------------------------------- // These methods must only be called on the compositor thread. // @@ -193,6 +197,18 @@ public: */ int GetDPI(); + /** + * Recalculates the displayport. Ideally, this should paint an area bigger + * than the composite-to dimensions so that when you scroll down, you don't + * checkerboard immediately. This includes a bunch of logic, including + * algorithms to bias painting in the direction of the velocity. + */ + static const gfx::Rect CalculatePendingDisplayPort( + const FrameMetrics& aFrameMetrics, + const gfx::Point& aVelocity, + const gfx::Point& aAcceleration, + double aEstimatedPaintDuration); + protected: /** * Internal handler for ReceiveInputEvent(). Does all the actual work. @@ -319,6 +335,11 @@ protected: */ const gfx::Point GetVelocityVector(); + /** + * Gets a vector of the acceleration factors of each axis. + */ + const gfx::Point GetAccelerationVector(); + /** * Gets a reference to the first SingleTouchData from a MultiTouchInput. This * gets only the first one and assumes the rest are either missing or not @@ -343,15 +364,6 @@ protected: */ void TrackTouch(const MultiTouchInput& aEvent); - /** - * Recalculates the displayport. Ideally, this should paint an area bigger - * than the actual screen. The viewport refers to the size of the screen, - * while the displayport is the area actually painted by Gecko. We paint - * a larger area than the screen so that when you scroll down, you don't - * checkerboard immediately. - */ - const nsIntRect CalculatePendingDisplayPort(); - /** * Attempts to enlarge the displayport along a single axis. Returns whether or * not the displayport was enlarged. This will fail in circumstances where the @@ -360,8 +372,13 @@ protected: * |aDisplayPortLength|. If enlarged, these will be updated with the new * metrics. */ - bool EnlargeDisplayPortAlongAxis(float aViewport, float aVelocity, - float* aDisplayPortOffset, float* aDisplayPortLength); + static bool EnlargeDisplayPortAlongAxis(float aSkateSizeMultiplier, + double aEstimatedPaintDuration, + float aCompositionBounds, + float aVelocity, + float aAcceleration, + float* aDisplayPortOffset, + float* aDisplayPortLength); /** * Utility function to send updated FrameMetrics to Gecko so that it can paint @@ -396,6 +413,14 @@ protected: */ void TimeoutTouchListeners(); + /** + * Utility function that sets the zoom and resolution simultaneously. This is + * useful when we want to repaint at the current zoom level. + * + * *** The monitor must be held while calling this. + */ + void SetZoomAndResolution(float aScale); + private: enum PanZoomState { NOTHING, /* no touch-start events received */ @@ -409,25 +434,6 @@ private: prevented the default actions yet. we still need to abort animations. */ }; - enum ContentPainterStatus { - // A paint may be happening, but it is not due to any action taken by this - // thread. For example, content could be invalidating itself, but - // AsyncPanZoomController has nothing to do with that. - CONTENT_IDLE, - // Set every time we dispatch a request for a repaint. When a - // ShadowLayersUpdate arrives and the metrics of this frame have changed, we - // toggle this off and assume that the paint has completed. - CONTENT_PAINTING, - // Set when we have a new displayport in the pipeline that we want to paint. - // When a ShadowLayersUpdate comes in, we dispatch a new repaint using - // mFrameMetrics.mDisplayPort (the most recent request) if this is toggled. - // This is distinct from CONTENT_PAINTING in that it signals that a repaint - // is happening, whereas this signals that we want to repaint as soon as the - // previous paint finishes. When the request is eventually made, it will use - // the most up-to-date metrics. - CONTENT_PAINTING_AND_PAINT_PENDING - }; - /** * Helper to set the current state. Holds the monitor before actually setting * it. If the monitor is already held by the current thread, it is safe to @@ -467,10 +473,19 @@ private: AxisX mX; AxisY mY; - // Protects |mFrameMetrics|, |mLastContentPaintMetrics| and |mState|. Before - // manipulating |mFrameMetrics| or |mLastContentPaintMetrics|, the monitor - // should be held. When setting |mState|, either the SetState() function can - // be used, or the monitor can be held and then |mState| updated. + // Most up-to-date constraints on zooming. These should always be reasonable + // values; for example, allowing a min zoom of 0.0 can cause very bad things + // to happen. + bool mAllowZoom; + float mMinZoom; + float mMaxZoom; + + // Protects |mFrameMetrics|, |mLastContentPaintMetrics|, |mState| and + // |mMetaViewportInfo|. Before manipulating |mFrameMetrics| or + // |mLastContentPaintMetrics|, the monitor should be held. When setting + // |mState|, either the SetState() function can be used, or the monitor can be + // held and then |mState| updated. |mMetaViewportInfo| should be updated + // using UpdateMetaViewport(). Monitor mMonitor; // The last time the compositor has sampled the content transform for this @@ -491,13 +506,20 @@ private: // |mMonitor|; that is, it should be held whenever this is updated. PanZoomState mState; + // How long it took in the past to paint after a series of previous requests. + nsTArray mPreviousPaintDurations; + + // When the last paint request started. Used to determine the duration of + // previous paints. + TimeStamp mPreviousPaintStartTime; + int mDPI; // Stores the current paint status of the frame that we're managing. Repaints // may be triggered by other things (like content doing things), in which case // this status will not be updated. It is only changed when this class // requests a repaint. - ContentPainterStatus mContentPainterStatus; + bool mWaitingForContentToPaint; // Flag used to determine whether or not we should disable handling of the // next batch of touch events. This is used for sync scrolling of subframes. diff --git a/gfx/layers/ipc/Axis.cpp b/gfx/layers/ipc/Axis.cpp index 9046998f63d..94f4b35efa8 100644 --- a/gfx/layers/ipc/Axis.cpp +++ b/gfx/layers/ipc/Axis.cpp @@ -18,7 +18,7 @@ static const float EPSILON = 0.0001f; * or we get a touch point very far away from the previous position for some * reason. */ -static const float MAX_EVENT_ACCELERATION = 0.5f; +static const float MAX_EVENT_ACCELERATION = 999.0f; /** * Amount of friction applied during flings. @@ -95,15 +95,19 @@ void Axis::StartTouch(int32_t aPos) { } float Axis::GetDisplacementForDuration(float aScale, const TimeDuration& aDelta) { - float velocityFactor = powf(ACCELERATION_MULTIPLIER, - NS_MAX(0, (mAcceleration - 4) * 3)); - float displacement = mVelocity * aScale * aDelta.ToMilliseconds() * velocityFactor; + if (fabsf(mVelocity) < VELOCITY_THRESHOLD) { + mAcceleration = 0; + } + + float accelerationFactor = GetAccelerationFactor(); + float displacement = mVelocity * aScale * aDelta.ToMilliseconds() * accelerationFactor; // If this displacement will cause an overscroll, throttle it. Can potentially // bring it to 0 even if the velocity is high. if (DisplacementWillOverscroll(displacement) != OVERSCROLL_NONE) { // No need to have a velocity along this axis anymore; it won't take us // anywhere, so we're just spinning needlessly. mVelocity = 0.0f; + mAcceleration = 0; displacement -= DisplacementWillOverscrollAmount(displacement); } return displacement; @@ -140,12 +144,12 @@ bool Axis::FlingApplyFrictionOrCancel(const TimeDuration& aDelta) { } Axis::Overscroll Axis::GetOverscroll() { - // If the current pan takes the viewport to the left of or above the current + // If the current pan takes the window to the left of or above the current // page rect. bool minus = GetOrigin() < GetPageStart(); - // If the current pan takes the viewport to the right of or below the current + // If the current pan takes the window to the right of or below the current // page rect. - bool plus = GetViewportEnd() > GetPageEnd(); + bool plus = GetCompositionEnd() > GetPageEnd(); if (minus && plus) { return OVERSCROLL_BOTH; } @@ -161,19 +165,20 @@ Axis::Overscroll Axis::GetOverscroll() { float Axis::GetExcess() { switch (GetOverscroll()) { case OVERSCROLL_MINUS: return GetOrigin() - GetPageStart(); - case OVERSCROLL_PLUS: return GetViewportEnd() - GetPageEnd(); - case OVERSCROLL_BOTH: return (GetViewportEnd() - GetPageEnd()) + (GetPageStart() - GetOrigin()); + case OVERSCROLL_PLUS: return GetCompositionEnd() - GetPageEnd(); + case OVERSCROLL_BOTH: return (GetCompositionEnd() - GetPageEnd()) + + (GetPageStart() - GetOrigin()); default: return 0; } } Axis::Overscroll Axis::DisplacementWillOverscroll(int32_t aDisplacement) { - // If the current pan plus a displacement takes the viewport to the left of or + // If the current pan plus a displacement takes the window to the left of or // above the current page rect. bool minus = GetOrigin() + aDisplacement < GetPageStart(); - // If the current pan plus a displacement takes the viewport to the right of or + // If the current pan plus a displacement takes the window to the right of or // below the current page rect. - bool plus = GetViewportEnd() + aDisplacement > GetPageEnd(); + bool plus = GetCompositionEnd() + aDisplacement > GetPageEnd(); if (minus && plus) { return OVERSCROLL_BOTH; } @@ -189,7 +194,7 @@ Axis::Overscroll Axis::DisplacementWillOverscroll(int32_t aDisplacement) { float Axis::DisplacementWillOverscrollAmount(int32_t aDisplacement) { switch (DisplacementWillOverscroll(aDisplacement)) { case OVERSCROLL_MINUS: return (GetOrigin() + aDisplacement) - GetPageStart(); - case OVERSCROLL_PLUS: return (GetViewportEnd() + aDisplacement) - GetPageEnd(); + case OVERSCROLL_PLUS: return (GetCompositionEnd() + aDisplacement) - GetPageEnd(); // Don't handle overscrolled in both directions; a displacement can't cause // this, it must have already been zoomed out too far. default: return 0; @@ -201,7 +206,7 @@ Axis::Overscroll Axis::ScaleWillOverscroll(float aScale, int32_t aFocus) { bool both = ScaleWillOverscrollBothSides(aScale); bool minus = originAfterScale < GetPageStart() * aScale; - bool plus = (originAfterScale + GetViewportLength()) > GetPageEnd() * aScale; + bool plus = (originAfterScale + GetCompositionLength()) > GetPageEnd() * aScale; if ((minus && plus) || both) { return OVERSCROLL_BOTH; @@ -219,7 +224,8 @@ float Axis::ScaleWillOverscrollAmount(float aScale, int32_t aFocus) { float originAfterScale = (GetOrigin() + aFocus) * aScale - aFocus; switch (ScaleWillOverscroll(aScale, aFocus)) { case OVERSCROLL_MINUS: return originAfterScale - GetPageStart() * aScale; - case OVERSCROLL_PLUS: return (originAfterScale + GetViewportLength()) - GetPageEnd() * aScale; + case OVERSCROLL_PLUS: return (originAfterScale + GetCompositionLength()) - + NS_lround(GetPageEnd() * aScale); // Don't handle OVERSCROLL_BOTH. Client code is expected to deal with it. default: return 0; } @@ -229,8 +235,12 @@ float Axis::GetVelocity() { return mVelocity; } -float Axis::GetViewportEnd() { - return GetOrigin() + GetViewportLength(); +float Axis::GetAccelerationFactor() { + return powf(ACCELERATION_MULTIPLIER, NS_MAX(0, (mAcceleration - 4) * 3)); +} + +float Axis::GetCompositionEnd() { + return GetOrigin() + GetCompositionLength(); } float Axis::GetPageEnd() { @@ -238,40 +248,44 @@ float Axis::GetPageEnd() { } float Axis::GetOrigin() { - gfx::Point origin = mAsyncPanZoomController->GetFrameMetrics().mViewportScrollOffset; + gfx::Point origin = mAsyncPanZoomController->GetFrameMetrics().mScrollOffset; return GetPointOffset(origin); } -float Axis::GetViewportLength() { - nsIntRect viewport = mAsyncPanZoomController->GetFrameMetrics().mViewport; - gfx::Rect scaledViewport = gfx::Rect(viewport.x, viewport.y, viewport.width, viewport.height); - scaledViewport.ScaleRoundIn(1 / mAsyncPanZoomController->GetFrameMetrics().mResolution.width); - return GetRectLength(scaledViewport); +float Axis::GetCompositionLength() { + nsIntRect compositionBounds = + mAsyncPanZoomController->GetFrameMetrics().mCompositionBounds; + gfx::Rect scaledCompositionBounds = + gfx::Rect(compositionBounds.x, compositionBounds.y, + compositionBounds.width, compositionBounds.height); + scaledCompositionBounds.ScaleInverseRoundIn( + mAsyncPanZoomController->GetFrameMetrics().mZoom.width); + return GetRectLength(scaledCompositionBounds); } float Axis::GetPageStart() { - gfx::Rect pageRect = mAsyncPanZoomController->GetFrameMetrics().mCSSContentRect; + gfx::Rect pageRect = mAsyncPanZoomController->GetFrameMetrics().mScrollableRect; return GetRectOffset(pageRect); } float Axis::GetPageLength() { - gfx::Rect pageRect = mAsyncPanZoomController->GetFrameMetrics().mCSSContentRect; + gfx::Rect pageRect = mAsyncPanZoomController->GetFrameMetrics().mScrollableRect; return GetRectLength(pageRect); } bool Axis::ScaleWillOverscrollBothSides(float aScale) { const FrameMetrics& metrics = mAsyncPanZoomController->GetFrameMetrics(); - gfx::Rect cssContentRect = metrics.mCSSContentRect; + gfx::Rect cssContentRect = metrics.mScrollableRect; - float currentScale = metrics.mResolution.width; - gfx::Rect viewport = gfx::Rect(metrics.mViewport.x, - metrics.mViewport.y, - metrics.mViewport.width, - metrics.mViewport.height); - viewport.ScaleRoundIn(1 / (currentScale * aScale)); + float currentScale = metrics.mZoom.width; + nsIntRect compositionBounds = metrics.mCompositionBounds; + gfx::Rect scaledCompositionBounds = + gfx::Rect(compositionBounds.x, compositionBounds.y, + compositionBounds.width, compositionBounds.height); + scaledCompositionBounds.ScaleInverseRoundIn(currentScale * aScale); - return GetRectLength(cssContentRect) < GetRectLength(viewport); + return GetRectLength(cssContentRect) < GetRectLength(scaledCompositionBounds); } AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController) diff --git a/gfx/layers/ipc/Axis.h b/gfx/layers/ipc/Axis.h index 9a9cbe32383..59bd313b19c 100644 --- a/gfx/layers/ipc/Axis.h +++ b/gfx/layers/ipc/Axis.h @@ -116,6 +116,12 @@ public: */ float GetExcess(); + /** + * Gets the factor of acceleration applied to the velocity, based on the + * amount of flings that have been done successively. + */ + float GetAccelerationFactor(); + /** * Gets the raw velocity of this axis at this moment. */ @@ -165,10 +171,10 @@ public: bool ScaleWillOverscrollBothSides(float aScale); float GetOrigin(); - float GetViewportLength(); + float GetCompositionLength(); float GetPageStart(); float GetPageLength(); - float GetViewportEnd(); + float GetCompositionEnd(); float GetPageEnd(); virtual float GetPointOffset(const gfx::Point& aPoint) = 0; diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index 766ee88f56d..af6251adc2b 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -467,8 +467,8 @@ private: } const FrameMetrics& fm = c->GetFrameMetrics(); gfx3DMatrix m(aContainer->GetTransform()); - m.Translate(gfxPoint3D(-fm.mViewportScrollOffset.x, - -fm.mViewportScrollOffset.y, 0)); + m.Translate(gfxPoint3D(-fm.GetScrollOffsetInLayerPixels().x, + -fm.GetScrollOffsetInLayerPixels().y, 0)); // The transform already takes the resolution scale into account. Since we // will apply the resolution scale again when computing the effective @@ -787,30 +787,43 @@ CompositorParent::TransformShadowTree(TimeStamp aCurrentFrame) float rootScaleX = rootTransform.GetXScale(), rootScaleY = rootTransform.GetYScale(); + // The ratio of layers pixels to device pixels. The Java + // compositor wants to see values in units of device pixels, so we + // map our FrameMetrics values to that space. This is not exposed + // as a FrameMetrics helper because it's a deprecated conversion. + float devPixelRatioX = 1 / rootScaleX, devPixelRatioY = 1 / rootScaleY; + + gfx::Point scrollOffsetLayersPixels(metrics.GetScrollOffsetInLayerPixels()); + nsIntPoint scrollOffsetDevPixels( + NS_lround(scrollOffsetLayersPixels.x * devPixelRatioX), + NS_lround(scrollOffsetLayersPixels.y * devPixelRatioY)); if (mIsFirstPaint) { mContentRect = metrics.mContentRect; - const gfx::Point& scrollOffset = metrics.mViewportScrollOffset; - SetFirstPaintViewport(nsIntPoint(NS_lround(scrollOffset.x), - NS_lround(scrollOffset.y)), + SetFirstPaintViewport(scrollOffsetDevPixels, 1/rootScaleX, mContentRect, - metrics.mCSSContentRect); + metrics.mScrollableRect); mIsFirstPaint = false; } else if (!metrics.mContentRect.IsEqualEdges(mContentRect)) { mContentRect = metrics.mContentRect; - SetPageRect(metrics.mCSSContentRect); + SetPageRect(metrics.mScrollableRect); } // We synchronise the viewport information with Java after sending the above // notifications, so that Java can take these into account in its response. // Calculate the absolute display port to send to Java - nsIntRect displayPort = metrics.mDisplayPort; - gfx::Point scrollOffset = metrics.mViewportScrollOffset; - displayPort.x += NS_lround(scrollOffset.x); - displayPort.y += NS_lround(scrollOffset.y); + gfx::Rect displayPortLayersPixels(metrics.mDisplayPort); + nsIntRect displayPortDevPixels( + NS_lround(displayPortLayersPixels.x * devPixelRatioX), + NS_lround(displayPortLayersPixels.y * devPixelRatioY), + NS_lround(displayPortLayersPixels.width * devPixelRatioX), + NS_lround(displayPortLayersPixels.height * devPixelRatioY)); - SyncViewportInfo(displayPort, 1/rootScaleX, mLayersUpdated, + displayPortDevPixels.x += scrollOffsetDevPixels.x; + displayPortDevPixels.y += scrollOffsetDevPixels.y; + + SyncViewportInfo(displayPortDevPixels, 1/rootScaleX, mLayersUpdated, mScrollOffset, mXScale, mYScale); mLayersUpdated = false; @@ -825,8 +838,7 @@ CompositorParent::TransformShadowTree(TimeStamp aCurrentFrame) nsIntPoint metricsScrollOffset(0, 0); if (metrics.IsScrollable()) { - metricsScrollOffset = - nsIntPoint(NS_lround(scrollOffset.x), NS_lround(scrollOffset.y)); + metricsScrollOffset = scrollOffsetDevPixels; } nsIntPoint scrollCompensation( diff --git a/gfx/layers/opengl/ReusableTileStoreOGL.cpp b/gfx/layers/opengl/ReusableTileStoreOGL.cpp index e153a58919a..4be94b40c74 100644 --- a/gfx/layers/opengl/ReusableTileStoreOGL.cpp +++ b/gfx/layers/opengl/ReusableTileStoreOGL.cpp @@ -37,7 +37,9 @@ ReusableTileStoreOGL::InvalidateTiles(TiledThebesLayerOGL* aLayer, // This will be incorrect when the transform involves rotation, but // it'd be quite hard to retain invalid tiles correctly in this // situation anyway. - renderBounds = parent->GetEffectiveTransform().TransformBounds(gfxRect(metrics.mDisplayPort)); + renderBounds = parent->GetEffectiveTransform().TransformBounds( + gfxRect(metrics.mDisplayPort.x, metrics.mDisplayPort.y, + metrics.mDisplayPort.width, metrics.mDisplayPort.height)); break; } } @@ -217,10 +219,14 @@ ReusableTileStoreOGL::DrawTiles(TiledThebesLayerOGL* aLayer, scrollableLayer = parent; if (!parentMetrics.mDisplayPort.IsEmpty() && scrollableLayer) { displayPort = parent->GetEffectiveTransform(). - TransformBounds(gfxRect(parentMetrics.mDisplayPort)); + TransformBounds(gfxRect( + parentMetrics.mDisplayPort.x, parentMetrics.mDisplayPort.y, + parentMetrics.mDisplayPort.width, parentMetrics.mDisplayPort.height)); const FrameMetrics& metrics = scrollableLayer->GetFrameMetrics(); const nsIntSize& contentSize = metrics.mContentRect.Size(); - const gfx::Point& scrollOffset = metrics.mViewportScrollOffset; + gfx::Point scrollOffset = + gfx::Point(metrics.mScrollOffset.x * metrics.LayersPixelsPerCSSPixel().width, + metrics.mScrollOffset.y * metrics.LayersPixelsPerCSSPixel().height); const nsIntPoint& contentOrigin = metrics.mContentRect.TopLeft() - nsIntPoint(NS_lround(scrollOffset.x), NS_lround(scrollOffset.y)); gfxRect contentRect = gfxRect(contentOrigin.x, contentOrigin.y, diff --git a/gfx/skia/src/effects/gradients/SkGradientShader.cpp b/gfx/skia/src/effects/gradients/SkGradientShader.cpp index 79e7202d66b..45de4e306f4 100644 --- a/gfx/skia/src/effects/gradients/SkGradientShader.cpp +++ b/gfx/skia/src/effects/gradients/SkGradientShader.cpp @@ -473,15 +473,17 @@ const SkPMColor* SkGradientShaderBase::getCache32() const { } // Write the clamp colours into the first and last entries of fCache32 - fCache32[kCache32ClampLower] = SkPackARGB32(fCacheAlpha, - SkColorGetR(fOrigColors[0]), - SkColorGetG(fOrigColors[0]), - SkColorGetB(fOrigColors[0])); + fCache32[kCache32ClampLower] = SkPremultiplyARGBInline(SkMulDiv255Round(SkColorGetA(fOrigColors[0]), + fCacheAlpha), + SkColorGetR(fOrigColors[0]), + SkColorGetG(fOrigColors[0]), + SkColorGetB(fOrigColors[0])); - fCache32[kCache32ClampUpper] = SkPackARGB32(fCacheAlpha, - SkColorGetR(fOrigColors[fColorCount - 1]), - SkColorGetG(fOrigColors[fColorCount - 1]), - SkColorGetB(fOrigColors[fColorCount - 1])); + fCache32[kCache32ClampUpper] = SkPremultiplyARGBInline(SkMulDiv255Round(SkColorGetA(fOrigColors[fColorCount - 1]), + fCacheAlpha), + SkColorGetR(fOrigColors[fColorCount - 1]), + SkColorGetG(fOrigColors[fColorCount - 1]), + SkColorGetB(fOrigColors[fColorCount - 1])); return fCache32; } diff --git a/ipc/glue/IPCMessageUtils.h b/ipc/glue/IPCMessageUtils.h index 7115b3fab48..89e81b1e82b 100644 --- a/ipc/glue/IPCMessageUtils.h +++ b/ipc/glue/IPCMessageUtils.h @@ -960,25 +960,31 @@ struct ParamTraits static void Write(Message* aMsg, const paramType& aParam) { - WriteParam(aMsg, aParam.mCSSContentRect); + WriteParam(aMsg, aParam.mScrollableRect); WriteParam(aMsg, aParam.mViewport); WriteParam(aMsg, aParam.mContentRect); - WriteParam(aMsg, aParam.mViewportScrollOffset); + WriteParam(aMsg, aParam.mScrollOffset); WriteParam(aMsg, aParam.mDisplayPort); + WriteParam(aMsg, aParam.mCompositionBounds); WriteParam(aMsg, aParam.mScrollId); WriteParam(aMsg, aParam.mResolution); + WriteParam(aMsg, aParam.mZoom); + WriteParam(aMsg, aParam.mDevPixelsPerCSSPixel); WriteParam(aMsg, aParam.mMayHaveTouchListeners); } static bool Read(const Message* aMsg, void** aIter, paramType* aResult) { - return (ReadParam(aMsg, aIter, &aResult->mCSSContentRect) && + return (ReadParam(aMsg, aIter, &aResult->mScrollableRect) && ReadParam(aMsg, aIter, &aResult->mViewport) && ReadParam(aMsg, aIter, &aResult->mContentRect) && - ReadParam(aMsg, aIter, &aResult->mViewportScrollOffset) && + ReadParam(aMsg, aIter, &aResult->mScrollOffset) && ReadParam(aMsg, aIter, &aResult->mDisplayPort) && + ReadParam(aMsg, aIter, &aResult->mCompositionBounds) && ReadParam(aMsg, aIter, &aResult->mScrollId) && ReadParam(aMsg, aIter, &aResult->mResolution) && + ReadParam(aMsg, aIter, &aResult->mZoom) && + ReadParam(aMsg, aIter, &aResult->mDevPixelsPerCSSPixel) && ReadParam(aMsg, aIter, &aResult->mMayHaveTouchListeners)); } }; diff --git a/js/src/ion/IonBuilder.cpp b/js/src/ion/IonBuilder.cpp index a87b89788da..fd7f226386f 100644 --- a/js/src/ion/IonBuilder.cpp +++ b/js/src/ion/IonBuilder.cpp @@ -5914,24 +5914,7 @@ IonBuilder::jsop_getprop(HandlePropertyName name) return makeCallBarrier(getter, 0, false, types, barrier); } - // If the input is guaranteed to be an object, then we want - // to specialize it via an slot load or an IC. If it's - // guaranteed to be an object or NULL, then we do the same - // thing except prefixed by a fallible unbox. - bool targetIsObject = (unary.ival == MIRType_Object); - - if (!targetIsObject) { - if (unaryTypes.inTypes->objectOrSentinel()) { - // Fallibly unwrap the object before getprop. Getprop - // on null or undefined will cause exception anyway. - MUnbox *unbox = MUnbox::New(obj, MIRType_Object, MUnbox::Fallible); - current->add(unbox); - obj = unbox; - targetIsObject = true; - } - } - - if (targetIsObject) { + if (unary.ival == MIRType_Object) { MIRType rvalType = MIRType_Value; if (!barrier && !IsNullOrUndefined(unary.rval)) rvalType = unary.rval; diff --git a/js/src/ion/TypeOracle.cpp b/js/src/ion/TypeOracle.cpp index 3c285dc11a5..0f102ab96e5 100644 --- a/js/src/ion/TypeOracle.cpp +++ b/js/src/ion/TypeOracle.cpp @@ -373,16 +373,6 @@ TypeInferenceOracle::elementReadGeneric(JSScript *script, jsbytecode *pc, bool * *cacheable = (obj == MIRType_Object && (id == MIRType_Value || id == MIRType_Int32 || id == MIRType_String)); - - // Turn off cacheing if the element is int32 and we've seen non-native objects as the target - // of this getelem. - if (*cacheable) { - if (id == MIRType_Int32) { - if (script->analysis()->getCode(pc).nonNativeGetElement) - *cacheable = false; - } - } - if (*cacheable) *monitorResult = (id == MIRType_String || script->analysis()->getCode(pc).getStringElement); else diff --git a/js/src/jsanalyze.h b/js/src/jsanalyze.h index 8456aebe217..1d37f696853 100644 --- a/js/src/jsanalyze.h +++ b/js/src/jsanalyze.h @@ -111,11 +111,10 @@ class Bytecode * Dynamically observed state about the execution of this opcode. These are * hints about the script for use during compilation. */ - bool arrayWriteHole: 1; /* SETELEM which has written to an array hole. */ - bool getStringElement:1; /* GETELEM which has accessed string properties. */ - bool nonNativeGetElement:1; /* GETELEM on a non-native object. */ - bool accessGetter: 1; /* Property read on a shape with a getter hook. */ - bool notIdempotent: 1; /* Don't use an idempotent cache for this property read. */ + bool arrayWriteHole: 1; /* SETELEM which has written to an array hole. */ + bool getStringElement:1; /* GETELEM which has accessed string properties. */ + bool accessGetter: 1; /* Property read on a shape with a getter hook. */ + bool notIdempotent: 1; /* Don't use an idempotent cache for this property read. */ /* Stack depth before this opcode. */ uint32_t stackDepth; diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index 11a2f8ff572..f9acb63ca8c 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -571,19 +571,6 @@ class StackTypeSet : public TypeSet /* Whether this value may be an object. */ bool maybeObject() { return unknownObject() || baseObjectCount() > 0; } - /* - * Whether this typeset represents a potentially sentineled object value: - * where the value may be an object, but maybe potentially null or undefined. - * This returns false in situations where the value cannot ever be an object. - */ - bool objectOrSentinel() { - TypeFlags flags = TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | TYPE_FLAG_ANYOBJECT; - if (baseFlags() & (~flags & TYPE_FLAG_BASE_MASK)) - return false; - - return hasAnyFlag(TYPE_FLAG_ANYOBJECT) || baseObjectCount() > 0; - } - /* Whether the type set contains objects with any of a set of flags. */ bool hasObjectFlags(JSContext *cx, TypeObjectFlags flags); diff --git a/js/src/jsinterpinlines.h b/js/src/jsinterpinlines.h index bb47f97d909..dc84c572f7a 100644 --- a/js/src/jsinterpinlines.h +++ b/js/src/jsinterpinlines.h @@ -686,19 +686,6 @@ GetObjectElementOperation(JSContext *cx, JSOp op, HandleObject obj, const Value } #endif - bool updateAnalysis = false; - RootedScript script(cx, NULL); - jsbytecode *pc = NULL; - if (!cx->fp()->beginsIonActivation()) { - // Don't call GetPcScript from inside Ion since it's expensive. - types::TypeScript::GetPcScript(cx, &script, &pc); - if (script->hasAnalysis()) - updateAnalysis = true; - } - - if (updateAnalysis && !obj->isNative()) - script->analysis()->getCode(pc).nonNativeGetElement = true; - uint32_t index; if (IsDefinitelyIndex(rref, &index)) { do { @@ -716,8 +703,16 @@ GetObjectElementOperation(JSContext *cx, JSOp op, HandleObject obj, const Value return false; } while(0); } else { - if (updateAnalysis) - script->analysis()->getCode(pc).getStringElement = true; + if (!cx->fp()->beginsIonActivation()) { + // Don't update getStringElement if called from Ion code, since + // ion::GetPcScript is expensive. + RootedScript script(cx); + jsbytecode *pc; + types::TypeScript::GetPcScript(cx, &script, &pc); + + if (script->hasAnalysis()) + script->analysis()->getCode(pc).getStringElement = true; + } SpecialId special; res.set(rref); diff --git a/js/xpconnect/src/dictionary_helper_gen.conf b/js/xpconnect/src/dictionary_helper_gen.conf index e3fdfa1df19..46b87df4e5d 100644 --- a/js/xpconnect/src/dictionary_helper_gen.conf +++ b/js/xpconnect/src/dictionary_helper_gen.conf @@ -21,7 +21,8 @@ dictionaries = [ [ 'CameraRegion', 'nsIDOMCameraManager.idl' ], [ 'CameraPosition', 'nsIDOMCameraManager.idl' ], [ 'CameraSelector', 'nsIDOMCameraManager.idl' ], - [ 'CameraPictureOptions', 'nsIDOMCameraManager.idl' ] + [ 'CameraPictureOptions', 'nsIDOMCameraManager.idl' ], + [ 'CameraRecordingOptions', 'nsIDOMCameraManager.idl' ] ] # include file names diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 487d3e7d63b..8950a74498c 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -575,6 +575,7 @@ static void RecordFrameMetrics(nsIFrame* aForFrame, bool aMayHaveTouchListeners) { nsPresContext* presContext = aForFrame->PresContext(); int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel(); + float auPerCSSPixel = nsPresContext::AppUnitsPerCSSPixel(); nsIntRect visible = aVisibleRect.ScaleToNearestPixels( aContainerParameters.mXScale, aContainerParameters.mYScale, auPerDevPixel); @@ -582,12 +583,18 @@ static void RecordFrameMetrics(nsIFrame* aForFrame, FrameMetrics metrics; - metrics.mViewport = aViewport.ScaleToNearestPixels( - aContainerParameters.mXScale, aContainerParameters.mYScale, auPerDevPixel); + metrics.mViewport = mozilla::gfx::Rect( + NSAppUnitsToDoublePixels(aViewport.x, auPerDevPixel), + NSAppUnitsToDoublePixels(aViewport.y, auPerDevPixel), + NSAppUnitsToDoublePixels(aViewport.width, auPerDevPixel), + NSAppUnitsToDoublePixels(aViewport.height, auPerDevPixel)); if (aDisplayPort) { - metrics.mDisplayPort = aDisplayPort->ScaleToNearestPixels( - aContainerParameters.mXScale, aContainerParameters.mYScale, auPerDevPixel); + metrics.mDisplayPort = mozilla::gfx::Rect( + NSAppUnitsToDoublePixels(aDisplayPort->x, auPerDevPixel), + NSAppUnitsToDoublePixels(aDisplayPort->y, auPerDevPixel), + NSAppUnitsToDoublePixels(aDisplayPort->width, auPerDevPixel), + NSAppUnitsToDoublePixels(aDisplayPort->height, auPerDevPixel)); } nsIScrollableFrame* scrollableFrame = nullptr; @@ -598,7 +605,7 @@ static void RecordFrameMetrics(nsIFrame* aForFrame, nsRect contentBounds = scrollableFrame->GetScrollRange(); contentBounds.width += scrollableFrame->GetScrollPortRect().width; contentBounds.height += scrollableFrame->GetScrollPortRect().height; - metrics.mCSSContentRect = + metrics.mScrollableRect = mozilla::gfx::Rect(nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.x), nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.y), nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.width), @@ -606,13 +613,13 @@ static void RecordFrameMetrics(nsIFrame* aForFrame, metrics.mContentRect = contentBounds.ScaleToNearestPixels( aContainerParameters.mXScale, aContainerParameters.mYScale, auPerDevPixel); nsPoint scrollPosition = scrollableFrame->GetScrollPosition(); - metrics.mViewportScrollOffset = mozilla::gfx::Point( - NSAppUnitsToDoublePixels(scrollPosition.x, auPerDevPixel) * aContainerParameters.mXScale, - NSAppUnitsToDoublePixels(scrollPosition.y, auPerDevPixel) * aContainerParameters.mYScale); + metrics.mScrollOffset = mozilla::gfx::Point( + NSAppUnitsToDoublePixels(scrollPosition.x, auPerCSSPixel), + NSAppUnitsToDoublePixels(scrollPosition.y, auPerCSSPixel)); } else { nsRect contentBounds = aForFrame->GetRect(); - metrics.mCSSContentRect = + metrics.mScrollableRect = mozilla::gfx::Rect(nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.x), nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.y), nsPresContext::AppUnitsToFloatCSSPixels(contentBounds.width), @@ -626,6 +633,8 @@ static void RecordFrameMetrics(nsIFrame* aForFrame, nsIPresShell* presShell = presContext->GetPresShell(); metrics.mResolution = gfxSize(presShell->GetXResolution(), presShell->GetYResolution()); + metrics.mDevPixelsPerCSSPixel = auPerCSSPixel / auPerDevPixel; + metrics.mMayHaveTouchListeners = aMayHaveTouchListeners; aRoot->SetFrameMetrics(metrics); @@ -1070,8 +1079,10 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder, } } + nsRect viewport(aBuilder->ToReferenceFrame(aForFrame), aForFrame->GetSize()); + RecordFrameMetrics(aForFrame, rootScrollFrame, - root, mVisibleRect, mVisibleRect, + root, mVisibleRect, viewport, (usingDisplayport ? &displayport : nullptr), id, containerParameters, mayHaveTouchListeners); if (usingDisplayport && diff --git a/layout/ipc/RenderFrameParent.cpp b/layout/ipc/RenderFrameParent.cpp index 28fd8829dde..9fbff4f416e 100644 --- a/layout/ipc/RenderFrameParent.cpp +++ b/layout/ipc/RenderFrameParent.cpp @@ -158,7 +158,7 @@ ComputeShadowTreeTransform(nsIFrame* aContainerFrame, nsIntPoint scrollOffset = aConfig.mScrollOffset.ToNearestPixels(auPerDevPixel); // metricsScrollOffset is in layer coordinates. - gfx::Point metricsScrollOffset = aMetrics->mViewportScrollOffset; + gfx::Point metricsScrollOffset = aMetrics->GetScrollOffsetInLayerPixels(); nsIntPoint roundedMetricsScrollOffset = nsIntPoint(NS_lround(metricsScrollOffset.x), NS_lround(metricsScrollOffset.y)); @@ -217,7 +217,9 @@ BuildListForLayer(Layer* aLayer, nsRect bounds; { nscoord auPerDevPixel = aSubdocFrame->PresContext()->AppUnitsPerDevPixel(); - bounds = metrics->mViewport.ToAppUnits(auPerDevPixel); + gfx::Rect viewport = metrics->mViewport; + bounds = nsIntRect(viewport.x, viewport.y, + viewport.width, viewport.height).ToAppUnits(auPerDevPixel); ApplyTransform(bounds, tmpTransform, auPerDevPixel); } @@ -362,6 +364,7 @@ BuildViewMap(ViewMap& oldContentViews, ViewMap& newContentViews, if (metrics.IsScrollable()) { nscoord auPerDevPixel = aFrameLoader->GetPrimaryFrameOfOwningContent() ->PresContext()->AppUnitsPerDevPixel(); + nscoord auPerCSSPixel = auPerDevPixel * metrics.mDevPixelsPerCSSPixel; nsContentView* view = FindViewForId(oldContentViews, scrollId); if (view) { // View already exists. Be sure to propagate scales for any values @@ -392,8 +395,8 @@ BuildViewMap(ViewMap& oldContentViews, ViewMap& newContentViews, // The default scale is 1, so no need to propagate scale down. ViewConfig config; config.mScrollOffset = nsPoint( - NSIntPixelsToAppUnits(metrics.mViewportScrollOffset.x, auPerDevPixel) * aXScale, - NSIntPixelsToAppUnits(metrics.mViewportScrollOffset.y, auPerDevPixel) * aYScale); + NSIntPixelsToAppUnits(metrics.mScrollOffset.x, auPerCSSPixel) * aXScale, + NSIntPixelsToAppUnits(metrics.mScrollOffset.y, auPerCSSPixel) * aYScale); view = new nsContentView(aFrameLoader, scrollId, config); view->mParentScaleX = aAccConfigXScale; view->mParentScaleY = aAccConfigYScale; @@ -728,7 +731,8 @@ void RenderFrameParent::NotifyDimensionsChanged(int width, int height) { if (mPanZoomController) { - mPanZoomController->UpdateViewportSize(width, height); + mPanZoomController->UpdateCompositionBounds( + nsIntRect(0, 0, width, height)); } } @@ -909,6 +913,14 @@ RenderFrameParent::ContentReceivedTouch(bool aPreventDefault) } } +void +RenderFrameParent::UpdateZoomConstraints(bool aAllowZoom, float aMinZoom, float aMaxZoom) +{ + if (mPanZoomController) { + mPanZoomController->UpdateZoomConstraints(aAllowZoom, aMinZoom, aMaxZoom); + } +} + } // namespace layout } // namespace mozilla diff --git a/layout/ipc/RenderFrameParent.h b/layout/ipc/RenderFrameParent.h index afb6b78afb7..dd13148f047 100644 --- a/layout/ipc/RenderFrameParent.h +++ b/layout/ipc/RenderFrameParent.h @@ -101,6 +101,8 @@ public: void ContentReceivedTouch(bool aPreventDefault); + void UpdateZoomConstraints(bool aAllowZoom, float aMinZoom, float aMaxZoom); + protected: void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE; diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 45cf3b067e1..8900d58b3e0 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -156,6 +156,7 @@ abstract public class GeckoApp public static int mOrientation; private boolean mIsRestoringActivity; private String mCurrentResponse = ""; + public static boolean sIsUsingCustomProfile = false; private PromptService mPromptService; private Favicons mFavicons; @@ -1554,6 +1555,7 @@ abstract public class GeckoApp if (profileName == null) profileName = "default"; } + GeckoApp.sIsUsingCustomProfile = true; } if (profileName != null || profilePath != null) { mProfile = GeckoProfile.get(this, profileName, profilePath); @@ -2252,7 +2254,8 @@ abstract public class GeckoApp ProfileMigrator profileMigrator = new ProfileMigrator(app); // Do a migration run on the first start after an upgrade. - if (!profileMigrator.hasMigrationRun()) { + if (!GeckoApp.sIsUsingCustomProfile && + !profileMigrator.hasMigrationRun()) { // Show the "Setting up Fennec" screen if this takes // a while. final SetupScreen setupScreen = new SetupScreen(app); @@ -2296,7 +2299,7 @@ abstract public class GeckoApp private void checkMigrateSync() { final File profileDir = getProfile().getDir(); - if (profileDir != null) { + if (!GeckoApp.sIsUsingCustomProfile && profileDir != null) { final GeckoApp app = GeckoApp.mAppContext; ProfileMigrator profileMigrator = new ProfileMigrator(app); if (!profileMigrator.hasSyncMigrated()) { diff --git a/mobile/android/base/GeckoProfile.java b/mobile/android/base/GeckoProfile.java index f04d957ed04..16fc0fa8a78 100644 --- a/mobile/android/base/GeckoProfile.java +++ b/mobile/android/base/GeckoProfile.java @@ -148,7 +148,8 @@ public final class GeckoProfile { try { // Check for old profiles that may need migration. ProfileMigrator profileMigrator = new ProfileMigrator(mContext); - if (!profileMigrator.isProfileMoved()) { + if (!GeckoApp.sIsUsingCustomProfile && + !profileMigrator.isProfileMoved()) { Log.i(LOGTAG, "New installation or update, checking for old profiles."); profileMigrator.launchMoveProfile(); } diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp index 3bc5b5e38c9..e87094db13d 100644 --- a/netwerk/dns/nsDNSService2.cpp +++ b/netwerk/dns/nsDNSService2.cpp @@ -687,6 +687,15 @@ nsDNSService::Resolve(const nsACString &hostname, uint32_t flags, nsIDNSRecord **result) { + NS_WARNING("Do not use synchronous DNS resolution! This API may be removed soon."); + + // We will not allow this to be called on the main thread. This is transitional + // and a bit of a test for removing the synchronous API entirely. + if (NS_IsMainThread()) { + NS_ERROR("Synchronous DNS resolve failing - not allowed on the main thread!"); + return NS_ERROR_FAILURE; + } + // grab reference to global host resolver and IDN service. beware // simultaneous shutdown!! nsRefPtr res; diff --git a/netwerk/dns/nsIDNSService.idl b/netwerk/dns/nsIDNSService.idl index 159565de011..c24afb9a075 100644 --- a/netwerk/dns/nsIDNSService.idl +++ b/netwerk/dns/nsIDNSService.idl @@ -59,9 +59,8 @@ interface nsIDNSService : nsISupports in nsresult aReason); /** - * called to synchronously resolve a hostname. warning this method may - * block the calling thread for a long period of time. it is extremely - * unwise to call this function on the UI thread of an application. + * Called to synchronously resolve a hostname. This method will fail + * if called from the main thread. * * @param aHostName * the hostname or IP-address-literal to resolve. diff --git a/toolkit/library/Makefile.in b/toolkit/library/Makefile.in index 09453a7721d..d2554041fed 100644 --- a/toolkit/library/Makefile.in +++ b/toolkit/library/Makefile.in @@ -118,6 +118,10 @@ ifeq (gonk,$(MOZ_WIDGET_TOOLKIT)) STATIC_LIBS += moznetd_s endif +ifdef MOZ_B2G_CAMERA #{ +OS_LIBS += -lstagefright -lstagefright_omx +endif #} + ifdef MOZ_IPDL_TESTS STATIC_LIBS += ipdlunittest_s endif