am f70d0619: (-s ours) am e0bc7603: (-s ours) Import translations. DO NOT MERGE

* commit 'f70d061975f732456888bfa31c81998985ed5ddf':
  Import translations. DO NOT MERGE
diff --git a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
index 11684c3..6c58da0 100644
--- a/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
+++ b/WallpaperPicker/src/com/android/launcher3/WallpaperCropActivity.java
@@ -824,17 +824,28 @@
         editor.commit();
 
         suggestWallpaperDimension(getResources(),
-                sp, getWindowManager(), WallpaperManager.getInstance(this));
+                sp, getWindowManager(), WallpaperManager.getInstance(this), true);
     }
 
     static public void suggestWallpaperDimension(Resources res,
             final SharedPreferences sharedPrefs,
             WindowManager windowManager,
-            final WallpaperManager wallpaperManager) {
+            final WallpaperManager wallpaperManager, boolean fallBackToDefaults) {
         final Point defaultWallpaperSize = getDefaultWallpaperSize(res, windowManager);
         // If we have saved a wallpaper width/height, use that instead
-        int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, defaultWallpaperSize.x);
-        int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, defaultWallpaperSize.y);
+
+        int savedWidth = sharedPrefs.getInt(WALLPAPER_WIDTH_KEY, -1);
+        int savedHeight = sharedPrefs.getInt(WALLPAPER_HEIGHT_KEY, -1);
+
+        if (savedWidth == -1 || savedHeight == -1) {
+            if (!fallBackToDefaults) {
+                return;
+            } else {
+                savedWidth = defaultWallpaperSize.x;
+                savedHeight = defaultWallpaperSize.y;
+            }
+        }
+
         if (savedWidth != wallpaperManager.getDesiredMinimumWidth() ||
                 savedHeight != wallpaperManager.getDesiredMinimumHeight()) {
             wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
diff --git a/res/anim/no_anim.xml b/res/anim/no_anim.xml
new file mode 100644
index 0000000..02b1625
--- /dev/null
+++ b/res/anim/no_anim.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="417" />
diff --git a/res/anim/task_open_enter.xml b/res/anim/task_open_enter.xml
new file mode 100644
index 0000000..b2aadd7
--- /dev/null
+++ b/res/anim/task_open_enter.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:background="#ff000000" android:shareInterpolator="false" android:zAdjustment="top">
+
+    <alpha android:fromAlpha="0" android:toAlpha="1.0"
+            android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+            android:interpolator="@interpolator/decelerate_quart"
+            android:startOffset="300"
+            android:duration="167"/>
+
+    <translate android:fromYDelta="110%" android:toYDelta="0"
+            android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:startOffset="300"
+            android:duration="417" />
+</set>
\ No newline at end of file
diff --git a/res/drawable-hdpi/ic_allapps_l.png b/res/drawable-hdpi/ic_allapps_l.png
new file mode 100644
index 0000000..4fe3bf0
--- /dev/null
+++ b/res/drawable-hdpi/ic_allapps_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_allapps_pressed_l.png b/res/drawable-hdpi/ic_allapps_pressed_l.png
new file mode 100644
index 0000000..af49dbb
--- /dev/null
+++ b/res/drawable-hdpi/ic_allapps_pressed_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_setting_l.png b/res/drawable-hdpi/ic_setting_l.png
new file mode 100644
index 0000000..1c12a5b
--- /dev/null
+++ b/res/drawable-hdpi/ic_setting_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_setting_pressed_l.png b/res/drawable-hdpi/ic_setting_pressed_l.png
new file mode 100644
index 0000000..d5b5ca2
--- /dev/null
+++ b/res/drawable-hdpi/ic_setting_pressed_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper_l.png b/res/drawable-hdpi/ic_wallpaper_l.png
new file mode 100644
index 0000000..34d5943
--- /dev/null
+++ b/res/drawable-hdpi/ic_wallpaper_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wallpaper_pressed_l.png b/res/drawable-hdpi/ic_wallpaper_pressed_l.png
new file mode 100644
index 0000000..1588ce7
--- /dev/null
+++ b/res/drawable-hdpi/ic_wallpaper_pressed_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_l.png b/res/drawable-hdpi/ic_widget_l.png
new file mode 100644
index 0000000..ed7e1ca
--- /dev/null
+++ b/res/drawable-hdpi/ic_widget_l.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_widget_pressed_l.png b/res/drawable-hdpi/ic_widget_pressed_l.png
new file mode 100644
index 0000000..19d6fed
--- /dev/null
+++ b/res/drawable-hdpi/ic_widget_pressed_l.png
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel_hover_l.9.png b/res/drawable-hdpi/screenpanel_hover_l.9.png
new file mode 100644
index 0000000..2cea8a4
--- /dev/null
+++ b/res/drawable-hdpi/screenpanel_hover_l.9.png
Binary files differ
diff --git a/res/drawable-hdpi/screenpanel_l.9.png b/res/drawable-hdpi/screenpanel_l.9.png
new file mode 100644
index 0000000..eed0f2c
--- /dev/null
+++ b/res/drawable-hdpi/screenpanel_l.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps_l.png b/res/drawable-mdpi/ic_allapps_l.png
new file mode 100644
index 0000000..09cd82a
--- /dev/null
+++ b/res/drawable-mdpi/ic_allapps_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_allapps_pressed_l.png b/res/drawable-mdpi/ic_allapps_pressed_l.png
new file mode 100644
index 0000000..d7ea96f
--- /dev/null
+++ b/res/drawable-mdpi/ic_allapps_pressed_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_l.png b/res/drawable-mdpi/ic_setting_l.png
new file mode 100644
index 0000000..c614e91
--- /dev/null
+++ b/res/drawable-mdpi/ic_setting_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_setting_pressed_l.png b/res/drawable-mdpi/ic_setting_pressed_l.png
new file mode 100644
index 0000000..61e574a
--- /dev/null
+++ b/res/drawable-mdpi/ic_setting_pressed_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper_l.png b/res/drawable-mdpi/ic_wallpaper_l.png
new file mode 100644
index 0000000..8f2a00a
--- /dev/null
+++ b/res/drawable-mdpi/ic_wallpaper_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wallpaper_pressed_l.png b/res/drawable-mdpi/ic_wallpaper_pressed_l.png
new file mode 100644
index 0000000..aa598c3
--- /dev/null
+++ b/res/drawable-mdpi/ic_wallpaper_pressed_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_l.png b/res/drawable-mdpi/ic_widget_l.png
new file mode 100644
index 0000000..1bd3935
--- /dev/null
+++ b/res/drawable-mdpi/ic_widget_l.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_widget_pressed_l.png b/res/drawable-mdpi/ic_widget_pressed_l.png
new file mode 100644
index 0000000..9b690d9
--- /dev/null
+++ b/res/drawable-mdpi/ic_widget_pressed_l.png
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel_hover_l.9.png b/res/drawable-mdpi/screenpanel_hover_l.9.png
new file mode 100644
index 0000000..8a94984
--- /dev/null
+++ b/res/drawable-mdpi/screenpanel_hover_l.9.png
Binary files differ
diff --git a/res/drawable-mdpi/screenpanel_l.9.png b/res/drawable-mdpi/screenpanel_l.9.png
new file mode 100644
index 0000000..6f8b7e6
--- /dev/null
+++ b/res/drawable-mdpi/screenpanel_l.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps_l.png b/res/drawable-xhdpi/ic_allapps_l.png
new file mode 100644
index 0000000..eff3bea
--- /dev/null
+++ b/res/drawable-xhdpi/ic_allapps_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_allapps_pressed_l.png b/res/drawable-xhdpi/ic_allapps_pressed_l.png
new file mode 100644
index 0000000..15a8aa9
--- /dev/null
+++ b/res/drawable-xhdpi/ic_allapps_pressed_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting_l.png b/res/drawable-xhdpi/ic_setting_l.png
new file mode 100644
index 0000000..3a7310b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_setting_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_setting_pressed_l.png b/res/drawable-xhdpi/ic_setting_pressed_l.png
new file mode 100644
index 0000000..005d49c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_setting_pressed_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper_l.png b/res/drawable-xhdpi/ic_wallpaper_l.png
new file mode 100644
index 0000000..d2bf246
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wallpaper_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wallpaper_pressed_l.png b/res/drawable-xhdpi/ic_wallpaper_pressed_l.png
new file mode 100644
index 0000000..5a9b84d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wallpaper_pressed_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_l.png b/res/drawable-xhdpi/ic_widget_l.png
new file mode 100644
index 0000000..cf6be81
--- /dev/null
+++ b/res/drawable-xhdpi/ic_widget_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_widget_pressed_l.png b/res/drawable-xhdpi/ic_widget_pressed_l.png
new file mode 100644
index 0000000..633c9c6
--- /dev/null
+++ b/res/drawable-xhdpi/ic_widget_pressed_l.png
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel_hover_l.9.png b/res/drawable-xhdpi/screenpanel_hover_l.9.png
new file mode 100644
index 0000000..0032fff
--- /dev/null
+++ b/res/drawable-xhdpi/screenpanel_hover_l.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/screenpanel_l.9.png b/res/drawable-xhdpi/screenpanel_l.9.png
new file mode 100644
index 0000000..2d70d7a
--- /dev/null
+++ b/res/drawable-xhdpi/screenpanel_l.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps_l.png b/res/drawable-xxhdpi/ic_allapps_l.png
new file mode 100644
index 0000000..2461984
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_allapps_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_allapps_pressed_l.png b/res/drawable-xxhdpi/ic_allapps_pressed_l.png
new file mode 100644
index 0000000..929a0e6
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_allapps_pressed_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting_l.png b/res/drawable-xxhdpi/ic_setting_l.png
new file mode 100644
index 0000000..01bdcd5
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_setting_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_setting_pressed_l.png b/res/drawable-xxhdpi/ic_setting_pressed_l.png
new file mode 100644
index 0000000..d0cad5e
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_setting_pressed_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper_l.png b/res/drawable-xxhdpi/ic_wallpaper_l.png
new file mode 100644
index 0000000..490c45a
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_wallpaper_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_wallpaper_pressed_l.png b/res/drawable-xxhdpi/ic_wallpaper_pressed_l.png
new file mode 100644
index 0000000..e5d200b
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_wallpaper_pressed_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_l.png b/res/drawable-xxhdpi/ic_widget_l.png
new file mode 100644
index 0000000..d4b8324
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_widget_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_widget_pressed_l.png b/res/drawable-xxhdpi/ic_widget_pressed_l.png
new file mode 100644
index 0000000..b8dd35d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_widget_pressed_l.png
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel_hover_l.9.png b/res/drawable-xxhdpi/screenpanel_hover_l.9.png
new file mode 100644
index 0000000..24d2266
--- /dev/null
+++ b/res/drawable-xxhdpi/screenpanel_hover_l.9.png
Binary files differ
diff --git a/res/drawable-xxhdpi/screenpanel_l.9.png b/res/drawable-xxhdpi/screenpanel_l.9.png
new file mode 100644
index 0000000..7ed058e
--- /dev/null
+++ b/res/drawable-xxhdpi/screenpanel_l.9.png
Binary files differ
diff --git a/res/drawable/all_apps_button_icon_l.xml b/res/drawable/all_apps_button_icon_l.xml
new file mode 100644
index 0000000..91de1b5
--- /dev/null
+++ b/res/drawable/all_apps_button_icon_l.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@drawable/ic_allapps_pressed_l" />
+    <item android:state_pressed="true" android:drawable="@drawable/ic_allapps_pressed_l" />
+    <item android:drawable="@drawable/ic_allapps_l" />
+</selector>
diff --git a/res/drawable/setting_button_l.xml b/res/drawable/setting_button_l.xml
new file mode 100644
index 0000000..01661db
--- /dev/null
+++ b/res/drawable/setting_button_l.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@drawable/ic_setting_pressed_l" />
+    <item android:state_pressed="true" android:drawable="@drawable/ic_setting_pressed_l" />
+    <item android:drawable="@drawable/ic_setting_l" />
+</selector>
diff --git a/res/drawable/wallpaper_button_l.xml b/res/drawable/wallpaper_button_l.xml
new file mode 100644
index 0000000..c539b61
--- /dev/null
+++ b/res/drawable/wallpaper_button_l.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@drawable/ic_wallpaper_pressed_l" />
+    <item android:state_pressed="true" android:drawable="@drawable/ic_wallpaper_pressed_l" />
+    <item android:drawable="@drawable/ic_wallpaper_l" />
+</selector>
diff --git a/res/drawable/widget_button_l.xml b/res/drawable/widget_button_l.xml
new file mode 100644
index 0000000..92521b9
--- /dev/null
+++ b/res/drawable/widget_button_l.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true" android:drawable="@drawable/ic_widget_pressed_l" />
+    <item android:state_pressed="true" android:drawable="@drawable/ic_widget_pressed_l" />
+    <item android:drawable="@drawable/ic_widget_l" />
+</selector>
diff --git a/res/interpolator/decelerate_quart.xml b/res/interpolator/decelerate_quart.xml
new file mode 100644
index 0000000..5dc5d38
--- /dev/null
+++ b/res/interpolator/decelerate_quart.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, 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.
+*/
+-->
+
+<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+        android:factor="2" />
diff --git a/res/interpolator/decelerate_quint.xml b/res/interpolator/decelerate_quint.xml
new file mode 100644
index 0000000..fa89a64
--- /dev/null
+++ b/res/interpolator/decelerate_quint.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, 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.
+*/
+-->
+
+<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+        android:factor="2.5" />
diff --git a/res/xml-sw720dp/default_workspace.xml b/res/xml-sw720dp/default_workspace.xml
deleted file mode 100644
index 1c1d70e..0000000
--- a/res/xml-sw720dp/default_workspace.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-    <!-- Far-left screen [0] -->
-
-    <!-- Left screen [1] -->
-    <appwidget
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
-        launcher:screen="1"
-        launcher:x="0"
-        launcher:y="3"
-        launcher:spanX="4"
-        launcher:spanY="1" />
-
-    <!-- Middle screen [2] -->
-    <appwidget
-        launcher:packageName="com.android.deskclock"
-        launcher:className="com.android.alarmclock.AnalogAppWidgetProvider"
-        launcher:screen="2"
-        launcher:x="1"
-        launcher:y="0"
-        launcher:spanX="2"
-        launcher:spanY="2" />
-    <favorite
-        launcher:packageName="com.android.camera"
-        launcher:className="com.android.camera.Camera"
-        launcher:screen="2"
-        launcher:x="0"
-        launcher:y="3" />
-
-    <!-- Right screen [3] -->
-    <favorite
-        launcher:packageName="com.android.gallery3d"
-        launcher:className="com.android.gallery3d.app.Gallery"
-        launcher:screen="3"
-        launcher:x="1"
-        launcher:y="3" />
-    <favorite
-        launcher:packageName="com.android.settings"
-        launcher:className="com.android.settings.Settings"
-        launcher:screen="3"
-        launcher:x="2"
-        launcher:y="3" />
-
-    <!-- Far-right screen [4] -->
-</favorites>
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace_4x4.xml
similarity index 100%
rename from res/xml/default_workspace.xml
rename to res/xml/default_workspace_4x4.xml
diff --git a/res/xml/default_workspace_no_all_apps.xml b/res/xml/default_workspace_4x4_no_all_apps.xml
similarity index 100%
rename from res/xml/default_workspace_no_all_apps.xml
rename to res/xml/default_workspace_4x4_no_all_apps.xml
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace_5x5.xml
similarity index 100%
copy from res/xml/default_workspace.xml
copy to res/xml/default_workspace_5x5.xml
diff --git a/res/xml/default_workspace_5x5_no_all_apps.xml b/res/xml/default_workspace_5x5_no_all_apps.xml
new file mode 100644
index 0000000..f54a204
--- /dev/null
+++ b/res/xml/default_workspace_5x5_no_all_apps.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <!-- Dialer Hangouts Maps Chrome Camera -->
+    <favorite
+        launcher:packageName="com.google.android.dialer"
+        launcher:className="com.google.android.dialer.extensions.GoogleDialtactsActivity"
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.google.android.talk"
+        launcher:className="com.google.android.talk.SigningInActivity"
+        launcher:container="-101"
+        launcher:screen="2"
+        launcher:x="2"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.google.android.apps.maps"
+        launcher:className="com.google.android.maps.MapsActivity"
+        launcher:container="-101"
+        launcher:screen="3"
+        launcher:x="3"
+        launcher:y="0"/>
+    <favorite
+        launcher:packageName="com.android.chrome"
+        launcher:className="com.google.android.apps.chrome.Main"
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.google.android.GoogleCamera"
+        launcher:className="com.android.camera.CameraLauncher"
+        launcher:container="-101"
+        launcher:screen="5"
+        launcher:x="5"
+        launcher:y="0" />
+</favorites>
+
diff --git a/res/xml-sw600dp/default_workspace.xml b/res/xml/default_workspace_5x6.xml
similarity index 100%
rename from res/xml-sw600dp/default_workspace.xml
rename to res/xml/default_workspace_5x6.xml
diff --git a/res/xml/default_workspace_5x6_no_all_apps.xml b/res/xml/default_workspace_5x6_no_all_apps.xml
new file mode 100644
index 0000000..f54a204
--- /dev/null
+++ b/res/xml/default_workspace_5x6_no_all_apps.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <!-- Dialer Hangouts Maps Chrome Camera -->
+    <favorite
+        launcher:packageName="com.google.android.dialer"
+        launcher:className="com.google.android.dialer.extensions.GoogleDialtactsActivity"
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.google.android.talk"
+        launcher:className="com.google.android.talk.SigningInActivity"
+        launcher:container="-101"
+        launcher:screen="2"
+        launcher:x="2"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.google.android.apps.maps"
+        launcher:className="com.google.android.maps.MapsActivity"
+        launcher:container="-101"
+        launcher:screen="3"
+        launcher:x="3"
+        launcher:y="0"/>
+    <favorite
+        launcher:packageName="com.android.chrome"
+        launcher:className="com.google.android.apps.chrome.Main"
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0" />
+    <favorite
+        launcher:packageName="com.google.android.GoogleCamera"
+        launcher:className="com.android.camera.CameraLauncher"
+        launcher:container="-101"
+        launcher:screen="5"
+        launcher:x="5"
+        launcher:y="0" />
+</favorites>
+
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 89b291f..c25aa40 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -23,6 +23,10 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -66,7 +70,7 @@
         if (mAppFilter != null && !mAppFilter.shouldShowApp(info.componentName)) {
             return;
         }
-        if (findActivity(data, info.componentName)) {
+        if (findActivity(data, info.componentName, info.user)) {
             return;
         }
         data.add(info);
@@ -92,12 +96,14 @@
     /**
      * Add the icons for the supplied apk called packageName.
      */
-    public void addPackage(Context context, String packageName) {
-        final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName);
+    public void addPackage(Context context, String packageName, UserHandleCompat user) {
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        final List<LauncherActivityInfoCompat> matches = launcherApps.getActivityList(packageName,
+                user);
 
         if (matches.size() > 0) {
-            for (ResolveInfo info : matches) {
-                add(new AppInfo(context.getPackageManager(), info, mIconCache, null));
+            for (LauncherActivityInfoCompat info : matches) {
+                add(new AppInfo(context, info, user, mIconCache, null));
             }
         }
     }
@@ -105,34 +111,37 @@
     /**
      * Remove the apps for the given apk identified by packageName.
      */
-    public void removePackage(String packageName) {
+    public void removePackage(String packageName, UserHandleCompat user) {
         final List<AppInfo> data = this.data;
         for (int i = data.size() - 1; i >= 0; i--) {
             AppInfo info = data.get(i);
             final ComponentName component = info.intent.getComponent();
-            if (packageName.equals(component.getPackageName())) {
+            if (info.user.equals(user) && packageName.equals(component.getPackageName())) {
                 removed.add(info);
                 data.remove(i);
             }
         }
-        mIconCache.remove(packageName);
+        mIconCache.remove(packageName, user);
     }
 
     /**
      * Add and remove icons for this package which has been updated.
      */
-    public void updatePackage(Context context, String packageName) {
-        final List<ResolveInfo> matches = findActivitiesForPackage(context, packageName);
+    public void updatePackage(Context context, String packageName, UserHandleCompat user) {
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        final List<LauncherActivityInfoCompat> matches = launcherApps.getActivityList(packageName,
+                user);
         if (matches.size() > 0) {
             // Find disabled/removed activities and remove them from data and add them
             // to the removed list.
             for (int i = data.size() - 1; i >= 0; i--) {
                 final AppInfo applicationInfo = data.get(i);
                 final ComponentName component = applicationInfo.intent.getComponent();
-                if (packageName.equals(component.getPackageName())) {
+                if (user.equals(applicationInfo.user)
+                        && packageName.equals(component.getPackageName())) {
                     if (!findActivity(matches, component)) {
                         removed.add(applicationInfo);
-                        mIconCache.remove(component);
+                        mIconCache.remove(component, user);
                         data.remove(i);
                     }
                 }
@@ -142,14 +151,14 @@
             // Also updates existing activities with new labels/icons
             int count = matches.size();
             for (int i = 0; i < count; i++) {
-                final ResolveInfo info = matches.get(i);
+                final LauncherActivityInfoCompat info = matches.get(i);
                 AppInfo applicationInfo = findApplicationInfoLocked(
-                        info.activityInfo.applicationInfo.packageName,
-                        info.activityInfo.name);
+                        info.getComponentName().getPackageName(), user,
+                        info.getComponentName().getShortClassName());
                 if (applicationInfo == null) {
-                    add(new AppInfo(context.getPackageManager(), info, mIconCache, null));
+                    add(new AppInfo(context, info, user, mIconCache, null));
                 } else {
-                    mIconCache.remove(applicationInfo.componentName);
+                    mIconCache.remove(applicationInfo.componentName, user);
                     mIconCache.getTitleAndIcon(applicationInfo, info, null);
                     modified.add(applicationInfo);
                 }
@@ -159,37 +168,24 @@
             for (int i = data.size() - 1; i >= 0; i--) {
                 final AppInfo applicationInfo = data.get(i);
                 final ComponentName component = applicationInfo.intent.getComponent();
-                if (packageName.equals(component.getPackageName())) {
+                if (user.equals(applicationInfo.user)
+                        && packageName.equals(component.getPackageName())) {
                     removed.add(applicationInfo);
-                    mIconCache.remove(component);
+                    mIconCache.remove(component, user);
                     data.remove(i);
                 }
             }
         }
     }
 
-    /**
-     * Query the package manager for MAIN/LAUNCHER activities in the supplied package.
-     */
-    static List<ResolveInfo> findActivitiesForPackage(Context context, String packageName) {
-        final PackageManager packageManager = context.getPackageManager();
-
-        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
-        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        mainIntent.setPackage(packageName);
-
-        final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
-        return apps != null ? apps : new ArrayList<ResolveInfo>();
-    }
 
     /**
      * Returns whether <em>apps</em> contains <em>component</em>.
      */
-    private static boolean findActivity(List<ResolveInfo> apps, ComponentName component) {
-        final String className = component.getClassName();
-        for (ResolveInfo info : apps) {
-            final ActivityInfo activityInfo = info.activityInfo;
-            if (activityInfo.name.equals(className)) {
+    private static boolean findActivity(List<LauncherActivityInfoCompat> apps,
+            ComponentName component) {
+        for (LauncherActivityInfoCompat info : apps) {
+            if (info.getComponentName().equals(component)) {
                 return true;
             }
         }
@@ -197,13 +193,24 @@
     }
 
     /**
+     * Query the launcher apps service for whether the supplied package has
+     * MAIN/LAUNCHER activities in the supplied package.
+     */
+    static boolean packageHasActivities(Context context, String packageName,
+            UserHandleCompat user) {
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        return launcherApps.getActivityList(packageName, user).size() > 0;
+    }
+
+    /**
      * Returns whether <em>apps</em> contains <em>component</em>.
      */
-    private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component) {
+    private static boolean findActivity(ArrayList<AppInfo> apps, ComponentName component,
+            UserHandleCompat user) {
         final int N = apps.size();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             final AppInfo info = apps.get(i);
-            if (info.componentName.equals(component)) {
+            if (info.user.equals(user) && info.componentName.equals(component)) {
                 return true;
             }
         }
@@ -213,10 +220,11 @@
     /**
      * Find an ApplicationInfo object for the given packageName and className.
      */
-    private AppInfo findApplicationInfoLocked(String packageName, String className) {
+    private AppInfo findApplicationInfoLocked(String packageName, UserHandleCompat user,
+            String className) {
         for (AppInfo info: data) {
             final ComponentName component = info.intent.getComponent();
-            if (packageName.equals(component.getPackageName())
+            if (user.equals(info.user) && packageName.equals(component.getPackageName())
                     && className.equals(component.getClassName())) {
                 return info;
             }
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index f85f691..c85626b 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -17,15 +17,20 @@
 package com.android.launcher3;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.util.Log;
 
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 
 /**
@@ -71,28 +76,25 @@
     /**
      * Must not hold the Context.
      */
-    public AppInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
-            HashMap<Object, CharSequence> labelCache) {
-        final String packageName = info.activityInfo.applicationInfo.packageName;
-
-        this.componentName = new ComponentName(packageName, info.activityInfo.name);
+    public AppInfo(Context context, LauncherActivityInfoCompat info, UserHandleCompat user,
+            IconCache iconCache, HashMap<Object, CharSequence> labelCache) {
+        this.componentName = info.getComponentName();
         this.container = ItemInfo.NO_ID;
-        this.setActivity(componentName,
-                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
 
-        try {
-            PackageInfo pi = pm.getPackageInfo(packageName, 0);
-            flags = initFlags(pi);
-            firstInstallTime = initFirstInstallTime(pi);
-        } catch (NameNotFoundException e) {
-            Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
-        }
-
+        flags = initFlags(info);
+        firstInstallTime = info.getFirstInstallTime();
         iconCache.getTitleAndIcon(this, info, labelCache);
+        intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setComponent(info.getComponentName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+        intent.putExtra(EXTRA_PROFILE, serialNumber);
+        this.user = user;
     }
 
-    public static int initFlags(PackageInfo pi) {
-        int appFlags = pi.applicationInfo.flags;
+    private static int initFlags(LauncherActivityInfoCompat info) {
+        int appFlags = info.getApplicationFlags();
         int flags = 0;
         if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
             flags |= DOWNLOADED_FLAG;
@@ -104,10 +106,6 @@
         return flags;
     }
 
-    public static long initFirstInstallTime(PackageInfo pi) {
-        return pi.firstInstallTime;
-    }
-
     public AppInfo(AppInfo info) {
         super(info);
         componentName = info.componentName;
@@ -115,21 +113,7 @@
         intent = new Intent(info.intent);
         flags = info.flags;
         firstInstallTime = info.firstInstallTime;
-    }
-
-    /**
-     * Creates the application intent based on a component name and various launch flags.
-     * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
-     *
-     * @param className the class name of the component representing the intent
-     * @param launchFlags the launch flags
-     */
-    final void setActivity(ComponentName className, int launchFlags) {
-        intent = new Intent(Intent.ACTION_MAIN);
-        intent.addCategory(Intent.CATEGORY_LAUNCHER);
-        intent.setComponent(className);
-        intent.setFlags(launchFlags);
-        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
+        iconBitmap = info.iconBitmap;
     }
 
     @Override
@@ -137,7 +121,8 @@
         return "ApplicationInfo(title=" + title.toString() + " id=" + this.id
                 + " type=" + this.itemType + " container=" + this.container
                 + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
-                + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+                + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
+                + " user=" + user + ")";
     }
 
     public static void dumpApplicationInfoList(String tag, String label, ArrayList<AppInfo> list) {
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index d6e0bb4..04426a8 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -808,13 +808,8 @@
                 !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) {
             // Exit spring loaded mode if we have not successfully dropped or have not handled the
             // drop in Workspace
-            mLauncher.getWorkspace().removeExtraEmptyScreen(true, new Runnable() {
-                @Override
-                public void run() {
-                    mLauncher.exitSpringLoadedDragMode();
-                    mLauncher.unlockScreenOrientation(false);
-                }
-            });
+            mLauncher.exitSpringLoadedDragMode();
+            mLauncher.unlockScreenOrientation(false);
         } else {
             mLauncher.unlockScreenOrientation(false);
         }
@@ -1575,7 +1570,8 @@
         int length = list.size();
         for (int i = 0; i < length; ++i) {
             AppInfo info = list.get(i);
-            if (info.intent.getComponent().equals(removeComponent)) {
+            if (info.user.equals(item.user)
+                    && info.intent.getComponent().equals(removeComponent)) {
                 return i;
             }
         }
diff --git a/src/com/android/launcher3/AppsCustomizeTabHost.java b/src/com/android/launcher3/AppsCustomizeTabHost.java
index bb7f045..c6455c2 100644
--- a/src/com/android/launcher3/AppsCustomizeTabHost.java
+++ b/src/com/android/launcher3/AppsCustomizeTabHost.java
@@ -29,6 +29,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import android.widget.TabHost;
@@ -430,6 +431,14 @@
             // prevent slowing down the animation)
             mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage());
 
+            // Opening apps, need to announce what page we are on.
+            AccessibilityManager am = (AccessibilityManager)
+                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+            if (am.isEnabled()) {
+                // Notify the user when the page changes
+                announceForAccessibility(mAppsCustomizePane.getCurrentPageDescription());
+            }
+
             // Going from Workspace -> All Apps
             // NOTE: We should do this at the end since we check visibility state in some of the
             // cling initialization/dismiss code above.
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index c180d32..95300c1 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.MotionEvent;
+import android.view.ViewConfiguration;
 import android.widget.TextView;
 
 /**
@@ -60,6 +61,8 @@
     private int mPressedOutlineColor;
     private int mPressedGlowColor;
 
+    private float mSlop;
+
     private int mTextColor;
     private boolean mShadowsEnabled = true;
     private boolean mIsTextVisible;
@@ -272,6 +275,11 @@
 
                 mLongPressHelper.cancelLongPress();
                 break;
+            case MotionEvent.ACTION_MOVE:
+                if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
+                    mLongPressHelper.cancelLongPress();
+                }
+                break;
         }
         return result;
     }
@@ -356,6 +364,7 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         if (mBackground != null) mBackground.setCallback(this);
+        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
     }
 
     @Override
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 2436a51..a0c9c2e 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -76,9 +76,6 @@
     private boolean mScrollingTransformsDirty = false;
     private boolean mDropPending = false;
 
-    private final Rect mRect = new Rect();
-    private final CellInfo mCellInfo = new CellInfo();
-
     // These are temporary variables to prevent having to allocate a new object just to
     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
     private final int[] mTmpXY = new int[2];
@@ -220,6 +217,11 @@
         mNormalBackground = res.getDrawable(R.drawable.screenpanel);
         mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover);
 
+        if (Utilities.isLmp()) {
+            mNormalBackground = res.getDrawable(R.drawable.screenpanel_l);
+            mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover_l);
+        }
+
         mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left);
         mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right);
         mForegroundPadding =
@@ -699,103 +701,20 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        if (getParent() instanceof Workspace) {
-            Workspace workspace = (Workspace) getParent();
-            mCellInfo.screenId = workspace.getIdForScreen(this);
-        }
-    }
-
-    public void setTagToCellInfoForPoint(int touchX, int touchY) {
-        final CellInfo cellInfo = mCellInfo;
-        Rect frame = mRect;
-        final int x = touchX + getScrollX();
-        final int y = touchY + getScrollY();
-        final int count = mShortcutsAndWidgets.getChildCount();
-
-        boolean found = false;
-        for (int i = count - 1; i >= 0; i--) {
-            final View child = mShortcutsAndWidgets.getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) &&
-                    lp.isLockedToGrid) {
-                child.getHitRect(frame);
-
-                float scale = child.getScaleX();
-                frame = new Rect(child.getLeft(), child.getTop(), child.getRight(),
-                        child.getBottom());
-                // The child hit rect is relative to the CellLayoutChildren parent, so we need to
-                // offset that by this CellLayout's padding to test an (x,y) point that is relative
-                // to this view.
-                frame.offset(getPaddingLeft(), getPaddingTop());
-                frame.inset((int) (frame.width() * (1f - scale) / 2),
-                        (int) (frame.height() * (1f - scale) / 2));
-
-                if (frame.contains(x, y)) {
-                    cellInfo.cell = child;
-                    cellInfo.cellX = lp.cellX;
-                    cellInfo.cellY = lp.cellY;
-                    cellInfo.spanX = lp.cellHSpan;
-                    cellInfo.spanY = lp.cellVSpan;
-                    found = true;
-                    break;
-                }
-            }
-        }
-
-        mLastDownOnOccupiedCell = found;
-
-        if (!found) {
-            final int cellXY[] = mTmpXY;
-            pointToCellExact(x, y, cellXY);
-
-            cellInfo.cell = null;
-            cellInfo.cellX = cellXY[0];
-            cellInfo.cellY = cellXY[1];
-            cellInfo.spanX = 1;
-            cellInfo.spanY = 1;
-        }
-        setTag(cellInfo);
-    }
-
-    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         // First we clear the tag to ensure that on every touch down we start with a fresh slate,
         // even in the case where we return early. Not clearing here was causing bugs whereby on
         // long-press we'd end up picking up an item from a previous drag operation.
         final int action = ev.getAction();
 
-        if (action == MotionEvent.ACTION_DOWN) {
-            clearTagCellInfo();
-        }
 
         if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
             return true;
         }
 
-        if (action == MotionEvent.ACTION_DOWN) {
-            setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
-        }
-
         return false;
     }
 
-    private void clearTagCellInfo() {
-        final CellInfo cellInfo = mCellInfo;
-        cellInfo.cell = null;
-        cellInfo.cellX = -1;
-        cellInfo.cellY = -1;
-        cellInfo.spanX = 0;
-        cellInfo.spanY = 0;
-        setTag(cellInfo);
-    }
-
-    public CellInfo getTag() {
-        return (CellInfo) super.getTag();
-    }
-
     /**
      * Given a point, return the cell that strictly encloses that point
      * @param x X coordinate of the point
@@ -3360,6 +3279,16 @@
         long screenId;
         long container;
 
+        CellInfo(View v, ItemInfo info) {
+            cell = v;
+            cellX = info.cellX;
+            cellY = info.cellY;
+            spanX = info.spanX;
+            spanY = info.spanY;
+            screenId = info.screenId;
+            container = info.container;
+        }
+
         @Override
         public String toString() {
             return "Cell[view=" + (cell == null ? "null" : cell.getClass())
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 75d906b..20546b8 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -38,6 +38,9 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.LinearInterpolator;
 
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+
 import java.util.List;
 import java.util.Set;
 
@@ -184,6 +187,11 @@
         if (!willAcceptDrop(info) || isAllAppsWidget(source, info)) {
             isVisible = false;
         }
+        if (useUninstallLabel &&
+                !(((ItemInfo) info).user.equals(UserHandleCompat.myUserHandle()))) {
+            // Don't support uninstall for apps from other profiles.
+            isVisible = false;
+        }
 
         if (useUninstallLabel) {
             setCompoundDrawablesRelativeWithIntrinsicBounds(mUninstallDrawable, null, null, null);
@@ -279,25 +287,27 @@
         if (isAllAppsApplication(d.dragSource, item)) {
             // Uninstall the application if it is being dragged from AppsCustomize
             AppInfo appInfo = (AppInfo) item;
-            mLauncher.startApplicationUninstallActivity(appInfo.componentName, appInfo.flags);
+            // We don't support uninstalling apps from other profiles.
+            if (item.user.equals(UserHandleCompat.myUserHandle())) {
+                mLauncher.startApplicationUninstallActivity(appInfo.componentName, appInfo.flags);
+            }
         } else if (isUninstallFromWorkspace(d)) {
             ShortcutInfo shortcut = (ShortcutInfo) item;
-            if (shortcut.intent != null && shortcut.intent.getComponent() != null) {
+            // We don't support uninstalling apps from other profiles.
+            if (shortcut.intent != null && shortcut.intent.getComponent() != null &&
+                    shortcut.user.equals(UserHandleCompat.myUserHandle())) {
                 final ComponentName componentName = shortcut.intent.getComponent();
                 final DragSource dragSource = d.dragSource;
-                int flags = AppInfo.initFlags(
-                    ShortcutInfo.getPackageInfo(getContext(), componentName.getPackageName()));
                 mWaitingForUninstall =
-                    mLauncher.startApplicationUninstallActivity(componentName, flags);
+                        mLauncher.startApplicationUninstallActivity(componentName, shortcut.flags);
                 if (mWaitingForUninstall) {
                     final Runnable checkIfUninstallWasSuccess = new Runnable() {
                         @Override
                         public void run() {
                             mWaitingForUninstall = false;
                             String packageName = componentName.getPackageName();
-                            List<ResolveInfo> activities =
-                                    AllAppsList.findActivitiesForPackage(getContext(), packageName);
-                            boolean uninstallSuccessful = activities.size() == 0;
+                            boolean uninstallSuccessful = !AllAppsList.packageHasActivities(
+                                    getContext(), packageName, UserHandleCompat.myUserHandle());
                             if (dragSource instanceof Folder) {
                                 ((Folder) dragSource).
                                     onUninstallActivityReturned(uninstallSuccessful);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index e67ec19..8470b39 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -43,16 +43,18 @@
 
 
 class DeviceProfileQuery {
+    DeviceProfile profile;
     float widthDps;
     float heightDps;
     float value;
     PointF dimens;
 
-    DeviceProfileQuery(float w, float h, float v) {
-        widthDps = w;
-        heightDps = h;
+    DeviceProfileQuery(DeviceProfile p, float v) {
+        widthDps = p.minWidthDps;
+        heightDps = p.minHeightDps;
         value = v;
-        dimens = new PointF(w, h);
+        dimens = new PointF(widthDps, heightDps);
+        profile = p;
     }
 }
 
@@ -72,6 +74,9 @@
     private int iconDrawablePaddingOriginalPx;
     private float hotseatIconSize;
 
+    int defaultLayoutId;
+    int defaultNoAllAppsLayoutId;
+
     boolean isLandscape;
     boolean isTablet;
     boolean isLargeTablet;
@@ -127,7 +132,7 @@
     private ArrayList<DeviceProfileCallbacks> mCallbacks = new ArrayList<DeviceProfileCallbacks>();
 
     DeviceProfile(String n, float w, float h, float r, float c,
-                  float is, float its, float hs, float his) {
+                  float is, float its, float hs, float his, int dlId, int dnalId) {
         // Ensure that we have an odd number of hotseat items (since we need to place all apps)
         if (!LauncherAppState.isDisableAllApps() && hs % 2 == 0) {
             throw new RuntimeException("All Device Profiles must have an odd number of hotseat spaces");
@@ -142,6 +147,8 @@
         iconTextSize = its;
         numHotseatIcons = hs;
         hotseatIconSize = his;
+        defaultLayoutId = dlId;
+        defaultNoAllAppsLayoutId = dnalId;
     }
 
     DeviceProfile(Context context,
@@ -182,29 +189,32 @@
         overviewModeScaleFactor =
                 res.getInteger(R.integer.config_dynamic_grid_overview_scale_percentage) / 100f;
 
-        // Interpolate the rows
+        // Find the closes profile given the width/height
         for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numRows));
+            points.add(new DeviceProfileQuery(p, 0f));
         }
-        numRows = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
-        // Interpolate the columns
-        points.clear();
-        for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numColumns));
-        }
-        numColumns = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
-        // Interpolate the hotseat length
-        points.clear();
-        for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.numHotseatIcons));
-        }
-        numHotseatIcons = Math.round(invDistWeightedInterpolate(minWidth, minHeight, points));
+        DeviceProfile closestProfile = findClosestDeviceProfile(minWidth, minHeight, points);
+
+        // Snap to the closest row count
+        numRows = closestProfile.numRows;
+
+        // Snap to the closest column count
+        numColumns = closestProfile.numColumns;
+
+        // Snap to the closest hotseat size
+        numHotseatIcons = closestProfile.numHotseatIcons;
         hotseatAllAppsRank = (int) (numHotseatIcons / 2);
 
+        // Snap to the closest default layout id
+        defaultLayoutId = closestProfile.defaultLayoutId;
+
+        // Snap to the closest default no all-apps layout id
+        defaultNoAllAppsLayoutId = closestProfile.defaultNoAllAppsLayoutId;
+
         // Interpolate the icon size
         points.clear();
         for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconSize));
+            points.add(new DeviceProfileQuery(p, p.iconSize));
         }
         iconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
         // AllApps uses the original non-scaled icon size
@@ -213,7 +223,7 @@
         // Interpolate the icon text size
         points.clear();
         for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.iconTextSize));
+            points.add(new DeviceProfileQuery(p, p.iconTextSize));
         }
         iconTextSize = invDistWeightedInterpolate(minWidth, minHeight, points);
         iconDrawablePaddingOriginalPx =
@@ -224,7 +234,7 @@
         // Interpolate the hotseat icon size
         points.clear();
         for (DeviceProfile p : profiles) {
-            points.add(new DeviceProfileQuery(p.minWidthDps, p.minHeightDps, p.hotseatIconSize));
+            points.add(new DeviceProfileQuery(p, p.hotseatIconSize));
         }
         // Hotseat
         hotseatIconSize = invDistWeightedInterpolate(minWidth, minHeight, points);
@@ -398,6 +408,28 @@
         return (float) (1f / Math.pow(d, pow));
     }
 
+    /** Returns the closest device profile given the width and height and a list of profiles */
+    private DeviceProfile findClosestDeviceProfile(float width, float height,
+                                                   ArrayList<DeviceProfileQuery> points) {
+        return findClosestDeviceProfiles(width, height, points).get(0).profile;
+    }
+
+    /** Returns the closest device profiles ordered by closeness to the specified width and height */
+    private ArrayList<DeviceProfileQuery> findClosestDeviceProfiles(float width, float height,
+                                                   ArrayList<DeviceProfileQuery> points) {
+        final PointF xy = new PointF(width, height);
+
+        // Sort the profiles by their closeness to the dimensions
+        ArrayList<DeviceProfileQuery> pointsByNearness = points;
+        Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
+            public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
+                return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
+            }
+        });
+
+        return pointsByNearness;
+    }
+
     private float invDistWeightedInterpolate(float width, float height,
                 ArrayList<DeviceProfileQuery> points) {
         float sum = 0;
@@ -406,12 +438,8 @@
         float kNearestNeighbors = 3;
         final PointF xy = new PointF(width, height);
 
-        ArrayList<DeviceProfileQuery> pointsByNearness = points;
-        Collections.sort(pointsByNearness, new Comparator<DeviceProfileQuery>() {
-            public int compare(DeviceProfileQuery a, DeviceProfileQuery b) {
-                return (int) (dist(xy, a.dimens) - dist(xy, b.dimens));
-            }
-        });
+        ArrayList<DeviceProfileQuery> pointsByNearness = findClosestDeviceProfiles(width, height,
+                points);
 
         for (int i = 0; i < pointsByNearness.size(); ++i) {
             DeviceProfileQuery p = pointsByNearness.get(i);
diff --git a/src/com/android/launcher3/DynamicGrid.java b/src/com/android/launcher3/DynamicGrid.java
index 447bb1c..94a07d7 100644
--- a/src/com/android/launcher3/DynamicGrid.java
+++ b/src/com/android/launcher3/DynamicGrid.java
@@ -60,36 +60,41 @@
         DEFAULT_ICON_SIZE_PX = pxFromDp(DEFAULT_ICON_SIZE_DP, dm);
         // Our phone profiles include the bar sizes in each orientation
         deviceProfiles.add(new DeviceProfile("Super Short Stubby",
-                255, 300,  2, 3,  48, 13, (hasAA ? 5 : 5), 48));
+                255, 300,  2, 3,  48, 13, (hasAA ? 3 : 5), 48, R.xml.default_workspace_4x4,
+                R.xml.default_workspace_4x4_no_all_apps));
         deviceProfiles.add(new DeviceProfile("Shorter Stubby",
-                255, 400,  3, 3,  48, 13, (hasAA ? 5 : 5), 48));
+                255, 400,  3, 3,  48, 13, (hasAA ? 3 : 5), 48, R.xml.default_workspace_4x4,
+                R.xml.default_workspace_4x4_no_all_apps));
         deviceProfiles.add(new DeviceProfile("Short Stubby",
-                275, 420,  3, 4,  48, 13, (hasAA ? 5 : 5), 48));
+                275, 420,  3, 4,  48, 13, (hasAA ? 5 : 5), 48, R.xml.default_workspace_4x4,
+                R.xml.default_workspace_4x4_no_all_apps));
         deviceProfiles.add(new DeviceProfile("Stubby",
-                255, 450,  3, 4,  48, 13, (hasAA ? 5 : 5), 48));
+                255, 450,  3, 4,  48, 13, (hasAA ? 5 : 5), 48, R.xml.default_workspace_4x4,
+                R.xml.default_workspace_4x4_no_all_apps));
         deviceProfiles.add(new DeviceProfile("Nexus S",
-                296, 491.33f,  4, 4,  48, 13, (hasAA ? 5 : 5), 48));
+                296, 491.33f,  4, 4,  48, 13, (hasAA ? 5 : 5), 48, R.xml.default_workspace_4x4,
+                R.xml.default_workspace_4x4_no_all_apps));
         deviceProfiles.add(new DeviceProfile("Nexus 4",
-                335, 567,  4, 4,  DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56));
+                335, 567,  4, 4,  DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4,
+                R.xml.default_workspace_4x4_no_all_apps));
         deviceProfiles.add(new DeviceProfile("Nexus 5",
-                359, 567,  4, 4,  DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56));
+                359, 567,  4, 4,  DEFAULT_ICON_SIZE_DP, 13, (hasAA ? 5 : 5), 56, R.xml.default_workspace_4x4,
+                R.xml.default_workspace_4x4_no_all_apps));
         deviceProfiles.add(new DeviceProfile("Large Phone",
-                406, 694,  5, 5,  64, 14.4f,  5, 56));
+                406, 694,  5, 5,  64, 14.4f,  5, 56, R.xml.default_workspace_5x5,
+                R.xml.default_workspace_5x5_no_all_apps));
         // The tablet profile is odd in that the landscape orientation
         // also includes the nav bar on the side
         deviceProfiles.add(new DeviceProfile("Nexus 7",
-                575, 904,  5, 6,  72, 14.4f,  7, 60));
+                575, 904,  5, 6,  72, 14.4f,  7, 60, R.xml.default_workspace_5x6,
+                R.xml.default_workspace_5x6_no_all_apps));
         // Larger tablet profiles always have system bars on the top & bottom
         deviceProfiles.add(new DeviceProfile("Nexus 10",
-                727, 1207,  5, 6,  76, 14.4f,  7, 64));
-        /*
-        deviceProfiles.add(new DeviceProfile("Nexus 7",
-                600, 960,  5, 5,  72, 14.4f,  5, 60));
-        deviceProfiles.add(new DeviceProfile("Nexus 10",
-                800, 1280,  5, 5,  80, 14.4f, (hasAA ? 7 : 6), 64));
-         */
+                727, 1207,  5, 6,  76, 14.4f,  7, 64, R.xml.default_workspace_5x6,
+                R.xml.default_workspace_5x6_no_all_apps));
         deviceProfiles.add(new DeviceProfile("20-inch Tablet",
-                1527, 2527,  7, 7,  100, 20,  7, 72));
+                1527, 2527,  7, 7,  100, 20,  7, 72, R.xml.default_workspace_4x4,
+                R.xml.default_workspace_4x4_no_all_apps));
         mMinWidth = dpiFromPx(minWidthPx, dm);
         mMinHeight = dpiFromPx(minHeightPx, dm);
         mProfile = new DeviceProfile(context, deviceProfiles,
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index fb226e5..e900c2b 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -391,7 +391,7 @@
         // We rearrange the items in case there are any empty gaps
         setupContentForNumItems(count);
 
-        // If our folder has too many items we prune them from the list. This is an issue 
+        // If our folder has too many items we prune them from the list. This is an issue
         // when upgrading from the old Folders implementation which could contain an unlimited
         // number of items.
         for (ShortcutInfo item: overflow) {
@@ -583,7 +583,7 @@
         // by another item.
         if (mContent.getChildAt(item.cellX, item.cellY) != null || item.cellX < 0 || item.cellY < 0
                 || item.cellX >= mContent.getCountX() || item.cellY >= mContent.getCountY()) {
-            // This shouldn't happen, log it. 
+            // This shouldn't happen, log it.
             Log.e(TAG, "Folder order not properly persisted during bind");
             if (!findAndSetEmptyCells(item)) {
                 return null;
@@ -797,12 +797,6 @@
             }
         }
 
-        // This is kind of hacky, but in general, dropping on the workspace handles removing
-        // the extra screen, but dropping elsewhere (back to self, or onto delete) doesn't.
-        if (target != mLauncher.getWorkspace()) {
-            mLauncher.getWorkspace().removeExtraEmptyScreen(true, null);
-        }
-
         mDeleteFolderOnDropCompleted = false;
         mDragInProgress = false;
         mItemAddedBackToSelfViaIcon = false;
@@ -1176,21 +1170,15 @@
     public void onDrop(DragObject d) {
         Runnable cleanUpRunnable = null;
 
-        // If we are coming from All Apps space, we need to remove the extra empty screen (which is
-        // normally done in Workspace#onDropExternal, as well zoom back in and close the folder.
+        // If we are coming from All Apps space, we defer removing the extra empty screen
+        // until the folder closes
         if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) {
             cleanUpRunnable = new Runnable() {
                 @Override
                 public void run() {
-                    mLauncher.getWorkspace().removeExtraEmptyScreen(false, new Runnable() {
-                        @Override
-                        public void run() {
-                            mLauncher.closeFolder();
-                            mLauncher.exitSpringLoadedDragModeDelayed(true,
-                                    Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT_FOLDER_CLOSE,
-                                    null);
-                        }
-                    }, CLOSE_FOLDER_DELAY_MS, false);
+                    mLauncher.exitSpringLoadedDragModeDelayed(true,
+                            Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT,
+                            null);
                 }
             };
         }
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index 71a7461..be6cf48 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -33,6 +33,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
@@ -72,6 +73,8 @@
     // The amount of vertical spread between items in the stack [0...1]
     private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f;
 
+    private static final float PERSPECTIVE_SHIFT_FACTOR_L = 0.18f;
+
     // Flag as to whether or not to draw an outer ring. Currently none is designed.
     public static final boolean HAS_OUTER_RING = true;
 
@@ -105,6 +108,8 @@
     boolean mAnimating = false;
     private Rect mOldBounds = new Rect();
 
+    private float mSlop;
+
     private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0);
     private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0);
     private ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
