1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.view;
18
19import android.content.Context;
20import android.content.pm.PackageManager;
21import android.content.res.Configuration;
22import android.database.ContentObserver;
23import android.graphics.Point;
24import android.net.Uri;
25import android.os.AsyncTask;
26import android.os.Handler;
27import android.os.RemoteException;
28import android.os.UserHandle;
29import android.provider.Settings;
30import android.util.Log;
31import android.view.Display;
32import android.view.IWindowManager;
33import android.view.Surface;
34import android.view.WindowManagerGlobal;
35
36import com.android.internal.R;
37
38/**
39 * Provides helper functions for configuring the display rotation policy.
40 */
41public final class RotationPolicy {
42    private static final String TAG = "RotationPolicy";
43    private static final int CURRENT_ROTATION = -1;
44
45    public static final int NATURAL_ROTATION = Surface.ROTATION_0;
46
47    private RotationPolicy() {
48    }
49
50    /**
51     * Gets whether the device supports rotation. In general such a
52     * device has an accelerometer and has the portrait and landscape
53     * features.
54     *
55     * @param context Context for accessing system resources.
56     * @return Whether the device supports rotation.
57     */
58    public static boolean isRotationSupported(Context context) {
59        PackageManager pm = context.getPackageManager();
60        return pm.hasSystemFeature(PackageManager.FEATURE_SENSOR_ACCELEROMETER)
61                && pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)
62                && pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE)
63                && context.getResources().getBoolean(
64                        com.android.internal.R.bool.config_supportAutoRotation);
65    }
66
67    /**
68     * Returns the orientation that will be used when locking the orientation from system UI
69     * with {@link #setRotationLock}.
70     *
71     * If the device only supports locking to its natural orientation, this will be either
72     * Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE,
73     * otherwise Configuration.ORIENTATION_UNDEFINED if any orientation is lockable.
74     */
75    public static int getRotationLockOrientation(Context context) {
76        if (!areAllRotationsAllowed(context)) {
77            final Point size = new Point();
78            final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
79            try {
80                wm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, size);
81                return size.x < size.y ?
82                        Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
83            } catch (RemoteException e) {
84                Log.w(TAG, "Unable to get the display size");
85            }
86        }
87        return Configuration.ORIENTATION_UNDEFINED;
88    }
89
90    /**
91     * Returns true if the rotation-lock toggle should be shown in system UI.
92     */
93    public static boolean isRotationLockToggleVisible(Context context) {
94        return isRotationSupported(context) &&
95                Settings.System.getIntForUser(context.getContentResolver(),
96                        Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
97                        UserHandle.USER_CURRENT) == 0;
98    }
99
100    /**
101     * Returns true if rotation lock is enabled.
102     */
103    public static boolean isRotationLocked(Context context) {
104        return Settings.System.getIntForUser(context.getContentResolver(),
105                Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) == 0;
106    }
107
108    /**
109     * Enables or disables rotation lock from the system UI toggle.
110     */
111    public static void setRotationLock(Context context, final boolean enabled) {
112        final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION;
113        setRotationLockAtAngle(context, enabled, rotation);
114    }
115
116    /**
117     * Enables or disables rotation lock at a specific rotation from system UI.
118     */
119    public static void setRotationLockAtAngle(Context context, final boolean enabled,
120            final int rotation) {
121        Settings.System.putIntForUser(context.getContentResolver(),
122                Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
123                UserHandle.USER_CURRENT);
124
125        setRotationLock(enabled, rotation);
126    }
127
128    /**
129     * Enables or disables natural rotation lock from Accessibility settings.
130     *
131     * If rotation is locked for accessibility, the system UI toggle is hidden to avoid confusion.
132     */
133    public static void setRotationLockForAccessibility(Context context, final boolean enabled) {
134        Settings.System.putIntForUser(context.getContentResolver(),
135                Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0,
136                        UserHandle.USER_CURRENT);
137
138        setRotationLock(enabled, NATURAL_ROTATION);
139    }
140
141    private static boolean areAllRotationsAllowed(Context context) {
142        return context.getResources().getBoolean(R.bool.config_allowAllRotations);
143    }
144
145    private static void setRotationLock(final boolean enabled, final int rotation) {
146        AsyncTask.execute(new Runnable() {
147            @Override
148            public void run() {
149                try {
150                    IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
151                    if (enabled) {
152                        wm.freezeRotation(rotation);
153                    } else {
154                        wm.thawRotation();
155                    }
156                } catch (RemoteException exc) {
157                    Log.w(TAG, "Unable to save auto-rotate setting");
158                }
159            }
160        });
161    }
162
163    /**
164     * Registers a listener for rotation policy changes affecting the caller's user
165     */
166    public static void registerRotationPolicyListener(Context context,
167            RotationPolicyListener listener) {
168        registerRotationPolicyListener(context, listener, UserHandle.getCallingUserId());
169    }
170
171    /**
172     * Registers a listener for rotation policy changes affecting a specific user,
173     * or USER_ALL for all users.
174     */
175    public static void registerRotationPolicyListener(Context context,
176            RotationPolicyListener listener, int userHandle) {
177        context.getContentResolver().registerContentObserver(Settings.System.getUriFor(
178                Settings.System.ACCELEROMETER_ROTATION),
179                false, listener.mObserver, userHandle);
180        context.getContentResolver().registerContentObserver(Settings.System.getUriFor(
181                Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY),
182                false, listener.mObserver, userHandle);
183    }
184
185    /**
186     * Unregisters a listener for rotation policy changes.
187     */
188    public static void unregisterRotationPolicyListener(Context context,
189            RotationPolicyListener listener) {
190        context.getContentResolver().unregisterContentObserver(listener.mObserver);
191    }
192
193    /**
194     * Listener that is invoked whenever a change occurs that might affect the rotation policy.
195     */
196    public static abstract class RotationPolicyListener {
197        final ContentObserver mObserver = new ContentObserver(new Handler()) {
198            @Override
199            public void onChange(boolean selfChange, Uri uri) {
200                RotationPolicyListener.this.onChange();
201            }
202        };
203
204        public abstract void onChange();
205    }
206}