am 72281585: AI 149721: Import Portuguese translations.

Merge commit '72281585d136353ed3814a96448763a2a6f7cca4' into donut

* commit '72281585d136353ed3814a96448763a2a6f7cca4':
  AI 149721: Import Portuguese translations.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4b11343..a0d0e5c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -56,6 +56,7 @@
     <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.BIND_APPWIDGET" />
     <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
     <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
 
@@ -90,6 +91,10 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+
+        <activity
+            android:name="GesturesActivity"
+            android:label="@string/gestures_activity" />        
         
         <!-- Enable system-default search mode for any activity in Home -->
         <meta-data
diff --git a/res/anim/fade_in_fast.xml b/res/anim/fade_in_fast.xml
new file mode 100644
index 0000000..4fa9847
--- /dev/null
+++ b/res/anim/fade_in_fast.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:anim/accelerate_interpolator"
+
+    android:fromAlpha="0.0"
+    android:toAlpha="1.0"
+
+    android:duration="@android:integer/config_mediumAnimTime" />
diff --git a/res/anim/fade_out_fast.xml b/res/anim/fade_out_fast.xml
new file mode 100644
index 0000000..a061a6c
--- /dev/null
+++ b/res/anim/fade_out_fast.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:anim/accelerate_interpolator"
+
+    android:fromAlpha="1.0"
+    android:toAlpha="0.0"
+
+    android:duration="@android:integer/config_mediumAnimTime" />
diff --git a/res/drawable/btn_circle.xml b/res/drawable/btn_circle.xml
new file mode 100644
index 0000000..9208010
--- /dev/null
+++ b/res/drawable/btn_circle.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/btn_circle_normal" />
+    <item android:state_window_focused="false" android:state_enabled="false"
+        android:drawable="@drawable/btn_circle_disable" />
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/btn_circle_pressed" />
+    <item android:state_focused="true" android:state_enabled="true"
+        android:drawable="@drawable/btn_circle_selected" />
+    <item android:state_enabled="true"
+        android:drawable="@drawable/btn_circle_normal" />
+    <item android:state_focused="true"
+        android:drawable="@drawable/btn_circle_disable_focused" />
+    <item
+         android:drawable="@drawable/btn_circle_disable" />
+</selector>
diff --git a/res/drawable/btn_circle_disable.png b/res/drawable/btn_circle_disable.png
new file mode 100644
index 0000000..33b74a6
--- /dev/null
+++ b/res/drawable/btn_circle_disable.png
Binary files differ
diff --git a/res/drawable/btn_circle_disable_focused.png b/res/drawable/btn_circle_disable_focused.png
new file mode 100644
index 0000000..005ad8d
--- /dev/null
+++ b/res/drawable/btn_circle_disable_focused.png
Binary files differ
diff --git a/res/drawable/btn_circle_normal.png b/res/drawable/btn_circle_normal.png
new file mode 100644
index 0000000..fc5af1c
--- /dev/null
+++ b/res/drawable/btn_circle_normal.png
Binary files differ
diff --git a/res/drawable/btn_circle_pressed.png b/res/drawable/btn_circle_pressed.png
new file mode 100644
index 0000000..8f40afd
--- /dev/null
+++ b/res/drawable/btn_circle_pressed.png
Binary files differ
diff --git a/res/drawable/btn_circle_selected.png b/res/drawable/btn_circle_selected.png
new file mode 100644
index 0000000..c74fac2
--- /dev/null
+++ b/res/drawable/btn_circle_selected.png
Binary files differ
diff --git a/res/drawable/gestures_background.xml b/res/drawable/gestures_background.xml
new file mode 100755
index 0000000..34ec051
--- /dev/null
+++ b/res/drawable/gestures_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   Copyright (C) 2009 Romain Guy
+
+   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.
+-->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+    android:src="@drawable/texture_paper"
+    android:tileMode="repeat" />
diff --git a/res/drawable/ic_btn_round_plus.png b/res/drawable/ic_btn_round_plus.png
new file mode 100644
index 0000000..1ec8a95
--- /dev/null
+++ b/res/drawable/ic_btn_round_plus.png
Binary files differ
diff --git a/res/drawable/search_button_bg.9.png b/res/drawable/search_button_bg.9.png
new file mode 100644
index 0000000..b140b24
--- /dev/null
+++ b/res/drawable/search_button_bg.9.png
Binary files differ
diff --git a/res/drawable/search_button_voice.png b/res/drawable/search_button_voice.png
new file mode 100644
index 0000000..4c3d683
--- /dev/null
+++ b/res/drawable/search_button_voice.png
Binary files differ
diff --git a/res/drawable/search_floater.9.png b/res/drawable/search_floater.9.png
new file mode 100644
index 0000000..a2007ad
--- /dev/null
+++ b/res/drawable/search_floater.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget.xml b/res/drawable/textfield_searchwidget.xml
new file mode 100644
index 0000000..80f3dca
--- /dev/null
+++ b/res/drawable/textfield_searchwidget.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    
+   <item android:state_window_focused="false" android:state_enabled="true"
+        android:drawable="@drawable/textfield_searchwidget_default" />
+    
+    <item android:state_pressed="true"
+        android:drawable="@drawable/textfield_searchwidget_pressed" />
+    
+    <item android:state_enabled="true" android:state_focused="true"
+        android:drawable="@drawable/textfield_searchwidget_selected" />
+    
+    <item android:state_enabled="true"
+        android:drawable="@drawable/textfield_searchwidget_default" />
+    
+</selector>
diff --git a/res/drawable/textfield_searchwidget_default.9.png b/res/drawable/textfield_searchwidget_default.9.png
new file mode 100644
index 0000000..247abca
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_default.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget_pressed.9.png b/res/drawable/textfield_searchwidget_pressed.9.png
new file mode 100644
index 0000000..d628cbc
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_pressed.9.png
Binary files differ
diff --git a/res/drawable/textfield_searchwidget_selected.9.png b/res/drawable/textfield_searchwidget_selected.9.png
new file mode 100644
index 0000000..9e93527
--- /dev/null
+++ b/res/drawable/textfield_searchwidget_selected.9.png
Binary files differ
diff --git a/res/drawable/texture_paper.jpg b/res/drawable/texture_paper.jpg
new file mode 100644
index 0000000..27f4fd6
--- /dev/null
+++ b/res/drawable/texture_paper.jpg
Binary files differ
diff --git a/res/layout-land/live_folder_grid.xml b/res/layout-land/live_folder_grid.xml
index d1b02a4..bb623b0 100644
--- a/res/layout-land/live_folder_grid.xml
+++ b/res/layout-land/live_folder_grid.xml
@@ -36,6 +36,7 @@
     	android:layout_height="0dip"
     	android:layout_weight="1"
             
+        android:cacheColorHint="#ff333333"
         android:background="@drawable/box_launcher_bottom"
 
         android:scrollbarAlwaysDrawVerticalTrack="true"
diff --git a/res/layout-land/user_folder.xml b/res/layout-land/user_folder.xml
index 61f859e..fbf337b 100644
--- a/res/layout-land/user_folder.xml
+++ b/res/layout-land/user_folder.xml
@@ -33,6 +33,7 @@
     	android:layout_height="0dip"
     	android:layout_weight="1"
             
+        android:cacheColorHint="#ff333333"
         android:background="@drawable/box_launcher_bottom"
 
         android:scrollbarAlwaysDrawVerticalTrack="true"
diff --git a/res/layout-port/live_folder_grid.xml b/res/layout-port/live_folder_grid.xml
index ec32d41..e5bcafe 100644
--- a/res/layout-port/live_folder_grid.xml
+++ b/res/layout-port/live_folder_grid.xml
@@ -36,6 +36,7 @@
     	android:layout_height="0dip"
     	android:layout_weight="1"
 
+        android:cacheColorHint="#ff333333"
         android:background="@drawable/box_launcher_bottom"
 
         android:scrollbarAlwaysDrawVerticalTrack="true"
diff --git a/res/layout-port/user_folder.xml b/res/layout-port/user_folder.xml
index 5795aea..27d70fc 100644
--- a/res/layout-port/user_folder.xml
+++ b/res/layout-port/user_folder.xml
@@ -33,6 +33,7 @@
     	android:layout_height="0dip"
     	android:layout_weight="1"
 
+        android:cacheColorHint="#ff333333"
         android:background="@drawable/box_launcher_bottom"
 
         android:scrollbarAlwaysDrawVerticalTrack="true"
diff --git a/res/layout/gestures.xml b/res/layout/gestures.xml
new file mode 100644
index 0000000..846c063
--- /dev/null
+++ b/res/layout/gestures.xml
@@ -0,0 +1,96 @@
+<?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.
+-->
+
+<!-- Note: GesturesPanel is a special implementation that forces the widget
+     to be opaque for performance reasons. Make sure it visually is. -->
+<com.android.launcher.GesturesPanel
+    xmlns:android="http://schemas.android.com/apk/res/android"
+        
+    android:id="@+id/gestures_panel"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <ViewSwitcher
+        android:id="@+id/gestures_actions"
+        android:layout_width="fill_parent"
+        android:layout_height="83dip"
+        android:layout_alignParentBottom="true"
+
+        android:inAnimation="@anim/fade_in_fast"
+        android:outAnimation="@anim/fade_out_fast"
+
+        android:foregroundGravity="top|fill_horizontal"
+        android:foreground="@*android:drawable/title_bar_shadow"
+        android:background="@android:drawable/title_bar_tall">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_horizontal"
+
+            android:gravity="center_vertical"
+
+            android:shadowColor="#FF000000"
+            android:shadowRadius="2.0"
+
+            android:drawablePadding="8dip"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:ellipsize="end"
+            android:maxLines="2"
+            android:text="@string/gestures_instructions" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_horizontal"
+
+            android:gravity="center_vertical"
+
+            android:shadowColor="#FF000000"
+            android:shadowRadius="2.0"
+
+            android:drawablePadding="8dip"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:ellipsize="end"
+            android:maxLines="2"
+            android:text="@string/gestures_instructions" />
+
+    </ViewSwitcher>
+
+    <android.gesture.GestureOverlayView
+        android:id="@+id/gestures_overlay"
+        android:layout_width="fill_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1.0"
+        android:layout_alignParentTop="true"
+        android:layout_above="@id/gestures_actions"
+
+        android:background="@drawable/gestures_background"
+
+        android:gestureStrokeType="multiple" />
+
+    <ImageButton
+        style="@style/PlusButton"
+
+        android:id="@+id/gestures_add"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_alignTop="@id/gestures_actions"
+        android:layout_marginRight="10dip"  
+        android:layout_marginTop="-22dip" />
+
+</com.android.launcher.GesturesPanel>
diff --git a/res/layout/gestures_settings.xml b/res/layout/gestures_settings.xml
new file mode 100644
index 0000000..4b1976f
--- /dev/null
+++ b/res/layout/gestures_settings.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <ListView
+        android:id="@android:id/list"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" />
+
+    <TextView
+        android:id="@android:id/empty"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+
+        android:gravity="center_horizontal"
+
+        android:text="@string/gestures_loading"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</FrameLayout>
diff --git a/res/layout/gestures_settings_item.xml b/res/layout/gestures_settings_item.xml
new file mode 100644
index 0000000..3c47cab
--- /dev/null
+++ b/res/layout/gestures_settings_item.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+
+    android:drawablePadding="12dip"
+    android:paddingLeft="6dip"
+    android:paddingRight="2dip"
+
+    android:ellipsize="marquee"
+    android:singleLine="true"
+    android:textAppearance="?android:attr/textAppearanceLarge" />
diff --git a/res/layout/live_folder_list.xml b/res/layout/live_folder_list.xml
index 1d32f88..f96a190 100644
--- a/res/layout/live_folder_list.xml
+++ b/res/layout/live_folder_list.xml
@@ -36,7 +36,7 @@
     	android:layout_height="0dip"
     	android:layout_weight="1"
 
-        android:cacheColorHint="#00000000"
+        android:cacheColorHint="#ff333333"
         android:background="@drawable/box_launcher_bottom" />
 
 </com.android.launcher.LiveFolder>
diff --git a/res/layout/rename_folder.xml b/res/layout/rename_folder.xml
index 2c578f3..ba78995 100644
--- a/res/layout/rename_folder.xml
+++ b/res/layout/rename_folder.xml
@@ -21,6 +21,7 @@
     android:orientation="vertical">
 
     <TextView 
+        android:id="@+id/label"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         android:text="@string/rename_folder_label"
diff --git a/res/layout/widget_search.xml b/res/layout/widget_search.xml
index 1db8488..841b155 100644
--- a/res/layout/widget_search.xml
+++ b/res/layout/widget_search.xml
@@ -14,52 +14,47 @@
      limitations under the License.
 -->
 
-<com.android.launcher.Search xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher.Search 
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
+    android:id="@+id/widget_search"  
     android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="horizontal"
-    android:background="@drawable/search_bg"
-    android:gravity="center_vertical"
-    >
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:gravity="top">
 
-    <ImageView
-        android:layout_width="wrap_content"
+    <LinearLayout
+        android:id="@+id/search_plate"
+        android:layout_width="fill_parent"
         android:layout_height="wrap_content"
-        android:paddingRight="3dip"
-        android:src="@drawable/google_logo" />
-        
-    <com.android.launcher.SearchAutoCompleteTextView
-        android:id="@+id/input"
-        android:layout_width="0dip"
-        android:layout_weight="1"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="1dip"
-        android:hint="@string/search_hint"
-        android:focusableInTouchMode="false"
-        android:singleLine="true"
-        android:selectAllOnFocus="true"
-        android:completionThreshold="1"
-        android:inputType="textAutoComplete"
-        android:imeOptions="actionSearch"
-        android:lines="1"
-        android:dropDownWidth="fill_parent"
-        android:popupBackground="@drawable/spinner_dropdown_background"
+        android:orientation="horizontal"
+        android:paddingLeft="12dip"
+        android:paddingRight="12dip"
+        android:paddingTop="7dip"
+        android:paddingBottom="13dip"
+        android:background="@drawable/search_floater" >
+
+        <TextView
+            android:id="@+id/search_src_text"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1.0"
+            android:editable="false"
+            android:inputType="text"
+            android:background="@drawable/textfield_searchwidget"
+            android:textAppearance="?android:attr/textAppearanceMediumInverse"
+            android:textColor="@android:color/primary_text_light"
         />
         
-     <ImageButton android:id="@+id/search_go_btn"
-         android:layout_marginLeft="5dip"
-         android:layout_width="wrap_content"
-         android:layout_height="wrap_content"
-         android:src="@*android:drawable/ic_btn_search"
-         style="@style/SearchButton"
-         />
-    
-     <ImageButton android:id="@+id/search_voice_btn"
-         android:layout_marginLeft="4dip"
-         android:layout_width="wrap_content"
-         android:layout_height="wrap_content"
-         android:src="@android:drawable/ic_btn_speak_now"
-         style="@style/SearchButton"
-         />
+        <ImageButton 
+            android:id="@+id/search_voice_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_marginLeft="8dip"
+            android:background="@*android:drawable/btn_search_dialog_voice"
+            android:src="@*android:drawable/ic_btn_speak_now"
+        />
+
+    </LinearLayout>
 
 </com.android.launcher.Search>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 7832ceb..f0cc369 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -26,20 +26,27 @@
     <string name="rename_action">"OK"</string>
     <string name="cancel_action">"Zrušit"</string>
     <string name="menu_item_add_item">"Přidat na plochu"</string>
-    <string name="group_applications">"Aplikace"</string>
-    <string name="group_shortcuts">"Klávesové zkratky"</string>
+    <!-- no translation found for group_applications (4118484163419674240) -->
+    <skip />
+    <!-- no translation found for group_shortcuts (9133529424900391877) -->
+    <skip />
     <string name="group_search">"Hledat"</string>
-    <string name="group_folder">"Nová složka"</string>
-    <string name="group_live_folders">"Složky"</string>
-    <string name="group_widgets">"Widgety"</string>
-    <string name="group_wallpapers">"Tapety"</string>
+    <!-- no translation found for group_folder (5143593791798929193) -->
+    <skip />
+    <!-- no translation found for group_live_folders (2664945399140647217) -->
+    <skip />
+    <!-- no translation found for group_widgets (6704978494073105844) -->
+    <skip />
+    <!-- no translation found for group_wallpapers (1568191644272224858) -->
+    <skip />
     <string name="add_folder">"Složka"</string>
     <string name="add_clock">"Hodiny"</string>
     <string name="add_photo_frame">"Rámeček fotografie"</string>
     <string name="add_search">"Vyhledávání"</string>
     <string name="out_of_space">"Na této ploše již není místo."</string>
     <string name="title_select_shortcut">"Vyberte zástupce"</string>
-    <string name="title_select_live_folder">"Vybrat složku"</string>
+    <!-- no translation found for title_select_live_folder (3753447798805166749) -->
+    <skip />
     <string name="menu_add">"Přidat"</string>
     <string name="menu_wallpaper">"Tapeta"</string>
     <string name="menu_search">"Hledat"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 9b39946..40f3cab 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -25,21 +25,28 @@
     <string name="rename_folder_title">"Ordner umbenennen"</string>
     <string name="rename_action">"OK"</string>
     <string name="cancel_action">"Abbrechen"</string>
-    <string name="menu_item_add_item">"Zum Startbildschirm hinzufügen"</string>
-    <string name="group_applications">"Anwendungen"</string>
-    <string name="group_shortcuts">"Verknüpfungen"</string>
+    <string name="menu_item_add_item">"Zur Startseite hinzufügen"</string>
+    <!-- no translation found for group_applications (4118484163419674240) -->
+    <skip />
+    <!-- no translation found for group_shortcuts (9133529424900391877) -->
+    <skip />
     <string name="group_search">"Suchen"</string>
-    <string name="group_folder">"Neuer Ordner"</string>
-    <string name="group_live_folders">"Ordner"</string>
-    <string name="group_widgets">"Widgets"</string>
-    <string name="group_wallpapers">"Hintergrundbilder"</string>
+    <!-- no translation found for group_folder (5143593791798929193) -->
+    <skip />
+    <!-- no translation found for group_live_folders (2664945399140647217) -->
+    <skip />
+    <!-- no translation found for group_widgets (6704978494073105844) -->
+    <skip />
+    <!-- no translation found for group_wallpapers (1568191644272224858) -->
+    <skip />
     <string name="add_folder">"Ordner"</string>
     <string name="add_clock">"Uhr"</string>
     <string name="add_photo_frame">"Bildrahmen"</string>
     <string name="add_search">"Suchen"</string>
-    <string name="out_of_space">"Auf dem Startbildschirm ist kein Platz mehr vorhanden."</string>
+    <string name="out_of_space">"Auf der Startseite ist kein Platz mehr vorhanden."</string>
     <string name="title_select_shortcut">"Tastenkürzel auswählen"</string>
-    <string name="title_select_live_folder">"Ordner auswählen"</string>
+    <!-- no translation found for title_select_live_folder (3753447798805166749) -->
+    <skip />
     <string name="menu_add">"Hinzufügen"</string>
     <string name="menu_wallpaper">"Hintergrund"</string>
     <string name="menu_search">"Suchen"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..221ab86
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,71 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="application_name">"Página principal"</string>
+    <string name="folder_name">"Carpeta"</string>
+    <string name="chooser_wallpaper">"Seleccionar papel tapiz desde"</string>
+    <string name="wallpaper_instructions">"Definir papel tapiz"</string>
+    <string name="pick_wallpaper">"Galería de papel tapiz"</string>
+    <string name="activity_not_found">"La aplicación no está instalada en tu computadora."</string>
+    <string name="rename_folder_label">"Nombre de carpeta"</string>
+    <string name="rename_folder_title">"Cambiar nombre de carpeta"</string>
+    <string name="rename_action">"Aceptar"</string>
+    <string name="cancel_action">"Cancelar"</string>
+    <string name="menu_item_add_item">"Agregar a la pantalla Página principal"</string>
+    <!-- no translation found for group_applications (4118484163419674240) -->
+    <skip />
+    <!-- no translation found for group_shortcuts (9133529424900391877) -->
+    <skip />
+    <string name="group_search">"Buscar"</string>
+    <!-- no translation found for group_folder (5143593791798929193) -->
+    <skip />
+    <!-- no translation found for group_live_folders (2664945399140647217) -->
+    <skip />
+    <!-- no translation found for group_widgets (6704978494073105844) -->
+    <skip />
+    <!-- no translation found for group_wallpapers (1568191644272224858) -->
+    <skip />
+    <string name="add_folder">"Carpeta"</string>
+    <string name="add_clock">"Reloj"</string>
+    <string name="add_photo_frame">"Marco de imagen"</string>
+    <string name="add_search">"Buscar"</string>
+    <string name="out_of_space">"No hay más espacio en esta pantalla de la página principal"</string>
+    <!-- no translation found for shortcut_installed (6623689857689040689) -->
+    <skip />
+    <!-- no translation found for shortcut_uninstalled (2758997515869993129) -->
+    <skip />
+    <!-- no translation found for shortcut_duplicate (4887186782641532074) -->
+    <skip />
+    <string name="title_select_shortcut">"Seleccionar acceso directo"</string>
+    <!-- no translation found for title_select_live_folder (3753447798805166749) -->
+    <skip />
+    <string name="menu_add">"Agregar"</string>
+    <string name="menu_wallpaper">"Papel tapiz"</string>
+    <string name="menu_search">"Buscar"</string>
+    <string name="menu_notifications">"Notificaciones"</string>
+    <string name="menu_settings">"Configuración"</string>
+    <string name="permlab_install_shortcut">"instalar accesos directos"</string>
+    <string name="permdesc_install_shortcut">"Permite a una aplicación agregar accesos directos sin intervención del usuario."</string>
+    <string name="permlab_uninstall_shortcut">"desinstalar papel tapiz"</string>
+    <string name="permdesc_uninstall_shortcut">"Permite a una aplicación suprimir accesos directos sin intervención del usuario."</string>
+    <string name="permlab_read_settings">"leer configuración y accesos directos de la página principal"</string>
+    <string name="permdesc_read_settings">"Permite a una aplicación leer la configuración y los accesos directos de la página principal."</string>
+    <string name="permlab_write_settings">"escribir configuración y accesos directos de la página principal"</string>
+    <string name="permdesc_write_settings">"Permite a una aplicación cambiar la configuración y los accesos directos de la página principal."</string>
+    <string name="search_hint">"Búsqueda de Google"</string>
+    <string name="gadget_error_text">"Problema al cargar el widget"</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index e0f01be..7558565 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -36,12 +36,12 @@
     <string name="add_folder">"Dossier"</string>
     <string name="add_clock">"Horloge"</string>
     <string name="add_photo_frame">"Cadre d\'image"</string>
