Merge "Avoid ANR in Home by avoiding a sycnhronized call on the UI thread. Bug #2614636" into froyo
diff --git a/res/drawable-hdpi/hotseat_bg_center.9.png b/res/drawable-hdpi/hotseat_bg_center.9.png
index 0567b77..468e766 100644
--- a/res/drawable-hdpi/hotseat_bg_center.9.png
+++ b/res/drawable-hdpi/hotseat_bg_center.9.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_bg_left.9.png b/res/drawable-hdpi/hotseat_bg_left.9.png
index aaf42c4..433a10e 100644
--- a/res/drawable-hdpi/hotseat_bg_left.9.png
+++ b/res/drawable-hdpi/hotseat_bg_left.9.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_bg_right.9.png b/res/drawable-hdpi/hotseat_bg_right.9.png
index 2b8149b..4ea2a73 100644
--- a/res/drawable-hdpi/hotseat_bg_right.9.png
+++ b/res/drawable-hdpi/hotseat_bg_right.9.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_browser_focused.png b/res/drawable-hdpi/hotseat_browser_focused.png
index 6044238..6717ad2 100644
--- a/res/drawable-hdpi/hotseat_browser_focused.png
+++ b/res/drawable-hdpi/hotseat_browser_focused.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_browser_normal.png b/res/drawable-hdpi/hotseat_browser_normal.png
index e6eb94e..d02fdd9 100644
--- a/res/drawable-hdpi/hotseat_browser_normal.png
+++ b/res/drawable-hdpi/hotseat_browser_normal.png
Binary files differ
diff --git a/res/drawable-hdpi/hotseat_browser_pressed.png b/res/drawable-hdpi/hotseat_browser_pressed.png
index 918afb4..71df2d1 100644
--- a/res/drawable-hdpi/hotseat_browser_pressed.png
+++ b/res/drawable-hdpi/hotseat_browser_pressed.png
Binary files differ
diff --git a/res/drawable-land-hdpi/hotseat_bg_center.9.png b/res/drawable-land-hdpi/hotseat_bg_center.9.png
index f6a59fa..958b4a4 100644
--- a/res/drawable-land-hdpi/hotseat_bg_center.9.png
+++ b/res/drawable-land-hdpi/hotseat_bg_center.9.png
Binary files differ
diff --git a/res/drawable-land-hdpi/hotseat_bg_left.9.png b/res/drawable-land-hdpi/hotseat_bg_left.9.png
index 8320e73..9ea8f9d 100644
--- a/res/drawable-land-hdpi/hotseat_bg_left.9.png
+++ b/res/drawable-land-hdpi/hotseat_bg_left.9.png
Binary files differ
diff --git a/res/drawable-land-hdpi/hotseat_bg_right.9.png b/res/drawable-land-hdpi/hotseat_bg_right.9.png
index 2d74f2bb..ce8f1e2 100644
--- a/res/drawable-land-hdpi/hotseat_bg_right.9.png
+++ b/res/drawable-land-hdpi/hotseat_bg_right.9.png
Binary files differ
diff --git a/res/drawable-land-mdpi/hotseat_bg_center.9.png b/res/drawable-land-mdpi/hotseat_bg_center.9.png
new file mode 100644
index 0000000..691ab01
--- /dev/null
+++ b/res/drawable-land-mdpi/hotseat_bg_center.9.png
Binary files differ
diff --git a/res/drawable-land-mdpi/hotseat_bg_left.9.png b/res/drawable-land-mdpi/hotseat_bg_left.9.png
new file mode 100644
index 0000000..bd235f3
--- /dev/null
+++ b/res/drawable-land-mdpi/hotseat_bg_left.9.png
Binary files differ
diff --git a/res/drawable-land-mdpi/hotseat_bg_right.9.png b/res/drawable-land-mdpi/hotseat_bg_right.9.png
new file mode 100644
index 0000000..6d87f0d
--- /dev/null
+++ b/res/drawable-land-mdpi/hotseat_bg_right.9.png
Binary files differ
diff --git a/res/drawable-mdpi/home_button_focused.png b/res/drawable-mdpi/home_button_focused.png
index 29fcbd0..701772b 100644
--- a/res/drawable-mdpi/home_button_focused.png
+++ b/res/drawable-mdpi/home_button_focused.png
Binary files differ
diff --git a/res/drawable-mdpi/home_button_normal.png b/res/drawable-mdpi/home_button_normal.png
index 58ff958..cc7e3b0 100644
--- a/res/drawable-mdpi/home_button_normal.png
+++ b/res/drawable-mdpi/home_button_normal.png
Binary files differ
diff --git a/res/drawable-mdpi/home_button_pressed.png b/res/drawable-mdpi/home_button_pressed.png
index 32337d6..ee8ddbe 100644
--- a/res/drawable-mdpi/home_button_pressed.png
+++ b/res/drawable-mdpi/home_button_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/hotseat_bg_center.9.png b/res/drawable-mdpi/hotseat_bg_center.9.png
index a551652..28c3402 100644
--- a/res/drawable-mdpi/hotseat_bg_center.9.png
+++ b/res/drawable-mdpi/hotseat_bg_center.9.png
Binary files differ
diff --git a/res/drawable-mdpi/hotseat_bg_left.9.png b/res/drawable-mdpi/hotseat_bg_left.9.png
old mode 100755
new mode 100644
index cd46c6c..605eb1e
--- a/res/drawable-mdpi/hotseat_bg_left.9.png
+++ b/res/drawable-mdpi/hotseat_bg_left.9.png
Binary files differ
diff --git a/res/drawable-mdpi/hotseat_bg_right.9.png b/res/drawable-mdpi/hotseat_bg_right.9.png
index dba0961..5df3f00 100644
--- a/res/drawable-mdpi/hotseat_bg_right.9.png
+++ b/res/drawable-mdpi/hotseat_bg_right.9.png
Binary files differ
diff --git a/res/drawable-mdpi/hotseat_browser_focused.png b/res/drawable-mdpi/hotseat_browser_focused.png
index de8defc..5b85840 100644
--- a/res/drawable-mdpi/hotseat_browser_focused.png
+++ b/res/drawable-mdpi/hotseat_browser_focused.png
Binary files differ
diff --git a/res/drawable-mdpi/hotseat_browser_normal.png b/res/drawable-mdpi/hotseat_browser_normal.png
index 35205a2..4c662fd 100644
--- a/res/drawable-mdpi/hotseat_browser_normal.png
+++ b/res/drawable-mdpi/hotseat_browser_normal.png
Binary files differ
diff --git a/res/drawable-mdpi/hotseat_browser_pressed.png b/res/drawable-mdpi/hotseat_browser_pressed.png
index d0a072e..ed10c18 100644
--- a/res/drawable-mdpi/hotseat_browser_pressed.png
+++ b/res/drawable-mdpi/hotseat_browser_pressed.png
Binary files differ
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 22b4825..1f13f1f 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -77,8 +77,9 @@
 
     <com.android.launcher2.DeleteZone
         android:id="@+id/delete_zone"
