1 package versioned.host.exp.exponent.modules.api;
2 
3 import android.app.Activity;
4 import android.content.pm.ActivityInfo;
5 import android.util.DisplayMetrics;
6 import android.view.Surface;
7 import android.view.WindowManager;
8 
9 import com.facebook.react.bridge.LifecycleEventListener;
10 import com.facebook.react.bridge.Promise;
11 import com.facebook.react.bridge.ReactApplicationContext;
12 import com.facebook.react.bridge.ReactContextBaseJavaModule;
13 import com.facebook.react.bridge.ReactMethod;
14 import com.facebook.react.bridge.WritableMap;
15 import com.facebook.react.bridge.WritableNativeMap;
16 
17 import expo.modules.core.errors.InvalidArgumentException;
18 
19 import javax.annotation.Nullable;
20 
21 public class ScreenOrientationModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
22   private @Nullable
23   Integer mInitialOrientation = null;
24   static final String ERR_SCREEN_ORIENTATION = "ERR_SCREEN_ORIENTATION";
25 
ScreenOrientationModule(ReactApplicationContext reactContext)26   public ScreenOrientationModule(ReactApplicationContext reactContext) {
27     super(reactContext);
28 
29     reactContext.addLifecycleEventListener(this);
30   }
31 
32   @Override
getName()33   public String getName() {
34     return "ExpoScreenOrientation";
35   }
36 
37   @Override
onHostResume()38   public void onHostResume() {
39     Activity activity = getCurrentActivity();
40     if (activity != null && mInitialOrientation == null) {
41       mInitialOrientation = activity.getRequestedOrientation();
42     }
43   }
44 
45   @Override
onHostPause()46   public void onHostPause() {
47 
48   }
49 
50   @Override
onHostDestroy()51   public void onHostDestroy() {
52 
53   }
54 
55   @Override
onCatalystInstanceDestroy()56   public void onCatalystInstanceDestroy() {
57     super.onCatalystInstanceDestroy();
58 
59     Activity activity = getCurrentActivity();
60     if (activity != null && mInitialOrientation != null) {
61       activity.setRequestedOrientation(mInitialOrientation);
62     }
63   }
64 
65   @ReactMethod
lockAsync(String orientationLockStr, Promise promise)66   public void lockAsync(String orientationLockStr, Promise promise) {
67     Activity activity = getCurrentActivity();
68 
69     try {
70       OrientationLock orientationLock = OrientationLock.valueOf(orientationLockStr);
71       int orientationAttr = orientationLockJSToNative(orientationLock);
72       activity.setRequestedOrientation(orientationAttr);
73     } catch (IllegalArgumentException e) {
74       promise.reject("ERR_SCREEN_ORIENTATION_INVALID_ORIENTATION_LOCK", "An invalid OrientationLock was passed in: " + orientationLockStr, e);
75       return;
76     } catch (InvalidArgumentException e) {
77       promise.reject(e);
78       return;
79     } catch (Exception e) {
80       promise.reject(ERR_SCREEN_ORIENTATION, "Could not apply the ScreenOrientation lock: " + orientationLockStr, e);
81       return;
82     }
83     promise.resolve(null);
84   }
85 
86   @ReactMethod
lockPlatformAsync(int orientationAttr, Promise promise)87   public void lockPlatformAsync(int orientationAttr, Promise promise) {
88     Activity activity = getCurrentActivity();
89 
90     try {
91       activity.setRequestedOrientation(orientationAttr);
92     } catch (Exception e) {
93       promise.reject(ERR_SCREEN_ORIENTATION, "Could not apply the ScreenOrientation platform lock: " + orientationAttr, e);
94       return;
95     }
96     promise.resolve(null);
97 
98   }
99 
100   @ReactMethod
unlockAsync(Promise promise)101   public void unlockAsync(Promise promise) {
102     lockAsync(OrientationLock.DEFAULT.toString(), promise);
103   }
104 
105   @ReactMethod
getOrientationAsync(Promise promise)106   public void getOrientationAsync(Promise promise) {
107     Activity activity = getCurrentActivity();
108 
109     try {
110       Orientation orientation = getScreenOrientation(activity);
111       WritableMap orientationInfo =  new WritableNativeMap();
112       orientationInfo.putString("orientation", orientation.toString());
113       promise.resolve(orientationInfo);
114     } catch (Exception e) {
115       promise.reject(ERR_SCREEN_ORIENTATION, "Could not get the current screen orientation", e);
116     }
117   }
118 
119   @ReactMethod
getOrientationLockAsync(Promise promise)120   public void getOrientationLockAsync(Promise promise) {
121     Activity activity = getCurrentActivity();
122 
123     try {
124       int orientationAttr = activity.getRequestedOrientation();
125       OrientationLock orientationLock = orientationLockNativeToJS(orientationAttr);
126       promise.resolve(orientationLock.toString());
127     } catch (Exception e) {
128       promise.reject(ERR_SCREEN_ORIENTATION, "Could not get the current screen orientation lock", e);
129     }
130 
131   }
132 
133   @ReactMethod
getPlatformOrientationLockAsync(Promise promise)134   public void getPlatformOrientationLockAsync(Promise promise) {
135     Activity activity = getCurrentActivity();
136     try {
137       promise.resolve(activity.getRequestedOrientation());
138     } catch (Exception e) {
139       promise.reject(ERR_SCREEN_ORIENTATION, "Could not get the current screen orientation platform lock", e);
140     }
141 
142   }
143 
144   @ReactMethod
supportsOrientationLockAsync(String orientationLockStr, Promise promise)145   public void supportsOrientationLockAsync(String orientationLockStr, Promise promise){
146     try {
147       // If we can get the native orientation value from the given string without throwing, we resolve with true
148       OrientationLock lockJS = OrientationLock.valueOf(orientationLockStr);
149       orientationLockJSToNative(lockJS);
150       promise.resolve(true);
151     } catch (Exception e) {
152       promise.resolve(false);
153     }
154   }
155 
156   // https://stackoverflow.com/questions/10380989/how-do-i-get-the-current-orientation-activityinfo-screen-orientation-of-an-a
157   // Will not work in all cases as surface rotation is not standardized across android devices, but this is best effort
getScreenOrientation(Activity activity)158   private Orientation getScreenOrientation(Activity activity) {
159     WindowManager windowManager = activity.getWindowManager();
160     int rotation = windowManager.getDefaultDisplay().getRotation();
161     DisplayMetrics dm = new DisplayMetrics();
162     windowManager.getDefaultDisplay().getMetrics(dm);
163     int width = dm.widthPixels;
164     int height = dm.heightPixels;
165     Orientation orientation;
166     // if the device's natural orientation is portrait:
167     if ((rotation == Surface.ROTATION_0
168         || rotation == Surface.ROTATION_180) && height > width ||
169         (rotation == Surface.ROTATION_90
170             || rotation == Surface.ROTATION_270) && width > height) {
171       switch (rotation) {
172         case Surface.ROTATION_0:
173           orientation = Orientation.PORTRAIT_UP;
174           break;
175         case Surface.ROTATION_90:
176           orientation = Orientation.LANDSCAPE_LEFT;
177           break;
178         case Surface.ROTATION_180:
179           orientation = Orientation.PORTRAIT_DOWN;
180           break;
181         case Surface.ROTATION_270:
182           orientation = Orientation.LANDSCAPE_RIGHT;
183           break;
184         default:
185           orientation = Orientation.UNKNOWN;
186           break;
187       }
188     }
189 
190     // if the device's natural orientation is landscape or if the device
191     // is square:
192     else {
193       switch (rotation) {
194         case Surface.ROTATION_0:
195           orientation = Orientation.LANDSCAPE_LEFT;
196           break;
197         case Surface.ROTATION_90:
198           orientation = Orientation.PORTRAIT_DOWN;
199           break;
200         case Surface.ROTATION_180:
201           orientation = Orientation.LANDSCAPE_RIGHT;
202           break;
203         case Surface.ROTATION_270:
204           orientation = Orientation.PORTRAIT_UP;
205           break;
206         default:
207           orientation = Orientation.UNKNOWN;
208           break;
209       }
210     }
211     return orientation;
212   }
213 
214   public enum Orientation {
215     PORTRAIT,
216     PORTRAIT_UP,
217     PORTRAIT_DOWN,
218     LANDSCAPE,
219     LANDSCAPE_LEFT,
220     LANDSCAPE_RIGHT,
221     UNKNOWN;
222   }
223 
224   public enum OrientationLock {
225     DEFAULT,
226     ALL,
227     PORTRAIT,
228     PORTRAIT_UP,
229     PORTRAIT_DOWN,
230     LANDSCAPE,
231     LANDSCAPE_LEFT,
232     LANDSCAPE_RIGHT,
233     OTHER,
234     ALL_BUT_UPSIDE_DOWN; // deprecated
235   }
236 
orientationLockNativeToJS(int orientationAttr)237   private OrientationLock orientationLockNativeToJS(int orientationAttr) {
238     switch (orientationAttr) {
239       case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED:
240         return OrientationLock.DEFAULT;
241       case ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR:
242         return OrientationLock.ALL;
243       case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
244         return OrientationLock.PORTRAIT;
245       case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
246         return OrientationLock.PORTRAIT_UP;
247       case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT:
248         return OrientationLock.PORTRAIT_DOWN;
249       case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
250         return OrientationLock.LANDSCAPE;
251       case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
252         return OrientationLock.LANDSCAPE_LEFT;
253       case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
254         return OrientationLock.LANDSCAPE_RIGHT;
255       default:
256         return OrientationLock. OTHER;
257     }
258   }
259 
orientationLockJSToNative(OrientationLock orientationLock)260   private int orientationLockJSToNative(OrientationLock orientationLock) {
261     switch (orientationLock) {
262       case DEFAULT:
263         return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
264       case ALL:
265         return ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
266       case ALL_BUT_UPSIDE_DOWN:
267         return ActivityInfo.SCREEN_ORIENTATION_SENSOR;
268       case PORTRAIT:
269         return ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
270       case PORTRAIT_UP:
271         return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
272       case PORTRAIT_DOWN:
273         return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
274       case LANDSCAPE:
275         return ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
276       case LANDSCAPE_LEFT:
277         return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
278       case LANDSCAPE_RIGHT:
279         return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
280       default:
281         throw new InvalidArgumentException("OrientationLock " + orientationLock.toString() + " is not mapped to a native Android orientation attr");
282     }
283   }
284 }
285 
286