@@ -341,7 +346,12 @@
         mFolderRingAnimator.animateToAcceptState();
         layout.showFolderAccept(mFolderRingAnimator);
         mOpenAlarm.setOnAlarmListener(mOnOpenListener);
-        if (SPRING_LOADING_ENABLED) {
+        if (SPRING_LOADING_ENABLED &&
+                ((dragInfo instanceof AppInfo) || (dragInfo instanceof ShortcutInfo))) {
+            // TODO: we currently don't support spring-loading for PendingAddShortcutInfos even
+            // though widget-style shortcuts can be added to folders. The issue is that we need
+            // to deal with configuration activities which are currently handled in
+            // Workspace#onDropExternal.
             mOpenAlarm.setAlarm(ON_OPEN_DELAY);
         }
         mDragInfo = (ItemInfo) dragInfo;
@@ -359,6 +369,7 @@
                 item.spanX = 1;
                 item.spanY = 1;
             } else {
+                // ShortcutInfo
                 item = (ShortcutInfo) mDragInfo;
             }
             mFolder.beginExternalDrag(item);
@@ -386,7 +397,7 @@
 
     public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
         Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
-        computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(), 
+        computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
                 finalView.getMeasuredWidth());
 
         // This will animate the first item from it's position as an icon into its
@@ -492,10 +503,16 @@
             int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f));
 
             int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
