Rotation lock.

IWindowManager now supports two new methods,
freezeRotation() and thawRotation(), that allow a caller to
temporarily stash the device's current rotation as the
default rotation (when no other constraints are present).

The system bar uses this to implement a user-accessible
rotation lock by calling freezeRotation() and then turning
off accelerometer-based display rotation; unless overridden
by an app, the display will continue to appear in the frozen
rotation until the rotation is unlocked by the user (either
via the rotation lock icon in the system bar or by checking
"rotate screen automatically" in Settings).

Bug: 2949639
Change-Id: Icd21c169d1053719590e72401f229424b254622f
diff --git a/api/current.xml b/api/current.xml
index c7cddac..9173afd 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -161089,6 +161089,17 @@
  visibility="public"
 >
 </field>
+<field name="USER_ROTATION"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;user_rotation&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="USE_GOOGLE_MAIL"
  type="java.lang.String"
  transient="false"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2229964..9c72dec 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1573,6 +1573,16 @@
         public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation";
 
         /**
+         * Default screen rotation when no other policy applies.
+         * When {@link #ACCELEROMETER_ROTATION} is zero and no on-screen Activity expresses a
+         * preference, this rotation value will be used. Must be one of the
+         * {@link android.view.Surface#ROTATION_0 Surface rotation constants}. 
+         *
+         * @see Display#getRotation
+         */
+        public static final String USER_ROTATION = "user_rotation";
+
+        /**
          * Whether the audible DTMF tones are played by the dialer when dialing. The value is
          * boolean (1 or 0).
          */
@@ -1806,6 +1816,7 @@
             TIME_12_24,
             DATE_FORMAT,
             ACCELEROMETER_ROTATION,
+            USER_ROTATION,
             DTMF_TONE_WHEN_DIALING,
             DTMF_TONE_TYPE_WHEN_DIALING,
             EMERGENCY_TONE,
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index d4dd05c..a54f342 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -156,4 +156,17 @@
      * calls back when it changes.
      */
     int watchRotation(IRotationWatcher watcher);
+
+	/**
+	 * Lock the device orientation to the current rotation. Sensor input will
+	 * be ignored until thawRotation() is called.
+	 * @hide
+	 */
+	void freezeRotation();
+
+	/**
+	 * Release the orientation lock imposed by freezeRotation().
+	 * @hide
+	 */
+	void thawRotation();
 }
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 1a341e1..e78d6a8 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -362,6 +362,13 @@
      * modify the rotation.
      */
     public final int USE_LAST_ROTATION = -1000;
+
+    /** When not otherwise specified by the activity's screenOrientation, rotation should be
+     * determined by the system (that is, using sensors). */
+    public final int USER_ROTATION_FREE = 0;
+    /** When not otherwise specified by the activity's screenOrientation, rotation is set by
+     * the user. */
+    public final int USER_ROTATION_LOCKED = 1;
     
     /**
      * Perform initialization of the policy.
@@ -787,4 +794,14 @@
      * Return false to disable key repeat events from being generated.
      */
     public boolean allowKeyRepeat();
+
+    /**
+     * Inform the policy that the user has chosen a preferred orientation ("rotation lock"). 
+     *
+     * @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or
+     *             {@link * WindowManagerPolicy#USER_ROTATION_FREE}. 
+     * @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+     *                 {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}. 
+     */
+    public void setUserRotationMode(int mode, int rotation);
 }
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 8de507e..8a6e82d 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -103,4 +103,6 @@
             0x120016=0x03050601:0x03040501:0x03030401:0x03020301:0x03070301:0x03010301:0x03000301;
     </string>
 
+    <!-- Default for Settings.System.USER_ROTATION -->
+    <integer name="def_user_rotation">0</integer>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 9ac832b..593edc8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -61,7 +61,7 @@
     // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
     // is properly propagated through your change.  Not doing so will result in a loss of user
     // settings.
-    private static final int DATABASE_VERSION = 59;
+    private static final int DATABASE_VERSION = 60;
 
     private Context mContext;
 
@@ -775,6 +775,23 @@
             upgradeVersion = 59;
         }
 