-    <string name="add_search">"Recherche"</string>
+    <string name="add_search">"Rechercher"</string>
     <string name="out_of_space">"Plus d\'espace libre sur l\'écran d\'accueil."</string>
     <string name="title_select_shortcut">"Sélectionner un raccourci"</string>
     <string name="title_select_live_folder">"Sélectionner le dossier"</string>
     <string name="menu_add">"Ajouter"</string>
-    <string name="menu_wallpaper">"Fond d\'écran"</string>
+    <string name="menu_wallpaper">"Arrière-plan"</string>
     <string name="menu_search">"Rechercher"</string>
     <string name="menu_notifications">"Notifications"</string>
     <string name="menu_settings">"Paramètres"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index e6815dd..954b5d7 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -26,20 +26,27 @@
     <string name="rename_action">"OK"</string>
     <string name="cancel_action">"Avbryt"</string>
     <string name="menu_item_add_item">"Legg til skrivebord"</string>
-    <string name="group_applications">"Applikasjoner"</string>
-    <string name="group_shortcuts">"Snarveier"</string>
+    <!-- no translation found for group_applications (4118484163419674240) -->
+    <skip />
+    <!-- no translation found for group_shortcuts (9133529424900391877) -->
+    <skip />
     <string name="group_search">"Søk"</string>
-    <string name="group_folder">"Ny mappe"</string>
-    <string name="group_live_folders">"Mapper"</string>
-    <string name="group_widgets">"Skrivebordselementer"</string>
-    <string name="group_wallpapers">"Bakgrunner"</string>
+    <!-- no translation found for group_folder (5143593791798929193) -->
+    <skip />
+    <!-- no translation found for group_live_folders (2664945399140647217) -->
+    <skip />
+    <!-- no translation found for group_widgets (6704978494073105844) -->
+    <skip />
+    <!-- no translation found for group_wallpapers (1568191644272224858) -->
+    <skip />
     <string name="add_folder">"Mappe"</string>
     <string name="add_clock">"Klokke"</string>
     <string name="add_photo_frame">"Bilderamme"</string>
     <string name="add_search">"Søk"</string>
     <string name="out_of_space">"Ikke nok plass på skrivebordet."</string>
     <string name="title_select_shortcut">"Velg snarvei"</string>
-    <string name="title_select_live_folder">"Velg mappe"</string>
+    <!-- no translation found for title_select_live_folder (3753447798805166749) -->
+    <skip />
     <string name="menu_add">"Legg til"</string>
     <string name="menu_wallpaper">"Bakgrunnsbilde"</string>
     <string name="menu_search">"Søk"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 5574944..e1b4843 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -23,5 +23,8 @@
     <color name="bubble_dark_background">#B2191919</color>
     <color name="delete_color_filter">#A5FF0000</color>
 
-    <color name="appwidget_error_color">#fccc</color>
+    <color name="appwidget_error_color">#FCCC</color>
+    <color name="snag_callout_color">#F444</color>
+
+    <color name="gesture_color">#FFFFFF00</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4ae6686..b802353 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -16,4 +16,6 @@
 
 <resources>
     <dimen name="search_widget_inset">19dip</dimen>
+    <dimen name="gesture_thumbnail_inset">8dip</dimen>
+    <dimen name="gesture_thumbnail_size">64dip</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b0421b2..f083c98 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -68,10 +68,16 @@
     <string name="add_clock">Clock</string>
     <!-- Options in "Add to Home" dialog box; Name of the Picture frame widget-->
     <string name="add_photo_frame">Picture frame</string>
-    <!-- Options in "Add to Home" dialog box; Name of the Google Search widget-->
+    <!-- Options in "Add to Home" dialog box; Name of the global search widget-->
     <string name="add_search">Search</string>
     <!-- Error message when user has filled a home screen, possibly not used -->
     <string name="out_of_space">No more room on this Home screen.</string>
+    <!-- Message displayed when a shortcut is created by an external application -->
+    <string name="shortcut_installed">Shortcut \"%s\" is installed.</string>
+    <!-- Message displayed when a shortcut is uninstalled by an external application -->
+    <string name="shortcut_uninstalled">Shortcut \"%s\" was removed.</string>
+    <!-- Message displayed when an external application attemps to create a shortcut that already exists -->
+    <string name="shortcut_duplicate">Shortcut \"%s\" already exists.</string>
 
     <!-- Title of dialog when user is selecting shortcut to add to homescreen -->
     <string name="title_select_shortcut">Select shortcut</string>
@@ -84,11 +90,13 @@
     <string name="menu_add">Add</string>
     <!-- Noun, menu item used to set the desktop's wallpaper -->
     <string name="menu_wallpaper">Wallpaper</string>
-    <!-- Verb, menu item used to initiate a Google Search -->
+    <!-- Verb, menu item used to initiate global search -->
     <string name="menu_search">Search</string>
     <!-- Noun, menu item used to bring down the notifications shade -->
     <string name="menu_notifications">Notifications</string>
-    <!-- Noun, menu item used to show the system settings -->    
+    <!-- Noun, menu item used to show the gestures settings -->
+    <string name="menu_gestures">Gestures</string>
+    <!-- Noun, menu item used to show the system settings -->
     <string name="menu_settings">Settings</string>
 
     <!-- Permissions: -->
@@ -107,7 +115,9 @@
 
     <!-- Widgets: -->
     <skip />    
-    <!-- This is the hint text shown in the search widget, before text is entered.
+
+    <!-- TODO: Determine if this can be removed.
+         This is the hint text shown in the search widget, before text is entered.
          This translation SHOULD MATCH the string "search_hint" which is found in 
          GoogleSearch/res/values/strings.xml -->
     <string name="search_hint">Google Search</string>
@@ -115,4 +125,31 @@
     <!-- Text to show user in place of a gadget when we can't display it properly -->
     <string name="gadget_error_text">Problem loading widget</string>
 
+    <!-- Gestures: -->
+    <skip />
+
+    <!-- Message displayed when the user enters gestures mode and is asked to draw a gesture -->
+    <string name="gestures_instructions">Draw a gesture to get started</string>
+    <!-- Message displayed when the gesture entered by the user cannot be recognized -->
+    <string name="gestures_unknown">Unknown gesture</string>
+    <!-- Message displayed when the user has successfully created a new gesture -->
+    <string name="gestures_created">Added gesture "%s"</string>
+    <!-- Message displayed when the user could not create a new gesture -->
+    <string name="gestures_failed">Gesture could not be created</string>
+    <!-- Message displayed when the user opens the gestures settings screen -->
+    <string name="gestures_loading">Loading gestures...</string>
+    <!-- Message displayed when the user has no gestures -->
+    <string name="gestures_empty">No gestures defined</string>
+    <!-- Title of the screen used to view/manage gestures -->
+    <string name="gestures_activity">Gestures</string>
+    <!-- Noun, menu item used to rename a gesture -->
+    <string name="gestures_rename">Rename</string>
+    <!-- Noun, menu item used to remove a gesture -->
+    <string name="gestures_delete">Delete</string>
+    <!-- Message displayed when a gesture is successfully deleted -->
+    <string name="gestures_delete_success">Gesture deleted</string>
+    <!-- Title of dialog box -->
+    <string name="gestures_rename_title">Rename gesture</string>
+    <!-- Label of gesture name field in Rename gesture dialog box -->
+    <string name="gestures_rename_label">Gesture name</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 9b06d26..5319bb0 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -60,4 +60,10 @@
         <item name="android:paddingLeft">10dip</item>
         <item name="android:paddingRight">10dip</item>
     </style>
+
+    <style name="PlusButton">
+        <item name="android:background">@drawable/btn_circle</item>
+        <item name="android:src">@drawable/ic_btn_round_plus</item>
+    </style>
+
 </resources>
diff --git a/res/xml/default_workspace.xml b/res/xml/default_workspace.xml
index ae2e2a6..2280b61 100644
--- a/res/xml/default_workspace.xml
+++ b/res/xml/default_workspace.xml
@@ -19,7 +19,7 @@
     <clock
         launcher:screen="2"
         launcher:x="1"
-        launcher:y="0" />
+        launcher:y="1" />
 
     <search
         launcher:screen="1"
diff --git a/src/com/android/launcher/ApplicationInfo.java b/src/com/android/launcher/ApplicationInfo.java
index 9bc0950..c9c8c5c 100644
--- a/src/com/android/launcher/ApplicationInfo.java
+++ b/src/com/android/launcher/ApplicationInfo.java
@@ -61,7 +61,7 @@
     Intent.ShortcutIconResource iconResource;
 
     ApplicationInfo() {
-        itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
     }
     
     public ApplicationInfo(ApplicationInfo info) {
@@ -80,7 +80,7 @@
 
     /**
      * Creates the application intent based on a component name and various launch flags.
-     * Sets {@link #itemType} to {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}.
+     * 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
@@ -90,7 +90,7 @@
         intent.addCategory(Intent.CATEGORY_LAUNCHER);
         intent.setComponent(className);
         intent.setFlags(launchFlags);
-        itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
     }
 
     @Override
@@ -98,22 +98,24 @@
         super.onAddToDatabase(values);
 
         String titleStr = title != null ? title.toString() : null;
-        values.put(LauncherSettings.Favorites.TITLE, titleStr);
+        values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
 
         String uri = intent != null ? intent.toURI() : null;
-        values.put(LauncherSettings.Favorites.INTENT, uri);
+        values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
 
         if (customIcon) {
-            values.put(LauncherSettings.Favorites.ICON_TYPE,
-                    LauncherSettings.Favorites.ICON_TYPE_BITMAP);
+            values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+                    LauncherSettings.BaseLauncherColumns.ICON_TYPE_BITMAP);
             Bitmap bitmap = ((FastBitmapDrawable) icon).getBitmap();
             writeBitmap(values, bitmap);
         } else {
-            values.put(LauncherSettings.Favorites.ICON_TYPE,
-                    LauncherSettings.Favorites.ICON_TYPE_RESOURCE);
+            values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
+                    LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
             if (iconResource != null) {
-                values.put(LauncherSettings.Favorites.ICON_PACKAGE, iconResource.packageName);
-                values.put(LauncherSettings.Favorites.ICON_RESOURCE, iconResource.resourceName);
+                values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
+                        iconResource.packageName);
+                values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
+                        iconResource.resourceName);
             }
         }
     }
diff --git a/src/com/android/launcher/CellLayout.java b/src/com/android/launcher/CellLayout.java
index 91f0420..058b22c 100644
--- a/src/com/android/launcher/CellLayout.java
+++ b/src/com/android/launcher/CellLayout.java
@@ -98,6 +98,18 @@
         }
     }
 
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+
+        // Cancel long press for all children
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.cancelLongPress();
+        }
+    }
+
     int getCountX() {
         return mPortrait ? mShortAxisCells : mLongAxisCells;
     }
@@ -174,7 +186,7 @@
                 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
 
                 final boolean[][] occupied = mOccupied;
-                findOccupiedCells(xCount, yCount, occupied);
+                findOccupiedCells(xCount, yCount, occupied, null);
 
                 cellInfo.cell = null;
                 cellInfo.cellX = cellXY[0];
@@ -215,7 +227,7 @@
             final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
 
             final boolean[][] occupied = mOccupied;
-            findOccupiedCells(xCount, yCount, occupied);
+            findOccupiedCells(xCount, yCount, occupied, null);
 
             findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
 
@@ -315,7 +327,7 @@
         return true;
     }
 
-    CellInfo findAllVacantCells(boolean[] occupiedCells) {
+    CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
         final boolean portrait = mPortrait;
         final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
@@ -329,7 +341,7 @@
                 }
             }
         } else {
-            findOccupiedCells(xCount, yCount, occupied);
+            findOccupiedCells(xCount, yCount, occupied, ignoreView);
         }
 
         CellInfo cellInfo = new CellInfo();
@@ -527,64 +539,72 @@
         super.setChildrenDrawnWithCacheEnabled(enabled);
     }
 
-    boolean acceptChildDrop(int x, int y, int cellHSpan, int cellVSpan, View cell) {
-        int[] cellXY = mCellXY;
-        pointToCellRounded(x, y, cellXY);
-        int cellX = cellXY[0];
-        int cellY = cellXY[1];
-
-        return findCell(cellX, cellY, cellHSpan, cellVSpan, cell) == null;
-    }
-
     /**
-     * Finds the first View intersecting with the specified cell. If the cell is outside
-     * of the layout, this is returned.
-     *
-     * @param cellX The X location of the cell to test.
-     * @param cellY The Y location of the cell to test.
-     * @param cellHSpan The horizontal span of the cell to test.
-     * @param cellVSpan The vertical span of the cell to test.
-     * @param ignoreCell View to ignore during the test.
-     *
-     * @return Returns the first View intersecting with the specified cell, this if the cell
-     *         lies outside of this layout's grid or null if no View was found.
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location. Uses Euclidean distance to score multiple vacant areas.
+     * 
+     * @param pixelX The X location at which you want to search for a vacant area.
+     * @param pixelY The Y location at which you want to search for a vacant area.
+     * @param spanX Horizontal span of the object.
+     * @param spanY Vertical span of the object.
+     * @param vacantCells Pre-computed set of vacant cells to search.
+     * @param recycle Previously returned value to possibly recycle.
+     * @return The X, Y cell of a vacant area that can contain this object,
+     *         nearest the requested location.
      */
-    View findCell(int cellX, int cellY, int cellHSpan, int cellVSpan, View ignoreCell) {
-        if (cellX < 0 || cellX + cellHSpan > (mPortrait ? mShortAxisCells : mLongAxisCells) ||
-                cellY < 0 || cellY + cellVSpan > (mPortrait ? mLongAxisCells : mShortAxisCells)) {
-            return this;
+    int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
+            CellInfo vacantCells, int[] recycle) {
+        
+        // Keep track of best-scoring drop area
+        final int[] bestXY = recycle != null ? recycle : new int[2];
+        final int[] cellXY = mCellXY;
+        double bestDistance = Double.MAX_VALUE;
+        
+        // Bail early if vacant cells aren't valid
+        if (!vacantCells.valid) {
+            return null;
         }
 
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View view = getChildAt(i);
-            if (view == ignoreCell) {
+        // Look across all vacant cells for best fit
+        final int size = vacantCells.vacantCells.size();
+        for (int i = 0; i < size; i++) {
+            final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
+            
+            // Reject if vacant cell isn't our exact size
+            if (cell.spanX != spanX || cell.spanY != spanY) {
                 continue;
             }
-
-            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            if (cellX < lp.cellX + lp.cellHSpan && lp.cellX < cellX + cellHSpan &&
-                    cellY < lp.cellY + lp.cellVSpan && lp.cellY < cellY + cellVSpan) {
-                return view;
+            
+            // Score is center distance from requested pixel
+            cellToPoint(cell.cellX, cell.cellY, cellXY);
+            
+            double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) +
+                    Math.pow(cellXY[1] - pixelY, 2));
+            if (distance <= bestDistance) {
+                bestDistance = distance;
+                bestXY[0] = cell.cellX;
+                bestXY[1] = cell.cellY;
             }
         }
 
-        return null;
+        // Return null if no suitable location found 
+        if (bestDistance < Double.MAX_VALUE) {
+            return bestXY;
+        } else {
+            return null;
+        }
     }
-
+    
     /**
      * Drop a child at the specified position
      *
      * @param child The child that is being dropped
-     * @param cellX The child's new x location
-     * @param cellY The child's new y location
+     * @param targetXY Destination area to move to
      */
-    void onDropChild(View child, int cellX, int cellY) {
-        int[] cellXY = mCellXY;
-        pointToCellRounded(cellX, cellY, cellXY);
+    void onDropChild(View child, int[] targetXY) {
         LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        lp.cellX = cellXY[0];
-        lp.cellY = cellXY[1];
+        lp.cellX = targetXY[0];
+        lp.cellY = targetXY[1];
         lp.isDragging = false;
         mDragRect.setEmpty();
         child.requestLayout();
@@ -688,7 +708,7 @@
         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
         final boolean[][] occupied = mOccupied;
 
-        findOccupiedCells(xCount, yCount, occupied);
+        findOccupiedCells(xCount, yCount, occupied, null);
 
         return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
     }
@@ -723,7 +743,7 @@
         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
         final boolean[][] occupied = mOccupied;
 
-        findOccupiedCells(xCount, yCount, occupied);
+        findOccupiedCells(xCount, yCount, occupied, null);
 
         final boolean[] flat = new boolean[xCount * yCount];
         for (int y = 0; y < yCount; y++) {
@@ -735,7 +755,7 @@
         return flat;
     }
 
-    private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied) {
+    private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) {
         for (int x = 0; x < xCount; x++) {
             for (int y = 0; y < yCount; y++) {
                 occupied[x][y] = false;
@@ -745,7 +765,7 @@
         int count = getChildCount();
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
-            if (child instanceof Folder) {
+            if (child instanceof Folder || child.equals(ignoreView)) {
                 continue;
             }
             LayoutParams lp = (LayoutParams) child.getLayoutParams();
@@ -919,7 +939,7 @@
         int maxVacantSpanYSpanX;
         final Rect current = new Rect();
 
-        private void clearVacantCells() {
+        void clearVacantCells() {
             final ArrayList<VacantCell> list = vacantCells;
             final int count = list.size();
 
@@ -960,6 +980,10 @@
          * @return True if a vacant cell of the specified dimension was found, false otherwise.
          */
         boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
+            return findCellForSpan(cellXY, spanX, spanY, true);
+        }
+
+        boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
             final ArrayList<VacantCell> list = vacantCells;
             final int count = list.size();
 
@@ -993,7 +1017,7 @@
                 }
             }
 
-            clearVacantCells();
+            if (clear) clearVacantCells();
 
             return found;
         }
diff --git a/src/com/android/launcher/DeleteZone.java b/src/com/android/launcher/DeleteZone.java
index 7f92c23..02e8011 100644
--- a/src/com/android/launcher/DeleteZone.java
+++ b/src/com/android/launcher/DeleteZone.java
@@ -19,6 +19,7 @@
 import android.widget.ImageView;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.animation.TranslateAnimation;
@@ -77,6 +78,10 @@
             Object dragInfo) {
         return true;
     }
+    
+    public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+        return null;
+    }
 
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
         final ItemInfo item = (ItemInfo) dragInfo;
diff --git a/src/com/android/launcher/DragLayer.java b/src/com/android/launcher/DragLayer.java
index b542de6..2e59b10 100644
--- a/src/com/android/launcher/DragLayer.java
+++ b/src/com/android/launcher/DragLayer.java
@@ -113,8 +113,25 @@
     private DropTarget mLastDropTarget;
 
     private final Paint mTrashPaint = new Paint();
+    private final Paint mEstimatedPaint = new Paint();
     private Paint mDragPaint;
 
+    /**
+     * If true, draw a "snag" showing where the object currently being dragged
+     * would end up if dropped from current location.
+     */
+    private static final boolean DRAW_TARGET_SNAG = false;
+
+    private Rect mEstimatedRect = new Rect();
+    private float[] mDragCenter = new float[2];
+    private float[] mEstimatedCenter = new float[2];
+    private boolean mDrawEstimated = false;
+
+    private int mTriggerWidth = -1;
+    private int mTriggerHeight = -1;
+
+    private static final int DISTANCE_DRAW_SNAG = 20;
+
     private static final int ANIMATION_STATE_STARTING = 1;
     private static final int ANIMATION_STATE_RUNNING = 2;
     private static final int ANIMATION_STATE_DONE = 3;
@@ -141,6 +158,13 @@
 
         final int srcColor = context.getResources().getColor(R.color.delete_color_filter);
         mTrashPaint.setColorFilter(new PorterDuffColorFilter(srcColor, PorterDuff.Mode.SRC_ATOP));
+
+        // Make estimated paint area in gray
+        int snagColor = context.getResources().getColor(R.color.snag_callout_color);
+        mEstimatedPaint.setColor(snagColor);
+        mEstimatedPaint.setStrokeWidth(3);
+        mEstimatedPaint.setAntiAlias(true);
+
     }
 
     public void startDrag(View v, DragSource source, Object dragInfo, int dragAction) {
@@ -171,12 +195,20 @@
 
         boolean willNotCache = v.willNotCacheDrawing();
         v.setWillNotCacheDrawing(false);
-        v.buildDrawingCache();
 
+        // Reset the drawing cache background color to fully transparent
+        // for the duration of this operation
+        int color = v.getDrawingCacheBackgroundColor();
+        v.setDrawingCacheBackgroundColor(0);
+
+        v.buildDrawingCache();
         Bitmap viewBitmap = v.getDrawingCache();
         int width = viewBitmap.getWidth();
         int height = viewBitmap.getHeight();
 
+        mTriggerWidth = width * 2 / 3;
+        mTriggerHeight = height * 2 / 3;
+
         Matrix scale = new Matrix();
         float scaleFactor = v.getWidth();
         scaleFactor = (scaleFactor + DRAG_SCALE) /scaleFactor;
@@ -191,6 +223,7 @@
         mDragBitmap = Bitmap.createBitmap(viewBitmap, 0, 0, width, height, scale, true);
         v.destroyDrawingCache();
         v.setWillNotCacheDrawing(willNotCache);
+        v.setDrawingCacheBackgroundColor(color);
 
         final Bitmap dragBitmap = mDragBitmap;
         mBitmapOffsetX = (dragBitmap.getWidth() - width) / 2;
@@ -252,6 +285,13 @@
                         break;
                 }
             } else {
+                // Only draw estimate drop "snag" when requested
+                if (DRAW_TARGET_SNAG && mDrawEstimated) {
+                    canvas.drawLine(mDragCenter[0], mDragCenter[1], mEstimatedCenter[0], mEstimatedCenter[1], mEstimatedPaint);
+                    canvas.drawCircle(mEstimatedCenter[0], mEstimatedCenter[1], 8, mEstimatedPaint);
+                }
+
+                // Draw actual icon being dragged
                 canvas.drawBitmap(mDragBitmap,
                         mScrollX + mLastMotionX - mTouchOffsetX - mBitmapOffsetX,
                         mScrollY + mLastMotionY - mTouchOffsetY - mBitmapOffsetY, mDragPaint);
@@ -355,8 +395,16 @@
             left = (int) (scrollX + x - touchX - offsetX);
             top = (int) (scrollY + y - touchY - offsetY);
 
+            // Invalidate current icon position
             rect.union(left - 1, top - 1, left + width + 1, top + height + 1);
-            invalidate(rect);
+
+            mDragCenter[0] = rect.centerX();
+            mDragCenter[1] = rect.centerY();
+
+            // Invalidate any old estimated location
+            if (DRAW_TARGET_SNAG && mDrawEstimated) {
+                rect.union(mEstimatedRect);
+            }
 
             final int[] coordinates = mDropCoordinates;
             DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);
@@ -378,6 +426,33 @@
                         (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo);
                 }
             }
