Merge "Changing long-press-on-workspace behaviour to show Applications & Widgets."
diff --git a/Android.mk b/Android.mk
index 89e626b..844f052 100644
--- a/Android.mk
+++ b/Android.mk
@@ -21,9 +21,10 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-common
+LOCAL_STATIC_JAVA_LIBRARIES := android-common android-support-v13
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files) $(call all-renderscript-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
+
 
 LOCAL_PACKAGE_NAME := Launcher2
 LOCAL_CERTIFICATE := shared
@@ -34,4 +35,6 @@
 
 include $(BUILD_PACKAGE)
 
+include $(call all-subdir-makefiles)
+
 endif
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5d0f323..5206896 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -67,7 +67,7 @@
         android:process="@string/process"
         android:label="@string/application_name"
         android:icon="@drawable/ic_launcher_home"
-        android:hardwareAccelerated="@bool/config_hardwareAccelerated"
+        android:hardwareAccelerated="true"
         android:largeHeap="true">
 
         <activity
@@ -100,6 +100,18 @@
                 android:resource="@xml/wallpaper_picker_preview" />
         </activity>
 
+        <activity android:name="com.android.launcher2.RocketLauncher"
+            android:label="@string/dream_name"
+            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
+            android:hardwareAccelerated="true"
+            >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.DREAM" />
+            </intent-filter>
+        </activity>
+
         <!-- Intent received used to install shortcuts from other applications -->
         <receiver
             android:name="com.android.launcher2.InstallShortcutReceiver"
diff --git a/res/drawable-large-hdpi/divider_launcher_holo.9.png b/res/drawable-hdpi/divider_launcher_holo.9.png
similarity index 100%
rename from res/drawable-large-hdpi/divider_launcher_holo.9.png
rename to res/drawable-hdpi/divider_launcher_holo.9.png
Binary files differ
diff --git a/res/drawable-large-hdpi/ic_generic_search.png b/res/drawable-hdpi/ic_generic_search.png
similarity index 100%
rename from res/drawable-large-hdpi/ic_generic_search.png
rename to res/drawable-hdpi/ic_generic_search.png
Binary files differ
diff --git a/res/drawable-large-hdpi/ic_voice_search.png b/res/drawable-hdpi/ic_voice_search.png
similarity index 100%
rename from res/drawable-large-hdpi/ic_voice_search.png
rename to res/drawable-hdpi/ic_voice_search.png
Binary files differ
diff --git a/res/drawable-land-hdpi/divider_launcher_holo.9.png b/res/drawable-land-hdpi/divider_launcher_holo.9.png
new file mode 100644
index 0000000..f07f6c4
--- /dev/null
+++ b/res/drawable-land-hdpi/divider_launcher_holo.9.png
Binary files differ
diff --git a/res/drawable-land-mdpi/divider_launcher_holo.9.png b/res/drawable-land-mdpi/divider_launcher_holo.9.png
new file mode 100644
index 0000000..ae77340
--- /dev/null
+++ b/res/drawable-land-mdpi/divider_launcher_holo.9.png
Binary files differ
diff --git a/res/drawable-large-mdpi/divider_launcher_holo.9.png b/res/drawable-mdpi/divider_launcher_holo.9.png
similarity index 100%
rename from res/drawable-large-mdpi/divider_launcher_holo.9.png
rename to res/drawable-mdpi/divider_launcher_holo.9.png
Binary files differ
diff --git a/res/drawable-large-mdpi/ic_generic_search.png b/res/drawable-mdpi/ic_generic_search.png
similarity index 100%
rename from res/drawable-large-mdpi/ic_generic_search.png
rename to res/drawable-mdpi/ic_generic_search.png
Binary files differ
diff --git a/res/drawable-large-mdpi/ic_voice_search.png b/res/drawable-mdpi/ic_voice_search.png
similarity index 100%
rename from res/drawable-large-mdpi/ic_voice_search.png
rename to res/drawable-mdpi/ic_voice_search.png
Binary files differ
diff --git a/res/drawable/flying_icon_bg.xml b/res/drawable/flying_icon_bg.xml
new file mode 100644
index 0000000..affd975
--- /dev/null
+++ b/res/drawable/flying_icon_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/homescreen_small_green" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index b2b2c9f..c8a82e6 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -41,6 +41,12 @@
 
     </com.android.launcher2.Workspace>
 
+    <include layout="@layout/qsb_bar"
+        android:id="@+id/qsb_bar"
+        android:layout_width="@dimen/qsb_bar_height"
+        android:layout_height="match_parent"
+        android:layout_gravity="left" />
+
     <include layout="@layout/apps_customize_pane"
         android:id="@+id/apps_customize_pane"
         android:layout_width="match_parent"
diff --git a/res/layout-land/workspace_screen.xml b/res/layout-land/workspace_screen.xml
index f2bac59..a9faf89 100644
--- a/res/layout-land/workspace_screen.xml
+++ b/res/layout-land/workspace_screen.xml
@@ -24,7 +24,7 @@
 
     launcher:cellWidth="@dimen/workspace_cell_width"
     launcher:cellHeight="@dimen/workspace_cell_height"
-    launcher:xAxisStartPadding="48dip"
+    launcher:xAxisStartPadding="@dimen/qsb_bar_height"
     launcher:xAxisEndPadding="0dip"
     launcher:yAxisStartPadding="0dip"
     launcher:yAxisEndPadding="0dip"/>
diff --git a/res/layout-large/customize_tab_widget_indicator.xml b/res/layout-large/customize_tab_widget_indicator.xml
new file mode 100644
index 0000000..186a342
--- /dev/null
+++ b/res/layout-large/customize_tab_widget_indicator.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.launcher2.AccessibleTabView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/CustomizeTabIndicator.Wide" />
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index c7bcbb0..f05f9b6 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -36,9 +36,14 @@
         <include android:id="@+id/cell3" layout="@layout/workspace_screen" />
         <include android:id="@+id/cell4" layout="@layout/workspace_screen" />
         <include android:id="@+id/cell5" layout="@layout/workspace_screen" />
-                
     </com.android.launcher2.Workspace>
 
+    <include layout="@layout/qsb_bar"
+        android:id="@+id/qsb_bar"
+        android:layout_width="fill_parent"
+        android:layout_height="@dimen/qsb_bar_height"
+        android:layout_gravity="top" />
+
     <include layout="@layout/apps_customize_pane"
         android:id="@+id/apps_customize_pane"
         android:layout_width="match_parent"
diff --git a/res/layout-port/workspace_screen.xml b/res/layout-port/workspace_screen.xml
index f400c40..7a6714f 100644
--- a/res/layout-port/workspace_screen.xml
+++ b/res/layout-port/workspace_screen.xml
@@ -24,7 +24,7 @@
 
     launcher:cellWidth="@dimen/workspace_cell_width"
     launcher:cellHeight="@dimen/workspace_cell_height"
-    launcher:yAxisStartPadding="8dip"
+    launcher:yAxisStartPadding="@dimen/qsb_bar_height"
     launcher:yAxisEndPadding="@dimen/button_bar_height"
     launcher:xAxisStartPadding="0dip"
     launcher:xAxisEndPadding="0dip" />
diff --git a/res/layout/apps_customize_pane.xml b/res/layout/apps_customize_pane.xml
index 05884ef..e22ba12 100644
--- a/res/layout/apps_customize_pane.xml
+++ b/res/layout/apps_customize_pane.xml
@@ -20,7 +20,7 @@
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="@drawable/apps_customize_bg_gradient">
+        android:background="#FF000000">
         <!-- The layout_width of the tab bar gets overriden to align the content
              with the text in the tabs in AppsCustomizeTabHost. -->
         <FrameLayout
diff --git a/res/layout/qsb_bar.xml b/res/layout/qsb_bar.xml
new file mode 100644
index 0000000..5469224
--- /dev/null
+++ b/res/layout/qsb_bar.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
+    android:focusable="false">
+
+    <!-- Search buttons container -->
+    <LinearLayout
+        android:id="@+id/qsb_search_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        style="@style/SearchBar">
+       <!-- Global search icon -->
+       <ImageView
+            style="@style/SearchButton"
+            android:id="@+id/search_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/ic_generic_search"
+            android:background="@drawable/button_bg"
+            android:onClick="onClickSearchButton"
+            android:focusable="true"
+            android:clickable="true"
+            android:contentDescription="@string/accessibility_search_button" />
+
+        <ImageView
+            style="@style/SearchButtonDivider"
+            android:id="@+id/search_divider"
+            android:src="@drawable/divider_launcher_holo"
+            android:onClick="onClickSearchButton"
+            android:focusable="false"
+            android:clickable="true" />
+
+        <!-- Voice search icon -->
+        <ImageView
+            style="@style/SearchButton"
+            android:id="@+id/voice_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/ic_voice_search"
+            android:background="@drawable/button_bg"
+            android:onClick="onClickVoiceButton"
+            android:focusable="true"
+            android:clickable="true"
+            android:contentDescription="@string/accessibility_voice_search_button" />
+    </LinearLayout>
+
+    <!-- Drag specific targets container -->
+    <LinearLayout
+        android:id="@+id/drag_target_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#FF00FF00"
+        android:visibility="gone">
+    </LinearLayout>
+</FrameLayout>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index b3d0b2e..13519e5 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -15,7 +15,11 @@
 -->
 
 <resources>
-    <dimen name="workspace_cell_width">96dip</dimen>
+<!-- QSB -->
+    <dimen name="toolbar_button_vertical_padding">12dip</dimen>
+    <dimen name="toolbar_button_horizontal_padding">12dip</dimen>
+
+    <dimen name="workspace_cell_width">106dip</dimen>
     <dimen name="workspace_cell_height">74dip</dimen>
     <dimen name="folder_cell_width">100dip</dimen>
     <dimen name="folder_cell_height">74dip</dimen>
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
index 251c717..50aba09 100644
--- a/res/values-land/styles.xml
+++ b/res/values-land/styles.xml
@@ -18,6 +18,23 @@
 -->
 
 <resources>
+<!-- Search Bar -->
+    <style name="SearchBar">
+        <item name="android:orientation">vertical</item>
+    </style>
+    <style name="SearchButton">
+        <item name="android:layout_gravity">center_horizontal</item>
+        <item name="android:paddingTop">@dimen/toolbar_button_vertical_padding</item>
+        <item name="android:paddingBottom">@dimen/toolbar_button_vertical_padding</item>
+        <item name="android:paddingLeft">@dimen/toolbar_button_horizontal_padding</item>
+        <item name="android:paddingRight">@dimen/toolbar_button_horizontal_padding</item>
+    </style>
+    <style name="SearchButtonDivider">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center_horizontal</item>
+    </style>
+
     <style name="HotseatButton">
         <item name="android:paddingTop">12dip</item>
         <item name="android:paddingBottom">12dip</item>
diff --git a/res/values-large-port/styles.xml b/res/values-large-port/styles.xml
new file mode 100644
index 0000000..ba23a89
--- /dev/null
+++ b/res/values-large-port/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2008 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>
+    <style name="CustomizeTabIndicator.Wide" parent="TabIndicator.Wide">
+        <item name="android:paddingLeft">20dp</item>
+        <item name="android:paddingRight">20dp</item>
+        <item name="android:paddingTop">12dp</item>
+        <item name="android:paddingBottom">16dp</item>
+        <item name="android:textSize">16sp</item>
+    </style>
+</resources>
diff --git a/res/values-large/dimens.xml b/res/values-large/dimens.xml
index 10836b9..fa660e5 100644
--- a/res/values-large/dimens.xml
+++ b/res/values-large/dimens.xml
@@ -62,9 +62,6 @@
     <integer name="land_all_apps_view_cellCountX">7</integer>
     <integer name="land_all_apps_view_cellCountY">5</integer>
 
-    <dimen name="toolbar_button_vertical_padding">12dip</dimen>
-    <dimen name="toolbar_button_horizontal_padding">16dip</dimen>
-
     <!-- height & width of the drop rectangle for the trash icon -->
     <dimen name="delete_zone_vertical_drag_padding">20dip</dimen>
     <dimen name="delete_zone_horizontal_drag_padding">20dip</dimen>
diff --git a/res/values-large/styles.xml b/res/values-large/styles.xml
index bcbe038..ebb26f5 100644
--- a/res/values-large/styles.xml
+++ b/res/values-large/styles.xml
@@ -50,6 +50,9 @@
         <item name="android:textSize">20sp</item>
     </style>
 
+    <style name="CustomizeTabIndicator.Wide" parent="TabIndicator.Wide">
+    </style>
+
     <style name="config_orientation">
         <item name="@android:screenOrientation">unspecified</item>
     </style>
diff --git a/res/values-xlarge/styles.xml b/res/values-xlarge/styles.xml
new file mode 100644
index 0000000..fdf2f19
--- /dev/null
+++ b/res/values-xlarge/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2011 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+
+<resources>
+    <style name="CustomizeTabIndicator.Wide" parent="TabIndicator.Wide">
+    </style>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 99b1240..9d159f9 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -15,6 +15,13 @@
 -->
 
 <resources>
+<!-- Workspace -->
+    <dimen name="qsb_bar_height">56dp</dimen>
+
+<!-- QSB -->
+    <dimen name="toolbar_button_vertical_padding">12dip</dimen>
+    <dimen name="toolbar_button_horizontal_padding">16dip</dimen>
+
 <!-- AllApps/Customize/AppsCustomize -->
     <!-- Size of icons in Workspace/AppsCustomize -->
     <dimen name="app_icon_size">50dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0b90353..32e649d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -248,4 +248,7 @@
 
     <!--  Text to inform the user that they can't uninstall a system application -->
     <string name="uninstall_system_app_text">This is a system application and cannot be uninstalled.</string>
+
+    <!-- Title of the Android Dreams (screensaver) module -->
+    <string name="dream_name">Rocket Launcher</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index abe6ac4..c3e6ce9 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -68,6 +68,23 @@
         <item name="android:layout_marginRight">10dip</item>
     </style>
 
+    <style name="SearchBar">
+        <item name="android:orientation">horizontal</item>
+    </style>
+    <style name="SearchButton">
+        <item name="android:layout_gravity">center_vertical</item>
+        <item name="android:paddingTop">@dimen/toolbar_button_vertical_padding</item>
+        <item name="android:paddingBottom">@dimen/toolbar_button_vertical_padding</item>
+        <item name="android:paddingLeft">@dimen/toolbar_button_horizontal_padding</item>
+        <item name="android:paddingRight">@dimen/toolbar_button_horizontal_padding</item>
+    </style>
+    <style name="SearchButtonDivider">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_gravity">center_vertical</item>
+    </style>
+
+
     <style name="TabIndicator">
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">match_parent</item>
@@ -85,13 +102,6 @@
         <item name="android:shadowRadius">1.0</item>
     </style>
 
-    <style name="SearchButton" parent="@android:style/Widget.Button.Small">
-        <item name="android:paddingTop">7dip</item>
-        <item name="android:paddingBottom">9dip</item>
-        <item name="android:paddingLeft">10dip</item>
-        <item name="android:paddingRight">10dip</item>
-    </style>
-
     <style name="MarketButton">
         <item name="android:paddingRight">20dp</item>
         <item name="android:text">@string/market</item>
diff --git a/src/com/android/launcher2/CachedTextView.java b/src/com/android/launcher2/CachedTextView.java
index d0f6dd8..ac2cc3b 100644
--- a/src/com/android/launcher2/CachedTextView.java
+++ b/src/com/android/launcher2/CachedTextView.java
@@ -107,7 +107,7 @@
         int width = (int) (textCacheRight - mTextCacheLeft + (2 * xCharWidth));
         int height = (int) (textCacheBottom - mTextCacheTop);
 
-        if (width != 0 && height != 0) {
+        if (width > 0 && height > 0) {
             if (mCache != null) {
                 if (mCache.getWidth() != width || mCache.getHeight() != height) {
                     mCache.recycle();
diff --git a/src/com/android/launcher2/CustomizeTrayTabHost.java b/src/com/android/launcher2/CustomizeTrayTabHost.java
index 6306bda..c6a39b3 100644
--- a/src/com/android/launcher2/CustomizeTrayTabHost.java
+++ b/src/com/android/launcher2/CustomizeTrayTabHost.java
@@ -77,19 +77,23 @@
         TextView tabView;
         TabWidget tabWidget = (TabWidget) findViewById(com.android.internal.R.id.tabs);
 
-        tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false);
+        tabView = (TextView) mInflater.inflate(
+                R.layout.customize_tab_widget_indicator, tabWidget, false);
         tabView.setText(mContext.getString(R.string.widgets_tab_label));
         addTab(newTabSpec(WIDGETS_TAG)
                 .setIndicator(tabView).setContent(contentFactory));
-        tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false);
+        tabView = (TextView) mInflater.inflate(
+                R.layout.customize_tab_widget_indicator, tabWidget, false);
         tabView.setText(mContext.getString(R.string.applications_tab_label));
         addTab(newTabSpec(APPLICATIONS_TAG)
                 .setIndicator(tabView).setContent(contentFactory));
-        tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false);
+        tabView = (TextView) mInflater.inflate(
+                R.layout.customize_tab_widget_indicator, tabWidget, false);
         tabView.setText(mContext.getString(R.string.wallpapers_tab_label));
         addTab(newTabSpec(WALLPAPERS_TAG)
                 .setIndicator(tabView).setContent(contentFactory));
-        tabView = (TextView) mInflater.inflate(R.layout.tab_widget_indicator, tabWidget, false);
+        tabView = (TextView) mInflater.inflate(
+                R.layout.customize_tab_widget_indicator, tabWidget, false);
         tabView.setText(mContext.getString(R.string.shortcuts_tab_label));
         addTab(newTabSpec(SHORTCUTS_TAG)
                 .setIndicator(tabView).setContent(contentFactory));
diff --git a/src/com/android/launcher2/IconCache.java b/src/com/android/launcher2/IconCache.java
index 7e37afe..5c07cfb 100644
--- a/src/com/android/launcher2/IconCache.java
+++ b/src/com/android/launcher2/IconCache.java
@@ -24,6 +24,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
+import android.util.Pair;
 import android.util.DisplayMetrics;
 
 import java.util.HashMap;
@@ -187,4 +188,16 @@
         }
         return entry;
     }
