Force AutoRotation

In our project, you can turn the device to any orientation and allow AutoRotation to switch things around and its fine.

In one particular view, we would like to only allow Landscape views.

            Screen.autorotateToPortrait = false;
            Screen.autorotateToPortraitUpsideDown = false;

Turning the autorotate options off prevents switching to those options but it doesn’t force a switch away from those modes if the app was in one of the Portrait modes when we disabled it.

I’d like to keep AutoRotation on, and not force a specific Landscape rotation, but I haven’t found a way to let Unity know that I think it should update it’s AutoRotation.

Any suggestions?

Here is how I’ve ended up having to handle it
Open is an inherited function on my UI panels

When I want to force Landscape mode

public override void Open()
        {
            base.Open();

            Screen.autorotateToPortrait = false;
            Screen.autorotateToPortraitUpsideDown = false;

            if (Screen.orientation == ScreenOrientation.Portrait || Screen.orientation == ScreenOrientation.PortraitUpsideDown)
            {
                Screen.orientation = (Input.deviceOrientation == DeviceOrientation.LandscapeLeft) ? ScreenOrientation.LandscapeLeft : ScreenOrientation.LandscapeRight;
                StartCoroutine("WaittoReEnableAutoRotate");
            }
        }

        IEnumerator WaittoReEnableAutoRotate()
        {
            float endtime = Time.time + .75f;
            while (Time.time < endtime)
            {
                yield return null;
            }

            Screen.orientation = ScreenOrientation.AutoRotation;
        }

and when I want to return to a full autorotation

 public override void Open()
        {
            base.Open();

            Screen.autorotateToPortrait = true;
            Screen.autorotateToPortraitUpsideDown = true;


            if( (Input.deviceOrientation == DeviceOrientation.Portrait || Input.deviceOrientation == DeviceOrientation.PortraitUpsideDown) &&
                (Screen.orientation == ScreenOrientation.LandscapeLeft || Screen.orientation == ScreenOrientation.LandscapeRight))
            {
                Screen.orientation = (Input.deviceOrientation == DeviceOrientation.Portrait) ? ScreenOrientation.Portrait : ScreenOrientation.PortraitUpsideDown;
                StartCoroutine("WaittoReEnableAutoRotate");
            }
          
        }

        IEnumerator WaittoReEnableAutoRotate()
        {
            float endtime = Time.time + .75f;
            while (Time.time < endtime)
            {
                yield return null;
            }

            Screen.orientation = ScreenOrientation.AutoRotation;
        }

Currently having the same issue in 2020.3.8f1 (setting allowed rotations doesn’t force a reevaluation of the current rotation when it becomes disallowed), and eventually I tried a solution as proposed above- force to an allowed orientation, then wait before returning to auto.(AllowedRotations being my own enum-flagset; code is run whenever Screen.width/Screen.height changes frame-over-frame in Update)

    private void SetRotations(AllowedRotations allowedRotations)
    {
        Screen.autorotateToPortrait = ((int)allowedRotations & (int)AllowedRotations.Portrait) != 0;
        Screen.autorotateToPortraitUpsideDown = ((int)allowedRotations & (int)AllowedRotations.PortraitUpsideDown) != 0;
        Screen.autorotateToLandscapeLeft = ((int)allowedRotations & (int)AllowedRotations.LandscapeLeft) != 0;
        Screen.autorotateToLandscapeRight = ((int)allowedRotations & (int)AllowedRotations.LandscapeRight) != 0;

        if (_attemptForce)
        {

            // we can't force a re-execution of the internal autorotation,
            // or at least I haven't found that API,
            // so force orientation to the first sensible allowed value
            // and wait for the user to rotate correspondingly.
            if (Screen.autorotateToPortrait && Input.deviceOrientation == DeviceOrientation.Portrait) Screen.orientation = ScreenOrientation.Portrait;
            else if (Screen.autorotateToPortraitUpsideDown && Input.deviceOrientation == DeviceOrientation.PortraitUpsideDown) Screen.orientation = ScreenOrientation.PortraitUpsideDown;
            else if (Screen.autorotateToLandscapeLeft && Input.deviceOrientation == DeviceOrientation.LandscapeLeft) Screen.orientation = ScreenOrientation.LandscapeLeft;
            else if (Screen.autorotateToLandscapeRight && Input.deviceOrientation == DeviceOrientation.LandscapeRight) Screen.orientation = ScreenOrientation.LandscapeRight;
            else if (Screen.autorotateToPortrait) Screen.orientation = ScreenOrientation.Portrait;
            else if (Screen.autorotateToPortraitUpsideDown) Screen.orientation = ScreenOrientation.PortraitUpsideDown;
            else if (Screen.autorotateToLandscapeLeft) Screen.orientation = ScreenOrientation.LandscapeLeft;
            else if (Screen.autorotateToLandscapeRight) Screen.orientation = ScreenOrientation.LandscapeRight;


            // immediate return will return to the last internally-detected auto value,
            // even if that value is no longer permitted
            //Screen.orientation = ScreenOrientation.AutoRotation;
            StartCoroutine(ResetToAuto());
        }
    }