+
+            // Render estimated drop "snag" only outside of width
+            mDrawEstimated = false;
+            if (DRAW_TARGET_SNAG && dropTarget != null) {
+                Rect foundEstimate = dropTarget.estimateDropLocation(mDragSource,
+                        (int) (scrollX + mLastMotionX), (int) (scrollY + mLastMotionY),
+                        (int) mTouchOffsetX, (int) mTouchOffsetY, mDragInfo, mEstimatedRect);
+
+                if (foundEstimate != null) {
+                    mEstimatedCenter[0] = foundEstimate.centerX();
+                    mEstimatedCenter[1] = foundEstimate.centerY();
+
+                    int deltaX = (int) Math.abs(mEstimatedCenter[0] - mDragCenter[0]);
+                    int deltaY = (int) Math.abs(mEstimatedCenter[1] - mDragCenter[1]);
+
+                    if (deltaX > mTriggerWidth || deltaY > mTriggerHeight) {
+                        mDrawEstimated = true;
+                    }
+                }
+            }
+
+            // Include new estimated area in invalidated rectangle
+            if (DRAW_TARGET_SNAG && mDrawEstimated) {
+                rect.union(mEstimatedRect);
+            }
+            invalidate(rect);
+
             mLastDropTarget = dropTarget;
 
             boolean inDragRegion = false;
@@ -478,9 +553,15 @@
                     }
                     if (target == null) {
                         if (child instanceof DropTarget) {
-                            dropCoordinates[0] = x;
-                            dropCoordinates[1] = y;
-                            return (DropTarget) child;
+                            // Only consider this child if they will accept
+                            DropTarget childTarget = (DropTarget) child;
+                            if (childTarget.acceptDrop(mDragSource, x, y, 0, 0, mDragInfo)) {
+                                dropCoordinates[0] = x;
+                                dropCoordinates[1] = y;
+                                return (DropTarget) child;
+                            } else {
+                                return null;
+                            }
                         }
                     } else {
                         return target;
@@ -531,6 +612,7 @@
 
         public void run() {
             if (mDragScroller != null) {
+                mDrawEstimated = false;
                 if (mDirection == SCROLL_LEFT) {
                     mDragScroller.scrollLeft();
                 } else {
diff --git a/src/com/android/launcher/DropTarget.java b/src/com/android/launcher/DropTarget.java
index 8129089..4835323 100644
--- a/src/com/android/launcher/DropTarget.java
+++ b/src/com/android/launcher/DropTarget.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher;
 
+import android.graphics.Rect;
+
 /**
  * Interface defining an object that can receive a drag.
  *
@@ -42,18 +44,38 @@
     void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);
 
     /**
-     * Indicates whether a drop action can occur at the specified location. The method
-     * {@link #onDrop(DragSource, int, int, int, int, Object)} will be invoked on this
-     * drop target only if this method returns true. 
-     *
+     * Check if a drop action can occur at, or near, the requested location.
+     * This may be called repeatedly during a drag, so any calls should return
+     * quickly.
+     * 
      * @param source DragSource where the drag started
      * @param x X coordinate of the drop location
      * @param y Y coordinate of the drop location
-     * @param xOffset Horizontal offset with the object being dragged where the original touch happened
-     * @param yOffset Vertical offset with the object being dragged where the original touch happened
+     * @param xOffset Horizontal offset with the object being dragged where the
+     *            original touch happened
+     * @param yOffset Vertical offset with the object being dragged where the
+     *            original touch happened
      * @param dragInfo Data associated with the object being dragged
-     *
-     * return True if the drop is accepted, false otherwise.
+     * @return True if the drop will be accepted, false otherwise.
      */
     boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo);
+
+    /**
+     * Estimate the surface area where this object would land if dropped at the
+     * given location.
+     * 
+     * @param source DragSource where the drag started
+     * @param x X coordinate of the drop location
+     * @param y Y coordinate of the drop location
+     * @param xOffset Horizontal offset with the object being dragged where the
+     *            original touch happened
+     * @param yOffset Vertical offset with the object being dragged where the
+     *            original touch happened
+     * @param dragInfo Data associated with the object being dragged
+     * @param recycle {@link Rect} object to be possibly recycled.
+     * @return Estimated area that would be occupied if object was dropped at
+     *         the given location. Should return null if no estimate is found,
+     *         or if this target doesn't provide estimations.
+     */
+    Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle);
 }
diff --git a/src/com/android/launcher/Folder.java b/src/com/android/launcher/Folder.java
index bcbccf7..fb4e8d6 100644
--- a/src/com/android/launcher/Folder.java
+++ b/src/com/android/launcher/Folder.java
@@ -24,7 +24,7 @@
 import android.widget.Button;
 import android.widget.LinearLayout;
 import android.widget.AbsListView;
-import android.widget.ListAdapter;
+import android.widget.BaseAdapter;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.AdapterView.OnItemLongClickListener;
 
@@ -122,10 +122,14 @@
      *
      * @param adapter The list of applications to display in the folder.
      */
-    void setContentAdapter(ListAdapter adapter) {
+    void setContentAdapter(BaseAdapter adapter) {
         mContent.setAdapter(adapter);
     }
 
+    void notifyDataSetChanged() {
+        ((BaseAdapter) mContent.getAdapter()).notifyDataSetChanged();
+    }
+
     void setLauncher(Launcher launcher) {
         mLauncher = launcher;
     }
diff --git a/src/com/android/launcher/FolderIcon.java b/src/com/android/launcher/FolderIcon.java
index 667f92e..a56101d 100644
--- a/src/com/android/launcher/FolderIcon.java
+++ b/src/com/android/launcher/FolderIcon.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -69,6 +70,10 @@
                 && item.container != mInfo.id;
     }
 
+    public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+        return null;
+    }
+
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
         final ApplicationInfo item = (ApplicationInfo) dragInfo;
         // TODO: update open folder that is looking at this data
diff --git a/src/com/android/launcher/GesturesActivity.java b/src/com/android/launcher/GesturesActivity.java
new file mode 100644
index 0000000..a112e1b
--- /dev/null
+++ b/src/com/android/launcher/GesturesActivity.java
@@ -0,0 +1,310 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher;
+
+import android.app.ListActivity;
+import android.app.Dialog;
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.os.AsyncTask;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+import android.widget.AdapterView;
+import android.widget.Toast;
+import android.widget.EditText;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.gesture.GestureLibrary;
+import android.gesture.Gesture;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.text.TextUtils;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Collections;
+import java.util.Map;
+
+public class GesturesActivity extends ListActivity {
+    private static final int MENU_ID_RENAME = 1;
+    private static final int MENU_ID_REMOVE = 2;
+
+    private static final int DIALOG_RENAME_GESTURE = 1;
+
+    private final Comparator<ApplicationInfo> mSorter =
+            new LauncherModel.ApplicationInfoComparator();
+
+    private GesturesAdapter mAdapter;
+    private GestureLibrary mStore;
+    private GesturesLoadTask mTask;
+    private TextView mEmpty;
+
+    private Dialog mRenameDialog;
+    private EditText mInput;
+    private ApplicationInfo mCurrentRenameInfo;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.gestures_settings);
+
+        mAdapter = new GesturesAdapter(this);
+        setListAdapter(mAdapter);
+
+        mStore = Launcher.getGestureLibrary();
+        mEmpty = (TextView) findViewById(android.R.id.empty);
+        mTask = (GesturesLoadTask) new GesturesLoadTask().execute();
+
+        registerForContextMenu(getListView());
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (mTask != null && mTask.getStatus() != GesturesLoadTask.Status.FINISHED) {
+            mTask.cancel(true);
+            mTask = null;
+        }
+
+        cleanupRenameDialog();
+    }
+
+    private void checkForEmpty() {
+        if (mAdapter.getCount() == 0) {
+            mEmpty.setText(R.string.gestures_empty);
+        }
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v,
+            ContextMenu.ContextMenuInfo menuInfo) {
+
+        super.onCreateContextMenu(menu, v, menuInfo);
+
+        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
+        menu.setHeaderTitle(((TextView) info.targetView).getText());
+
+        menu.add(0, MENU_ID_RENAME, 0, R.string.gestures_rename);
+        menu.add(0, MENU_ID_REMOVE, 0, R.string.gestures_delete);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)
+                item.getMenuInfo();
+        final ApplicationInfo info = (ApplicationInfo) menuInfo.targetView.getTag();
+
+        switch (item.getItemId()) {
+            case MENU_ID_RENAME:
+                renameGesture(info);
+                return true;
+            case MENU_ID_REMOVE:
+                deleteGesture(info);
+                return true;
+        }
+
+        return super.onContextItemSelected(item);
+    }
+
+    private void renameGesture(ApplicationInfo info) {
+        mCurrentRenameInfo = info;
+        showDialog(DIALOG_RENAME_GESTURE);
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id) {
+        if (id == DIALOG_RENAME_GESTURE) {
+            return createRenameDialog();
+        }
+        return super.onCreateDialog(id);
+    }
+
+    @Override
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        super.onPrepareDialog(id, dialog);
+        if (id == DIALOG_RENAME_GESTURE) {
+            mInput.setText(mCurrentRenameInfo.title);
+        }
+    }
+
+    private Dialog createRenameDialog() {
+        final View layout = View.inflate(this, R.layout.rename_folder, null);
+        mInput = (EditText) layout.findViewById(R.id.folder_name);
+        ((TextView) layout.findViewById(R.id.label)).setText(R.string.gestures_rename_label);
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setIcon(0);
+        builder.setTitle(getString(R.string.gestures_rename_title));
+        builder.setCancelable(true);
+        builder.setOnCancelListener(new Dialog.OnCancelListener() {
+            public void onCancel(DialogInterface dialog) {
+                cleanupRenameDialog();
+            }
+        });
+        builder.setNegativeButton(getString(R.string.cancel_action),
+            new Dialog.OnClickListener() {
+                public void onClick(DialogInterface dialog, int which) {
+                    cleanupRenameDialog();
+                }
+            }
+        );
+        builder.setPositiveButton(getString(R.string.rename_action),
+            new Dialog.OnClickListener() {
+                public void onClick(DialogInterface dialog, int which) {
+                    changeGestureName();
+                }
+            }
+        );
+        builder.setView(layout);
+        return builder.create();
+    }
+
+    private void changeGestureName() {
+        final String name = mInput.getText().toString();
+        if (!TextUtils.isEmpty(name)) {
+            mCurrentRenameInfo.title = mInput.getText();
+            LauncherModel.updateGestureInDatabase(this, mCurrentRenameInfo);
+        }
+    }
+
+    private void cleanupRenameDialog() {
+        if (mRenameDialog != null) {
+            mRenameDialog.dismiss();
+            mRenameDialog = null;
+            mInput = null;
+        }
+    }
+
+    private void deleteGesture(ApplicationInfo info) {
+        mStore.removeEntry(String.valueOf(info.id));
+        // TODO: On a thread?
+        mStore.save();
+
+        final GesturesActivity.GesturesAdapter adapter = mAdapter;
+        adapter.setNotifyOnChange(false);
+        adapter.remove(info);
+        adapter.sort(mSorter);
+        checkForEmpty();
+        adapter.notifyDataSetChanged();
+        
+        LauncherModel.deleteGestureFromDatabase(this, info);
+
+        Toast.makeText(this, R.string.gestures_delete_success, Toast.LENGTH_SHORT).show();
+    }
+
+    private class GesturesLoadTask extends AsyncTask<Void, ApplicationInfo, Boolean> {
+        private int mThumbnailSize;
+        private int mThumbnailInset;
+        private int mPathColor;
+
+        @Override
+        protected void onPreExecute() {
+            super.onPreExecute();
+
+            final Resources resources = getResources();
+            mPathColor = resources.getColor(R.color.gesture_color);
+            mThumbnailInset = (int) resources.getDimension(R.dimen.gesture_thumbnail_inset);
+            mThumbnailSize = (int) resources.getDimension(R.dimen.gesture_thumbnail_size);
+        }
+
+        protected Boolean doInBackground(Void... params) {
+            if (isCancelled()) return Boolean.FALSE;
+
+            final GestureLibrary store = mStore;
+
+            if (store.load()) {
+                final LauncherModel model = Launcher.getModel();
+
+                for (String name : store.getGestureEntries()) {
+                    final Gesture gesture = store.getGestures(name).get(0);
+                    final Bitmap bitmap = gesture.toBitmap(mThumbnailSize, mThumbnailSize,
+                            mThumbnailInset, mPathColor);
+                    final ApplicationInfo info = model.queryGesture(GesturesActivity.this, name);
+
+                    mAdapter.addBitmap(info.id, bitmap);
+                    publishProgress(info);
+                }
+
+                return Boolean.TRUE;
+            }
+
+            return Boolean.FALSE;
+        }
+
+        @Override
+        protected void onProgressUpdate(ApplicationInfo... values) {
+            super.onProgressUpdate(values);
+
+            final GesturesActivity.GesturesAdapter adapter = mAdapter;
+            adapter.setNotifyOnChange(false);
+
+            for (ApplicationInfo info : values) {
+                adapter.add(info);
+            }
+
+            adapter.sort(mSorter);
+            adapter.notifyDataSetChanged();
+        }
+
+        @Override
+        protected void onPostExecute(Boolean aBoolean) {
+            super.onPostExecute(aBoolean);
+            checkForEmpty();
+        }
+    }
+
+    private class GesturesAdapter extends ArrayAdapter<ApplicationInfo> {
+        private final LayoutInflater mInflater;
+        private final Map<Long, Drawable> mThumbnails = Collections.synchronizedMap(
+                new HashMap<Long, Drawable>());
+
+        public GesturesAdapter(Context context) {
+            super(context, 0);
+            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        void addBitmap(Long id, Bitmap bitmap) {
+            mThumbnails.put(id, new BitmapDrawable(bitmap));
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.gestures_settings_item, parent, false);
+            }
+
+            final ApplicationInfo info = getItem(position);
+            final TextView label = (TextView) convertView;
+
+            label.setTag(info);
+            label.setText(info.title);
+            label.setCompoundDrawablesWithIntrinsicBounds(info.icon, null,
+                    mThumbnails.get(info.id), null);
+
+            return convertView;
+        }
+    }
+}
diff --git a/src/com/android/launcher/GesturesConstants.java b/src/com/android/launcher/GesturesConstants.java
new file mode 100644
index 0000000..3151ea3
--- /dev/null
+++ b/src/com/android/launcher/GesturesConstants.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher;
+
+interface GesturesConstants {
+    final double PREDICTION_THRESHOLD = 1.0;
+    final String STORE_NAME = "gestures";
+    final long MATCH_DELAY = 370;
+    final float LENGTH_THRESHOLD = 120.0f;
+    int PATH_SAMPLE_COUNT = 10;
+}
diff --git a/src/com/android/launcher/GesturesPanel.java b/src/com/android/launcher/GesturesPanel.java
new file mode 100644
index 0000000..ee39613
--- /dev/null
+++ b/src/com/android/launcher/GesturesPanel.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package com.android.launcher;
+
+import android.widget.RelativeLayout;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+
+public class GesturesPanel extends RelativeLayout {
+    public GesturesPanel(Context context) {
+        super(context);
+    }
+
+    public GesturesPanel(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return true;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            ((Launcher) mContext).hideGesturesPanel();
+            return true;
+        }
+
+        return super.dispatchKeyEvent(event);
+    }
+}
diff --git a/src/com/android/launcher/InstallShortcutReceiver.java b/src/com/android/launcher/InstallShortcutReceiver.java
index a1e954a..fd2789c 100644
--- a/src/com/android/launcher/InstallShortcutReceiver.java
+++ b/src/com/android/launcher/InstallShortcutReceiver.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.ContentResolver;
 import android.database.Cursor;
+import android.widget.Toast;
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
     private final int[] mCoordinates = new int[2];
@@ -37,6 +38,8 @@
     }
 
     private boolean installShortcut(Context context, Intent data, int screen) {
+        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+
         if (findEmptyCell(context, mCoordinates, screen)) {
             CellLayout.CellInfo cell = new CellLayout.CellInfo();
             cell.cellX = mCoordinates[0];
@@ -44,7 +47,6 @@
             cell.screen = screen;
 
             Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
-            String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
 
             if (intent.getAction() == null) {
                 intent.setAction(Intent.ACTION_VIEW);
@@ -55,9 +57,17 @@
             boolean duplicate = data.getBooleanExtra(Launcher.EXTRA_SHORTCUT_DUPLICATE, true);
             if (duplicate || !LauncherModel.shortcutExists(context, name, intent)) {
                 Launcher.addShortcut(context, data, cell, true);
+                Toast.makeText(context, context.getString(R.string.shortcut_installed, name),
+                        Toast.LENGTH_SHORT).show();
+            } else {
+                Toast.makeText(context, context.getString(R.string.shortcut_duplicate, name),
+                        Toast.LENGTH_SHORT).show();
             }
 
             return true;
+        } else {
+            Toast.makeText(context, context.getString(R.string.out_of_space),
+                    Toast.LENGTH_SHORT).show();
         }
 
         return false;
diff --git a/src/com/android/launcher/ItemInfo.java b/src/com/android/launcher/ItemInfo.java
index 51449a7..71cee18 100644
--- a/src/com/android/launcher/ItemInfo.java
+++ b/src/com/android/launcher/ItemInfo.java
@@ -76,6 +76,11 @@
      */
     int spanY = 1;
 
+    /**
+     * Indicates whether the item is a gesture.
+     */
+    boolean isGesture = false;
+
     ItemInfo() {
     }
 
@@ -96,13 +101,15 @@
      * @param values
      */
     void onAddToDatabase(ContentValues values) { 
-        values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType);
-        values.put(LauncherSettings.Favorites.CONTAINER, container);
-        values.put(LauncherSettings.Favorites.SCREEN, screen);
-        values.put(LauncherSettings.Favorites.CELLX, cellX);
-        values.put(LauncherSettings.Favorites.CELLY, cellY);
-        values.put(LauncherSettings.Favorites.SPANX, spanX);
-        values.put(LauncherSettings.Favorites.SPANY, spanY);
+        values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType);
+        if (!isGesture) {
+            values.put(LauncherSettings.Favorites.CONTAINER, container);
+            values.put(LauncherSettings.Favorites.SCREEN, screen);
+            values.put(LauncherSettings.Favorites.CELLX, cellX);
+            values.put(LauncherSettings.Favorites.CELLY, cellY);
+            values.put(LauncherSettings.Favorites.SPANX, spanX);
+            values.put(LauncherSettings.Favorites.SPANY, spanY);
+        }
     }
 
     static void writeBitmap(ContentValues values, Bitmap bitmap) {
diff --git a/src/com/android/launcher/Launcher.java b/src/com/android/launcher/Launcher.java
index 7dd3418..88c411a 100644
--- a/src/com/android/launcher/Launcher.java
+++ b/src/com/android/launcher/Launcher.java
@@ -20,6 +20,7 @@
 import android.app.AlertDialog;
 import android.app.Application;
 import android.app.Dialog;
+import android.app.IWallpaperService;
 import android.app.SearchManager;
 import android.app.StatusBarManager;
 import android.content.ActivityNotFoundException;
@@ -35,31 +36,30 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.PorterDuff;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.TransitionDrawable;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Message;
 import android.os.MessageQueue;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.Message;
-import android.provider.*;
-import android.telephony.PhoneNumberUtils;
+import android.provider.LiveFolders;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
-import android.util.Log;
 import static android.util.Log.*;
 import android.view.Display;
 import android.view.KeyEvent;
@@ -68,16 +68,25 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.MotionEvent;
+import android.view.Gravity;
 import android.view.View.OnLongClickListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
 import android.widget.GridView;
 import android.widget.SlidingDrawer;
-import android.app.IWallpaperService;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+import android.widget.ViewSwitcher;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
+import android.gesture.GestureOverlayView;
+import android.gesture.GestureLibraries;
+import android.gesture.GestureLibrary;
+import android.gesture.Gesture;
+import android.gesture.Prediction;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -94,6 +103,7 @@
     private static final boolean PROFILE_DRAWER = false;
     private static final boolean PROFILE_ROTATE = false;
     private static final boolean DEBUG_USER_INTERFACE = false;
+    private static final boolean DEBUG_GESTURES = false;
 
     private static final int WALLPAPER_SCREENS_SPAN = 2;
 
@@ -102,7 +112,8 @@
     private static final int MENU_WALLPAPER_SETTINGS = MENU_ADD + 1;
     private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1;
     private static final int MENU_NOTIFICATIONS = MENU_SEARCH + 1;
-    private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1;
+    private static final int MENU_GESTURES = MENU_NOTIFICATIONS + 1;
+    private static final int MENU_SETTINGS = MENU_GESTURES + 1;
 
     private static final int REQUEST_CREATE_SHORTCUT = 1;
     private static final int REQUEST_CREATE_LIVE_FOLDER = 4;
@@ -111,6 +122,9 @@
     private static final int REQUEST_PICK_SHORTCUT = 7;
     private static final int REQUEST_PICK_LIVE_FOLDER = 8;
     private static final int REQUEST_PICK_APPWIDGET = 9;
+    private static final int REQUEST_PICK_GESTURE_ACTION = 10;
+    private static final int REQUEST_CREATE_GESTURE_ACTION = 11;
+    private static final int REQUEST_CREATE_GESTURE_APPLICATION_ACTION = 12;
 
     static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
 
@@ -120,10 +134,10 @@
     static final int SCREEN_COUNT = 3;
     static final int DEFAULT_SCREN = 1;
     static final int NUMBER_CELLS_X = 4;
-    static final int NUMBER_CELLS_Y = 4;    
+    static final int NUMBER_CELLS_Y = 4;
 
     private static final int DIALOG_CREATE_SHORTCUT = 1;
-    static final int DIALOG_RENAME_FOLDER = 2;    
+    static final int DIALOG_RENAME_FOLDER = 2;
 
     private static final String PREFERENCES = "launcher";
     private static final String KEY_LOCALE = "locale";
@@ -156,6 +170,8 @@
     private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder";
     // Type: long
     private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id";
+    // Type: Gesture (Parcelable)
+    private static final String RUNTIME_STATE_PENDING_GESTURE = "launcher.gesture";
 
     private static final LauncherModel sModel = new LauncherModel();
 
@@ -166,20 +182,21 @@
 
     private static WallpaperIntentReceiver sWallpaperReceiver;
 
+    private static GestureLibrary sLibrary;
+
     private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver();
     private final ContentObserver mObserver = new FavoritesChangeObserver();
-    private final ContentObserver mAppWidgetResetObserver = new AppWidgetResetObserver();
 
     private LayoutInflater mInflater;
 
     private DragLayer mDragLayer;
     private Workspace mWorkspace;
-    
+
     private AppWidgetManager mAppWidgetManager;
     private LauncherAppWidgetHost mAppWidgetHost;
-    
+
     static final int APPWIDGET_HOST_ID = 1024;
-    
+
     private CellLayout.CellInfo mAddItemCellInfo;
     private CellLayout.CellInfo mMenuAddInfo;
     private final int[] mCellCoordinates = new int[2];
@@ -204,17 +221,33 @@
     private Bundle mSavedInstanceState;
 
     private DesktopBinder mBinder;
-    
+
+    private View mGesturesPanel;
+    private GestureOverlayView mGesturesOverlay;
+    private ViewSwitcher mGesturesPrompt;
+    private ImageView mGesturesAdd;
+    private PopupWindow mGesturesWindow;
+    private Launcher.GesturesProcessor mGesturesProcessor;
+    private Gesture mCurrentGesture;
+    private GesturesAction mGesturesAction;
+    private boolean mHideGesturesPanel;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mInflater = getLayoutInflater();
-        
+
+        if (sLibrary == null) {
+            // The context is not kept by the library so it's safe to do this
+            sLibrary = GestureLibraries.fromPrivateFile(Launcher.this,
+                    GesturesConstants.STORE_NAME);
+        }
+
         mAppWidgetManager = AppWidgetManager.getInstance(this);
-        
+
         mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
         mAppWidgetHost.startListening();
-        
+
         if (PROFILE_STARTUP) {
             android.os.Debug.startMethodTracing("/sdcard/launcher");
         }
@@ -243,7 +276,7 @@
         mDefaultKeySsb = new SpannableStringBuilder();
         Selection.setSelection(mDefaultKeySsb, 0);
     }
