Home | History | Annotate | Download | only in browser
      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          */
     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          */
     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          */
     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          */
     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          */
     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
     97         public void startListening() {
     98             mAppContext.registerComponentCallbacks(this);
     99         }
    100 
    101         @Override
    102         public void stopListening() {
    103             mAppContext.unregisterComponentCallbacks(this);
    104         }
    105 
    106         @Override
    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
    131         public void stopAccurateListening() {
    132             --mAccurateCount;
    133             assert mAccurateCount >= 0;
    134         }
    135 
    136         // ComponentCallbacks implementation:
    137 
    138         @Override
    139         public void onConfigurationChanged(Configuration newConfig) {
    140             notifyObservers();
    141         }
    142 
    143         @Override
    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
    161         public void startListening() {
    162             DisplayManager displayManager =
    163                     (DisplayManager) mAppContext.getSystemService(Context.DISPLAY_SERVICE);
    164             displayManager.registerDisplayListener(this, null);
    165         }
    166 
    167         @Override
    168         public void stopListening() {
    169             DisplayManager displayManager =
    170                     (DisplayManager) mAppContext.getSystemService(Context.DISPLAY_SERVICE);
    171             displayManager.unregisterDisplayListener(this);
    172         }
    173 
    174         @Override
    175         public void startAccurateListening() {
    176             // Always accurate. Do nothing.
    177         }
    178 
    179         @Override
    180         public void stopAccurateListening() {
    181             // Always accurate. Do nothing.
    182         }
    183 
    184         // DisplayListener implementation:
    185 
    186         @Override
    187         public void onDisplayAdded(int displayId) {
    188         }
    189 
    190         @Override
    191         public void onDisplayRemoved(int displayId) {
    192         }
    193 
    194         @Override
    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      */
    223     public static ScreenOrientationListener getInstance() {
    224         ThreadUtils.assertOnUiThread();
    225 
    226         if (sInstance == null) {
    227             sInstance = new ScreenOrientationListener();
    228         }
    229 
    230         return sInstance;
    231     }
    232 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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