+        if (upgradeVersion == 59) {
+            // Persistence for the rotation lock feature.
+            db.beginTransaction();
+            SQLiteStatement stmt = null;
+            try {
+                stmt = db.compileStatement("INSERT INTO system(name,value)"
+                        + " VALUES(?,?);");
+                loadBooleanSetting(stmt, Settings.System.USER_ROTATION,
+                        R.integer.def_user_rotation); // should be zero degrees
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+                if (stmt != null) stmt.close();
+            }
+            upgradeVersion = 60;
+        }
+
         // *** Remember to update DATABASE_VERSION above!
 
         if (upgradeVersion != currentVersion) {
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off_lanscape.png b/packages/SystemUI/res/drawable-land-mdpi/ic_sysbar_rotate_off.png
similarity index 100%
rename from packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off_lanscape.png
rename to packages/SystemUI/res/drawable-land-mdpi/ic_sysbar_rotate_off.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off.png
new file mode 100644
index 0000000..9e6793a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off_portrait.png b/packages/SystemUI/res/drawable-port-mdpi/ic_sysbar_rotate_off.png
similarity index 100%
rename from packages/SystemUI/res/drawable-mdpi/ic_sysbar_rotate_off_portrait.png
rename to packages/SystemUI/res/drawable-port-mdpi/ic_sysbar_rotate_off.png
Binary files differ
diff --git a/packages/SystemUI/res/values-land/strings.xml b/packages/SystemUI/res/values-land/strings.xml
new file mode 100644
index 0000000..e05e36b
--- /dev/null
+++ b/packages/SystemUI/res/values-land/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Rotation lock toast text: shown when rotation lock is turned on in landscape orientation.
+         -->
+    <string name="toast_rotation_locked">Screen is now locked in landscape orientation.</string>
+</resources>
diff --git a/packages/SystemUI/res/values-port/strings.xml b/packages/SystemUI/res/values-port/strings.xml
new file mode 100644
index 0000000..d3ab67b
--- /dev/null
+++ b/packages/SystemUI/res/values-port/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Rotation lock toast text: shown when rotation lock is turned on in portrait orientation.
+         -->
+    <string name="toast_rotation_locked">Screen is now locked in portrait orientation.</string>
+</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 701aa9f..f34dc22 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -84,5 +84,11 @@
      -->
     <string name="recent_tasks_empty">No recent applications.</string>
 
+    <!-- Rotation lock toast text: shown when rotation lock is turned off (and the screen will
+         auto-rotate based on the accelerometer). -->
+    <string name="toast_rotation_free">Screen will rotate automatically.</string>
     
+    <!-- Rotation lock toast text: shown when rotation lock is turned on and the orientation is
+         undefined.  -->
+    <string name="toast_rotation_locked">Screen rotation is now locked.</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java
index a0f5be6..b4a9d76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/SystemPanel.java
@@ -55,6 +55,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.IWindowManager;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
 import android.widget.Button;
@@ -74,6 +75,8 @@
 import com.android.internal.telephony.cdma.EriInfo;
 import com.android.internal.telephony.cdma.TtyIntent;
 
+import com.android.server.WindowManagerService;
+
 import com.android.systemui.statusbar.*;
 import com.android.systemui.R;
 
@@ -102,6 +105,8 @@
     private TextView mBatteryText;
     private TextView mSignalText;
 
+    private final IWindowManager mWM;
+
     private final AudioManager mAudioManager;
     private final WifiManager mWifiManager;
     private final TelephonyManager mPhone;
@@ -397,6 +402,11 @@
     public SystemPanel(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
+        // our mighty overlord
+        mWM = IWindowManager.Stub.asInterface(
+                    ServiceManager.getService("window"));
+
+
         // get notified of phone state changes
         TelephonyManager telephonyManager =
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
@@ -437,6 +447,7 @@
         });
 
         mSoundButton = (ImageButton)findViewById(R.id.sound);
+        mSoundButton.setAlpha(getSilentMode() ? 0x7F : 0xFF);
         mSoundButton.setOnClickListener(new View.OnClickListener() {
             public void onClick(View v) {
                 setSilentMode(!getSilentMode());
@@ -444,9 +455,22 @@
             }
         });
         mOrientationButton = (ImageButton)findViewById(R.id.orientation);
+        mOrientationButton.setImageResource(
+            getAutoRotate()
+                ? R.drawable.ic_sysbar_rotate_on
+                : R.drawable.ic_sysbar_rotate_off);
         mOrientationButton.setOnClickListener(new View.OnClickListener() {
             public void onClick(View v) {
-                Toast.makeText(getContext(), "Orientation control not implemented; please adjust neck angle.", Toast.LENGTH_SHORT).show();
+                setAutoRotate(!getAutoRotate());
+                mOrientationButton.setImageResource(
+                    getAutoRotate()
+                        ? R.drawable.ic_sysbar_rotate_on
+                        : R.drawable.ic_sysbar_rotate_off);
+                Toast.makeText(getContext(), 
+                    getAutoRotate() 
+                        ? R.string.toast_rotation_free
+                        : R.string.toast_rotation_locked,
+                    Toast.LENGTH_SHORT).show();
             }
         });
 
@@ -709,4 +733,31 @@
                                          ? R.drawable.sysbar_toggle_bg_on
                                          : R.drawable.sysbar_toggle_bg_off);
     }
