• Home
  • History
  • Annotate
  • 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 org.chromium.base.ObserverList;
19 import org.chromium.base.ThreadUtils;
20 import org.chromium.base.VisibleForTesting;
21 import org.chromium.ui.gfx.DeviceDisplayInfo;
22 
23 /**
24  * ScreenOrientationListener is a class that informs its observers when the
25  * screen orientation changes.
26  */
27 @VisibleForTesting
28 public class ScreenOrientationListener {
29 
30     /**
31      * Observes changes in screen orientation.
32      */
33     public interface ScreenOrientationObserver {
34         /**
35          * Called whenever the screen orientation changes.
36          *
37          * @param orientation The orientation angle of the screen.
38          */
onScreenOrientationChanged(int orientation)39         void onScreenOrientationChanged(int orientation);
40     }
41 
42     /**
43      * ScreenOrientationListenerBackend is an interface that abstract the
44      * mechanism used for the actual screen orientation listening. The reason
45      * being that from Android API Level 17 DisplayListener will be used. Before
46      * that, an unreliable solution based on onConfigurationChanged has to be
47      * used.
48      */
49     private interface ScreenOrientationListenerBackend {
50 
51         /**
52          * Starts to listen for screen orientation changes. This will be called
53          * when the first observer is added.
54          */
startListening()55         void startListening();
56 
57         /**
58          * Stops to listen for screen orientation changes. This will be called
59          * when the last observer is removed.
60          */
stopListening()61         void stopListening();
62 
63         /**
64          * Toggle the accurate mode if it wasn't already doing so. The backend
65          * will keep track of the number of times this has been called.
66          */
startAccurateListening()67         void startAccurateListening();
68 
69         /**
70          * Request to stop the accurate mode. It will effectively be stopped
71          * only if this method is called as many times as
72          * startAccurateListening().
73          */
stopAccurateListening()74         void stopAccurateListening();
75     }
76 
77     /**
78      * ScreenOrientationConfigurationListener implements ScreenOrientationListenerBackend
79      * to use ComponentCallbacks in order to listen for screen orientation
80      * changes.
81      *
82      * This method is known to not correctly detect 180 degrees changes but it
83      * is the only method that will work before API Level 17 (excluding polling).
84      * When toggleAccurateMode() is called, it will start polling in order to
85      * find out if the display has changed.
86      */
87     private class ScreenOrientationConfigurationListener
88             implements ScreenOrientationListenerBackend, ComponentCallbacks {
89 
90         private static final long POLLING_DELAY = 500;
91 
92         private int mAccurateCount = 0;
93 
94         // ScreenOrientationListenerBackend implementation:
95 
96         @Override
startListening()97         public void startListening() {
98             mAppContext.registerComponentCallbacks(this);
99         }
100 
101         @Override
stopListening()102         public void stopListening() {
103             mAppContext.unregisterComponentCallbacks(this);
104         }
105 
106         @Override
startAccurateListening()107         public void startAccurateListening() {
108             ++mAccurateCount;
109 
110             if (mAccurateCount > 1)
111                 return;
112 
113             // Start polling if we went from 0 to 1. The polling will
114             // automatically stop when mAccurateCount reaches 0.
115             final ScreenOrientationConfigurationListener self = this;
116             ThreadUtils.postOnUiThreadDelayed(new Runnable() {
117                 @Override
118                 public void run() {
119                     self.onConfigurationChanged(null);
120 
121                     if (self.mAccurateCount < 1)
122                         return;
123 
124                     ThreadUtils.postOnUiThreadDelayed(this,
125                             ScreenOrientationConfigurationListener.POLLING_DELAY);
126                 }
127             }, POLLING_DELAY);
128         }
129 
130         @Override
stopAccurateListening()131         public void stopAccurateListening() {
132             --mAccurateCount;
133             assert mAccurateCount >= 0;
134         }
135 
136         // ComponentCallbacks implementation:
137 
138         @Override
onConfigurationChanged(Configuration newConfig)139         public void onConfigurationChanged(Configuration newConfig) {
140             notifyObservers();
141         }
142 
143         @Override
onLowMemory()144         public void onLowMemory() {
145         }
146     }
147 
148     /**
149      * ScreenOrientationDisplayListener implements ScreenOrientationListenerBackend
150      * to use DisplayListener in order to listen for screen orientation changes.
151      *
152      * This method is reliable but DisplayListener is only available for API Level 17+.
153      */
154     @SuppressLint("NewApi")
155     private class ScreenOrientationDisplayListener
156             implements ScreenOrientationListenerBackend, DisplayListener {
157 
158         // ScreenOrientationListenerBackend implementation:
159 
160         @Override
startListening()161         public void startListening() {
162             DisplayManager displayManager =
163                     (DisplayManager) mAppContext.getSystemService(Context.DISPLAY_SERVICE);
164             displayManager.registerDisplayListener(this, null);
165         }
166 
167         @Override
stopListening()168         public void stopListening() {
169             DisplayManager displayManager =
170                     (DisplayManager) mAppContext.getSystemService(Context.DISPLAY_SERVICE);
171             displayManager.unregisterDisplayListener(this);
172         }
173 
174         @Override
startAccurateListening()175         public void startAccurateListening() {
176             // Always accurate. Do nothing.
177         }
178 
179         @Override
stopAccurateListening()180         public void stopAccurateListening() {
181             // Always accurate. Do nothing.
182         }
183 
184         // DisplayListener implementation:
185 
186         @Override
onDisplayAdded(int displayId)187         public void onDisplayAdded(int displayId) {
188         }
189 
190         @Override
onDisplayRemoved(int displayId)191         public void onDisplayRemoved(int displayId) {
192         }
193 
194         @Override
onDisplayChanged(int displayId)195         public void onDisplayChanged(int displayId) {
196             notifyObservers();
197         }
198 
199     }
200 
201     private static final String TAG = "ScreenOrientationListener";
202 
203     // List of observers to notify when the screen orientation changes.
204     private final ObserverList<ScreenOrientationObserver> mObservers =
205             new ObserverList<ScreenOrientationObserver>();
206 
207     // mOrientation will be updated every time the orientation changes. When not
208     // listening for changes, the value will be invalid and will be updated when
209     // starting to listen again.
210     private int mOrientation;
211 
212     // Current application context derived from the first context being received.
213     private Context mAppContext;
214 
215     private ScreenOrientationListenerBackend mBackend;
216 
217     private static ScreenOrientationListener sInstance;
218 
219     /**
220      * Returns a ScreenOrientationListener implementation based on the device's
221      * supported API level.
222      */
getInstance()223     public static ScreenOrientationListener getInstance() {
224         ThreadUtils.assertOnUiThread();
225 
226         if (sInstance == null) {
227             sInstance = new ScreenOrientationListener();
228         }
229 
230         return sInstance;
231     }
232 
ScreenOrientationListener()233     private ScreenOrientationListener() {
234         mBackend = Build.VERSION.SDK_INT >= 17 ?
235                 new ScreenOrientationDisplayListener() :
236                 new ScreenOrientationConfigurationListener();
237     }
238 
239     /**
240      * Add |observer| in the ScreenOrientationListener observer list and
241      * immediately call |onScreenOrientationChanged| on it with the current
242      * orientation value.
243      *
244      * @param observer The observer that will get notified.
245      * @param context The context associated with this observer.
246      */
addObserver(ScreenOrientationObserver observer, Context context)247     public void addObserver(ScreenOrientationObserver observer, Context context) {
248         if (mAppContext == null) {
249             mAppContext = context.getApplicationContext();
250         }
251 
252         assert mAppContext == context.getApplicationContext();
253         assert mAppContext != null;
254 
255         if (!mObservers.addObserver(observer)) {
256             Log.w(TAG, "Adding an observer that is already present!");
257             return;
258         }
259 
260         // If we got our first observer, we should start listening.
261         if (mObservers.size() == 1) {
262             updateOrientation();
263             mBackend.startListening();
264         }
265 
266         // We need to send the current value to the added observer as soon as
267         // possible but outside of the current stack.
268         final ScreenOrientationObserver obs = observer;
269         ThreadUtils.assertOnUiThread();
270         ThreadUtils.postOnUiThread(new Runnable() {
271             @Override
272             public void run() {
273                 obs.onScreenOrientationChanged(mOrientation);
274             }
275         });
276     }
277 
278     /**
279      * Remove the |observer| from the ScreenOrientationListener observer list.
280      *
281      * @param observer The observer that will no longer receive notification.
282      */
removeObserver(ScreenOrientationObserver observer)283     public void removeObserver(ScreenOrientationObserver observer) {
284         if (!mObservers.removeObserver(observer)) {
285             Log.w(TAG, "Removing an inexistent observer!");
286             return;
287         }
288 
289         if (mObservers.isEmpty()) {
290             // The last observer was removed, we should just stop listening.
291             mBackend.stopListening();
292         }
293     }
294 
295     /**
296      * Toggle the accurate mode if it wasn't already doing so. The backend will
297      * keep track of the number of times this has been called.
298      */
startAccurateListening()299     public void startAccurateListening() {
300         mBackend.startAccurateListening();
301     }
302 
303     /**
304      * Request to stop the accurate mode. It will effectively be stopped only if
305      * this method is called as many times as startAccurateListening().
306      */
stopAccurateListening()307     public void stopAccurateListening() {
308         mBackend.stopAccurateListening();
309     }
310 
311     /**
312      * This should be called by classes extending ScreenOrientationListener when
313      * it is possible that there is a screen orientation change. If there is an
314      * actual change, the observers will get notified.
315      */
notifyObservers()316     private void notifyObservers() {
317         int previousOrientation = mOrientation;
318         updateOrientation();
319 
320         if (mOrientation == previousOrientation) {
321             return;
322         }
323 
324         DeviceDisplayInfo.create(mAppContext).updateNativeSharedDisplayInfo();
325 
326         for (ScreenOrientationObserver observer : mObservers) {
327             observer.onScreenOrientationChanged(mOrientation);
328         }
329     }
330 
331     /**
332      * Updates |mOrientation| based on the default display rotation.
333      */
updateOrientation()334     private void updateOrientation() {
335         WindowManager windowManager =
336                 (WindowManager) mAppContext.getSystemService(Context.WINDOW_SERVICE);
337 
338         switch (windowManager.getDefaultDisplay().getRotation()) {
339             case Surface.ROTATION_0:
340                 mOrientation = 0;
341                 break;
342             case Surface.ROTATION_90:
343                 mOrientation = 90;
344                 break;
345             case Surface.ROTATION_180:
346                 mOrientation = 180;
347                 break;
348             case Surface.ROTATION_270:
349                 mOrientation = -90;
350                 break;
351             default:
352                 throw new IllegalStateException(
353                         "Display.getRotation() shouldn't return that value");
354         }
355     }
356 }
357