+
+    public HashMap<ComponentName,Bitmap> getAllIcons() {
+        synchronized (mCache) {
+            HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
+            int i = 0;
+            for (ComponentName cn : mCache.keySet()) {
+                final CacheEntry e = mCache.get(cn);
+                set.put(cn, e.icon);
+            }
+            return set;
+        }
+    }
 }
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index f8d3411..5cf8e9c 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -3152,22 +3152,34 @@
     }
 
     private void updateGlobalSearchIcon() {
-        if (LauncherApplication.isScreenLarge()) {
-            final View searchButton = findViewById(R.id.search_button);
-            final View searchDivider = findViewById(R.id.search_divider);
+        final ImageView searchButton = (ImageView) findViewById(R.id.search_button);
+        final View searchDivider = findViewById(R.id.search_divider);
 
-            final SearchManager searchManager =
-                    (SearchManager) getSystemService(Context.SEARCH_SERVICE);
-            ComponentName activityName = searchManager.getGlobalSearchActivity();
-            if (activityName != null) {
+        final SearchManager searchManager =
+                (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+        ComponentName activityName = searchManager.getGlobalSearchActivity();
+        if (activityName != null) {
+            // In landscape mode on the Phone UI, we only have enough space to show the magnifying
+            // glass icon
+            boolean iconLoaded = false;
+            if (!LauncherApplication.isScreenLarge()) {
+                // TODO-APPS_CUSTOMIZE: Remove when the QSB fixes itself?
+                int orientation = getResources().getConfiguration().orientation;
+                if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+                    searchButton.setImageResource(R.drawable.ic_generic_search);
+                    iconLoaded = true;
+                    sGlobalSearchIcon = null;
+                }
+            }
+            if (!iconLoaded) {
                 sGlobalSearchIcon = updateButtonWithIconFromExternalActivity(
                         R.id.search_button, activityName, R.drawable.ic_generic_search);
-                searchButton.setVisibility(View.VISIBLE);
-                searchDivider.setVisibility(View.VISIBLE);
-            } else {
-                searchButton.setVisibility(View.GONE);
-                searchDivider.setVisibility(View.GONE);
             }
+            searchButton.setVisibility(View.VISIBLE);
+            searchDivider.setVisibility(View.VISIBLE);
+        } else {
+            searchButton.setVisibility(View.GONE);
+            searchDivider.setVisibility(View.GONE);
         }
     }
 
@@ -3176,21 +3188,19 @@
     }
 
     private void updateVoiceSearchIcon() {
-        if (LauncherApplication.isScreenLarge()) {
-            final View searchDivider = findViewById(R.id.search_divider);
-            final View voiceButton = findViewById(R.id.voice_button);
+        final View searchDivider = findViewById(R.id.search_divider);
+        final View voiceButton = findViewById(R.id.voice_button);
 
-            Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
-            ComponentName activityName = intent.resolveActivity(getPackageManager());
-            if (activityName != null) {
-                sVoiceSearchIcon = updateButtonWithIconFromExternalActivity(
-                        R.id.voice_button, activityName, R.drawable.ic_voice_search);
-                searchDivider.setVisibility(View.VISIBLE);
-                voiceButton.setVisibility(View.VISIBLE);
-            } else {
-                searchDivider.setVisibility(View.GONE);
-                voiceButton.setVisibility(View.GONE);
-            }
+        Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+        ComponentName activityName = intent.resolveActivity(getPackageManager());
+        if (activityName != null) {
+            sVoiceSearchIcon = updateButtonWithIconFromExternalActivity(
+                    R.id.voice_button, activityName, R.drawable.ic_voice_search);
+            searchDivider.setVisibility(View.VISIBLE);
+            voiceButton.setVisibility(View.VISIBLE);
+        } else {
+            searchDivider.setVisibility(View.GONE);
+            voiceButton.setVisibility(View.GONE);
         }
     }
 
diff --git a/src/com/android/launcher2/RocketLauncher.java b/src/com/android/launcher2/RocketLauncher.java
new file mode 100644
index 0000000..1c6510f
--- /dev/null
+++ b/src/com/android/launcher2/RocketLauncher.java
@@ -0,0 +1,426 @@
+/*);
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// TODO:
+// background stellar matter:
+//  - add some slow horizontal parallax motion, or perhaps veeeeery gradual outward drift
+
+package com.android.launcher2;
+
+import android.animation.AnimatorSet;
+import android.animation.PropertyValuesHolder;
+import android.animation.ObjectAnimator;
+import android.animation.TimeAnimator;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.support.v13.dreams.BasicDream;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Pair;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import java.util.HashMap;
+import java.util.Random;
+
+import com.android.launcher.R;
+
+public class RocketLauncher extends BasicDream {
+    public static final boolean ROCKET_LAUNCHER = true;
+
+    public static class Board extends FrameLayout
+    {
+        public static final boolean FIXED_STARS = true;
+        public static final boolean FLYING_STARS = true;
+        public static final int NUM_ICONS = 20;
+
+        public static final float MANEUVERING_THRUST_SCALE = 0.1f; // tenth speed
+        private boolean mManeuveringThrusters = false;
+        private float mSpeedScale = 1.0f;
+
+        public static final int LAUNCH_ZOOM_TIME = 400; // ms
+
+        HashMap<ComponentName, Bitmap> mIcons;
+        ComponentName[] mComponentNames;
+
+        static Random sRNG = new Random();
+
+        static float lerp(float a, float b, float f) {
+            return (b-a)*f + a;
+        }
+
+        static float randfrange(float a, float b) {
+            return lerp(a, b, sRNG.nextFloat());
+        }
+
+        static int randsign() {
+            return sRNG.nextBoolean() ? 1 : -1;
+        }
+
+        static <E> E pick(E[] array) {
+            if (array.length == 0) return null;
+            return array[sRNG.nextInt(array.length)];
+        }
+
+        public class FlyingIcon extends ImageView {
+            public static final float VMAX = 1000.0f;
+            public static final float VMIN = 100.0f;
+            public static final float ANGULAR_VMAX = 45f;
+            public static final float ANGULAR_VMIN = 0f;
+            public static final float SCALE_MIN = 0.5f;
+            public static final float SCALE_MAX = 4f;
+
+            public float v, vr;
+
+            public final float[] hsv = new float[3];
+
+            public float angle, anglex, angley;
+            public float fuse;
+            public float dist;
+            public float endscale;
+            public float boardCenterX, boardCenterY;
+
+            public ComponentName component;
+
+            public FlyingIcon(Context context, AttributeSet as) {
+                super(context, as);
+                setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+                setBackgroundResource(R.drawable.flying_icon_bg);
+                //android.util.Log.d("RocketLauncher", "ctor: " + this);
+                hsv[1] = 1f;
+                hsv[2] = 1f;
+            }
+
+            @Override
+            public boolean onTouchEvent(MotionEvent event) {
+                if (!mManeuveringThrusters || component == null) {
+                    return false;
+                }
+                if (getAlpha() < 0.5f) {
+                    setPressed(false);
+                    return false;
+                }
+
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        setPressed(true);
+                        Board.this.resetWarpTimer();
+                        break;
+                    case MotionEvent.ACTION_MOVE:
+                        final Rect hit = new Rect();
+                        final Point offset = new Point();
+                        getGlobalVisibleRect(hit, offset);
+                        final int globx = (int) event.getX() + offset.x;
+                        final int globy = (int) event.getY() + offset.y;
+                        setPressed(hit.contains(globx, globy));
+                        Board.this.resetWarpTimer();
+                        break;
+                    case MotionEvent.ACTION_UP:
+                        if (isPressed()) {
+                            setPressed(false);
+                            postDelayed(new Runnable() {
+                                public void run() {
+                                    try {
+                                        getContext().startActivity(new Intent(Intent.ACTION_MAIN)
+                                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                                            .setComponent(component));
+                                    } catch (android.content.ActivityNotFoundException e) {
+                                    } catch (SecurityException e) {
+                                    }
+                                }
+                            }, LAUNCH_ZOOM_TIME);
+                            endscale = 0;
+                            AnimatorSet s = new AnimatorSet();
+                            s.playTogether(
+                                ObjectAnimator.ofFloat(this, "scaleX", 15f),
+                                ObjectAnimator.ofFloat(this, "scaleY", 15f),
+                                ObjectAnimator.ofFloat(this, "alpha", 0f)
+                            );
+
+                            // make sure things are still moving until the very last instant the
+                            // activity is visible
+                            s.setDuration((int)(LAUNCH_ZOOM_TIME * 1.25));
+                            s.setInterpolator(new android.view.animation.AccelerateInterpolator(3));
+                            s.start();
+                        }
+                        break;
+                }
+                return true;
+            }
+
+            public String toString() {
+                return String.format("<'%s' @ (%.1f, %.1f) v=%.1f a=%.1f dist/fuse=%.1f/%.1f>",
+                        "icon", getX(), getY(), v, angle, dist, fuse);
+            }
+
+            public void randomizeIcon() {
+                component = pick(mComponentNames);
+                setImageBitmap(mIcons.get(component));
+            }
+
+            public void randomize() {
+                v = randfrange(VMIN, VMAX);
+                angle = randfrange(0, 360f);
+                anglex = (float) Math.sin(angle / 180. * Math.PI);
+                angley = (float) Math.cos(angle / 180. * Math.PI);
+                vr = randfrange(ANGULAR_VMIN, ANGULAR_VMAX) * randsign();
+                endscale = randfrange(SCALE_MIN, SCALE_MAX);
+
+                randomizeIcon();
+            }
+            public void reset() {
+                randomize();
+                boardCenterX = (Board.this.getWidth() - getWidth()) / 2;
+                boardCenterY = (Board.this.getHeight() - getHeight()) / 2;
+                setX(boardCenterX);
+                setY(boardCenterY);
+                fuse = (float) Math.max(boardCenterX, boardCenterY);
+                setRotation(180-angle);
+                setScaleX(0f);
+                setScaleY(0f);
+                dist = 0;
+                setAlpha(0f);
+            }
+            public void update(float dt) {
+                dist += v * dt;
+                setX(getX() + anglex * v * dt);
+                setY(getY() + angley * v * dt);
+                //setRotation(getRotation() + vr * dt);
+                if (endscale > 0) {
+                    float scale = lerp(0, endscale, (float) Math.sqrt(dist / fuse));
+                        setScaleX(scale * lerp(1f, 0.75f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3)));
+                        setScaleY(scale * lerp(1f, 1.5f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3)));
+                    final float q1 = fuse*0.15f;
+                    final float q4 = fuse*0.75f;
+                    if (dist < q1) {
+                        setAlpha((float) Math.sqrt(dist/q1));
+                    } else if (dist > q4) {
+                        setAlpha((dist >= fuse) ? 0f : (1f-(float)Math.pow((dist-q4)/(fuse-q4),2)));
+                    } else {
+                        setAlpha(1f);
+                    }
+                }
+            }
+        }
+
+        public class FlyingStar extends FlyingIcon {
+            public FlyingStar(Context context, AttributeSet as) {
+                super(context, as);
+            }
+            public void randomizeIcon() {
+                setImageResource(R.drawable.widget_resize_handle_bottom);
+            }
+            public void randomize() {
+                super.randomize();
+                v = randfrange(VMAX*0.75f, VMAX*2f); // fasticate
+                endscale = randfrange(1f, 2f); // ensmallen
+            }
+        }
+
+        TimeAnimator mAnim;
+
+        public Board(Context context, AttributeSet as) {
+            super(context, as);
+
+            setBackgroundColor(0xFF000000);
+
+            LauncherApplication app = (LauncherApplication)context.getApplicationContext();
+            mIcons = app.getIconCache().getAllIcons();
+            mComponentNames = new ComponentName[mIcons.size()];
+            mComponentNames = mIcons.keySet().toArray(mComponentNames);
+        }
+
+        private void reset() {
+            removeAllViews();
+
+            final ViewGroup.LayoutParams wrap = new ViewGroup.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+
+            if (FIXED_STARS) {
+                for(int i=0; i<20; i++) {
+                    ImageView fixedStar = new ImageView(getContext(), null);
+                    fixedStar.setImageResource(R.drawable.widget_resize_handle_bottom);
+                    final float s = randfrange(0.25f, 0.75f);
+                    fixedStar.setScaleX(s);
+                    fixedStar.setScaleY(s);
+                    fixedStar.setAlpha(0.75f);
+                    addView(fixedStar, wrap);
+                    fixedStar.setX(randfrange(0, getWidth()));
+                    fixedStar.setY(randfrange(0, getHeight()));
+                }
+            }
+
+            for(int i=0; i<NUM_ICONS*2; i++) {
+                FlyingIcon nv = (FLYING_STARS && (i < NUM_ICONS))
+                    ? new FlyingStar(getContext(), null)
+                    : new FlyingIcon(getContext(), null);
+                addView(nv, wrap);
+                nv.reset();
+            }
+
+            mAnim = new TimeAnimator();
+            mAnim.setTimeListener(new TimeAnimator.TimeListener() {
+                public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+                    // setRotation(totalTime * 0.01f); // not as cool as you would think
+
+                    final int START_ZOOM_TIME = 3000;
+                    if (totalTime < START_ZOOM_TIME) {
+                        final float x = totalTime/(float)START_ZOOM_TIME;
+                        final float s = 1f-(float)Math.pow(x-1, 4);
+                        setScaleX(s); setScaleY(s);
+                    } else {
+                        setScaleX(1.0f); setScaleY(1.0f);
+                    }
+
+                    if (mManeuveringThrusters) {
+                        if (mSpeedScale > MANEUVERING_THRUST_SCALE) {
+                            mSpeedScale -= (2*deltaTime/1000f);
+                        }
+                        if (mSpeedScale < MANEUVERING_THRUST_SCALE) {
+                            mSpeedScale = MANEUVERING_THRUST_SCALE;
+                        }
+                    } else {
+                        if (mSpeedScale < 1.0f) {
+                            mSpeedScale += (deltaTime/1000f);
+                        }
+                        if (mSpeedScale > 1.0f) {
+                            mSpeedScale = 1.0f;
+                        }
+                    }
+
+                    for (int i=0; i<getChildCount(); i++) {
+                        View v = getChildAt(i);
+                        if (!(v instanceof FlyingIcon)) continue;
+                        FlyingIcon nv = (FlyingIcon) v;
+                        nv.update(deltaTime / 1000f * mSpeedScale);
+                        final float scaledWidth = nv.getWidth() * nv.getScaleX();
+                        final float scaledHeight = nv.getHeight() * nv.getScaleY();
+                        if (   nv.getX() + scaledWidth < 0
+                            || nv.getX() - scaledWidth > getWidth()
+                            || nv.getY() + scaledHeight < 0 
+                            || nv.getY() - scaledHeight > getHeight())
+                        {
+                            nv.reset();
+                        }
+                    }
+                }
+            });
+        }
+
+        @Override
+        protected void onAttachedToWindow() {
+            super.onAttachedToWindow();
+            setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
+
+            reset();
+            mAnim.start();
+        }
+
+        protected void onSizeChanged (int w, int h, int oldw, int oldh) {
+            super.onSizeChanged(w,h,oldw,oldh);
+            mAnim.cancel();
+            reset();
+            mAnim.start();
+        }
+
+
+        @Override
+        protected void onDetachedFromWindow() {
+            super.onDetachedFromWindow();
+            mAnim.cancel();
+        }
+
+        @Override
+        public boolean isOpaque() {
+            return true;
+        }
+
+        @Override
+        public boolean onInterceptTouchEvent(MotionEvent e) {
+            // we want to eat touch events ourselves if we're in warp speed
+            return (!(ROCKET_LAUNCHER && mManeuveringThrusters));
+        }
+
+        final Runnable mEngageWarp = new Runnable() {
+            @Override
+            public void run() {
+                mManeuveringThrusters = false;
+            }
+        };
+        public void resetWarpTimer() {
+            final Handler h = getHandler();
+            h.removeCallbacks(mEngageWarp);
+            h.postDelayed(mEngageWarp, 5000);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            if (!ROCKET_LAUNCHER) {
+                return true;
+            }
+
+            if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                if (!mManeuveringThrusters) {
+                    mManeuveringThrusters = true;
+                    resetWarpTimer();
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        DisplayMetrics metrics = new DisplayMetrics();
+        getWindowManager().getDefaultDisplay().getMetrics(metrics);
+        final int longside = metrics.widthPixels > metrics.heightPixels 
+            ? metrics.widthPixels : metrics.heightPixels;
+
+        Board b = new Board(this, null);
+        setContentView(b, new ViewGroup.LayoutParams(longside, longside));
+        b.setX((metrics.widthPixels - longside) / 2);
+        b.setY((metrics.heightPixels - longside) / 2);
+    }
+
+    @Override
+    public void onUserInteraction() {
+        if (!ROCKET_LAUNCHER) {
+            finish();
+        }
+    }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..84e9559
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,15 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+include $(call all-subdir-makefiles)
diff --git a/tests/stress/Android.mk b/tests/stress/Android.mk
new file mode 100644
index 0000000..da22bb9
--- /dev/null
+++ b/tests/stress/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := LauncherRotationStressTest
+
+LOCAL_CERTIFICATE := shared
+
+LOCAL_INSTRUMENTATION_FOR := Launcher2
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/tests/stress/AndroidManifest.xml b/tests/stress/AndroidManifest.xml
new file mode 100644
index 0000000..ce5dbe4
--- /dev/null
+++ b/tests/stress/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="2.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.launcher.stress.launcherrotation">
+
+    <uses-sdk android:minSdkVersion="8" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.android.launcher"
+        android:label="Rotation stress test using Launcher2">
+    </instrumentation>
+</manifest>
diff --git a/tests/stress/src/com/android/launcher2/stress/LauncherRotationStressTest.java b/tests/stress/src/com/android/launcher2/stress/LauncherRotationStressTest.java
new file mode 100644
index 0000000..3d787f2
--- /dev/null
+++ b/tests/stress/src/com/android/launcher2/stress/LauncherRotationStressTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher2.stress;
+
+
+import com.android.launcher2.Launcher;
+
+import android.content.pm.ActivityInfo;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.TimedTest;
+import android.util.Log;
+
+/**
+ * Run rotation stress test using Launcher2 for 50 iterations.
+ */
+public class LauncherRotationStressTest extends ActivityInstrumentationTestCase2<Launcher> {
+
+    private static final int NUM_ITERATIONS = 50;
+    private static final String LOG_TAG = "LauncherRotationStressTest";
+
+    public LauncherRotationStressTest() {
+        super(Launcher.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @TimedTest(includeDetailedStats=true)
+    public void testLauncherRotationStress() throws Exception {
+        Launcher launcher = getActivity();
+        for (int i = 0; i < NUM_ITERATIONS; i++) {
+            Log.i(LOG_TAG, "Starting LauncherRotationStressTest " + (i + 1) + " of " +
+                  NUM_ITERATIONS);
+            getInstrumentation().waitForIdleSync();
+            SystemClock.sleep(500);
+            launcher.setRequestedOrientation(
+                    ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+            getInstrumentation().waitForIdleSync();
+            SystemClock.sleep(500);
+            launcher.setRequestedOrientation(
+                    ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+            Log.i(LOG_TAG, "Finished LauncherRotationStressTest " + (i + 1) + " of " +
+                  NUM_ITERATIONS);
+        }
+    }
+}