-        android:layout_width="@dimen/button_bar_height_portrait"
-        android:layout_height="match_parent"
+        android:layout_width="@dimen/delete_zone_size"
+        android:layout_height="@dimen/delete_zone_size"
+        android:paddingLeft="@dimen/delete_zone_padding"
         android:layout_marginBottom="@dimen/half_status_bar_height"
         android:layout_gravity="right|center_vertical"
 
@@ -94,13 +95,13 @@
         android:layout_width="@dimen/button_bar_height_portrait"
         android:layout_gravity="right|center_vertical"
         android:layout_marginBottom="@dimen/half_status_bar_height"
-        android:padding="4dip"
         >
 
         <com.android.launcher2.HandleView
             style="@style/HotseatButton"
             android:id="@+id/all_apps_button"
-            android:layout_centerInParent="true"
+            android:layout_centerVertical="true"
+            android:layout_alignParentRight="true"
 
             android:src="@drawable/all_apps_button"
             launcher:direction="vertical"
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index 3b181d1..8dc5092 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -71,8 +71,9 @@
 
     <com.android.launcher2.DeleteZone
         android:id="@+id/delete_zone"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/button_bar_height"
+        android:layout_width="@dimen/delete_zone_size"
+        android:layout_height="@dimen/delete_zone_size"
+        android:paddingTop="@dimen/delete_zone_padding"
         android:layout_gravity="bottom|center_horizontal"
 
         android:scaleType="center"