+            if (Utilities.isLmp()) {
+                unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR_L));
+            }
             mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight);
 
             mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale);
             mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR;
+            if (Utilities.isLmp()) {
+                mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR_L;
+            }
 
             mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2;
             mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset;
@@ -550,6 +567,10 @@
         float totalScale = mBaselineIconScale * scale;
         final int overlayAlpha = (int) (80 * (1 - r));
 
+        if (Utilities.isLmp()) {
+            transX = (mAvailableSpaceInPreview - scaledSize) / 2;
+        }
+
         if (params == null) {
             params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
         } else {
@@ -703,11 +724,22 @@
             case MotionEvent.ACTION_UP:
                 mLongPressHelper.cancelLongPress();
                 break;
+            case MotionEvent.ACTION_MOVE:
+                if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
+                    mLongPressHelper.cancelLongPress();
+                }
+                break;
         }
         return result;
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    @Override
     public void cancelLongPress() {
         super.cancelLongPress();
 
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 42e2ec3..85a792f 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -17,8 +17,12 @@
 package com.android.launcher3;
 
 import android.content.ContentValues;
+import android.content.Context;
+
+import com.android.launcher3.compat.UserHandleCompat;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Represents a folder containing shortcuts or apps.
@@ -39,6 +43,7 @@
 
     FolderInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+        user = UserHandleCompat.myUserHandle();
     }
 
     /**
@@ -75,8 +80,8 @@
     }
 
     @Override
-    void onAddToDatabase(ContentValues values) {
-        super.onAddToDatabase(values);
+    void onAddToDatabase(Context context, ContentValues values) {
+        super.onAddToDatabase(context, values);
         values.put(LauncherSettings.Favorites.TITLE, title.toString());
     }
 
@@ -114,6 +119,6 @@
         return "FolderInfo(id=" + this.id + " type=" + this.itemType
                 + " container=" + this.container + " screen=" + screenId
                 + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
-                + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+                + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos) + ")";
     }
 }
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 2ac2f00..4b25433 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -150,6 +150,11 @@
             TextView allAppsButton = (TextView)
                     inflater.inflate(R.layout.all_apps_button, mContent, false);
             Drawable d = context.getResources().getDrawable(R.drawable.all_apps_button_icon);
+
+            if (Utilities.isLmp()) {
+                d = context.getResources().getDrawable(R.drawable.all_apps_button_icon_l);
+            }
+
             Utilities.resizeIconDrawable(d);
             allAppsButton.setCompoundDrawables(null, d, null, null);
 
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index ee9f4d4..be02d35 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -31,8 +31,14 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.util.Log;
 
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -61,11 +67,35 @@
         public String title;
     }
 
-    private final Bitmap mDefaultIcon;
+    private static class CacheKey {
+        public ComponentName componentName;
+        public UserHandleCompat user;
+
+        CacheKey(ComponentName componentName, UserHandleCompat user) {
+            this.componentName = componentName;
+            this.user = user;
+        }
+
+        @Override
+        public int hashCode() {
+            return componentName.hashCode() + user.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            CacheKey other = (CacheKey) o;
+            return other.componentName.equals(componentName) && other.user.equals(user);
+        }
+    }
+
+    private final HashMap<UserHandleCompat, Bitmap> mDefaultIcons =
+            new HashMap<UserHandleCompat, Bitmap>();
     private final Context mContext;
     private final PackageManager mPackageManager;
-    private final HashMap<ComponentName, CacheEntry> mCache =
-            new HashMap<ComponentName, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
+    private final UserManagerCompat mUserManager;
+    private final LauncherAppsCompat mLauncherApps;
+    private final HashMap<CacheKey, CacheEntry> mCache =
+            new HashMap<CacheKey, CacheEntry>(INITIAL_ICON_CACHE_CAPACITY);
     private int mIconDpi;
 
     public IconCache(Context context) {
@@ -74,10 +104,13 @@
 
         mContext = context;
         mPackageManager = context.getPackageManager();
+        mUserManager = UserManagerCompat.getInstance(mContext);
+        mLauncherApps = LauncherAppsCompat.getInstance(mContext);
         mIconDpi = activityManager.getLauncherLargeIconDensity();
 
         // need to set mIconDpi before getting default icon
-        mDefaultIcon = makeDefaultIcon();
+        UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+        mDefaultIcons.put(myUser, makeDefaultIcon(myUser));
     }
 
     public Drawable getFullResDefaultActivityIcon() {
@@ -134,8 +167,9 @@
         return getFullResDefaultActivityIcon();
     }
 
-    private Bitmap makeDefaultIcon() {
-        Drawable d = getFullResDefaultActivityIcon();
+    private Bitmap makeDefaultIcon(UserHandleCompat user) {
+        Drawable unbadged = getFullResDefaultActivityIcon();
+        Drawable d = mUserManager.getBadgedDrawableForUser(unbadged, user);
         Bitmap b = Bitmap.createBitmap(Math.max(d.getIntrinsicWidth(), 1),
                 Math.max(d.getIntrinsicHeight(), 1),
                 Bitmap.Config.ARGB_8888);
@@ -149,24 +183,25 @@
     /**
      * Remove any records for the supplied ComponentName.
      */