-    
+
     private void checkForLocaleChange() {
         final SharedPreferences preferences = getSharedPreferences(PREFERENCES, MODE_PRIVATE);
         final Configuration configuration = getResources().getConfiguration();
@@ -305,19 +338,25 @@
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        mWaitingForResult = false;
+
         // The pattern used here is that a user PICKs a specific application,
         // which, depending on the target, might need to CREATE the actual target.
-        
+
         // 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 && mAddItemCellInfo != null) {
+
+        if (resultCode == RESULT_OK && (mAddItemCellInfo != null ||
+                ((requestCode == REQUEST_PICK_GESTURE_ACTION ||
+                requestCode == REQUEST_CREATE_GESTURE_ACTION ||
+                requestCode == REQUEST_CREATE_GESTURE_APPLICATION_ACTION) && mCurrentGesture != null))) {
+
             switch (requestCode) {
                 case REQUEST_PICK_APPLICATION:
                     completeAddApplication(this, data, mAddItemCellInfo, !mDesktopLocked);
                     break;
                 case REQUEST_PICK_SHORTCUT:
-                    addShortcut(data);
+                    processShortcut(data, REQUEST_PICK_APPLICATION, REQUEST_CREATE_SHORTCUT);
                     break;
                 case REQUEST_CREATE_SHORTCUT:
                     completeAddShortcut(data, mAddItemCellInfo, !mDesktopLocked);
@@ -334,6 +373,16 @@
                 case REQUEST_CREATE_APPWIDGET:
                     completeAddAppWidget(data, mAddItemCellInfo, !mDesktopLocked);
                     break;
+                case REQUEST_PICK_GESTURE_ACTION:
+                    processShortcut(data, REQUEST_CREATE_GESTURE_APPLICATION_ACTION,
+                            REQUEST_CREATE_GESTURE_ACTION);
+                    break;
+                case REQUEST_CREATE_GESTURE_ACTION:
+                    completeCreateGesture(data, true);
+                    break;
+                case REQUEST_CREATE_GESTURE_APPLICATION_ACTION:
+                    completeCreateGesture(data, false);
+                    break;
             }
         } else if (requestCode == REQUEST_PICK_APPWIDGET &&
                 resultCode == RESULT_CANCELED && data != null) {
@@ -343,7 +392,6 @@
                 mAppWidgetHost.deleteAppWidgetId(appWidgetId);
             }
         }
-        mWaitingForResult = false;
     }
 
     @Override
@@ -353,21 +401,37 @@
         if (mRestoring) {
             startLoaders();
         }
+
+        // Make sure that the search gadget (if any) is in its normal place.
+        stopSearch();
     }
 
     @Override
     protected void onPause() {
         super.onPause();
-        closeDrawer(false);        
+        if (mGesturesWindow != null) {
+            mGesturesWindow.setAnimationStyle(0);
+            mGesturesWindow.update();
+        }
+        closeDrawer(false);
     }
-    
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        if (mHideGesturesPanel) {
+            mHideGesturesPanel = false;
+            hideGesturesPanel();
+        }
+    }
+
     @Override
     public Object onRetainNonConfigurationInstance() {
         // Flag any binder to stop early before switching
         if (mBinder != null) {
             mBinder.mTerminate = true;
         }
-        
+
         if (PROFILE_ROTATE) {
             android.os.Debug.startMethodTracing("/sdcard/launcher-rotate");
         }
@@ -387,45 +451,29 @@
             boolean gotKey = TextKeyListener.getInstance().onKeyDown(mWorkspace, mDefaultKeySsb,
                     keyCode, event);
             if (gotKey && mDefaultKeySsb != null && mDefaultKeySsb.length() > 0) {
-                // something usable has been typed - dispatch it now.
-                final String str = mDefaultKeySsb.toString();
-
-                boolean isDialable = true;
-                final int count = str.length();
-                for (int i = 0; i < count; i++) {
-                    if (!PhoneNumberUtils.isReallyDialable(str.charAt(i))) {
-                        isDialable = false;
-                        break;
-                    }
-                }
-                Intent intent;
-                if (isDialable) {
-                    intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", str, null));
-                } else {
-                    intent = new Intent(Contacts.Intents.UI.FILTER_CONTACTS_ACTION);
-                    intent.putExtra(Contacts.Intents.UI.FILTER_TEXT_EXTRA_KEY, str);
-                }
-
-                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-
-                try {
-                    startActivity(intent);
-                } catch (android.content.ActivityNotFoundException ex) {
-                    // Oh well... no one knows how to filter/dial. Life goes on.
-                }
-
-                mDefaultKeySsb.clear();
-                mDefaultKeySsb.clearSpans();
-                Selection.setSelection(mDefaultKeySsb, 0);
-
-                return true;
+                // something usable has been typed - start a search
+                // the typed text will be retrieved and cleared by
+                // showSearchDialog()
+                // If there are multiple keystrokes before the search dialog takes focus,
+                // onSearchRequested() will be called for every keystroke,
+                // but it is idempotent, so it's fine.
+                return onSearchRequested();
             }
         }
 
         return handled;
     }
 
+    private String getTypedText() {
+        return mDefaultKeySsb.toString();
+    }
+
+    private void clearTypedText() {
+        mDefaultKeySsb.clear();
+        mDefaultKeySsb.clearSpans();
+        Selection.setSelection(mDefaultKeySsb, 0);
+    }
+
     /**
      * Restores the previous state, if it exists.
      *
@@ -464,6 +512,8 @@
             mFolderInfo = sModel.getFolderById(this, id);
             mRestoring = true;
         }
+
+        mCurrentGesture = (Gesture) savedState.get(RUNTIME_STATE_PENDING_GESTURE);
     }
 
     /**
@@ -495,7 +545,7 @@
         drawer.setOnDrawerCloseListener(drawerManager);
         drawer.setOnDrawerScrollListener(drawerManager);
 
-        grid.setTextFilterEnabled(true);
+        grid.setTextFilterEnabled(false);
         grid.setDragger(dragLayer);
         grid.setLauncher(this);
 
@@ -511,6 +561,68 @@
         dragLayer.setIgnoredDropTarget(grid);
         dragLayer.setDragScoller(workspace);
         dragLayer.setDragListener(deleteZone);
+
+        mGesturesPanel = mInflater.inflate(R.layout.gestures, mDragLayer, false);
+        final View gesturesPanel = mGesturesPanel;
+
+        mGesturesPrompt = (ViewSwitcher) gesturesPanel.findViewById(R.id.gestures_actions);
+        mGesturesAction = new GesturesAction();
+
+        mGesturesPrompt.getChildAt(0).setOnClickListener(mGesturesAction);
+        mGesturesPrompt.getChildAt(1).setOnClickListener(mGesturesAction);
+
+        mGesturesAdd = (ImageView) gesturesPanel.findViewById(R.id.gestures_add);
+        final ImageView gesturesAdd = mGesturesAdd;
+        gesturesAdd.setAlpha(128);
+        gesturesAdd.setEnabled(false);
+        gesturesAdd.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                createGesture();
+            }
+        });
+
+        mGesturesOverlay = (GestureOverlayView) gesturesPanel.findViewById(R.id.gestures_overlay);
+        mGesturesProcessor = new GesturesProcessor();
+
+        final GestureOverlayView overlay = mGesturesOverlay;
+        overlay.setFadeOffset(GesturesConstants.MATCH_DELAY);
+        overlay.addOnGestureListener(mGesturesProcessor);
+        overlay.getGesturePaint().setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
+    }
+
+    private void createGesture() {
+        mCurrentGesture = mGesturesOverlay.getGesture();
+        mWaitingForResult = true;
+        pickShortcut(REQUEST_PICK_GESTURE_ACTION, R.string.title_select_shortcut);
+    }
+
+    private void completeCreateGesture(Intent data, boolean isShortcut) {
+        ApplicationInfo info;
+
+        if (isShortcut) {
+            info = infoFromShortcutIntent(this, data);
+        } else {
+            info = infoFromApplicationIntent(this, data);
+        }
+
+        boolean success = false;
+        if (info != null) {
+            info.isGesture = true;
+
+            if (LauncherModel.addGestureToDatabase(this, info, false)) {
+                mGesturesProcessor.addGesture(String.valueOf(info.id), mCurrentGesture);
+                mGesturesProcessor.update(info, mCurrentGesture);
+                Toast.makeText(this, getString(R.string.gestures_created, info.title),
+                        Toast.LENGTH_SHORT).show();
+                success = true;
+            }
+        }
+
+        if (!success) {
+            Toast.makeText(this, getString(R.string.gestures_failed), Toast.LENGTH_SHORT).show();
+        }
+
+        mCurrentGesture = null;
     }
 
     /**
@@ -561,33 +673,41 @@
         cellInfo.screen = mWorkspace.getCurrentScreen();
         if (!findSingleSlot(cellInfo)) return;
 
-        // Find details for this application
+        final ApplicationInfo info = infoFromApplicationIntent(context, data);
+        if (info != null) {
+            mWorkspace.addApplicationShortcut(info, cellInfo, insertAtFirst);
+        }
+    }
+
+    private static ApplicationInfo infoFromApplicationIntent(Context context, Intent data) {
         ComponentName component = data.getComponent();
         PackageManager packageManager = context.getPackageManager();
         ActivityInfo activityInfo = null;
         try {
             activityInfo = packageManager.getActivityInfo(component, 0 /* no flags */);
         } catch (NameNotFoundException e) {
-            Log.e(LOG_TAG, "Couldn't find ActivityInfo for selected application", e);
+            e(LOG_TAG, "Couldn't find ActivityInfo for selected application", e);
         }
-        
+
         if (activityInfo != null) {
             ApplicationInfo itemInfo = new ApplicationInfo();
-            
+
             itemInfo.title = activityInfo.loadLabel(packageManager);
             if (itemInfo.title == null) {
                 itemInfo.title = activityInfo.name;
             }
-            
+
             itemInfo.setActivity(component, Intent.FLAG_ACTIVITY_NEW_TASK |
                     Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
             itemInfo.icon = activityInfo.loadIcon(packageManager);
             itemInfo.container = ItemInfo.NO_ID;
 
-            mWorkspace.addApplicationShortcut(itemInfo, cellInfo, insertAtFirst);
+            return itemInfo;
         }
+
+        return null;
     }
-    
+
     /**
      * Add a shortcut to the workspace.
      *
@@ -599,7 +719,7 @@
             boolean insertAtFirst) {
         cellInfo.screen = mWorkspace.getCurrentScreen();
         if (!findSingleSlot(cellInfo)) return;
-        
+
         final ApplicationInfo info = addShortcut(this, data, cellInfo, false);
 
         if (!mRestoring) {
@@ -612,7 +732,7 @@
         }
     }
 
-    
+
     /**
      * Add a widget to the workspace.
      *
@@ -624,15 +744,15 @@
 
         Bundle extras = data.getExtras();
         int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
-        
+
         d(LOG_TAG, "dumping extras content="+extras.toString());
-        
+
         AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
-        
+
         // Calculate the grid spans needed to fit this widget
         CellLayout layout = (CellLayout) mWorkspace.getChildAt(cellInfo.screen);
         int[] spans = layout.rectToCell(appWidgetInfo.minWidth, appWidgetInfo.minHeight);
-        
+
         // Try finding open space on Launcher screen
         final int[] xy = mCellCoordinates;
         if (!findSlot(cellInfo, xy, spans[0], spans[1])) return;
@@ -641,34 +761,42 @@
         LauncherAppWidgetInfo launcherInfo = new LauncherAppWidgetInfo(appWidgetId);
         launcherInfo.spanX = spans[0];
         launcherInfo.spanY = spans[1];
-        
+
         LauncherModel.addItemToDatabase(this, launcherInfo,
                 LauncherSettings.Favorites.CONTAINER_DESKTOP,
                 mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
 
         if (!mRestoring) {
             sModel.addDesktopAppWidget(launcherInfo);
-            
+
             // Perform actual inflation because we're live
             launcherInfo.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
-            
+
             launcherInfo.hostView.setAppWidget(appWidgetId, appWidgetInfo);
             launcherInfo.hostView.setTag(launcherInfo);
-            
+
             mWorkspace.addInCurrentScreen(launcherInfo.hostView, xy[0], xy[1],
                     launcherInfo.spanX, launcherInfo.spanY, insertAtFirst);
         } else if (sModel.isDesktopLoaded()) {
             sModel.addDesktopAppWidget(launcherInfo);
         }
     }
-    
+
     public LauncherAppWidgetHost getAppWidgetHost() {
         return mAppWidgetHost;
     }
-    
+
     static ApplicationInfo addShortcut(Context context, Intent data,
             CellLayout.CellInfo cellInfo, boolean notify) {
 
+        final ApplicationInfo info = infoFromShortcutIntent(context, data);
+        LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
+
+        return info;
+    }
+
+    private static ApplicationInfo infoFromShortcutIntent(Context context, Intent data) {
         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
         Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
@@ -676,7 +804,7 @@
         Drawable icon = null;
         boolean filtered = false;
         boolean customIcon = false;
-        Intent.ShortcutIconResource iconResource = null;
+        ShortcutIconResource iconResource = null;
 
         if (bitmap != null) {
             icon = new FastBitmapDrawable(Utilities.createBitmapThumbnail(bitmap, context));
@@ -684,9 +812,9 @@
             customIcon = true;
         } else {
             Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
-            if (extra != null && extra instanceof Intent.ShortcutIconResource) {
+            if (extra != null && extra instanceof ShortcutIconResource) {
                 try {
-                    iconResource = (Intent.ShortcutIconResource) extra;
+                    iconResource = (ShortcutIconResource) extra;
                     final PackageManager packageManager = context.getPackageManager();
                     Resources resources = packageManager.getResourcesForApplication(
                             iconResource.packageName);
@@ -710,8 +838,6 @@
         info.customIcon = customIcon;
         info.iconResource = iconResource;
 
-        LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
-                cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify);
         return info;
     }
 
@@ -739,15 +865,19 @@
                 // An exception is thrown if the dialog is not visible, which is fine
             }
 
-            // If we are already in front we go back to the default screen,
-            // otherwise we don't
             if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) !=
                     Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) {
-                if (!mWorkspace.isDefaultScreenShowing()) {
-                    mWorkspace.moveToDefaultScreen();
+
+                if (mGesturesPanel != null && mDragLayer.getWindowVisibility() == View.VISIBLE) {
+                    SearchManager searchManager =
+                            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+                    if (!searchManager.isVisible()) {
+                        onHomeKeyPressed();
+                    }
                 }
                 closeDrawer();
-                View v = getWindow().peekDecorView();
+
+                final View v = getWindow().peekDecorView();
                 if (v != null && v.getWindowToken() != null) {
                     InputMethodManager imm = (InputMethodManager)getSystemService(
                             INPUT_METHOD_SERVICE);
@@ -759,6 +889,74 @@
         }
     }
 
+    private void onHomeKeyPressed() {
+        if (mGesturesWindow == null || !mGesturesWindow.isShowing()) {
+            showGesturesPanel();
+        } else {
+            hideGesturesPanel();
+        }
+    }
+
+    private void showGesturesPanel() {
+        resetGesturesPrompt();
+
+        mGesturesAdd.setEnabled(false);
+        mGesturesAdd.setAlpha(128);
+
+        mGesturesOverlay.clear(false);
+
+        PopupWindow window;
+        if (mGesturesWindow == null) {
+            mGesturesWindow = new PopupWindow(this);
+            window = mGesturesWindow;
+            window.setFocusable(true);
+            window.setTouchable(true);
+            window.setBackgroundDrawable(null);
+            window.setContentView(mGesturesPanel);
+        } else {
+            window = mGesturesWindow;
+        }
+        window.setAnimationStyle(com.android.internal.R.style.Animation_SlidingCard);
+
+        final int[] xy = new int[2];
+        final DragLayer dragLayer = mDragLayer;
+        dragLayer.getLocationOnScreen(xy);
+
+        window.setWidth(dragLayer.getWidth());
+        window.setHeight(dragLayer.getHeight() - 1);
+        window.showAtLocation(dragLayer, Gravity.TOP | Gravity.LEFT, xy[0], xy[1] + 1);
+    }
+
+    private void resetGesturesPrompt() {
+        mGesturesAction.intent = null;
+        final TextView prompt = (TextView) mGesturesPrompt.getCurrentView();
+        prompt.setText(R.string.gestures_instructions);
+        prompt.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+        prompt.setClickable(false);
+    }
+
+    private void resetGesturesNextPrompt() {
+        mGesturesAction.intent = null;
+        setGesturesNextPrompt(null, getString(R.string.gestures_instructions));
+        mGesturesPrompt.getNextView().setClickable(false);
+    }
+
+    private void setGesturesNextPrompt(Drawable icon, CharSequence title) {
+        final TextView prompt = (TextView) mGesturesPrompt.getNextView();
+        prompt.setText(title);
+        prompt.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+        prompt.setClickable(true);
+        mGesturesPrompt.showNext();
+    }
+
+    void hideGesturesPanel() {
+        if (mGesturesWindow != null) {
+            mGesturesWindow.setAnimationStyle(com.android.internal.R.style.Animation_SlidingCard);
+            mGesturesWindow.update();
+            mGesturesWindow.dismiss();
+        }
+    }
+
     @Override
     protected void onRestoreInstanceState(Bundle savedInstanceState) {
         // Do not call super here
@@ -782,9 +980,11 @@
             super.onSaveInstanceState(outState);
         }
 
-        if (mDrawer.isOpened()) {
+        // When the drawer is opened and we are saving the state because of a
+        // configuration change
+        if (mDrawer.isOpened() && getChangingConfigurations() != 0) {
             outState.putBoolean(RUNTIME_STATE_ALL_APPS_FOLDER, true);
-        }        
+        }
 
         if (mAddItemCellInfo != null && mAddItemCellInfo.valid && mWaitingForResult) {
             final CellLayout.CellInfo addItemCellInfo = mAddItemCellInfo;
@@ -805,6 +1005,10 @@
             outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true);
             outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id);
         }
+
+        if (mCurrentGesture != null && mWaitingForResult) {
+            outState.putParcelable(RUNTIME_STATE_PENDING_GESTURE, mCurrentGesture);
+        }
     }
 
     @Override
@@ -812,7 +1016,7 @@
         mDestroyed = true;
 
         super.onDestroy();
-        
+
         try {
             mAppWidgetHost.stopListening();
         } catch (NullPointerException ex) {
@@ -827,7 +1031,6 @@
         sModel.abortLoaders();
 
         getContentResolver().unregisterContentObserver(mObserver);
-        getContentResolver().unregisterContentObserver(mAppWidgetResetObserver);
         unregisterReceiver(mApplicationsReceiver);
     }
 
@@ -838,13 +1041,74 @@
     }
 
     @Override
-    public void startSearch(String initialQuery, boolean selectInitialQuery, 
+    public void startSearch(String initialQuery, boolean selectInitialQuery,
             Bundle appSearchData, boolean globalSearch) {
+
+        closeDrawer(false);
+
+        // Slide the search widget to the top, if it's on the current screen,
+        // otherwise show the search dialog immediately.
+        Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+        if (searchWidget == null) {
+            showSearchDialog(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+        } else {
+            searchWidget.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+            // show the currently typed text in the search widget while sliding
+            searchWidget.setQuery(getTypedText());
+        }
+    }
+
+    /**
+     * Show the search dialog immediately, without changing the search widget.
+     *
+     * @see Activity#startSearch(String, boolean, android.os.Bundle, boolean)
+     */
+    void showSearchDialog(String initialQuery, boolean selectInitialQuery,
+            Bundle appSearchData, boolean globalSearch) {
+
+        if (initialQuery == null) {
+            // Use any text typed in the launcher as the initial query
+            initialQuery = getTypedText();
+            clearTypedText();
+        }
         if (appSearchData == null) {
             appSearchData = new Bundle();
             appSearchData.putString(SearchManager.SOURCE, "launcher-search");
         }
-        super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+
+        final SearchManager searchManager =
+                (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+
+        final Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+        if (searchWidget != null) {
+            // This gets called when the user leaves the search dialog to go back to
+            // the Launcher.
+            searchManager.setOnCancelListener(new SearchManager.OnCancelListener() {
+                public void onCancel() {
+                    searchManager.setOnCancelListener(null);
+                    stopSearch();
+                }
+            });
+        }
+
+        searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+            appSearchData, globalSearch);
+    }
+
+    /**
+     * Cancel search dialog if it is open.
+     */
+    void stopSearch() {
+        // Close search dialog
+        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+        if (searchManager.isVisible()) {
+            searchManager.stopSearch();
+        }
+        // Restore search widget to its normal position
+        Search searchWidget = mWorkspace.findSearchWidgetOnCurrentScreen();
+        if (searchWidget != null) {
+            searchWidget.stopSearch(false);
+        }
     }
 
     @Override
@@ -865,9 +1129,14 @@
                 .setIcon(com.android.internal.R.drawable.ic_menu_notifications)
                 .setAlphabeticShortcut('N');
 
+        final Intent gestures = new Intent(this, GesturesActivity.class);
+        menu.add(0, MENU_GESTURES, 0, R.string.menu_gestures)
+                .setIcon(com.android.internal.R.drawable.ic_menu_compose).setAlphabeticShortcut('G')
+                .setIntent(gestures);
+
         final Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS);
-        settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
 
         menu.add(0, MENU_SETTINGS, 0, R.string.menu_settings)
                 .setIcon(android.R.drawable.ic_menu_preferences).setAlphabeticShortcut('P')
