Merge "Add the Allow Rotation setting to Launcher3." into ub-launcher3-burnaby
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 5dac3b3..51f0916 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -218,7 +218,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;
 
@@ -400,6 +401,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) {
@@ -499,7 +518,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) {
@@ -1192,8 +1223,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,
@@ -1975,6 +2009,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);
@@ -2757,6 +2798,8 @@
         if (LOGD) Log.d(TAG, "onClickSettingsButton");
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onClickSettingsButton(v);
+        } else {
+            showSettingsActivity();
         }
     }
 
@@ -4364,7 +4407,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));
@@ -4373,8 +4416,9 @@
             }
         }
     }
+
     public void unlockScreenOrientation(boolean immediate) {
-        if (Utilities.isRotationEnabled(this)) {
+        if (mRotationEnabled) {
             if (immediate) {
                 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
             } else {
@@ -4479,6 +4523,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 c08cd0b..ec4e4f9 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -26,6 +26,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,
@@ -35,7 +37,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);
     }
 
     /**