-    public void remove(ComponentName componentName) {
+    public void remove(ComponentName componentName, UserHandleCompat user) {
         synchronized (mCache) {
-            mCache.remove(componentName);
+            mCache.remove(new CacheKey(componentName, user));
         }
     }
 
     /**
      * Remove any records for the supplied package name.
      */
-    public void remove(String packageName) {
-        HashSet<ComponentName> forDeletion = new HashSet<ComponentName>();
-        for (ComponentName componentName: mCache.keySet()) {
-            if (componentName.getPackageName().equals(packageName)) {
-                forDeletion.add(componentName);
+    public void remove(String packageName, UserHandleCompat user) {
+        HashSet<CacheKey> forDeletion = new HashSet<CacheKey>();
+        for (CacheKey key: mCache.keySet()) {
+            if (key.componentName.getPackageName().equals(packageName)
+                    && key.user.equals(user)) {
+                forDeletion.add(key);
             }
         }
-        for (ComponentName condemned: forDeletion) {
-            remove(condemned);
+        for (CacheKey condemned: forDeletion) {
+            mCache.remove(condemned);
         }
     }
 
@@ -184,7 +219,7 @@
      */
     public void flushInvalidIcons(DeviceProfile grid) {
         synchronized (mCache) {
-            Iterator<Entry<ComponentName, CacheEntry>> it = mCache.entrySet().iterator();
+            Iterator<Entry<CacheKey, CacheEntry>> it = mCache.entrySet().iterator();
             while (it.hasNext()) {
                 final CacheEntry e = it.next().getValue();
                 if (e.icon.getWidth() < grid.iconSizePx || e.icon.getHeight() < grid.iconSizePx) {
@@ -197,30 +232,34 @@
     /**
      * Fill in "application" with the icon and label for "info."
      */
-    public void getTitleAndIcon(AppInfo application, ResolveInfo info,
+    public void getTitleAndIcon(AppInfo application, LauncherActivityInfoCompat info,
             HashMap<Object, CharSequence> labelCache) {
         synchronized (mCache) {
-            CacheEntry entry = cacheLocked(application.componentName, info, labelCache);
+            CacheEntry entry = cacheLocked(application.componentName, info, labelCache,
+                    info.getUser());
 
             application.title = entry.title;
             application.iconBitmap = entry.icon;
         }
     }
 
-    public Bitmap getIcon(Intent intent) {
-        return getIcon(intent, null);
+    public Bitmap getIcon(Intent intent, UserHandleCompat user) {
+        return getIcon(intent, null, user);
     }
 
-    public Bitmap getIcon(Intent intent, String title) {
+    public Bitmap getIcon(Intent intent, String title, UserHandleCompat user) {
         synchronized (mCache) {
-            final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
+            LauncherActivityInfoCompat launcherActInfo =
+                    mLauncherApps.resolveActivity(intent, user);
             ComponentName component = intent.getComponent();
 
-            if (component == null) {
-                return mDefaultIcon;
+            // null info means not installed, but if we have a component from the intent then
+            // we should still look in the cache for restored app icons.
+            if (launcherActInfo == null && component == null) {
+                return getDefaultIcon(user);
             }
 
-            CacheEntry entry = cacheLocked(component, resolveInfo, null);
+            CacheEntry entry = cacheLocked(component, launcherActInfo, null, user);
             if (title != null) {
                 entry.title = title;
             }
@@ -228,49 +267,54 @@
         }
     }
 
-    public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo,
+    public Bitmap getDefaultIcon(UserHandleCompat user) {
+        if (!mDefaultIcons.containsKey(user)) {
+            mDefaultIcons.put(user, makeDefaultIcon(user));
+        }
+        return mDefaultIcons.get(user);
+    }
+
+    public Bitmap getIcon(ComponentName component, LauncherActivityInfoCompat info,
             HashMap<Object, CharSequence> labelCache) {
         synchronized (mCache) {
-            if (resolveInfo == null || component == null) {
+            if (info == null || component == null) {
                 return null;
             }
 
-            CacheEntry entry = cacheLocked(component, resolveInfo, labelCache);
+            CacheEntry entry = cacheLocked(component, info, labelCache, info.getUser());
             return entry.icon;
         }
     }
 
-    public boolean isDefaultIcon(Bitmap icon) {
-        return mDefaultIcon == icon;
+    public boolean isDefaultIcon(Bitmap icon, UserHandleCompat user) {
+        return mDefaultIcons.get(user) == icon;
     }
 
-    private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
-            HashMap<Object, CharSequence> labelCache) {
-        CacheEntry entry = mCache.get(componentName);
+    private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
+            HashMap<Object, CharSequence> labelCache, UserHandleCompat user) {
+        CacheKey cacheKey = new CacheKey(componentName, user);
+        CacheEntry entry = mCache.get(cacheKey);
         if (entry == null) {
             entry = new CacheEntry();
 
-            mCache.put(componentName, entry);
+            mCache.put(cacheKey, entry);
 
             if (info != null) {
-                ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
-                if (labelCache != null && labelCache.containsKey(key)) {
-                    entry.title = labelCache.get(key).toString();
+                ComponentName labelKey = info.getComponentName();
+                if (labelCache != null && labelCache.containsKey(labelKey)) {
+                    entry.title = labelCache.get(labelKey).toString();
                 } else {
-                    entry.title = info.loadLabel(mPackageManager).toString();
+                    entry.title = info.getLabel().toString();
                     if (labelCache != null) {
-                        labelCache.put(key, entry.title);
+                        labelCache.put(labelKey, entry.title);
                     }
                 }
-                if (entry.title == null) {
-                    entry.title = info.activityInfo.name;
-                }
 
                 entry.icon = Utilities.createIconBitmap(
-                        getFullResIcon(info), mContext);
+                        info.getBadgedIcon(mIconDpi), mContext);
             } else {
                 entry.title = "";
-                Bitmap preloaded = getPreloadedIcon(componentName);
+                Bitmap preloaded = getPreloadedIcon(componentName, user);
                 if (preloaded != null) {
                     if (DEBUG) Log.d(TAG, "using preloaded icon for " +
                             componentName.toShortString());
@@ -278,7 +322,7 @@
                 } else {
                     if (DEBUG) Log.d(TAG, "using default icon for " +
                             componentName.toShortString());
-                    entry.icon = mDefaultIcon;
+                    entry.icon = getDefaultIcon(user);
                 }
             }
         }
@@ -288,9 +332,9 @@
     public HashMap<ComponentName,Bitmap> getAllIcons() {
         synchronized (mCache) {
             HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
-            for (ComponentName cn : mCache.keySet()) {
-                final CacheEntry e = mCache.get(cn);
-                set.put(cn, e.icon);
+            for (CacheKey ck : mCache.keySet()) {
+                final CacheEntry e = mCache.get(ck);
+                set.put(ck.componentName, e.icon);
             }
             return set;
         }
@@ -353,9 +397,14 @@
      * @param componentName the component that should own the icon
      * @returns a bitmap if one is cached, or null.
      */
-    private Bitmap getPreloadedIcon(ComponentName componentName) {
+    private Bitmap getPreloadedIcon(ComponentName componentName, UserHandleCompat user) {
         final String key = componentName.flattenToShortString();
 
+        // We don't keep icons for other profiles in persistent cache.
+        if (!user.equals(UserHandleCompat.myUserHandle())) {
+            return null;
+        }
+
         if (DEBUG) Log.v(TAG, "looking for pre-load icon for " + key);
         Bitmap icon = null;
         FileInputStream resourceFile = null;
@@ -396,7 +445,11 @@
      * @param componentName the component that should own the icon
      * @returns true on success
      */
-    public boolean deletePreloadedIcon(ComponentName componentName) {
+    public boolean deletePreloadedIcon(ComponentName componentName, UserHandleCompat user) {
+        // We don't keep icons for other profiles in persistent cache.
+        if (!user.equals(UserHandleCompat.myUserHandle())) {
+            return false;
+        }
         if (componentName == null) {
             return false;
         }
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index 374238c..c8541a9 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -49,6 +49,13 @@
         Resources r = getResources();
         mHoverColor = r.getColor(R.color.info_target_hover_tint);
         mDrawable = (TransitionDrawable) getCurrentDrawable();
+
+        if (mDrawable == null) {
+            // TODO: investigate why this is ever happening. Presently only on one known device.
+            mDrawable = (TransitionDrawable) r.getDrawable(R.drawable.info_target_selector);
+            setCompoundDrawablesRelativeWithIntrinsicBounds(mDrawable, null, null, null);
+        }
+
         if (null != mDrawable) {
             mDrawable.setCrossFadeEnabled(true);
         }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 28cef13..2edde4f 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -29,6 +30,8 @@
 import android.util.Log;
 import android.widget.Toast;
 
+import com.android.launcher3.compat.UserHandleCompat;
+
 import org.json.JSONObject;
 import org.json.JSONStringer;
 import org.json.JSONTokener;
@@ -280,19 +283,27 @@
                 final boolean exists = LauncherModel.shortcutExists(context, name, intent);
                 //final boolean allowDuplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
 
-                // TODO-XXX: Disable duplicates for now
-                if (!exists /* && allowDuplicate */) {
+                // If the intent specifies a package, make sure the package exists
+                String packageName = intent.getPackage();
+                if (packageName == null) {
+                    packageName = intent.getComponent() == null ? null :
+                        intent.getComponent().getPackageName();
+                }
+                if (packageName != null && !packageName.isEmpty()) {
+                    UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
+                    if (!LauncherModel.isValidPackage(context, packageName, myUserHandle)) {
+                        if (DBG) Log.d(TAG, "Ignoring shortcut for absent package:" + intent);
+                        continue;
+                    }
+                }
+
+                if (!exists) {
                     // Generate a shortcut info to add into the model
                     ShortcutInfo info = getShortcutInfo(context, pendingInfo.data,
                             pendingInfo.launchIntent);
                     addShortcuts.add(info);
                 }
-                /*
-                else if (exists && !allowDuplicate) {
-                    result = INSTALL_SHORTCUT_IS_DUPLICATE;
-                    duplicateName = name;
-                }
-                */
+
             }
 
             // Notify the user once if we weren't able to place any duplicates
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 3fe4c60..74f16e3 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -17,17 +17,27 @@
 package com.android.launcher3;
 
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.util.Log;
 
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.util.Arrays;
 
 /**
  * Represents an item in the launcher.
  */
 public class ItemInfo {
+
+    /**
+     * Intent extra to store the profile. Format: UserHandle
+     */
+    static final String EXTRA_PROFILE = "profile";
     
     static final int NO_ID = -1;
     
@@ -102,6 +112,8 @@
      */
     int[] dropPos = null;
 
+    UserHandleCompat user;
+
     ItemInfo() {
     }
 
@@ -114,6 +126,7 @@
         screenId = info.screenId;
         itemType = info.itemType;
         container = info.container;
+        user = info.user;
         // tempdebug:
         LauncherModel.checkItemInfo(this);
     }
@@ -129,9 +142,11 @@
     /**
      * Write the fields of this item to the DB
      * 
+     * @param context A context object to use for getting UserManagerCompat
      * @param values
      */
-    void onAddToDatabase(ContentValues values) { 
+
+    void onAddToDatabase(Context context, ContentValues values) {
         values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType);
         values.put(LauncherSettings.Favorites.CONTAINER, container);
         values.put(LauncherSettings.Favorites.SCREEN, screenId);
@@ -139,6 +154,13 @@
         values.put(LauncherSettings.Favorites.CELLY, cellY);
         values.put(LauncherSettings.Favorites.SPANX, spanX);
         values.put(LauncherSettings.Favorites.SPANY, spanY);
+        long serialNumber = UserManagerCompat.getInstance(context).getSerialNumberForUser(user);
+        values.put(LauncherSettings.Favorites.PROFILE_ID, serialNumber);
+
+        if (screenId == Workspace.EXTRA_EMPTY_SCREEN_ID) {
+            // We should never persist an item on the extra empty screen.
+            throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID");
+        }
     }
 
     void updateValuesWithCoordinates(ContentValues values, int cellX, int cellY) {
@@ -182,6 +204,7 @@
     public String toString() {
         return "Item(id=" + this.id + " type=" + this.itemType + " container=" + this.container
             + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX
-            + " spanY=" + spanY + " dropPos=" + dropPos + ")";
+            + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
+            + " user=" + user + ")";
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ff5b1eb..60efcea 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -24,6 +24,7 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -44,12 +45,12 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
@@ -79,6 +80,7 @@
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
+import android.view.Window;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
@@ -94,10 +96,12 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
-
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.PagedView.PageSwitchListener;
-
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
@@ -106,6 +110,9 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -134,7 +141,6 @@
 
     private static final int REQUEST_CREATE_SHORTCUT = 1;
     private static final int REQUEST_CREATE_APPWIDGET = 5;
-    private static final int REQUEST_PICK_APPLICATION = 6;
     private static final int REQUEST_PICK_SHORTCUT = 7;
     private static final int REQUEST_PICK_APPWIDGET = 9;
     private static final int REQUEST_PICK_WALLPAPER = 10;
@@ -212,7 +218,6 @@
 
     static final int APPWIDGET_HOST_ID = 1024;
     public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300;
-    public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT_FOLDER_CLOSE = 400;
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
     private static final int ACTIVITY_START_DELAY = 1000;
 
@@ -355,8 +360,7 @@
         }
     };
 
-    private static ArrayList<PendingAddArguments> sPendingAddList
-            = new ArrayList<PendingAddArguments>();
+    private static PendingAddArguments sPendingAddItem;
 
     public static boolean sForceEnableRotation = isPropertyEnabled(FORCE_ENABLE_ROTATION_PROPERTY);
 
@@ -367,6 +371,7 @@
         long screenId;
         int cellX;
         int cellY;
+        int appWidgetId;
     }
 
     private Stats mStats;
@@ -731,40 +736,31 @@
      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
      * a configuration step, this allows the proper animations to run after other transitions.
      */
-    private boolean completeAdd(PendingAddArguments args) {
-        boolean result = false;
+    private long completeAdd(PendingAddArguments args) {
+
+        long screenId = ensurePendingDropLayoutExists(args.screenId);
         switch (args.requestCode) {
-            case REQUEST_PICK_APPLICATION:
-                completeAddApplication(args.intent, args.container, args.screenId, args.cellX,
-                        args.cellY);
-                break;
-            case REQUEST_PICK_SHORTCUT:
-                processShortcut(args.intent);
-                break;
             case REQUEST_CREATE_SHORTCUT:
-                completeAddShortcut(args.intent, args.container, args.screenId, args.cellX,
+                completeAddShortcut(args.intent, args.container, screenId, args.cellX,
                         args.cellY);
-                result = true;
                 break;
             case REQUEST_CREATE_APPWIDGET:
-                int appWidgetId = args.intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
-                completeAddAppWidget(appWidgetId, args.container, args.screenId, null, null);
-                result = true;
+                completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null);
                 break;
         }
         // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen,
         // if you turned the screen off and then back while in All Apps, Launcher would not
         // return to the workspace. Clearing mAddInfo.container here fixes this issue
         resetAddInfo();
-        return result;
+        return screenId;
     }
 
     @Override
     protected void onActivityResult(
             final int requestCode, final int resultCode, final Intent data) {
         // Reset the startActivity waiting flag
-        mWaitingForResult = false;
-        int pendingAddWidgetId = mPendingAddWidgetId;
+        setWaitingForResult(false);
+        final int pendingAddWidgetId = mPendingAddWidgetId;
         mPendingAddWidgetId = -1;
 
         Runnable exitSpringLoaded = new Runnable() {
@@ -780,7 +776,7 @@
                     data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
             if (resultCode == RESULT_CANCELED) {
                 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId);
-                mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
+                mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
                         ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
             } else if (resultCode == RESULT_OK) {
                 addAppWidgetImpl(appWidgetId, mPendingAddInfo, null,
@@ -797,6 +793,7 @@
         boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
                 requestCode == REQUEST_CREATE_APPWIDGET);
 
+        final boolean workspaceLocked = isWorkspaceLocked();
         // We have special handling for widgets
         if (isWidgetDrop) {
             final int appWidgetId;
@@ -809,33 +806,46 @@
             }
 
             final int result;
-            final Runnable onComplete;
             if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
-                Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not returned from the \\" +
-                        "widget configuration activity.");
+                Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
+                        "returned from the widget configuration activity.");
                 result = RESULT_CANCELED;
                 completeTwoStageWidgetDrop(result, appWidgetId);
-                onComplete = new Runnable() {
+                final Runnable onComplete = new Runnable() {
                     @Override
                     public void run() {
                         exitSpringLoadedDragModeDelayed(false, 0, null);
                     }
                 };
+                if (workspaceLocked) {
+                    // No need to remove the empty screen if we're mid-binding, as the
+                    // the bind will not add the empty screen.
+                    mWorkspace.postDelayed(onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY);
+                } else {
+                    mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
+                            ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                }
             } else {
-                result = resultCode;
-                final CellLayout dropLayout =
-                        (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
-                dropLayout.setDropPending(true);
-                onComplete = new Runnable() {
-                    @Override
-                    public void run() {
-                        completeTwoStageWidgetDrop(result, appWidgetId);
-                        dropLayout.setDropPending(false);
-                    }
-                };
+                if (!workspaceLocked) {
+                    mPendingAddInfo.screenId = ensurePendingDropLayoutExists(mPendingAddInfo.screenId);
+                    final CellLayout dropLayout = mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
+
+                    dropLayout.setDropPending(true);
+                    final Runnable onComplete = new Runnable() {
+                        @Override
+                        public void run() {
+                            completeTwoStageWidgetDrop(resultCode, appWidgetId);
+                            dropLayout.setDropPending(false);
+                        }
+                    };
+                    mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete,
+                            ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
+                } else {
+                    PendingAddArguments args = preparePendingAddArgs(requestCode, data, appWidgetId,
+                            mPendingAddInfo);
+                    sPendingAddItem = args;
+                }
             }
-            mWorkspace.removeExtraEmptyScreen(true, onComplete, ON_ACTIVITY_RESULT_ANIMATION_DELAY,
-                    false);
             return;
         }
 
@@ -845,27 +855,54 @@
         // For example, the user would PICK_SHORTCUT for "Music playlist", and we
         // launch over to the Music app to actually CREATE_SHORTCUT.
         if (resultCode == RESULT_OK && mPendingAddInfo.container != ItemInfo.NO_ID) {
-            final PendingAddArguments args = new PendingAddArguments();
-            args.requestCode = requestCode;
-            args.intent = data;
-            args.container = mPendingAddInfo.container;
-            args.screenId = mPendingAddInfo.screenId;
-            args.cellX = mPendingAddInfo.cellX;
-            args.cellY = mPendingAddInfo.cellY;
+            final PendingAddArguments args = preparePendingAddArgs(requestCode, data, -1,
+                    mPendingAddInfo);
             if (isWorkspaceLocked()) {
-                sPendingAddList.add(args);
+                sPendingAddItem = args;
             } else {
                 completeAdd(args);
+                mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
+                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
             }
-            mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
-                    ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
         } else if (resultCode == RESULT_CANCELED) {
-            mWorkspace.removeExtraEmptyScreen(true, exitSpringLoaded,
+            mWorkspace.removeExtraEmptyScreenDelayed(true, exitSpringLoaded,
                     ON_ACTIVITY_RESULT_ANIMATION_DELAY, false);
         }
         mDragLayer.clearAnimatedView();
     }
 
+    private PendingAddArguments preparePendingAddArgs(int requestCode, Intent data, int
+            appWidgetId, ItemInfo info) {
+        PendingAddArguments args = new PendingAddArguments();
+        args.requestCode = requestCode;
+        args.intent = data;
+        args.container = info.container;
+        args.screenId = info.screenId;
+        args.cellX = info.cellX;
+        args.cellY = info.cellY;
+        args.appWidgetId = appWidgetId;
+        return args;
+    }
+
+    /**
+     * Check to see if a given screen id exists. If not, create it at the end, return the new id.
+     *
+     * @param screenId the screen id to check
+     * @return the new screen, or screenId if it exists
+     */
+    private long ensurePendingDropLayoutExists(long screenId) {
+        CellLayout dropLayout =
+                (CellLayout) mWorkspace.getScreenWithId(screenId);
+        if (dropLayout == null) {
+            // it's possible that the add screen was removed because it was
+            // empty and a re-bind occurred
+            mWorkspace.addExtraEmptyScreen();
+            return mWorkspace.commitExtraEmptyScreen();
+        } else {
+            return screenId;
+        }
+    }
+
     private void completeTwoStageWidgetDrop(final int resultCode, final int appWidgetId) {
         CellLayout cellLayout =
                 (CellLayout) mWorkspace.getScreenWithId(mPendingAddInfo.screenId);
@@ -936,7 +973,7 @@
         mPaused = false;
         sPausedFromUserAction = false;
         if (mRestoring || mOnResumeNeedsLoad) {
-            mWorkspaceLoading = true;
+            setWorkspaceLoading(true);
             mModel.startLoader(true, PagedView.INVALID_RESTORE_PAGE);
             mRestoring = false;
             mOnResumeNeedsLoad = false;
@@ -1190,7 +1227,7 @@
             mPendingAddInfo.spanY = savedState.getInt(RUNTIME_STATE_PENDING_ADD_SPAN_Y);
             mPendingAddWidgetInfo = savedState.getParcelable(RUNTIME_STATE_PENDING_ADD_WIDGET_INFO);
             mPendingAddWidgetId = savedState.getInt(RUNTIME_STATE_PENDING_ADD_WIDGET_ID);
-            mWaitingForResult = true;
+            setWaitingForResult(true);
             mRestoring = true;
         }
 
@@ -1255,6 +1292,10 @@
             }
         });
         widgetButton.setOnTouchListener(getHapticFeedbackTouchListener());
+        if (Utilities.isLmp()) {
+            ((TextView) widgetButton).setCompoundDrawablesWithIntrinsicBounds(0,
+                    R.drawable.widget_button_l, 0, 0);
+        }
 
         View wallpaperButton = findViewById(R.id.wallpaper_button);
         wallpaperButton.setOnClickListener(new OnClickListener() {
@@ -1267,6 +1308,11 @@
         });
         wallpaperButton.setOnTouchListener(getHapticFeedbackTouchListener());
 
+        if (Utilities.isLmp()) {
+            ((TextView) wallpaperButton).setCompoundDrawablesWithIntrinsicBounds(0,
+                    R.drawable.wallpaper_button_l, 0, 0);
+        }
+
         View settingsButton = findViewById(R.id.settings_button);
         if (hasSettings()) {
             settingsButton.setOnClickListener(new OnClickListener() {
@@ -1278,6 +1324,10 @@
                 }
             });
             settingsButton.setOnTouchListener(getHapticFeedbackTouchListener());
+            if (Utilities.isLmp()) {
+                ((TextView) settingsButton).setCompoundDrawablesWithIntrinsicBounds(0,
+                        R.drawable.setting_button_l, 0, 0);
+            }
         } else {
             settingsButton.setVisibility(View.GONE);
             FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) widgetButton.getLayoutParams();
@@ -1368,38 +1418,6 @@
     }
 
     /**
-     * Add an application shortcut to the workspace.
-     *
-     * @param data The intent describing the application.
-     * @param cellInfo The position on screen where to create the shortcut.
-     */
-    void completeAddApplication(Intent data, long container, long screenId, int cellX, int cellY) {
-        final int[] cellXY = mTmpAddItemCellCoordinates;
-        final CellLayout layout = getCellLayout(container, screenId);
-
-        // First we check if we already know the exact location where we want to add this item.
-        if (cellX >= 0 && cellY >= 0) {
-            cellXY[0] = cellX;
-            cellXY[1] = cellY;
-        } else if (!layout.findCellForSpan(cellXY, 1, 1)) {
-            showOutOfSpaceMessage(isHotseatLayout(layout));
-            return;
-        }
-
-        final ShortcutInfo info = mModel.getShortcutInfo(getPackageManager(), data, this);
-
-        if (info != null) {
-            info.setActivity(this, data.getComponent(), Intent.FLAG_ACTIVITY_NEW_TASK |
-                    Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-            info.container = ItemInfo.NO_ID;
-            mWorkspace.addApplicationShortcut(info, layout, container, screenId, cellXY[0], cellXY[1],
-                    isWorkspaceLocked(), cellX, cellY);
-        } else {
-            Log.e(TAG, "Couldn't find ActivityInfo for selected application: " + data);
-        }
-    }
-
-    /**
      * Add a shortcut to the workspace.
      *
      * @param data The intent describing the shortcut.
@@ -1601,6 +1619,9 @@
                 mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
                         LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE
                                 | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS);
+            } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
+                    || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
+                getModel().forceReload();
             }
         }
     };
@@ -1613,16 +1634,61 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         filter.addAction(Intent.ACTION_USER_PRESENT);
+        // For handling managed profiles
+        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
+        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
         if (ENABLE_DEBUG_INTENTS) {
             filter.addAction(DebugIntents.DELETE_DATABASE);
             filter.addAction(DebugIntents.MIGRATE_DATABASE);
         }
         registerReceiver(mReceiver, filter);
         FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView());
+        setupTransparentSystemBarsForLmp();
         mAttached = true;
         mVisible = true;
     }
 
+    /**
+     * Sets up transparent navigation and status bars in LMP.
+     * This method is a no-op for other platform versions.
+     */
+    @TargetApi(19)
+    private void setupTransparentSystemBarsForLmp() {
+        // TODO(sansid): use the APIs directly when compiling against L sdk.
+        // Currently we use reflection to access the flags and the API to set the transparency
+        // on the System bars.
+        if (Utilities.isLmp()) {
+            try {
+                getWindow().getAttributes().systemUiVisibility |=
+                        (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+                getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
+                        | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+                Field drawsSysBackgroundsField = WindowManager.LayoutParams.class.getField(
+                        "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS");
+                getWindow().addFlags(drawsSysBackgroundsField.getInt(null));
+
+                Method setStatusBarColorMethod =
+                        Window.class.getDeclaredMethod("setStatusBarColor", int.class);
+                Method setNavigationBarColorMethod =
+                        Window.class.getDeclaredMethod("setNavigationBarColor", int.class);
+                setStatusBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
+                setNavigationBarColorMethod.invoke(getWindow(), Color.TRANSPARENT);
+            } catch (NoSuchFieldException e) {
+                Log.w(TAG, "NoSuchFieldException while setting up transparent bars");
+            } catch (NoSuchMethodException ex) {
+                Log.w(TAG, "NoSuchMethodException while setting up transparent bars");
+            } catch (IllegalAccessException e) {
+                Log.w(TAG, "IllegalAccessException while setting up transparent bars");
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG, "IllegalArgumentException while setting up transparent bars");
+            } catch (InvocationTargetException e) {
+                Log.w(TAG, "InvocationTargetException while setting up transparent bars");
+            } finally {}
+        }
+    }
+
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
@@ -1796,7 +1862,7 @@
         getWindow().closeAllPanels();
 
         // Whatever we were doing is hereby canceled.
-        mWaitingForResult = false;
+        setWaitingForResult(false);
     }
 
     @Override
@@ -1974,7 +2040,9 @@
 
     @Override
     public void startActivityForResult(Intent intent, int requestCode) {
-        if (requestCode >= 0) mWaitingForResult = true;
+        if (requestCode >= 0) {
+            setWaitingForResult(true);
+        }
         super.startActivityForResult(intent, requestCode);
     }
 
@@ -2088,6 +2156,24 @@
         return mWorkspaceLoading;
     }
 
+    private void setWorkspaceLoading(boolean value) {
+        boolean isLocked = isWorkspaceLocked();
+        mWorkspaceLoading = value;
+        if (isLocked != isWorkspaceLocked()) {
+            onWorkspaceLockedChanged();
+        }
+    }
+
+    private void setWaitingForResult(boolean value) {
+        boolean isLocked = isWorkspaceLocked();
+        mWaitingForResult = value;
+        if (isLocked != isWorkspaceLocked()) {
+            onWorkspaceLockedChanged();
+        }
+    }
+
+    protected void onWorkspaceLockedChanged() { }
+
     private void resetAddInfo() {
         mPendingAddInfo.container = ItemInfo.NO_ID;
         mPendingAddInfo.screenId = -1;
@@ -2126,7 +2212,7 @@
             };
             completeAddAppWidget(appWidgetId, info.container, info.screenId, boundWidget,
                     appWidgetInfo);
-            mWorkspace.removeExtraEmptyScreen(true, onComplete, delay, false);
+            mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false);
         }
     }
 
@@ -2220,21 +2306,7 @@
     }
 
     void processShortcut(Intent intent) {
-        // Handle case where user selected "Applications"
-        String applicationName = getResources().getString(R.string.group_applications);
-        String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-
-        if (applicationName != null && applicationName.equals(shortcutName)) {
-            Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
-            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
-            Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
-            pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
-            pickIntent.putExtra(Intent.EXTRA_TITLE, getText(R.string.title_select_application));
-            Utilities.startActivityForResultSafely(this, pickIntent, REQUEST_PICK_APPLICATION);
-        } else {
-            Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
-        }
+        Utilities.startActivityForResultSafely(this, intent, REQUEST_CREATE_SHORTCUT);
     }
 
     void processWallpaper(Intent intent) {
@@ -2652,19 +2724,37 @@
 
     boolean startActivity(View v, Intent intent, Object tag) {
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
         try {
             // Only launch using the new animation if the shortcut has not opted out (this is a
             // private contract between launcher and may be ignored in the future).
             boolean useLaunchAnimation = (v != null) &&
                     !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
+            LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
+            UserManagerCompat userManager = UserManagerCompat.getInstance(this);
+
+            UserHandleCompat user = null;
+            if (intent.hasExtra(AppInfo.EXTRA_PROFILE)) {
+                long serialNumber = intent.getLongExtra(AppInfo.EXTRA_PROFILE, -1);
+                user = userManager.getUserForSerialNumber(serialNumber);
+            }
+
+            Bundle optsBundle = null;
             if (useLaunchAnimation) {
                 ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
                         v.getMeasuredWidth(), v.getMeasuredHeight());
+                optsBundle = opts.toBundle();
+            }
+            if (useLaunchAnimation && Utilities.isLmp()) {
+                ActivityOptions opts = ActivityOptions.makeCustomAnimation(this, R.anim.task_open_enter, R.anim.no_anim);
+                optsBundle = opts.toBundle();
+            }
 
-                startActivity(intent, opts.toBundle());
+            if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
+                // Could be launching some bookkeeping activity
+                startActivity(intent, optsBundle);
             } else {
-                startActivity(intent);
+                launcherApps.startActivityForProfile(intent.getComponent(),
+                        intent.getSourceBounds(), optsBundle, user);
             }
             return true;
         } catch (SecurityException e) {
@@ -2864,23 +2954,22 @@
                 } else {
                     return false;
                 }
+            } else {
+                return false;
             }
         }
 
-        if (!(v instanceof CellLayout)) {
-            v = (View) v.getParent().getParent();
-        }
-
-        resetAddInfo();
-        CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
-        // This happens when long clicking an item with the dpad/trackball
-        if (longClickCellInfo == null) {
-            return true;
+        CellLayout.CellInfo longClickCellInfo = null;
+        View itemUnderLongClick = null;
+        if (v.getTag() instanceof ItemInfo) {
+            ItemInfo info = (ItemInfo) v.getTag();
+            longClickCellInfo = new CellLayout.CellInfo(v, info);;
+            itemUnderLongClick = longClickCellInfo.cell;
+            resetAddInfo();
         }
 
         // The hotseat touch handling does not go through Workspace, and we always allow long press
         // on hotseat items.
-        final View itemUnderLongClick = longClickCellInfo.cell;
         final boolean inHotseat = isHotseatLayout(v);
         boolean allowLongPress = inHotseat || mWorkspace.allowLongPress();
         if (allowLongPress && !mDragController.isDragging()) {
@@ -2888,7 +2977,6 @@
                 // User long pressed on empty space
                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-                // Disabling reordering until we sort out some issues.
                 if (mWorkspace.isInOverviewMode()) {
                     mWorkspace.startReordering(v);
                 } else {
@@ -3831,7 +3919,7 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void startBinding() {
-        mWorkspaceLoading = true;
+        setWorkspaceLoading(true);
 
         // If we're starting binding all over again, clear any bind calls we'd postponed in
         // the past (see waitUntilResume) -- we don't need them since we're starting binding
@@ -3931,7 +4019,7 @@
         }
 
         // Remove the extra empty screen
-        mWorkspace.removeExtraEmptyScreen(false, null);
+        mWorkspace.removeExtraEmptyScreen(false, false);
 
         if (!LauncherAppState.isDisableAllApps() &&
                 addedApps != null && mAppsCustomizeContent != null) {
@@ -4135,20 +4223,31 @@
 
         mWorkspace.restoreInstanceStateForRemainingPages();
 
-        // If we received the result of any pending adds while the loader was running (e.g. the
-        // widget configuration forced an orientation change), process them now.
-        for (int i = 0; i < sPendingAddList.size(); i++) {
-            completeAdd(sPendingAddList.get(i));
-        }
-        sPendingAddList.clear();
-
         // Update the market app icon as necessary (the other icons will be managed in response to
         // package changes in bindSearchablesChanged()
         if (!DISABLE_MARKET_BUTTON) {
             updateAppMarketIcon();
         }
 
-        mWorkspaceLoading = false;
+        setWorkspaceLoading(false);
+
+        // If we received the result of any pending adds while the loader was running (e.g. the
+        // widget configuration forced an orientation change), process them now.
+        if (sPendingAddItem != null) {
+            final long screenId = completeAdd(sPendingAddItem);
+
+            // TODO: this moves the user to the page where the pending item was added. Ideally,
+            // the screen would be guaranteed to exist after bind, and the page would be set through
+            // the workspace restore process.
+            mWorkspace.post(new Runnable() {
+                @Override
+                public void run() {
+                    mWorkspace.snapToScreenId(screenId);
+                }
+            });
+            sPendingAddItem = null;
+        }
+
         if (upgradePath) {
             mWorkspace.getUniqueComponents(true, null);
             mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
@@ -4270,10 +4369,10 @@
      * Implementation of the method from LauncherModel.Callbacks.
      */
     public void bindComponentsRemoved(final ArrayList<String> packageNames,
-                                      final ArrayList<AppInfo> appInfos) {
+            final ArrayList<AppInfo> appInfos, final UserHandleCompat user) {
         Runnable r = new Runnable() {
             public void run() {
-                bindComponentsRemoved(packageNames, appInfos);
+                bindComponentsRemoved(packageNames, appInfos, user);
             }
         };
         if (waitUntilResume(r)) {
@@ -4281,10 +4380,10 @@
         }
 
         if (!packageNames.isEmpty()) {
-            mWorkspace.removeItemsByPackageName(packageNames);
+            mWorkspace.removeItemsByPackageName(packageNames, user);
         }
         if (!appInfos.isEmpty()) {
-            mWorkspace.removeItemsByApplicationInfo(appInfos);
+            mWorkspace.removeItemsByApplicationInfo(appInfos, user);
         }
 
         // Notify the drag controller
@@ -4400,6 +4499,17 @@
         }
     }
 
+    /**
+     * This method indicates whether or not we should suggest default wallpaper dimensions
+     * when our wallpaper cropper was not yet used to set a wallpaper.
+     */
+    protected boolean overrideWallpaperDimensions() {
+        return true;
+    }
+
+    protected boolean shouldClingFocusHotseatApp() {
+        return false;
+    }
     protected String getFirstRunClingSearchBarHint() {
         return "";
     }
@@ -4440,7 +4550,9 @@
     public void dismissFolderCling(View v) {
         mLauncherClings.dismissFolderCling(v);
     }
-
+    public void markFolderClingDismissedIfNecessary() {
+        mLauncherClings.markFolderClingDismissedIfNecessary();
+    }
 
     /**
      * To be overridden by subclasses to indicate that there is an activity to launch
@@ -4555,16 +4667,27 @@
     }
 
     public ItemInfo createAppDragInfo(Intent appLaunchIntent) {
-        ResolveInfo ri = getPackageManager().resolveActivity(appLaunchIntent, 0);
-        if (ri == null) {
+        // Called from search suggestion, not supported in other profiles.
+        final UserHandleCompat myUser = UserHandleCompat.myUserHandle();
+        LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(this);
+        LauncherActivityInfoCompat activityInfo = launcherApps.resolveActivity(appLaunchIntent,
+                myUser);
+        if (activityInfo == null) {
             return null;
         }
-        return new AppInfo(getPackageManager(), ri, mIconCache, null);
+        return new AppInfo(this, activityInfo, myUser, mIconCache, null);
     }
 
     public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
             Bitmap icon) {
-        return new ShortcutInfo(shortcutIntent, caption, icon);
+        // Called from search suggestion, not supported in other profiles.
+        return createShortcutDragInfo(shortcutIntent, caption, icon,
+                UserHandleCompat.myUserHandle());
+    }
+
+    public ItemInfo createShortcutDragInfo(Intent shortcutIntent, CharSequence caption,
+            Bitmap icon, UserHandleCompat user) {
+        return new ShortcutInfo(shortcutIntent, caption, icon, user);
     }
 
     protected void moveWorkspaceToDefaultScreen() {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 5ddafea..79bc084 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -24,13 +24,15 @@
 import android.os.Handler;
 import android.util.Log;
 
+import com.android.launcher3.compat.LauncherAppsCompat;
+
 import java.lang.ref.WeakReference;
 
 public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
     private static final String TAG = "LauncherAppState";
     private static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;  // STOPSHIP(cwren) temporary for debugging
 
     private final AppFilter mAppFilter;
     private final BuildInfo mBuildInfo;
@@ -92,16 +94,11 @@
         mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
         mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
         mModel = new LauncherModel(this, mIconCache, mAppFilter);
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext);
+        launcherApps.addOnAppsChangedListener(mModel);
 
         // Register intent receivers
-        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        filter.addDataScheme("package");
-        sContext.registerReceiver(mModel, filter);
-        filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
-        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+        IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         sContext.registerReceiver(mModel, filter);
@@ -117,7 +114,7 @@
         resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
                 mFavoritesObserver);
     }
-    
+
     public void recreateWidgetPreviewDb() {
         if (mWidgetPreviewCacheDb != null) {
             mWidgetPreviewCacheDb.close();
@@ -130,6 +127,8 @@
      */
     public void onTerminate() {
         sContext.unregisterReceiver(mModel);
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext);
+        launcherApps.removeOnAppsChangedListener(mModel);
 
         ContentResolver resolver = sContext.getContentResolver();
         resolver.unregisterContentObserver(mFavoritesObserver);
diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java
index 51a649a..f47fd13 100644
--- a/src/com/android/launcher3/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java
@@ -21,6 +21,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.widget.RemoteViews;
 
@@ -36,6 +37,8 @@
     private int mPreviousOrientation;
     private DragLayer mDragLayer;
 
+    private float mSlop;
+
     public LauncherAppWidgetHostView(Context context) {
         super(context);
         mContext = context;
@@ -90,6 +93,11 @@
             case MotionEvent.ACTION_CANCEL:
                 mLongPressHelper.cancelLongPress();
                 break;
+            case MotionEvent.ACTION_MOVE:
+                if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) {
+                    mLongPressHelper.cancelLongPress();
+                }
+                break;
         }
 
         // Otherwise continue letting touch events fall through to children
@@ -104,11 +112,22 @@
             case MotionEvent.ACTION_CANCEL:
                 mLongPressHelper.cancelLongPress();
                 break;
+            case MotionEvent.ACTION_MOVE:
+                if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) {
+                    mLongPressHelper.cancelLongPress();
+                }
+                break;
         }
         return false;
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+    }
+
+    @Override
     public void cancelLongPress() {
         super.cancelLongPress();
         mLongPressHelper.cancelLongPress();
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index 950828e..bec1f9e 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -19,6 +19,9 @@
 import android.appwidget.AppWidgetHostView;
 import android.content.ComponentName;
 import android.content.ContentValues;
+import android.content.Context;
+
+import com.android.launcher3.compat.UserHandleCompat;
 
 /**
  * Represents a widget (either instantiated or about to be) in the Launcher.
@@ -59,11 +62,13 @@
         // to indicate that they should be calculated based on the layout and minWidth/minHeight
         spanX = -1;
         spanY = -1;
+        // We only support app widgets on current user.
+        user = UserHandleCompat.myUserHandle();
     }
 
     @Override
-    void onAddToDatabase(ContentValues values) {
-        super.onAddToDatabase(values);
+    void onAddToDatabase(Context context, ContentValues values) {
+        super.onAddToDatabase(context, values);
         values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
         values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString());
     }
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index cab55c7..5663314 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -28,6 +28,8 @@
 import com.android.launcher3.backup.BackupProtos.Resource;
 import com.android.launcher3.backup.BackupProtos.Screen;
 import com.android.launcher3.backup.BackupProtos.Widget;
+import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.compat.UserHandleCompat;
 
 import android.app.backup.BackupDataInputStream;
 import android.app.backup.BackupDataOutput;
@@ -108,6 +110,7 @@
             Favorites.SPANX,                   // 14
             Favorites.SPANY,                   // 15
             Favorites.TITLE,                   // 16
+            Favorites.PROFILE_ID,              // 17
     };
 
     private static final int ID_INDEX = 0;
@@ -127,6 +130,7 @@
     private static final int SPANX_INDEX = 14;
     private static final int SPANY_INDEX = 15;
     private static final int TITLE_INDEX = 16;
+    private static final int PROFILE_ID_INDEX = 17;
 
     private static final String[] SCREEN_PROJECTION = {
             WorkspaceScreens._ID,              // 0
@@ -295,6 +299,11 @@
         Set<String> savedIds = getSavedIdsByType(Key.FAVORITE, in);
         if (DEBUG) Log.d(TAG, "favorite savedIds.size()=" + savedIds.size());
 
+        // Don't backup apps in other profiles for now.
+        UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
+        long userSerialNumber =
+                UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
+
         // persist things that have changed since the last backup
         ContentResolver cr = mContext.getContentResolver();
         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
@@ -304,16 +313,22 @@
             cursor.moveToPosition(-1);
             while(cursor.moveToNext()) {
                 final long id = cursor.getLong(ID_INDEX);
-                final long updateTime = cursor.getLong(ID_MODIFIED);
-                Key key = getKey(Key.FAVORITE, id);
-                keys.add(key);
-                final String backupKey = keyToBackupKey(key);
-                currentIds.add(backupKey);
-                if (!savedIds.contains(backupKey) || updateTime >= in.t) {
-                    byte[] blob = packFavorite(cursor);
-                    writeRowToBackup(key, blob, out, data);
+                final long profileId = cursor.getLong(PROFILE_ID_INDEX);
+                if (userSerialNumber == profileId) {
+                    final long updateTime = cursor.getLong(ID_MODIFIED);
+                    Key key = getKey(Key.FAVORITE, id);
+                    keys.add(key);
+                    final String backupKey = keyToBackupKey(key);
+                    currentIds.add(backupKey);
+                    if (!savedIds.contains(backupKey) || updateTime >= in.t) {
+                        byte[] blob = packFavorite(cursor);
+                        writeRowToBackup(key, blob, out, data);
+                    } else {
+                        if (VERBOSE) Log.v(TAG, "favorite " + id + " was too old: " + updateTime);
+                    }
                 } else {
-                    if (VERBOSE) Log.v(TAG, "favorite " + id + " was too old: " + updateTime);
+                    if (VERBOSE) Log.v(TAG, "favorite " + id + " is for other profile: "
+                            + profileId);
                 }
             }
         } finally {
@@ -459,10 +474,15 @@
         Set<String> savedIds = getSavedIdsByType(Key.ICON, in);
         if (DEBUG) Log.d(TAG, "icon savedIds.size()=" + savedIds.size());
 
+        // Don't backup apps in other profiles for now.
+        UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
+        long userSerialNumber =
+                UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
         int startRows = out.rows;
         if (DEBUG) Log.d(TAG, "starting here: " + startRows);
-        String where = Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
-                Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT;
+        String where = "(" + Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_APPLICATION + " OR " +
+                Favorites.ITEM_TYPE + "=" + Favorites.ITEM_TYPE_SHORTCUT + ") AND " +
+                Favorites.PROFILE_ID + "=" + userSerialNumber;
         Cursor cursor = cr.query(Favorites.CONTENT_URI, FAVORITE_PROJECTION,
                 where, null, null);
         Set<String> currentIds = new HashSet<String>(cursor.getCount());
@@ -492,9 +512,9 @@
                         if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
                         if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
                             if (VERBOSE) Log.v(TAG, "saving icon " + backupKey);
-                            Bitmap icon = mIconCache.getIcon(intent);
+                            Bitmap icon = mIconCache.getIcon(intent, myUserHandle);
                             keys.add(key);
-                            if (icon != null && !mIconCache.isDefaultIcon(icon)) {
+                            if (icon != null && !mIconCache.isDefaultIcon(icon, myUserHandle)) {
                                 byte[] blob = packIcon(dpi, icon);
                                 writeRowToBackup(key, blob, out, data);
                             }
@@ -557,6 +577,7 @@
                 }
                 return;
             } else {
+                if (VERBOSE) Log.v(TAG, "saving restored icon as: " + key.name);
                 IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name),
                         icon, res.dpi);
             }
@@ -799,9 +820,15 @@
         if (!TextUtils.isEmpty(title)) {
             favorite.title = title;
         }
-        String intent = c.getString(INTENT_INDEX);
-        if (!TextUtils.isEmpty(intent)) {
-            favorite.intent = intent;
+        String intentDescription = c.getString(INTENT_INDEX);
+        if (!TextUtils.isEmpty(intentDescription)) {
+            try {
+                Intent intent = Intent.parseUri(intentDescription, 0);
+                intent.removeExtra(ItemInfo.EXTRA_PROFILE);
+                favorite.intent = intent.toUri(0);
+            } catch (URISyntaxException e) {
+                Log.e(TAG, "Invalid intent", e);
+           }
         }
         favorite.itemType = c.getInt(ITEM_TYPE_INDEX);
         if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
@@ -854,6 +881,11 @@
             values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId);
         }
 
+        UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle();
+        long userSerialNumber =
+                UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle);
+        values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
+
         // Let LauncherModel know we've been here.
         values.put(LauncherSettings.Favorites.RESTORED, 1);
 
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index 97138ee..1176aa5 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -264,21 +264,13 @@
                 WORKSPACE_CLING_DISMISSED_KEY, false)) {
             Cling c = initCling(R.id.workspace_cling, 0, false, true);
             c.updateWorkspaceBubblePosition();
-
-            try {
-                // We only enable the focused hotseat app if we are preinstalled
-                PackageManager pm = mLauncher.getPackageManager();
-                ApplicationInfo ai = pm.getApplicationInfo(mLauncher.getPackageName(), 0);
-                if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
-                    // Set the focused hotseat app
-                    c.setFocusedHotseatApp(mLauncher.getFirstRunFocusedHotseatAppDrawableId(),
-                        mLauncher.getFirstRunFocusedHotseatAppRank(),
-                        mLauncher.getFirstRunFocusedHotseatAppComponentName(),
-                        mLauncher.getFirstRunFocusedHotseatAppBubbleTitle(),
-                        mLauncher.getFirstRunFocusedHotseatAppBubbleDescription());
-                }
-            } catch (PackageManager.NameNotFoundException e) {
-                e.printStackTrace();
+            if (mLauncher.shouldClingFocusHotseatApp()) {
+                // Set the focused hotseat app
+                c.setFocusedHotseatApp(mLauncher.getFirstRunFocusedHotseatAppDrawableId(),
+                    mLauncher.getFirstRunFocusedHotseatAppRank(),
+                    mLauncher.getFirstRunFocusedHotseatAppComponentName(),
+                    mLauncher.getFirstRunFocusedHotseatAppBubbleTitle(),
+                    mLauncher.getFirstRunFocusedHotseatAppBubbleDescription());
             }
         } else {
             removeCling(R.id.workspace_cling);
@@ -315,12 +307,6 @@
         editor.commit();
     }
 
-    public void markFolderClingDismissed() {
-        SharedPreferences.Editor editor = mLauncher.getSharedPrefs().edit();
-        editor.putBoolean(LauncherClings.FOLDER_CLING_DISMISSED_KEY, true);
-        editor.apply();
-    }
-
     /** Removes the cling outright from the DragLayer */
     private void removeCling(int id) {
         final View cling = mLauncher.findViewById(id);
@@ -415,6 +401,15 @@
         mLauncher.getSearchBar().showSearchBar(true);
     }
 
+    public void markFolderClingDismissedIfNecessary() {
+        SharedPreferences prefs = mLauncher.getSharedPrefs();
+        if (!prefs.getBoolean(FOLDER_CLING_DISMISSED_KEY, false)) {
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.putBoolean(FOLDER_CLING_DISMISSED_KEY, true);
+            editor.apply();
+        }
+    }
+
     public void dismissMigrationClingCopyApps(View v) {
         // Copy the shortcuts from the old database
         LauncherModel model = mLauncher.getModel();
@@ -435,11 +430,8 @@
     }
 
     public void dismissMigrationClingUseDefault(View v) {
-        // Clear the workspace
-        LauncherModel model = mLauncher.getModel();
-        model.resetLoadedState(false, true);
-        model.startLoader(false, PagedView.INVALID_RESTORE_PAGE,
-                LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE);
+        // Don't need to do anything special here. We've already loaded the default workspace,
+        // (which is the default loader behavior triggered from Launcher#onCreate.).
 
         // Disable the migration cling
         dismissMigrationCling();
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index d8645aa..141368c 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -25,6 +25,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -44,6 +45,10 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.launcher3.compat.LauncherActivityInfoCompat;
+import com.android.launcher3.compat.LauncherAppsCompat;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
 
 import java.lang.ref.WeakReference;
@@ -67,14 +72,16 @@
  * LauncherModel object held in a static. Also provide APIs for updating the database state
  * for the Launcher.
  */
-public class LauncherModel extends BroadcastReceiver {
+public class LauncherModel extends BroadcastReceiver
+        implements LauncherAppsCompat.OnAppsChangedListenerCompat {
     static final boolean DEBUG_LOADERS = false;
+    private static final boolean DEBUG_RECEIVER = true;  // STOPSHIP(cwren) temporary for debugging
+
     static final String TAG = "Launcher.Model";
 
     // true = use a "More Apps" folder for non-workspace apps on upgrade
     // false = strew non-workspace apps across the workspace on upgrade
     public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false;
-
     public static final int LOADER_FLAG_NONE = 0;
     public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
     public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
@@ -97,6 +104,7 @@
     private static final int MAIN_THREAD_NORMAL_RUNNABLE = 0;
     private static final int MAIN_THREAD_BINDING_RUNNABLE = 1;
 
+    private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
 
     private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
     static {
@@ -152,10 +160,12 @@
     // </ only access in worker thread >
 
     private IconCache mIconCache;
-    private Bitmap mDefaultIcon;
 
     protected int mPreviousConfigMcc;
 
+    private final LauncherAppsCompat mLauncherApps;
+    private final UserManagerCompat mUserManager;
+
     public interface Callbacks {
         public boolean setLoadOnResume();
         public int getCurrentWorkspaceScreen();
@@ -175,7 +185,7 @@
         public void bindAppsUpdated(ArrayList<AppInfo> apps);
         public void updatePackageState(String pkgName, int state);
         public void bindComponentsRemoved(ArrayList<String> packageNames,
-                        ArrayList<AppInfo> appInfos);
+                        ArrayList<AppInfo> appInfos, UserHandleCompat user);
         public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
         public void bindSearchablesChanged();
         public boolean isAllAppsButtonRank(int rank);
@@ -189,15 +199,26 @@
 
     LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
         Context context = app.getContext();
-        ContentResolver contentResolver = context.getContentResolver();
 
         mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
-        ContentProviderClient client = contentResolver.acquireContentProviderClient(
-                Uri.parse(context.getString(R.string.old_launcher_provider_uri)));
-        mOldContentProviderExists = (client != null);
-        if (client != null) {
-            client.release();
+        String oldProvider = context.getString(R.string.old_launcher_provider_uri);
+        // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
+        // resource string.
+        String redirectAuthority = Uri.parse(oldProvider).getAuthority();
+        ProviderInfo providerInfo =
+                context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
+        ProviderInfo redirectProvider =
+                context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
+
+        Log.d(TAG, "Old launcher provider: " + oldProvider);
+        mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
+
+        if (mOldContentProviderExists) {
+            Log.d(TAG, "Old launcher provider exists.");
+        } else {
+            Log.d(TAG, "Old launcher provider does not exist.");
         }
+
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
         mIconCache = iconCache;
@@ -205,6 +226,8 @@
         final Resources res = context.getResources();
         Configuration config = res.getConfiguration();
         mPreviousConfigMcc = config.mcc;
+        mLauncherApps = LauncherAppsCompat.getInstance(context);
+        mUserManager = UserManagerCompat.getInstance(context);
     }
 
     /** Runs the specified runnable immediately if called from the main thread, otherwise it is
@@ -324,7 +347,7 @@
         Iterator<AppInfo> iter = allAppsApps.iterator();
         while (iter.hasNext()) {
             ItemInfo a = iter.next();
-            if (LauncherModel.appWasRestored(ctx, a.getIntent())) {
+            if (LauncherModel.appWasRestored(ctx, a.getIntent(), a.user)) {
                 restoredAppsFinal.add((AppInfo) a);
             }
         }
@@ -340,7 +363,8 @@
                                 for (AppInfo info : restoredAppsFinal) {
                                     final Intent intent = info.getIntent();
                                     if (intent != null) {
-                                        mIconCache.deletePreloadedIcon(intent.getComponent());
+                                        mIconCache.deletePreloadedIcon(intent.getComponent(),
+                                                info.user);
                                     }
                                 }
                                 callbacks.bindAppsUpdated(restoredAppsFinal);
@@ -392,7 +416,7 @@
                         if (LauncherModel.shortcutExists(context, name, launchIntent)) {
                             // Only InstallShortcutReceiver sends us shortcutInfos, ignore them
                             if (a instanceof AppInfo &&
-                                    LauncherModel.appWasRestored(context, launchIntent)) {
+                                    LauncherModel.appWasRestored(context, launchIntent, a.user)) {
                                 restoredAppsFinal.add((AppInfo) a);
                             }
                             continue;
@@ -482,15 +506,6 @@
         runOnWorkerThread(r);
     }
 
-    public Bitmap getFallbackIcon() {
-        if (mDefaultIcon == null) {
-            final Context context = LauncherAppState.getInstance().getContext();
-            mDefaultIcon = Utilities.createIconBitmap(
-                    mIconCache.getFullResDefaultActivityIcon(), context);
-        }
-        return Bitmap.createBitmap(mDefaultIcon);
-    }
-
     public void unbindItemInfosAndClearQueuedBindRunnables() {
         if (sWorkerThread.getThreadId() == Process.myTid()) {
             throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
@@ -498,7 +513,9 @@
         }
 
         // Clear any deferred bind runnables
-        mDeferredBindRunnables.clear();
+        synchronized (mDeferredBindRunnables) {
+            mDeferredBindRunnables.clear();
+        }
         // Remove any queued bind runnables
         mHandler.cancelAllRunnablesOfType(MAIN_THREAD_BINDING_RUNNABLE);
         // Unbind all the workspace items
@@ -814,7 +831,7 @@
      */
     static void updateItemInDatabase(Context context, final ItemInfo item) {
         final ContentValues values = new ContentValues();
-        item.onAddToDatabase(values);
+        item.onAddToDatabase(context, values);
         item.updateValuesWithCoordinates(values, item.cellX, item.cellY);
         updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
     }
@@ -839,18 +856,24 @@
 
     /**
      * Returns true if the shortcuts already exists in the database.
-     * we identify a shortcut by the component name of the intent.
+     * we identify a shortcut by the component name of the intent
+     * and the user.
      */
-    static boolean appWasRestored(Context context, Intent intent) {
+    static boolean appWasRestored(Context context, Intent intent, UserHandleCompat user) {
         final ContentResolver cr = context.getContentResolver();
         final ComponentName component = intent.getComponent();
         if (component == null) {
             return false;
         }
         String componentName = component.flattenToString();
-        final String where = "intent glob \"*component=" + componentName + "*\" and restored = 1";
+        String shortName = component.flattenToShortString();
+        long serialNumber = UserManagerCompat.getInstance(context)
+                .getSerialNumberForUser(user);
+        final String where = "(intent glob \"*component=" + componentName + "*\" or " +
+                "intent glob \"*component=" + shortName + "*\")" +
+                "and restored = 1 and profileId = " + serialNumber;
         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
-                new String[]{"intent", "restored"}, where, null, null);
+                new String[]{"intent", "restored", "profileId"}, where, null, null);
         boolean result = false;
         try {
             result = c.moveToFirst();
@@ -870,8 +893,10 @@
         final ContentResolver cr = context.getContentResolver();
         Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, new String[] {
                 LauncherSettings.Favorites.ITEM_TYPE, LauncherSettings.Favorites.CONTAINER,
-                LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
-                LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY }, null, null, null);
+                LauncherSettings.Favorites.SCREEN,
+                LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
+                LauncherSettings.Favorites.SPANX, LauncherSettings.Favorites.SPANY,
+                LauncherSettings.Favorites.PROFILE_ID }, null, null, null);
 
         final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
         final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
@@ -880,7 +905,8 @@
         final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
         final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
         final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
-
+        final int profileIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE_ID);
+        UserManagerCompat userManager = UserManagerCompat.getInstance(context);
         try {
             while (c.moveToNext()) {
                 ItemInfo item = new ItemInfo();
@@ -891,8 +917,12 @@
                 item.container = c.getInt(containerIndex);
                 item.itemType = c.getInt(itemTypeIndex);
                 item.screenId = c.getInt(screenIndex);
-
-                items.add(item);
+                long serialNumber = c.getInt(profileIdIndex);
+                item.user = userManager.getUserForSerialNumber(serialNumber);
+                // Skip if user has been deleted.
+                if (item.user != null) {
+                    items.add(item);
+                }
             }
         } catch (Exception e) {
             items.clear();
@@ -965,7 +995,7 @@
 
         final ContentValues values = new ContentValues();
         final ContentResolver cr = context.getContentResolver();
-        item.onAddToDatabase(values);
+        item.onAddToDatabase(context, values);
 
         item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
         values.put(LauncherSettings.Favorites._ID, item.id);
@@ -1155,74 +1185,67 @@
         }
     }
 
+    @Override
+    public void onPackageChanged(UserHandleCompat user, String packageName) {
+        int op = PackageUpdatedTask.OP_UPDATE;
+        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+                user));
+    }
+
+    @Override
+    public void onPackageRemoved(UserHandleCompat user, String packageName) {
+        int op = PackageUpdatedTask.OP_REMOVE;
+        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+                user));
+    }
+
+    @Override
+    public void onPackageAdded(UserHandleCompat user, String packageName) {
+        int op = PackageUpdatedTask.OP_ADD;
+        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
+                user));
+    }
+
+    @Override
+    public void onPackagesAvailable(UserHandleCompat user, String[] packageNames,
+            boolean replacing) {
+        if (!replacing) {
+            enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
+                    user));
+            if (mAppsCanBeOnRemoveableStorage) {
+                // Only rebind if we support removable storage. It catches the
+                // case where
+                // apps on the external sd card need to be reloaded
+                startLoaderFromBackground();
+            }
+        } else {
+            // If we are replacing then just update the packages in the list
+            enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
+                    packageNames, user));
+        }
+    }
+
+    @Override
+    public void onPackagesUnavailable(UserHandleCompat user, String[] packageNames,
+            boolean replacing) {
+        if (!replacing) {
+            enqueuePackageUpdated(new PackageUpdatedTask(
+                    PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
+                    user));
+        }
+
+    }
+
     /**
      * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
      * ACTION_PACKAGE_CHANGED.
      */
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (DEBUG_LOADERS) Log.d(TAG, "onReceive intent=" + intent);
+        if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
 
         final String action = intent.getAction();
-
-        if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
-                || Intent.ACTION_PACKAGE_REMOVED.equals(action)
-                || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-            final String packageName = intent.getData().getSchemeSpecificPart();
-            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-
-            int op = PackageUpdatedTask.OP_NONE;
-
-            if (packageName == null || packageName.length() == 0) {
-                // they sent us a bad intent
-                return;
-            }
-
-            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-                op = PackageUpdatedTask.OP_UPDATE;
-            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
-                if (!replacing) {
-                    op = PackageUpdatedTask.OP_REMOVE;
-                }
-                // else, we are replacing the package, so a PACKAGE_ADDED will be sent
-                // later, we will update the package at this time
-            } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
-                if (!replacing) {
-                    op = PackageUpdatedTask.OP_ADD;
-                } else {
-                    op = PackageUpdatedTask.OP_UPDATE;
-                }
-            }
-
-            if (op != PackageUpdatedTask.OP_NONE) {
-                enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName }));
-            }
-
-        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
-            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-            String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            if (!replacing) {
-                enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages));
-                if (mAppsCanBeOnRemoveableStorage) {
-                    // Only rebind if we support removable storage.  It catches the case where
-                    // apps on the external sd card need to be reloaded
-                    startLoaderFromBackground();
-                }
-            } else {
-                // If we are replacing then just update the packages in the list
-                enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
-                        packages));
-            }
-        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
-            final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
-            if (!replacing) {
-                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                enqueuePackageUpdated(new PackageUpdatedTask(
-                            PackageUpdatedTask.OP_UNAVAILABLE, packages));
-            }
-            // else, we are replacing the packages, so ignore this event and wait for
-            // EXTERNAL_APPLICATIONS_AVAILABLE to update the packages at that time
-        } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+        if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
             // If we have changed locale we need to clear out the labels in all apps/workspace.
             forceReload();
         } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