@@ -906,14 +1175,15 @@
         return super.onOptionsItemSelected(item);
     }
 
+    /**
+     * Indicates that we want global search for this activity by setting the globalSearch
+     * argument for {@link #startSearch} to true.
+     */
+
     @Override
     public boolean onSearchRequested() {
-        if (mWorkspace.snapToSearch()) {
-            closeDrawer(true);                // search widget: get drawer out of the way
-            return true;
-        } else {
-            return super.onSearchRequested(); // no search widget: use system search UI
-        }
+        startSearch(null, false, null, true);
+        return true;
     }
 
     private void addItems() {
@@ -925,7 +1195,7 @@
             mWorkspace.removeShortcutsForPackage(packageName);
         }
     }
-    
+
     private void updateShortcutsForPackage(String packageName) {
         if (packageName != null && packageName.length() > 0) {
             mWorkspace.updateShortcutsForPackage(packageName);
@@ -958,41 +1228,43 @@
             }
         }
     }
-    
+
     void addSearch() {
         final Widget info = Widget.makeSearch();
         final CellLayout.CellInfo cellInfo = mAddItemCellInfo;
-        
+
         final int[] xy = mCellCoordinates;
         final int spanX = info.spanX;
         final int spanY = info.spanY;
-    
+
         if (!findSlot(cellInfo, xy, spanX, spanY)) return;
-    
+
         sModel.addDesktopItem(info);
         LauncherModel.addItemToDatabase(this, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
         mWorkspace.getCurrentScreen(), xy[0], xy[1], false);
-    
+
         final View view = mInflater.inflate(info.layoutResource, null);
         view.setTag(info);
-    
+        Search search = (Search) view.findViewById(R.id.widget_search);
+        search.setLauncher(this);
+
         mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY);
     }
 
-    void addShortcut(Intent intent) {
+    void processShortcut(Intent intent, int requestCodeApplication, int requestCodeShortcut) {
         // 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);
-            startActivityForResult(pickIntent, REQUEST_PICK_APPLICATION);
+            startActivityForResult(pickIntent, requestCodeApplication);
         } else {
-            startActivityForResult(intent, REQUEST_CREATE_SHORTCUT);
+            startActivityForResult(intent, requestCodeShortcut);
         }
     }
 
@@ -1000,7 +1272,7 @@
         // Handle case where user selected "Folder"
         String folderName = getResources().getString(R.string.group_folder);
         String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-        
+
         if (folderName != null && folderName.equals(shortcutName)) {
             addFolder(!mDesktopLocked);
         } else {
@@ -1028,7 +1300,7 @@
         mWorkspace.addInCurrentScreen(newFolder,
                 cellInfo.cellX, cellInfo.cellY, 1, 1, insertAtFirst);
     }
-    
+
     private void completeAddLiveFolder(Intent data, CellLayout.CellInfo cellInfo,
             boolean insertAtFirst) {
         cellInfo.screen = mWorkspace.getCurrentScreen();
@@ -1158,7 +1430,6 @@
     private void registerContentObservers() {
         ContentResolver resolver = getContentResolver();
         resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mObserver);
-        resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI, true, mAppWidgetResetObserver);
     }
 
     @Override
@@ -1166,11 +1437,11 @@
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
             switch (event.getKeyCode()) {
                 case KeyEvent.KEYCODE_BACK:
-                    mWorkspace.dispatchKeyEvent(event); 
+                    mWorkspace.dispatchKeyEvent(event);
                     if (mDrawer.isOpened()) {
                         closeDrawer();
                     } else {
-                        closeFolder();                        
+                        closeFolder();
                     }
                     return true;
                 case KeyEvent.KEYCODE_HOME:
@@ -1224,21 +1495,11 @@
         sModel.loadUserItems(false, this, false, false);
     }
 
-    /**
-     * When reset, we handle by calling {@link AppWidgetHost#startListening()}
-     * to make sure our callbacks are set correctly.
-     */
-    private void onAppWidgetReset() {
-        if (mAppWidgetHost != null) {
-            mAppWidgetHost.startListening();
-        }
-    }
-
     void onDesktopItemsLoaded() {
         if (mDestroyed) return;
         bindDesktopItems();
     }
-    
+
     /**
      * Refreshes the shortcuts shown on the workspace.
      */
@@ -1250,14 +1511,12 @@
             return;
         }
 
-        mAllAppsGrid.setAdapter(drawerAdapter);
-
         final Workspace workspace = mWorkspace;
         int count = workspace.getChildCount();
         for (int i = 0; i < count; i++) {
             ((ViewGroup) workspace.getChildAt(i)).removeAllViewsInLayout();
         }
-        
+
         if (DEBUG_USER_INTERFACE) {
             android.widget.Button finishButton = new android.widget.Button(this);
             finishButton.setText("Finish");
@@ -1269,13 +1528,13 @@
                 }
             });
         }
-        
+
         // Flag any old binder to terminate early
         if (mBinder != null) {
             mBinder.mTerminate = true;
         }
-        
-        mBinder = new DesktopBinder(this, shortcuts, appWidgets);
+
+        mBinder = new DesktopBinder(this, shortcuts, appWidgets, drawerAdapter);
         mBinder.startBindingItems();
     }
 
@@ -1316,10 +1575,13 @@
                     final int screen = workspace.getCurrentScreen();
                     final View view = mInflater.inflate(R.layout.widget_search,
                             (ViewGroup) workspace.getChildAt(screen), false);
-                    
+
+                    Search search = (Search) view.findViewById(R.id.widget_search);
+                    search.setLauncher(this);
+
                     final Widget widget = (Widget) item;
                     view.setTag(widget);
-                    
+
                     workspace.addWidget(view, widget, !desktopLocked);
                     break;
             }
@@ -1329,7 +1591,7 @@
 
         if (end >= count) {
             finishBindDesktopItems();
-            binder.startBindingAppWidgetsWhenIdle();
+            binder.startBindingDrawer();
         } else {
             binder.obtainMessage(DesktopBinder.MESSAGE_BIND_ITEMS, i, count).sendToTarget();
         }
@@ -1375,32 +1637,34 @@
         mDesktopLocked = false;
         mDrawer.unlock();
     }
-    
+
+    private void bindDrawer(Launcher.DesktopBinder binder,
+            ApplicationsAdapter drawerAdapter) {
+        mAllAppsGrid.setAdapter(drawerAdapter);
+        binder.startBindingAppWidgetsWhenIdle();
+    }
+
     private void bindAppWidgets(Launcher.DesktopBinder binder,
             LinkedList<LauncherAppWidgetInfo> appWidgets) {
-        
+
         final Workspace workspace = mWorkspace;
         final boolean desktopLocked = mDesktopLocked;
 
         if (!appWidgets.isEmpty()) {
             final LauncherAppWidgetInfo item = appWidgets.removeFirst();
-            
+
             final int appWidgetId = item.appWidgetId;
-            final AppWidgetProviderInfo appWidgetInfo =
-                    mAppWidgetManager.getAppWidgetInfo(appWidgetId);
+            final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
             item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
-            
-            if (LOGD) {
-                d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s",
-                        appWidgetId, appWidgetInfo));
-            }
-            
+
+            if (LOGD) d(LOG_TAG, String.format("about to setAppWidget for id=%d, info=%s", appWidgetId, appWidgetInfo));
+
             item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
             item.hostView.setTag(item);
-            
+
             workspace.addInScreen(item.hostView, item.screen, item.cellX,
                     item.cellY, item.spanX, item.spanY, !desktopLocked);
-    
+
             workspace.requestLayout();
         }
 
@@ -1434,6 +1698,7 @@
     }
 
     void startActivitySafely(Intent intent) {
+        mHideGesturesPanel = true;
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         try {
             startActivity(intent);
@@ -1441,7 +1706,7 @@
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
         } catch (SecurityException e) {
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
-            Log.e(LOG_TAG, "Launcher does not have the permission to launch " + intent +
+            e(LOG_TAG, "Launcher does not have the permission to launch " + intent +
                     ". Make sure to create a MAIN intent-filter for the corresponding activity " +
                     "or use the exported attribute for this activity.", e);
         }
@@ -1560,6 +1825,10 @@
         return sModel;
     }
 
+    static GestureLibrary getGestureLibrary() {
+        return sLibrary;
+    }
+
     void closeAllApplications() {
         mDrawer.close();
     }
@@ -1611,7 +1880,7 @@
                 EditText input = (EditText) dialog.findViewById(R.id.folder_name);
                 final CharSequence text = mFolderInfo.title;
                 input.setText(text);
-                input.setSelection(0, text.length());                
+                input.setSelection(0, text.length());
                 break;
         }
     }
@@ -1628,6 +1897,26 @@
         showDialog(DIALOG_CREATE_SHORTCUT);
     }
 
+    private void pickShortcut(int requestCode, int title) {
+        Bundle bundle = new Bundle();
+
+        ArrayList<String> shortcutNames = new ArrayList<String>();
+        shortcutNames.add(getString(R.string.group_applications));
+        bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
+
+        ArrayList<ShortcutIconResource> shortcutIcons = new ArrayList<ShortcutIconResource>();
+        shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
+                        R.drawable.ic_launcher_application));
+        bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
+
+        Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
+        pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT));
+        pickIntent.putExtra(Intent.EXTRA_TITLE, getText(title));
+        pickIntent.putExtras(bundle);
+
+        startActivityForResult(pickIntent, requestCode);
+    }
+
     private class RenameFolder {
         private EditText mInput;
 
@@ -1703,22 +1992,23 @@
      * appropriate activity.
      */
     private class CreateShortcut implements DialogInterface.OnClickListener,
-            DialogInterface.OnCancelListener {
+            DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
         private AddAdapter mAdapter;
 
         Dialog createDialog() {
             mWaitingForResult = true;
-            
+
             mAdapter = new AddAdapter(Launcher.this);
-            
+
             final AlertDialog.Builder builder = new AlertDialog.Builder(Launcher.this);
             builder.setTitle(getString(R.string.menu_item_add_item));
             builder.setAdapter(mAdapter, this);
-            
+
             builder.setInverseBackgroundForced(true);
 
             AlertDialog dialog = builder.create();
             dialog.setOnCancelListener(this);
+            dialog.setOnDismissListener(this);
 
             return dialog;
         }
@@ -1728,6 +2018,10 @@
             cleanup();
         }
 
+        public void onDismiss(DialogInterface dialog) {
+            mWorkspace.unlock();
+        }
+
         private void cleanup() {
             mWorkspace.unlock();
             dismissDialog(DIALOG_CREATE_SHORTCUT);
@@ -1739,36 +2033,17 @@
         public void onClick(DialogInterface dialog, int which) {
             Resources res = getResources();
             cleanup();
-            
+
             switch (which) {
                 case AddAdapter.ITEM_SHORTCUT: {
                     // Insert extra item to handle picking application
-                    Bundle bundle = new Bundle();
-                    
-                    ArrayList<String> shortcutNames = new ArrayList<String>();
-                    shortcutNames.add(res.getString(R.string.group_applications));
-                    bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
-                    
-                    ArrayList<ShortcutIconResource> shortcutIcons =
-                            new ArrayList<ShortcutIconResource>();
-                    shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
-                            R.drawable.ic_launcher_application));
-                    bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons);
-                    
-                    Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
-                    pickIntent.putExtra(Intent.EXTRA_INTENT,
-                            new Intent(Intent.ACTION_CREATE_SHORTCUT));
-                    pickIntent.putExtra(Intent.EXTRA_TITLE,
-                            getText(R.string.title_select_shortcut));
-                    pickIntent.putExtras(bundle);
-                    
-                    startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT);
+                    pickShortcut(REQUEST_PICK_SHORTCUT, R.string.title_select_shortcut);
                     break;
                 }
-                
+
                 case AddAdapter.ITEM_APPWIDGET: {
                     int appWidgetId = Launcher.this.mAppWidgetHost.allocateAppWidgetId();
-                    
+
                     Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
                     pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
                     // add the search widget
@@ -1791,15 +2066,15 @@
                     startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
                     break;
                 }
-                
+
                 case AddAdapter.ITEM_LIVE_FOLDER: {
                     // Insert extra item to handle inserting folder
                     Bundle bundle = new Bundle();
-                    
+
                     ArrayList<String> shortcutNames = new ArrayList<String>();
                     shortcutNames.add(res.getString(R.string.group_folder));
                     bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames);
-                    
+
                     ArrayList<ShortcutIconResource> shortcutIcons =
                             new ArrayList<ShortcutIconResource>();
                     shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this,
@@ -1812,7 +2087,7 @@
                     pickIntent.putExtra(Intent.EXTRA_TITLE,
                             getText(R.string.title_select_live_folder));
                     pickIntent.putExtras(bundle);
-                    
+
                     startActivityForResult(pickIntent, REQUEST_PICK_LIVE_FOLDER);
                     break;
                 }
@@ -1891,21 +2166,6 @@
     }
 
     /**
-     * Receives notifications when the {@link AppWidgetHost} has been reset,
-     * usually only when the {@link LauncherProvider} database is first created.
-     */
-    private class AppWidgetResetObserver extends ContentObserver {
-        public AppWidgetResetObserver() {
-            super(new Handler());
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            onAppWidgetReset();
-        }
-    }
-
-    /**
      * Receives intents from other applications to change the wallpaper.
      */
     private static class WallpaperIntentReceiver extends BroadcastReceiver {
@@ -1996,27 +2256,31 @@
     private static class DesktopBinder extends Handler implements MessageQueue.IdleHandler {
         static final int MESSAGE_BIND_ITEMS = 0x1;
         static final int MESSAGE_BIND_APPWIDGETS = 0x2;
-        
+        static final int MESSAGE_BIND_DRAWER = 0x3;
+
         // Number of items to bind in every pass
         static final int ITEMS_COUNT = 6;
 
         private final ArrayList<ItemInfo> mShortcuts;
         private final LinkedList<LauncherAppWidgetInfo> mAppWidgets;
+        private final ApplicationsAdapter mDrawerAdapter;
         private final WeakReference<Launcher> mLauncher;
-        
-        public volatile boolean mTerminate = false;
+
+        public boolean mTerminate = false;
 
         DesktopBinder(Launcher launcher, ArrayList<ItemInfo> shortcuts,
-                ArrayList<LauncherAppWidgetInfo> appWidgets) {
+                ArrayList<LauncherAppWidgetInfo> appWidgets,
+                ApplicationsAdapter drawerAdapter) {
 
             mLauncher = new WeakReference<Launcher>(launcher);
             mShortcuts = shortcuts;
-            
+            mDrawerAdapter = drawerAdapter;
+
             // Sort widgets so active workspace is bound first
             final int currentScreen = launcher.mWorkspace.getCurrentScreen();
             final int size = appWidgets.size();
             mAppWidgets = new LinkedList<LauncherAppWidgetInfo>();
-            
+
             for (int i = 0; i < size; i++) {
                 LauncherAppWidgetInfo appWidgetInfo = appWidgets.get(i);
                 if (appWidgetInfo.screen == currentScreen) {
@@ -2026,17 +2290,21 @@
                 }
             }
         }
-        
+
         public void startBindingItems() {
             obtainMessage(MESSAGE_BIND_ITEMS, 0, mShortcuts.size()).sendToTarget();
         }
-        
+
+        public void startBindingDrawer() {
+            obtainMessage(MESSAGE_BIND_DRAWER).sendToTarget();
+        }
+
         public void startBindingAppWidgetsWhenIdle() {
             // Ask for notification when message queue becomes idle
             final MessageQueue messageQueue = Looper.myQueue();
             messageQueue.addIdleHandler(this);
         }
-        
+
         public boolean queueIdle() {
             // Queue is idle, so start binding items
             startBindingAppWidgets();
@@ -2053,12 +2321,16 @@
             if (launcher == null || mTerminate) {
                 return;
             }
-            
+
             switch (msg.what) {
                 case MESSAGE_BIND_ITEMS: {
                     launcher.bindItems(this, mShortcuts, msg.arg1, msg.arg2);
                     break;
                 }
+                case MESSAGE_BIND_DRAWER: {
+                    launcher.bindDrawer(this, mDrawerAdapter);
+                    break;
+                }
                 case MESSAGE_BIND_APPWIDGETS: {
                     launcher.bindAppWidgets(this, mAppWidgets);
                     break;
@@ -2066,4 +2338,113 @@
             }
         }
     }
+
+    private class GesturesProcessor implements GestureOverlayView.OnGestureListener,
+            GestureOverlayView.OnGesturePerformedListener {
+
+        private final GestureMatcher mMatcher = new GestureMatcher();
+
+        GesturesProcessor() {
+            // TODO: Maybe the load should happen on a background thread?
+            sLibrary.load();
+        }
+
+        public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
+            overlay.removeCallbacks(mMatcher);
+            resetGesturesNextPrompt();
+
+            mGesturesAdd.setAlpha(128);
+            mGesturesAdd.setEnabled(false);
+        }
+
+        public void onGesture(GestureOverlayView overlay, MotionEvent event) {
+        }
+
+        public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
+        }
+
+        public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
+            overlay.removeCallbacks(mMatcher);
+
+            mMatcher.gesture = overlay.getGesture();
+            if (mMatcher.gesture.getLength() < GesturesConstants.LENGTH_THRESHOLD) {
+                overlay.clear(false);
+            } else {
+                overlay.postDelayed(mMatcher, GesturesConstants.MATCH_DELAY);
+            }
+        }
+
+        private void matchGesture(Gesture gesture) {
+            mGesturesAdd.setAlpha(255);
+            mGesturesAdd.setEnabled(true);
+
+            if (gesture != null) {
+                final ArrayList<Prediction> predictions = sLibrary.recognize(gesture);
+
+                if (DEBUG_GESTURES) {
+                    for (Prediction p : predictions) {
+                        d(LOG_TAG, String.format("name=%s, score=%f", p.name, p.score));
+                    }
+                }
+
+                boolean match = false;
+                if (predictions.size() > 0) {
+                    final Prediction prediction = predictions.get(0);
+                    if (prediction.score > GesturesConstants.PREDICTION_THRESHOLD) {
+                        match = true;
+
+                        ApplicationInfo info = sModel.queryGesture(Launcher.this, prediction.name);
+                        if (info != null) {
+                            updatePrompt(info);
+                        }
+                    }
+                }
+
+                if (!match){
+                    setGesturesNextPrompt(null, getString(R.string.gestures_unknown));
+                }
+            }
+        }
+
+        private void updatePrompt(ApplicationInfo info) {
+            setGesturesNextPrompt(info.icon, info.title);
+            mGesturesAction.intent = info.intent;
+        }
+
+        public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) {
+            overlay.removeCallbacks(mMatcher);
+        }
+
+        void addGesture(String name, Gesture gesture) {
+            sLibrary.addGesture(name, gesture);
+            // TODO: On a background thread?
+            sLibrary.save();
+        }
+
+        void update(ApplicationInfo info, Gesture gesture) {
+            mGesturesOverlay.setGesture(gesture);
+            updatePrompt(info);
+        }
+
+        class GestureMatcher implements Runnable {
+            Gesture gesture;
+
+            public void run() {
+                if (gesture != null) {
+                    matchGesture(gesture);
+                }
+            }
+        }
+    }
+
+    private class GesturesAction implements View.OnClickListener {
+        Intent intent;
+
+        public void onClick(View v) {
+            if (intent != null) {
+                startActivitySafely(intent);
+            }
+        }
+    }
 }
+
diff --git a/src/com/android/launcher/LauncherAppWidgetHostView.java b/src/com/android/launcher/LauncherAppWidgetHostView.java
index 1e21a19..da5b3a0 100644
--- a/src/com/android/launcher/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher/LauncherAppWidgetHostView.java
@@ -98,4 +98,14 @@
         mPendingCheckForLongPress.rememberWindowAttachCount();
         postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
     }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+
+        mHasPerformedLongPress = false;
+        if (mPendingCheckForLongPress != null) {
+            removeCallbacks(mPendingCheckForLongPress);
+        }
+    }
 }
diff --git a/src/com/android/launcher/LauncherModel.java b/src/com/android/launcher/LauncherModel.java
index da41f14..271f9f4 100644
--- a/src/com/android/launcher/LauncherModel.java
+++ b/src/com/android/launcher/LauncherModel.java
@@ -174,7 +174,7 @@
 
                 for (ResolveInfo info : matches) {
                     adapter.setNotifyOnChange(false);
-                    adapter.add(makeAndCacheApplicationInfo(packageManager, cache, info));
+                    adapter.add(makeAndCacheApplicationInfo(packageManager, cache, info, launcher));
                 }
 
                 adapter.sort(new ApplicationInfoComparator());
@@ -239,7 +239,7 @@
                 final ApplicationInfo applicationInfo = findIntent(adapter,
                         info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
                 if (applicationInfo != null) {
-                    updateAndCacheApplicationInfo(packageManager, info, applicationInfo);
+                    updateAndCacheApplicationInfo(packageManager, info, applicationInfo, launcher);
                     changed = true;
                 }
             }
@@ -252,9 +252,9 @@
     }
 
     private void updateAndCacheApplicationInfo(PackageManager packageManager, ResolveInfo info,
-            ApplicationInfo applicationInfo) {
+            ApplicationInfo applicationInfo, Context context) {
 
-        updateApplicationInfoTitleAndIcon(packageManager, info, applicationInfo);
+        updateApplicationInfoTitleAndIcon(packageManager, info, applicationInfo, context);
 
         ComponentName componentName = new ComponentName(
                 info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
@@ -326,10 +326,11 @@
                     info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
             if (applicationInfo == null) {
                 toAdd.add(makeAndCacheApplicationInfo(launcher.getPackageManager(),
-                        mAppInfoCache, info));
+                        mAppInfoCache, info, launcher));
                 changed = true;
             } else {
-                updateAndCacheApplicationInfo(launcher.getPackageManager(), info, applicationInfo);
+                updateAndCacheApplicationInfo(
+                        launcher.getPackageManager(), info, applicationInfo, launcher);
                 changed = true;
             }
         }
