blob: 7e809c0e27578f5fdbf9ebe9cdd67fe464869d66 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.content.browser;
import android.annotation.SuppressLint;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Build;
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
import com.google.common.annotations.VisibleForTesting;
import org.chromium.base.ObserverList;
import org.chromium.base.ThreadUtils;
import org.chromium.ui.gfx.DeviceDisplayInfo;
/**
* ScreenOrientationListener is a class that informs its observers when the
* screen orientation changes.
*/
@VisibleForTesting
public class ScreenOrientationListener {
/**
* Observes changes in screen orientation.
*/
public interface ScreenOrientationObserver {
/**
* Called whenever the screen orientation changes.
*
* @param orientation The orientation angle of the screen.
*/
void onScreenOrientationChanged(int orientation);
}
/**
* ScreenOrientationListenerBackend is an interface that abstract the
* mechanism used for the actual screen orientation listening. The reason
* being that from Android API Level 17 DisplayListener will be used. Before
* that, an unreliable solution based on onConfigurationChanged has to be
* used.
*/
private interface ScreenOrientationListenerBackend {
/**
* Starts to listen for screen orientation changes. This will be called
* when the first observer is added.
*/
void startListening();
/**
* Stops to listen for screen orientation changes. This will be called
* when the last observer is removed.
*/
void stopListening();
}
/**
* ScreenOrientationConfigurationListener implements ScreenOrientationListenerBackend
* to use ComponentCallbacks in order to listen for screen orientation
* changes.
*
* This method is known to not correctly detect 180 degrees changes but it
* is the only method that will work before API Level 17 (excluding polling).
*/
private class ScreenOrientationConfigurationListener
implements ScreenOrientationListenerBackend, ComponentCallbacks {
// ScreenOrientationListenerBackend implementation:
@Override
public void startListening() {
mAppContext.registerComponentCallbacks(this);
}
@Override
public void stopListening() {
mAppContext.unregisterComponentCallbacks(this);
}
// ComponentCallbacks implementation:
@Override
public void onConfigurationChanged(Configuration newConfig) {
notifyObservers();
}
@Override
public void onLowMemory() {
}
}
/**
* ScreenOrientationDisplayListener implements ScreenOrientationListenerBackend
* to use DisplayListener in order to listen for screen orientation changes.
*
* This method is reliable but DisplayListener is only available for API Level 17+.
*/
@SuppressLint("NewApi")
private class ScreenOrientationDisplayListener
implements ScreenOrientationListenerBackend, DisplayListener {
// ScreenOrientationListenerBackend implementation:
@Override
public void startListening() {
DisplayManager displayManager =
(DisplayManager) mAppContext.getSystemService(Context.DISPLAY_SERVICE);
displayManager.registerDisplayListener(this, null);
}
@Override
public void stopListening() {
DisplayManager displayManager =
(DisplayManager) mAppContext.getSystemService(Context.DISPLAY_SERVICE);
displayManager.unregisterDisplayListener(this);
}
// DisplayListener implementation:
@Override
public void onDisplayAdded(int displayId) {
}
@Override
public void onDisplayRemoved(int displayId) {
}
@Override
public void onDisplayChanged(int displayId) {
notifyObservers();
}
}
private static final String TAG = "ScreenOrientationListener";
// List of observers to notify when the screen orientation changes.
private final ObserverList<ScreenOrientationObserver> mObservers =
new ObserverList<ScreenOrientationObserver>();
// mOrientation will be updated every time the orientation changes. When not
// listening for changes, the value will be invalid and will be updated when
// starting to listen again.
private int mOrientation;
// Current application context derived from the first context being received.
private Context mAppContext;
private ScreenOrientationListenerBackend mBackend;
private static ScreenOrientationListener sInstance;
/**
* Returns a ScreenOrientationListener implementation based on the device's
* supported API level.
*/
public static ScreenOrientationListener getInstance() {
ThreadUtils.assertOnUiThread();
if (sInstance == null) {
sInstance = new ScreenOrientationListener();
}
return sInstance;
}
private ScreenOrientationListener() {
mBackend = Build.VERSION.SDK_INT >= 17 ?
new ScreenOrientationDisplayListener() :
new ScreenOrientationConfigurationListener();
}
/**
* Creates a ScreenOrientationConfigurationListener backend regardless of
* the current SDK.
*/
@VisibleForTesting
void injectConfigurationListenerBackendForTest() {
mBackend = new ScreenOrientationConfigurationListener();
}
/**
* Add |observer| in the ScreenOrientationListener observer list and
* immediately call |onScreenOrientationChanged| on it with the current
* orientation value.
*
* @param observer The observer that will get notified.
* @param context The context associated with this observer.
*/
public void addObserver(ScreenOrientationObserver observer, Context context) {
if (mAppContext == null) {
mAppContext = context.getApplicationContext();
}
assert mAppContext == context.getApplicationContext();
assert mAppContext != null;
if (!mObservers.addObserver(observer)) {
Log.w(TAG, "Adding an observer that is already present!");
return;
}
// If we got our first observer, we should start listening.
if (mObservers.size() == 1) {
updateOrientation();
mBackend.startListening();
}
// We need to send the current value to the added observer as soon as
// possible but outside of the current stack.
final ScreenOrientationObserver obs = observer;
ThreadUtils.assertOnUiThread();
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
obs.onScreenOrientationChanged(mOrientation);
}
});
}
/**
* Remove the |observer| from the ScreenOrientationListener observer list.
*
* @param observer The observer that will no longer receive notification.
*/
public void removeObserver(ScreenOrientationObserver observer) {
if (!mObservers.removeObserver(observer)) {
Log.w(TAG, "Removing an inexistent observer!");
return;
}
if (mObservers.isEmpty()) {
// The last observer was removed, we should just stop listening.
mBackend.stopListening();
}
}
/**
* This should be called by classes extending ScreenOrientationListener when
* it is possible that there is a screen orientation change. If there is an
* actual change, the observers will get notified.
*/
private void notifyObservers() {
int previousOrientation = mOrientation;
updateOrientation();
DeviceDisplayInfo.create(mAppContext).updateNativeSharedDisplayInfo();
if (mOrientation == previousOrientation) {
return;
}
for (ScreenOrientationObserver observer : mObservers) {
observer.onScreenOrientationChanged(mOrientation);
}
}
/**
* Updates |mOrientation| based on the default display rotation.
*/
private void updateOrientation() {
WindowManager windowManager =
(WindowManager) mAppContext.getSystemService(Context.WINDOW_SERVICE);
switch (windowManager.getDefaultDisplay().getRotation()) {
case Surface.ROTATION_0:
mOrientation = 0;
break;
case Surface.ROTATION_90:
mOrientation = 90;
break;
case Surface.ROTATION_180:
mOrientation = 180;
break;
case Surface.ROTATION_270:
mOrientation = -90;
break;
default:
throw new IllegalStateException(
"Display.getRotation() shouldn't return that value");
}
}
}