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