@@ -1248,7 +1271,7 @@
         }
     }
 
-    private void forceReload() {
+    void forceReload() {
         resetLoadedState(true, true);
 
         // Do this here because if the launcher activity is running it will be restarted.
@@ -1315,7 +1338,9 @@
 
             // Clear any deferred bind-runnables from the synchronized load process
             // We must do this before any loading/binding is scheduled below.
-            mDeferredBindRunnables.clear();
+            synchronized (mDeferredBindRunnables) {
+                mDeferredBindRunnables.clear();
+            }
 
             // Don't bother to start the thread if we know it's not going to do anything
             if (mCallbacks != null && mCallbacks.get() != null) {
@@ -1337,10 +1362,15 @@
     void bindRemainingSynchronousPages() {
         // Post the remaining side pages to be loaded
         if (!mDeferredBindRunnables.isEmpty()) {
-            for (final Runnable r : mDeferredBindRunnables) {
+            Runnable[] deferredBindRunnables = null;
+            synchronized (mDeferredBindRunnables) {
+                deferredBindRunnables = mDeferredBindRunnables.toArray(
+                        new Runnable[mDeferredBindRunnables.size()]);
+                mDeferredBindRunnables.clear();
+            }
+            for (final Runnable r : deferredBindRunnables) {
                 mHandler.post(r, MAIN_THREAD_BINDING_RUNNABLE);
             }
-            mDeferredBindRunnables.clear();
         }
     }
 
@@ -1649,7 +1679,7 @@
             ArrayList<ItemInfo> added = new ArrayList<ItemInfo>();
             synchronized (sBgLock) {
                 for (AppInfo app : mBgAllAppsList.data) {
-                    tmpInfos = getItemInfoForComponentName(app.componentName);
+                    tmpInfos = getItemInfoForComponentName(app.componentName, app.user);
                     if (tmpInfos.isEmpty()) {
                         // We are missing an application icon, so add this to the workspace
                         added.add(app);
@@ -1800,9 +1830,8 @@
                 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
             }
 
-            // Check if we need to do any upgrade-path logic
-            // (Includes having just imported default favorites)
-            boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb();
+            // This code path is for our old migration code and should no longer be exercised
+            boolean loadedOldDb = false;
 
             // Log to disk
             Launcher.addDumpLog(TAG, "11683562 -   loadedOldDb: " + loadedOldDb, true);
@@ -1854,6 +1883,8 @@
                             LauncherSettings.Favorites.SPANY);
                     final int restoredIndex = c.getColumnIndexOrThrow(
                             LauncherSettings.Favorites.RESTORED);
+                    final int profileIdIndex = c.getColumnIndexOrThrow(
+                            LauncherSettings.Favorites.PROFILE_ID);
                     //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
                     //final int displayModeIndex = c.getColumnIndexOrThrow(
                     //        LauncherSettings.Favorites.DISPLAY_MODE);
@@ -1864,6 +1895,7 @@
                     int container;
                     long id;
                     Intent intent;
+                    UserHandleCompat user;
 
                     while (!mStopped && c.moveToNext()) {
                         AtomicBoolean deleteOnInvalidPlacement = new AtomicBoolean(false);
@@ -1876,10 +1908,17 @@
                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                                 id = c.getLong(idIndex);
                                 intentDescription = c.getString(intentIndex);
+                                long serialNumber = c.getInt(profileIdIndex);
+                                user = mUserManager.getUserForSerialNumber(serialNumber);
+                                if (user == null) {
+                                    // User has been deleted remove the item.
+                                    itemsToRemove.add(id);
+                                    continue;
+                                }
                                 try {
                                     intent = Intent.parseUri(intentDescription, 0);
                                     ComponentName cn = intent.getComponent();
-                                    if (cn != null && !isValidPackageComponent(manager, cn)) {
+                                    if (cn != null && !isValidPackageActivity(context, cn, user)) {
                                         if (restored) {
                                             // might be installed later
                                             Launcher.addDumpLog(TAG,
@@ -1911,14 +1950,20 @@
                                 }
 
                                 if (restored) {
-                                    Launcher.addDumpLog(TAG,
-                                            "constructing info for partially restored package",
-                                            true);
-                                    info = getRestoredItemInfo(c, titleIndex, intent);
-                                    intent = getRestoredItemIntent(c, context, intent);
+                                    if (user.equals(UserHandleCompat.myUserHandle())) {
+                                        Launcher.addDumpLog(TAG,
+                                                "constructing info for partially restored package",
+                                                true);
+                                        info = getRestoredItemInfo(c, titleIndex, intent);
+                                        intent = getRestoredItemIntent(c, context, intent);
+                                    } else {
+                                        // Don't restore items for other profiles.
+                                        itemsToRemove.add(id);
+                                        continue;
+                                    }
                                 } else if (itemType ==
                                         LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                    info = getShortcutInfo(manager, intent, context, c, iconIndex,
+                                    info = getShortcutInfo(manager, intent, user, context, c, iconIndex,
                                             titleIndex, mLabelCache);
                                 } else {
                                     info = getShortcutInfo(c, context, iconTypeIndex,
@@ -1948,6 +1993,7 @@
                                     info.cellY = c.getInt(cellYIndex);
                                     info.spanX = 1;
                                     info.spanY = 1;
+                                    info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
 
                                     // check & update map of what's occupied
                                     deleteOnInvalidPlacement.set(false);
@@ -2367,7 +2413,9 @@
                     }
                 };
                 if (postOnMainThread) {
-                    deferredBindRunnables.add(r);
+                    synchronized (deferredBindRunnables) {
+                        deferredBindRunnables.add(r);
+                    }
                 } else {
                     runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
                 }
@@ -2384,7 +2432,9 @@
                     }
                 };
                 if (postOnMainThread) {
-                    deferredBindRunnables.add(r);
+                    synchronized (deferredBindRunnables) {
+                        deferredBindRunnables.add(r);
+                    }
                 } else {
                     runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
                 }
@@ -2506,7 +2556,9 @@
 
             // Load all the remaining pages (if we are loading synchronously, we want to defer this
             // work until after the first render)
-            mDeferredBindRunnables.clear();
+            synchronized (mDeferredBindRunnables) {
+                mDeferredBindRunnables.clear();
+            }
             bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
                     (isLoadingSynchronously ? mDeferredBindRunnables : null));
 
@@ -2528,7 +2580,9 @@
                 }
             };
             if (isLoadingSynchronously) {
-                mDeferredBindRunnables.add(r);
+                synchronized (mDeferredBindRunnables) {
+                    mDeferredBindRunnables.add(r);
+                }
             } else {
                 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
             }
@@ -2594,42 +2648,42 @@
                 return;
             }
 
-            final PackageManager packageManager = mContext.getPackageManager();
             final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
 
+            final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
+
             // Clear the list of apps
             mBgAllAppsList.clear();
+            for (UserHandleCompat user : profiles) {
+                // Query for the set of apps
+                final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+                List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
+                if (DEBUG_LOADERS) {
+                    Log.d(TAG, "getActivityList took "
+                            + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
+                    Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
+                }
+                // Fail if we don't have any apps
+                if (apps == null || apps.isEmpty()) {
+                    return;
+                }
+                // Sort the applications by name
+                final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
+                Collections.sort(apps,
+                        new LauncherModel.ShortcutNameComparator(mLabelCache));
+                if (DEBUG_LOADERS) {
+                    Log.d(TAG, "sort took "
+                            + (SystemClock.uptimeMillis()-sortTime) + "ms");
+                }
 
-            // Query for the set of apps
-            final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
-            List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "queryIntentActivities took "
-                        + (SystemClock.uptimeMillis()-qiaTime) + "ms");
-                Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps");
+                // Create the ApplicationInfos
+                for (int i = 0; i < apps.size(); i++) {
+                    LauncherActivityInfoCompat app = apps.get(i);
+                    // This builds the icon bitmaps.
+                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, mLabelCache));
+                }
             }
-            // Fail if we don't have any apps
-            if (apps == null || apps.isEmpty()) {
-                return;
-            }
-            // Sort the applications by name
-            final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
-            Collections.sort(apps,
-                    new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
-            if (DEBUG_LOADERS) {
-                Log.d(TAG, "sort took "
-                        + (SystemClock.uptimeMillis()-sortTime) + "ms");
-            }
-
-            // Create the ApplicationInfos
-            for (int i = 0; i < apps.size(); i++) {
-                ResolveInfo app = apps.get(i);
-                // This builds the icon bitmaps.
-                mBgAllAppsList.add(new AppInfo(packageManager, app,
-                        mIconCache, mLabelCache));
-            }
-
             // Huh? Shouldn't this be inside the Runnable below?
             final ArrayList<AppInfo> added = mBgAllAppsList.added;
             mBgAllAppsList.added = new ArrayList<AppInfo>();
@@ -2675,6 +2729,7 @@
     private class PackageUpdatedTask implements Runnable {
         int mOp;
         String[] mPackages;
+        UserHandleCompat mUser;
 
         public static final int OP_NONE = 0;
         public static final int OP_ADD = 1;
@@ -2683,9 +2738,10 @@
         public static final int OP_UNAVAILABLE = 4; // external media unmounted
 
 
-        public PackageUpdatedTask(int op, String[] packages) {
+        public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
             mOp = op;
             mPackages = packages;
+            mUser = user;
         }
 
         public void run() {
@@ -2697,14 +2753,14 @@
                 case OP_ADD:
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
-                        mIconCache.remove(packages[i]);
-                        mBgAllAppsList.addPackage(context, packages[i]);
+                        mIconCache.remove(packages[i], mUser);
+                        mBgAllAppsList.addPackage(context, packages[i], mUser);
                     }
                     break;
                 case OP_UPDATE:
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
-                        mBgAllAppsList.updatePackage(context, packages[i]);
+                        mBgAllAppsList.updatePackage(context, packages[i], mUser);
                         WidgetPreviewLoader.removePackageFromDb(
                                 mApp.getWidgetPreviewCacheDb(), packages[i]);
                     }
@@ -2713,7 +2769,7 @@
                 case OP_UNAVAILABLE:
                     for (int i=0; i<N; i++) {
                         if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
-                        mBgAllAppsList.removePackage(packages[i]);
+                        mBgAllAppsList.removePackage(packages[i], mUser);
                         WidgetPreviewLoader.removePackageFromDb(
                                 mApp.getWidgetPreviewCacheDb(), packages[i]);
                     }
@@ -2759,7 +2815,7 @@
                 // Update the launcher db to reflect the changes
                 for (AppInfo a : modifiedFinal) {
                     ArrayList<ItemInfo> infos =
-                            getItemInfoForComponentName(a.componentName);
+                            getItemInfoForComponentName(a.componentName, mUser);
                     for (ItemInfo i : infos) {
                         if (isShortcutInfoUpdateable(i)) {
                             ShortcutInfo info = (ShortcutInfo) i;
@@ -2788,21 +2844,21 @@
                 // Mark disabled packages in the broadcast to be removed
                 final PackageManager pm = context.getPackageManager();
                 for (int i=0; i<N; i++) {
-                    if (isPackageDisabled(pm, packages[i])) {
+                    if (isPackageDisabled(context, packages[i], mUser)) {
                         removedPackageNames.add(packages[i]);
                     }
                 }
             }
             // Remove all the components associated with this package
             for (String pn : removedPackageNames) {
-                ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn);
+                ArrayList<ItemInfo> infos = getItemInfoForPackageName(pn, mUser);
                 for (ItemInfo i : infos) {
                     deleteItemFromDatabase(context, i);
                 }
             }
             // Remove all the specific components
             for (AppInfo a : removedApps) {
-                ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName);
+                ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
                 for (ItemInfo i : infos) {
                     deleteItemFromDatabase(context, i);
                 }
@@ -2818,14 +2874,14 @@
                     public void run() {
                         Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
                         if (callbacks == cb && cb != null) {
-                            callbacks.bindComponentsRemoved(removedPackageNames, removedApps);
+                            callbacks.bindComponentsRemoved(removedPackageNames, removedApps, mUser);
                         }
                     }
                 });
             }
 
             final ArrayList<Object> widgetsAndShortcuts =
-                getSortedWidgetsAndShortcuts(context);
+                    getSortedWidgetsAndShortcuts(context);
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -2860,31 +2916,31 @@
         return widgetsAndShortcuts;
     }
 
-    private static boolean isPackageDisabled(PackageManager pm, String packageName) {
-        try {
-            PackageInfo pi = pm.getPackageInfo(packageName, 0);
-            return !pi.applicationInfo.enabled;
-        } catch (NameNotFoundException e) {
-            // Fall through
-        }
-        return false;
+    private static boolean isPackageDisabled(Context context, String packageName,
+            UserHandleCompat user) {
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        return !launcherApps.isPackageEnabledForProfile(packageName, user);
     }
 
-    public static boolean isValidPackageComponent(PackageManager pm, ComponentName cn) {
+    public static boolean isValidPackageActivity(Context context, ComponentName cn,
+            UserHandleCompat user) {
         if (cn == null) {
             return false;
         }
-        if (isPackageDisabled(pm, cn.getPackageName())) {
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
             return false;
         }
+        return launcherApps.isActivityEnabledForProfile(cn, user);
+    }
 
-        try {
-            // Check the activity
-            PackageInfo pi = pm.getPackageInfo(cn.getPackageName(), 0);
-            return (pm.getActivityInfo(cn, 0) != null);
-        } catch (NameNotFoundException e) {
+    public static boolean isValidPackage(Context context, String packageName,
+            UserHandleCompat user) {
+        if (packageName == null) {
             return false;
         }
+        final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
+        return launcherApps.isPackageEnabledForProfile(packageName, user);
     }
 
     /**
@@ -2898,7 +2954,8 @@
         } else {
             info.title = "";
         }
-        info.setIcon(mIconCache.getIcon(intent, info.title.toString()));
+        info.user = UserHandleCompat.myUserHandle();
+        info.setIcon(mIconCache.getIcon(intent, info.title.toString(), info.user));
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
         info.restoredIntent = intent;
         return info;
@@ -2926,8 +2983,9 @@
      * This is called from the code that adds shortcuts from the intent receiver.  This
      * doesn't have a Cursor, but
      */
-    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context) {
-        return getShortcutInfo(manager, intent, context, null, -1, -1, null);
+    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent,
+            UserHandleCompat user, Context context) {
+        return getShortcutInfo(manager, intent, user, context, null, -1, -1, null);
     }
 
     /**
@@ -2935,54 +2993,37 @@
      *
      * If c is not null, then it will be used to fill in missing data like the title and icon.
      */
-    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent, Context context,
-            Cursor c, int iconIndex, int titleIndex, HashMap<Object, CharSequence> labelCache) {
-        ComponentName componentName = intent.getComponent();
-        final ShortcutInfo info = new ShortcutInfo();
-        if (componentName != null && !isValidPackageComponent(manager, componentName)) {
-            Log.d(TAG, "Invalid package found in getShortcutInfo: " + componentName);
+    public ShortcutInfo getShortcutInfo(PackageManager manager, Intent intent,
+            UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
+            HashMap<Object, CharSequence> labelCache) {
+        if (user == null) {
+            Log.d(TAG, "Null user found in getShortcutInfo");
             return null;
-        } else {
-            try {
-                PackageInfo pi = manager.getPackageInfo(componentName.getPackageName(), 0);
-                info.initFlagsAndFirstInstallTime(pi);
-            } catch (NameNotFoundException e) {
-                Log.d(TAG, "getPackInfo failed for package " +
-                        componentName.getPackageName());
-            }
         }
 
-        // TODO: See if the PackageManager knows about this case.  If it doesn't
-        // then return null & delete this.
+        ComponentName componentName = intent.getComponent();
+        if (componentName == null) {
+            Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
+            return null;
+        }
+
+        Intent newIntent = new Intent(intent.getAction(), null);
+        newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        newIntent.setComponent(componentName);
+        LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
+        if (lai == null) {
+            Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
+            return null;
+        }
+
+        final ShortcutInfo info = new ShortcutInfo();
 
         // the resource -- This may implicitly give us back the fallback icon,
         // but don't worry about that.  All we're doing with usingFallbackIcon is
         // to avoid saving lots of copies of that in the database, and most apps
         // have icons anyway.
+        Bitmap icon = mIconCache.getIcon(componentName, lai, labelCache);
 
-        // Attempt to use queryIntentActivities to get the ResolveInfo (with IntentFilter info) and
-        // if that fails, or is ambiguious, fallback to the standard way of getting the resolve info
-        // via resolveActivity().
-        Bitmap icon = null;
-        ResolveInfo resolveInfo = null;
-        ComponentName oldComponent = intent.getComponent();
-        Intent newIntent = new Intent(intent.getAction(), null);
-        newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-        newIntent.setPackage(oldComponent.getPackageName());
-        List<ResolveInfo> infos = manager.queryIntentActivities(newIntent, 0);
-        for (ResolveInfo i : infos) {
-            ComponentName cn = new ComponentName(i.activityInfo.packageName,
-                    i.activityInfo.name);
-            if (cn.equals(oldComponent)) {
-                resolveInfo = i;
-            }
-        }
-        if (resolveInfo == null) {
-            resolveInfo = manager.resolveActivity(intent, 0);
-        }
-        if (resolveInfo != null) {
-            icon = mIconCache.getIcon(componentName, resolveInfo, labelCache);
-        }
         // the db
         if (icon == null) {
             if (c != null) {
@@ -2991,21 +3032,21 @@
         }
         // the fallback icon
         if (icon == null) {
-            icon = getFallbackIcon();
+            icon = mIconCache.getDefaultIcon(user);
             info.usingFallbackIcon = true;
         }
         info.setIcon(icon);
 
+        // From the cache.
+        if (labelCache != null) {
+            info.title = labelCache.get(componentName);
+        }
+
         // from the resource
-        if (resolveInfo != null) {
-            ComponentName key = LauncherModel.getComponentNameFromResolveInfo(resolveInfo);
-            if (labelCache != null && labelCache.containsKey(key)) {
-                info.title = labelCache.get(key);
-            } else {
-                info.title = resolveInfo.activityInfo.loadLabel(manager);
-                if (labelCache != null) {
-                    labelCache.put(key, info.title);
-                }
+        if (info.title == null && lai != null) {
+            info.title = lai.getLabel();
+            if (labelCache != null) {
+                labelCache.put(componentName, info.title);
             }
         }
         // from the db
@@ -3019,6 +3060,7 @@
             info.title = componentName.getClassName();
         }
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+        info.user = user;
         return info;
     }
 
@@ -3051,21 +3093,27 @@
         return new ArrayList<ItemInfo>(filtered);
     }
 
-    private ArrayList<ItemInfo> getItemInfoForPackageName(final String pn) {
+    private ArrayList<ItemInfo> getItemInfoForPackageName(final String pn,
+            final UserHandleCompat user) {
         ItemInfoFilter filter  = new ItemInfoFilter() {
             @Override
             public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
-                return cn.getPackageName().equals(pn);
+                return cn.getPackageName().equals(pn) && info.user.equals(user);
             }
         };
         return filterItemInfos(sBgItemsIdMap.values(), filter);
     }
 
-    private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname) {
+    private ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
+            final UserHandleCompat user) {
         ItemInfoFilter filter  = new ItemInfoFilter() {
             @Override
             public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
-                return cn.equals(cname);
+                if (info.user == null) {
+                    return cn.equals(cname);
+                } else {
+                    return cn.equals(cname) && info.user.equals(user);
+                }
             }
         };
         return filterItemInfos(sBgItemsIdMap.values(), filter);
@@ -3100,6 +3148,8 @@
 
         Bitmap icon = null;
         final ShortcutInfo info = new ShortcutInfo();
+        // Non-app shortcuts are only supported for current user.
+        info.user = UserHandleCompat.myUserHandle();
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
 
         // TODO: If there's an explicit component and we can't install that, delete it.
@@ -3130,14 +3180,14 @@
             }
             // the fallback icon
             if (icon == null) {
-                icon = getFallbackIcon();
+                icon = mIconCache.getDefaultIcon(info.user);
                 info.usingFallbackIcon = true;
             }
             break;
         case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
             icon = getIconFromCursor(c, iconIndex, context);
             if (icon == null) {
-                icon = getFallbackIcon();
+                icon = mIconCache.getDefaultIcon(info.user);
                 info.customIcon = false;
                 info.usingFallbackIcon = true;
             } else {
@@ -3145,7 +3195,7 @@
             }
             break;
         default:
-            icon = getFallbackIcon();
+            icon = mIconCache.getDefaultIcon(info.user);
             info.usingFallbackIcon = true;
             info.customIcon = false;
             break;
@@ -3262,7 +3312,8 @@
                             iconResource.packageName);
                     final int id = resources.getIdentifier(iconResource.resourceName, null, null);
                     icon = Utilities.createIconBitmap(
-                            mIconCache.getFullResIcon(resources, id), context);
+                            mIconCache.getFullResIcon(resources, id),
+                            context);
                 } catch (Exception e) {
                     Log.w(TAG, "Could not load shortcut icon: " + extra);
                 }
@@ -3271,11 +3322,14 @@
 
         final ShortcutInfo info = new ShortcutInfo();
 
+        // Only support intents for current user for now. Intents sent from other
+        // users wouldn't get here without intent forwarding anyway.
+        info.user = UserHandleCompat.myUserHandle();
         if (icon == null) {
             if (fallbackIcon != null) {
                 icon = fallbackIcon;
             } else {
-                icon = getFallbackIcon();
+                icon = mIconCache.getDefaultIcon(info.user);
                 info.usingFallbackIcon = true;
             }
         }
@@ -3347,12 +3401,18 @@
         final Collator collator = Collator.getInstance();
         return new Comparator<AppInfo>() {
             public final int compare(AppInfo a, AppInfo b) {
-                int result = collator.compare(a.title.toString().trim(),
-                        b.title.toString().trim());
-                if (result == 0) {
-                    result = a.componentName.compareTo(b.componentName);
+                if (a.user.equals(b.user)) {
+                    int result = collator.compare(a.title.toString().trim(),
+                            b.title.toString().trim());
+                    if (result == 0) {
+                        result = a.componentName.compareTo(b.componentName);
+                    }
+                    return result;
+                } else {
+                    // TODO Need to figure out rules for sorting
+                    // profiles, this puts work second.
+                    return a.user.toString().compareTo(b.user.toString());
                 }
-                return result;
             }
         };
     }
@@ -3379,35 +3439,32 @@
             return new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
         }
     }