@@ -86,13 +87,14 @@
         android:layout_width="fill_parent"
         android:layout_height="@dimen/button_bar_height"
         android:layout_gravity="bottom|center_horizontal"
-        android:padding="4dip"
+        android:paddingTop="2dip"
         >
 
         <com.android.launcher2.HandleView
             style="@style/HotseatButton"
             android:id="@+id/all_apps_button"
-            android:layout_centerInParent="true"
+            android:layout_centerHorizontal="true"
+            android:layout_alignParentBottom="true"
 
             android:src="@drawable/all_apps_button"
             launcher:direction="horizontal"
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index bad2730..104f286 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -46,7 +46,7 @@
     <string name="shortcut_uninstalled" msgid="2129499669449749995">"Zástupce <xliff:g id="NAME">%s</xliff:g> byl odebrán."</string>
     <string name="shortcut_duplicate" msgid="4757756326465060694">"Zástupce <xliff:g id="NAME">%s</xliff:g> již existuje."</string>
     <string name="title_select_shortcut" msgid="2858897527672831763">"Vyberte zástupce"</string>
-    <string name="title_select_live_folder" msgid="3753447798805166749">"Vybrat složku"</string>
+    <string name="title_select_live_folder" msgid="3753447798805166749">"Vyberte složku"</string>
     <string name="all_apps_button_label" msgid="3953036962111614813">"Všechny aplikace"</string>
     <string name="all_apps_home_button_label" msgid="1022222300329398558">"Plocha"</string>
     <string name="menu_add" msgid="3065046628354640854">"Přidat"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 2c10765..e4f705e 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -19,7 +19,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="application_name" msgid="8424725141379931883">"Launcher"</string>
+    <string name="application_name" msgid="8424725141379931883">"Übersicht"</string>
     <string name="uid_name" msgid="3371120195364560632">"Android Core Apps"</string>
     <string name="folder_name" msgid="4588446541914685904">"Ordner"</string>
     <string name="chooser_wallpaper" msgid="5988031014201479733">"Hintergrund auswählen"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index d4746ab..5f6b4e5 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -23,7 +23,7 @@
     <string name="uid_name" msgid="3371120195364560632">"Aplicaciones del núcleo de Android"</string>
     <string name="folder_name" msgid="4588446541914685904">"Carpeta"</string>
     <string name="chooser_wallpaper" msgid="5988031014201479733">"Seleccionar papel tapiz desde"</string>
-    <string name="wallpaper_instructions" msgid="4215640646180727542">"Definir papel tapiz"</string>
+    <string name="wallpaper_instructions" msgid="4215640646180727542">"Definir como fondo de pantalla"</string>
     <string name="pick_wallpaper" msgid="5630222540525626723">"Papeles tapiz"</string>
     <string name="activity_not_found" msgid="3571057450431950427">"La aplicación no está instalada en tu computadora."</string>
     <string name="configure_wallpaper" msgid="2820186271419674623">"Configurar..."</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 5ded7a8..36dd82b 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -31,7 +31,7 @@
     <string name="rename_folder_title" msgid="4544573104191526550">"Cambiar nombre de carpeta"</string>
     <string name="rename_action" msgid="6016003384693240896">"Aceptar"</string>
     <string name="cancel_action" msgid="3811860427489435048">"Cancelar"</string>
-    <string name="menu_item_add_item" msgid="6233177331075781114">"Añadir a pantalla de inicio"</string>
+    <string name="menu_item_add_item" msgid="6233177331075781114">"Añadir a pantalla principal"</string>
     <string name="group_applications" msgid="4118484163419674240">"Aplicaciones"</string>
     <string name="group_shortcuts" msgid="9133529424900391877">"Accesos directos"</string>
     <string name="group_folder" msgid="5143593791798929193">"Nueva carpeta"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index f8250f4..5702b33 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -40,7 +40,7 @@
     <string name="group_wallpapers" msgid="1568191644272224858">"배경화면"</string>
     <string name="add_folder" msgid="3521088587367839879">"폴더"</string>
     <string name="add_clock" msgid="2337943840175865746">"시계"</string>
