Add the Allow Rotation setting to Launcher3.

This CL adds a Settings activity along with the code needed to provide
a "Allow Rotation" setting to all phones and tablets. This setting is
set to false for phones and true for tablets. On changing the setting
from unlocked to locked, the launcher (and the Settings activity)
will get locked to the orientation the user was in when he disabled
"Allow Rotation". This is consistent with how the natural rotation
feature of Android works.

Change-Id: I8a1c59d1fa0bb9262530cad96e0a9bdbab0d9344
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b61b90c..9d8a76a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -148,6 +148,12 @@
             </intent-filter>
         </activity>
 
+        <activity
+            android:name="com.android.launcher3.SettingsActivity"
+            android:label="@string/settings_button_text"
+            android:autoRemoveFromRecents="true">
+        </activity>
+
         <!-- Debugging tools -->
         <activity
             android:name="com.android.launcher3.MemoryDumpActivity"
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
index 3f90203..659c306 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperPickerActivity.java
@@ -1132,6 +1132,11 @@
 
     @Override
     public boolean enableRotation() {
-        return Utilities.isRotationEnabled(getContext());
+        // Check if rotation is enabled for this device.
+        if (Utilities.isRotationAllowedForDevice(getContext()))
+            return true;
+
+        // Check if the user has specifically enabled rotation via preferences.
+        return Utilities.isAllowRotationPrefEnabled(getApplicationContext());
     }
 }
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a8c668d..440a537 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -166,6 +166,12 @@
     <!-- Text for settings button -->
     <string name="settings_button_text">Settings</string>
 
+    <!-- Strings for settings -->
+    <!-- Title for Allow Rotation setting. [CHAR LIMIT=50] [DO NOT TRANSLATE] -->
+    <string name="allow_rotation_title">Allow rotation</string>
+    <!-- Summary for Allow Rotation setting. [CHAR LIMIT=150] [DO NOT TRANSLATE] -->
+    <string name="allow_rotation_summary">Allow rotation of the home screen</string>
+
     <!-- Label on an icon that references an uninstalled package, for which we have no information about when it might be installed. [CHAR_LIMIT=15] -->
     <string name="package_state_unknown">Unknown</string>
 
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
new file mode 100644
index 0000000..f283575
--- /dev/null
+++ b/res/xml/launcher_preferences.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 Google Inc.
+
+     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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreference
+            android:key="pref_allowRotation"
+            android:title="@string/allow_rotation_title"
+            android:summary="@string/allow_rotation_summary"
+            android:defaultValue="@bool/allow_rotation"
+            android:persistent="true"
+    />
+
+</PreferenceScreen>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 1aa446e..da6430a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -221,7 +221,8 @@
     public static final String USER_HAS_MIGRATED = "launcher.user_migrated_from_old_data";
 
     /** The different states that Launcher can be in. */
-    enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED };
+    enum State { NONE, WORKSPACE, APPS, APPS_SPRING_LOADED, WIDGETS, WIDGETS_SPRING_LOADED }
+
     @Thunk State mState = State.WORKSPACE;
     @Thunk LauncherStateTransitionAnimation mStateTransitionAnimation;
 
@@ -405,6 +406,24 @@
 
     FocusIndicatorView mFocusHandler;
 