-    public static class ShortcutNameComparator implements Comparator<ResolveInfo> {
+    public static class ShortcutNameComparator implements Comparator<LauncherActivityInfoCompat> {
         private Collator mCollator;
-        private PackageManager mPackageManager;
         private HashMap<Object, CharSequence> mLabelCache;
         ShortcutNameComparator(PackageManager pm) {
-            mPackageManager = pm;
             mLabelCache = new HashMap<Object, CharSequence>();
             mCollator = Collator.getInstance();
         }
-        ShortcutNameComparator(PackageManager pm, HashMap<Object, CharSequence> labelCache) {
-            mPackageManager = pm;
+        ShortcutNameComparator(HashMap<Object, CharSequence> labelCache) {
             mLabelCache = labelCache;
             mCollator = Collator.getInstance();
         }
-        public final int compare(ResolveInfo a, ResolveInfo b) {
+        public final int compare(LauncherActivityInfoCompat a, LauncherActivityInfoCompat b) {
             CharSequence labelA, labelB;
-            ComponentName keyA = LauncherModel.getComponentNameFromResolveInfo(a);
-            ComponentName keyB = LauncherModel.getComponentNameFromResolveInfo(b);
+            ComponentName keyA = a.getComponentName();
+            ComponentName keyB = b.getComponentName();
             if (mLabelCache.containsKey(keyA)) {
                 labelA = mLabelCache.get(keyA);
             } else {
-                labelA = a.loadLabel(mPackageManager).toString().trim();
+                labelA = a.getLabel().toString().trim();
 
                 mLabelCache.put(keyA, labelA);
             }
             if (mLabelCache.containsKey(keyB)) {
                 labelB = mLabelCache.get(keyB);
             } else {
-                labelB = b.loadLabel(mPackageManager).toString().trim();
+                labelB = b.getLabel().toString().trim();
 
                 mLabelCache.put(keyB, labelB);
             }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 9a004f2..bbc75b8 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -56,8 +56,10 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
-import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.compat.UserHandleCompat;
+import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.config.ProviderConfig;
+import com.android.launcher3.LauncherSettings.Favorites;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -66,6 +68,7 @@
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 
@@ -75,7 +78,7 @@
 
     private static final String DATABASE_NAME = "launcher.db";
 
-    private static final int DATABASE_VERSION = 18;
+    private static final int DATABASE_VERSION = 20;
 
     static final String OLD_AUTHORITY = "com.android.launcher2.settings";
     static final String AUTHORITY = ProviderConfig.AUTHORITY;
@@ -155,7 +158,7 @@
         if (values == null) {
             throw new RuntimeException("Error: attempting to insert null values");
         }
-        if (!values.containsKey(LauncherSettings.BaseLauncherColumns._ID)) {
+        if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) {
             throw new RuntimeException("Error: attempting to add item without specifying an id");
         }
         helper.checkId(table, values);
@@ -323,7 +326,6 @@
             }
 
             mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
-            mOpenHelper.setFlagJustLoadedOldDb();
             editor.commit();
         }
     }
@@ -334,10 +336,12 @@
     }
 
     private static int getDefaultWorkspaceResourceId() {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
         if (LauncherAppState.isDisableAllApps()) {
-            return R.xml.default_workspace_no_all_apps;
+            return grid.defaultNoAllAppsLayoutId;
         } else {
-            return R.xml.default_workspace;
+            return grid.defaultLayoutId;
         }
     }
 
@@ -429,6 +433,10 @@
             mMaxScreenId = 0;
             mNewDbCreated = true;
 
+            UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
+            long userSerialNumber = userManager.getSerialNumberForUser(
+                    UserHandleCompat.myUserHandle());
+
             db.execSQL("CREATE TABLE favorites (" +
                     "_id INTEGER PRIMARY KEY," +
                     "title TEXT," +
@@ -450,7 +458,8 @@
                     "displayMode INTEGER," +
                     "appWidgetProvider TEXT," +
                     "modified INTEGER NOT NULL DEFAULT 0," +
-                    "restored INTEGER NOT NULL DEFAULT 0" +
+                    "restored INTEGER NOT NULL DEFAULT 0," +
+                    "profileId INTEGER DEFAULT " + userSerialNumber +
                     ");");
             addWorkspacesTable(db);
 
@@ -503,10 +512,34 @@
         }
 
         private void removeOrphanedItems(SQLiteDatabase db) {
-            db.execSQL("DELETE FROM " + TABLE_FAVORITES + " WHERE " +
+            // Delete items directly on the workspace who's screen id doesn't exist
+            //  "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens)
+            //   AND container = -100"
+            String removeOrphanedDesktopItems = "DELETE FROM " + TABLE_FAVORITES +
+                    " WHERE " +
                     LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " +
-                    LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS +
-                    ")");
+                    LauncherSettings.WorkspaceScreens._ID + " FROM " + TABLE_WORKSPACE_SCREENS + ")" +
+                    " AND " +
+                    LauncherSettings.Favorites.CONTAINER + " = " +
+                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
+            db.execSQL(removeOrphanedDesktopItems);
+
+            // Delete items contained in folders which no longer exist (after above statement)
+            //  "DELETE FROM favorites  WHERE container <> -100 AND container <> -101 AND container
+            //   NOT IN (SELECT _id FROM favorites WHERE itemType = 2)"
+            String removeOrphanedFolderItems = "DELETE FROM " + TABLE_FAVORITES +
+                    " WHERE " +
+                    LauncherSettings.Favorites.CONTAINER + " <> " +
+                    LauncherSettings.Favorites.CONTAINER_DESKTOP +
+                    " AND "
+                    + LauncherSettings.Favorites.CONTAINER + " <> " +
+                    LauncherSettings.Favorites.CONTAINER_HOTSEAT +
+                    " AND "
+                    + LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " +
+                    LauncherSettings.Favorites._ID + " FROM " + TABLE_FAVORITES +
+                    " WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " +
+                    LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")";
+            db.execSQL(removeOrphanedFolderItems);
         }
 
         private void setFlagJustLoadedOldDb() {
@@ -810,14 +843,25 @@
             }
 
             if (version < 18) {
+                // No-op
+                version = 18;
+            }
+
+            if (version < 19) {
                 // Due to a data loss bug, some users may have items associated with screen ids
                 // which no longer exist. Since this can cause other problems, and since the user
                 // will never see these items anyway, we use database upgrade as an opportunity to
                 // clean things up.
+                removeOrphanedItems(db);
+                version = 19;
+            }
 
-                // TODO: this needs to be fixed, currently causes data loss.
-                //removeOrphanedItems(db);
-                version = 18;
+            if (version < 20) {
+                // Add userId column
+                if (addProfileColumn(db)) {
+                    version = 20;
+                }
+                // else old version remains, which means we wipe old data
             }
 
             if (version != DATABASE_VERSION) {
@@ -829,6 +873,40 @@
             }
         }
 
+        @Override
+        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            // This shouldn't happen -- throw our hands up in the air and start over.
+            Log.w(TAG, "Database version downgrade from: " + oldVersion + " to " + newVersion +
+                    ". Wiping databse.");
+
+            db.execSQL("DROP TABLE IF EXISTS " + TABLE_FAVORITES);
+            db.execSQL("DROP TABLE IF EXISTS " + TABLE_WORKSPACE_SCREENS);
+            onCreate(db);
+        }
+
+        private boolean addProfileColumn(SQLiteDatabase db) {
+            db.beginTransaction();
+            try {
+                UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
+                // Default to the serial number of this user, for older
+                // shortcuts.
+                long userSerialNumber = userManager.getSerialNumberForUser(
+                        UserHandleCompat.myUserHandle());
+                // Insert new column for holding user serial number
+                db.execSQL("ALTER TABLE favorites " +
+                        "ADD COLUMN profileId INTEGER DEFAULT "
+                                        + userSerialNumber + ";");
+                db.setTransactionSuccessful();
+            } catch (SQLException ex) {
+                // Old version remains, which means we wipe old data
+                Log.e(TAG, ex.getMessage(), ex);
+                return false;
+            } finally {
+                db.endTransaction();
+            }
+            return true;
+        }
+
         private boolean updateContactsShortcuts(SQLiteDatabase db) {
             final String selectWhere = buildOrWhereString(Favorites.ITEM_TYPE,
                     new int[] { Favorites.ITEM_TYPE_SHORTCUT });
@@ -1166,18 +1244,47 @@
             return intent;
         }
 
+        private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
+            ArrayList<Long> screenIds = new ArrayList<Long>();
+            int count = loadFavoritesRecursive(db, workspaceResourceId, screenIds);
+
+            // Add the screens specified by the items above
+            Collections.sort(screenIds);
+            int rank = 0;
+            ContentValues values = new ContentValues();
+            for (Long id : screenIds) {
+                values.clear();
+                values.put(LauncherSettings.WorkspaceScreens._ID, id);
+                values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
+                if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) < 0) {
+                    throw new RuntimeException("Failed initialize screen table"
+                            + "from default layout");
+                }
+                rank++;
+            }
+
+            // Ensure that the max ids are initialized
+            mMaxItemId = initializeMaxItemId(db);
+            mMaxScreenId = initializeMaxScreenId(db);
+            return count;
+        }
+
         /**
          * Loads the default set of favorite packages from an xml file.
          *
          * @param db The database to write the values into
          * @param filterContainerId The specific container id of items to load
+         * @param the set of screenIds which are used by the favorites
          */
-        private int loadFavorites(SQLiteDatabase db, int workspaceResourceId) {
-            ContentValues values = new ContentValues();
+        private int loadFavoritesRecursive(SQLiteDatabase db, int workspaceResourceId,
+                ArrayList<Long> screenIds) {
 
+
+
+            ContentValues values = new ContentValues();
             if (LOGD) Log.v(TAG, String.format("Loading favorites from resid=0x%08x", workspaceResourceId));
 
-            int i = 0;
+            int count = 0;
             try {
                 XmlResourceParser parser = mContext.getResources().getXml(workspaceResourceId);
                 AttributeSet attrs = Xml.asAttributeSet(parser);
@@ -1206,7 +1313,7 @@
 
                         if (resId != 0 && resId != workspaceResourceId) {
                             // recursively load some more favorites, why not?
-                            i += loadFavorites(db, resId);
+                            count += loadFavoritesRecursive(db, resId, screenIds);
                             added = false;
                         } else {
                             Log.w(TAG, String.format("Skipping <include workspace=0x%08x>", resId));
@@ -1302,7 +1409,15 @@
                             }
                         }
                     }
-                    if (added) i++;
+                    if (added) {
+                        long screenId = Long.parseLong(screen);
+                        // Keep track of the set of screens which need to be added to the db.
+                        if (!screenIds.contains(screenId) &&
+                                container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+                            screenIds.add(screenId);
+                        }
+                        count++;
+                    }
                     a.recycle();
                 }
             } catch (XmlPullParserException e) {
@@ -1312,13 +1427,7 @@
             } catch (RuntimeException e) {
                 Log.w(TAG, "Got exception parsing favorites.", e);
             }
-
-            // Update the max item id after we have loaded the database
-            if (mMaxItemId == -1) {
-                mMaxItemId = initializeMaxItemId(db);
-            }
-
-            return i;
+            return count;
         }
 
         /**
@@ -1759,6 +1868,8 @@
                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
                         final int displayModeIndex
                                 = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE);
+                        final int profileIndex
+                                = c.getColumnIndex(LauncherSettings.Favorites.PROFILE_ID);
 
                         int i = 0;
                         int curX = 0;
@@ -1790,6 +1901,19 @@
                             final int screen = c.getInt(screenIndex);
                             int container = c.getInt(containerIndex);
                             final String intentStr = c.getString(intentIndex);
+
+                            UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
+                            UserHandleCompat userHandle;
+                            final long userSerialNumber;
+                            if (profileIndex != -1 && !c.isNull(profileIndex)) {
+                                userSerialNumber = c.getInt(profileIndex);
+                                userHandle = userManager.getUserForSerialNumber(userSerialNumber);
+                            } else {
+                                // Default to the serial number of this user, for older
+                                // shortcuts.
+                                userHandle = UserHandleCompat.myUserHandle();
+                                userSerialNumber = userManager.getSerialNumberForUser(userHandle);
+                            }
                             Launcher.addDumpLog(TAG, "migrating \""
                                 + c.getString(titleIndex) + "\" ("
                                 + cellX + "," + cellY + "@"
@@ -1816,7 +1940,8 @@
                                     Launcher.addDumpLog(TAG, "skipping empty intent", true);
                                     continue;
                                 } else if (cn != null &&
-                                        !LauncherModel.isValidPackageComponent(pm, cn)) {
+                                        !LauncherModel.isValidPackageActivity(mContext, cn,
+                                                userHandle)) {
                                     // component no longer exists.
                                     Launcher.addDumpLog(TAG, "skipping item whose component " +
                                             "no longer exists.", true);
@@ -1855,6 +1980,7 @@
                             values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex));
                             values.put(LauncherSettings.Favorites.DISPLAY_MODE,
                                     c.getInt(displayModeIndex));
+                            values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber);
 
                             if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                                 hotseat.put(screen, values);
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 2a768a2..3553702 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -212,6 +212,14 @@
         static final String SPANY = "spanY";
 
         /**
+         * The profile id of the item in the cell.
+         * <P>
+         * Type: INTEGER
+         * </P>
+         */
+        static final String PROFILE_ID = "profileId";
+
+        /**
          * The favorite is a user created folder
          */
         static final int ITEM_TYPE_FOLDER = 2;
diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java
new file mode 100644
index 0000000..866b17c
--- /dev/null
+++ b/src/com/android/launcher3/MainThreadExecutor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 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.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An executor service that executes its tasks on the main thread.
+ *
+ * Shutting down this executor is not supported.
+ */
+public class MainThreadExecutor extends AbstractExecutorService {
+
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+
+    @Override
+    public void execute(Runnable runnable) {
+        if (Looper.getMainLooper() == Looper.myLooper()) {
+            runnable.run();
+        } else {
+            mHandler.post(runnable);
+        }
+    }
+
+    /**
+     * Not supported and throws an exception when used.
+     */
+    @Override
+    @Deprecated
+    public void shutdown() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Not supported and throws an exception when used.
+     */
+    @Override
+    @Deprecated
+    public List<Runnable> shutdownNow() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isShutdown() {
+        return false;
+    }
+
+    @Override
+    public boolean isTerminated() {
+        return false;
+    }
+
+    /**
+     * Not supported and throws an exception when used.
+     */
+    @Override
+    @Deprecated
+    public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 8d5d8dd..9763126 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -2482,7 +2482,7 @@
     public boolean startReordering(View v) {
         int dragViewIndex = indexOfChild(v);
 
-        if (mTouchState != TOUCH_STATE_REST) return false;
+        if (mTouchState != TOUCH_STATE_REST || dragViewIndex == -1) return false;
 
         mTempVisiblePagesRange[0] = 0;
         mTempVisiblePagesRange[1] = getPageCount() - 1;
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 5afa784..f40cf9f 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -26,7 +26,10 @@
 import android.graphics.Bitmap;
 import android.util.Log;
 
+import com.android.launcher3.compat.UserHandleCompat;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Represents a launchable icon on the workspaces and in folders.
@@ -110,12 +113,12 @@
         }
     }
 