+
+    void setAutoRotate(boolean rot) {
+        try {
+            ContentResolver cr = getContext().getContentResolver();
+            if (rot) {
+                mWM.thawRotation();
+            } else {
+                mWM.freezeRotation();
+            }
+        } catch (android.os.RemoteException exc) {
+        }
+    }
+
+    boolean getAutoRotate() {
+        ContentResolver cr = getContext().getContentResolver();
+        return 1 == Settings.System.getInt(cr,
+                Settings.System.ACCELEROMETER_ROTATION,
+                1);
+    }
+
+    int getDisplayRotation() {
+        try {
+            return mWM.getRotation();
+        } catch (android.os.RemoteException exc) {
+            return 0;
+        }
+    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index af1bf59..2f2f943 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -230,6 +230,10 @@
     int mLidOpenRotation;
     int mCarDockRotation;
     int mDeskDockRotation;
+
+    int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
+    int mUserRotation = Surface.ROTATION_0;
+
     boolean mCarDockEnablesAccelerometer;
     boolean mDeskDockEnablesAccelerometer;
     int mLidKeyboardAccessibility;
@@ -336,6 +340,8 @@
             resolver.registerContentObserver(Settings.System.getUriFor(
                     Settings.System.ACCELEROMETER_ROTATION), false, this);
             resolver.registerContentObserver(Settings.System.getUriFor(
+                    Settings.System.USER_ROTATION), false, this);
+            resolver.registerContentObserver(Settings.System.getUriFor(
                     Settings.System.SCREEN_OFF_TIMEOUT), false, this);
             resolver.registerContentObserver(Settings.System.getUriFor(
                     Settings.System.POINTER_LOCATION), false, this);
@@ -678,10 +684,20 @@
                     "fancy_rotation_anim", 0) != 0 ? 0x80 : 0;
             int accelerometerDefault = Settings.System.getInt(resolver,
                     Settings.System.ACCELEROMETER_ROTATION, DEFAULT_ACCELEROMETER_ROTATION);
+            
+            // set up rotation lock state
+            mUserRotationMode = (mAccelerometerDefault == 0)
+                ? WindowManagerPolicy.USER_ROTATION_LOCKED
+                : WindowManagerPolicy.USER_ROTATION_FREE;
+            mUserRotation = Settings.System.getInt(resolver,
+                    Settings.System.USER_ROTATION,
+                    Surface.ROTATION_0);
+
             if (mAccelerometerDefault != accelerometerDefault) {
                 mAccelerometerDefault = accelerometerDefault;
                 updateOrientationListenerLp();
             }
+
             if (mSystemReady) {
                 int pointerLocation = Settings.System.getInt(resolver,
                         Settings.System.POINTER_LOCATION, 0);
@@ -2280,6 +2296,8 @@
                 return mCarDockRotation;
             } else if (mDockMode == Intent.EXTRA_DOCK_STATE_DESK && mDeskDockRotation >= 0) {
                 return mDeskDockRotation;
+            } else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED) {
+                return mUserRotation;
             } else {
                 if (useSensorForOrientationLp(orientation)) {
                     return mOrientationListener.getCurrentRotation(lastRotation);
@@ -2323,6 +2341,26 @@
         return sensorRotation == mPortraitRotation || sensorRotation == mUpsideDownRotation;
     }
 
+
+    // User rotation: to be used when all else fails in assigning an orientation to the device
+    public void setUserRotationMode(int mode, int rot) {
+        ContentResolver res = mContext.getContentResolver();
+        mUserRotationMode = mode;
+        if (mode == WindowManagerPolicy.USER_ROTATION_LOCKED) {
+            mUserRotation = rot;
+            Settings.System.putInt(res,
+                    Settings.System.ACCELEROMETER_ROTATION,
+                    0);
+            Settings.System.putInt(res,
+                    Settings.System.USER_ROTATION,
+                    rot);
+        } else {
+            Settings.System.putInt(res,
+                    Settings.System.ACCELEROMETER_ROTATION,
+                    1);
+        }
+    }
+
     public boolean detectSafeMode() {
         try {
             int menuState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_MENU);
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index dbc7f48..41b3da6 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -4842,6 +4842,26 @@
         }
     }
 
+    public void freezeRotation() {
+        if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
+                "setRotation()")) {
+            throw new SecurityException("Requires SET_ORIENTATION permission");
+        }
+
+        mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED, mRotation);
+        setRotationUnchecked(WindowManagerPolicy.USE_LAST_ROTATION, false, 0);
+    }
+
+    public void thawRotation() {
+        if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
+                "setRotation()")) {
+            throw new SecurityException("Requires SET_ORIENTATION permission");
+        }
+
+        mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE, 0);
+        setRotationUnchecked(WindowManagerPolicy.USE_LAST_ROTATION, false, 0);
+    }
+
     public void setRotation(int rotation,
             boolean alwaysSendConfiguration, int animFlags) {
         if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,