How can I globally force screen orientation in Android?

Solution 1:

I tried kagronick's answer but couldn't make it work. After messing around for a bit I eventually got it working using a system overlay window and removing the LayoutParams that I found were all unnecessary. Here's my eventual solution:

orientationChanger = new LinearLayout(this);
// Using TYPE_SYSTEM_OVERLAY is crucial to make your window appear on top
// You'll need the permission android.permission.SYSTEM_ALERT_WINDOW
WindowManager.LayoutParams orientationLayout = new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, 0, PixelFormat.RGBA_8888);
// Use whatever constant you need for your desired rotation
orientationLayout.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR;

// Optional: Replace "Context" with "Service" when used in a service
WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
wm.addView(orientationChanger, orientationLayout);
orientationChanger.setVisibility(View.VISIBLE);

You can see my success in the app I released based on this: Force Dock Rotation.

Solution 2:

This can be done by creating a hidden system dialog. Its kind of a hack but its crazy enough to work.

    wm = (WindowManager) content.getSystemService(Service.WINDOW_SERVICE);

    orientationChanger = new LinearLayout(content);
    orientationChanger.setClickable(false);
    orientationChanger.setFocusable(false);
    orientationChanger.setFocusableInTouchMode(false);
    orientationChanger.setLongClickable(false);

    orientationLayout = new WindowManager.LayoutParams(
            LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
            windowType, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.RGBA_8888);

    wm.addView(orientationChanger, orientationLayout);
    orientationChanger.setVisibility(View.GONE);

    orientationLayout.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
    wm.updateViewLayout(orientationChanger, orientationLayout);
    orientationChanger.setVisibility(View.VISIBLE);

Solution 3:

As an aside, you can also globally change screen orientation without forcing it in unsupported apps. (I know this question is about overriding apps' preferences, but this is still useful and mostly relevant info.)

  1. Grant access to system settings (WRITE_SETTINGS) in AndroidManifest.xml

    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    
  2. Import Settings

    import android.provider.Settings;
    
  3. Ensure Android screen auto-rotation is disabled

    Settings.System.putInt(
        getContentResolver(),
        Settings.System.ACCELEROMETER_ROTATION,
        0
    );
    
  4. Set USER_ROTATION to the desired setting, which should be one of the ROTATION_ constants. These values represent screen rotations from the device's natural orientation, which could be either landscape or portrait.

    Settings.System.putInt(
        getContentResolver(),
        Settings.System.USER_ROTATION,
        Surface.ROTATION_0 //Or a different ROTATION_ constant
    );
    

Note that changes to USER_ROTATION persist even if your app is not running or is uninstalled. If I recall correctly, users can reset this value by manually disabling and enabling auto-rotation.

Solution 4:

Create a system view

Create an invisible View that always displays on top of other applications. Views can specify their own orientation preferences, so your view's orientation can be used to override the orientations of underlying views and applications.

Options

I'm aware of a couple of different view types that you can use to achieve this, each with its own disadvantages.

  1. System Overlay window (TYPE_SYSTEM_OVERLAY)

    Requires the SYSTEM_ALERT_WINDOW permission. (Add the following to AndroidManifest.xml.)

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    
  2. Toast window (TYPE_TOAST)

    No permission required, except on MIUI V8.

    Doesn't display over some window types, so their orientations mightn't be affected:

    • Incoming call dialog (TYPE_PRIORITY_PHONE)
    • Dreams (TYPE_DREAM)
    • Key guard / lock screen (TYPE_KEYGUARD_DIALOG, TYPE_KEYGUARD_SCRIM)

Instructions

  1. Create a View.
  2. Create a WindowManager.LayoutParams.
    1. Set type to value you chose above.
    2. Zero its width and height to prevent problems with applications that won't function when overlapped. (For example, Android won't let you press the "OK" button when you try to enable an accessibility service if a window is on top of it.)
    3. Set its screenOrientation to whichever orientation value you want.

Example

Implementation
public class ScreenOrientationEnforcer {

    private final View view;
    private final WindowManager windows;

    public ScreenOrientationEnforcer(Context context) {
        windows = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        view = new View(context);
    }

    public void start() {
        WindowManager.LayoutParams layout = generateLayout();
        windows.addView(view, layout);
        view.setVisibility(View.VISIBLE);
    }

    public void stop() {
        windows.removeView(view);
    }

    private WindowManager.LayoutParams generateLayout() {
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();

        //So we don't need a permission or activity
        //Note that this won't work on some devices running MIUI
        layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;

        //Just in case the window type somehow doesn't enforce this
        layoutParams.flags =
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;

        //Prevents breaking apps that detect overlying windows enabling
        //(eg UBank app, or enabling an accessibility service)
        layoutParams.width = 0;
        layoutParams.height = 0;

        //Try to make it completely invisible
        layoutParams.format = PixelFormat.TRANSPARENT;
        layoutParams.alpha = 0f;

        //The orientation to force
        layoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;

        return layoutParams;
    }

}
Usage
ScreenOrientationEnforcer screenOrientationEnforcer
    = new ScreenOrientationEnforcer(context);

//Force screen orientation
screenOrientationEnforcer.start();

//Stop forcing screen orientation
screenOrientationEnforcer.stop();

Disadvantages

  • From my experience, the view might persist if your app crashes or is killed. Restarting the device seems to work around this.

References

  • Mapping between window types and z-order: PhoneWindowManager.windowTypeToLayerLw
  • Permissions required for each window type: PhoneWindowManager.checkAddPermission