-
-    ShortcutInfo(Intent intent, CharSequence title, Bitmap icon) {
+    ShortcutInfo(Intent intent, CharSequence title, Bitmap icon, UserHandleCompat user) {
         this();
         this.intent = intent;
         this.title = title;
         mIcon = icon;
+        this.user = user;
     }
 
     public ShortcutInfo(Context context, ShortcutInfo info) {
@@ -129,8 +132,9 @@
         }
         mIcon = info.mIcon; // TODO: should make a copy here.  maybe we don't need this ctor at all
         customIcon = info.customIcon;
-        initFlagsAndFirstInstallTime(
-                getPackageInfo(context, intent.getComponent().getPackageName()));
+        flags = info.flags;
+        firstInstallTime = info.firstInstallTime;
+        user = info.user;
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
@@ -143,22 +147,6 @@
         firstInstallTime = info.firstInstallTime;
     }
 
-    public static PackageInfo getPackageInfo(Context context, String packageName) {
-        PackageInfo pi = null;
-        try {
-            PackageManager pm = context.getPackageManager();
-            pi = pm.getPackageInfo(packageName, 0);
-        } catch (NameNotFoundException e) {
-            Log.d("ShortcutInfo", "PackageManager.getPackageInfo failed for " + packageName);
-        }
-        return pi;
-    }
-
-    void initFlagsAndFirstInstallTime(PackageInfo pi) {
-        flags = AppInfo.initFlags(pi);
-        firstInstallTime = AppInfo.initFirstInstallTime(pi);
-    }
-
     public void setIcon(Bitmap b) {
         mIcon = b;
     }
@@ -171,30 +159,13 @@
     }
 
     public void updateIcon(IconCache iconCache) {
-        mIcon = iconCache.getIcon(intent);
-        usingFallbackIcon = iconCache.isDefaultIcon(mIcon);
-    }
-
-    /**
-     * Creates the application intent based on a component name and various launch flags.
-     * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}.
-     *
-     * @param className the class name of the component representing the intent
-     * @param launchFlags the launch flags
-     */
-    final void setActivity(Context context, ComponentName className, int launchFlags) {
-        intent = new Intent(Intent.ACTION_MAIN);
-        intent.addCategory(Intent.CATEGORY_LAUNCHER);
-        intent.setComponent(className);
-        intent.setFlags(launchFlags);
-        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
-        initFlagsAndFirstInstallTime(
-                getPackageInfo(context, intent.getComponent().getPackageName()));
+        mIcon = iconCache.getIcon(intent, user);
+        usingFallbackIcon = iconCache.isDefaultIcon(mIcon, user);
     }
 
     @Override
-    void onAddToDatabase(ContentValues values) {
-        super.onAddToDatabase(values);
+    void onAddToDatabase(Context context, ContentValues values) {
+        super.onAddToDatabase(context, values);
 
         String titleStr = title != null ? title.toString() : null;
         values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
@@ -223,10 +194,10 @@
 
     @Override
     public String toString() {
-        return "ShortcutInfo(title=" + title.toString() + "intent=" + intent + "id=" + this.id
+        return "ShortcutInfo(title=" + title + "intent=" + intent + "id=" + this.id
                 + " type=" + this.itemType + " container=" + this.container + " screen=" + screenId
                 + " cellX=" + cellX + " cellY=" + cellY + " spanX=" + spanX + " spanY=" + spanY
-                + " dropPos=" + dropPos + ")";
+                + " dropPos=" + Arrays.toString(dropPos) + " user=" + user + ")";
     }
 
     public static void dumpShortcutInfoList(String tag, String label,
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index cbc9785..74b6e47 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -33,6 +33,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PaintDrawable;
+import android.os.Build;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.View;
@@ -99,6 +100,14 @@
     }
 
     /**
+     * Indicates if the device is running LMP or not.
+     * TODO(sansid): Change the check to a VERSION_CODES code check once we have a version for L.
+     */
+    public static boolean isLmp() {
+        return "L".equals(Build.VERSION.CODENAME);
+    }
+
+    /**
      * Returns a bitmap suitable for the all apps view. Used to convert pre-ICS
      * icon bitmaps that are stored in the database (which were 74x74 pixels at hdpi size)
      * to the proper size (48dp)
@@ -305,6 +314,17 @@
         return scale;
     }
 
+    /**
+     * Utility method to determine whether the given point, in local coordinates,
+     * is inside the view, where the area of the view is expanded by the slop factor.
+     * This method is called while processing touch-move events to determine if the event
+     * is still within the view.
+     */
+    public static boolean pointInView(View v, float localX, float localY, float slop) {
+        return localX >= -slop && localY >= -slop && localX < (v.getWidth() + slop) &&
+                localY < (v.getHeight() + slop);
+    }
+
     private static void initStatics(Context context) {
         final Resources resources = context.getResources();
         final DisplayMetrics metrics = resources.getDisplayMetrics();
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 36152f8..1b37700 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -9,6 +9,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.database.Cursor;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDiskIOException;
 import android.database.sqlite.SQLiteOpenHelper;
@@ -30,11 +31,16 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
 import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
 
 abstract class SoftReferenceThreadLocal<T> {
     private ThreadLocal<SoftReference<T>> mThreadLocal;
@@ -137,6 +143,8 @@
     private final ArrayList<SoftReference<Bitmap>> mUnusedBitmaps;
     private final static HashSet<String> sInvalidPackages;
 
+    private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
+
     static {
         sInvalidPackages = new HashSet<String>();
     }
@@ -358,6 +366,9 @@
             db.insert(CacheDb.TABLE_NAME, null, values);
         } catch (SQLiteDiskIOException e) {
             recreateDb();
+        } catch (SQLiteCantOpenDatabaseException e) {
+            dumpOpenFiles();
+            throw e;
         }
     }
 
@@ -367,6 +378,9 @@
         try {
             db.delete(CacheDb.TABLE_NAME, null, null);
         } catch (SQLiteDiskIOException e) {
+        } catch (SQLiteCantOpenDatabaseException e) {
+            dumpOpenFiles();
+            throw e;
         }
     }
 
@@ -387,6 +401,9 @@
                             } // args to SELECT query
                     );
                 } catch (SQLiteDiskIOException e) {
+                } catch (SQLiteCantOpenDatabaseException e) {
+                    dumpOpenFiles();
+                    throw e;
                 }
                 synchronized(sInvalidPackages) {
                     sInvalidPackages.remove(packageName);
@@ -405,6 +422,9 @@
                             CacheDb.COLUMN_NAME + " = ? ", // SELECT query
                             new String[] { objectName }); // args to SELECT query
                 } catch (SQLiteDiskIOException e) {
+                } catch (SQLiteCantOpenDatabaseException e) {
+                    dumpOpenFiles();
+                    throw e;
                 }
                 return null;
             }
