I am using
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
for my Activity and I am trying to use OrientationEventListener to find the orientation of the device. On some devices my landscape orientation is showing values around 270 and other devices shows values around 0. On every device where the Home button is placed the orientation value shows 0. I want to have same values when holding the all devices in landscape mode. Is there a flag that can show me which device is showing 270 or 0 for landscape mode as default orientation.
This is my OrientationEventListener
mOrientationListener = new OrientationEventListener(this,
SensorManager.SENSOR_DELAY_NORMAL) {
#Override
public void onOrientationChanged(int orientation) {
Log.v("Orientation changed to ", String.valueOf(orientation));
}
};
Is there a trick that can help to resolve the issue?
I have found the solution.
With this code I am getting the default display rotation:
int rotation = getWindowManager().getDefaultDisplay().getRotation();
in the body of my onOrientationChanged method I can make this:
if (rotation == 0) {
if (orientation >= 90 && orientation <= 270) {
}
} else if (rotation == 1) {
if (orientation < 180 && orientation > 0 ) {
}
}
This will enter the if only if my device is not in the correct landscape position. If my device is on 90+ angle I can give the user a message.
Related
I want to achieve the way youtube handles its video orientation.
There are a few things to have in mind before just saying that I have to use onConfigurationChanged.
THINGS TO CONSIDER
The phone orientation. This means the way you hold de phone. It could be PORTRAIT or LANDSCAPE
The screen orientation. This means the way the view is displayed. It could be PORTRAIT or LANDSCAPE regardless of the orientation of the phone
HOW YOUTUBE ROTATION WORKS?
If the phone is in PORTRAIT mode (using OS quick settings)
The screen will keep in PORTRAIT. It doesn't matter if you rotate the phone
The only way to rotate de screen is by pressing the rotate button.
The screen will keep in the last orientation chooses, and it will not change even if you rotate the phone. PORTRAIT will remain PORTRAIT, and LANDSCAPE will remain LANDSCAPE.
If the phone is in AUTO-ROTATE mode
if you rotate the phone, then the screen rotation follows it. I mean, if the phone is in PORTRAIT, the screen will be PORTRAIT. And the same happens with LANDSCAPE.
if the rotate button is pressed, the screen orientation will change regardless of the phone orientation. In this case, the only way to listen to the sensor orientation is to rotate the phone to the same side of the screen rotation and go back. This is to let the phone sensor have control of the orientation again.
WHAT I HAVE?
With onConfigurationChanged I could know when I rotate the phone (if AUTO-ROTATE is enabled) and define the layout:
override fun onConfigurationChanged(newConfig: Configuration) {
when (newConfig.orientation) {
Configuration.ORIENTATION_PORTRAIT -> buildPortrait()
Configuration.ORIENTATION_LANDSCAPE -> buildLandscape()
}
super.onConfigurationChanged(newConfig)
}
With the rotation button, I can handle screen orientation (when the phone is in PORTRAIT BLOCKED from OS)
fun rotate() {
val orientation = requireActivity().requestedOrientation
val isRotationOn = Settings.System.getInt(requireActivity().contentResolver,
Settings.System.ACCELEROMETER_ROTATION, 0) == 1
requireActivity().requestedOrientation = if (isRotationOn) {
// AUTO-ROTATE enabled
TODO()
} else
// PORTRAIT BLOCKED
if (orientation == PORTRAIT) {
buildLandscape()
LANDSCAPE
} else {
buildPortrait()
PORTRAIT
}
}
WHAT I CAN'T ACHIEVE?
I don't know how to handle screen rotation when I have the AUTO-ROTATE enabled that handles the rotation by the sensor, and at the same time, there is a rotate button that does a rotation regardless of the AUTO-ROTATE.
IMPORTANT: I can't find any example with the solution anywhere.
You need to use OrientationEventListener to listen to orientation change and set requestedOrientation back to ActivityInfo.SCREEN_ORIENTATION_USER when orientation changed.
The orientation value passed in onOrientationChanged is from 0 to 359 and -1 means flat. You need to map it into 4 directions according to the following code.
orientationEventListener = object: OrientationEventListener(this) {
override fun onOrientationChanged(orientation: Int) {
val newOrientation = when (orientation) {
in 0 .. 44 -> 0
in 45 .. 134 -> 1
in 135 .. 224 -> 2
in 225 .. 314 -> 3
in 315 .. 359 -> 0
else -> ORIENTATION_UNKNOWN
}
if (newOrientation != previousOrientation) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
}
previousOrientation = newOrientation
}
}
And remember to only enable the above listener if auto-rotate setting is on
val autoRotationOn = Settings.System.getInt(contentResolver,
Settings.System.ACCELEROMETER_ROTATION, 0) == 1
if (autoRotationOn) {
orientationEventListener.enable()
} else {
orientationEventListener.disable()
}
As the auto-rotate setting could be changed within the lifecycle of current activity, you have to listen to the value change and enable/disable the orientationEventListener.
private val contentObserver = object:ContentObserver(handler) {
override fun onChange(selfChange: Boolean) {
super.onChange(selfChange)
setupOrientationEventListener()
}
}
val settingUri = Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION)
contentResolver.registerContentObserver(settingUri, false, contentObserver)
From Multi-Window documentation:
Disabled features in multi-window mode
Certain features are disabled or ignored when a device is in multi-window mode, because they don’t make sense for an activity which may be sharing the device screen with other activities or apps. Such features include:
Some System UI customization options are disabled; for example, apps cannot hide the status bar if they are not running in full-screen mode.
The system ignores changes to the android:screenOrientation attribute.
I get that for most apps it doesn't make sense to distinct between portrait and landscape modes, however I am working on SDK which contains camera view which user can put on any activity they wish - including activity that supports multi-window mode. The problem is that camera view contains SurfaceView/TextureView which displays the camera preview and in order to display preview correctly in all activity orientations, knowledge about correct activity orientation is required so that camera preview can be correctly rotated.
The problem is that my code which calculates correct activity orientation by examining current configuration orientation (portrait or landscape) and current screen rotation. The problem is that in multi-window mode current configuration orientation does not reflect the real activity orientation. This then results with camera preview being rotated by 90 degrees because Android reports different configuration than orientation.
My current workaround is to check for requested activity orientation and use that as a basis, but there are two problems with that:
the requested activity orientation does not have to reflect actual activity orientation (i.e. request may still not be fulfilled)
the requested activity orientation can be 'behind', 'sensor', 'user', etc. which does not reveal any information about current activity orientation.
According to documentation, screen orientation is actually ignored in multi-window mode, so 1. and 2. just won't work
Is there any way to robustly calculate correct activity orientation even in multi-window configuration?
Here is my code that I currently use (see comments for problematic parts):
protected int calculateHostScreenOrientation() {
int hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
int rotation = getDisplayOrientation(wm);
boolean activityInPortrait;
if ( !isInMultiWindowMode() ) {
activityInPortrait = (mConfigurationOrientation == Configuration.ORIENTATION_PORTRAIT);
} else {
// in multi-window mode configuration orientation can be landscape even if activity is actually in portrait and vice versa
// Try determining from requested orientation (not entirely correct, because the requested orientation does not have to
// be the same as actual orientation (when they differ, this means that OS will soon rotate activity into requested orientation)
// Also not correct because, according to https://developer.android.com/guide/topics/ui/multi-window.html#running this orientation
// is actually ignored.
int requestedOrientation = getHostActivity().getRequestedOrientation();
if ( requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT ||
requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT ||
requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT ||
requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT ) {
activityInPortrait = true;
} else if ( requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ||
requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE ||
requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE ||
requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE ) {
activityInPortrait = false;
} else {
// what to do when requested orientation is 'behind', 'sensor', 'user', etc. ?!?
activityInPortrait = true; // just guess
}
}
if ( activityInPortrait ) {
Log.d(this, "Activity is in portrait");
if (rotation == Surface.ROTATION_0) {
Log.d(this, "Screen orientation is 0");
hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
} else if (rotation == Surface.ROTATION_180) {
Log.d(this, "Screen orientation is 180");
hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
} else if (rotation == Surface.ROTATION_270) {
Log.d(this, "Screen orientation is 270");
// natural display rotation is landscape (tablet)
hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
} else {
Log.d(this, "Screen orientation is 90");
// natural display rotation is landscape (tablet)
hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
}
} else {
Log.d(this, "Activity is in landscape");
if (rotation == Surface.ROTATION_90) {
Log.d(this, "Screen orientation is 90");
hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
} else if (rotation == Surface.ROTATION_270) {
Log.d(this, "Screen orientation is 270");
hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
} else if (rotation == Surface.ROTATION_0) {
Log.d(this, "Screen orientation is 0");
// natural display rotation is landscape (tablet)
hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
} else {
Log.d(this, "Screen orientation is 180");
// natural display rotation is landscape (tablet)
hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
}
}
return hostScreenOrientation;
}
private int getDisplayOrientation(WindowManager wm) {
if (DeviceManager.getSdkVersion() < 8) {
return wm.getDefaultDisplay().getOrientation();
}
return wm.getDefaultDisplay().getRotation();
}
private boolean isInMultiWindowMode() {
return Build.VERSION.SDK_INT >= 24 && getHostActivity().isInMultiWindowMode();
}
protected Activity getHostActivity() {
Context context = getContext();
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
return (Activity) context;
}
context = ((ContextWrapper) context).getBaseContext();
}
return null;
}
EDIT: I've reported this also to Android issue tracker.
I don’t know if this should be considered a solution or just a workaround.
As you say, your problems come with Android N and its multi-window mode. When the app is in multi window, your Activity is not tied to the full display dimensions. This redefines the concept of Activity orientation. Quoting Ian Lake:
Turns out: “portrait” really just means the height is greater than the
width and “landscape” means the width is greater than the height. So
it certainly makes sense, with that definition in mind, that your app
could transition from one to the other while being resized.
So there is no link anymore between Activity orientation changing and device physically being rotated. (I think the only reasonable use of Activity orientation changes now is to update your resources.)
Since you are interested in device dimensions, just get its DisplayMetrics. Quoting docs,
If requested from non-Activity context
metrics will report the size of the entire display based on current
rotation and with subtracted system decoration areas.
So the solution is:
final Context app = context.getApplicationContext();
WindowManager manager = (WindowManager) app.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
int width = metrics.widthPixels;
int height = metrics.heightPixels;
boolean portrait = height >= width;
Width and height values will be swapped (more or less) when the device is tilted.
If this works, I would personally run it every time, deleting the isInMultiWindowMode() branch, because
it’s not expensive
our assumptions stand also in the non-multi-window mode
it will presumably work well with any other future kinds of modes
you avoid the race condition of isInMultiWindowMode() described by CommonsWare
I thought you could utilise the accelerometer to detect where's "down" - and thus the orientation of the phone. The Engineer Guy explains that that's the way the phone itself does it.
I searched here on SO for a way to do that and found this answer. Basically you need to check which of the 3 accelerometers detect the most significant component of the gravitational pull, which you know is roughly 9.8m/s² near the ground of the earth. Here's the code snippet from it:
private boolean isLandscape;
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
mSensorManager.registerListener(mSensorListener, mSensorManager.getDefaultSensor(
Sensor.TYPE_ACCELEROMETER),1000000);
private final SensorEventListener mSensorListener = new SensorEventListener() {
#Override
public void onSensorChanged(SensorEvent mSensorEvent) {
float X_Axis = mSensorEvent.values[0];
float Y_Axis = mSensorEvent.values[1];
if((X_Axis <= 6 && X_Axis >= -6) && Y_Axis > 5){
isLandscape = false;
}
else if(X_Axis >= 6 || X_Axis <= -6){
isLandscape = true;
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
Be careful as this code might not work in every situation, as you need to take into account scenarios like being on a stopping/accelerating train or moving the phone fast in a game - here's the orders of magnitude page on wiki to get you started. It looks like you're safe with the values Khalil put in his code (in his answer), but I would take extra caution and research into what values might be generated in the different scenarios.
It's not a flawless idea, but I think as long as the API is built the way it is - whithout allowing you to get their calculated orientation - I think it's a beneficial workaround.
I am trying to mimic the behavior of the YouTube Android app when the "fullscreen" button is clicked in the video player:
If device is currently in portrait, immediately rotate to landscape (even if user is still holding the device in portrait) and remain in landscape until user rotates device to landscape and then rotates back to portrait
If device is currently in landscape, immediately rotate to portrait (Even if user is still holding the device in portrait) and remain in portrait until the user rotates the device to portrait and then rotates back to landscape.
At anytime, allow the user to manually rotate their device to the desired orientation.
It seems that if I force the rotation to landscape or portrait using:
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
or
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
... I immediately lose the ability to detect sensor orientation changes (i.e. once the user is in landscape, and they want to manually rotate the device back to portrait).
If I change the requested orientation to unspecified or sensor in onConfigurationChanged, the orientation briefly flips to landscape/portrait (whatever I requested from above) and then snaps back to the orientation that matches how the device is held.
Any thoughts on how to achieve my goals above?
I had the excact same problem. What I ended up with was using an OrientationListener to detect when the user had actually tilted the phone to landscape and then setting the orientation to SCREEN_ORIENTATION_SENSOR.
OrientationEventListener orientationEventListener =
new OrientationEventListener(getActivity()) {
#Override
public void onOrientationChanged(int orientation) {
int epsilon = 10;
int leftLandscape = 90;
int rightLandscape = 270;
if(epsilonCheck(orientation, leftLandscape, epsilon) ||
epsilonCheck(orientation, rightLandscape, epsilon)){
getMainActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
}
private boolean epsilonCheck(int a, int b, int epsilon) {
return a > b - epsilon && a < b + epsilon;
}
};
orientationEventListener.enable();
Here is the documentation for OrientationEventListener : Documentation
You would also need to add checks for portrait , because you described needing that in your original post.
Big tnx to havch
It is gold, I was stuck on that for 5 hours. Here is my kotlin piece of code to deal with it.
orientationEventListener = object: OrientationEventListener(this) {
override fun onOrientationChanged(orientation: Int) {
val isPortrait = orientation > 300 || orientation < 60 || orientation in 120..240
if ((requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && isPortrait) ||
(requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE && !isPortrait)){
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
}
}
orientationEventListener.enable()
This modification of Alexander code is working better for me
object : OrientationEventListener(requireContext()) {
override fun onOrientationChanged(orientation: Int) {
val isPortrait = orientation > 345 || orientation < 15 || orientation in 165..195
val isLandscape = orientation in 255..285 || orientation in 75..105
if (
(requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && isPortrait) ||
(requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE && isLandscape)
) {
lifecycleScope.launch {
// adding a delay to avoid orientation change glitch
delay(200)
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
}
}
}
/**
* -1 -> Unknown
* 1 -> Portrait
* 0 -> Landscape
*/
var previousOrientation = -1
val orientationEventListener: OrientationEventListener =
object : OrientationEventListener(this) {
override fun onOrientationChanged(orientation: Int) {
val isPortrait = (orientation > 340 || orientation < 20 || orientation in 160..200) &&
requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
val isLandscape = (orientation in 250..290 || orientation in 70..110) &&
requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
if (isPortrait || isLandscape) {
lifecycleScope.launch {
if (previousOrientation == -1) {
previousOrientation = if (isPortrait) 1 else 0
}
delay(700)
val currentOrientation = if (isPortrait) 1 else 0
if (previousOrientation == currentOrientation) {
previousOrientation = -1
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
}
}
}
}
orientationEventListener.enable()
This one handles the orientation glitch more properly.
this code is improved version of what #Buntupana posted
Try to setRequestedOrientation to SCREEN_ORIENTATION_SENSOR when exit from fullscreen !
setting OrientationEventListener may cause some unexpected resources usage.
My application is an augmented reality application and i draw icons in the camera's surfaceview. The icons where drawn according to the result of the compass. The application is locked in landscape mode. In my device which has portrait mode as default orientation and everything works fine.
Recently i tested the app in Galaxy Nexus 10 and i found that the icons were moving upwards and downwards while i was moving the device right and left respectively. The opposite happens when i move the device right and left. What is going wrong. My guess is that the nexus has a landscape default orientation and the problem comes from the remapCoordinateSystem(). I added the next code before the remapCoordinateSystem but nothing happened.
int defaultRotation = getRotation();
switch(defaultRotation){
case Configuration.ORIENTATION_LANDSCAPE:
SensorManager.remapCoordinateSystem(temp, SensorManager.AXIS_X,
SensorManager.AXIS_MINUS_Z, rotation);
break;
case Configuration.ORIENTATION_PORTRAIT:
SensorManager.remapCoordinateSystem(temp, SensorManager.AXIS_Y,
SensorManager.AXIS_MINUS_Z, rotation);
break;
}
private int getRotation() {
WindowManager lWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
Configuration cfg = getResources().getConfiguration();
int lRotation = lWindowManager.getDefaultDisplay().getRotation();
if( (((lRotation == Surface.ROTATION_0) ||(lRotation == Surface.ROTATION_180)) &&
(cfg.orientation == Configuration.ORIENTATION_LANDSCAPE)) ||
(((lRotation == Surface.ROTATION_90) ||(lRotation == Surface.ROTATION_270)) &&
(cfg.orientation == Configuration.ORIENTATION_PORTRAIT))){
return Configuration.ORIENTATION_LANDSCAPE;
}
return Configuration.ORIENTATION_PORTRAIT;
}
getRotation() tells you how the device is rotated with respect to it's 'normal' orientation.
You need to find out what 'normal' is.
There may be a better way to do this, but a 'quick and dirty' method would be to inspect the display's width and height with display.getSize(Point point) - if the width is greater than the height, then 'normal' mode is landscape, otherwise portrait. Then you can look at rotation to see how the current rotation differs from that.
You would think that there would be a straight forward solution. The Android docs state:
The orientation sensor was deprecated in Android 2.2 (API level 8).
Instead of using raw data from the orientation sensor, we recommend
that you use the getRotationMatrix() method in conjunction with the
getOrientation() method to compute orientation values.
Yet, they don't provide a solution on how to implement getOrientation() and getRotationMatrix(). I've spent several hours reading through posts here on developers using these methods but they all have partially pasted code or some weird implementation. Googling hasn't provided a tutorial. Can someone please paste a simple solution using these two methods to generate the orientation??
Here is the implementation for getOrientation():
public int getscrOrientation()
{
Display getOrient = getWindowManager().getDefaultDisplay();
int orientation = getOrient.getOrientation();
// Sometimes you may get undefined orientation Value is 0
// simple logic solves the problem compare the screen
// X,Y Co-ordinates and determine the Orientation in such cases
if(orientation==Configuration.ORIENTATION_UNDEFINED){
Configuration config = getResources().getConfiguration();
orientation = config.orientation;
if(orientation==Configuration.ORIENTATION_UNDEFINED){
//if height and widht of screen are equal then
// it is square orientation
if(getOrient.getWidth()==getOrient.getHeight()){
orientation = Configuration.ORIENTATION_SQUARE;
}else{ //if widht is less than height than it is portrait
if(getOrient.getWidth() < getOrient.getHeight()){
orientation = Configuration.ORIENTATION_PORTRAIT;
}else{ // if it is not any of the above it will definitely be landscape
orientation = Configuration.ORIENTATION_LANDSCAPE;
}
}
}
}
return orientation; // return value 1 is portrait and 2 is Landscape Mode
}
And you can also refer this example which represent the use of both the methods:
getOrientation and getRotationMatrix
http://www.codingforandroid.com/2011/01/using-orientation-sensors-simple.html
public int getScreenOrientation() {
// Query what the orientation currently really is.
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
return 1; // Portrait Mode
}else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
return 2; // Landscape mode
}
return 0;
}
protected void onResume() {
// change the screen orientation
if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
setContentView(R.layout.portrait);
} else if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
setContentView(R.layout.landscape);
} else {
setContentView(R.layout.oops);
}
}