summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java
blob: bdb7b4b331978e8c09736270ab35d5dbb89eaaee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/* -*- 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;

    private 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<OrientationChangeListener> 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();
  }
}