• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.content.browser;
6 
7 import android.annotation.SuppressLint;
8 import android.content.ComponentCallbacks;
9 import android.content.Context;
10 import android.content.res.Configuration;
11 import android.hardware.display.DisplayManager;
12 import android.hardware.display.DisplayManager.DisplayListener;
13 import android.os.Build;
14 import android.util.Log;
15 import android.view.Surface;
16 import android.view.WindowManager;
17 
18 import com.google.common.annotations.VisibleForTesting;
19 
20 import org.chromium.base.ObserverList;
21 import org.chromium.base.ThreadUtils;
22 import org.chromium.ui.gfx.DeviceDisplayInfo;
23 
24 /**
25  * ScreenOrientationListener is a class that informs its observers when the
26  * screen orientation changes.
27  */
28 @VisibleForTesting
29 public class ScreenOrientationListener {
30 
31     /**
32      * Observes changes in screen orientation.
33      */
34     public interface ScreenOrientationObserver {
35         /**
36          * Called whenever the screen orientation changes.
37          *
38          * @param orientation The orientation angle of the screen.
39          */
onScreenOrientationChanged(int orientation)40         void onScreenOrientationChanged(int orientation);
41     }
42 
43     /**
44      * ScreenOrientationListenerBackend is an interface that abstract the
45      * mechanism used for the actual screen orientation listening. The reason
46      * being that from Android API Level 17 DisplayListener will be used. Before
47      * that, an unreliable solution based on onConfigurationChanged has to be
48      * used.
49      */
50     private interface ScreenOrientationListenerBackend {
51 
52         /**
53          * Starts to listen for screen orientation changes. This will be called
54          * when the first observer is added.
55          */
startListening()56         void startListening();
57 
58         /**
59          * Stops to listen for screen orientation changes. This will be called
60          * when the last observer is removed.
61          */
stopListening()62         void stopListening();
63     }
64 
65     /**
66      * ScreenOrientationConfigurationListener implements ScreenOrientationListenerBackend
67      * to use ComponentCallbacks in order to listen for screen orientation
68      * changes.
69      *
70      * This method is known to not correctly detect 180 degrees changes but it
71      * is the only method that will work before API Level 17 (excluding polling).
72      */
73     private class ScreenOrientationConfigurationListener
74             implements ScreenOrientationListenerBackend, ComponentCallbacks {
75 
76         // ScreenOrientationListenerBackend implementation:
77 
78         @Override
startListening()79         public void startListening() {
80             mAppContext.registerComponentCallbacks(this);
81         }
82 
83         @Override
stopListening()84         public void stopListening() {
85             mAppContext.unregisterComponentCallbacks(this);
86         }
87 
88         // ComponentCallbacks implementation:
89 
90         @Override
onConfigurationChanged(Configuration newConfig)91         public void onConfigurationChanged(Configuration newConfig) {
92             notifyObservers();
93         }
94 
95         @Override
onLowMemory()96         public void onLowMemory() {
97         }
98     }
99 
100     /**
101      * ScreenOrientationDisplayListener implements ScreenOrientationListenerBackend
102      * to use DisplayListener in order to listen for screen orientation changes.
103      *
104      * This method is reliable but DisplayListener is only available for API Level 17+.
105      */
106     @SuppressLint("NewApi")
107     private class ScreenOrientationDisplayListener
108             implements ScreenOrientationListenerBackend, DisplayListener {
109 
110         // ScreenOrientationListenerBackend implementation:
111 
112         @Override
startListening()113         public void startListening() {
114             DisplayManager displayManager =
115                     (DisplayManager) mAppContext.getSystemService(Context.DISPLAY_SERVICE);
116             displayManager.registerDisplayListener(this, null);
117         }
118 
119         @Override
stopListening()120         public void stopListening() {
121             DisplayManager displayManager =
122                     (DisplayManager) mAppContext.getSystemService(Context.DISPLAY_SERVICE);
123             displayManager.unregisterDisplayListener(this);
124         }
125 
126         // DisplayListener implementation:
127 
128         @Override
onDisplayAdded(int displayId)129         public void onDisplayAdded(int displayId) {
130         }
131 
132         @Override
onDisplayRemoved(int displayId)133         public void onDisplayRemoved(int displayId) {
134         }
135 
136         @Override
onDisplayChanged(int displayId)137         public void onDisplayChanged(int displayId) {
138             notifyObservers();
139         }
140 
141     }
142 
143     private static final String TAG = "ScreenOrientationListener";
144 
145     // List of observers to notify when the screen orientation changes.
146     private final ObserverList<ScreenOrientationObserver> mObservers =
147             new ObserverList<ScreenOrientationObserver>();
148 
149     // mOrientation will be updated every time the orientation changes. When not
150     // listening for changes, the value will be invalid and will be updated when
151     // starting to listen again.
152     private int mOrientation;
153 
154     // Current application context derived from the first context being received.
155     private Context mAppContext;
156 
157     private ScreenOrientationListenerBackend mBackend;
158 
159     private static ScreenOrientationListener sInstance;
160 
161     /**
162      * Returns a ScreenOrientationListener implementation based on the device's
163      * supported API level.
164      */
getInstance()165     public static ScreenOrientationListener getInstance() {
166         ThreadUtils.assertOnUiThread();
167 
168         if (sInstance == null) {
169             sInstance = new ScreenOrientationListener();
170         }
171 
172         return sInstance;
173     }
174 
ScreenOrientationListener()175     private ScreenOrientationListener() {
176         mBackend = Build.VERSION.SDK_INT >= 17 ?
177                 new ScreenOrientationDisplayListener() :
178                 new ScreenOrientationConfigurationListener();
179     }
180 
181     /**
182      * Creates a ScreenOrientationConfigurationListener backend regardless of
183      * the current SDK.
184      */
185     @VisibleForTesting
injectConfigurationListenerBackendForTest()186     void injectConfigurationListenerBackendForTest() {
187         mBackend = new ScreenOrientationConfigurationListener();
188     }
189 
190     /**
191      * Add |observer| in the ScreenOrientationListener observer list and
192      * immediately call |onScreenOrientationChanged| on it with the current
193      * orientation value.
194      *
195      * @param observer The observer that will get notified.
196      * @param context The context associated with this observer.
197      */
addObserver(ScreenOrientationObserver observer, Context context)198     public void addObserver(ScreenOrientationObserver observer, Context context) {
199         if (mAppContext == null) {
200             mAppContext = context.getApplicationContext();
201         }
202 
203         assert mAppContext == context.getApplicationContext();
204         assert mAppContext != null;
205 
206         if (!mObservers.addObserver(observer)) {
207             Log.w(TAG, "Adding an observer that is already present!");
208             return;
209         }
210 
211         // If we got our first observer, we should start listening.
212         if (mObservers.size() == 1) {
213             updateOrientation();
214             mBackend.startListening();
215         }
216 
217         // We need to send the current value to the added observer as soon as
218         // possible but outside of the current stack.
219         final ScreenOrientationObserver obs = observer;
220         ThreadUtils.assertOnUiThread();
221         ThreadUtils.postOnUiThread(new Runnable() {
222             @Override
223             public void run() {
224                 obs.onScreenOrientationChanged(mOrientation);
225             }
226         });
227     }
228 
229     /**
230      * Remove the |observer| from the ScreenOrientationListener observer list.
231      *
232      * @param observer The observer that will no longer receive notification.
233      */
removeObserver(ScreenOrientationObserver observer)234     public void removeObserver(ScreenOrientationObserver observer) {
235         if (!mObservers.removeObserver(observer)) {
236             Log.w(TAG, "Removing an inexistent observer!");
237             return;
238         }
239 
240         if (mObservers.isEmpty()) {
241             // The last observer was removed, we should just stop listening.
242             mBackend.stopListening();
243         }
244     }
245 
246     /**
247      * This should be called by classes extending ScreenOrientationListener when
248      * it is possible that there is a screen orientation change. If there is an
249      * actual change, the observers will get notified.
250      */
notifyObservers()251     private void notifyObservers() {
252         int previousOrientation = mOrientation;
253         updateOrientation();
254 
255         DeviceDisplayInfo.create(mAppContext).updateNativeSharedDisplayInfo();
256 
257         if (mOrientation == previousOrientation) {
258             return;
259         }
260 
261         for (ScreenOrientationObserver observer : mObservers) {
262             observer.onScreenOrientationChanged(mOrientation);
263         }
264     }
265 
266     /**
267      * Updates |mOrientation| based on the default display rotation.
268      */
updateOrientation()269     private void updateOrientation() {
270         WindowManager windowManager =
271                 (WindowManager) mAppContext.getSystemService(Context.WINDOW_SERVICE);
272 
273         switch (windowManager.getDefaultDisplay().getRotation()) {
274             case Surface.ROTATION_0:
275                 mOrientation = 0;
276                 break;
277             case Surface.ROTATION_90:
278                 mOrientation = 90;
279                 break;
280             case Surface.ROTATION_180:
281                 mOrientation = 180;
282                 break;
283             case Surface.ROTATION_270:
284                 mOrientation = -90;
285                 break;
286             default:
287                 throw new IllegalStateException(
288                         "Display.getRotation() shouldn't return that value");
289         }
290     }
291 }
292