@@ -430,6 +450,9 @@
         } catch (SQLiteDiskIOException e) {
             recreateDb();
             return null;
+        } catch (SQLiteCantOpenDatabaseException e) {
+            dumpOpenFiles();
+            throw e;
         }
         if (result.getCount() > 0) {
             result.moveToFirst();
@@ -493,7 +516,9 @@
         Drawable drawable = null;
         if (previewImage != 0) {
             drawable = mPackageManager.getDrawable(packageName, previewImage, null);
-            if (drawable == null) {
+            if (drawable != null) {
+                drawable = mutateOnMainThread(drawable);
+            } else {
                 Log.w(TAG, "Can't load widget preview drawable 0x" +
                         Integer.toHexString(previewImage) + " for provider: " + provider);
             }
@@ -511,6 +536,7 @@
             if (cellHSpan < 1) cellHSpan = 1;
             if (cellVSpan < 1) cellVSpan = 1;
 
+            // This Drawable is not directly drawn, so there's no need to mutate it.
             BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources()
                     .getDrawable(R.drawable.widget_tile);
             final int previewDrawableWidth = previewDrawable
@@ -547,9 +573,11 @@
                         (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2);
                 int yoffset =
                         (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2);
-                if (iconId > 0)
+                if (iconId > 0) {
                     icon = mIconCache.getFullResIcon(packageName, iconId);
+                }
                 if (icon != null) {
+                    icon = mutateOnMainThread(icon);
                     renderDrawableToBitmap(icon, defaultPreview, hoffset,
                             yoffset, (int) (mAppIconSize * iconScale),
                             (int) (mAppIconSize * iconScale));
@@ -617,7 +645,7 @@
             c.setBitmap(null);
         }
         // Render the icon
-        Drawable icon = mIconCache.getFullResIcon(info);
+        Drawable icon = mutateOnMainThread(mIconCache.getFullResIcon(info));
 
         int paddingTop = mContext.
                 getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
@@ -677,4 +705,98 @@
         }
     }
 
+    private Drawable mutateOnMainThread(final Drawable drawable) {
+        try {
+            return mMainThreadExecutor.submit(new Callable<Drawable>() {
+                @Override
+                public Drawable call() throws Exception {
+                    return drawable.mutate();
+                }
+            }).get();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new RuntimeException(e);
+        } catch (ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static final int MAX_OPEN_FILES = 1024;
+    private static final int SAMPLE_RATE = 23;
+    /**
+     * Dumps all files that are open in this process without allocating a file descriptor.
+     */
+    private static void dumpOpenFiles() {
+        try {
+            Log.i(TAG, "DUMP OF OPEN FILES (sample rate: 1 every " + SAMPLE_RATE + "):");
+            final String TYPE_APK = "apk";
+            final String TYPE_JAR = "jar";
+            final String TYPE_PIPE = "pipe";
+            final String TYPE_SOCKET = "socket";
+            final String TYPE_DB = "db";
+            final String TYPE_ANON_INODE = "anon_inode";
+            final String TYPE_DEV = "dev";
+            final String TYPE_NON_FS = "non-fs";
+            final String TYPE_OTHER = "other";
+            List<String> types = Arrays.asList(TYPE_APK, TYPE_JAR, TYPE_PIPE, TYPE_SOCKET, TYPE_DB,
+                    TYPE_ANON_INODE, TYPE_DEV, TYPE_NON_FS, TYPE_OTHER);
+            int[] count = new int[types.size()];
+            int[] duplicates = new int[types.size()];
+            HashSet<String> files = new HashSet<String>();
+            int total = 0;
+            for (int i = 0; i < MAX_OPEN_FILES; i++) {
+                // This is a gigantic hack but unfortunately the only way to resolve an fd
+                // to a file name. Note that we have to loop over all possible fds because
+                // reading the directory would require allocating a new fd. The kernel is
+                // currently implemented such that no fd is larger then the current rlimit,
+                // which is why it's safe to loop over them in such a way.
+                String fd = "/proc/self/fd/" + i;
+                try {
+                    // getCanonicalPath() uses readlink behind the scene which doesn't require
+                    // a file descriptor.
+                    String resolved = new File(fd).getCanonicalPath();
+                    int type = types.indexOf(TYPE_OTHER);
+                    if (resolved.startsWith("/dev/")) {
+                        type = types.indexOf(TYPE_DEV);
+                    } else if (resolved.endsWith(".apk")) {
+                        type = types.indexOf(TYPE_APK);
+                    } else if (resolved.endsWith(".jar")) {
+                        type = types.indexOf(TYPE_JAR);
+                    } else if (resolved.contains("/fd/pipe:")) {
+                        type = types.indexOf(TYPE_PIPE);
+                    } else if (resolved.contains("/fd/socket:")) {
+                        type = types.indexOf(TYPE_SOCKET);
+                    } else if (resolved.contains("/fd/anon_inode:")) {
+                        type = types.indexOf(TYPE_ANON_INODE);
+                    } else if (resolved.endsWith(".db") || resolved.contains("/databases/")) {
+                        type = types.indexOf(TYPE_DB);
+                    } else if (resolved.startsWith("/proc/") && resolved.contains("/fd/")) {
+                        // Those are the files that don't point anywhere on the file system.
+                        // getCanonicalPath() wrongly interprets these as relative symlinks and
+                        // resolves them within /proc/<pid>/fd/.
+                        type = types.indexOf(TYPE_NON_FS);
+                    }
+                    count[type]++;
+                    total++;
+                    if (files.contains(resolved)) {
+                        duplicates[type]++;
+                    }
+                    files.add(resolved);
+                    if (total % SAMPLE_RATE == 0) {
+                        Log.i(TAG, " fd " + i + ": " + resolved
+                                + " (" + types.get(type) + ")");
+                    }
+                } catch (IOException e) {
+                    // Ignoring exceptions for non-existing file descriptors.
+                }
+            }
+            for (int i = 0; i < types.size(); i++) {
+                Log.i(TAG, String.format("Open %10s files: %4d total, %4d duplicates",
+                        types.get(i), count[i], duplicates[i]));
+            }
+        } catch (Throwable t) {
+            // Catch everything. This is called from an exception handler that we shouldn't upset.
+            Log.e(TAG, "Unable to log open files.", t);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 9800cf3..0015418 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -60,6 +60,8 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.widget.TextView;
+
+import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.FolderIcon.FolderRingAnimator;
 import com.android.launcher3.Launcher.CustomContentCallbacks;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -126,13 +128,14 @@
     private static boolean sAccessibilityEnabled;
 
     // The screen id used for the empty screen always present to the right.
-    private final static long EXTRA_EMPTY_SCREEN_ID = -201;
+    final static long EXTRA_EMPTY_SCREEN_ID = -201;
     private final static long CUSTOM_CONTENT_SCREEN_ID = -301;
 
     private HashMap<Long, CellLayout> mWorkspaceScreens = new HashMap<Long, CellLayout>();
     private ArrayList<Long> mScreenOrder = new ArrayList<Long>();
 
     private Runnable mRemoveEmptyScreenRunnable;
+    private boolean mDeferRemoveExtraEmptyScreen = false;
 
     /**
      * CellInfo for the cell that is currently being dragged
@@ -395,13 +398,23 @@
             @Override
             public void run() {
                 if (mIsDragOccuring) {
+                    mDeferRemoveExtraEmptyScreen = false;
                     addExtraEmptyScreenOnDrag();
                 }
             }
         });
     }
 
+
+    public void deferRemoveExtraEmptyScreen() {
+        mDeferRemoveExtraEmptyScreen = true;
+    }
+
     public void onDragEnd() {
+        if (!mDeferRemoveExtraEmptyScreen) {
+            removeExtraEmptyScreen(true, mDragSourceInternal != null);
+        }
+
         mIsDragOccuring = false;
         updateChildrenLayersEnabled(false);
         mLauncher.unlockScreenOrientation(false);
@@ -724,11 +737,11 @@
         }
     }
 
-    public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete) {
-        removeExtraEmptyScreen(animate, onComplete, 0, false);
+    public void removeExtraEmptyScreen(final boolean animate, boolean stripEmptyScreens) {
+        removeExtraEmptyScreenDelayed(animate, null, 0, stripEmptyScreens);
     }
 
-    public void removeExtraEmptyScreen(final boolean animate, final Runnable onComplete,
+    public void removeExtraEmptyScreenDelayed(final boolean animate, final Runnable onComplete,
             final int delay, final boolean stripEmptyScreens) {
         // Log to disk
         Launcher.addDumpLog(TAG, "11683562 - removeExtraEmptyScreen()", true);
@@ -742,9 +755,8 @@
             postDelayed(new Runnable() {
                 @Override
                 public void run() {
-                    removeExtraEmptyScreen(animate, onComplete, 0, stripEmptyScreens);
+                    removeExtraEmptyScreenDelayed(animate, onComplete, 0, stripEmptyScreens);
                 }
-
             }, delay);
             return;
         }
@@ -1264,7 +1276,8 @@
                 SharedPreferences sp =
                         mLauncher.getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS);
                 LauncherWallpaperPickerActivity.suggestWallpaperDimension(mLauncher.getResources(),
-                        sp, mLauncher.getWindowManager(), mWallpaperManager);
+                        sp, mLauncher.getWindowManager(), mWallpaperManager,
+                        mLauncher.overrideWallpaperDimensions());
                 return null;
             }
         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
@@ -1282,6 +1295,10 @@
         snapToPage(whichPage, duration);
     }
 
+    public void snapToScreenId(long screenId) {
+        snapToScreenId(screenId, null);
+    }
+
     protected void snapToScreenId(long screenId, Runnable r) {
         snapToPage(getPageIndexForScreenId(screenId), r);
     }
@@ -1466,6 +1483,14 @@
         mWallpaperOffset.syncWithScroll();
     }
 
+    @Override
+    public void announceForAccessibility(CharSequence text) {
+        // Don't announce if apps is on top of us.
+        if (!mLauncher.isAllAppsVisible()) {
+            super.announceForAccessibility(text);
+        }
+    }
+
     void showOutlines() {
         if (!isSmall() && !mIsSwitchingState) {
             if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel();
@@ -2699,8 +2724,9 @@
             BubbleTextView icon = (BubbleTextView) child;
             icon.clearPressedOrFocusedBackground();
         } else if (child instanceof FolderIcon) {
-            // Dismiss the folder cling if we haven't already
-            mLauncher.getLauncherClings().markFolderClingDismissed();
+            // The folder cling isn't flexible enough to be shown in non-default workspace positions
+            // Also if they are dragging it a folder, we assume they don't need to see the cling.
+            mLauncher.markFolderClingDismissedIfNecessary();
         }
 
         if (child.getTag() == null || !(child.getTag() instanceof ItemInfo)) {
@@ -3046,13 +3072,15 @@
                 // cell also contains a shortcut, then create a folder with the two shortcuts.
                 if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
                         dropTargetLayout, mTargetCell, distance, false, d.dragView, null)) {
-                    removeExtraEmptyScreen(true, null, 0, true);
+                    // The folder cling isn't flexible enough to be shown in non-default workspace
+                    // positions. Also if they are creating a folder, we assume they don't need to
+                    // see the cling.
+                    mLauncher.markFolderClingDismissedIfNecessary();
                     return;
                 }
 
                 if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
                         distance, d, false)) {
-                    removeExtraEmptyScreen(true, null, 0, true);
                     return;
                 }
 
@@ -3158,7 +3186,6 @@
                     if (finalResizeRunnable != null) {
                         finalResizeRunnable.run();
                     }
-                    removeExtraEmptyScreen(true, null, 0, true);
                 }
             };
             mAnimatingViewIntoPlace = true;
@@ -3812,13 +3839,8 @@
         final Runnable exitSpringLoadedRunnable = new Runnable() {
             @Override
             public void run() {
-                removeExtraEmptyScreen(false, new Runnable() {
-                    @Override
-                    public void run() {
-                        mLauncher.exitSpringLoadedDragModeDelayed(true,
-                                Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
-                    }
-                });
+                mLauncher.exitSpringLoadedDragModeDelayed(true,
+                        Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT, null);
             }
         };
 
@@ -3880,6 +3902,11 @@
             Runnable onAnimationCompleteRunnable = new Runnable() {
                 @Override
                 public void run() {
+                    // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
+                    // adding an item that may not be dropped right away (due to a config activity)
+                    // we defer the removal until the activity returns.
+                    deferRemoveExtraEmptyScreen();
+
                     // When dragging and dropping from customization tray, we deal with creating
                     // widgets/shortcuts/folders in a slightly different way
                     switch (pendingInfo.itemType) {
@@ -3948,6 +3975,10 @@
                 d.postAnimationRunnable = exitSpringLoadedRunnable;
                 if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
                         true, d.dragView, d.postAnimationRunnable)) {
+                    // The folder cling isn't flexible enough to be shown in non-default workspace
+                    // positions. Also if they are creating a folder, we assume they don't need to
+                    // see the cling.
+                    mLauncher.markFolderClingDismissedIfNecessary();
                     return;
                 }
                 if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
@@ -4180,10 +4211,6 @@
                 if (mDragInfo.cell instanceof DropTarget) {
                     mDragController.removeDropTarget((DropTarget) mDragInfo.cell);
                 }
-                // If we move the item to anything not on the Workspace, check if any empty
-                // screens need to be removed. If we dropped back on the workspace, this will
-                // be done post drop animation.
-                removeExtraEmptyScreen(true, null, 0, true);
             }
         } else if (mDragInfo != null) {
             CellLayout cellLayout;
@@ -4613,7 +4640,7 @@
     // Removes ALL items that match a given package name, this is usually called when a package
     // has been removed and we want to remove all components (widgets, shortcuts, apps) that
     // belong to that package.
-    void removeItemsByPackageName(final ArrayList<String> packages) {
+    void removeItemsByPackageName(final ArrayList<String> packages, final UserHandleCompat user) {
         final HashSet<String> packageNames = new HashSet<String>();
         packageNames.addAll(packages);
 
@@ -4633,7 +4660,8 @@
             @Override
             public boolean filterItem(ItemInfo parent, ItemInfo info,
                                       ComponentName cn) {
-                if (packageNames.contains(cn.getPackageName())) {
+                if (packageNames.contains(cn.getPackageName())
+                        && info.user.equals(user)) {
                     cns.add(cn);
                     return true;
                 }
@@ -4643,13 +4671,13 @@
         LauncherModel.filterItemInfos(infos, filter);
 
         // Remove the affected components
-        removeItemsByComponentName(cns);
+        removeItemsByComponentName(cns, user);
     }
 
     // Removes items that match the application info specified, when applications are removed
     // as a part of an update, this is called to ensure that other widgets and application
     // shortcuts are not removed.
-    void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos) {
+    void removeItemsByApplicationInfo(final ArrayList<AppInfo> appInfos, UserHandleCompat user) {
         // Just create a hash table of all the specific components that this will affect
         HashSet<ComponentName> cns = new HashSet<ComponentName>();
         for (AppInfo info : appInfos) {
@@ -4657,10 +4685,11 @@
         }
 
         // Remove all the things
-        removeItemsByComponentName(cns);
+        removeItemsByComponentName(cns, user);
     }
 
-    void removeItemsByComponentName(final HashSet<ComponentName> componentNames) {
+    void removeItemsByComponentName(final HashSet<ComponentName> componentNames,
+            final UserHandleCompat user) {
         ArrayList<CellLayout> cellLayouts = getWorkspaceAndHotseatCellLayouts();
         for (final CellLayout layoutParent: cellLayouts) {
             final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
@@ -4679,7 +4708,7 @@
                 public boolean filterItem(ItemInfo parent, ItemInfo info,
                                           ComponentName cn) {
                     if (parent instanceof FolderInfo) {
-                        if (componentNames.contains(cn)) {
+                        if (componentNames.contains(cn) && info.user.equals(user)) {
                             FolderInfo folder = (FolderInfo) parent;
                             ArrayList<ShortcutInfo> appsToRemove;
                             if (folderAppsToRemove.containsKey(folder)) {
@@ -4692,7 +4721,7 @@
                             return true;
                         }
                     } else {
-                        if (componentNames.contains(cn)) {
+                        if (componentNames.contains(cn) && info.user.equals(user)) {
                             childrenToRemove.add(children.get(info));
                             return true;
                         }
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
new file mode 100644
index 0000000..3ba93ea
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.content.ComponentName;
+import android.graphics.drawable.Drawable;
+
+public abstract class LauncherActivityInfoCompat {
+
+    LauncherActivityInfoCompat() {
+    }
+
+    public abstract ComponentName getComponentName();
+    public abstract UserHandleCompat getUser();
+    public abstract CharSequence getLabel();
+    public abstract Drawable getIcon(int density);
+    public abstract int getApplicationFlags();
+    public abstract long getFirstInstallTime();
+    public abstract Drawable getBadgedIcon(int density);
+}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
new file mode 100644
index 0000000..052d434
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompatV16.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+
+
+public class LauncherActivityInfoCompatV16 extends LauncherActivityInfoCompat {
+    private ActivityInfo mActivityInfo;
+    private ComponentName mComponentName;
+    private PackageManager mPm;
+
+    LauncherActivityInfoCompatV16(Context context, ResolveInfo info) {
+        super();
+        this.mActivityInfo = info.activityInfo;
+        mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
+        mPm = context.getPackageManager();
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    public UserHandleCompat getUser() {
+        return UserHandleCompat.myUserHandle();
+    }
+
+    public CharSequence getLabel() {
+        return mActivityInfo.loadLabel(mPm);
+    }
+
+    public Drawable getIcon(int density) {
+        Drawable d = null;
+        if (mActivityInfo.getIconResource() != 0) {
+            Resources resources;
+            try {
+                resources = mPm.getResourcesForApplication(mActivityInfo.packageName);
+            } catch (PackageManager.NameNotFoundException e) {
+                resources = null;
+            }
+            if (resources != null) {
+                try {
+                    d = resources.getDrawableForDensity(mActivityInfo.getIconResource(), density);
+                } catch (Resources.NotFoundException e) {
+                    // Return default icon below.
+                }
+            }
+        }
+        if (d == null) {
+            Resources resources = Resources.getSystem();
+            d = resources.getDrawableForDensity(android.R.mipmap.sym_def_app_icon, density);
+        }
+        return d;
+    }
+
+    public int getApplicationFlags() {
+        return mActivityInfo.applicationInfo.flags;
+    }
+
+    public long getFirstInstallTime() {
+        try {
+            PackageInfo info = mPm.getPackageInfo(mActivityInfo.packageName, 0);
+            return info != null ? info.firstInstallTime : 0;
+        } catch (NameNotFoundException e) {
+            return 0;
+        }
+    }
+
+    public String getName() {
+        return mActivityInfo.name;
+    }
+
+    public Drawable getBadgedIcon(int density) {
+        return getIcon(density);
+    }
+}
diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
new file mode 100644
index 0000000..76125bd
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompatVL.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.content.ComponentName;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class LauncherActivityInfoCompatVL extends LauncherActivityInfoCompat {
+    private Object mLauncherActivityInfo;
+    private Class mLauncherActivityInfoClass;
+    private Method mGetComponentName;
+    private Method mGetUser;
+    private Method mGetLabel;
+    private Method mGetIcon;
+    private Method mGetApplicationFlags;
+    private Method mGetFirstInstallTime;
+    private Method mGetBadgedIcon;
+
+    LauncherActivityInfoCompatVL(Object launcherActivityInfo) {
+        super();
+        mLauncherActivityInfo = launcherActivityInfo;
+        mLauncherActivityInfoClass = ReflectUtils.getClassForName(
+                "android.content.pm.LauncherActivityInfo");
+        mGetComponentName = ReflectUtils.getMethod(mLauncherActivityInfoClass, "getComponentName");
+        mGetUser = ReflectUtils.getMethod(mLauncherActivityInfoClass, "getUser");
+        mGetLabel = ReflectUtils.getMethod(mLauncherActivityInfoClass, "getLabel");
+        mGetIcon = ReflectUtils.getMethod(mLauncherActivityInfoClass, "getIcon", int.class);
+        mGetApplicationFlags = ReflectUtils.getMethod(mLauncherActivityInfoClass,
+                "getApplicationFlags");
+        mGetFirstInstallTime = ReflectUtils.getMethod(mLauncherActivityInfoClass,
+                "getFirstInstallTime");
+        mGetBadgedIcon = ReflectUtils.getMethod(mLauncherActivityInfoClass, "getBadgedIcon",
+                int.class);
+    }
+
+    public ComponentName getComponentName() {
+        return (ComponentName) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetComponentName);
+    }
+
+    public UserHandleCompat getUser() {
+        return UserHandleCompat.fromUser((UserHandle) ReflectUtils.invokeMethod(
+                        mLauncherActivityInfo, mGetUser));
+    }
+
+    public CharSequence getLabel() {
+        return (CharSequence) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetLabel);
+    }
+
+    public Drawable getIcon(int density) {
+        return (Drawable) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetIcon, density);
+    }
+
+    public int getApplicationFlags() {
+        return (Integer) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetApplicationFlags);
+    }
+
+    public long getFirstInstallTime() {
+        return (Long) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetFirstInstallTime);
+    }
+
+    public Drawable getBadgedIcon(int density) {
+        return (Drawable) ReflectUtils.invokeMethod(mLauncherActivityInfo, mGetBadgedIcon, density);
+    }
+}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java
new file mode 100644
index 0000000..069e3de
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public abstract class LauncherAppsCompat {
+
+    public static final String ACTION_MANAGED_PROFILE_ADDED =
+            "android.intent.action.MANAGED_PROFILE_ADDED";
+    public static final String ACTION_MANAGED_PROFILE_REMOVED =
+            "android.intent.action.MANAGED_PROFILE_REMOVED";
+
+    public interface OnAppsChangedListenerCompat {
+        void onPackageRemoved(UserHandleCompat user, String packageName);
+        void onPackageAdded(UserHandleCompat user, String packageName);
+        void onPackageChanged(UserHandleCompat user, String packageName);
+        void onPackagesAvailable(UserHandleCompat user, String[] packageNames, boolean replacing);
+        void onPackagesUnavailable(UserHandleCompat user, String[] packageNames, boolean replacing);
+    }
+
+    protected LauncherAppsCompat() {
+    }
+
+    public static LauncherAppsCompat getInstance(Context context) {
+        // TODO change this to use api version once L gets an API number.
+        if ("L".equals(Build.VERSION.CODENAME)) {
+            Object launcherApps = context.getSystemService("launcherapps");
+            if (launcherApps != null) {
+                LauncherAppsCompatVL compat = LauncherAppsCompatVL.build(context, launcherApps);
+                if (compat != null) {
+                    return compat;
+                }
+            }
+        }
+        // Pre L or lunacher apps service not running, or reflection failed to find something.
+        return new LauncherAppsCompatV16(context);
+    }
+
+    public abstract List<LauncherActivityInfoCompat> getActivityList(String packageName,
+            UserHandleCompat user);
+    public abstract LauncherActivityInfoCompat resolveActivity(Intent intent,
+            UserHandleCompat user);
+    public abstract void startActivityForProfile(ComponentName component, Rect sourceBounds,
+            Bundle opts, UserHandleCompat user);
+    public abstract void addOnAppsChangedListener(OnAppsChangedListenerCompat listener);
+    public abstract void removeOnAppsChangedListener(OnAppsChangedListenerCompat listener);
+    public abstract boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user);
+    public abstract boolean isActivityEnabledForProfile(ComponentName component,
+            UserHandleCompat user);
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatV16.java b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
new file mode 100644
index 0000000..c739eb9
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatV16.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageInfo;
+import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class LauncherAppsCompatV16 extends LauncherAppsCompat {
+
+    private PackageManager mPm;
+    private Context mContext;
+    private List<OnAppsChangedListenerCompat> mListeners
+            = new ArrayList<OnAppsChangedListenerCompat>();
+    private PackageMonitor mPackageMonitor;
+
+    LauncherAppsCompatV16(Context context) {
+        mPm = context.getPackageManager();
+        mContext = context;
+        mPackageMonitor = new PackageMonitor();
+   }
+
+    public List<LauncherActivityInfoCompat> getActivityList(String packageName,
+            UserHandleCompat user) {
+        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
+        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        mainIntent.setPackage(packageName);
+        List<ResolveInfo> infos = mPm.queryIntentActivities(mainIntent, 0);
+        List<LauncherActivityInfoCompat> list =
+                new ArrayList<LauncherActivityInfoCompat>(infos.size());
+        for (ResolveInfo info : infos) {
+            list.add(new LauncherActivityInfoCompatV16(mContext, info));
+        }
+        return list;
+    }
+
+    public LauncherActivityInfoCompat resolveActivity(Intent intent, UserHandleCompat user) {
+        ResolveInfo info = mPm.resolveActivity(intent, 0);
+        if (info != null) {
+            return new LauncherActivityInfoCompatV16(mContext, info);
+        }
+        return null;
+    }
+
+    public void startActivityForProfile(ComponentName component, Rect sourceBounds,
+            Bundle opts, UserHandleCompat user) {
+        Intent launchIntent = new Intent(Intent.ACTION_MAIN);
+        launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        launchIntent.setComponent(component);
+        launchIntent.setSourceBounds(sourceBounds);
+        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(launchIntent, opts);
+    }
+
+    public synchronized void addOnAppsChangedListener(OnAppsChangedListenerCompat listener) {
+        if (listener != null && !mListeners.contains(listener)) {
+            mListeners.add(listener);
+            if (mListeners.size() == 1) {
+                registerForPackageIntents();
+            }
+        }
+    }
+
+    public synchronized void removeOnAppsChangedListener(OnAppsChangedListenerCompat listener) {
+        mListeners.remove(listener);
+        if (mListeners.size() == 0) {
+            unregisterForPackageIntents();
+        }
+    }
+
+    public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) {
+        try {
+            PackageInfo info = mPm.getPackageInfo(packageName, 0);
+            return info != null && info.applicationInfo.enabled;
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) {
+        try {
+            ActivityInfo info = mPm.getActivityInfo(component, 0);
+            return info != null && info.isEnabled();
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    private void unregisterForPackageIntents() {
+        mContext.unregisterReceiver(mPackageMonitor);
+    }
+
+    private void registerForPackageIntents() {
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(mPackageMonitor, filter);
+        filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+        mContext.registerReceiver(mPackageMonitor, filter);
+    }
+
+    private synchronized List<OnAppsChangedListenerCompat> getListeners() {
+        return new ArrayList<OnAppsChangedListenerCompat>(mListeners);
+    }
+
+    private class PackageMonitor extends BroadcastReceiver {
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            final UserHandleCompat user = UserHandleCompat.myUserHandle();
+
+            if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
+                    || Intent.ACTION_PACKAGE_REMOVED.equals(action)
+                    || Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                final String packageName = intent.getData().getSchemeSpecificPart();
+                final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+
+                if (packageName == null || packageName.length() == 0) {
+                    // they sent us a bad intent
+                    return;
+                }
+                if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+                    for (OnAppsChangedListenerCompat listener : getListeners()) {
+                        listener.onPackageChanged(user, packageName);
+                    }
+                } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                    if (!replacing) {
+                        for (OnAppsChangedListenerCompat listener : getListeners()) {
+                            listener.onPackageRemoved(user, packageName);
+                        }
+                    }
+                    // else, we are replacing the package, so a PACKAGE_ADDED will be sent
+                    // later, we will update the package at this time
+                } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+                    if (!replacing) {
+                        for (OnAppsChangedListenerCompat listener : getListeners()) {
+                            listener.onPackageAdded(user, packageName);
+                        }
+                    } else {
+                        for (OnAppsChangedListenerCompat listener : getListeners()) {
+                            listener.onPackageChanged(user, packageName);
+                        }
+                    }
+                }
+            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+                final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                for (OnAppsChangedListenerCompat listener : getListeners()) {
+                    listener.onPackagesAvailable(user, packages, replacing);
+                }
+            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+                final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                for (OnAppsChangedListenerCompat listener : getListeners()) {
+                    listener.onPackagesUnavailable(user, packages, replacing);
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
new file mode 100644
index 0000000..21f2659
--- /dev/null
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import java.lang.reflect.InvocationHandler;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Method;
+
+public class LauncherAppsCompatVL extends LauncherAppsCompat {
+
+    private Object mLauncherApps;
+    private Class mLauncherAppsClass;
+    private Class mListenerClass;
+    private Method mGetActivityList;
+    private Method mResolveActivity;
+    private Method mStartActivityForProfile;
+    private Method mAddOnAppsChangedListener;
+    private Method mRemoveOnAppsChangedListener;
+    private Method mIsPackageEnabledForProfile;
+    private Method mIsActivityEnabledForProfile;
+
+    private Map<OnAppsChangedListenerCompat, Object> mListeners
+            = new HashMap<OnAppsChangedListenerCompat, Object>();
+
+    static LauncherAppsCompatVL build(Context context, Object launcherApps) {
+        LauncherAppsCompatVL compat = new LauncherAppsCompatVL(context, launcherApps);
+
+        compat.mListenerClass = ReflectUtils.getClassForName(
+                "android.content.pm.LauncherApps$OnAppsChangedListener");
+        compat.mLauncherAppsClass = ReflectUtils.getClassForName("android.content.pm.LauncherApps");
+
+        compat.mGetActivityList = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+                "getActivityList",
+                String.class, UserHandle.class);
+        compat.mResolveActivity = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+                "resolveActivity",
+                Intent.class, UserHandle.class);
+        compat.mStartActivityForProfile = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+                "startActivityForProfile",
+                ComponentName.class, Rect.class, Bundle.class, UserHandle.class);
+        compat.mAddOnAppsChangedListener = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+                "addOnAppsChangedListener", compat.mListenerClass);
+        compat.mRemoveOnAppsChangedListener = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+                "removeOnAppsChangedListener", compat.mListenerClass);
+        compat.mIsPackageEnabledForProfile = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+                "isPackageEnabledForProfile", String.class, UserHandle.class);
+        compat.mIsActivityEnabledForProfile = ReflectUtils.getMethod(compat.mLauncherAppsClass,
+                "isActivityEnabledForProfile", ComponentName.class, UserHandle.class);
+
+        if (compat.mListenerClass != null
+                && compat.mLauncherAppsClass != null
+                && compat.mGetActivityList != null
+                && compat.mResolveActivity != null
+                && compat.mStartActivityForProfile != null
+                && compat.mAddOnAppsChangedListener != null
+                && compat.mRemoveOnAppsChangedListener != null
+                && compat.mIsPackageEnabledForProfile != null
+                && compat.mIsActivityEnabledForProfile != null) {
+            return compat;
+        }
+        return null;
+    }
+
+    private LauncherAppsCompatVL(Context context, Object launcherApps) {
+        super();
+        mLauncherApps = launcherApps;
+    }
+
+    public List<LauncherActivityInfoCompat> getActivityList(String packageName,
+            UserHandleCompat user) {
+        List<Object> list = (List<Object>) ReflectUtils.invokeMethod(mLauncherApps,
+                mGetActivityList, packageName, user.getUser());
+        if (list.size() == 0) {
+            return Collections.EMPTY_LIST;
+        }
+        ArrayList<LauncherActivityInfoCompat> compatList =
+                new ArrayList<LauncherActivityInfoCompat>(list.size());
+        for (Object info : list) {
+            compatList.add(new LauncherActivityInfoCompatVL(info));
+        }
+        return compatList;
+    }
+
+    public LauncherActivityInfoCompat resolveActivity(Intent intent, UserHandleCompat user) {
+        Object activity = ReflectUtils.invokeMethod(mLauncherApps, mResolveActivity,
+                        intent, user.getUser());
+        if (activity != null) {
+            return new LauncherActivityInfoCompatVL(activity);
+        } else {
+            return null;
+        }
+    }
+
+    public void startActivityForProfile(ComponentName component, Rect sourceBounds,
+            Bundle opts, UserHandleCompat user) {
+        ReflectUtils.invokeMethod(mLauncherApps, mStartActivityForProfile,
+                component, sourceBounds, opts, user.getUser());
+    }
+
+    public void addOnAppsChangedListener(LauncherAppsCompat.OnAppsChangedListenerCompat listener) {
+        Object wrappedListener = Proxy.newProxyInstance(mListenerClass.getClassLoader(),
+                new Class[]{mListenerClass}, new WrappedListener(listener));
+        synchronized (mListeners) {
+            mListeners.put(listener, wrappedListener);
+        }
+        ReflectUtils.invokeMethod(mLauncherApps, mAddOnAppsChangedListener, wrappedListener);
+    }
+
+    public void removeOnAppsChangedListener(
+            LauncherAppsCompat.OnAppsChangedListenerCompat listener) {
+        Object wrappedListener = null;
+        synchronized (mListeners) {
+            wrappedListener = mListeners.remove(listener);
+        }
+        if (wrappedListener != null) {
+            ReflectUtils.invokeMethod(mLauncherApps, mRemoveOnAppsChangedListener, wrappedListener);
+        }
+    }
+
+    public boolean isPackageEnabledForProfile(String packageName, UserHandleCompat user) {
+        return (Boolean) ReflectUtils.invokeMethod(mLauncherApps, mIsPackageEnabledForProfile,
+                packageName, user.getUser());
+    }
+
+    public boolean isActivityEnabledForProfile(ComponentName component, UserHandleCompat user) {
+        return (Boolean) ReflectUtils.invokeMethod(mLauncherApps, mIsActivityEnabledForProfile,
+                component, user.getUser());
+    }
+
+    private static class WrappedListener implements InvocationHandler {
+        private LauncherAppsCompat.OnAppsChangedListenerCompat mListener;
+
+        public WrappedListener(LauncherAppsCompat.OnAppsChangedListenerCompat listener) {
+            mListener = listener;
+        }
+
+        public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
+            try {
+                String methodName = m.getName();
+                if ("onPackageRemoved".equals(methodName)) {
+                    onPackageRemoved((UserHandle) args[0], (String) args[1]);
+                } else if ("onPackageAdded".equals(methodName)) {
+                    onPackageAdded((UserHandle) args[0], (String) args[1]);
+                } else if ("onPackageChanged".equals(methodName)) {
+                    onPackageChanged((UserHandle) args[0], (String) args[1]);
+                } else if ("onPackagesAvailable".equals(methodName)) {
+                    onPackagesAvailable((UserHandle) args[0], (String []) args[1],
+                            (Boolean) args[2]);
+                } else if ("onPackagesUnavailable".equals(methodName)) {
+                    onPackagesUnavailable((UserHandle) args[0], (String []) args[1],
+                            (Boolean) args[2]);
+                }
+            } finally {
+                return null;
+            }
+        }
+
+        public void onPackageRemoved(UserHandle user, String packageName) {
+            mListener.onPackageRemoved(UserHandleCompat.fromUser(user), packageName);
+        }
+
+        public void onPackageAdded(UserHandle user, String packageName) {
+            mListener.onPackageAdded(UserHandleCompat.fromUser(user), packageName);
+        }
+
+        public void onPackageChanged(UserHandle user, String packageName) {
+            mListener.onPackageChanged(UserHandleCompat.fromUser(user), packageName);
+        }
+
+        public void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing) {
+            mListener.onPackagesAvailable(UserHandleCompat.fromUser(user), packageNames, replacing);
+        }
+
+        public void onPackagesUnavailable(UserHandle user, String[] packageNames,
+                boolean replacing) {
+            mListener.onPackagesUnavailable(UserHandleCompat.fromUser(user), packageNames,
+                    replacing);
+        }
+    }
+}
+
diff --git a/src/com/android/launcher3/compat/ReflectUtils.java b/src/com/android/launcher3/compat/ReflectUtils.java
new file mode 100644
index 0000000..e1b8a1f
--- /dev/null
+++ b/src/com/android/launcher3/compat/ReflectUtils.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class ReflectUtils {
+    private static final String TAG = "LauncherReflect";
+
+    public static Class getClassForName(String className) {
+        try {
+            return Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            Log.e(TAG, "Couldn't find class " + className, e);
+            return null;
+        }
+    }
+
+    public static Method getMethod(Class clazz, String method) {
+        try {
+            return clazz.getMethod(method);
+        } catch (NoSuchMethodException e) {
+            Log.e(TAG, "Couldn't find methid " + clazz.getName() + " " + method, e);
+            return null;
+        }
+    }
+
+    public static Method getMethod(Class clazz, String method, Class param1) {
+        try {
+            return clazz.getMethod(method, param1);
+        } catch (NoSuchMethodException e) {
+            Log.e(TAG, "Couldn't find methid " + clazz.getName() + " " + method, e);
+            return null;
+        }
+    }
+
+    public static Method getMethod(Class clazz, String method, Class param1, Class param2) {
+        try {
+            return clazz.getMethod(method, param1, param2);
+        } catch (NoSuchMethodException e) {
+            Log.e(TAG, "Couldn't find methid " + clazz.getName() + " " + method, e);
+            return null;
+        }
+    }
+
+    public static Method getMethod(Class clazz, String method, Class param1, Class param2,
+            Class param3) {
+        try {
+            return clazz.getMethod(method, param1, param2, param3);
+        } catch (NoSuchMethodException e) {
+            Log.e(TAG, "Couldn't find methid " + clazz.getName() + " " + method, e);
+            return null;
+        }
+    }
+
+    public static Method getMethod(Class clazz, String method, Class param1, Class param2,
+            Class param3, Class param4) {
+        try {
+            return clazz.getMethod(method, param1, param2, param3, param4);
+        } catch (NoSuchMethodException e) {
+            Log.e(TAG, "Couldn't find methid " + clazz.getName() + " " + method, e);
+            return null;
+        }
+    }
+
+    public static Object invokeMethod(Object object, Method method) {
+        try {
+            return method.invoke(object);
+        } catch (SecurityException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        }
+        return null;
+    }
+
+    public static Object invokeMethod(Object object, Method method, Object param1) {
+        try {
+            return method.invoke(object, param1);
+        } catch (SecurityException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        }
+        return null;
+    }
+
+    public static Object invokeMethod(Object object, Method method, Object param1, Object param2) {
+        try {
+            return method.invoke(object, param1, param2);
+        } catch (SecurityException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        }
+        return null;
+    }
+
+    public static Object invokeMethod(Object object, Method method, Object param1, Object param2,
+            Object param3) {
+        try {
+            return method.invoke(object, param1, param2, param3);
+        } catch (SecurityException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        }
+        return null;
+    }
+
+    public static Object invokeMethod(Object object, Method method, Object param1, Object param2,
+            Object param3, Object param4) {
+        try {
+            return method.invoke(object, param1, param2, param3, param4);
+        } catch (SecurityException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Couldn't invoke method " + method, e);
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/compat/UserHandleCompat.java b/src/com/android/launcher3/compat/UserHandleCompat.java
new file mode 100644
index 0000000..8f5dda2
--- /dev/null
+++ b/src/com/android/launcher3/compat/UserHandleCompat.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.os.Build;
+import android.os.UserHandle;
+
+public class UserHandleCompat {
+    private UserHandle mUser;
+
+    private UserHandleCompat(UserHandle user) {
+        mUser = user;
+    }
+
+    private UserHandleCompat() {
+    }
+
+    public static UserHandleCompat myUserHandle() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return new UserHandleCompat(android.os.Process.myUserHandle());
+        } else {
+            return new UserHandleCompat();
+        }
+    }
+
+    static UserHandleCompat fromUser(UserHandle user) {
+        if (user == null) {
+            return null;
+        } else {
+            return new UserHandleCompat(user);
+        }
+    }
+
+    UserHandle getUser() {
+        return mUser;
+    }
+
+    @Override
+    public String toString() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return mUser.toString();
+        } else {
+            return "";
+        }
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof UserHandleCompat)) {
+            return false;
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return mUser.equals(((UserHandleCompat) other).mUser);
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return mUser.hashCode();
+        } else {
+            return 0;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/compat/UserManagerCompat.java b/src/com/android/launcher3/compat/UserManagerCompat.java
new file mode 100644
index 0000000..256e04a
--- /dev/null
+++ b/src/com/android/launcher3/compat/UserManagerCompat.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+import java.util.List;
+
+public abstract class UserManagerCompat {
+    protected UserManagerCompat() {
+    }
+
+    public static UserManagerCompat getInstance(Context context) {
+        // TODO change this to use api version once L gets an API number.
+        if ("L".equals(Build.VERSION.CODENAME)) {
+            return new UserManagerCompatVL(context);
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return new UserManagerCompatV17(context);
+        } else {
+            return new UserManagerCompatV16();
+        }
+    }
+
+    public abstract List<UserHandleCompat> getUserProfiles();
+    public abstract long getSerialNumberForUser(UserHandleCompat user);
+    public abstract UserHandleCompat getUserForSerialNumber(long serialNumber);
+    public abstract Drawable getBadgedDrawableForUser(Drawable unbadged, UserHandleCompat user);
+}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV16.java b/src/com/android/launcher3/compat/UserManagerCompatV16.java
new file mode 100644
index 0000000..2009e4e
--- /dev/null
+++ b/src/com/android/launcher3/compat/UserManagerCompatV16.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UserManagerCompatV16 extends UserManagerCompat {
+
+    UserManagerCompatV16() {
+    }
+
+    public List<UserHandleCompat> getUserProfiles() {
+        List<UserHandleCompat> profiles = new ArrayList<UserHandleCompat>(1);
+        profiles.add(UserHandleCompat.myUserHandle());
+        return profiles;
+    }
+
+    public UserHandleCompat getUserForSerialNumber(long serialNumber) {
+        return UserHandleCompat.myUserHandle();
+    }
+
+    public Drawable getBadgedDrawableForUser(Drawable unbadged,
+            UserHandleCompat user) {
+        return unbadged;
+    }
+
+    public long getSerialNumberForUser(UserHandleCompat user) {
+        return 0;
+    }
+}
diff --git a/src/com/android/launcher3/compat/UserManagerCompatV17.java b/src/com/android/launcher3/compat/UserManagerCompatV17.java
new file mode 100644
index 0000000..055359a
--- /dev/null
+++ b/src/com/android/launcher3/compat/UserManagerCompatV17.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UserManagerCompatV17 extends UserManagerCompatV16 {
+    protected UserManager mUserManager;
+
+    UserManagerCompatV17(Context context) {
+        mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+    }
+
+    public long getSerialNumberForUser(UserHandleCompat user) {
+        return mUserManager.getSerialNumberForUser(user.getUser());
+    }
+
+    public UserHandleCompat getUserForSerialNumber(long serialNumber) {
+        return UserHandleCompat.fromUser(mUserManager.getUserForSerialNumber(serialNumber));
+    }
+}
+
diff --git a/src/com/android/launcher3/compat/UserManagerCompatVL.java b/src/com/android/launcher3/compat/UserManagerCompatVL.java
new file mode 100644
index 0000000..8d3ca85
--- /dev/null
+++ b/src/com/android/launcher3/compat/UserManagerCompatVL.java
@@ -0,0 +1,68 @@
+
+/*
+ * Copyright (C) 2014 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.compat;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class UserManagerCompatVL extends UserManagerCompatV17 {
+
+    UserManagerCompatVL(Context context) {
+        super(context);
+    }
+
+    public List<UserHandleCompat> getUserProfiles() {
+        Method method = ReflectUtils.getMethod(mUserManager.getClass(), "getUserProfiles");
+        if (method != null) {
+            List<UserHandle> users = (List<UserHandle>) ReflectUtils.invokeMethod(
+                    mUserManager, method);
+            if (users != null) {
+                ArrayList<UserHandleCompat> compatUsers = new ArrayList<UserHandleCompat>(
+                        users.size());
+                for (UserHandle user : users) {
+                    compatUsers.add(UserHandleCompat.fromUser(user));
+                }
+                return compatUsers;
+            }
+        }
+        // Fall back to non L version.
+        return super.getUserProfiles();
+    }
+
+    public Drawable getBadgedDrawableForUser(Drawable unbadged, UserHandleCompat user) {
+        Method method = ReflectUtils.getMethod(mUserManager.getClass(), "getBadgedDrawableForUser",
+                Drawable.class, UserHandle.class);
+        if (method != null) {
+            Drawable d = (Drawable) ReflectUtils.invokeMethod(mUserManager, method, unbadged,
+                    user.getUser());
+            if (d != null) {
+                return d;
+            }
+        }
+        // Fall back to non L version.
+        return super.getBadgedDrawableForUser(unbadged, user);
+    }
+}
+