+    private boolean mRotationEnabled = false;
+    private boolean mPreferenceObserverRegistered = false;
+
+    final private SharedPreferences.OnSharedPreferenceChangeListener mSettingsObserver =
+            new SharedPreferences.OnSharedPreferenceChangeListener() {
+        @Override
+        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+            if (Utilities.ALLOW_ROTATION_PREFERENCE_KEY.equals(key)) {
+                if (mRotationEnabled = sharedPreferences.getBoolean(
+                        Utilities.ALLOW_ROTATION_PREFERENCE_KEY, false)) {
+                    unlockScreenOrientation(true);
+                } else {
+                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
+                }
+            }
+        }
+    };
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         if (DEBUG_STRICT_MODE) {
@@ -505,7 +524,19 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         registerReceiver(mCloseSystemDialogsReceiver, filter);
 
-        // On large interfaces, we want the screen to auto-rotate based on the current orientation
+        mRotationEnabled = Utilities.isRotationAllowedForDevice(getApplicationContext());
+        // In case we are on a device with locked rotation, we should look at preferences to check
+        // if the user has specifically allowed rotation.
+        if (!mRotationEnabled) {
+            getSharedPreferences(LauncherFiles.ROTATION_PREF_FILE,
+                    Context.MODE_MULTI_PROCESS).registerOnSharedPreferenceChangeListener(
+                    mSettingsObserver);
+            mPreferenceObserverRegistered = true;
+            mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
+        }
+
+        // On large interfaces, or on devices that a user has specifically enabled screen rotation,
+        // we want the screen to auto-rotate based on the current orientation
         unlockScreenOrientation(true);
 
         if (mLauncherCallbacks != null) {
@@ -1300,8 +1331,11 @@
     protected boolean hasSettings() {
         if (mLauncherCallbacks != null) {
             return mLauncherCallbacks.hasSettings();
+        } else {
+            // On devices with a locked orientation, we will at least have the allow rotation
+            // setting.
+            return !Utilities.isRotationAllowedForDevice(this);
         }
-        return false;
     }
 
     public void addToCustomContentPage(View customContent,
@@ -2083,6 +2117,13 @@
     public void onDestroy() {
         super.onDestroy();
 
+        if (mPreferenceObserverRegistered) {
+            getSharedPreferences(LauncherFiles.ROTATION_PREF_FILE,
+                    Context.MODE_MULTI_PROCESS).unregisterOnSharedPreferenceChangeListener(
+                    mSettingsObserver);
+            mPreferenceObserverRegistered = false;
+        }
+
         // Remove all pending runnables
         mHandler.removeMessages(ADVANCE_MSG);
         mHandler.removeMessages(0);
@@ -2865,6 +2906,8 @@
         if (LOGD) Log.d(TAG, "onClickSettingsButton");
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onClickSettingsButton(v);
+        } else {
+            showSettingsActivity();
         }
     }
 
@@ -4472,7 +4515,7 @@
     }
 
     public void lockScreenOrientation() {
-        if (Utilities.isRotationEnabled(this)) {
+        if (mRotationEnabled) {
             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
                 setRequestedOrientation(mapConfigurationOriActivityInfoOri(getResources()
                         .getConfiguration().orientation));
@@ -4481,8 +4524,9 @@
             }
         }
     }
+
     public void unlockScreenOrientation(boolean immediate) {
-        if (Utilities.isRotationEnabled(this)) {
+        if (mRotationEnabled) {
             if (immediate) {
                 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
             } else {
@@ -4587,6 +4631,10 @@
         editor.apply();
     }
 
+    private void showSettingsActivity() {
+        startActivity(new Intent(this, SettingsActivity.class));
+    }
+
     /**
      * To be overridden by subclasses to indicate that there is an in-activity full-screen intro
      * screen that must be displayed and dismissed.
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index 03ec4bf..4aeaef0 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -27,6 +27,8 @@
     public static final String WIDGET_PREVIEWS_DB = "widgetpreviews.db";
     public static final String APP_ICONS_DB = "app_icons.db";
 
+    public static final String ROTATION_PREF_FILE = "com.android.launcher3.rotation.prefs";
+
     public static final List<String> ALL_FILES = Collections.unmodifiableList(Arrays.asList(
             DEFAULT_WALLPAPER_THUMBNAIL,
             DEFAULT_WALLPAPER_THUMBNAIL_OLD,
@@ -37,7 +39,8 @@
             WALLPAPER_IMAGES_DB,
             WIDGET_PREVIEWS_DB,
             MANAGED_USER_PREFERENCES_KEY,
-            APP_ICONS_DB));
+            APP_ICONS_DB,
+            ROTATION_PREF_FILE));
 
     // TODO: Delete these files on upgrade
     public static final List<String> OBSOLETE_FILES = Collections.unmodifiableList(Arrays.asList(
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
new file mode 100644
index 0000000..a1da1b6
--- /dev/null
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+package com.android.launcher3;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+/**
+ * Settings activity for Launcher. Currently implements the following setting:
+ * LockToPortrait
+ */
+public class SettingsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        getFragmentManager().beginTransaction()
+                .replace(android.R.id.content, new LauncherSettingsFragment())
+                .commit();
+    }
+
+    /**
+     * This fragment shows the launcher preferences.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public static class LauncherSettingsFragment extends PreferenceFragment {
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            getPreferenceManager().setSharedPreferencesMode(Context.MODE_PRIVATE);
+            getPreferenceManager().setSharedPreferencesName(LauncherFiles.ROTATION_PREF_FILE);
+            addPreferencesFromResource(R.xml.launcher_preferences);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 256eba0..a9cbf69 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -25,6 +25,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -67,6 +68,7 @@
  * Various utilities shared amongst the Launcher's classes.
  */
 public final class Utilities {
+
     private static final String TAG = "Launcher.Utilities";
 
     private static int sIconWidth = -1;
@@ -93,6 +95,8 @@
     static final String FORCE_ENABLE_ROTATION_PROPERTY = "launcher_force_rotate";
     public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
 
+    public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
+
     /**
      * Returns a FastBitmapDrawable with the icon, accurately sized.
      */
@@ -114,10 +118,15 @@
         return Log.isLoggable(propertyName, Log.VERBOSE);
     }
 
-    public static boolean isRotationEnabled(Context c) {
-        boolean enableRotation = sForceEnableRotation ||
-                c.getResources().getBoolean(R.bool.allow_rotation);
-        return enableRotation;
+    public static boolean isAllowRotationPrefEnabled(Context context) {
+        SharedPreferences sharedPrefs = context.getSharedPreferences(LauncherFiles.ROTATION_PREF_FILE,
+                Context.MODE_MULTI_PROCESS);
+        boolean allowRotationPref = sharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false);
+        return sForceEnableRotation || allowRotationPref;
+    }
+
+    public static boolean isRotationAllowedForDevice(Context context) {
+        return sForceEnableRotation || context.getResources().getBoolean(R.bool.allow_rotation);
     }
 
     /**