Unfortunately, it appears that the auto-orientation my device is returning to is the most recently “cached” autorotation from before I set the explicit rotation. E.g. if I am in portrait, force to landscape, coroutine-wait a few frames, then set back to auto, Unity immediately returns to portrait even though this is disallowed.

I then tried a more sophisticated solution- waiting until the device is in the forced orientation before releasing… and as of writing, that seems to either not change the orientation at all, or occasionally just throw everything into a mess where Screen width, height, orientation and deviceOrientation get into a change cascade which never stabilizes to a consistent representation of device state.

    bool IsRightSideUp()
    {
        bool yes = Screen.orientation == ScreenOrientation.AutoRotation || Screen.orientation == ScreenOrientation.Unknown;

        if (Screen.orientation == ScreenOrientation.Portrait && Input.deviceOrientation == DeviceOrientation.Portrait) yes = true;
        else if (Screen.orientation == ScreenOrientation.PortraitUpsideDown && Input.deviceOrientation == DeviceOrientation.PortraitUpsideDown) yes = true;
        else if (Screen.orientation == ScreenOrientation.LandscapeLeft && Input.deviceOrientation == DeviceOrientation.LandscapeLeft) yes = true;
        else if (Screen.orientation == ScreenOrientation.LandscapeRight && Input.deviceOrientation == DeviceOrientation.LandscapeRight) yes = true;

        return yes;
    }

    IEnumerator ResetToAuto()
    {
        while (!IsRightSideUp())
        {
            yield return new WaitForEndOfFrame();
        }
        Screen.orientation = ScreenOrientation.AutoRotation;
    }

So… there are a few possibilities here, obviously.

Unity could be bugged and not quite keeping screen orientation, autorotation, device rotation, and screen size in sync as far as they’re exposed to the developer on a given frame, meaning e.g. ScreenOrientation.Portrait does not always occur, when allowed, during DeviceOrientation.Portrait, and does not always mean that width<=height.

The (prototype) device I’m targeting could equally be bugged and not consistently exposing its orientation state to Unity, so Unity legitimately thinks it’s in portrait when the device is sideways.

However, one way or another, if I don’t try to force orientation, everything resolves correctly when the device is manually rotated to an allowed orientation, at which point, as per the OP, the allowed rotations are enforced.

Thus, it would remain very useful to simply be able to manually invoke whatever Unity does upon device rotation to recompute autorotation, assuming the API is too entrenched to allow disabling the current rotation to fall back on an allowed rotation.

So… I think I found a fix… and it’s dumb and counterintuitive.

It looks like no matter what you set Screen.orientation to, if you then or thereafter set it to auto, your earlier explicit orientation is ignored and the allowed flags may or may not also be ignored.

If, however, you set the “forced” Screen.orientation first and then update the flags, the orientation goes through and then autorotation also occurs in accordance with the flags even if auto is not what you set Screen.orientation to.

So… it may be that there is some undisclosed, or poorly documented, interplay between Screen.orientation and the Screen.autorotateTo flags where setting one implicitly drives new values on the others even if the old values were consistent. And this may be a bug or it may be a feature. But either way, revised one-shot code no longer requiring async/coroutine logic:

    [System.Serializable, System.Flags]
    enum AllowedRotations
    {
        Portrait = 1,
        PortraitUpsideDown = 2,
        LandscapeLeft = 4,
        LandscapeRight = 8,
    }

    private void SetRotations(AllowedRotations allowedRotations)
    {
        bool canPortrait = ((int)allowedRotations & (int)AllowedRotations.Portrait) != 0;
        bool canPortraitUpsideDown = ((int)allowedRotations & (int)AllowedRotations.PortraitUpsideDown) != 0;
        bool canLandscapeLeft = ((int)allowedRotations & (int)AllowedRotations.LandscapeLeft) != 0;
        bool canLandscapeRight = ((int)allowedRotations & (int)AllowedRotations.LandscapeRight) != 0;

        if (_attemptForce)
        {
            if (canPortrait && Input.deviceOrientation == DeviceOrientation.Portrait) Screen.orientation = ScreenOrientation.Portrait;
            else if (canPortraitUpsideDown && Input.deviceOrientation == DeviceOrientation.PortraitUpsideDown) Screen.orientation = ScreenOrientation.PortraitUpsideDown;
            else if (canLandscapeLeft && Input.deviceOrientation == DeviceOrientation.LandscapeLeft) Screen.orientation = ScreenOrientation.LandscapeLeft;
            else if (canLandscapeRight && Input.deviceOrientation == DeviceOrientation.LandscapeRight) Screen.orientation = ScreenOrientation.LandscapeRight;
            else if (canPortrait) Screen.orientation = ScreenOrientation.Portrait;
            else if (canPortraitUpsideDown) Screen.orientation = ScreenOrientation.PortraitUpsideDown;
            else if (canLandscapeLeft) Screen.orientation = ScreenOrientation.LandscapeLeft;
            else if (canLandscapeRight) Screen.orientation = ScreenOrientation.LandscapeRight;
        }

        Screen.autorotateToPortrait = canPortrait;
        Screen.autorotateToPortraitUpsideDown = canPortraitUpsideDown;
        Screen.autorotateToLandscapeLeft = canLandscapeLeft;
        Screen.autorotateToLandscapeRight = canLandscapeRight;
    }