-    <string name="add_photo_frame" msgid="3154058437359487954">"사진 프레임"</string>
+    <string name="add_photo_frame" msgid="3154058437359487954">"사진 액자"</string>
     <string name="out_of_space" msgid="8365249326091984698">"홈 화면에 더 이상 공간이 없습니다."</string>
     <string name="shortcut_installed" msgid="7071557296331322355">"바로가기(\'<xliff:g id="NAME">%s</xliff:g>\')가 생성되었습니다."</string>
     <string name="shortcut_uninstalled" msgid="2129499669449749995">"바로가기(\'<xliff:g id="NAME">%s</xliff:g>\')가 삭제되었습니다."</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 0dbed93..b72f7e0 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -22,8 +22,8 @@
     <string name="application_name" msgid="8424725141379931883">"Launcher"</string>
     <string name="uid_name" msgid="3371120195364560632">"Principais aplicativos do Android"</string>
     <string name="folder_name" msgid="4588446541914685904">"Pasta"</string>
-    <string name="chooser_wallpaper" msgid="5988031014201479733">"Selecionar papel de parede de"</string>
-    <string name="wallpaper_instructions" msgid="4215640646180727542">"Definir papel de parede"</string>
+    <string name="chooser_wallpaper" msgid="5988031014201479733">"Selecionar plano de fundo de"</string>
+    <string name="wallpaper_instructions" msgid="4215640646180727542">"Definir plano de fundo"</string>
     <string name="pick_wallpaper" msgid="5630222540525626723">"Papéis de parede"</string>
     <string name="activity_not_found" msgid="3571057450431950427">"O aplicativo não está instalado no seu telefone."</string>
     <string name="configure_wallpaper" msgid="2820186271419674623">"Configurar..."</string>
@@ -50,8 +50,8 @@
     <string name="all_apps_button_label" msgid="3953036962111614813">"Todos os aplicativos"</string>
     <string name="all_apps_home_button_label" msgid="1022222300329398558">"Página inicial"</string>
     <string name="menu_add" msgid="3065046628354640854">"Adicionar"</string>
-    <string name="menu_wallpaper" msgid="5837429080911269832">"Papel de parede"</string>
-    <string name="menu_search" msgid="4826514464423239041">"Pesquisar"</string>
+    <string name="menu_wallpaper" msgid="5837429080911269832">"Plano de fundo"</string>
+    <string name="menu_search" msgid="4826514464423239041">"Pesquisa"</string>
     <string name="menu_notifications" msgid="6424587053194766192">"Notificações"</string>
     <string name="menu_gestures" msgid="514678675575912237">"Gestos"</string>
     <string name="menu_settings" msgid="6233960148378443661">"Configurações"</string>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..1fcabb2
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string-array name="hotseats" translatable="false">
+        <item>intent:#Intent;action=android.intent.action.MAIN;component=com.android.contacts/.ContactsLaunchActivity;end</item>
+        <item>*BROWSER*</item>
+    </string-array>
+    <array name="hotseat_icons" translatable="false">
+        <item>@drawable/hotseat_phone</item>
+        <item>@drawable/hotseat_browser</item>
+    </array>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5e3bb98..26cb7e6 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -27,4 +27,10 @@
     <!-- roughly half a status bar (for vertically centering the right-hand
          button cluster in landscape) -->
     <dimen name="half_status_bar_height">12dip</dimen>
+
+    <!-- height & width of the drop rectangle for the trash icon -->
+    <dimen name="delete_zone_size">70dip</dimen>
+    
+    <!-- delete_zone_size_full - button_bar_height_portrait -->
+    <dimen name="delete_zone_padding">14dip</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 73c6a8a..86c1b3c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -141,4 +141,10 @@
     <!-- 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>
 