@@ -419,7 +420,8 @@
     }
 
     private static ApplicationInfo makeAndCacheApplicationInfo(PackageManager manager,
-            HashMap<ComponentName, ApplicationInfo> appInfoCache, ResolveInfo info) {
+            HashMap<ComponentName, ApplicationInfo> appInfoCache, ResolveInfo info,
+            Context context) {
 
         ComponentName componentName = new ComponentName(
                 info.activityInfo.applicationInfo.packageName,
@@ -430,7 +432,7 @@
             application = new ApplicationInfo();
             application.container = ItemInfo.NO_ID;
 
-            updateApplicationInfoTitleAndIcon(manager, info, application);
+            updateApplicationInfoTitleAndIcon(manager, info, application, context);
 
             application.setActivity(componentName,
                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
@@ -442,14 +444,15 @@
     }
 
     private static void updateApplicationInfoTitleAndIcon(PackageManager manager, ResolveInfo info,
-            ApplicationInfo application) {
+            ApplicationInfo application, Context context) {
 
         application.title = info.loadLabel(manager);
         if (application.title == null) {
             application.title = info.activityInfo.name;
         }
 
-        application.icon = info.activityInfo.loadIcon(manager);
+        application.icon =
+                Utilities.createIconThumbnail(info.activityInfo.loadIcon(manager), context);
         application.filtered = false;
     }
 
@@ -495,7 +498,7 @@
                 for (int i = 0; i < count && !mStopped; i++) {
                     ResolveInfo info = apps.get(i);
                     ApplicationInfo application =
-                            makeAndCacheApplicationInfo(manager, appInfoCache, info);
+                        makeAndCacheApplicationInfo(manager, appInfoCache, info, launcher);
 
                     if (action.add(application) && !mStopped) {
                         launcher.runOnUiThread(action);
@@ -557,7 +560,7 @@
         }
     }
 
-    private static class ApplicationInfoComparator implements Comparator<ApplicationInfo> {
+    static class ApplicationInfoComparator implements Comparator<ApplicationInfo> {
         public final int compare(ApplicationInfo a, ApplicationInfo b) {
             return sCollator.compare(a.title.toString(), b.title.toString());
         }
@@ -611,11 +614,11 @@
 
     private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) {
         final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI,
-                new String[] { LauncherSettings.Favorites.ID, LauncherSettings.Favorites.TITLE,
+                new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE,
                         LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE },
                 null, null, null);
 
-        final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID);
+        final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
         final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
         final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
         final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
@@ -722,7 +725,7 @@
                     LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
 
             try {
-                final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID);
+                final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
                 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
                 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
                 final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
@@ -765,7 +768,7 @@
                             }
 
                             if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
-                                info = getApplicationInfo(manager, intent);
+                                info = getApplicationInfo(manager, intent, launcher);
                             } else {
                                 info = getApplicationInfoShortcut(c, launcher, iconTypeIndex,
                                         iconPackageIndex, iconResourceIndex, iconIndex);
@@ -1116,7 +1119,8 @@
     /**
      * Make an ApplicationInfo object for an application
      */
-    private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent) {
+    private static ApplicationInfo getApplicationInfo(PackageManager manager, Intent intent,
+                                                      Context context) {
         final ResolveInfo resolveInfo = manager.resolveActivity(intent, 0);
 
         if (resolveInfo == null) {
@@ -1125,7 +1129,7 @@
         
         final ApplicationInfo info = new ApplicationInfo();
         final ActivityInfo activityInfo = resolveInfo.activityInfo;
-        info.icon = activityInfo.loadIcon(manager);
+        info.icon = Utilities.createIconThumbnail(activityInfo.loadIcon(manager), context);
         if (info.title == null || info.title.length() == 0) {
             info.title = activityInfo.loadLabel(manager);
         }
@@ -1139,7 +1143,7 @@
     /**
      * Make an ApplicationInfo object for a sortcut
      */
-    private ApplicationInfo getApplicationInfoShortcut(Cursor c, Launcher launcher,
+    private ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context,
             int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) {
 
         final ApplicationInfo info = new ApplicationInfo();
@@ -1150,11 +1154,11 @@
             case LauncherSettings.Favorites.ICON_TYPE_RESOURCE:
                 String packageName = c.getString(iconPackageIndex);
                 String resourceName = c.getString(iconResourceIndex);
-                PackageManager packageManager = launcher.getPackageManager();
+                PackageManager packageManager = context.getPackageManager();
                 try {
                     Resources resources = packageManager.getResourcesForApplication(packageName);
                     final int id = resources.getIdentifier(resourceName, null, null);
-                    info.icon = resources.getDrawable(id);
+                    info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context);
                 } catch (Exception e) {
                     info.icon = packageManager.getDefaultActivityIcon();
                 }
@@ -1165,14 +1169,19 @@
                 break;
             case LauncherSettings.Favorites.ICON_TYPE_BITMAP:
                 byte[] data = c.getBlob(iconIndex);
-                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
-                info.icon = new FastBitmapDrawable(
-                        Utilities.createBitmapThumbnail(bitmap, launcher));
+                try {
+                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+                    info.icon = new FastBitmapDrawable(
+                            Utilities.createBitmapThumbnail(bitmap, context));
+                } catch (Exception e) {
+                    packageManager = context.getPackageManager();
+                    info.icon = packageManager.getDefaultActivityIcon();
+                }
                 info.filtered = true;
                 info.customIcon = true;
                 break;
             default:
-                info.icon = launcher.getPackageManager().getDefaultActivityIcon();
+                info.icon = context.getPackageManager().getDefaultActivityIcon();
                 info.customIcon = false;
                 break;
         }
@@ -1317,6 +1326,26 @@
     }
 
     /**
+     * Add an item to the database in a specified container. Sets the container, screen, cellX and
+     * cellY fields of the item. Also assigns an ID to the item.
+     */
+    static boolean addGestureToDatabase(Context context, ItemInfo item, boolean notify) {
+        final ContentValues values = new ContentValues();
+        final ContentResolver cr = context.getContentResolver();
+
+        item.onAddToDatabase(values);
+
+        Uri result = cr.insert(notify ? LauncherSettings.Gestures.CONTENT_URI :
+                LauncherSettings.Gestures.CONTENT_URI_NO_NOTIFICATION, values);
+
+        if (result != null) {
+            item.id = Integer.parseInt(result.getPathSegments().get(1));
+        }
+
+        return result != null;
+    }
+
+    /**
      * Update an item to the database in a specified container.
      */
     static void updateItemInDatabase(Context context, ItemInfo item) {
@@ -1350,4 +1379,84 @@
         cr.delete(LauncherSettings.Favorites.CONTENT_URI,
                 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
     }
+
+    static void deleteGestureFromDatabase(Context context, ItemInfo item) {
+        final ContentResolver cr = context.getContentResolver();
+
+        cr.delete(LauncherSettings.Gestures.getContentUri(item.id, false), null, null);
+    }
+
+    static void updateGestureInDatabase(Context context, ItemInfo item) {
+        final ContentValues values = new ContentValues();
+        final ContentResolver cr = context.getContentResolver();
+
+        item.onAddToDatabase(values);
+
+        cr.update(LauncherSettings.Gestures.getContentUri(item.id, false), values, null, null);
+    }
+
+
+    ApplicationInfo queryGesture(Context context, String id) {
+        final ContentResolver contentResolver = context.getContentResolver();
+        final PackageManager manager = context.getPackageManager();
+        final Cursor c = contentResolver.query(
+                LauncherSettings.Gestures.CONTENT_URI, null, LauncherSettings.Gestures._ID + "=?",
+                new String[] { id }, null);
+
+        ApplicationInfo info = null;
+
+        try {
+            final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures._ID);
+            final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.INTENT);
+            final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.TITLE);
+            final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_TYPE);
+            final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON);
+            final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_PACKAGE);
+            final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_RESOURCE);
+            final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ITEM_TYPE);
+
+            String intentDescription;
+            Intent intent;
+
+            if (c.moveToNext()) {
+                int itemType = c.getInt(itemTypeIndex);
+
+                switch (itemType) {
+                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                        intentDescription = c.getString(intentIndex);
+                        try {
+                            intent = Intent.getIntent(intentDescription);
+                        } catch (java.net.URISyntaxException e) {
+                            return null;
+                        }
+
+                        if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+                            info = getApplicationInfo(manager, intent, context);
+                        } else {
+                            info = getApplicationInfoShortcut(c, context, iconTypeIndex,
+                                    iconPackageIndex, iconResourceIndex, iconIndex);
+                        }
+
+                        if (info == null) {
+                            info = new ApplicationInfo();
+                            info.icon = manager.getDefaultActivityIcon();
+                        }
+
+                        info.isGesture = true;
+                        info.title = c.getString(titleIndex);
+                        info.intent = intent;
+                        info.id = c.getLong(idIndex);
+
+                        break;
+                }
+            }
+        } catch (Exception e) {
+            w(LOG_TAG, "Could not load gesture with name " + id);
+        } finally {
+            c.close();
+        }
+
+        return info;
+    }
 }
diff --git a/src/com/android/launcher/LauncherProvider.java b/src/com/android/launcher/LauncherProvider.java
index a27b746..ba8ebda 100644
--- a/src/com/android/launcher/LauncherProvider.java
+++ b/src/com/android/launcher/LauncherProvider.java
@@ -55,7 +55,7 @@
 
     private static final String DATABASE_NAME = "launcher.db";
     
-    private static final int DATABASE_VERSION = 3;
+    private static final int DATABASE_VERSION = 4;
 
     static final String AUTHORITY = "com.android.launcher.settings";
     
@@ -63,10 +63,11 @@
     static final String EXTRA_BIND_TARGETS = "com.android.launcher.settings.bindtargets";
 
     static final String TABLE_FAVORITES = "favorites";
+    static final String TABLE_GESTURES = "gestures";
     static final String PARAMETER_NOTIFY = "notify";
 
     /**
-     * {@link Uri} triggered at any registered {@link ContentObserver} when
+     * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when
      * {@link AppWidgetHost#deleteHost()} is called during database creation.
      * Use this to recall {@link AppWidgetHost#startListening()} if needed.
      */
@@ -99,7 +100,7 @@
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
         qb.setTables(args.table);
 
-        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
         result.setNotificationUri(getContext().getContentResolver(), uri);
 
@@ -220,6 +221,17 @@
                     "displayMode INTEGER" +
                     ");");
 
+            db.execSQL("CREATE TABLE gestures (" +
+                    "_id INTEGER PRIMARY KEY," +
+                    "title TEXT," +
+                    "intent TEXT," +
+                    "itemType INTEGER," +
+                    "iconType INTEGER," +
+                    "iconPackage TEXT," +
+                    "iconResource TEXT," +
+                    "icon BLOB" +
+                    ");");
+
             // Database was just created, so wipe any previous widgets
             if (mAppWidgetHost != null) {
                 mAppWidgetHost.deleteHost();
@@ -270,7 +282,7 @@
         }
 
         private int copyFromCursor(SQLiteDatabase db, Cursor c) {
-            final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID);
+            final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
             final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
             final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
             final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE);
@@ -289,7 +301,7 @@
             int i = 0;
             while (c.moveToNext()) {
                 ContentValues values = new ContentValues(c.getColumnCount());
-                values.put(LauncherSettings.Favorites.ID, c.getLong(idIndex));
+                values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex));
                 values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex));
                 values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex));
                 values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex));
@@ -352,6 +364,29 @@
                     convertWidgets(db);
                 }
             }
