/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import android.content.Context; import android.graphics.Rect; import android.util.Log; import android.view.Display; import android.view.Surface; import java.util.ArrayList; import java.util.List; import org.mozilla.gecko.util.ThreadUtils; /* * Updates, locks and unlocks the screen orientation. * * Note: Replaces the OnOrientationChangeListener to avoid redundant rotation * event handling. */ public class GeckoScreenOrientation { private static final String LOGTAG = "GeckoScreenOrientation"; // Make sure that any change in hal/HalScreenConfiguration.h happens here too. public enum ScreenOrientation { NONE(0), PORTRAIT_PRIMARY(1 << 0), PORTRAIT_SECONDARY(1 << 1), PORTRAIT(PORTRAIT_PRIMARY.value | PORTRAIT_SECONDARY.value), LANDSCAPE_PRIMARY(1 << 2), LANDSCAPE_SECONDARY(1 << 3), LANDSCAPE(LANDSCAPE_PRIMARY.value | LANDSCAPE_SECONDARY.value), ANY( PORTRAIT_PRIMARY.value | PORTRAIT_SECONDARY.value | LANDSCAPE_PRIMARY.value | LANDSCAPE_SECONDARY.value), DEFAULT(1 << 4); public final short value; ScreenOrientation(final int value) { this.value = (short) value; } private static final ScreenOrientation[] sValues = ScreenOrientation.values(); public static ScreenOrientation get(final int value) { for (final ScreenOrientation orient : sValues) { if (orient.value == value) { return orient; } } return NONE; } } // Singleton instance. private static GeckoScreenOrientation sInstance; // Default rotation, used when device rotation is unknown. private static final int DEFAULT_ROTATION = Surface.ROTATION_0; // Last updated screen orientation with Gecko value space. private ScreenOrientation mScreenOrientation = ScreenOrientation.PORTRAIT_PRIMARY; public interface OrientationChangeListener { void onScreenOrientationChanged(ScreenOrientation newOrientation); } private final List mListeners; public static GeckoScreenOrientation getInstance() { if (sInstance == null) { sInstance = new GeckoScreenOrientation(); } return sInstance; } private GeckoScreenOrientation() { mListeners = new ArrayList<>(); update(); } /** Add a listener that will be notified when the screen orientation has changed. */ public void addListener(final OrientationChangeListener aListener) { ThreadUtils.assertOnUiThread(); mListeners.add(aListener); } /** Remove a OrientationChangeListener again. */ public void removeListener(final OrientationChangeListener aListener) { ThreadUtils.assertOnUiThread(); mListeners.remove(aListener); } /* * Update screen orientation. * Retrieve orientation and rotation via GeckoAppShell. * * @return Whether the screen orientation has changed. */ public boolean update() { // Check whether we have the application context for fenix/a-c unit test. final Context appContext = GeckoAppShell.getApplicationContext(); if (appContext == null) { return false; } final Rect rect = GeckoAppShell.getScreenSizeIgnoreOverride(); final int orientation = rect.width() >= rect.height() ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; return update(getScreenOrientation(orientation, getRotation())); } /* * Update screen orientation. * Retrieve orientation and rotation via Display. * * @param aDisplay The Display that has screen orientation information * * @return Whether the screen orientation has changed. */ public boolean update(final Display aDisplay) { return update(getScreenOrientation(aDisplay)); } /* * Update screen orientation given the android orientation. * Retrieve rotation via GeckoAppShell. * * @param aAndroidOrientation * Android screen orientation from Configuration.orientation. * * @return Whether the screen orientation has changed. */ public boolean update(final int aAndroidOrientation) { return update(getScreenOrientation(aAndroidOrientation, getRotation())); } /* * Update screen orientation given the screen orientation. * * @param aScreenOrientation * Gecko screen orientation based on android orientation and rotation. * * @return Whether the screen orientation has changed. */ public synchronized boolean update(final ScreenOrientation aScreenOrientation) { // Gecko expects a definite screen orientation, so we default to the // primary orientations. final ScreenOrientation screenOrientation; if ((aScreenOrientation.value & ScreenOrientation.PORTRAIT_PRIMARY.value) != 0) { screenOrientation = ScreenOrientation.PORTRAIT_PRIMARY; } else if ((aScreenOrientation.value & ScreenOrientation.PORTRAIT_SECONDARY.value) != 0) { screenOrientation = ScreenOrientation.PORTRAIT_SECONDARY; } else if ((aScreenOrientation.value & ScreenOrientation.LANDSCAPE_PRIMARY.value) != 0) { screenOrientation = ScreenOrientation.LANDSCAPE_PRIMARY; } else if ((aScreenOrientation.value & ScreenOrientation.LANDSCAPE_SECONDARY.value) != 0) { screenOrientation = ScreenOrientation.LANDSCAPE_SECONDARY; } else { screenOrientation = ScreenOrientation.PORTRAIT_PRIMARY; } if (mScreenOrientation == screenOrientation) { return false; } mScreenOrientation = screenOrientation; Log.d(LOGTAG, "updating to new orientation " + mScreenOrientation); notifyListeners(mScreenOrientation); ScreenManagerHelper.refreshScreenInfo(); return true; } private void notifyListeners(final ScreenOrientation newOrientation) { final Runnable notifier = new Runnable() { @Override public void run() { for (final OrientationChangeListener listener : mListeners) { listener.onScreenOrientationChanged(newOrientation); } } }; if (ThreadUtils.isOnUiThread()) { notifier.run(); } else { ThreadUtils.runOnUiThread(notifier); } } /* * @return The Gecko screen orientation derived from Android orientation and * rotation. */ public ScreenOrientation getScreenOrientation() { return mScreenOrientation; } /* * Combine the Android orientation and rotation to the Gecko orientation. * * @param aAndroidOrientation * Android orientation from Configuration.orientation. * @param aRotation * Device rotation from Display.getRotation(). * * @return Gecko screen orientation. */ private ScreenOrientation getScreenOrientation( final int aAndroidOrientation, final int aRotation) { final boolean isPrimary = aRotation == Surface.ROTATION_0 || aRotation == Surface.ROTATION_90; if (aAndroidOrientation == ORIENTATION_PORTRAIT) { if (isPrimary) { // Non-rotated portrait device or landscape device rotated // to primary portrait mode counter-clockwise. return ScreenOrientation.PORTRAIT_PRIMARY; } return ScreenOrientation.PORTRAIT_SECONDARY; } if (aAndroidOrientation == ORIENTATION_LANDSCAPE) { if (isPrimary) { // Non-rotated landscape device or portrait device rotated // to primary landscape mode counter-clockwise. return ScreenOrientation.LANDSCAPE_PRIMARY; } return ScreenOrientation.LANDSCAPE_SECONDARY; } return ScreenOrientation.NONE; } /* * Get the Gecko orientation from Display. * * @param aDisplay The display that has orientation information. * * @return Gecko screen orientation. */ private ScreenOrientation getScreenOrientation(final Display aDisplay) { final Rect rect = GeckoAppShell.getScreenSizeIgnoreOverride(); final int orientation = rect.width() >= rect.height() ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; return getScreenOrientation(orientation, aDisplay.getRotation()); } /* * @return Device rotation converted to an angle. */ public short getAngle() { switch (getRotation()) { case Surface.ROTATION_0: return 0; case Surface.ROTATION_90: return 90; case Surface.ROTATION_180: return 180; case Surface.ROTATION_270: return 270; default: Log.w(LOGTAG, "getAngle: unexpected rotation value"); return 0; } } /* * @return Device rotation. */ private int getRotation() { return GeckoAppShell.getRotation(); } }