+    <!-- URL to use when detecting the current default browser. 
+         Attempts to mimic homepage_base in Browser; {CID} should be replaced
+         by the client-id, e.g. "android-google". -->
+    <string name="default_browser_url" translatable="false">
+        http://www.google.com/m?client=ms-{CID}&amp;source=android-home-hotseat</string>
+
 </resources>
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 5a2a7d3..132f0e9 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -35,8 +35,10 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
@@ -74,6 +76,7 @@
 import android.appwidget.AppWidgetProviderInfo;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.HashMap;
 import java.io.DataOutputStream;
 import java.io.FileNotFoundException;
@@ -203,13 +206,10 @@
     // Hotseats (quick-launch icons next to AllApps)
     // TODO: move these intial intents out to Uris in an XML resource
     private static final int NUM_HOTSEATS = 2;
-    private Intent[] mHotseats = new Intent[] {
-        new Intent(Intent.ACTION_MAIN)
-            .setComponent(ComponentName.unflattenFromString(
-                "com.android.contacts/.ContactsLaunchActivity")),
-        new Intent(Intent.ACTION_WEB_SEARCH, Uri.EMPTY),
-    };
-    private CharSequence[] mHotseatLabels = new CharSequence[2];
+    private String[] mHotseatConfig = null;
+    private Intent[] mHotseats = null;
+    private Drawable[] mHotseatIcons = null;
+    private CharSequence[] mHotseatLabels = null;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -368,37 +368,144 @@
         wpm.suggestDesiredDimensions(width * WALLPAPER_SCREENS_SPAN, height);
     }
 