+
+            if (version < 4) {
+                db.beginTransaction();
+                try {
+                    db.execSQL("CREATE TABLE gestures (" +
+                        "_id INTEGER PRIMARY KEY," +
+                        "title TEXT," +
+                        "intent TEXT," +
+                        "itemType INTEGER," +
+                        "iconType INTEGER," +
+                        "iconPackage TEXT," +
+                        "iconResource TEXT," +
+                        "icon BLOB" +
+                        ");");
+                    db.setTransactionSuccessful();
+                    version = 4;
+                } catch (SQLException ex) {
+                    // Old version remains, which means we wipe old data
+                    Log.e(LOG_TAG, ex.getMessage(), ex);
+                } finally {
+                    db.endTransaction();
+                }
+            }
             
             if (version != DATABASE_VERSION) {
                 Log.w(LOG_TAG, "Destroying all old data.");
diff --git a/src/com/android/launcher/LauncherSettings.java b/src/com/android/launcher/LauncherSettings.java
index 60ea0df..062c8a6 100644
--- a/src/com/android/launcher/LauncherSettings.java
+++ b/src/com/android/launcher/LauncherSettings.java
@@ -23,11 +23,109 @@
  * Settings related utilities.
  */
 class LauncherSettings {
+    static interface BaseLauncherColumns extends BaseColumns {
+        /**
+         * Descriptive name of the gesture that can be displayed to the user.
+         * <P>Type: TEXT</P>
+         */
+        static final String TITLE = "title";
+
+        /**
+         * The Intent URL of the gesture, describing what it points to. This
+         * value is given to {@link android.content.Intent#getIntent} to create
+         * an Intent that can be launched.
+         * <P>Type: TEXT</P>
+         */
+        static final String INTENT = "intent";
+
+        /**
+         * The type of the gesture
+         *
+         * <P>Type: INTEGER</P>
+         */
+        static final String ITEM_TYPE = "itemType";
+
+        /**
+         * The gesture is an application
+         */
+        static final int ITEM_TYPE_APPLICATION = 0;
+
+        /**
+         * The gesture is an application created shortcut
+         */
+        static final int ITEM_TYPE_SHORTCUT = 1;
+
+        /**
+         * The icon type.
+         * <P>Type: INTEGER</P>
+         */
+        static final String ICON_TYPE = "iconType";
+
+        /**
+         * The icon is a resource identified by a package name and an integer id.
+         */
+        static final int ICON_TYPE_RESOURCE = 0;
+
+        /**
+         * The icon is a bitmap.
+         */
+        static final int ICON_TYPE_BITMAP = 1;
+
+        /**
+         * The icon package name, if icon type is ICON_TYPE_RESOURCE.
+         * <P>Type: TEXT</P>
+         */
+        static final String ICON_PACKAGE = "iconPackage";
+
+        /**
+         * The icon resource id, if icon type is ICON_TYPE_RESOURCE.
+         * <P>Type: TEXT</P>
+         */
+        static final String ICON_RESOURCE = "iconResource";
+
+        /**
+         * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP.
+         * <P>Type: BLOB</P>
+         */
+        static final String ICON = "icon";
+    }
+
+    static final class Gestures implements BaseLauncherColumns {
+                /**
+         * The content:// style URL for this table
+         */
+        static final Uri CONTENT_URI = Uri.parse("content://" +
+                LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_GESTURES +
+                "?" + LauncherProvider.PARAMETER_NOTIFY + "=true");
+
+        /**
+         * The content:// style URL for this table. When this Uri is used, no notification is
+         * sent if the content changes.
+         */
+        static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" +
+                LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_GESTURES +
+                "?" + LauncherProvider.PARAMETER_NOTIFY + "=false");
+
+        /**
+         * The content:// style URL for a given row, identified by its id.
+         *
+         * @param id The row id.
+         * @param notify True to send a notification is the content changes.
+         *
+         * @return The unique content URL for the specified row.
+         */
+        static Uri getContentUri(long id, boolean notify) {
+            return Uri.parse("content://" + LauncherProvider.AUTHORITY +
+                    "/" + LauncherProvider.TABLE_GESTURES + "/" + id + "?" +
+                    LauncherProvider.PARAMETER_NOTIFY + "=" + notify);
+        }
+    }
+
     /**
      * Favorites. When changing these values, be sure to update
      * {@link com.android.settings.LauncherAppWidgetBinder} as needed.
      */
-    static final class Favorites implements BaseColumns {
+    static final class Favorites implements BaseLauncherColumns {
         /**
          * The content:// style URL for this table
          */
@@ -58,26 +156,6 @@
         }
 
         /**
-         * The row ID.
-         * <p>Type: INTEGER</p>
-         */
-        static final String ID = "_id";
-
-        /**
-         * Descriptive name of the favorite that can be displayed to the user.
-         * <P>Type: TEXT</P>
-         */
-        static final String TITLE = "title";
-
-        /**
-         * The Intent URL of the favorite, describing what it points to.  This
-         * value is given to {@link android.content.Intent#getIntent} to create
-         * an Intent that can be launched.
-         * <P>Type: TEXT</P>
-         */
-        static final String INTENT = "intent";
-
-        /**
          * The container holding the favorite
          * <P>Type: INTEGER</P>
          */
@@ -121,23 +199,6 @@
         static final String SPANY = "spanY";
 
         /**
-         * The type of the favorite
-         *
-         * <P>Type: INTEGER</P>
-         */
-        static final String ITEM_TYPE = "itemType";
-
-        /**
-         * The favorite is an application
-         */
-        static final int ITEM_TYPE_APPLICATION = 0;
-
-        /**
-         * The favorite is an application created shortcut
-         */
-        static final int ITEM_TYPE_SHORTCUT = 1;
-
-        /**
          * The favorite is a user created folder
          */
         static final int ITEM_TYPE_USER_FOLDER = 2;
@@ -180,43 +241,10 @@
          * value is 1, it is an application-created shortcut.
          * <P>Type: INTEGER</P>
          */
+        @Deprecated
         static final String IS_SHORTCUT = "isShortcut";
 
         /**
-         * The icon type.
-         * <P>Type: INTEGER</P>
-         */
-        static final String ICON_TYPE = "iconType";
-
-        /**
-         * The icon is a resource identified by a package name and an integer id.
-         */
-        static final int ICON_TYPE_RESOURCE = 0;
-
-        /**
-         * The icon is a bitmap.
-         */
-        static final int ICON_TYPE_BITMAP = 1;
-
-        /**
-         * The icon package name, if icon type is ICON_TYPE_RESOURCE.
-         * <P>Type: TEXT</P>
-         */
-        static final String ICON_PACKAGE = "iconPackage";
-
-        /**
-         * The icon resource id, if icon type is ICON_TYPE_RESOURCE.
-         * <P>Type: TEXT</P>
-         */
-        static final String ICON_RESOURCE = "iconResource";
-
-        /**
-         * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP.
-         * <P>Type: BLOB</P>
-         */
-        static final String ICON = "icon";
-
-        /**
          * The URI associated with the favorite. It is used, for instance, by
          * live folders to find the content provider.
          * <P>Type: TEXT</P>
diff --git a/src/com/android/launcher/LiveFolderIcon.java b/src/com/android/launcher/LiveFolderIcon.java
index 33cb0b7..14a4ee6 100644
--- a/src/com/android/launcher/LiveFolderIcon.java
+++ b/src/com/android/launcher/LiveFolderIcon.java
@@ -41,8 +41,8 @@
         final Resources resources = launcher.getResources();
         Drawable d = folderInfo.icon;
         if (d == null) {
-            resources.getDrawable(R.drawable.ic_launcher_folder);
-            d = Utilities.createIconThumbnail(d, launcher);
+            d = Utilities.createIconThumbnail(
+                    resources.getDrawable(R.drawable.ic_launcher_folder), launcher);
             folderInfo.filtered = true;
         }
         icon.setCompoundDrawablesWithIntrinsicBounds(null, d, null, null);
diff --git a/src/com/android/launcher/Search.java b/src/com/android/launcher/Search.java
index 71ab7ef..96c0022 100644
--- a/src/com/android/launcher/Search.java
+++ b/src/com/android/launcher/Search.java
@@ -16,71 +16,59 @@
 
 package com.android.launcher;
 
-import android.app.ISearchManager;
-import android.app.SearchManager;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
+import android.content.res.Configuration;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.server.search.SearchableInfo;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.View.OnClickListener;
 import android.view.View.OnKeyListener;
 import android.view.View.OnLongClickListener;
-import android.widget.AdapterView;
-import android.widget.AutoCompleteTextView;
-import android.widget.CursorAdapter;
-import android.widget.Filter;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.ImageButton;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.SimpleCursorAdapter;
 import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemSelectedListener;
 
-public class Search extends LinearLayout implements OnClickListener, OnKeyListener,
-        OnLongClickListener, TextWatcher, OnItemClickListener, OnItemSelectedListener {
+public class Search extends LinearLayout 
+        implements OnClickListener, OnKeyListener, OnLongClickListener {
+
+    // Speed at which the widget slides up/down, in pixels/ms.
+    private static final float ANIMATION_VELOCITY = 1.0f;
 
     private final String TAG = "SearchWidget";
 
-    private AutoCompleteTextView mSearchText;
-    private ImageButton mGoButton;
+    private Launcher mLauncher;
+
+    private TextView mSearchText;
     private ImageButton mVoiceButton;
-    private OnLongClickListener mLongClickListener;
-    
-    // Support for suggestions
-    private SuggestionsAdapter mSuggestionsAdapter;
-    private SearchableInfo mSearchable;
-    private String mSuggestionAction = null;
-    private Uri mSuggestionData = null;
-    private String mSuggestionQuery = null;
-    private int mItemSelected = -1;
-    
+
+    /** The animation that morphs the search widget to the search dialog. */
+    private Animation mMorphAnimation;
+
+    /** The animation that morphs the search widget back to its normal position. */
+    private Animation mUnmorphAnimation;
+
+    // These four are passed to Launcher.startSearch() when the search widget
+    // has finished morphing. They are instance variables to make it possible to update
+    // them while the widget is morphing.
+    private String mInitialQuery;
+    private boolean mSelectInitialQuery;    
+    private Bundle mAppSearchData;
+    private boolean mGlobalSearch;
+
     // For voice searching
     private Intent mVoiceSearchIntent;
 
-    private Rect mTempRect = new Rect();
-    private boolean mRestoreFocus = false;
-
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -89,293 +77,235 @@
      */
     public Search(Context context, AttributeSet attrs) {
         super(context, attrs);
+
+        Interpolator interpolator = new AccelerateDecelerateInterpolator();
+
+        mMorphAnimation = new ToParentOriginAnimation();
+        // no need to apply transformation before the animation starts,
+        // since the gadget is already in its normal place.
+        mMorphAnimation.setFillBefore(false);
+        // stay in the top position after the animation finishes
+        mMorphAnimation.setFillAfter(true);
+        mMorphAnimation.setInterpolator(interpolator);
+        mMorphAnimation.setAnimationListener(new Animation.AnimationListener() {
+            // The amount of time before the animation ends to show the search dialog.
+            private static final long TIME_BEFORE_ANIMATION_END = 80;
+            
+            // The runnable which we'll pass to our handler to show the search dialog.
+            private final Runnable mShowSearchDialogRunnable = new Runnable() {
+                public void run() {
+                    showSearchDialog();
+                }
+            };
+            
+            public void onAnimationEnd(Animation animation) { }
+            public void onAnimationRepeat(Animation animation) { }
+            public void onAnimationStart(Animation animation) {
+                // Make the search dialog show up ideally *just* as the animation reaches
+                // the top, to aid the illusion that the widget becomes the search dialog.
+                // Otherwise, there is a short delay when the widget reaches the top before
+                // the search dialog shows. We do this roughly 80ms before the animation ends.
+                getHandler().postDelayed(
+                        mShowSearchDialogRunnable,
+                        Math.max(mMorphAnimation.getDuration() - TIME_BEFORE_ANIMATION_END, 0));
+            }
+        });
+
+        mUnmorphAnimation = new FromParentOriginAnimation();
+        // stay in the top position until the animation starts
+        mUnmorphAnimation.setFillBefore(true);
+        // no need to apply transformation after the animation finishes,
+        // since the gadget is now back in its normal place.
+        mUnmorphAnimation.setFillAfter(false);
+        mUnmorphAnimation.setInterpolator(interpolator);
+        mUnmorphAnimation.setAnimationListener(new Animation.AnimationListener(){
+            public void onAnimationEnd(Animation animation) {
+                clearAnimation();
+            }
+            public void onAnimationRepeat(Animation animation) { }
+            public void onAnimationStart(Animation animation) { }
+        });
         
         mVoiceSearchIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
         mVoiceSearchIntent.putExtra(android.speech.RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                 android.speech.RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
     }
-    
+
     /**
-     * Implements OnClickListener (for button)
+     * Implements OnClickListener.
      */
     public void onClick(View v) {
-        if (v == mGoButton) {
-            query();
-        } else if (v == mVoiceButton) {
-            try {
-                getContext().startActivity(mVoiceSearchIntent);
-            } catch (ActivityNotFoundException ex) {
-                // Should not happen, since we check the availability of
-                // voice search before showing the button. But just in case...
-                Log.w(TAG, "Could not find voice search activity");
+        if (v == mVoiceButton) {
+            startVoiceSearch();
+        } else {
+            mLauncher.onSearchRequested();
+        }
+    }
+
+    private void startVoiceSearch() {
+        try {
+            getContext().startActivity(mVoiceSearchIntent);
+        } catch (ActivityNotFoundException ex) {
+            // Should not happen, since we check the availability of
+            // voice search before showing the button. But just in case...
+            Log.w(TAG, "Could not find voice search activity");
+        }
+    }
+
+    /**
+     * Sets the query text. The query field is not editable, instead we forward
+     * the key events to the launcher, which keeps track of the text, 
+     * calls setQuery() to show it, and gives it to the search dialog.
+     */
+    public void setQuery(String query) {
+        mSearchText.setText(query, TextView.BufferType.NORMAL);
+    }
+
+    /**
+     * Morph the search gadget to the search dialog.
+     * See {@link Activity.startSearch()} for the arguments.
+     */
+    public void startSearch(String initialQuery, boolean selectInitialQuery, 
+            Bundle appSearchData, boolean globalSearch) {
+        mInitialQuery = initialQuery;
+        mSelectInitialQuery = selectInitialQuery;
+        mAppSearchData = appSearchData;
+        mGlobalSearch = globalSearch;
+        
+        if (isAtTop()) {
+            showSearchDialog();
+        } else {
+            // Call up the keyboard before we actually call the search dialog so that it
+            // (hopefully) animates in at about the same time as the widget animation, and
+            // so that it becomes available as soon as possible. Only do this if a hard
+            // keyboard is not currently available.
+            if (getContext().getResources().getConfiguration().hardKeyboardHidden ==
+                    Configuration.HARDKEYBOARDHIDDEN_YES) {
+                // Make sure the text field is not focusable, so it's not responsible for
+                // causing the whole view to shift up to accommodate the keyboard.
+                mSearchText.setFocusable(false);
+                
+                InputMethodManager inputManager = (InputMethodManager)
+                        getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+                inputManager.showSoftInputUnchecked(0, null);
+            }
+            
+            // Start the animation, unless it has already started.
+            if (getAnimation() != mMorphAnimation) {
+                mMorphAnimation.setDuration(getAnimationDuration());
+                startAnimation(mMorphAnimation);
             }
         }
     }
 
-    private void query() {
-        String query = mSearchText.getText().toString();
-        if (TextUtils.getTrimmedLength(mSearchText.getText()) == 0) {
-            return;
+    /**
+     * Shows the system search dialog immediately, without any animation.
+     */
+    private void showSearchDialog() {
+        mLauncher.showSearchDialog(
+                mInitialQuery, mSelectInitialQuery, mAppSearchData, mGlobalSearch);
+    }
+
+    /**
+     * Restore the search gadget to its normal position.
+     * 
+     * @param animate Whether to animate the movement of the gadget.
+     */
+    public void stopSearch(boolean animate) {
+        setQuery("");
+        
+        // Set the search field back to focusable after making it unfocusable in
+        // startSearch, so that the home screen doesn't try to shift around when the
+        // keyboard comes up.
+        mSearchText.setFocusable(true);
+        // Only restore if we are not already restored.
+        if (getAnimation() == mMorphAnimation) {
+            if (animate && !isAtTop()) {
+                mUnmorphAnimation.setDuration(getAnimationDuration());
+                startAnimation(mUnmorphAnimation);
+            } else {
+                clearAnimation();
+            }
         }
-        Bundle appData = new Bundle();
-        appData.putString(SearchManager.SOURCE, "launcher-widget");
-        sendLaunchIntent(Intent.ACTION_SEARCH, null, query, appData, 0, null, mSearchable);
-        clearQuery();
+    }
+
+    private boolean isAtTop() {
+        return getTop() == 0;
+    }
+
+    private int getAnimationDuration() {
+        return (int) (getTop() / ANIMATION_VELOCITY);
+    }
+
+    /**
+     * Modify clearAnimation() to invalidate the parent. This works around
+     * an issue where the region where the end of the animation placed the view
+     * was not redrawn after clearing the animation.
+     */
+    @Override
+    public void clearAnimation() {
+        Animation animation = getAnimation();
+        if (animation != null) {
+            super.clearAnimation();
+            if (animation.hasEnded() 
+                    && animation.getFillAfter()
+                    && animation.willChangeBounds()) {
+                ((View) getParent()).invalidate();
+            } else {
+                invalidate();
+            }
+        }
     }
     
-    /**
-     * Assemble a search intent and send it.
-     * 
-     * This is copied from SearchDialog.
-     *
-     * @param action The intent to send, typically Intent.ACTION_SEARCH
-     * @param data The data for the intent
-     * @param query The user text entered (so far)
-     * @param appData The app data bundle (if supplied)
-     * @param actionKey If the intent was triggered by an action key, e.g. KEYCODE_CALL, it will
-     * be sent here.  Pass KeyEvent.KEYCODE_UNKNOWN for no actionKey code.
-     * @param actionMsg If the intent was triggered by an action key, e.g. KEYCODE_CALL, the
-     * corresponding tag message will be sent here.  Pass null for no actionKey message.
-     * @param si Reference to the current SearchableInfo.  Passed here so it can be used even after
-     * we've called dismiss(), which attempts to null mSearchable.
-     */
-    private void sendLaunchIntent(final String action, final Uri data, final String query,
-            final Bundle appData, int actionKey, final String actionMsg, final SearchableInfo si) {
-        Intent launcher = new Intent(action);
-        launcher.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-        if (query != null) {
-            launcher.putExtra(SearchManager.QUERY, query);
-        }
-
-        if (data != null) {
-            launcher.setData(data);
-        }
-
-        if (appData != null) {
-            launcher.putExtra(SearchManager.APP_DATA, appData);
-        }
-
-        // add launch info (action key, etc.)
-        if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
-            launcher.putExtra(SearchManager.ACTION_KEY, actionKey);
-            launcher.putExtra(SearchManager.ACTION_MSG, actionMsg);
-        }
-
-        // attempt to enforce security requirement (no 3rd-party intents)
-        if (si != null) {
-            launcher.setComponent(si.mSearchActivity);
-        }
-
-        getContext().startActivity(launcher);
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        if (!hasWindowFocus && hasFocus()) {
-            mRestoreFocus = true;
-        }
-
-        super.onWindowFocusChanged(hasWindowFocus);
-
-        if (hasWindowFocus && mRestoreFocus) {
-            if (isInTouchMode()) {
-                final AutoCompleteTextView searchText = mSearchText;
-                searchText.setSelectAllOnFocus(false);
-                searchText.requestFocusFromTouch();
-                searchText.setSelectAllOnFocus(true);
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (!event.isSystem() && 
+                (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
+                (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
+            // Forward key events to Launcher, which will forward text 
+            // to search dialog
+            switch (event.getAction()) {
+                case KeyEvent.ACTION_DOWN:
+                    return mLauncher.onKeyDown(keyCode, event);
+                case KeyEvent.ACTION_MULTIPLE:
+                    return mLauncher.onKeyMultiple(keyCode, event.getRepeatCount(), event);
+                case KeyEvent.ACTION_UP:
+                    return mLauncher.onKeyUp(keyCode, event);
             }
-            mRestoreFocus = false;
         }
-    }
-
-    /**
-     * Implements TextWatcher (for EditText)
-     */
-    public void beforeTextChanged(CharSequence s, int start, int before, int after) { 
-    }
-
-    /**
-     * Implements TextWatcher (for EditText)
-     */
-    public void onTextChanged(CharSequence s, int start, int before, int after) {
-        // enable the button if we have one or more non-space characters
-        boolean enabled = TextUtils.getTrimmedLength(mSearchText.getText()) != 0;
-        mGoButton.setEnabled(enabled);
-        mGoButton.setFocusable(enabled);
-    }
-
-    /**
-     * Implements TextWatcher (for EditText)
-     */
-    public void afterTextChanged(Editable s) {
-    }
-
-    /**
-     * Implements OnKeyListener (for EditText and for button)
-     * 
-     * This plays some games with state in order to "soften" the strength of suggestions
-     * presented.  Suggestions should not be used unless the user specifically navigates to them
-     * (or clicks them, in which case it's obvious).  This is not the way that AutoCompleteTextBox
-     * normally works.
-     */
-    public final boolean onKey(View v, int keyCode, KeyEvent event) {
-        if (v == mSearchText) {
-            boolean searchTrigger = (keyCode == KeyEvent.KEYCODE_ENTER || 
-                    keyCode == KeyEvent.KEYCODE_SEARCH ||
-                    keyCode == KeyEvent.KEYCODE_DPAD_CENTER);
-            if (event.getAction() == KeyEvent.ACTION_UP) {
-//              Log.d(TAG, "onKey() ACTION_UP isPopupShowing:" + mSearchText.isPopupShowing());
-                if (!mSearchText.isPopupShowing()) {
-                    if (searchTrigger) {
-                        query();
-                        return true;
-                    }
-                }
-            } else {
-//              Log.d(TAG, "onKey() ACTION_DOWN isPopupShowing:" + mSearchText.isPopupShowing() +
-//                      " mItemSelected="+ mItemSelected);
-                if (searchTrigger && mItemSelected < 0) {
-                    query();
-                    return true;
-                }
-            }
-        } else if (v == mGoButton || v == mVoiceButton) {
-            boolean handled = false;
-            if (!event.isSystem() && 
-                    (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
-                    (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
-                    (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
-                    (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
-                    (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
-                if (mSearchText.requestFocus()) {
-                    handled = mSearchText.dispatchKeyEvent(event);
-                }
-            }
-            return handled;
-        }
-
         return false;
     }
-    
-    @Override
-    public void setOnLongClickListener(OnLongClickListener l) {
-        super.setOnLongClickListener(l);
-        mLongClickListener = l;
-    }
-    
+
     /**
-     * Implements OnLongClickListener (for button)
+     * Implements OnLongClickListener to pass long clicks on child views 
+     * to the widget. This makes it possible to pick up the widget by long
+     * clicking on the text field or a button.
      */
     public boolean onLongClick(View v) {
-        // Pretend that a long press on a child view is a long press on the search widget
-        if (mLongClickListener != null) {
-            return mLongClickListener.onLongClick(this);
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        // Request focus unless the user tapped on the voice search button
-        final int x = (int) ev.getX();
-        final int y = (int) ev.getY();
-        final Rect frame = mTempRect;
-        mVoiceButton.getHitRect(frame);
-        if (!frame.contains(x, y)) {
-            requestFocusFromTouch();
-        }
-        return super.onInterceptTouchEvent(ev);
-    }
-    
-    /**
-     * In order to keep things simple, the external trigger will clear the query just before
-     * focusing, so as to give you a fresh query.  This way we eliminate any sources of
-     * accidental query launching.
-     */
-    public void clearQuery() {
-        mSearchText.setText(null);
+        return performLongClick();
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mSearchText = (AutoCompleteTextView) findViewById(R.id.input);
-        // TODO: This can be confusing when the user taps the text field to give the focus
-        // (it is not necessary but I ran into this issue several times myself)
-        // mTitleInput.setOnClickListener(this);
-        mSearchText.setOnKeyListener(this);
-        mSearchText.addTextChangedListener(this);
-
-        mGoButton = (ImageButton) findViewById(R.id.search_go_btn);
+        mSearchText = (TextView) findViewById(R.id.search_src_text);
         mVoiceButton = (ImageButton) findViewById(R.id.search_voice_btn);
-        mGoButton.setOnClickListener(this);
+
+        mSearchText.setOnKeyListener(this);
+
+        mSearchText.setOnClickListener(this);
         mVoiceButton.setOnClickListener(this);
-        mGoButton.setOnKeyListener(this);
-        mVoiceButton.setOnKeyListener(this);
-        
+        setOnClickListener(this);        
+
         mSearchText.setOnLongClickListener(this);
-        mGoButton.setOnLongClickListener(this);
         mVoiceButton.setOnLongClickListener(this);
-        
-        // disable the button since we start out w/empty input
-        mGoButton.setEnabled(false);
-        mGoButton.setFocusable(false);
-        
-        configureSearchableInfo();
-        configureSuggestions();
+
         configureVoiceSearchButton();
     }
-    
-    /**
-     * Cache of popup padding value after read from {@link Resources}.
-     */
-    private static float mPaddingInset = -1;
-    
-    /**
-     * When our size is changed, pass down adjusted width and offset values to
-     * correctly center the {@link AutoCompleteTextView} popup and include our
-     * padding.
-     */
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        if (changed) {
-            if (mPaddingInset == -1) {
-                mPaddingInset = getResources().getDimension(R.dimen.search_widget_inset);
-            }
-            
-            // Fill entire width of widget, minus padding inset
-            float paddedWidth = getWidth() - (mPaddingInset * 2);
-            float paddedOffset = -(mSearchText.getLeft() - mPaddingInset);
-                
-            mSearchText.setDropDownWidth((int) paddedWidth);
-            mSearchText.setDropDownHorizontalOffset((int) paddedOffset);
-        }
-    }
-    
-    /**
-     * Read the searchable info from the search manager
-     */
-    private void configureSearchableInfo() {
-        ISearchManager sms;
-        SearchableInfo searchable;
-        sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE));
-        try {
-            // TODO null isn't the published use of this API, but it works when global=true
-            // TODO better implementation:  defer all of this, let Home set it up 
-            searchable = sms.getSearchableInfo(null, true);
-        } catch (RemoteException e) {
-            searchable = null;
-        }
-        if (searchable == null) {
-            // no suggestions so just get out (no need to continue)
-            return;
-        }
-        mSearchable = searchable;
-    }
-    
+
     /**
      * If appropriate & available, configure voice search
      * 
@@ -384,346 +314,45 @@
      * voice search.
      */
     private void configureVoiceSearchButton() {
-        boolean voiceSearchVisible = false;
-        if (mSearchable.getVoiceSearchEnabled() && mSearchable.getVoiceSearchLaunchWebSearch()) {
-            // Enable the voice search button if there is an activity that can handle it
-            PackageManager pm = getContext().getPackageManager();
-            ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
-                    PackageManager.MATCH_DEFAULT_ONLY);
-            voiceSearchVisible = ri != null;
-        }
-        
+        // Enable the voice search button if there is an activity that can handle it
+        PackageManager pm = getContext().getPackageManager();
+        ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        boolean voiceSearchVisible = ri != null;
+
         // finally, set visible state of voice search button, as appropriate
         mVoiceButton.setVisibility(voiceSearchVisible ? View.VISIBLE : View.GONE);
     }
-     
-    /** The rest of the class deals with providing search suggestions */
-    
+
     /**
-     * Set up the suggestions provider mechanism
+     * Sets the {@link Launcher} that this gadget will call on to display the search dialog. 
      */
-    private void configureSuggestions() {
-        // get SearchableInfo
-        
-        mSearchText.setOnItemClickListener(this);
-        mSearchText.setOnItemSelectedListener(this);
-        
-        // attach the suggestions adapter
-        mSuggestionsAdapter = new SuggestionsAdapter(mContext, 
-                com.android.internal.R.layout.search_dropdown_item_2line, null,
-                SuggestionsAdapter.TWO_LINE_FROM, SuggestionsAdapter.TWO_LINE_TO, mSearchable);
-        mSearchText.setAdapter(mSuggestionsAdapter);
+    public void setLauncher(Launcher launcher) {
+        mLauncher = launcher;
     }
-    
-    /**
-     * Remove internal cursor references when detaching from window which
-     * prevents {@link Context} leaks.
-     */
-    @Override
-    public void onDetachedFromWindow() {
-        if (mSuggestionsAdapter != null) {
-            mSuggestionsAdapter.changeCursor(null);
-            mSuggestionsAdapter = null;
-        }
-    }
-    
-    /**
-     * Implements OnItemClickListener
-     */
-    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-//      Log.d(TAG, "onItemClick() position " + position);
-        launchSuggestion(mSuggestionsAdapter, position);
-    }
-    
+
     /** 
-     * Implements OnItemSelectedListener
+     * Moves the view to the top left corner of its parent.
      */
-     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-//       Log.d(TAG, "onItemSelected() position " + position);
-         mItemSelected = position;
-     }
-
-     /** 
-      * Implements OnItemSelectedListener
-      */
-     public void onNothingSelected(AdapterView<?> parent) {
-//       Log.d(TAG, "onNothingSelected()");
-         mItemSelected = -1;
-     }
-
-    /**
-     * Code to launch a suggestion query.  
-     * 
-     * This is copied from SearchDialog.
-     * 
-     * @param ca The CursorAdapter containing the suggestions
-     * @param position The suggestion we'll be launching from
-     * 
-     * @return Returns true if a successful launch, false if could not (e.g. bad position)
-     */
-    private boolean launchSuggestion(CursorAdapter ca, int position) {
-        if (ca != null) {
-            Cursor c = ca.getCursor();
-            if ((c != null) && c.moveToPosition(position)) {
-                setupSuggestionIntent(c, mSearchable);
-                
-                SearchableInfo si = mSearchable;
-                String suggestionAction = mSuggestionAction;
-                Uri suggestionData = mSuggestionData;
-                String suggestionQuery = mSuggestionQuery;
-                sendLaunchIntent(suggestionAction, suggestionData, suggestionQuery, null,
-                                    KeyEvent.KEYCODE_UNKNOWN, null, si);
-                clearQuery();
-                return true;
-            }
-        }
-        return false;
-    }
-    
-    /**
-     * When a particular suggestion has been selected, perform the various lookups required
-     * to use the suggestion.  This includes checking the cursor for suggestion-specific data,
-     * and/or falling back to the XML for defaults;  It also creates REST style Uri data when
-     * the suggestion includes a data id.
-     * 
-     * NOTE:  Return values are in member variables mSuggestionAction, mSuggestionData and
-     * mSuggestionQuery.
-     * 
-     * This is copied from SearchDialog.
-     * 
-     * @param c The suggestions cursor, moved to the row of the user's selection
-     * @param si The searchable activity's info record
-     */
-    void setupSuggestionIntent(Cursor c, SearchableInfo si) {
-        try {
-            // use specific action if supplied, or default action if supplied, or fixed default
-            mSuggestionAction = null;
-            int column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
-            if (column >= 0) {
-                final String action = c.getString(column);
-                if (action != null) {
-                    mSuggestionAction = action;
-                }
-            }
-            if (mSuggestionAction == null) {
-                mSuggestionAction = si.getSuggestIntentAction();
-            }
-            if (mSuggestionAction == null) {
-                mSuggestionAction = Intent.ACTION_SEARCH;
-            }
-            
-            // use specific data if supplied, or default data if supplied
-            String data = null;
-            column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
-            if (column >= 0) {
-                final String rowData = c.getString(column);
-                if (rowData != null) {
-                    data = rowData;
-                }
-            }
-            if (data == null) {
-                data = si.getSuggestIntentData();
-            }
-            
-            // then, if an ID was provided, append it.
-            if (data != null) {
-                column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
-                if (column >= 0) {
-                    final String id = c.getString(column);
-                    if (id != null) {
-                        data = data + "/" + Uri.encode(id);
-                    }
-                }
-            }
-            mSuggestionData = (data == null) ? null : Uri.parse(data);
-            
-            mSuggestionQuery = null;
-            column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
-            if (column >= 0) {
-                final String query = c.getString(column);
-                if (query != null) {
-                    mSuggestionQuery = query;
-                }
-            }
-        } catch (RuntimeException e ) {
-            int rowNum;
-            try {                       // be really paranoid now
-                rowNum = c.getPosition();
-            } catch (RuntimeException e2 ) {
-                rowNum = -1;
-            }
-            Log.w(TAG, "Search Suggestions cursor at row " + rowNum + 
-                            " returned exception" + e.toString());
-        }
-    }
-
-    SearchAutoCompleteTextView getSearchInputField() {
-        return (SearchAutoCompleteTextView) mSearchText;
-    }
-
-    /**
-     * This class provides the filtering-based interface to suggestions providers.
-     * It is hardwired in a couple of places to support GoogleSearch - for example, it supports
-     * two-line suggestions, but it does not support icons.
-     */
-    private static class SuggestionsAdapter extends SimpleCursorAdapter {
-        public final static String[] TWO_LINE_FROM =    {SearchManager.SUGGEST_COLUMN_TEXT_1,
-                                                         SearchManager.SUGGEST_COLUMN_TEXT_2 };
-        public final static int[] TWO_LINE_TO =         {com.android.internal.R.id.text1, 
-                                                         com.android.internal.R.id.text2};
-        
-        private final String TAG = "SuggestionsAdapter";
-        
-        Filter mFilter;
-        SearchableInfo mSearchable;
-        private Resources mProviderResources;
-        String[] mFromStrings;
-
-        public SuggestionsAdapter(Context context, int layout, Cursor c,
-                String[] from, int[] to, SearchableInfo searchable) {
-            super(context, layout, c, from, to);
-            mFromStrings = from;
-            mSearchable = searchable;
-            
-            // set up provider resources (gives us icons, etc.)
-            Context activityContext = mSearchable.getActivityContext(mContext);
-            Context providerContext = mSearchable.getProviderContext(mContext, activityContext);
-            mProviderResources = providerContext.getResources();
-        }
-        
-        /**
-         * Use the search suggestions provider to obtain a live cursor.  This will be called
-         * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
-         * The results will be processed in the UI thread and changeCursor() will be called.
-         */
+    private class ToParentOriginAnimation extends Animation {
         @Override
-        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
-            String query = (constraint == null) ? "" : constraint.toString();
-            return getSuggestions(mSearchable, query);
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            float dx = -getLeft() * interpolatedTime;
+            float dy = -getTop() * interpolatedTime;
+            t.getMatrix().setTranslate(dx, dy);
         }
-        
-        /**
-         * Overriding this allows us to write the selected query back into the box.
-         * NOTE:  This is a vastly simplified version of SearchDialog.jamQuery() and does
-         * not universally support the search API.  But it is sufficient for Google Search.
-         */
-        @Override
-        public CharSequence convertToString(Cursor cursor) {
-            CharSequence result = null;
-            if (cursor != null) {
-                int column = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
-                if (column >= 0) {
-                    final String query = cursor.getString(column);
-                    if (query != null) {
-                        result = query;
-                    }
-                }
-            }
-            return result;
-        }
-
-        /**
-         * Get the query cursor for the search suggestions.
-         * 
-         * TODO this is functionally identical to the version in SearchDialog.java.  Perhaps it 
-         * could be hoisted into SearchableInfo or some other shared spot.
-         * 
-         * @param query The search text entered (so far)
-         * @return Returns a cursor with suggestions, or null if no suggestions 
-         */
-        private Cursor getSuggestions(final SearchableInfo searchable, final String query) {
-            Cursor cursor = null;
-            if (searchable.getSuggestAuthority() != null) {
-                try {
-                    StringBuilder uriStr = new StringBuilder("content://");
-                    uriStr.append(searchable.getSuggestAuthority());
-
-                    // if content path provided, insert it now
-                    final String contentPath = searchable.getSuggestPath();
-                    if (contentPath != null) {
-                        uriStr.append('/');
-                        uriStr.append(contentPath);
-                    }
-
-                    // append standard suggestion query path 
-                    uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY);
-
-                    // inject query, either as selection args or inline
-                    String[] selArgs = null;
-                    if (searchable.getSuggestSelection() != null) {    // use selection if provided
-                        selArgs = new String[] {query};
-                    } else {
-                        uriStr.append('/');                             // no sel, use REST pattern
-                        uriStr.append(Uri.encode(query));
-                    }
-
-                    // finally, make the query
-                    cursor = mContext.getContentResolver().query(
-                                                        Uri.parse(uriStr.toString()), null, 
-                                                        searchable.getSuggestSelection(), selArgs,
-                                                        null);
-                } catch (RuntimeException e) {
-                    Log.w(TAG, "Search Suggestions query returned exception " + e.toString());
-                    cursor = null;
-                }
-            }
-            
-            return cursor;
-        }
-
-        /**
-         * Overriding this allows us to affect the way that an icon is loaded.  Specifically,
-         * we can be more controlling about the resource path (and allow icons to come from other
-         * packages).
-         * 
-         * TODO: This is 100% identical to the version in SearchDialog.java
-         *
-         * @param v ImageView to receive an image
-         * @param value the value retrieved from the cursor
-         */
-        @Override
-        public void setViewImage(ImageView v, String value) {
-            int resID;
-            Drawable img = null;
-
-            try {
-                resID = Integer.parseInt(value);
-                if (resID != 0) {
-                    img = mProviderResources.getDrawable(resID);
-                }
-            } catch (NumberFormatException nfe) {
-                // img = null;
-            } catch (NotFoundException e2) {
-                // img = null;
-            }
-            
-            // finally, set the image to whatever we've gotten
-            v.setImageDrawable(img);
-        }
-        
-        /**
-         * This method is overridden purely to provide a bit of protection against
-         * flaky content providers.
-         * 
-         * TODO: This is 100% identical to the version in SearchDialog.java
-         * 
-         * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
-         */
-        @Override 
-        public View getView(int position, View convertView, ViewGroup parent) {
-            try {
-                return super.getView(position, convertView, parent);
-            } catch (RuntimeException e) {
-                Log.w(TAG, "Search Suggestions cursor returned exception " + e.toString());
-                // what can I return here?
-                View v = newView(mContext, mCursor, parent);
-                if (v != null) {
-                    TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1);
-                    tv.setText(e.toString());
-                }
-                return v;
-            }
-        }
-
     }
+
+    /** 
+     * Moves the view from the top left corner of its parent.
+     */
+    private class FromParentOriginAnimation extends Animation {
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            float dx = -getLeft() * (1.0f - interpolatedTime);
+            float dy = -getTop() * (1.0f - interpolatedTime);
+            t.getMatrix().setTranslate(dx, dy);
+        }
+    }
+
 }
diff --git a/src/com/android/launcher/UninstallShortcutReceiver.java b/src/com/android/launcher/UninstallShortcutReceiver.java
index e490f9c..bf71815 100644
--- a/src/com/android/launcher/UninstallShortcutReceiver.java
+++ b/src/com/android/launcher/UninstallShortcutReceiver.java
@@ -22,6 +22,7 @@
 import android.content.ContentResolver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.widget.Toast;
 
 import java.net.URISyntaxException;
 
@@ -34,7 +35,7 @@
         if (intent != null && name != null) {
             final ContentResolver cr = context.getContentResolver();
             Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
-                new String[] { LauncherSettings.Favorites.ID, LauncherSettings.Favorites.INTENT },
+                new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT },
                 LauncherSettings.Favorites.TITLE + "=?", new String[] { name }, null);
 
             final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
@@ -62,7 +63,11 @@
                 c.close();
             }
 
-            if (changed) cr.notifyChange(LauncherSettings.Favorites.CONTENT_URI, null);
+            if (changed) {
+                cr.notifyChange(LauncherSettings.Favorites.CONTENT_URI, null);
+                Toast.makeText(context, context.getString(R.string.shortcut_uninstalled, name),
+                        Toast.LENGTH_SHORT).show();
+            }
         }
     }
 }
diff --git a/src/com/android/launcher/UserFolder.java b/src/com/android/launcher/UserFolder.java
index 1044e96..6cdfed9 100644
--- a/src/com/android/launcher/UserFolder.java
+++ b/src/com/android/launcher/UserFolder.java
@@ -1,6 +1,7 @@
 package com.android.launcher;
 
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -33,6 +34,10 @@
         return (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
                 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && item.container != mInfo.id;
     }
+    
+    public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+        return null;
+    }
 
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
         final ApplicationInfo item = (ApplicationInfo) dragInfo;
diff --git a/src/com/android/launcher/Utilities.java b/src/com/android/launcher/Utilities.java
index cb8976c..33b084b 100644
--- a/src/com/android/launcher/Utilities.java
+++ b/src/com/android/launcher/Utilities.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher;
 
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.PaintDrawable;
 import android.graphics.Bitmap;
@@ -80,24 +81,35 @@
     static Drawable createIconThumbnail(Drawable icon, Context context) {
         if (sIconWidth == -1) {
             final Resources resources = context.getResources();
-            sIconWidth = sIconHeight = (int) resources.getDimension(
-                    android.R.dimen.app_icon_size);
+            sIconWidth = sIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size);
         }
 
         int width = sIconWidth;
         int height = sIconHeight;
 
-        final int iconWidth = icon.getIntrinsicWidth();
-        final int iconHeight = icon.getIntrinsicHeight();
-
+        float scale = 1.0f;
         if (icon instanceof PaintDrawable) {
             PaintDrawable painter = (PaintDrawable) icon;
             painter.setIntrinsicWidth(width);
             painter.setIntrinsicHeight(height);
+        } else if (icon instanceof BitmapDrawable) {
+            float displayDensity = context.getResources().getDisplayMetrics().density;
+            BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+            Bitmap bitmap = bitmapDrawable.getBitmap();
+            float iconDensity = bitmap.getDensityScale();
+            scale = displayDensity / iconDensity;
+
+            // Scale the bitmap to the screen density size if it's not loaded at the same density.
+            if (scale != 1.0f) {
+                icon = bitmapDrawable = new BitmapDrawable(bitmap);
+                bitmapDrawable.setDensityScale(scale);
+            }
         }
+        int iconWidth = icon.getIntrinsicWidth();
+        int iconHeight = icon.getIntrinsicHeight();
 
         if (width > 0 && height > 0) {
-            if (width < iconWidth || height < iconHeight) {
+            if (width < iconWidth || height < iconHeight || scale != 1.0f) {
                 final float ratio = (float) iconWidth / iconHeight;
 
                 if (iconWidth > iconHeight) {
diff --git a/src/com/android/launcher/Workspace.java b/src/com/android/launcher/Workspace.java
index 359767a..d91519c 100644
--- a/src/com/android/launcher/Workspace.java
+++ b/src/com/android/launcher/Workspace.java
@@ -48,7 +48,7 @@
  */
 public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
     private static final int INVALID_SCREEN = -1;
-
+    
     /**
      * The velocity at which a fling gesture will cause us to snap to the next screen
      */
@@ -75,6 +75,11 @@
      * CellInfo for the cell that is currently being dragged
      */
     private CellLayout.CellInfo mDragInfo;
+    
+    /**
+     * Target drop area calculated during last acceptDrop call.
+     */
+    private int[] mTargetCell = null;
 
     private float mLastMotionX;
     private float mLastMotionY;
@@ -88,8 +93,14 @@
 
     private Launcher mLauncher;
     private DragController mDragger;
-
+    
+    /**
+     * Cache of vacant cells, used during drag events and invalidated as needed.
+     */
+    private CellLayout.CellInfo mVacantCache = null;
+    
     private int[] mTempCell = new int[2];
+    private int[] mTempEstimate = new int[2];
 
     private boolean mAllowLongPress;
     private boolean mLocked;
@@ -261,6 +272,7 @@
      * @param currentScreen
      */
     void setCurrentScreen(int currentScreen) {
+        clearVacantCache();
         mCurrentScreen = Math.max(0, Math.min(currentScreen, getChildCount() - 1));
         scrollTo(mCurrentScreen * getWidth(), 0);
         invalidate();
@@ -334,6 +346,8 @@
             throw new IllegalStateException("The screen must be >= 0 and < " + getChildCount());
         }
 
+        clearVacantCache();
+
         final CellLayout group = (CellLayout) getChildAt(screen);
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
         if (lp == null) {
@@ -363,11 +377,18 @@
     CellLayout.CellInfo findAllVacantCells(boolean[] occupied) {
         CellLayout group = (CellLayout) getChildAt(mCurrentScreen);
         if (group != null) {
-            return group.findAllVacantCells(occupied);
+            return group.findAllVacantCells(occupied, null);
         }
         return null;
     }
 
+    private void clearVacantCache() {
+        if (mVacantCache != null) {
+            mVacantCache.clearVacantCells();
+            mVacantCache = null;
+        }
+    }
+    
     /**
      * Returns the coordinate of a vacant cell for the current screen.
      */
@@ -446,6 +467,11 @@
     }
 
     @Override
+    public boolean isOpaque() {
+        return !mWallpaper.hasAlpha();
+    }
+
+    @Override
     protected void dispatchDraw(Canvas canvas) {
         boolean restore = false;
 
@@ -609,7 +635,7 @@
     }
 
     @Override
-    public void addFocusables(ArrayList<View> views, int direction) {
+    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
         if (mLauncher.isDrawerDown()) {
             final Folder openFolder = getOpenFolder();
             if (openFolder == null) {
@@ -822,6 +848,9 @@
     }
 
     void snapToScreen(int whichScreen) {
+        if (!mScroller.isFinished()) return;
+
+        clearVacantCache();
         enableChildrenCache();
 
         whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
@@ -890,7 +919,7 @@
     }
 
     public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset, Object dragInfo) {
-        final CellLayout cellLayout = (CellLayout) getChildAt(mCurrentScreen);
+        final CellLayout cellLayout = getCurrentDropLayout();
         if (source != this) {
             onDropExternal(x - xOffset, y - yOffset, dragInfo, cellLayout);
         } else {
@@ -902,7 +931,9 @@
                     originalCellLayout.removeView(cell);
                     cellLayout.addView(cell);
                 }
-                cellLayout.onDropChild(cell, x - xOffset, y - yOffset);
+                mTargetCell = estimateDropCell(x - xOffset, y - yOffset,
+                        mDragInfo.spanX, mDragInfo.spanY, cell, cellLayout, mTargetCell);
+                cellLayout.onDropChild(cell, mTargetCell);
 
                 final ItemInfo info = (ItemInfo)cell.getTag();
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
@@ -914,6 +945,7 @@
 
     public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
             Object dragInfo) {
+        clearVacantCache();
     }
 
     public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
@@ -922,6 +954,7 @@
 
     public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
             Object dragInfo) {
+        clearVacantCache();
     }
 
     private void onDropExternal(int x, int y, Object dragInfo, CellLayout cellLayout) {
@@ -955,7 +988,8 @@
 
         cellLayout.addView(view, insertAtFirst ? 0 : -1);
         view.setOnLongClickListener(mLongClickListener);
-        cellLayout.onDropChild(view, x, y);
+        mTargetCell = estimateDropCell(x, y, 1, 1, view, cellLayout, mTargetCell);
+        cellLayout.onDropChild(view, mTargetCell);
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
 
         final LauncherModel model = Launcher.getModel();
@@ -963,18 +997,82 @@
         LauncherModel.addOrMoveItemInDatabase(mLauncher, info,
                 LauncherSettings.Favorites.CONTAINER_DESKTOP, mCurrentScreen, lp.cellX, lp.cellY);
     }
-
-    public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
-            Object dragInfo) {
-
-        final CellLayout.CellInfo cellInfo = mDragInfo;
-        int cellHSpan = cellInfo == null ? 1 : cellInfo.spanX;
-        int cellVSpan = cellInfo == null ? 1 : cellInfo.spanY;
-
-        return ((CellLayout) getChildAt(mCurrentScreen)).acceptChildDrop(x - xOffset, y - yOffset,
-                cellHSpan, cellVSpan, cellInfo == null ? null : cellInfo.cell);
+    
+    /**
+     * Return the current {@link CellLayout}, correctly picking the destination
+     * screen while a scroll is in progress.
+     */
+    private CellLayout getCurrentDropLayout() {
+        int index = mScroller.isFinished() ? mCurrentScreen : mNextScreen;
+        return (CellLayout) getChildAt(index);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public boolean acceptDrop(DragSource source, int x, int y,
+            int xOffset, int yOffset, Object dragInfo) {
+        final CellLayout layout = getCurrentDropLayout();
+        final CellLayout.CellInfo cellInfo = mDragInfo;
+        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
+        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
+
+        if (mVacantCache == null) {
+            final View ignoreView = cellInfo == null ? null : cellInfo.cell;
+            mVacantCache = layout.findAllVacantCells(null, ignoreView);
+        }
+
+        return mVacantCache.findCellForSpan(mTempEstimate, spanX, spanY, false);
+    }
+    
+    /**
+     * {@inheritDoc}
+     */
+    public Rect estimateDropLocation(DragSource source, int x, int y,
+            int xOffset, int yOffset, Object dragInfo, Rect recycle) {
+        final CellLayout layout = getCurrentDropLayout();
+        
+        final CellLayout.CellInfo cellInfo = mDragInfo;
+        final int spanX = cellInfo == null ? 1 : cellInfo.spanX;
+        final int spanY = cellInfo == null ? 1 : cellInfo.spanY;
+        final View ignoreView = cellInfo == null ? null : cellInfo.cell;
+        
+        final Rect location = recycle != null ? recycle : new Rect();
+        
+        // Find drop cell and convert into rectangle
+        int[] dropCell = estimateDropCell(x - xOffset, y - yOffset,
+                spanX, spanY, ignoreView, layout, mTempCell);
+        
+        if (dropCell == null) {
+            return null;
+        }
+        
+        layout.cellToPoint(dropCell[0], dropCell[1], mTempEstimate);
+        location.left = mTempEstimate[0];
+        location.top = mTempEstimate[1];
+        
+        layout.cellToPoint(dropCell[0] + spanX, dropCell[1] + spanY, mTempEstimate);
+        location.right = mTempEstimate[0];
+        location.bottom = mTempEstimate[1];
+        
+        return location;
+    }
+
+    /**
+     * Calculate the nearest cell where the given object would be dropped.
+     */
+    private int[] estimateDropCell(int pixelX, int pixelY,
+            int spanX, int spanY, View ignoreView, CellLayout layout, int[] recycle) {
+        // Create vacant cell cache if none exists
+        if (mVacantCache == null) {
+            mVacantCache = layout.findAllVacantCells(null, ignoreView);
+        }
+
+        // Find the best target drop location
+        return layout.findNearestVacantArea(pixelX, pixelY,
+                spanX, spanY, mVacantCache, recycle);
+    }
+    
     void setLauncher(Launcher launcher) {
         mLauncher = launcher;
     }
@@ -1002,12 +1100,14 @@
     }
 
     public void scrollLeft() {
+        clearVacantCache();
         if (mNextScreen == INVALID_SCREEN && mCurrentScreen > 0 && mScroller.isFinished()) {
             snapToScreen(mCurrentScreen - 1);
         }
     }
 
     public void scrollRight() {
+        clearVacantCache();
         if (mNextScreen == INVALID_SCREEN && mCurrentScreen < getChildCount() -1 &&
                 mScroller.isFinished()) {
             snapToScreen(mCurrentScreen + 1);
@@ -1027,7 +1127,7 @@
         }
         return result;
     }
-    
+
     /**
      * Find a search widget on the given screen
      */
@@ -1041,102 +1141,14 @@
         }
         return null;
     }
-    
+
     /**
-     * Focuses on the search widget on the specified screen,
-     * if there is one.  Also clears the current search selection so we don't 
+     * Gets the first search widget on the current screen, if there is one.
+     * Returns <code>null</code> otherwise.
      */
-    private boolean focusOnSearch(int screen) {
-        CellLayout currentScreen = (CellLayout) getChildAt(screen);
-        final Search searchWidget = findSearchWidget(currentScreen);
-        if (searchWidget != null) {
-            // This is necessary when focus on search is requested from the menu
-            // If the workspace was not in touch mode before the menu is invoked
-            // and the user clicks "Search" by touching the menu item, the following
-            // happens:
-            //
-            // - We request focus from touch on the search widget
-            // - The search widget gains focus
-            // - The window focus comes back to Home's window
-            // - The touch mode change is propagated to Home's window
-            // - The search widget is not focusable in touch mode and ViewRoot
-            //   clears its focus
-            //
-            // Forcing focusable in touch mode ensures the search widget will
-            // keep the focus no matter what happens.
-            //
-            // Note: the search input field disables focusable in touch mode
-            // after the window gets the focus back, see SearchAutoCompleteTextView
-            final SearchAutoCompleteTextView input = searchWidget.getSearchInputField();
-            input.setFocusableInTouchMode(true);
-            input.showKeyboardOnNextFocus();
-
-            if (isInTouchMode()) {
-                searchWidget.requestFocusFromTouch();
-            } else {
-                searchWidget.requestFocus();
-            }
-            searchWidget.clearQuery();
-            return true;
-        }
-        return false;
-    }
-    
-    /**
-     * Snap to the nearest screen with a search widget and give it focus
-     * 
-     * @return True if a search widget was found
-     */
-    public boolean snapToSearch() {
-        // The screen we are searching
-        int current = mCurrentScreen;
-        
-        // first position scanned so far
-        int first = current;
-
-        // last position scanned so far
-        int last = current;
-
-        // True if we should move down on the next iteration
-        boolean next = false;
-
-        // True when we have looked at the first item in the data
-        boolean hitFirst;
-
-        // True when we have looked at the last item in the data
-        boolean hitLast;
-        
-        final int count = getChildCount();
-
-        while (true) {
-            if (focusOnSearch(current)) {
-                return true;
-            }
-
-            hitLast = last == count - 1;
-            hitFirst = first == 0;
-
-            if (hitLast && hitFirst) {
-                // Looked at everything
-                break;
-            }
-
-            if (hitFirst || (next && !hitLast)) {
-                // Either we hit the top, or we are trying to move down
-                last++;
-                current = last;
-                // Try going up next time
-                next = false;
-            } else {
-                // Either we hit the bottom, or we are trying to move up
-                first--;
-                current = first;
-                // Try going down next time
-                next = true;
-            }
-
-        }
-        return false;
+    public Search findSearchWidgetOnCurrentScreen() {
+        CellLayout currentScreen = (CellLayout)getChildAt(mCurrentScreen);
+        return findSearchWidget(currentScreen);
     }
 
     public Folder getFolderForTag(Object tag) {
@@ -1210,32 +1222,64 @@
         final ArrayList<View> childrenToRemove = new ArrayList<View>();
         final LauncherModel model = Launcher.getModel();
         final int count = getChildCount();
+
         for (int i = 0; i < count; i++) {
             final CellLayout layout = (CellLayout) getChildAt(i);
             int childCount = layout.getChildCount();
+
             childrenToRemove.clear();
+
             for (int j = 0; j < childCount; j++) {
                 final View view = layout.getChildAt(j);
                 Object tag = view.getTag();
+
                 if (tag instanceof ApplicationInfo) {
-                    ApplicationInfo info = (ApplicationInfo) tag;
+                    final ApplicationInfo info = (ApplicationInfo) tag;
                     // We need to check for ACTION_MAIN otherwise getComponent() might
                     // return null for some shortcuts (for instance, for shortcuts to
                     // web pages.)
                     final Intent intent = info.intent;
                     final ComponentName name = intent.getComponent();
+
                     if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
                             name != null && packageName.equals(name.getPackageName())) {
                         model.removeDesktopItem(info);
                         LauncherModel.deleteItemFromDatabase(mLauncher, info);
                         childrenToRemove.add(view);
                     }
+                } else if (tag instanceof UserFolderInfo) {
+                    final UserFolderInfo info = (UserFolderInfo) tag;
+                    final ArrayList<ApplicationInfo> contents = info.contents;
+                    final ArrayList<ApplicationInfo> toRemove = new ArrayList<ApplicationInfo>(1);
+                    final int contentsCount = contents.size();
+                    boolean removedFromFolder = false;
+
+                    for (int k = 0; k < contentsCount; k++) {
+                        final ApplicationInfo appInfo = contents.get(k);
+                        final Intent intent = appInfo.intent;
+                        final ComponentName name = intent.getComponent();
+
+                        if (Intent.ACTION_MAIN.equals(intent.getAction()) &&
+                                name != null && packageName.equals(name.getPackageName())) {
+                            toRemove.add(appInfo);
+                            LauncherModel.deleteItemFromDatabase(mLauncher, appInfo);
+                            removedFromFolder = true;
+                        }
+                    }
+
+                    contents.removeAll(toRemove);
+                    if (removedFromFolder) {
+                        final Folder folder = getOpenFolder();
+                        if (folder != null) folder.notifyDataSetChanged();
+                    }
                 }
             }
+
             childCount = childrenToRemove.size();
             for (int j = 0; j < childCount; j++) {
                 layout.removeViewInLayout(childrenToRemove.get(j));
             }
+
             if (childCount > 0) {
                 layout.requestLayout();
                 layout.invalidate();
@@ -1277,10 +1321,6 @@
         }
     }
 
-    // TODO: remove widgets when appwidgetmanager tells us they're gone
-//    void removeAppWidgetsForProvider() {
-//    }
-
     void moveToDefaultScreen() {
         snapToScreen(mDefaultScreen);
         getChildAt(mDefaultScreen).requestFocus();