michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=8 et ft=cpp : */ michael@0: /* Copyright 2012 Mozilla Foundation and Mozilla contributors michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #include "base/basictypes.h" michael@0: #include "mozilla/ClearOnShutdown.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "mozilla/Hal.h" michael@0: #include "nsIScreen.h" michael@0: #include "nsIScreenManager.h" michael@0: #include "OrientationObserver.h" michael@0: #include "mozilla/HalSensor.h" michael@0: #include "ProcessOrientation.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace dom; michael@0: michael@0: namespace { michael@0: michael@0: struct OrientationMapping { michael@0: uint32_t mScreenRotation; michael@0: ScreenOrientation mDomOrientation; michael@0: }; michael@0: michael@0: static OrientationMapping sOrientationMappings[] = { michael@0: {nsIScreen::ROTATION_0_DEG, eScreenOrientation_PortraitPrimary}, michael@0: {nsIScreen::ROTATION_180_DEG, eScreenOrientation_PortraitSecondary}, michael@0: {nsIScreen::ROTATION_90_DEG, eScreenOrientation_LandscapePrimary}, michael@0: {nsIScreen::ROTATION_270_DEG, eScreenOrientation_LandscapeSecondary}, michael@0: }; michael@0: michael@0: const static int sDefaultLandscape = 2; michael@0: const static int sDefaultPortrait = 0; michael@0: michael@0: static uint32_t sOrientationOffset = 0; michael@0: michael@0: static already_AddRefed michael@0: GetPrimaryScreen() michael@0: { michael@0: nsCOMPtr screenMgr = michael@0: do_GetService("@mozilla.org/gfx/screenmanager;1"); michael@0: NS_ENSURE_TRUE(screenMgr, nullptr); michael@0: michael@0: nsCOMPtr screen; michael@0: screenMgr->GetPrimaryScreen(getter_AddRefs(screen)); michael@0: return screen.forget(); michael@0: } michael@0: michael@0: static void michael@0: DetectDefaultOrientation() michael@0: { michael@0: nsCOMPtr screen = GetPrimaryScreen(); michael@0: if (!screen) { michael@0: return; michael@0: } michael@0: michael@0: int32_t left, top, width, height; michael@0: if (NS_FAILED(screen->GetRect(&left, &top, &width, &height))) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t rotation; michael@0: if (NS_FAILED(screen->GetRotation(&rotation))) { michael@0: return; michael@0: } michael@0: michael@0: if (width < height) { michael@0: if (rotation == nsIScreen::ROTATION_0_DEG || michael@0: rotation == nsIScreen::ROTATION_180_DEG) { michael@0: sOrientationOffset = sDefaultPortrait; michael@0: } else { michael@0: sOrientationOffset = sDefaultLandscape; michael@0: } michael@0: } else { michael@0: if (rotation == nsIScreen::ROTATION_0_DEG || michael@0: rotation == nsIScreen::ROTATION_180_DEG) { michael@0: sOrientationOffset = sDefaultLandscape; michael@0: } else { michael@0: sOrientationOffset = sDefaultPortrait; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Converts DOM orientation to nsIScreen rotation. Portrait and Landscape are michael@0: * treated as PortraitPrimary and LandscapePrimary, respectively, during michael@0: * conversion. michael@0: * michael@0: * @param aOrientation DOM orientation e.g. michael@0: * dom::eScreenOrientation_PortraitPrimary. michael@0: * @param aResult output nsIScreen rotation e.g. nsIScreen::ROTATION_0_DEG. michael@0: * @return NS_OK on success. NS_ILLEGAL_VALUE on failure. michael@0: */ michael@0: static nsresult michael@0: ConvertToScreenRotation(ScreenOrientation aOrientation, uint32_t *aResult) michael@0: { michael@0: for (int i = 0; i < ArrayLength(sOrientationMappings); i++) { michael@0: if (aOrientation & sOrientationMappings[i].mDomOrientation) { michael@0: // Shift the mappings in sOrientationMappings so devices with default michael@0: // landscape orientation map landscape-primary to 0 degree and so forth. michael@0: int adjusted = (i + sOrientationOffset) % michael@0: ArrayLength(sOrientationMappings); michael@0: *aResult = sOrientationMappings[adjusted].mScreenRotation; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: *aResult = nsIScreen::ROTATION_0_DEG; michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: /** michael@0: * Converts nsIScreen rotation to DOM orientation. michael@0: * michael@0: * @param aRotation nsIScreen rotation e.g. nsIScreen::ROTATION_0_DEG. michael@0: * @param aResult output DOM orientation e.g. michael@0: * dom::eScreenOrientation_PortraitPrimary. michael@0: * @return NS_OK on success. NS_ILLEGAL_VALUE on failure. michael@0: */ michael@0: nsresult michael@0: ConvertToDomOrientation(uint32_t aRotation, ScreenOrientation *aResult) michael@0: { michael@0: for (int i = 0; i < ArrayLength(sOrientationMappings); i++) { michael@0: if (aRotation == sOrientationMappings[i].mScreenRotation) { michael@0: // Shift the mappings in sOrientationMappings so devices with default michael@0: // landscape orientation map 0 degree to landscape-primary and so forth. michael@0: int adjusted = (i + sOrientationOffset) % michael@0: ArrayLength(sOrientationMappings); michael@0: *aResult = sOrientationMappings[adjusted].mDomOrientation; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: *aResult = eScreenOrientation_None; michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: // Note that all operations with sOrientationSensorObserver michael@0: // should be on the main thread. michael@0: static StaticAutoPtr sOrientationSensorObserver; michael@0: michael@0: } // Anonymous namespace michael@0: michael@0: OrientationObserver* michael@0: OrientationObserver::GetInstance() michael@0: { michael@0: if (!sOrientationSensorObserver) { michael@0: sOrientationSensorObserver = new OrientationObserver(); michael@0: ClearOnShutdown(&sOrientationSensorObserver); michael@0: } michael@0: michael@0: return sOrientationSensorObserver; michael@0: } michael@0: michael@0: OrientationObserver::OrientationObserver() michael@0: : mAutoOrientationEnabled(false) michael@0: , mAllowedOrientations(sDefaultOrientations) michael@0: , mOrientation(new mozilla::ProcessOrientation()) michael@0: { michael@0: DetectDefaultOrientation(); michael@0: michael@0: EnableAutoOrientation(); michael@0: } michael@0: michael@0: OrientationObserver::~OrientationObserver() michael@0: { michael@0: if (mAutoOrientationEnabled) { michael@0: DisableAutoOrientation(); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: OrientationObserver::ShutDown() michael@0: { michael@0: if (!sOrientationSensorObserver) { michael@0: return; michael@0: } michael@0: michael@0: if (sOrientationSensorObserver->mAutoOrientationEnabled) { michael@0: sOrientationSensorObserver->DisableAutoOrientation(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: OrientationObserver::Notify(const hal::SensorData& aSensorData) michael@0: { michael@0: // Sensor will call us on the main thread. michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(aSensorData.sensor() == hal::SensorType::SENSOR_ACCELERATION); michael@0: michael@0: nsCOMPtr screen = GetPrimaryScreen(); michael@0: if (!screen) { michael@0: return; michael@0: } michael@0: michael@0: uint32_t currRotation; michael@0: if(NS_FAILED(screen->GetRotation(&currRotation))) { michael@0: return; michael@0: } michael@0: michael@0: int rotation = mOrientation->OnSensorChanged(aSensorData, static_cast(currRotation)); michael@0: if (rotation < 0 || rotation == currRotation) { michael@0: return; michael@0: } michael@0: michael@0: ScreenOrientation orientation; michael@0: if (NS_FAILED(ConvertToDomOrientation(rotation, &orientation))) { michael@0: return; michael@0: } michael@0: michael@0: if ((mAllowedOrientations & orientation) == eScreenOrientation_None) { michael@0: // The orientation from sensor is not allowed. michael@0: return; michael@0: } michael@0: michael@0: if (NS_FAILED(screen->SetRotation(static_cast(rotation)))) { michael@0: // Don't notify dom on rotation failure. michael@0: return; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Register the observer. Note that the observer shouldn't be registered. michael@0: */ michael@0: void michael@0: OrientationObserver::EnableAutoOrientation() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread() && !mAutoOrientationEnabled); michael@0: michael@0: mOrientation->Reset(); michael@0: hal::RegisterSensorObserver(hal::SENSOR_ACCELERATION, this); michael@0: mAutoOrientationEnabled = true; michael@0: } michael@0: michael@0: /** michael@0: * Unregister the observer. Note that the observer should already be registered. michael@0: */ michael@0: void michael@0: OrientationObserver::DisableAutoOrientation() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread() && mAutoOrientationEnabled); michael@0: michael@0: hal::UnregisterSensorObserver(hal::SENSOR_ACCELERATION, this); michael@0: mAutoOrientationEnabled = false; michael@0: } michael@0: michael@0: bool michael@0: OrientationObserver::LockScreenOrientation(ScreenOrientation aOrientation) michael@0: { michael@0: MOZ_ASSERT(aOrientation | (eScreenOrientation_PortraitPrimary | michael@0: eScreenOrientation_PortraitSecondary | michael@0: eScreenOrientation_LandscapePrimary | michael@0: eScreenOrientation_LandscapeSecondary | michael@0: eScreenOrientation_Default)); michael@0: michael@0: if (aOrientation == eScreenOrientation_Default) { michael@0: aOrientation = (sOrientationOffset == sDefaultPortrait) ? michael@0: eScreenOrientation_PortraitPrimary : michael@0: eScreenOrientation_LandscapePrimary; michael@0: } michael@0: michael@0: // If there are multiple orientations allowed, we should enable the michael@0: // auto-rotation. michael@0: if (aOrientation != eScreenOrientation_LandscapePrimary && michael@0: aOrientation != eScreenOrientation_LandscapeSecondary && michael@0: aOrientation != eScreenOrientation_PortraitPrimary && michael@0: aOrientation != eScreenOrientation_PortraitSecondary) { michael@0: if (!mAutoOrientationEnabled) { michael@0: EnableAutoOrientation(); michael@0: } michael@0: } else if (mAutoOrientationEnabled) { michael@0: DisableAutoOrientation(); michael@0: } michael@0: michael@0: mAllowedOrientations = aOrientation; michael@0: michael@0: nsCOMPtr screen = GetPrimaryScreen(); michael@0: NS_ENSURE_TRUE(screen, false); michael@0: michael@0: uint32_t currRotation; michael@0: nsresult rv = screen->GetRotation(&currRotation); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: ScreenOrientation currOrientation = eScreenOrientation_None; michael@0: rv = ConvertToDomOrientation(currRotation, &currOrientation); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // Don't rotate if the current orientation matches one of the michael@0: // requested orientations. michael@0: if (currOrientation & aOrientation) { michael@0: return true; michael@0: } michael@0: michael@0: // Return false on invalid orientation value. michael@0: uint32_t rotation; michael@0: rv = ConvertToScreenRotation(aOrientation, &rotation); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: rv = screen->SetRotation(rotation); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // This conversion will disambiguate aOrientation. michael@0: ScreenOrientation orientation; michael@0: rv = ConvertToDomOrientation(rotation, &orientation); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: OrientationObserver::UnlockScreenOrientation() michael@0: { michael@0: if (!mAutoOrientationEnabled) { michael@0: EnableAutoOrientation(); michael@0: } michael@0: michael@0: mAllowedOrientations = sDefaultOrientations; michael@0: }