+    // Note: This doesn't do all the client-id magic that BrowserProvider does
+    // in Browser. (http://b/2425179)
+    private Uri getDefaultBrowserUri() {
+        String url = getString(R.string.default_browser_url);
+        if (url.indexOf("{CID}") != -1) {
+            url = url.replace("{CID}", "android-google");
+        }
+        return Uri.parse(url);
+    }
+
+    // Load the Intent templates from arrays.xml to populate the hotseats. For
+    // each Intent, if it resolves to a single app, use that as the launch
+    // intent & use that app's label as the contentDescription. Otherwise,
+    // retain the ResolveActivity so the user can pick an app.
     private void loadHotseats() {
+        if (mHotseatConfig == null) {
+            mHotseatConfig = getResources().getStringArray(R.array.hotseats);
+            if (mHotseatConfig.length > 0) {
+                mHotseats = new Intent[mHotseatConfig.length];
+                mHotseatLabels = new CharSequence[mHotseatConfig.length];
+                mHotseatIcons = new Drawable[mHotseatConfig.length];
+            } else {
+                mHotseats = null;
+                mHotseatIcons = null;
+                mHotseatLabels = null;
+            }
+
+            TypedArray hotseatIconDrawables = getResources().obtainTypedArray(R.array.hotseat_icons);
+            for (int i=0; i<mHotseatConfig.length; i++) {
+                // load icon for this slot; currently unrelated to the actual activity
+                try {
+                    mHotseatIcons[i] = hotseatIconDrawables.getDrawable(i);
+                } catch (ArrayIndexOutOfBoundsException ex) {
+                    Log.w(TAG, "Missing hotseat_icons array item #" + i);
+                    mHotseatIcons[i] = null;
+                }
+            }
+            hotseatIconDrawables.recycle();
+        }
+
         PackageManager pm = getPackageManager();
-        for (int i=0; i<mHotseats.length; i++) {
-            Intent intent = mHotseats[i];
+        for (int i=0; i<mHotseatConfig.length; i++) {
+            Intent intent = null;
+            if (mHotseatConfig[i].equals("*BROWSER*")) {
+                // magic value meaning "launch user's default web browser"
+                // replace it with a generic web request so we can see if there is indeed a default
+                String defaultUri = getString(R.string.default_browser_url);
+                intent = new Intent(
+                        Intent.ACTION_VIEW,
+                        ((defaultUri != null)
+                            ? Uri.parse(defaultUri)
+                            : getDefaultBrowserUri())
+                    ).addCategory(Intent.CATEGORY_BROWSABLE);
+                // note: if the user launches this without a default set, she
+                // will always be taken to the default URL above; this is
+                // unavoidable as we must specify a valid URL in order for the
+                // chooser to appear, and once the user selects something, that 
+                // URL is unavoidably sent to the chosen app.
+            } else {
+                try {
+                    intent = Intent.parseUri(mHotseatConfig[i], 0);
+                } catch (java.net.URISyntaxException ex) {
+                    Log.w(TAG, "Invalid hotseat intent: " + mHotseatConfig[i]);
+                    // bogus; leave intent=null
+                }
+            }
+            
+            if (intent == null) {
+                mHotseats[i] = null;
+                mHotseatLabels[i] = getText(R.string.activity_not_found);
+                continue;
+            }
 
             if (LOGD) {
                 Log.d(TAG, "loadHotseats: hotseat " + i 
-                    + " initial intent=[" + intent.toUri(Intent.URI_INTENT_SCHEME)
+                    + " initial intent=[" 
+                    + intent.toUri(Intent.URI_INTENT_SCHEME)
                     + "]");
             }
 
-            // fix up the default intents
-            if (intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
-                    && intent.getData().equals(Uri.EMPTY)) {
-                // use this to represent "default web browser"
-                intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com/"));
+            ResolveInfo bestMatch = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+            List<ResolveInfo> allMatches = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+            if (LOGD) { 
+                Log.d(TAG, "Best match for intent: " + bestMatch);
+                Log.d(TAG, "All matches: ");
+                for (ResolveInfo ri : allMatches) {
+                    Log.d(TAG, "  --> " + ri);
+                }
             }
-            ComponentName com = intent.resolveActivity(pm);
-            mHotseats[i] = new Intent(Intent.ACTION_MAIN).setComponent(com);
+            // did this resolve to a single app, or the resolver?
+            if (allMatches.size() == 0 || bestMatch == null) {
+                // can't find any activity to handle this. let's leave the 
+                // intent as-is and let Launcher show a toast when it fails 
+                // to launch.
+                mHotseats[i] = intent;
 
-            // load the labels for accessibility
-            try {
-                ActivityInfo ai = pm.getActivityInfo(com, 0);
-                mHotseatLabels[i] = ai.loadLabel(pm);
-            } catch (PackageManager.NameNotFoundException ex) {
-                mHotseatLabels[i] = "";
+                // set accessibility text to "Not installed"
+                mHotseatLabels[i] = getText(R.string.activity_not_found);
+            } else {
+                boolean found = false;
+                for (ResolveInfo ri : allMatches) {
+                    if (bestMatch.activityInfo.name.equals(ri.activityInfo.name)
+                        && bestMatch.activityInfo.applicationInfo.packageName
+                            .equals(ri.activityInfo.applicationInfo.packageName)) {
+                        found = true;
+                        break;
+                    }
+                }
+                
+                if (!found) {
+                    if (LOGD) Log.d(TAG, "Multiple options, no default yet");
+                    // the bestMatch is probably the ResolveActivity, meaning the
+                    // user has not yet selected a default
+                    // so: we'll keep the original intent for now
+                    mHotseats[i] = intent;
+
+                    // set the accessibility text to "Select shortcut"
+                    mHotseatLabels[i] = getText(R.string.title_select_shortcut);
+                } else {
+                    // we have an app!
+                    // now reconstruct the intent to launch it through the front
+                    // door
+                    ComponentName com = new ComponentName(
+                        bestMatch.activityInfo.applicationInfo.packageName,
+                        bestMatch.activityInfo.name);
+                    mHotseats[i] = new Intent(Intent.ACTION_MAIN).setComponent(com);
+
+                    // load the app label for accessibility
+                    mHotseatLabels[i] = bestMatch.activityInfo.loadLabel(pm);
+                }
             }
 
             if (LOGD) {
                 Log.d(TAG, "loadHotseats: hotseat " + i 
-                    + " intent=[" + mHotseats[i].toUri(Intent.URI_INTENT_SCHEME)
+                    + " final intent=[" 
+                    + ((mHotseats[i] == null)
+                        ? "null"
+                        : mHotseats[i].toUri(Intent.URI_INTENT_SCHEME))
                     + "] label=[" + mHotseatLabels[i]
                     + "]"
                     );
@@ -622,8 +729,12 @@
         mHandleView.setOnClickListener(this);
         mHandleView.setOnLongClickListener(this);
 
-        findViewById(R.id.hotseat_left).setContentDescription(mHotseatLabels[0]);
-        findViewById(R.id.hotseat_right).setContentDescription(mHotseatLabels[1]);
+        ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left);
+        hotseatLeft.setContentDescription(mHotseatLabels[0]);
+        hotseatLeft.setImageDrawable(mHotseatIcons[0]);
+        ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right);
+        hotseatRight.setContentDescription(mHotseatLabels[1]);
+        hotseatRight.setImageDrawable(mHotseatIcons[1]);
 
         mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen);
         mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen);
@@ -678,7 +789,10 @@
             index = 1;
         }
 
-        if (index >= 0 && mHotseats[index] != null) {
+        // reload these every tap; you never know when they might change
+        loadHotseats();
+        if (index >= 0 && index < mHotseats.length && mHotseats[index] != null) {
+            Intent intent = mHotseats[index];
             startActivitySafely(
                 mHotseats[index],
                 "hotseat"
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index b85b12c..d84d4cd 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -42,7 +42,7 @@
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
-import android.view.animation.OvershootInterpolator;
+import android.view.animation.Interpolator;
 import android.widget.Scroller;
 import android.widget.TextView;
 
@@ -119,6 +119,35 @@
     private Drawable mPreviousIndicator;
     private Drawable mNextIndicator;
 
+    private WorkspaceOvershootInterpolator mScrollInterpolator;
+
+    private static final float BASELINE_FLING_VELOCITY = 2500.f;
+    private static final float FLING_VELOCITY_INFLUENCE = 0.4f;
+    
+    private static class WorkspaceOvershootInterpolator implements Interpolator {
+        private static final float DEFAULT_TENSION = 1.3f;
+        private float mTension;
+
+        public WorkspaceOvershootInterpolator() {
+            mTension = DEFAULT_TENSION;
+        }
+        
+        public void setDistance(int distance) {
+            mTension = distance > 0 ? DEFAULT_TENSION / distance : DEFAULT_TENSION;
+        }
+
+        public void disableSettle() {
+            mTension = 0.f;
+        }
+
+        public float getInterpolation(float t) {
+            // _o(t) = t * t * ((tension + 1) * t + tension)
+            // o(t) = _o(t - 1) + 1
+            t -= 1.0f;
+            return t * t * ((mTension + 1) * t + mTension) + 1.0f;
+        }
+    }
+    
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -154,7 +183,8 @@
      */
     private void initWorkspace() {
         Context context = getContext();
-        mScroller = new Scroller(context, new OvershootInterpolator());
+        mScrollInterpolator = new WorkspaceOvershootInterpolator();
+        mScroller = new Scroller(context, mScrollInterpolator);
         mCurrentScreen = mDefaultScreen;
         Launcher.setScreen(mCurrentScreen);
         LauncherApplication app = (LauncherApplication)context.getApplicationContext();
@@ -429,17 +459,14 @@
             drawChild(canvas, getChildAt(mCurrentScreen), getDrawingTime());
         } else {
             final long drawingTime = getDrawingTime();
-            // If we are flinging, draw only the current screen and the target screen
-            if (mNextScreen >= 0 && mNextScreen < getChildCount() &&
-                    Math.abs(mCurrentScreen - mNextScreen) == 1) {
-                drawChild(canvas, getChildAt(mCurrentScreen), drawingTime);
-                drawChild(canvas, getChildAt(mNextScreen), drawingTime);
-            } else {
-                // If we are scrolling, draw all of our children
-                final int count = getChildCount();
-                for (int i = 0; i < count; i++) {
-                    drawChild(canvas, getChildAt(i), drawingTime);
-                }
+            final float scrollPos = (float) mScrollX / getWidth();
+            final int leftScreen = (int) scrollPos;
+            final int rightScreen = leftScreen + 1;
+            if (leftScreen >= 0) {
+                drawChild(canvas, getChildAt(leftScreen), drawingTime);
+            }
+            if (scrollPos != leftScreen && rightScreen < getChildCount()) {
+                drawChild(canvas, getChildAt(rightScreen), drawingTime);
             }
         }
 
@@ -757,8 +784,11 @@
 
     void enableChildrenCache(int fromScreen, int toScreen) {
         if (fromScreen > toScreen) {
-            fromScreen = toScreen;
-            toScreen = fromScreen;
+            final int temp = fromScreen; 
+            fromScreen = toScreen - 1;
+            toScreen = temp;
+        } else {
+            toScreen++;
         }
         
         final int count = getChildCount();
@@ -857,15 +887,15 @@
                     // Don't fling across more than one screen at a time.
                     final int bound = scrolledPos < whichScreen ?
                             mCurrentScreen - 1 : mCurrentScreen;
-                    snapToScreen(Math.min(whichScreen, bound));
+                    snapToScreen(Math.min(whichScreen, bound), velocityX, true);
                 } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
                     // Fling hard enough to move right
                     // Don't fling across more than one screen at a time.
                     final int bound = scrolledPos > whichScreen ?
                             mCurrentScreen + 1 : mCurrentScreen;
-                    snapToScreen(Math.max(whichScreen, bound));
+                    snapToScreen(Math.max(whichScreen, bound), velocityX, true);
                 } else {
-                    snapToDestination();
+                    snapToScreen(whichScreen, 0, true);
                 }
 
                 if (mVelocityTracker != null) {
@@ -887,15 +917,12 @@
 
         return true;
     }
-
-    private void snapToDestination() {
-        final int screenWidth = getWidth();
-        final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
-
-        snapToScreen(whichScreen);
+    
+    void snapToScreen(int whichScreen) {
+        snapToScreen(whichScreen, 0, false);
     }
 
-    void snapToScreen(int whichScreen) {
+    private void snapToScreen(int whichScreen, int velocity, boolean settle) {
         //if (!mScroller.isFinished()) return;
 
         whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
@@ -903,24 +930,41 @@
         clearVacantCache();
         enableChildrenCache(mCurrentScreen, whichScreen);
 
-        final int screenDelta = Math.abs(whichScreen - mCurrentScreen);
-        
         mNextScreen = whichScreen;
 
         mPreviousIndicator.setLevel(mNextScreen);
         mNextIndicator.setLevel(mNextScreen);
 
         View focusedChild = getFocusedChild();
-        if (focusedChild != null && screenDelta != 0 && focusedChild == getChildAt(mCurrentScreen)) {
+        if (focusedChild != null && whichScreen != mCurrentScreen &&
+                focusedChild == getChildAt(mCurrentScreen)) {
             focusedChild.clearFocus();
         }
         
+        final int screenDelta = Math.max(1, Math.abs(whichScreen - mCurrentScreen));
         final int newX = whichScreen * getWidth();
         final int delta = newX - mScrollX;
-        final int duration = screenDelta != 0 ? screenDelta * 300 : 300;
-        awakenScrollBars(duration);
+        int duration = (screenDelta + 1) * 100;
 
-        if (!mScroller.isFinished()) mScroller.abortAnimation();
+        if (!mScroller.isFinished()) {
+            mScroller.abortAnimation();
+        }
+        
+        if (settle) {
+            mScrollInterpolator.setDistance(screenDelta);
+        } else {
+            mScrollInterpolator.disableSettle();
+        }
+        
+        velocity = Math.abs(velocity);
+        if (velocity > 0) {
+            duration += (duration / (velocity / BASELINE_FLING_VELOCITY))
+                    * FLING_VELOCITY_INFLUENCE;
+        } else {
+            duration += 100;
+        }
+
+        awakenScrollBars(duration);
         mScroller.startScroll(mScrollX, 0, delta, 0, duration);
         invalidate();
     }