Merge "Fix attachFunctor path to ignore delay" into jb-dev
diff --git a/api/16.txt b/api/16.txt
index 25885fb..c95de36 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -675,6 +675,7 @@
     field public static final int maxWidth = 16843039; // 0x101011f
     field public static final int measureAllChildren = 16843018; // 0x101010a
     field public static final int measureWithLargestChild = 16843476; // 0x10102d4
+    field public static final int mediaRouteButtonStyle = 16843693; // 0x10103ad
     field public static final int menuCategory = 16843230; // 0x10101de
     field public static final int mimeType = 16842790; // 0x1010026
     field public static final int minDate = 16843583; // 0x101033f
@@ -1789,6 +1790,7 @@
     field public static final int Widget_DeviceDefault_Light_ListPopupWindow = 16974235; // 0x103019b
     field public static final int Widget_DeviceDefault_Light_ListView = 16974210; // 0x1030182
     field public static final int Widget_DeviceDefault_Light_ListView_DropDown = 16974205; // 0x103017d
+    field public static final int Widget_DeviceDefault_Light_MediaRouteButton = 16974296; // 0x10301d8
     field public static final int Widget_DeviceDefault_Light_PopupMenu = 16974236; // 0x103019c
     field public static final int Widget_DeviceDefault_Light_PopupWindow = 16974211; // 0x1030183
     field public static final int Widget_DeviceDefault_Light_ProgressBar = 16974212; // 0x1030184
@@ -1814,6 +1816,7 @@
     field public static final int Widget_DeviceDefault_ListPopupWindow = 16974180; // 0x1030164
     field public static final int Widget_DeviceDefault_ListView = 16974158; // 0x103014e
     field public static final int Widget_DeviceDefault_ListView_DropDown = 16974153; // 0x1030149
+    field public static final int Widget_DeviceDefault_MediaRouteButton = 16974295; // 0x10301d7
     field public static final int Widget_DeviceDefault_PopupMenu = 16974181; // 0x1030165
     field public static final int Widget_DeviceDefault_PopupWindow = 16974159; // 0x103014f
     field public static final int Widget_DeviceDefault_ProgressBar = 16974160; // 0x1030150
@@ -1905,6 +1908,7 @@
     field public static final int Widget_Holo_Light_ListPopupWindow = 16974043; // 0x10300db
     field public static final int Widget_Holo_Light_ListView = 16974018; // 0x10300c2
     field public static final int Widget_Holo_Light_ListView_DropDown = 16974013; // 0x10300bd
+    field public static final int Widget_Holo_Light_MediaRouteButton = 16974294; // 0x10301d6
     field public static final int Widget_Holo_Light_PopupMenu = 16974044; // 0x10300dc
     field public static final int Widget_Holo_Light_PopupWindow = 16974019; // 0x10300c3
     field public static final int Widget_Holo_Light_ProgressBar = 16974020; // 0x10300c4
@@ -1930,6 +1934,7 @@
     field public static final int Widget_Holo_ListPopupWindow = 16973997; // 0x10300ad
     field public static final int Widget_Holo_ListView = 16973975; // 0x1030097
     field public static final int Widget_Holo_ListView_DropDown = 16973970; // 0x1030092
+    field public static final int Widget_Holo_MediaRouteButton = 16974293; // 0x10301d5
     field public static final int Widget_Holo_PopupMenu = 16973998; // 0x10300ae
     field public static final int Widget_Holo_PopupWindow = 16973976; // 0x1030098
     field public static final int Widget_Holo_ProgressBar = 16973977; // 0x1030099
@@ -3671,6 +3676,20 @@
     method public android.view.Window startActivity(java.lang.String, android.content.Intent);
   }
 
+  public class MediaRouteActionProvider extends android.view.ActionProvider {
+    ctor public MediaRouteActionProvider(android.content.Context);
+    method public android.view.View onCreateActionView();
+    method public void setRouteTypes(int);
+  }
+
+  public class MediaRouteButton extends android.view.View {
+    ctor public MediaRouteButton(android.content.Context);
+    ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet);
+    ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet, int);
+    method public int getRouteTypes();
+    method public void setRouteTypes(int);
+  }
+
   public class NativeActivity extends android.app.Activity implements android.view.InputQueue.Callback android.view.SurfaceHolder.Callback2 android.view.ViewTreeObserver.OnGlobalLayoutListener {
     ctor public NativeActivity();
     method public void onGlobalLayout();
@@ -5317,6 +5336,7 @@
     field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
     field public static final java.lang.String LAYOUT_INFLATER_SERVICE = "layout_inflater";
     field public static final java.lang.String LOCATION_SERVICE = "location";
+    field public static final java.lang.String MEDIA_ROUTER_SERVICE = "media_router";
     field public static final int MODE_APPEND = 32768; // 0x8000
     field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
     field public static final int MODE_MULTI_PROCESS = 4; // 0x4
@@ -11486,6 +11506,74 @@
     field public static final int DEFAULT = 0; // 0x0
   }
 
+  public class MediaRouter {
+    method public void addCallback(int, android.media.MediaRouter.Callback);
+    method public void addUserRoute(android.media.MediaRouter.UserRouteInfo);
+    method public void clearUserRoutes();
+    method public android.media.MediaRouter.RouteCategory createRouteCategory(java.lang.CharSequence, boolean);
+    method public android.media.MediaRouter.UserRouteInfo createUserRoute(android.media.MediaRouter.RouteCategory);
+    method public android.media.MediaRouter.RouteCategory getCategoryAt(int);
+    method public int getCategoryCount();
+    method public android.media.MediaRouter.RouteInfo getRouteAt(int);
+    method public int getRouteCount();
+    method public android.media.MediaRouter.RouteInfo getSelectedRoute(int);
+    method public void removeCallback(android.media.MediaRouter.Callback);
+    method public void removeUserRoute(android.media.MediaRouter.UserRouteInfo);
+    method public void selectRoute(int, android.media.MediaRouter.RouteInfo);
+    field public static final int ROUTE_TYPE_LIVE_AUDIO = 1; // 0x1
+    field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
+  }
+
+  public static abstract interface MediaRouter.Callback {
+    method public abstract void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public abstract void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public abstract void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int);
+    method public abstract void onRouteRemoved(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public abstract void onRouteSelected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
+    method public abstract void onRouteUngrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup);
+    method public abstract void onRouteUnselected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
+  }
+
+  public static class MediaRouter.RouteCategory {
+    method public java.lang.CharSequence getName();
+    method public java.util.List<android.media.MediaRouter.RouteInfo> getRoutes(java.util.List<android.media.MediaRouter.RouteInfo>);
+    method public int getSupportedTypes();
+    method public boolean isGroupable();
+  }
+
+  public static class MediaRouter.RouteGroup extends android.media.MediaRouter.RouteInfo {
+    method public void addRoute(android.media.MediaRouter.RouteInfo);
+    method public void addRoute(android.media.MediaRouter.RouteInfo, int);
+    method public android.media.MediaRouter.RouteInfo getRouteAt(int);
+    method public int getRouteCount();
+    method public void removeRoute(android.media.MediaRouter.RouteInfo);
+    method public void removeRoute(int);
+  }
+
+  public static class MediaRouter.RouteInfo {
+    method public android.media.MediaRouter.RouteCategory getCategory();
+    method public android.media.MediaRouter.RouteGroup getGroup();
+    method public java.lang.CharSequence getName();
+    method public java.lang.CharSequence getStatus();
+    method public int getSupportedTypes();
+  }
+
+  public static class MediaRouter.SimpleCallback implements android.media.MediaRouter.Callback {
+    ctor public MediaRouter.SimpleCallback();
+    method public void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int);
+    method public void onRouteRemoved(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public void onRouteSelected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
+    method public void onRouteUngrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup);
+    method public void onRouteUnselected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
+  }
+
+  public static class MediaRouter.UserRouteInfo extends android.media.MediaRouter.RouteInfo {
+    method public void setName(java.lang.CharSequence);
+    method public void setStatus(java.lang.CharSequence);
+  }
+
   public class MediaScannerConnection implements android.content.ServiceConnection {
     ctor public MediaScannerConnection(android.content.Context, android.media.MediaScannerConnection.MediaScannerConnectionClient);
     method public void connect();
@@ -22710,7 +22798,8 @@
   public abstract class ActionProvider {
     ctor public ActionProvider(android.content.Context);
     method public boolean hasSubMenu();
-    method public abstract android.view.View onCreateActionView();
+    method public abstract deprecated android.view.View onCreateActionView();
+    method public android.view.View onCreateActionView(android.view.MenuItem);
     method public boolean onPerformDefaultAction();
     method public void onPrepareSubMenu(android.view.SubMenu);
   }
diff --git a/api/current.txt b/api/current.txt
index 3beaaa5..55c9e28 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -98,7 +98,6 @@
     field public static final java.lang.String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
     field public static final java.lang.String REORDER_TASKS = "android.permission.REORDER_TASKS";
     field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
-    field public static final java.lang.String ROUTE_MEDIA_OUTPUT = "android.permission.ROUTE_MEDIA_OUTPUT";
     field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
     field public static final java.lang.String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
     field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -3680,6 +3679,7 @@
   public class MediaRouteActionProvider extends android.view.ActionProvider {
     ctor public MediaRouteActionProvider(android.content.Context);
     method public android.view.View onCreateActionView();
+    method public void setExtendedSettingsClickListener(android.view.View.OnClickListener);
     method public void setRouteTypes(int);
   }
 
@@ -3688,6 +3688,7 @@
     ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet);
     ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet, int);
     method public int getRouteTypes();
+    method public void setExtendedSettingsClickListener(android.view.View.OnClickListener);
     method public void setRouteTypes(int);
   }
 
@@ -5337,6 +5338,7 @@
     field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
     field public static final java.lang.String LAYOUT_INFLATER_SERVICE = "layout_inflater";
     field public static final java.lang.String LOCATION_SERVICE = "location";
+    field public static final java.lang.String MEDIA_ROUTER_SERVICE = "media_router";
     field public static final int MODE_APPEND = 32768; // 0x8000
     field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
     field public static final int MODE_MULTI_PROCESS = 4; // 0x4
@@ -11512,7 +11514,6 @@
     method public void clearUserRoutes();
     method public android.media.MediaRouter.RouteCategory createRouteCategory(java.lang.CharSequence, boolean);
     method public android.media.MediaRouter.UserRouteInfo createUserRoute(android.media.MediaRouter.RouteCategory);
-    method public static android.media.MediaRouter forApplication(android.content.Context);
     method public android.media.MediaRouter.RouteCategory getCategoryAt(int);
     method public int getCategoryCount();
     method public android.media.MediaRouter.RouteInfo getRouteAt(int);
@@ -11521,56 +11522,63 @@
     method public void removeCallback(android.media.MediaRouter.Callback);
     method public void removeUserRoute(android.media.MediaRouter.UserRouteInfo);
     method public void selectRoute(int, android.media.MediaRouter.RouteInfo);
-    method public void setSelectedRouteVolume(int, float);
     field public static final int ROUTE_TYPE_LIVE_AUDIO = 1; // 0x1
     field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
   }
 
   public static abstract interface MediaRouter.Callback {
-    method public abstract void onRouteAdded(int, android.media.MediaRouter.RouteInfo);
-    method public abstract void onRouteChanged(android.media.MediaRouter.RouteInfo);
-    method public abstract void onRouteRemoved(int, android.media.MediaRouter.RouteInfo);
-    method public abstract void onRouteSelected(int, android.media.MediaRouter.RouteInfo);
-    method public abstract void onRouteUnselected(int, android.media.MediaRouter.RouteInfo);
-    method public abstract void onVolumeChanged(int, float);
+    method public abstract void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public abstract void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public abstract void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int);
+    method public abstract void onRouteRemoved(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public abstract void onRouteSelected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
+    method public abstract void onRouteUngrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup);
+    method public abstract void onRouteUnselected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
   }
 
-  public class MediaRouter.RouteCategory {
+  public static class MediaRouter.RouteCategory {
     method public java.lang.CharSequence getName();
-    method public android.media.MediaRouter.RouteInfo getRouteAt(int);
-    method public int getRouteCount();
+    method public java.util.List<android.media.MediaRouter.RouteInfo> getRoutes(java.util.List<android.media.MediaRouter.RouteInfo>);
     method public int getSupportedTypes();
     method public boolean isGroupable();
   }
 
-  public class MediaRouter.RouteGroup extends android.media.MediaRouter.RouteInfo {
+  public static class MediaRouter.RouteGroup extends android.media.MediaRouter.RouteInfo {
     method public void addRoute(android.media.MediaRouter.RouteInfo);
     method public void addRoute(android.media.MediaRouter.RouteInfo, int);
+    method public android.media.MediaRouter.RouteInfo getRouteAt(int);
+    method public int getRouteCount();
     method public void removeRoute(android.media.MediaRouter.RouteInfo);
     method public void removeRoute(int);
+    method public void setIconDrawable(android.graphics.drawable.Drawable);
+    method public void setIconResource(int);
   }
 
-  public class MediaRouter.RouteInfo {
+  public static class MediaRouter.RouteInfo {
     method public android.media.MediaRouter.RouteCategory getCategory();
     method public android.media.MediaRouter.RouteGroup getGroup();
+    method public android.graphics.drawable.Drawable getIconDrawable();
     method public java.lang.CharSequence getName();
     method public java.lang.CharSequence getStatus();
     method public int getSupportedTypes();
-    method public float getVolume();
   }
 
   public static class MediaRouter.SimpleCallback implements android.media.MediaRouter.Callback {
     ctor public MediaRouter.SimpleCallback();
-    method public void onRouteAdded(int, android.media.MediaRouter.RouteInfo);
-    method public void onRouteChanged(android.media.MediaRouter.RouteInfo);
-    method public void onRouteRemoved(int, android.media.MediaRouter.RouteInfo);
-    method public void onRouteSelected(int, android.media.MediaRouter.RouteInfo);
-    method public void onRouteUnselected(int, android.media.MediaRouter.RouteInfo);
-    method public void onVolumeChanged(int, float);
+    method public void onRouteAdded(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public void onRouteChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public void onRouteGrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup, int);
+    method public void onRouteRemoved(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
+    method public void onRouteSelected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
+    method public void onRouteUngrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup);
+    method public void onRouteUnselected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
   }
 
-  public class MediaRouter.UserRouteInfo extends android.media.MediaRouter.RouteInfo {
+  public static class MediaRouter.UserRouteInfo extends android.media.MediaRouter.RouteInfo {
+    method public void setIconDrawable(android.graphics.drawable.Drawable);
+    method public void setIconResource(int);
     method public void setName(java.lang.CharSequence);
+    method public void setRemoteControlClient(android.media.RemoteControlClient);
     method public void setStatus(java.lang.CharSequence);
   }
 
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 1d5f207..225be9c 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -160,7 +160,7 @@
     codec->setDitherImage(false);
     if (codec) {
         codec->decode(&stream, &bitmap,
-                SkBitmap::kRGB_565_Config,
+                SkBitmap::kARGB_8888_Config,
                 SkImageDecoder::kDecodePixels_Mode);
         delete codec;
     }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4c35a8c..b902550 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -55,6 +55,7 @@
 import android.location.ILocationManager;
 import android.location.LocationManager;
 import android.media.AudioManager;
+import android.media.MediaRouter;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.INetworkPolicyManager;
@@ -288,6 +289,11 @@
                     return new AudioManager(ctx);
                 }});
 
+        registerService(MEDIA_ROUTER_SERVICE, new ServiceFetcher() {
+                public Object createService(ContextImpl ctx) {
+                    return new MediaRouter(ctx);
+                }});
+
         registerService(CLIPBOARD_SERVICE, new ServiceFetcher() {
                 public Object createService(ContextImpl ctx) {
                     return new ClipboardManager(ctx.getOuterContext(),
diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java
index 301b125..5fe08ec 100644
--- a/core/java/android/app/MediaRouteActionProvider.java
+++ b/core/java/android/app/MediaRouteActionProvider.java
@@ -33,11 +33,12 @@
     private MediaRouteButton mView;
     private int mRouteTypes;
     private final RouterCallback mRouterCallback = new RouterCallback();
+    private View.OnClickListener mExtendedSettingsListener;
 
     public MediaRouteActionProvider(Context context) {
         super(context);
         mContext = context;
-        mRouter = MediaRouter.forApplication(context);
+        mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
 
         // Start with live audio by default.
         // TODO Update this when new route types are added; segment by API level
@@ -76,6 +77,7 @@
         mView = new MediaRouteButton(mContext);
         mMenuItem.setVisible(mRouter.getRouteCount() > 1);
         mView.setRouteTypes(mRouteTypes);
+        mView.setExtendedSettingsClickListener(mExtendedSettingsListener);
         return mView;
     }
 
@@ -85,14 +87,21 @@
         return true;
     }
 
+    public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+        mExtendedSettingsListener = listener;
+        if (mView != null) {
+            mView.setExtendedSettingsClickListener(listener);
+        }
+    }
+
     private class RouterCallback extends MediaRouter.SimpleCallback {
         @Override
-        public void onRouteAdded(int type, RouteInfo info) {
+        public void onRouteAdded(MediaRouter router, RouteInfo info) {
             mMenuItem.setVisible(mRouter.getRouteCount() > 1);
         }
 
         @Override
-        public void onRouteRemoved(int type, RouteInfo info) {
+        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
             mMenuItem.setVisible(mRouter.getRouteCount() > 1);
         }
     }
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index 3aabd5d..385241c 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -43,6 +43,8 @@
     private int mMinWidth;
     private int mMinHeight;
 
+    private OnClickListener mExtendedSettingsClickListener;
+
     private static final int[] ACTIVATED_STATE_SET = {
         R.attr.state_activated
     };
@@ -58,7 +60,7 @@
     public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        mRouter = MediaRouter.forApplication(context);
+        mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0);
@@ -260,24 +262,29 @@
         mRemoteIndicator.draw(canvas);
     }
 
+    public void setExtendedSettingsClickListener(OnClickListener listener) {
+        // TODO: if dialog is already open, propagate so that it updates live.
+        mExtendedSettingsClickListener = listener;
+    }
+
     private class MediaRouteCallback extends MediaRouter.SimpleCallback {
         @Override
-        public void onRouteSelected(int type, RouteInfo info) {
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
             updateRemoteIndicator();
         }
 
         @Override
-        public void onRouteUnselected(int type, RouteInfo info) {
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
             updateRemoteIndicator();
         }
 
         @Override
-        public void onRouteAdded(int type, RouteInfo info) {
+        public void onRouteAdded(MediaRouter router, RouteInfo info) {
             updateRouteCount();
         }
 
         @Override
-        public void onRouteRemoved(int type, RouteInfo info) {
+        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
             updateRouteCount();
         }
     }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index caade70..ec35a3f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -911,7 +911,7 @@
      * </pre>
      */
     public static class Builder {
-        private static final int MAX_ACTION_BUTTONS = 2;
+        private static final int MAX_ACTION_BUTTONS = 3;
 
         private Context mContext;
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4c169d38..7588cda 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1528,6 +1528,8 @@
      * @see android.net.wifi.WifiManager
      * @see #AUDIO_SERVICE
      * @see android.media.AudioManager
+     * @see #MEDIA_ROUTER_SERVICE
+     * @see android.media.MediaRouter
      * @see #TELEPHONY_SERVICE
      * @see android.telephony.TelephonyManager
      * @see #INPUT_METHOD_SERVICE
@@ -1778,7 +1780,6 @@
      */
     public static final String NSD_SERVICE = "servicediscovery";
 
-
     /**
      * Use with {@link #getSystemService} to retrieve a
      * {@link android.media.AudioManager} for handling management of volume,
@@ -1791,6 +1792,16 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
+     * {@link android.media.MediaRouter} for controlling and managing
+     * routing of media.
+     *
+     * @see #getSystemService
+     * @see android.media.MediaRouter
+     */
+    public static final String MEDIA_ROUTER_SERVICE = "media_router";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
      * {@link android.telephony.TelephonyManager} for handling management the
      * telephony features of the device.
      *
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 70c0c48..90b4247 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -376,4 +376,7 @@
 
     void setPermissionEnforced(String permission, boolean enforced);
     boolean isPermissionEnforced(String permission);
+
+    /** Reflects current DeviceStorageMonitorService state */
+    boolean isStorageLow();
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index b9811c82..f8898c1 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -127,9 +127,13 @@
      */
     public static final PackageParser.SplitPermissionInfo SPLIT_PERMISSIONS[] =
         new PackageParser.SplitPermissionInfo[] {
+            // READ_EXTERNAL_STORAGE is always required when an app requests
+            // WRITE_EXTERNAL_STORAGE, because we can't have an app that has
+            // write access without read access.  The hack here with the target
+            // target SDK version ensures that this grant is always done.
             new PackageParser.SplitPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
                     new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE },
-                    android.os.Build.VERSION_CODES.JELLY_BEAN),
+                    android.os.Build.VERSION_CODES.CUR_DEVELOPMENT+1),
             new PackageParser.SplitPermissionInfo(android.Manifest.permission.READ_CONTACTS,
                     new String[] { android.Manifest.permission.READ_CALL_LOG },
                     android.os.Build.VERSION_CODES.JELLY_BEAN),
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index c453a5d..12f16fd 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -456,6 +456,7 @@
         final int top = Math.max(dtop, 0);
         final int bottom = Math.min(getLineTop(getLineCount()), dbottom);
 
+        if (top >= bottom) return TextUtils.packRangeInLong(0, -1);
         return TextUtils.packRangeInLong(getLineForVertical(top), getLineForVertical(bottom));
     }
 
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
index deed737..8e6147c 100644
--- a/core/java/android/text/style/StyleSpan.java
+++ b/core/java/android/text/style/StyleSpan.java
@@ -98,7 +98,6 @@
         }
 
         int fake = want & ~tf.getStyle();
-        fake |= tf.getStyle() & Typeface.BOLD;
 
         if ((fake & Typeface.BOLD) != 0) {
             paint.setFakeBoldText(true);
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index abd02cf..ecbf4bc 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -235,7 +235,6 @@
             }
 
             int fake = style & ~tf.getStyle();
-            fake |= tf.getStyle() & Typeface.BOLD;
 
             if ((fake & Typeface.BOLD) != 0) {
                 ds.setFakeBoldText(true);
diff --git a/core/java/android/text/style/TypefaceSpan.java b/core/java/android/text/style/TypefaceSpan.java
index 3d74ed0..f194060 100644
--- a/core/java/android/text/style/TypefaceSpan.java
+++ b/core/java/android/text/style/TypefaceSpan.java
@@ -82,7 +82,6 @@
 
         Typeface tf = Typeface.create(family, oldStyle);
         int fake = oldStyle & ~tf.getStyle();
-        fake |= tf.getStyle() & Typeface.BOLD;
 
         if ((fake & Typeface.BOLD) != 0) {
             paint.setFakeBoldText(true);
diff --git a/core/java/android/view/AccessibilityIterators.java b/core/java/android/view/AccessibilityIterators.java
index 2a7dc18..17ce4f6 100644
--- a/core/java/android/view/AccessibilityIterators.java
+++ b/core/java/android/view/AccessibilityIterators.java
@@ -70,20 +70,19 @@
             implements ComponentCallbacks {
         private static CharacterTextSegmentIterator sInstance;
 
-        private final Context mAppContext;
+        private Locale mLocale;
 
         protected BreakIterator mImpl;
 
-        public static CharacterTextSegmentIterator getInstance(Context context) {
+        public static CharacterTextSegmentIterator getInstance(Locale locale) {
             if (sInstance == null) {
-                sInstance = new CharacterTextSegmentIterator(context);
+                sInstance = new CharacterTextSegmentIterator(locale);
             }
             return sInstance;
         }
 
-        private CharacterTextSegmentIterator(Context context) {
-            mAppContext = context.getApplicationContext();
-            Locale locale = mAppContext.getResources().getConfiguration().locale;
+        private CharacterTextSegmentIterator(Locale locale) {
+            mLocale = locale;
             onLocaleChanged(locale);
             ViewRootImpl.addConfigCallback(this);
         }
@@ -148,10 +147,9 @@
 
         @Override
         public void onConfigurationChanged(Configuration newConfig) {
-            Configuration oldConfig = mAppContext.getResources().getConfiguration();
-            final int changed = oldConfig.diff(newConfig);
-            if ((changed & ActivityInfo.CONFIG_LOCALE) != 0) {
-                Locale locale = newConfig.locale;
+            Locale locale = newConfig.locale;
+            if (!mLocale.equals(locale)) {
+                mLocale = locale;
                 onLocaleChanged(locale);
             }
         }
@@ -169,15 +167,15 @@
     static class WordTextSegmentIterator extends CharacterTextSegmentIterator {
         private static WordTextSegmentIterator sInstance;
 
-        public static WordTextSegmentIterator getInstance(Context context) {
+        public static WordTextSegmentIterator getInstance(Locale locale) {
             if (sInstance == null) {
-                sInstance = new WordTextSegmentIterator(context);
+                sInstance = new WordTextSegmentIterator(locale);
             }
             return sInstance;
         }
 
-        private WordTextSegmentIterator(Context context) {
-           super(context);
+        private WordTextSegmentIterator(Locale locale) {
+           super(locale);
         }
 
         @Override
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index aaa081c..78dc86f 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -103,6 +103,11 @@
     private static final boolean USE_FRAME_TIME = SystemProperties.getBoolean(
             "debug.choreographer.frametime", true);
 
+    // Set a limit to warn about skipped frames.
+    // Skipped frames imply jank.
+    private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
+            "debug.choreographer.skipwarning", 30);
+
     private static final long NANOS_PER_MS = 1000000;
 
     private static final int MSG_DO_FRAME = 0;
@@ -486,13 +491,18 @@
             startNanos = System.nanoTime();
             final long jitterNanos = startNanos - frameTimeNanos;
             if (jitterNanos >= mFrameIntervalNanos) {
+                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
+                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
+                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
+                            + "The application may be doing too much work on its main thread.");
+                }
                 final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                 if (DEBUG) {
                     Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                             + "which is more than the frame interval of "
                             + (mFrameIntervalNanos * 0.000001f) + " ms!  "
-                            + "Setting frame time to " + (lastFrameOffset * 0.000001f)
-                            + " ms in the past.");
+                            + "Skipping " + skippedFrames + " frames and setting frame "
+                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                 }
                 frameTimeNanos = startNanos - lastFrameOffset;
             }
@@ -500,7 +510,7 @@
             if (frameTimeNanos < mLastFrameTimeNanos) {
                 if (DEBUG) {
                     Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
-                            + "previously skipped frame.  Waiting for next vsync");
+                            + "previously skipped frame.  Waiting for next vsync.");
                 }
                 scheduleVsyncLocked();
                 return;
@@ -658,6 +668,7 @@
 
     private final class FrameDisplayEventReceiver extends DisplayEventReceiver
             implements Runnable {
+        private boolean mHavePendingVsync;
         private long mTimestampNanos;
         private int mFrame;
 
@@ -672,6 +683,21 @@
             // the message queue.  If there are no messages in the queue with timestamps
             // earlier than the frame time, then the vsync event will be processed immediately.
             // Otherwise, messages that predate the vsync event will be handled first.
+            long now = System.nanoTime();
+            if (timestampNanos > now) {
+                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+                        + " ms in the future!  Check that graphics HAL is generating vsync "
+                        + "timestamps using the correct timebase.");
+                timestampNanos = now;
+            }
+
+            if (mHavePendingVsync) {
+                Log.w(TAG, "Already have a pending vsync event.  There should only be "
+                        + "one at a time.");
+            } else {
+                mHavePendingVsync = true;
+            }
+
             mTimestampNanos = timestampNanos;
             mFrame = frame;
             Message msg = Message.obtain(mHandler, this);
@@ -681,6 +707,7 @@
 
         @Override
         public void run() {
+            mHavePendingVsync = false;
             doFrame(mTimestampNanos, mFrame);
         }
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f005eeb..816b631 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6957,7 +6957,8 @@
                 CharSequence text = getIterableTextForAccessibility();
                 if (text != null && text.length() > 0) {
                     CharacterTextSegmentIterator iterator =
-                        CharacterTextSegmentIterator.getInstance(mContext);
+                        CharacterTextSegmentIterator.getInstance(
+                                mContext.getResources().getConfiguration().locale);
                     iterator.initialize(text.toString());
                     return iterator;
                 }
@@ -6966,7 +6967,8 @@
                 CharSequence text = getIterableTextForAccessibility();
                 if (text != null && text.length() > 0) {
                     WordTextSegmentIterator iterator =
-                        WordTextSegmentIterator.getInstance(mContext);
+                        WordTextSegmentIterator.getInstance(
+                                mContext.getResources().getConfiguration().locale);
                     iterator.initialize(text.toString());
                     return iterator;
                 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b5fff8a..cdc51d1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -229,6 +229,7 @@
     boolean mWindowsAnimating;
     boolean mIsDrawing;
     int mLastSystemUiVisibility;
+    int mClientWindowLayoutFlags;
 
     // Pool of queued input events.
     private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10;
@@ -485,6 +486,8 @@
                 mFallbackEventHandler.setView(view);
                 mWindowAttributes.copyFrom(attrs);
                 attrs = mWindowAttributes;
+                // Keep track of the actual window flags supplied by the client.
+                mClientWindowLayoutFlags = attrs.flags;
 
                 setAccessibilityFocusedHost(null);
 
@@ -671,6 +674,7 @@
     }
 
     public boolean attachFunctor(int functor) {
+        //noinspection SimplifiableIfStatement
         if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
             return mAttachInfo.mHardwareRenderer.attachFunctor(mAttachInfo, functor);
         }
@@ -678,7 +682,7 @@
     }
 
     public void detachFunctor(int functor) {
-        if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
+        if (mAttachInfo.mHardwareRenderer != null) {
             mAttachInfo.mHardwareRenderer.detachFunctor(functor);
         }
     }
@@ -759,6 +763,8 @@
     void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
         synchronized (this) {
             int oldSoftInputMode = mWindowAttributes.softInputMode;
+            // Keep track of the actual window flags supplied by the client.
+            mClientWindowLayoutFlags = attrs.flags;
             // preserve compatible window flag if exists.
             int compatibleWindowFlag =
                 mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
@@ -767,7 +773,9 @@
             attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
             mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
             mWindowAttributes.flags |= compatibleWindowFlag;
-            
+
+            applyKeepScreenOnFlag(mWindowAttributes);
+
             if (newView) {
                 mSoftInputMode = attrs.softInputMode;
                 requestLayout();
@@ -999,6 +1007,18 @@
         }
     }
 
+    private void applyKeepScreenOnFlag(WindowManager.LayoutParams params) {
+        // Update window's global keep screen on flag: if a view has requested
+        // that the screen be kept on, then it is always set; otherwise, it is
+        // set to whatever the client last requested for the global state.
+        if (mAttachInfo.mKeepScreenOn) {
+            params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+        } else {
+            params.flags = (params.flags&~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+                    | (mClientWindowLayoutFlags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        }
+    }
+
     private boolean collectViewAttributes() {
         final View.AttachInfo attachInfo = mAttachInfo;
         if (attachInfo.mRecomputeGlobalAttributes) {
@@ -1016,9 +1036,7 @@
                     || attachInfo.mSystemUiVisibility != oldVis
                     || attachInfo.mHasSystemUiListeners != oldHasSystemUiListeners) {
                 WindowManager.LayoutParams params = mWindowAttributes;
-                if (attachInfo.mKeepScreenOn) {
-                    params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
-                }
+                applyKeepScreenOnFlag(params);
                 params.subtreeSystemUiVisibility = attachInfo.mSystemUiVisibility;
                 params.hasSystemUiListeners = attachInfo.mHasSystemUiListeners;
                 mView.dispatchWindowSystemUiVisiblityChanged(attachInfo.mSystemUiVisibility);
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 39bc7c2..471f259 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -259,12 +259,10 @@
             // now compute what (if any) algorithmic styling is needed
             int typefaceStyle = tf != null ? tf.getStyle() : 0;
             int need = style & ~typefaceStyle;
-            need |= typefaceStyle & Typeface.BOLD;
             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
         } else {
-            int typefaceStyle = tf != null ? tf.getStyle() : 0;
-            mTextPaint.setFakeBoldText((typefaceStyle & Typeface.BOLD) != 0);
+            mTextPaint.setFakeBoldText(false);
             mTextPaint.setTextSkewX(0);
             setSwitchTypeface(tf);
         }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 464a527..01617da 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1237,12 +1237,10 @@
             // now compute what (if any) algorithmic styling is needed
             int typefaceStyle = tf != null ? tf.getStyle() : 0;
             int need = style & ~typefaceStyle;
-            need |= typefaceStyle & Typeface.BOLD; // keep bold in
             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
         } else {
-            int typefaceStyle = tf != null ? tf.getStyle() : 0;
-            mTextPaint.setFakeBoldText((typefaceStyle & Typeface.BOLD) != 0);
+            mTextPaint.setFakeBoldText(false);
             mTextPaint.setTextSkewX(0);
             setTypeface(tf);
         }
diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
new file mode 100644
index 0000000..5096be6
--- /dev/null
+++ b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
@@ -0,0 +1,1226 @@
+/*
+ * Copyright (C) 2012 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.internal.widget.multiwaveview;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Vibrator;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/**
+ * A re-usable widget containing a center, outer ring and wave animation.
+ */
+public class GlowPadView extends View {
+    private static final String TAG = "GlowPadView";
+    private static final boolean DEBUG = false;
+
+    // Wave state machine
+    private static final int STATE_IDLE = 0;
+    private static final int STATE_START = 1;
+    private static final int STATE_FIRST_TOUCH = 2;
+    private static final int STATE_TRACKING = 3;
+    private static final int STATE_SNAP = 4;
+    private static final int STATE_FINISH = 5;
+
+    // Animation properties.
+    private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it
+
+    public interface OnTriggerListener {
+        int NO_HANDLE = 0;
+        int CENTER_HANDLE = 1;
+        public void onGrabbed(View v, int handle);
+        public void onReleased(View v, int handle);
+        public void onTrigger(View v, int target);
+        public void onGrabbedStateChange(View v, int handle);
+        public void onFinishFinalAnimation();
+    }
+
+    // Tuneable parameters for animation
+    private static final int WAVE_ANIMATION_DURATION = 1200;
+    private static final int RETURN_TO_HOME_DELAY = 1200;
+    private static final int RETURN_TO_HOME_DURATION = 200;
+    private static final int HIDE_ANIMATION_DELAY = 200;
+    private static final int HIDE_ANIMATION_DURATION = 200;
+    private static final int SHOW_ANIMATION_DURATION = 200;
+    private static final int SHOW_ANIMATION_DELAY = 50;
+    private static final int INITIAL_SHOW_HANDLE_DURATION = 200;
+    private static final int REVEAL_GLOW_DELAY = 0;
+    private static final int REVEAL_GLOW_DURATION = 0;
+
+    private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f;
+    private static final float TARGET_SCALE_EXPANDED = 1.0f;
+    private static final float TARGET_SCALE_COLLAPSED = 0.8f;
+    private static final float RING_SCALE_EXPANDED = 1.0f;
+    private static final float RING_SCALE_COLLAPSED = 0.5f;
+
+    private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>();
+    private AnimationBundle mWaveAnimations = new AnimationBundle();
+    private AnimationBundle mTargetAnimations = new AnimationBundle();
+    private AnimationBundle mGlowAnimations = new AnimationBundle();
+    private ArrayList<String> mTargetDescriptions;
+    private ArrayList<String> mDirectionDescriptions;
+    private OnTriggerListener mOnTriggerListener;
+    private TargetDrawable mHandleDrawable;
+    private TargetDrawable mOuterRing;
+    private Vibrator mVibrator;
+
+    private int mFeedbackCount = 3;
+    private int mVibrationDuration = 0;
+    private int mGrabbedState;
+    private int mActiveTarget = -1;
+    private float mGlowRadius;
+    private float mWaveCenterX;
+    private float mWaveCenterY;
+    private int mMaxTargetHeight;
+    private int mMaxTargetWidth;
+
+    private float mOuterRadius = 0.0f;
+    private float mHitRadius = 0.0f;
+    private float mSnapMargin = 0.0f;
+    private boolean mDragging;
+    private int mNewTargetResources;
+
+    private class AnimationBundle extends ArrayList<Tweener> {
+        private static final long serialVersionUID = 0xA84D78726F127468L;
+        private boolean mSuspended;
+
+        public void start() {
+            if (mSuspended) return; // ignore attempts to start animations
+            final int count = size();
+            for (int i = 0; i < count; i++) {
+                Tweener anim = get(i);
+                anim.animator.start();
+            }
+        }
+
+        public void cancel() {
+            final int count = size();
+            for (int i = 0; i < count; i++) {
+                Tweener anim = get(i);
+                anim.animator.cancel();
+            }
+            clear();
+        }
+
+        public void stop() {
+            final int count = size();
+            for (int i = 0; i < count; i++) {
+                Tweener anim = get(i);
+                anim.animator.end();
+            }
+            clear();
+        }
+
+        public void setSuspended(boolean suspend) {
+            mSuspended = suspend;
+        }
+    };
+
+    private AnimatorListener mResetListener = new AnimatorListenerAdapter() {
+        public void onAnimationEnd(Animator animator) {
+            switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
+            dispatchOnFinishFinalAnimation();
+        }
+    };
+
+    private AnimatorListener mResetListenerWithPing = new AnimatorListenerAdapter() {
+        public void onAnimationEnd(Animator animator) {
+            ping();
+            switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
+            dispatchOnFinishFinalAnimation();
+        }
+    };
+
+    private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() {
+        public void onAnimationUpdate(ValueAnimator animation) {
+            invalidate();
+        }
+    };
+
+    private boolean mAnimatingTargets;
+    private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() {
+        public void onAnimationEnd(Animator animator) {
+            if (mNewTargetResources != 0) {
+                internalSetTargetResources(mNewTargetResources);
+                mNewTargetResources = 0;
+                hideTargets(false, false);
+            }
+            mAnimatingTargets = false;
+        }
+    };
+    private int mTargetResourceId;
+    private int mTargetDescriptionsResourceId;
+    private int mDirectionDescriptionsResourceId;
+    private boolean mAlwaysTrackFinger;
+    private int mHorizontalInset;
+    private int mVerticalInset;
+    private int mGravity = Gravity.TOP;
+    private boolean mInitialLayout = true;
+    private Tweener mBackgroundAnimator;
+    private PointCloud mPointCloud;
+    private float mInnerRadius;
+
+    public GlowPadView(Context context) {
+        this(context, null);
+    }
+
+    public GlowPadView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        Resources res = context.getResources();
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GlowPadView);
+        mInnerRadius = a.getDimension(R.styleable.GlowPadView_innerRadius, mInnerRadius);
+        mOuterRadius = a.getDimension(R.styleable.GlowPadView_outerRadius, mOuterRadius);
+        mHitRadius = a.getDimension(R.styleable.GlowPadView_hitRadius, mHitRadius);
+        mSnapMargin = a.getDimension(R.styleable.GlowPadView_snapMargin, mSnapMargin);
+        mVibrationDuration = a.getInt(R.styleable.GlowPadView_vibrationDuration,
+                mVibrationDuration);
+        mFeedbackCount = a.getInt(R.styleable.GlowPadView_feedbackCount,
+                mFeedbackCount);
+        mHandleDrawable = new TargetDrawable(res,
+                a.peekValue(R.styleable.GlowPadView_handleDrawable).resourceId);
+        mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
+        mOuterRing = new TargetDrawable(res,
+                getResourceId(a, R.styleable.GlowPadView_outerRingDrawable));
+
+        mAlwaysTrackFinger = a.getBoolean(R.styleable.GlowPadView_alwaysTrackFinger, false);
+
+        int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable);
+        Drawable pointDrawable = pointId != 0 ? res.getDrawable(pointId) : null;
+        mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f);
+
+        TypedValue outValue = new TypedValue();
+
+        // Read array of target drawables
+        if (a.getValue(R.styleable.GlowPadView_targetDrawables, outValue)) {
+            internalSetTargetResources(outValue.resourceId);
+        }
+        if (mTargetDrawables == null || mTargetDrawables.size() == 0) {
+            throw new IllegalStateException("Must specify at least one target drawable");
+        }
+
+        // Read array of target descriptions
+        if (a.getValue(R.styleable.GlowPadView_targetDescriptions, outValue)) {
+            final int resourceId = outValue.resourceId;
+            if (resourceId == 0) {
+                throw new IllegalStateException("Must specify target descriptions");
+            }
+            setTargetDescriptionsResourceId(resourceId);
+        }
+
+        // Read array of direction descriptions
+        if (a.getValue(R.styleable.GlowPadView_directionDescriptions, outValue)) {
+            final int resourceId = outValue.resourceId;
+            if (resourceId == 0) {
+                throw new IllegalStateException("Must specify direction descriptions");
+            }
+            setDirectionDescriptionsResourceId(resourceId);
+        }
+
+        a.recycle();
+
+        // Use gravity attribute from LinearLayout
+        a = context.obtainStyledAttributes(attrs, android.R.styleable.LinearLayout);
+        mGravity = a.getInt(android.R.styleable.LinearLayout_gravity, Gravity.TOP);
+        a.recycle();
+
+        setVibrateEnabled(mVibrationDuration > 0);
+
+        assignDefaultsIfNeeded();
+
+        mPointCloud = new PointCloud(pointDrawable);
+        mPointCloud.makePointCloud(mInnerRadius, mOuterRadius);
+        mPointCloud.glowManager.setRadius(mGlowRadius);
+    }
+
+    private int getResourceId(TypedArray a, int id) {
+        TypedValue tv = a.peekValue(id);
+        return tv == null ? 0 : tv.resourceId;
+    }
+
+    private void dump() {
+        Log.v(TAG, "Outer Radius = " + mOuterRadius);
+        Log.v(TAG, "HitRadius = " + mHitRadius);
+        Log.v(TAG, "SnapMargin = " + mSnapMargin);
+        Log.v(TAG, "FeedbackCount = " + mFeedbackCount);
+        Log.v(TAG, "VibrationDuration = " + mVibrationDuration);
+        Log.v(TAG, "GlowRadius = " + mGlowRadius);
+        Log.v(TAG, "WaveCenterX = " + mWaveCenterX);
+        Log.v(TAG, "WaveCenterY = " + mWaveCenterY);
+    }
+
+    public void suspendAnimations() {
+        mWaveAnimations.setSuspended(true);
+        mTargetAnimations.setSuspended(true);
+        mGlowAnimations.setSuspended(true);
+    }
+
+    public void resumeAnimations() {
+        mWaveAnimations.setSuspended(false);
+        mTargetAnimations.setSuspended(false);
+        mGlowAnimations.setSuspended(false);
+        mWaveAnimations.start();
+        mTargetAnimations.start();
+        mGlowAnimations.start();
+    }
+
+    @Override
+    protected int getSuggestedMinimumWidth() {
+        // View should be large enough to contain the background + handle and
+        // target drawable on either edge.
+        return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth);
+    }
+
+    @Override
+    protected int getSuggestedMinimumHeight() {
+        // View should be large enough to contain the unlock ring + target and
+        // target drawable on either edge
+        return (int) (Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) + mMaxTargetHeight);
+    }
+
+    private int resolveMeasured(int measureSpec, int desired)
+    {
+        int result = 0;
+        int specSize = MeasureSpec.getSize(measureSpec);
+        switch (MeasureSpec.getMode(measureSpec)) {
+            case MeasureSpec.UNSPECIFIED:
+                result = desired;
+                break;
+            case MeasureSpec.AT_MOST:
+                result = Math.min(specSize, desired);
+                break;
+            case MeasureSpec.EXACTLY:
+            default:
+                result = specSize;
+        }
+        return result;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int minimumWidth = getSuggestedMinimumWidth();
+        final int minimumHeight = getSuggestedMinimumHeight();
+        int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
+        int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
+        computeInsets((computedWidth - minimumWidth), (computedHeight - minimumHeight));
+        setMeasuredDimension(computedWidth, computedHeight);
+    }
+
+    private void switchToState(int state, float x, float y) {
+        switch (state) {
+            case STATE_IDLE:
+                deactivateTargets();
+                hideGlow(0, 0, 0.0f, null);
+                startBackgroundAnimation(0, 0.0f);
+                mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
+                mHandleDrawable.setAlpha(1.0f);
+                break;
+
+            case STATE_START:
+                startBackgroundAnimation(0, 0.0f);
+                break;
+
+            case STATE_FIRST_TOUCH:
+                mHandleDrawable.setAlpha(0.0f);
+                deactivateTargets();
+                showTargets(true);
+                startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f);
+                setGrabbedState(OnTriggerListener.CENTER_HANDLE);
+                if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+                    announceTargets();
+                }
+                break;
+
+            case STATE_TRACKING:
+                mHandleDrawable.setAlpha(0.0f);
+                showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 1.0f, null);
+                break;
+
+            case STATE_SNAP:
+                // TODO: Add transition states (see list_selector_background_transition.xml)
+                mHandleDrawable.setAlpha(0.0f);
+                showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 0.0f, null);
+                break;
+
+            case STATE_FINISH:
+                doFinish();
+                break;
+        }
+    }
+
+    private void showGlow(int duration, int delay, float finalAlpha,
+            AnimatorListener finishListener) {
+        mGlowAnimations.cancel();
+        mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
+                "ease", Ease.Cubic.easeIn,
+                "delay", delay,
+                "alpha", finalAlpha,
+                "onUpdate", mUpdateListener,
+                "onComplete", finishListener));
+        mGlowAnimations.start();
+    }
+
+    private void hideGlow(int duration, int delay, float finalAlpha,
+            AnimatorListener finishListener) {
+        mGlowAnimations.cancel();
+        mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
+                "ease", Ease.Quart.easeOut,
+                "delay", delay,
+                "alpha", finalAlpha,
+                "x", 0.0f,
+                "y", 0.0f,
+                "onUpdate", mUpdateListener,
+                "onComplete", finishListener));
+        mGlowAnimations.start();
+    }
+
+    private void deactivateTargets() {
+        final int count = mTargetDrawables.size();
+        for (int i = 0; i < count; i++) {
+            TargetDrawable target = mTargetDrawables.get(i);
+            target.setState(TargetDrawable.STATE_INACTIVE);
+        }
+        mActiveTarget = -1;
+    }
+
+    /**
+     * Dispatches a trigger event to listener. Ignored if a listener is not set.
+     * @param whichTarget the target that was triggered.
+     */
+    private void dispatchTriggerEvent(int whichTarget) {
+        vibrate();
+        if (mOnTriggerListener != null) {
+            mOnTriggerListener.onTrigger(this, whichTarget);
+        }
+    }
+
+    private void dispatchOnFinishFinalAnimation() {
+        if (mOnTriggerListener != null) {
+            mOnTriggerListener.onFinishFinalAnimation();
+        }
+    }
+
+    private void doFinish() {
+        final int activeTarget = mActiveTarget;
+        final boolean targetHit =  activeTarget != -1;
+
+        if (targetHit) {
+            if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit);
+
+            highlightSelected(activeTarget);
+
+            // Inform listener of any active targets.  Typically only one will be active.
+            hideGlow(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener);
+            dispatchTriggerEvent(activeTarget);
+            if (!mAlwaysTrackFinger) {
+                // Force ring and targets to finish animation to final expanded state
+                mTargetAnimations.stop();
+            }
+        } else {
+            // Animate handle back to the center based on current state.
+            hideGlow(HIDE_ANIMATION_DURATION, 0, 0.0f, mResetListenerWithPing);
+            hideTargets(true, false);
+        }
+
+        setGrabbedState(OnTriggerListener.NO_HANDLE);
+    }
+
+    private void highlightSelected(int activeTarget) {
+        // Highlight the given target and fade others
+        mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
+        hideUnselected(activeTarget);
+    }
+
+    private void hideUnselected(int active) {
+        for (int i = 0; i < mTargetDrawables.size(); i++) {
+            if (i != active) {
+                mTargetDrawables.get(i).setAlpha(0.0f);
+            }
+        }
+    }
+
+    private void hideTargets(boolean animate, boolean expanded) {
+        mTargetAnimations.cancel();
+        // Note: these animations should complete at the same time so that we can swap out
+        // the target assets asynchronously from the setTargetResources() call.
+        mAnimatingTargets = animate;
+        final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
+        final int delay = animate ? HIDE_ANIMATION_DELAY : 0;
+
+        final float targetScale = expanded ? TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED;
+        final int length = mTargetDrawables.size();
+        final TimeInterpolator interpolator = Ease.Cubic.easeOut;
+        for (int i = 0; i < length; i++) {
+            TargetDrawable target = mTargetDrawables.get(i);
+            target.setState(TargetDrawable.STATE_INACTIVE);
+            mTargetAnimations.add(Tweener.to(target, duration,
+                    "ease", interpolator,
+                    "alpha", 0.0f,
+                    "scaleX", targetScale,
+                    "scaleY", targetScale,
+                    "delay", delay,
+                    "onUpdate", mUpdateListener));
+        }
+
+        final float ringScaleTarget = expanded ? RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED;
+        mTargetAnimations.add(Tweener.to(mOuterRing, duration,
+                "ease", interpolator,
+                "alpha", 0.0f,
+                "scaleX", ringScaleTarget,
+                "scaleY", ringScaleTarget,
+                "delay", delay,
+                "onUpdate", mUpdateListener,
+                "onComplete", mTargetUpdateListener));
+
+        mTargetAnimations.start();
+    }
+
+    private void showTargets(boolean animate) {
+        mTargetAnimations.stop();
+        mAnimatingTargets = animate;
+        final int delay = animate ? SHOW_ANIMATION_DELAY : 0;
+        final int duration = animate ? SHOW_ANIMATION_DURATION : 0;
+        final int length = mTargetDrawables.size();
+        for (int i = 0; i < length; i++) {
+            TargetDrawable target = mTargetDrawables.get(i);
+            target.setState(TargetDrawable.STATE_INACTIVE);
+            mTargetAnimations.add(Tweener.to(target, duration,
+                    "ease", Ease.Cubic.easeOut,
+                    "alpha", 1.0f,
+                    "scaleX", 1.0f,
+                    "scaleY", 1.0f,
+                    "delay", delay,
+                    "onUpdate", mUpdateListener));
+        }
+        mTargetAnimations.add(Tweener.to(mOuterRing, duration,
+                "ease", Ease.Cubic.easeOut,
+                "alpha", 1.0f,
+                "scaleX", 1.0f,
+                "scaleY", 1.0f,
+                "delay", delay,
+                "onUpdate", mUpdateListener,
+                "onComplete", mTargetUpdateListener));
+
+        mTargetAnimations.start();
+    }
+
+    private void vibrate() {
+        if (mVibrator != null) {
+            mVibrator.vibrate(mVibrationDuration);
+        }
+    }
+
+    private ArrayList<TargetDrawable> loadDrawableArray(int resourceId) {
+        Resources res = getContext().getResources();
+        TypedArray array = res.obtainTypedArray(resourceId);
+        final int count = array.length();
+        ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count);
+        for (int i = 0; i < count; i++) {
+            TypedValue value = array.peekValue(i);
+            TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0);
+            drawables.add(target);
+        }
+        array.recycle();
+        return drawables;
+    }
+
+    private void internalSetTargetResources(int resourceId) {
+        final ArrayList<TargetDrawable> targets = loadDrawableArray(resourceId);
+        mTargetDrawables = targets;
+        mTargetResourceId = resourceId;
+
+        int maxWidth = mHandleDrawable.getWidth();
+        int maxHeight = mHandleDrawable.getHeight();
+        final int count = targets.size();
+        for (int i = 0; i < count; i++) {
+            TargetDrawable target = targets.get(i);
+            maxWidth = Math.max(maxWidth, target.getWidth());
+            maxHeight = Math.max(maxHeight, target.getHeight());
+        }
+        if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) {
+            mMaxTargetWidth = maxWidth;
+            mMaxTargetHeight = maxHeight;
+            requestLayout(); // required to resize layout and call updateTargetPositions()
+        } else {
+            updateTargetPositions(mWaveCenterX, mWaveCenterY);
+            updatePointCloudPosition(mWaveCenterX, mWaveCenterY);
+        }
+    }
+
+    /**
+     * Loads an array of drawables from the given resourceId.
+     *
+     * @param resourceId
+     */
+    public void setTargetResources(int resourceId) {
+        if (mAnimatingTargets) {
+            // postpone this change until we return to the initial state
+            mNewTargetResources = resourceId;
+        } else {
+            internalSetTargetResources(resourceId);
+        }
+    }
+
+    public int getTargetResourceId() {
+        return mTargetResourceId;
+    }
+
+    /**
+     * Sets the resource id specifying the target descriptions for accessibility.
+     *
+     * @param resourceId The resource id.
+     */
+    public void setTargetDescriptionsResourceId(int resourceId) {
+        mTargetDescriptionsResourceId = resourceId;
+        if (mTargetDescriptions != null) {
+            mTargetDescriptions.clear();
+        }
+    }
+
+    /**
+     * Gets the resource id specifying the target descriptions for accessibility.
+     *
+     * @return The resource id.
+     */
+    public int getTargetDescriptionsResourceId() {
+        return mTargetDescriptionsResourceId;
+    }
+
+    /**
+     * Sets the resource id specifying the target direction descriptions for accessibility.
+     *
+     * @param resourceId The resource id.
+     */
+    public void setDirectionDescriptionsResourceId(int resourceId) {
+        mDirectionDescriptionsResourceId = resourceId;
+        if (mDirectionDescriptions != null) {
+            mDirectionDescriptions.clear();
+        }
+    }
+
+    /**
+     * Gets the resource id specifying the target direction descriptions.
+     *
+     * @return The resource id.
+     */
+    public int getDirectionDescriptionsResourceId() {
+        return mDirectionDescriptionsResourceId;
+    }
+
+    /**
+     * Enable or disable vibrate on touch.
+     *
+     * @param enabled
+     */
+    public void setVibrateEnabled(boolean enabled) {
+        if (enabled && mVibrator == null) {
+            mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
+        } else {
+            mVibrator = null;
+        }
+    }
+
+    /**
+     * Starts wave animation.
+     *
+     */
+    public void ping() {
+        if (mFeedbackCount > 0) {
+            startWaveAnimation();
+        }
+    }
+
+    private void stopAndHideWaveAnimation() {
+        mWaveAnimations.cancel();
+        mPointCloud.waveManager.setAlpha(0.0f);
+    }
+
+    private void startWaveAnimation() {
+        mWaveAnimations.cancel();
+        mPointCloud.waveManager.setAlpha(1.0f);
+        mPointCloud.waveManager.setRadius(mHandleDrawable.getWidth()/2.0f);
+        mWaveAnimations.add(Tweener.to(mPointCloud.waveManager, WAVE_ANIMATION_DURATION,
+                "ease", Ease.Linear.easeNone,
+                "delay", 0,
+                "radius", 2.0f * mOuterRadius,
+                "onUpdate", mUpdateListener,
+                "onComplete",
+                new AnimatorListenerAdapter() {
+                    public void onAnimationEnd(Animator animator) {
+                        mPointCloud.waveManager.setRadius(0.0f);
+                        mPointCloud.waveManager.setAlpha(0.0f);
+                    }
+                }));
+        mWaveAnimations.start();
+    }
+
+    /**
+     * Resets the widget to default state and cancels all animation. If animate is 'true', will
+     * animate objects into place. Otherwise, objects will snap back to place.
+     *
+     * @param animate
+     */
+    public void reset(boolean animate) {
+        mGlowAnimations.stop();
+        mTargetAnimations.stop();
+        startBackgroundAnimation(0, 0.0f);
+        stopAndHideWaveAnimation();
+        hideTargets(animate, false);
+        hideGlow(0, 0, 1.0f, null);
+        Tweener.reset();
+    }
+
+    private void startBackgroundAnimation(int duration, float alpha) {
+        final Drawable background = getBackground();
+        if (mAlwaysTrackFinger && background != null) {
+            if (mBackgroundAnimator != null) {
+                mBackgroundAnimator.animator.cancel();
+            }
+            mBackgroundAnimator = Tweener.to(background, duration,
+                    "ease", Ease.Cubic.easeIn,
+                    "alpha", (int)(255.0f * alpha),
+                    "delay", SHOW_ANIMATION_DELAY);
+            mBackgroundAnimator.animator.start();
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final int action = event.getAction();
+        boolean handled = false;
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                if (DEBUG) Log.v(TAG, "*** DOWN ***");
+                handleDown(event);
+                handleMove(event);
+                handled = true;
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                if (DEBUG) Log.v(TAG, "*** MOVE ***");
+                handleMove(event);
+                handled = true;
+                break;
+
+            case MotionEvent.ACTION_UP:
+                if (DEBUG) Log.v(TAG, "*** UP ***");
+                handleMove(event);
+                handleUp(event);
+                handled = true;
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+                if (DEBUG) Log.v(TAG, "*** CANCEL ***");
+                handleMove(event);
+                handleCancel(event);
+                handled = true;
+                break;
+        }
+        invalidate();
+        return handled ? true : super.onTouchEvent(event);
+    }
+
+    private void updateGlowPosition(float x, float y) {
+        mPointCloud.glowManager.setX(x);
+        mPointCloud.glowManager.setY(y);
+    }
+
+    private void handleDown(MotionEvent event) {
+        float eventX = event.getX();
+        float eventY = event.getY();
+        switchToState(STATE_START, eventX, eventY);
+        if (!trySwitchToFirstTouchState(eventX, eventY)) {
+            mDragging = false;
+        } else {
+            updateGlowPosition(eventX, eventY);
+        }
+    }
+
+    private void handleUp(MotionEvent event) {
+        if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE");
+        switchToState(STATE_FINISH, event.getX(), event.getY());
+    }
+
+    private void handleCancel(MotionEvent event) {
+        if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL");
+
+        // We should drop the active target here but it interferes with
+        // moving off the screen in the direction of the navigation bar. At some point we may
+        // want to revisit how we handle this. For now we'll allow a canceled event to
+        // activate the current target.
+
+        // mActiveTarget = -1; // Drop the active target if canceled.
+
+        switchToState(STATE_FINISH, event.getX(), event.getY());
+    }
+
+    private void handleMove(MotionEvent event) {
+        int activeTarget = -1;
+        final int historySize = event.getHistorySize();
+        ArrayList<TargetDrawable> targets = mTargetDrawables;
+        int ntargets = targets.size();
+        final boolean singleTarget = ntargets == 1;
+        float x = 0.0f;
+        float y = 0.0f;
+        for (int k = 0; k < historySize + 1; k++) {
+            float eventX = k < historySize ? event.getHistoricalX(k) : event.getX();
+            float eventY = k < historySize ? event.getHistoricalY(k) : event.getY();
+            // tx and ty are relative to wave center
+            float tx = eventX - mWaveCenterX;
+            float ty = eventY - mWaveCenterY;
+            float touchRadius = (float) Math.sqrt(dist2(tx, ty));
+            final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f;
+            float limitX = tx * scale;
+            float limitY = ty * scale;
+
+            if (!mDragging) {
+                trySwitchToFirstTouchState(eventX, eventY);
+            }
+
+            if (mDragging) {
+                if (singleTarget) {
+                    // Snap to outer ring if there's only one target
+                    float snapRadius = mOuterRadius - mSnapMargin;
+                    if (touchRadius > snapRadius) {
+                        activeTarget = 0;
+                    }
+                } else {
+                    // For more than one target, snap to the closest one less than hitRadius away.
+                    float best = Float.MAX_VALUE;
+                    final float hitRadius2 = mHitRadius * mHitRadius;
+                    // Find first target in range
+                    for (int i = 0; i < ntargets; i++) {
+                        TargetDrawable target = targets.get(i);
+                        float dx = limitX - target.getX();
+                        float dy = limitY - target.getY();
+                        float dist2 = dx*dx + dy*dy;
+                        if (target.isEnabled() && dist2 < hitRadius2 && dist2 < best) {
+                            activeTarget = i;
+                            best = dist2;
+                        }
+                    }
+                }
+            }
+            x = limitX;
+            y = limitY;
+        }
+
+        if (!mDragging) {
+            return;
+        }
+
+        if (activeTarget != -1) {
+            switchToState(STATE_SNAP, x,y);
+            TargetDrawable target = targets.get(activeTarget);
+            final float newX = singleTarget ? x : target.getX();
+            final float newY = singleTarget ? y : target.getY();
+            updateGlowPosition(newX, newY);
+        } else {
+            switchToState(STATE_TRACKING, x, y);
+            updateGlowPosition(x, y);
+        }
+
+        if (mActiveTarget != activeTarget) {
+            // Defocus the old target
+            if (mActiveTarget != -1) {
+                TargetDrawable target = targets.get(mActiveTarget);
+                if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
+                    target.setState(TargetDrawable.STATE_INACTIVE);
+                }
+            }
+            // Focus the new target
+            if (activeTarget != -1) {
+                TargetDrawable target = targets.get(activeTarget);
+                if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
+                    target.setState(TargetDrawable.STATE_FOCUSED);
+                }
+                if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+                    String targetContentDescription = getTargetDescription(activeTarget);
+                    announceText(targetContentDescription);
+                }
+            }
+        }
+        mActiveTarget = activeTarget;
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
+            final int action = event.getAction();
+            switch (action) {
+                case MotionEvent.ACTION_HOVER_ENTER:
+                    event.setAction(MotionEvent.ACTION_DOWN);
+                    break;
+                case MotionEvent.ACTION_HOVER_MOVE:
+                    event.setAction(MotionEvent.ACTION_MOVE);
+                    break;
+                case MotionEvent.ACTION_HOVER_EXIT:
+                    event.setAction(MotionEvent.ACTION_UP);
+                    break;
+            }
+            onTouchEvent(event);
+            event.setAction(action);
+        }
+        return super.onHoverEvent(event);
+    }
+
+    /**
+     * Sets the current grabbed state, and dispatches a grabbed state change
+     * event to our listener.
+     */
+    private void setGrabbedState(int newState) {
+        if (newState != mGrabbedState) {
+            if (newState != OnTriggerListener.NO_HANDLE) {
+                vibrate();
+            }
+            mGrabbedState = newState;
+            if (mOnTriggerListener != null) {
+                if (newState == OnTriggerListener.NO_HANDLE) {
+                    mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE);
+                } else {
+                    mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE);
+                }
+                mOnTriggerListener.onGrabbedStateChange(this, newState);
+            }
+        }
+    }
+
+    private boolean trySwitchToFirstTouchState(float x, float y) {
+        final float tx = x - mWaveCenterX;
+        final float ty = y - mWaveCenterY;
+        if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledGlowRadiusSquared()) {
+            if (DEBUG) Log.v(TAG, "** Handle HIT");
+            switchToState(STATE_FIRST_TOUCH, x, y);
+            updateGlowPosition(tx, ty);
+            mDragging = true;
+            return true;
+        }
+        return false;
+    }
+
+    private void assignDefaultsIfNeeded() {
+        if (mOuterRadius == 0.0f) {
+            mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f;
+        }
+        if (mHitRadius == 0.0f) {
+            // Use the radius of inscribed circle of the first target.
+            mHitRadius = mTargetDrawables.get(0).getWidth() / 2.0f;
+        }
+        if (mSnapMargin == 0.0f) {
+            mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics());
+        }
+        if (mInnerRadius == 0.0f) {
+            mInnerRadius = mHandleDrawable.getWidth() / 10.0f;
+        }
+    }
+
+    private void computeInsets(int dx, int dy) {
+        final int layoutDirection = getResolvedLayoutDirection();
+        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
+
+        switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+            case Gravity.LEFT:
+                mHorizontalInset = 0;
+                break;
+            case Gravity.RIGHT:
+                mHorizontalInset = dx;
+                break;
+            case Gravity.CENTER_HORIZONTAL:
+            default:
+                mHorizontalInset = dx / 2;
+                break;
+        }
+        switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
+            case Gravity.TOP:
+                mVerticalInset = 0;
+                break;
+            case Gravity.BOTTOM:
+                mVerticalInset = dy;
+                break;
+            case Gravity.CENTER_VERTICAL:
+            default:
+                mVerticalInset = dy / 2;
+                break;
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        final int width = right - left;
+        final int height = bottom - top;
+
+        // Target placement width/height. This puts the targets on the greater of the ring
+        // width or the specified outer radius.
+        final float placementWidth = Math.max(mOuterRing.getWidth(), 2 * mOuterRadius);
+        final float placementHeight = Math.max(mOuterRing.getHeight(), 2 * mOuterRadius);
+        float newWaveCenterX = mHorizontalInset
+                + Math.max(width, mMaxTargetWidth + placementWidth) / 2;
+        float newWaveCenterY = mVerticalInset
+                + Math.max(height, + mMaxTargetHeight + placementHeight) / 2;
+
+        if (mInitialLayout) {
+            stopAndHideWaveAnimation();
+            hideTargets(false, false);
+            mInitialLayout = false;
+        }
+
+        mOuterRing.setPositionX(newWaveCenterX);
+        mOuterRing.setPositionY(newWaveCenterY);
+
+        mHandleDrawable.setPositionX(newWaveCenterX);
+        mHandleDrawable.setPositionY(newWaveCenterY);
+
+        updateTargetPositions(newWaveCenterX, newWaveCenterY);
+        updatePointCloudPosition(newWaveCenterX, newWaveCenterY);
+        updateGlowPosition(newWaveCenterX, newWaveCenterY);
+
+        mWaveCenterX = newWaveCenterX;
+        mWaveCenterY = newWaveCenterY;
+
+        if (DEBUG) dump();
+    }
+
+    private void updateTargetPositions(float centerX, float centerY) {
+        // Reposition the target drawables if the view changed.
+        ArrayList<TargetDrawable> targets = mTargetDrawables;
+        final int size = targets.size();
+        final float alpha = (float) (-2.0f * Math.PI / size);
+        for (int i = 0; i < size; i++) {
+            final TargetDrawable targetIcon = targets.get(i);
+            final float angle = alpha * i;
+            targetIcon.setPositionX(centerX);
+            targetIcon.setPositionY(centerY);
+            targetIcon.setX(mOuterRadius * (float) Math.cos(angle));
+            targetIcon.setY(mOuterRadius * (float) Math.sin(angle));
+        }
+    }
+
+    private void updatePointCloudPosition(float centerX, float centerY) {
+        mPointCloud.setCenter(centerX, centerY);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        mPointCloud.draw(canvas);
+        mOuterRing.draw(canvas);
+        final int ntargets = mTargetDrawables.size();
+        for (int i = 0; i < ntargets; i++) {
+            TargetDrawable target = mTargetDrawables.get(i);
+            if (target != null) {
+                target.draw(canvas);
+            }
+        }
+        mHandleDrawable.draw(canvas);
+    }
+
+    public void setOnTriggerListener(OnTriggerListener listener) {
+        mOnTriggerListener = listener;
+    }
+
+    private float square(float d) {
+        return d * d;
+    }
+
+    private float dist2(float dx, float dy) {
+        return dx*dx + dy*dy;
+    }
+
+    private float getScaledGlowRadiusSquared() {
+        final float scaledTapRadius;
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mGlowRadius;
+        } else {
+            scaledTapRadius = mGlowRadius;
+        }
+        return square(scaledTapRadius);
+    }
+
+    private void announceTargets() {
+        StringBuilder utterance = new StringBuilder();
+        final int targetCount = mTargetDrawables.size();
+        for (int i = 0; i < targetCount; i++) {
+            String targetDescription = getTargetDescription(i);
+            String directionDescription = getDirectionDescription(i);
+            if (!TextUtils.isEmpty(targetDescription)
+                    && !TextUtils.isEmpty(directionDescription)) {
+                String text = String.format(directionDescription, targetDescription);
+                utterance.append(text);
+            }
+            if (utterance.length() > 0) {
+                announceText(utterance.toString());
+            }
+        }
+    }
+
+    private void announceText(String text) {
+        setContentDescription(text);
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+        setContentDescription(null);
+    }
+
+    private String getTargetDescription(int index) {
+        if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) {
+            mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId);
+            if (mTargetDrawables.size() != mTargetDescriptions.size()) {
+                Log.w(TAG, "The number of target drawables must be"
+                        + " equal to the number of target descriptions.");
+                return null;
+            }
+        }
+        return mTargetDescriptions.get(index);
+    }
+
+    private String getDirectionDescription(int index) {
+        if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) {
+            mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId);
+            if (mTargetDrawables.size() != mDirectionDescriptions.size()) {
+                Log.w(TAG, "The number of target drawables must be"
+                        + " equal to the number of direction descriptions.");
+                return null;
+            }
+        }
+        return mDirectionDescriptions.get(index);
+    }
+
+    private ArrayList<String> loadDescriptions(int resourceId) {
+        TypedArray array = getContext().getResources().obtainTypedArray(resourceId);
+        final int count = array.length();
+        ArrayList<String> targetContentDescriptions = new ArrayList<String>(count);
+        for (int i = 0; i < count; i++) {
+            String contentDescription = array.getString(i);
+            targetContentDescriptions.add(contentDescription);
+        }
+        array.recycle();
+        return targetContentDescriptions;
+    }
+
+    public int getResourceIdForTarget(int index) {
+        final TargetDrawable drawable = mTargetDrawables.get(index);
+        return drawable == null ? 0 : drawable.getResourceId();
+    }
+
+    public void setEnableTarget(int resourceId, boolean enabled) {
+        for (int i = 0; i < mTargetDrawables.size(); i++) {
+            final TargetDrawable target = mTargetDrawables.get(i);
+            if (target.getResourceId() == resourceId) {
+                target.setEnabled(enabled);
+                break; // should never be more than one match
+            }
+        }
+    }
+
+    /**
+     * Gets the position of a target in the array that matches the given resource.
+     * @param resourceId
+     * @return the index or -1 if not found
+     */
+    public int getTargetPosition(int resourceId) {
+        for (int i = 0; i < mTargetDrawables.size(); i++) {
+            final TargetDrawable target = mTargetDrawables.get(i);
+            if (target.getResourceId() == resourceId) {
+                return i; // should never be more than one match
+            }
+        }
+        return -1;
+    }
+
+    private boolean replaceTargetDrawables(Resources res, int existingResourceId,
+            int newResourceId) {
+        if (existingResourceId == 0 || newResourceId == 0) {
+            return false;
+        }
+
+        boolean result = false;
+        final ArrayList<TargetDrawable> drawables = mTargetDrawables;
+        final int size = drawables.size();
+        for (int i = 0; i < size; i++) {
+            final TargetDrawable target = drawables.get(i);
+            if (target != null && target.getResourceId() == existingResourceId) {
+                target.setDrawable(res, newResourceId);
+                result = true;
+            }
+        }
+
+        if (result) {
+            requestLayout(); // in case any given drawable's size changes
+        }
+
+        return result;
+    }
+
+    /**
+     * Searches the given package for a resource to use to replace the Drawable on the
+     * target with the given resource id
+     * @param component of the .apk that contains the resource
+     * @param name of the metadata in the .apk
+     * @param existingResId the resource id of the target to search for
+     * @return true if found in the given package and replaced at least one target Drawables
+     */
+    public boolean replaceTargetDrawablesIfPresent(ComponentName component, String name,
+                int existingResId) {
+        if (existingResId == 0) return false;
+
+        try {
+            PackageManager packageManager = mContext.getPackageManager();
+            // Look for the search icon specified in the activity meta-data
+            Bundle metaData = packageManager.getActivityInfo(
+                    component, PackageManager.GET_META_DATA).metaData;
+            if (metaData != null) {
+                int iconResId = metaData.getInt(name);
+                if (iconResId != 0) {
+                    Resources res = packageManager.getResourcesForActivity(component);
+                    return replaceTargetDrawables(res, existingResId, iconResId);
+                }
+            }
+        } catch (NameNotFoundException e) {
+            Log.w(TAG, "Failed to swap drawable; "
+                    + component.flattenToShortString() + " not found", e);
+        } catch (Resources.NotFoundException nfe) {
+            Log.w(TAG, "Failed to swap drawable from "
+                    + component.flattenToShortString(), nfe);
+        }
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
index 89dbd1b..afeac00 100644
--- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
@@ -117,8 +117,6 @@
     private float mWaveCenterY;
     private int mMaxTargetHeight;
     private int mMaxTargetWidth;
-    private float mHorizontalOffset;
-    private float mVerticalOffset;
 
     private float mOuterRadius = 0.0f;
     private float mHitRadius = 0.0f;
@@ -215,9 +213,6 @@
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiWaveView);
         mOuterRadius = a.getDimension(R.styleable.MultiWaveView_outerRadius, mOuterRadius);
-//        mHorizontalOffset = a.getDimension(R.styleable.MultiWaveView_horizontalOffset,
-//                mHorizontalOffset);
-//        mVerticalOffset = a.getDimension(R.styleable.MultiWaveView_verticalOffset, mVerticalOffset);
         mHitRadius = a.getDimension(R.styleable.MultiWaveView_hitRadius, mHitRadius);
         mSnapMargin = a.getDimension(R.styleable.MultiWaveView_snapMargin, mSnapMargin);
         mVibrationDuration = a.getInt(R.styleable.MultiWaveView_vibrationDuration,
@@ -230,7 +225,6 @@
         mOuterRing = new TargetDrawable(res,
                 a.peekValue(R.styleable.MultiWaveView_waveDrawable).resourceId);
         mAlwaysTrackFinger = a.getBoolean(R.styleable.MultiWaveView_alwaysTrackFinger, false);
-        mGravity = a.getInt(R.styleable.MultiWaveView_gravity, Gravity.TOP);
 
         // Read array of chevron drawables
         TypedValue outValue = new TypedValue();
@@ -244,24 +238,6 @@
             }
         }
 
-        // Support old-style chevron specification if new specification not found
-        if (mChevronDrawables.size() == 0) {
-            final int chevronResIds[] = {
-                    R.styleable.MultiWaveView_rightChevronDrawable,
-                    R.styleable.MultiWaveView_topChevronDrawable,
-                    R.styleable.MultiWaveView_leftChevronDrawable,
-                    R.styleable.MultiWaveView_bottomChevronDrawable
-            };
-
-            for (int i = 0; i < chevronResIds.length; i++) {
-                TypedValue typedValue = a.peekValue(chevronResIds[i]);
-                for (int k = 0; k < mFeedbackCount; k++) {
-                    mChevronDrawables.add(
-                        typedValue != null ? new TargetDrawable(res, typedValue.resourceId) : null);
-                }
-            }
-        }
-
         // Read array of target drawables
         if (a.getValue(R.styleable.MultiWaveView_targetDrawables, outValue)) {
             internalSetTargetResources(outValue.resourceId);
@@ -289,6 +265,12 @@
         }
 
         a.recycle();
+
+        // Use gravity attribute from LinearLayout
+        a = context.obtainStyledAttributes(attrs, android.R.styleable.LinearLayout);
+        mGravity = a.getInt(android.R.styleable.LinearLayout_gravity, Gravity.TOP);
+        a.recycle();
+
         setVibrateEnabled(mVibrationDuration > 0);
         assignDefaultsIfNeeded();
     }
@@ -302,8 +284,6 @@
         Log.v(TAG, "TapRadius = " + mTapRadius);
         Log.v(TAG, "WaveCenterX = " + mWaveCenterX);
         Log.v(TAG, "WaveCenterY = " + mWaveCenterY);
-        Log.v(TAG, "HorizontalOffset = " + mHorizontalOffset);
-        Log.v(TAG, "VerticalOffset = " + mVerticalOffset);
     }
 
     public void suspendAnimations() {
@@ -1042,9 +1022,9 @@
         // width or the specified outer radius.
         final float placementWidth = Math.max(mOuterRing.getWidth(), 2 * mOuterRadius);
         final float placementHeight = Math.max(mOuterRing.getHeight(), 2 * mOuterRadius);
-        float newWaveCenterX = mHorizontalOffset + mHorizontalInset
+        float newWaveCenterX = mHorizontalInset
                 + Math.max(width, mMaxTargetWidth + placementWidth) / 2;
-        float newWaveCenterY = mVerticalOffset + mVerticalInset
+        float newWaveCenterY = mVerticalInset
                 + Math.max(height, + mMaxTargetHeight + placementHeight) / 2;
 
         if (mInitialLayout) {
diff --git a/core/java/com/android/internal/widget/multiwaveview/PointCloud.java b/core/java/com/android/internal/widget/multiwaveview/PointCloud.java
new file mode 100644
index 0000000..2ef8c78
--- /dev/null
+++ b/core/java/com/android/internal/widget/multiwaveview/PointCloud.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2012 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.internal.widget.multiwaveview;
+
+import java.util.ArrayList;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.util.FloatMath;
+import android.util.Log;
+
+public class PointCloud {
+    private static final float MIN_POINT_SIZE = 2.0f;
+    private static final float MAX_POINT_SIZE = 4.0f;
+    private static final int INNER_POINTS = 8;
+    private static final String TAG = "PointCloud";
+    private ArrayList<Point> mPointCloud = new ArrayList<Point>();
+    private Drawable mDrawable;
+    private float mCenterX;
+    private float mCenterY;
+    private Paint mPaint;
+    private float mScale = 1.0f;
+    private static final float PI = (float) Math.PI;
+
+    // These allow us to have multiple concurrent animations.
+    WaveManager waveManager = new WaveManager();
+    GlowManager glowManager = new GlowManager();
+    private float mOuterRadius;
+
+    public class WaveManager {
+        private float radius = 50;
+        private float width = 200.0f; // TODO: Make configurable
+        private float alpha = 0.0f;
+        public void setRadius(float r) {
+            radius = r;
+        }
+
+        public float getRadius() {
+            return radius;
+        }
+
+        public void setAlpha(float a) {
+            alpha = a;
+        }
+
+        public float getAlpha() {
+            return alpha;
+        }
+    };
+
+    public class GlowManager {
+        private float x;
+        private float y;
+        private float radius = 0.0f;
+        private float alpha = 0.0f;
+
+        public void setX(float x1) {
+            x = x1;
+        }
+
+        public float getX() {
+            return x;
+        }
+
+        public void setY(float y1) {
+            y = y1;
+        }
+
+        public float getY() {
+            return y;
+        }
+
+        public void setAlpha(float a) {
+            alpha = a;
+        }
+
+        public float getAlpha() {
+            return alpha;
+        }
+
+        public void setRadius(float r) {
+            radius = r;
+        }
+
+        public float getRadius() {
+            return radius;
+        }
+    }
+
+    class Point {
+        float x;
+        float y;
+        float radius;
+
+        public Point(float x2, float y2, float r) {
+            x = (float) x2;
+            y = (float) y2;
+            radius = r;
+        }
+    }
+
+    public PointCloud(Drawable drawable) {
+        mPaint = new Paint();
+        mPaint.setFilterBitmap(true);
+        mPaint.setColor(Color.rgb(255, 255, 255)); // TODO: make configurable
+        mPaint.setAntiAlias(true);
+        mPaint.setDither(true);
+
+        mDrawable = drawable;
+        if (mDrawable != null) {
+            drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+        }
+    }
+
+    public void setCenter(float x, float y) {
+        mCenterX = x;
+        mCenterY = y;
+    }
+
+    public void makePointCloud(float innerRadius, float outerRadius) {
+        if (innerRadius == 0) {
+            Log.w(TAG, "Must specify an inner radius");
+            return;
+        }
+        mOuterRadius = outerRadius;
+        mPointCloud.clear();
+        final float pointAreaRadius =  (outerRadius - innerRadius);
+        final float ds = (2.0f * PI * innerRadius / INNER_POINTS);
+        final int bands = (int) Math.round(pointAreaRadius / ds);
+        final float dr = pointAreaRadius / bands;
+        float r = innerRadius;
+        for (int b = 0; b <= bands; b++, r += dr) {
+            float circumference = 2.0f * PI * r;
+            final int pointsInBand = (int) (circumference / ds);
+            float eta = PI/2.0f;
+            float dEta = 2.0f * PI / pointsInBand;
+            for (int i = 0; i < pointsInBand; i++) {
+                float x = r * FloatMath.cos(eta);
+                float y = r * FloatMath.sin(eta);
+                eta += dEta;
+                mPointCloud.add(new Point(x, y, r));
+            }
+        }
+    }
+
+    public void setScale(float scale) {
+        mScale  = scale;
+    }
+
+    public float getScale() {
+        return mScale;
+    }
+
+    private static float hypot(float x, float y) {
+        return FloatMath.sqrt(x*x + y*y);
+    }
+
+    private static float max(float a, float b) {
+        return a > b ? a : b;
+    }
+
+    public int getAlphaForPoint(Point point) {
+        // Contribution from positional glow
+        float glowDistance = hypot(glowManager.x - point.x, glowManager.y - point.y);
+        float glowAlpha = 0.0f;
+        if (glowDistance < glowManager.radius) {
+            float cosf = FloatMath.cos(PI * 0.5f * glowDistance / glowManager.radius);
+            glowAlpha = glowManager.alpha * max(0.0f, (float) Math.pow(cosf, 0.5f));
+        }
+
+        // Compute contribution from Wave
+        float radius = hypot(point.x, point.y);
+        float distanceToWaveRing = Math.abs(radius - waveManager.radius);
+        float waveAlpha = 0.0f;
+        if (distanceToWaveRing < waveManager.width * 0.5f) {
+            float cosf = FloatMath.cos(PI * 0.5f * distanceToWaveRing / waveManager.width);
+            waveAlpha = waveManager.alpha * max(0.0f, (float) Math.pow(cosf, 15.0f));
+        }
+
+        return (int) (max(glowAlpha, waveAlpha) * 255);
+    }
+
+    private float interp(float min, float max, float f) {
+        return min + (max - min) * f;
+    }
+
+    public void draw(Canvas canvas) {
+        ArrayList<Point> points = mPointCloud;
+        final float cx = mDrawable != null ? (-mDrawable.getIntrinsicWidth() / 2) : 0;
+        final float cy = mDrawable != null ? (-mDrawable.getIntrinsicHeight() / 2) : 0;
+        canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        canvas.scale(mScale, mScale, mCenterX, mCenterY);
+        for (int i = 0; i < points.size(); i++) {
+            Point point = points.get(i);
+            final float pointSize = interp(MAX_POINT_SIZE, MIN_POINT_SIZE,
+                    point.radius / mOuterRadius);
+            final float px = point.x + cx + mCenterX;
+            final float py = point.y + cy + mCenterY;
+            int alpha = getAlphaForPoint(point);
+
+            if (alpha == 0) continue;
+
+            if (mDrawable != null) {
+                canvas.save(Canvas.MATRIX_SAVE_FLAG);
+                float s = pointSize / MAX_POINT_SIZE;
+                canvas.scale(s, s, px, py);
+                canvas.translate(px, py);
+                mDrawable.setAlpha(alpha);
+                mDrawable.draw(canvas);
+                canvas.restore();
+            } else {
+                mPaint.setAlpha(alpha);
+                canvas.drawCircle(px, py, pointSize, mPaint);
+            }
+        }
+        canvas.restore();
+    }
+
+}
diff --git a/core/jni/android_database_SQLiteCommon.cpp b/core/jni/android_database_SQLiteCommon.cpp
index 46009bd..eefcb74 100644
--- a/core/jni/android_database_SQLiteCommon.cpp
+++ b/core/jni/android_database_SQLiteCommon.cpp
@@ -16,6 +16,8 @@
 
 #include "android_database_SQLiteCommon.h"
 
+#include <utils/String8.h>
+
 namespace android {
 
 /* throw a SQLiteException with a message appropriate for the error in handle */
@@ -37,7 +39,8 @@
         // the error message may contain more information than the error code
         // because it is based on the extended error code rather than the simplified
         // error code that SQLite normally returns.
-        throw_sqlite3_exception(env, sqlite3_errcode(handle), sqlite3_errmsg(handle), message);
+        throw_sqlite3_exception(env, sqlite3_extended_errcode(handle),
+                sqlite3_errmsg(handle), message);
     } else {
         // we use SQLITE_OK so that a generic SQLiteException is thrown;
         // any code not specified in the switch statement below would do.
@@ -49,9 +52,7 @@
  * should only be used when the database connection is not available because the
  * error information will not be quite as rich */
 void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) {
-    char temp[21];
-    sprintf(temp, "error code %d", errcode);
-    throw_sqlite3_exception(env, errcode, temp, message);
+    throw_sqlite3_exception(env, errcode, "unknown error", message);
 }
 
 /* throw a SQLiteException for a given error code, sqlite3message, and
@@ -60,7 +61,7 @@
 void throw_sqlite3_exception(JNIEnv* env, int errcode,
                              const char* sqlite3Message, const char* message) {
     const char* exceptionClass;
-    switch (errcode) {
+    switch (errcode & 0xff) { /* mask off extended error code */
         case SQLITE_IOERR:
             exceptionClass = "android/database/sqlite/SQLiteDiskIOException";
             break;
@@ -119,19 +120,15 @@
             break;
     }
 
-    if (sqlite3Message != NULL && message != NULL) {
-        char* fullMessage = (char *)malloc(strlen(sqlite3Message) + strlen(message) + 3);
-        if (fullMessage != NULL) {
-            strcpy(fullMessage, sqlite3Message);
-            strcat(fullMessage, ": ");
-            strcat(fullMessage, message);
-            jniThrowException(env, exceptionClass, fullMessage);
-            free(fullMessage);
-        } else {
-            jniThrowException(env, exceptionClass, sqlite3Message);
+    if (sqlite3Message) {
+        String8 fullMessage;
+        fullMessage.append(sqlite3Message);
+        fullMessage.appendFormat(" (code %d)", errcode); // print extended error code
+        if (message) {
+            fullMessage.append(": ");
+            fullMessage.append(message);
         }
-    } else if (sqlite3Message != NULL) {
-        jniThrowException(env, exceptionClass, sqlite3Message);
+        jniThrowException(env, exceptionClass, fullMessage.string());
     } else {
         jniThrowException(env, exceptionClass, message);
     }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9698018..6546fed 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -636,12 +636,6 @@
         android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
         android:protectionLevel="signature" />
 
-    <!-- Allows an application to route media output to other devices. -->
-    <permission android:name="android.permission.ROUTE_MEDIA_OUTPUT"
-                android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
-                android:label="@string/permlab_route_media_output"
-                android:description="@string/permdesc_route_media_output" />
-
     <!-- =========================================== -->
     <!-- Permissions associated with telephony state -->
     <!-- =========================================== -->
diff --git a/core/res/res/drawable-hdpi/ic_lockscreen_glowdot.png b/core/res/res/drawable-hdpi/ic_lockscreen_glowdot.png
new file mode 100644
index 0000000..983c45e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_lockscreen_glowdot.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lockscreen_glowdot.png b/core/res/res/drawable-mdpi/ic_lockscreen_glowdot.png
new file mode 100644
index 0000000..056c3f17
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_lockscreen_glowdot.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_lockscreen_glowdot.png b/core/res/res/drawable-xhdpi/ic_lockscreen_glowdot.png
new file mode 100644
index 0000000..cbd039a
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_lockscreen_glowdot.png
Binary files differ
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml
index a666077..cd9c913 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock.xml
@@ -82,7 +82,7 @@
             android:drawablePadding="4dip"
             />
 
-        <com.android.internal.widget.multiwaveview.MultiWaveView
+        <com.android.internal.widget.multiwaveview.GlowPadView
             android:id="@+id/unlock_widget"
             android:orientation="horizontal"
             android:layout_width="wrap_content"
@@ -94,13 +94,15 @@
             android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera"
             android:directionDescriptions="@array/lockscreen_direction_descriptions"
             android:handleDrawable="@drawable/ic_lockscreen_handle"
-            android:waveDrawable="@drawable/ic_lockscreen_outerring"
-            android:outerRadius="@dimen/multiwaveview_target_placement_radius"
-            android:snapMargin="@dimen/multiwaveview_snap_margin"
-            android:hitRadius="@dimen/multiwaveview_hit_radius"
-            android:chevronDrawables="@array/lockscreen_chevron_drawables"
-            android:feedbackCount="3"
+            android:outerRingDrawable="@drawable/ic_lockscreen_outerring"
+            android:outerRadius="@dimen/glowpadview_target_placement_radius"
+            android:innerRadius="@dimen/glowpadview_inner_radius"
+            android:snapMargin="@dimen/glowpadview_snap_margin"
+            android:hitRadius="@dimen/glowpadview_hit_radius"
+            android:feedbackCount="1"
             android:vibrationDuration="20"
+            android:glowRadius="@dimen/glowpadview_glow_radius"
+            android:pointDrawable="@drawable/ic_lockscreen_glowdot"
             />
 
         <!-- emergency call button shown when sim is PUKd and tab_selector is hidden -->
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml
index 17a3c84..32ca602 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_tab_unlock_land.xml
@@ -82,7 +82,7 @@
             android:layout_alignParentTop="true"
             android:drawablePadding="4dip"/>
 
-        <com.android.internal.widget.multiwaveview.MultiWaveView
+        <com.android.internal.widget.multiwaveview.GlowPadView
             android:id="@+id/unlock_widget"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
@@ -94,13 +94,15 @@
             android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera"
             android:directionDescriptions="@array/lockscreen_direction_descriptions"
             android:handleDrawable="@drawable/ic_lockscreen_handle"
-            android:waveDrawable="@drawable/ic_lockscreen_outerring"
-            android:outerRadius="@dimen/multiwaveview_target_placement_radius"
-            android:snapMargin="@dimen/multiwaveview_snap_margin"
-            android:hitRadius="@dimen/multiwaveview_hit_radius"
-            android:chevronDrawables="@array/lockscreen_chevron_drawables"
-            android:feedbackCount="3"
+            android:outerRingDrawable="@drawable/ic_lockscreen_outerring"
+            android:outerRadius="@dimen/glowpadview_target_placement_radius"
+            android:innerRadius="@dimen/glowpadview_inner_radius"
+            android:snapMargin="@dimen/glowpadview_snap_margin"
+            android:hitRadius="@dimen/glowpadview_hit_radius"
+            android:feedbackCount="1"
             android:vibrationDuration="20"
+            android:glowRadius="@dimen/glowpadview_glow_radius"
+            android:pointDrawable="@drawable/ic_lockscreen_glowdot"
         />
 
         <!-- emergency call button shown when sim is PUKd and tab_selector is hidden -->
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_unlock_portrait.xml b/core/res/res/layout-sw600dp/keyguard_screen_unlock_portrait.xml
index 78a01dd..7231ead 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_unlock_portrait.xml
@@ -41,7 +41,6 @@
                 android:layout_height="wrap_content"
                 android:layout_marginLeft="50dip"
                 android:layout_marginTop="50dip"
-                android:layout_marginBottom="100dip"
                 android:layout_marginRight="64dip"
                 android:layout_alignParentTop="true"
                 android:layout_alignParentLeft="true"/>
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
index 2dcb774..4e646a6 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -123,7 +123,7 @@
         android:layout_width="match_parent"
         android:layout_height="302dip">
 
-        <com.android.internal.widget.multiwaveview.MultiWaveView
+        <com.android.internal.widget.multiwaveview.GlowPadView
             android:id="@+id/unlock_widget"
             android:orientation="horizontal"
             android:layout_width="match_parent"
@@ -135,13 +135,15 @@
             android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera"
             android:directionDescriptions="@array/lockscreen_direction_descriptions"
             android:handleDrawable="@drawable/ic_lockscreen_handle"
-            android:waveDrawable="@drawable/ic_lockscreen_outerring"
-            android:outerRadius="@dimen/multiwaveview_target_placement_radius"
-            android:snapMargin="@dimen/multiwaveview_snap_margin"
-            android:hitRadius="@dimen/multiwaveview_hit_radius"
-            android:chevronDrawables="@array/lockscreen_chevron_drawables"
-            android:feedbackCount="3"
+            android:outerRingDrawable="@drawable/ic_lockscreen_outerring"
+            android:outerRadius="@dimen/glowpadview_target_placement_radius"
+            android:innerRadius="@dimen/glowpadview_inner_radius"
+            android:snapMargin="@dimen/glowpadview_snap_margin"
+            android:hitRadius="@dimen/glowpadview_hit_radius"
+            android:feedbackCount="1"
             android:vibrationDuration="20"
+            android:glowRadius="@dimen/glowpadview_glow_radius"
+            android:pointDrawable="@drawable/ic_lockscreen_glowdot"
             />
 
         <TextView
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
index 10ddd1e..5a357fb 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -129,24 +129,26 @@
     <Space android:layout_width="64dip" android:layout_rowSpan="7" />
 
     <!-- Column 2 -->
-    <com.android.internal.widget.multiwaveview.MultiWaveView
+    <com.android.internal.widget.multiwaveview.GlowPadView
         android:id="@+id/unlock_widget"
         android:layout_width="302dip"
         android:layout_height="match_parent"
         android:layout_rowSpan="7"
-        android:gravity="center"
+        android:gravity="left|center_vertical"
 
         android:targetDrawables="@array/lockscreen_targets_with_camera"
         android:targetDescriptions="@array/lockscreen_target_descriptions_with_camera"
         android:directionDescriptions="@array/lockscreen_direction_descriptions"
         android:handleDrawable="@drawable/ic_lockscreen_handle"
-        android:waveDrawable="@drawable/ic_lockscreen_outerring"
-        android:outerRadius="@dimen/multiwaveview_target_placement_radius"
-        android:snapMargin="@dimen/multiwaveview_snap_margin"
-        android:hitRadius="@dimen/multiwaveview_hit_radius"
-        android:chevronDrawables="@array/lockscreen_chevron_drawables"
-        android:feedbackCount="3"
+        android:outerRingDrawable="@drawable/ic_lockscreen_outerring"
+        android:outerRadius="@dimen/glowpadview_target_placement_radius"
+        android:innerRadius="@dimen/glowpadview_inner_radius"
+        android:snapMargin="@dimen/glowpadview_snap_margin"
+        android:hitRadius="@dimen/glowpadview_hit_radius"
+        android:feedbackCount="1"
         android:vibrationDuration="20"
+        android:glowRadius="@dimen/glowpadview_glow_radius"
+        android:pointDrawable="@drawable/ic_lockscreen_glowdot"
         />
 
     <!-- Music transport control -->
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 6b56b5c..28a892d 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -209,7 +209,7 @@
     <string name="permdesc_sendSmsNoConfirmation" msgid="402569800862935907">"Permite que la aplicación envíe mensajes SMS, lo que puede generar cargos inesperados. Las aplicaciones malintencionadas pueden causarte gastos imprevistos al enviar mensajes sin tu confirmación."</string>
     <string name="permlab_readSms" msgid="8745086572213270480">"leer tus mensajes de texto (SMS o MMS)"</string>
     <string name="permdesc_readSms" product="tablet" msgid="2467981548684735522">"Permite que la aplicación consulte los mensajes SMS almacenados en la tableta o en la tarjeta SIM. La aplicación puede utilizar este permiso para leer todos los mensajes SMS, independientemente de cuál sea su contenido o su nivel de confidencialidad."</string>
-    <string name="permdesc_readSms" product="default" msgid="3695967533457240550">"Permite que la aplicación consulte los mensajes SMS almacenados en el teléfono o en la tarjeta SIM. La aplicación puede utilizar este permiso para leer todos los mensajes SMS, independientemente de cuál sea su contenido o su nivel de confidencialidad."</string>
+    <string name="permdesc_readSms" product="default" msgid="3695967533457240550">"Permite que la aplicación consulte los mensajes SMS almacenados en el dispositivo o en la tarjeta SIM. La aplicación puede utilizar este permiso para leer todos los mensajes SMS, independientemente de cuál sea su contenido o su nivel de confidencialidad."</string>
     <string name="permlab_writeSms" msgid="3216950472636214774">"editar tus mensajes de texto (SMS o MMS)"</string>
     <string name="permdesc_writeSms" product="tablet" msgid="5160413947794501538">"Permite que la aplicación escriba en mensajes SMS almacenados en tu tableta o tarjeta SIM. Las aplicaciones maliciosas pueden eliminar tus mensajes."</string>
     <string name="permdesc_writeSms" product="default" msgid="7268668709052328567">"Permite que la aplicación escriba en mensajes SMS almacenados en tu dispositivo o tarjeta SIM. Las aplicaciones maliciosas pueden eliminar tus mensajes."</string>
@@ -302,7 +302,7 @@
     <string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Permite que la aplicación solicite que la señal suministrada se envíe a todos los procesos persistentes."</string>
     <string name="permlab_persistentActivity" msgid="8841113627955563938">"hacer que la aplicación se ejecute siempre"</string>
     <string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"Permite que la aplicación haga que algunas de sus partes se mantengan persistentes en la memoria. Esto puede limitar la memoria disponible para otras aplicaciones y ralentizar la tableta."</string>
-    <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"Permite que la aplicación haga que algunas de sus partes se mantengan persistentes en la memoria. Esto puede limitar la memoria disponible para otras aplicaciones y ralentizar el teléfono."</string>
+    <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"Permite que la aplicación haga que algunas de sus partes se mantengan persistentes en la memoria. Esto puede limitar la memoria disponible para otras aplicaciones y ralentizar el dispositivo."</string>
     <string name="permlab_deletePackages" msgid="184385129537705938">"eliminar aplicaciones"</string>
     <string name="permdesc_deletePackages" msgid="7411480275167205081">"Permite que la aplicación elimine paquetes de Android. Las aplicaciones maliciosas pueden utilizar este permiso para eliminar aplicaciones importantes."</string>
     <string name="permlab_clearAppUserData" msgid="274109191845842756">"eliminar datos de otras aplicaciones"</string>
@@ -343,16 +343,16 @@
     <string name="permdesc_receiveBootCompleted" product="default" msgid="513950589102617504">"Permite que la aplicación se inicie en cuanto el sistema haya finalizado la inicialización. Esto puede ocasionar que el dispositivo tarde más en inicializarse y que la aplicación ralentice el funcionamiento general del dispositivo al estar en ejecución constante."</string>
     <string name="permlab_broadcastSticky" msgid="7919126372606881614">"enviar emisiones pegajosas"</string>
     <string name="permdesc_broadcastSticky" product="tablet" msgid="7749760494399915651">"Permite que la aplicación envíe transmisiones persistentes que permanecen después de que finaliza la transmisión. Un uso excesivo podría ralentizar la tableta o hacer que funcione de manera inestable al forzarla a utilizar mucha memoria."</string>
-    <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"Permite que la aplicación envíe transmisiones persistentes que permanecen después de que finaliza la transmisión. Un uso excesivo podría ralentizar el teléfono o hacer que funcione de manera inestable al forzarlo a utilizar mucha memoria."</string>
+    <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"Permite que la aplicación envíe transmisiones persistentes que permanecen después de que finaliza la transmisión. Un uso excesivo podría ralentizar el dispositivo o hacer que funcione de manera inestable al forzarlo a utilizar mucha memoria."</string>
     <string name="permlab_readContacts" msgid="8348481131899886131">"leer tus contactos"</string>
-    <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"Permite que la aplicación consulte información sobre los contactos almacenados en la tableta, incluida la frecuencia con la que los has llamado, les has enviado un correo electrónico o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para guardar los datos de los contactos, y las aplicaciones malintencionadas podrían compartirlos sin tu consentimiento."</string>
-    <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"Permite que la aplicación consulte información sobre contactos almacenados en el teléfono, incluida la frecuencia con la que los has llamado, les has enviado un correo electrónico o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para guardar los datos de los contactos, y las aplicaciones malintencionadas podrían compartirlos sin tu consentimiento."</string>
+    <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"Permite que la aplicación consulte información sobre los contactos almacenados en la tableta, incluida la frecuencia con la que los has llamado, les has enviado correos o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para guardar los datos de los contactos, y las aplicaciones malintencionadas podrían compartirlos sin tu consentimiento."</string>
+    <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"Permite que la aplicación consulte información sobre contactos almacenados en el dispositivo, incluida la frecuencia con la que los has llamado, les has enviado correos o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para guardar los datos de los contactos, y las aplicaciones malintencionadas podrían compartirlos sin tu consentimiento."</string>
     <string name="permlab_writeContacts" msgid="5107492086416793544">"modificar tus contactos"</string>
-    <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"Permite que la aplicación modifique los datos de los contactos almacenados en la tableta, incluida la frecuencia con la que los has llamado, les has enviado un correo electrónico o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para eliminar datos de contactos."</string>
-    <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"Permite que la aplicación modifique los datos de los contactos almacenados en el teléfono, incluida la frecuencia con la que los has llamado, les has enviado un correo electrónico o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para eliminar datos de contactos."</string>
+    <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"Permite que la aplicación modifique los datos de los contactos almacenados en la tableta, incluida la frecuencia con la que los has llamado, les has enviado correos o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para eliminar datos de contactos."</string>
+    <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"Permite que la aplicación modifique los datos de los contactos almacenados en el dispositivo, incluida la frecuencia con la que los has llamado, les has enviado correos o te has puesto en contacto con ellos de otro modo. Las aplicaciones pueden utilizar este permiso para eliminar datos de contactos."</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"leer el registro de llamadas"</string>
     <string name="permdesc_readCallLog" product="tablet" msgid="3700645184870760285">"Permite que la aplicación consulte el registro de llamadas de la tableta, incluidos los datos sobre llamadas entrantes y salientes. Las aplicaciones pueden utilizar este permiso para guardar los datos del registro de llamadas, y las aplicaciones malintencionadas podrían compartirlos sin tu consentimiento."</string>
-    <string name="permdesc_readCallLog" product="default" msgid="5777725796813217244">"Permite que la aplicación consulte el registro de llamadas del teléfono, incluidos los datos sobre llamadas entrantes y salientes. Las aplicaciones pueden utilizar este permiso para guardar los datos del registro de llamadas, y las aplicaciones malintencionadas podrían compartirlos sin tu consentimiento."</string>
+    <string name="permdesc_readCallLog" product="default" msgid="5777725796813217244">"Permite que la aplicación consulte el registro de llamadas del dispositivo, incluidos los datos sobre llamadas entrantes y salientes. Las aplicaciones pueden utilizar este permiso para guardar los datos del registro de llamadas, y las aplicaciones malintencionadas podrían compartirlos sin tu consentimiento."</string>
     <string name="permlab_writeCallLog" msgid="8552045664743499354">"escribir en el registro de llamadas"</string>
     <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"Permite que la aplicación modifique el registro de llamadas de la tableta, incluidos los datos sobre llamadas entrantes y salientes. Las aplicaciones malintencionadas pueden usar este permiso para borrar o modificar el registro de llamadas."</string>
     <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"Permite que la aplicación modifique el registro de llamadas del dispositivo, incluidos los datos sobre llamadas entrantes y salientes. Las aplicaciones malintencionadas pueden usar este permiso para borrar o modificar el registro de llamadas."</string>
@@ -366,10 +366,10 @@
     <string name="permdesc_writeSocialStream" product="default" msgid="3086557552204114849">"Permite que la aplicación muestre actualizaciones de las redes sociales de tus amigos. Ten cuidado al compartir información, ya que la aplicación puede generar mensajes que parezcan proceder de amigos. Nota: Este permiso no se puede utilizar en todas las redes sociales."</string>
     <string name="permlab_readCalendar" msgid="5972727560257612398">"Leer eventos de calendario e información confidencial"</string>
     <string name="permdesc_readCalendar" product="tablet" msgid="4216462049057658723">"Permite que la aplicación consulte todos los eventos de calendario almacenados en la tableta, incluidos los de amigos y compañeros de trabajo. La aplicación puede utilizar este permiso para compartir o guardar datos del calendario del usuario sin tener en cuenta si son privados o confidenciales."</string>
-    <string name="permdesc_readCalendar" product="default" msgid="7434548682470851583">"Permite que la aplicación consulte todos los eventos de calendario almacenados en el teléfono, incluidos los de amigos y compañeros de trabajo. La aplicación puede utilizar este permiso para compartir o guardar los datos del calendario sin tener en cuenta si son privados o confidenciales."</string>
+    <string name="permdesc_readCalendar" product="default" msgid="7434548682470851583">"Permite que la aplicación consulte todos los eventos de calendario almacenados en el dispositivo, incluidos los de amigos y compañeros de trabajo. La aplicación puede utilizar este permiso para compartir o guardar los datos del calendario sin tener en cuenta si son privados o confidenciales."</string>
     <string name="permlab_writeCalendar" msgid="8438874755193825647">"Agregar o modificar los eventos de calendario y enviar un correo a los invitados sin que los propietarios lo sepan"</string>
     <string name="permdesc_writeCalendar" product="tablet" msgid="6679035520113668528">"Permite que la aplicación agregue, elimine y cambie eventos que se pueden modificar en la tableta, incluidos los de amigos o compañeros de trabajo. La aplicación puede utilizar este permiso para enviar mensajes que parezcan proceder de propietarios de un calendario o para modificar eventos sin el consentimiento de los propietarios."</string>
-    <string name="permdesc_writeCalendar" product="default" msgid="2324469496327249376">"Permite que la aplicación agregue, elimine y cambie eventos que se pueden modificar en el teléfono, incluidos los de amigos o compañeros de trabajo. La aplicación puede utilizar este permiso para enviar mensajes que parezcan proceder de propietarios de un calendario o para modificar los eventos sin el consentimiento de los propietarios."</string>
+    <string name="permdesc_writeCalendar" product="default" msgid="2324469496327249376">"Permite que la aplicación agregue, elimine y cambie eventos que se pueden modificar en el dispositivo, incluidos los de amigos o compañeros de trabajo. La aplicación puede utilizar este permiso para enviar mensajes que parezcan proceder de propietarios de un calendario o para modificar los eventos sin el consentimiento de los propietarios."</string>
     <string name="permlab_accessMockLocation" msgid="8688334974036823330">"crear fuentes de ubicación de prueba"</string>
     <string name="permdesc_accessMockLocation" msgid="5808711039482051824">"Permite crear fuentes de ubicación simuladas para hacer pruebas o instalar un nuevo proveedor de ubicación. Esto autoriza a la aplicación a sobrescribir la ubicación o el estado proporcionados por otras fuentes de ubicación, como los proveedores de ubicación o GPS."</string>
     <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"acceder a comandos adicionales del proveedor del lugar"</string>
@@ -378,7 +378,7 @@
     <string name="permdesc_installLocationProvider" msgid="9066146120470591509">"Permite crear fuentes de ubicación simuladas para hacer pruebas o instalar un nuevo proveedor de ubicación. Este permiso autoriza a la aplicación a sobrescribir la ubicación o el estado proporcionados por otras fuentes de ubicación, como los proveedores de ubicación o GPS."</string>
     <string name="permlab_accessFineLocation" msgid="5885550969882561436">"ubicación exacta (GPS)"</string>
     <string name="permdesc_accessFineLocation" product="tablet" msgid="8960597421469894181">"Permite acceder a fuentes de determinación de la ubicación exacta, como el sistema de posicionamiento global (GPS) de la tableta. Cuando los servicios de ubicación están disponibles y activados, este permiso permite que la aplicación determine tu ubicación exacta."</string>
-    <string name="permdesc_accessFineLocation" product="default" msgid="239268765496141815">"Permite acceder a fuentes de determinación de la ubicación exacta, como el sistema de posicionamiento global (GPS) del teléfono. Cuando los servicios de ubicación están disponibles y activados, este permiso permite que la aplicación determine tu ubicación exacta."</string>
+    <string name="permdesc_accessFineLocation" product="default" msgid="239268765496141815">"Permite acceder a fuentes de determinación de la ubicación exacta, como el sistema de posicionamiento global (GPS) del dispositivo. Cuando los servicios de ubicación están disponibles y activados, este permiso permite que la aplicación determine tu ubicación exacta."</string>
     <string name="permlab_accessCoarseLocation" msgid="7422827215441638984">"ubicación aproximada (basada en red)"</string>
     <string name="permdesc_accessCoarseLocation" msgid="5383798877137640762">"Permite acceder a la ubicación aproximada a través de proveedores de ubicación que utilizan fuentes de conexión como torres de telefonía móvil y redes Wi-Fi. Cuando estos servicios de ubicación están disponibles y activados, este permiso permite que la aplicación determine tu ubicación aproximada."</string>
     <string name="permlab_accessSurfaceFlinger" msgid="2363969641792388947">"acceder a SurfaceFlinger"</string>
@@ -469,7 +469,7 @@
     <string name="permdesc_accountManagerService" msgid="1948455552333615954">"Permite que la aplicación haga llamadas a los autenticadores de cuentas."</string>
     <string name="permlab_getAccounts" msgid="1086795467760122114">"buscar cuentas en el dispositivo"</string>
     <string name="permdesc_getAccounts" product="tablet" msgid="2741496534769660027">"Permite que la aplicación obtenga una lista de las cuentas reconocidas por la tableta, entre las que se pueden incluir las cuentas creadas por las aplicaciones que hayas instalado."</string>
-    <string name="permdesc_getAccounts" product="default" msgid="3448316822451807382">"Permite que la aplicación obtenga una lista de las cuentas reconocidas por el teléfono, entre las que se pueden incluir las cuentas creadas por las aplicaciones que hayas instalado."</string>
+    <string name="permdesc_getAccounts" product="default" msgid="3448316822451807382">"Permite que la aplicación obtenga una lista de las cuentas reconocidas por el dispositivo, entre las que se pueden incluir las cuentas creadas por las aplicaciones que hayas instalado."</string>
     <string name="permlab_authenticateAccounts" msgid="5265908481172736933">"crear cuentas y establecer contraseñas"</string>
     <string name="permdesc_authenticateAccounts" msgid="5472124296908977260">"Permite que la aplicación utilice las capacidades del autenticador de cuentas del administrador de cuentas, incluida la creación de cuentas y la obtención y configuración de sus contraseñas."</string>
     <string name="permlab_manageAccounts" msgid="4983126304757177305">"agregar o eliminar cuentas"</string>
@@ -489,31 +489,31 @@
     <string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"cambiar la configuración del uso de datos del fondo"</string>
     <string name="permdesc_changeBackgroundDataSetting" msgid="5347729578468744379">"Permite que la aplicación cambe la configuración de uso de los datos de referencia."</string>
     <string name="permlab_accessWifiState" msgid="5202012949247040011">"ver conexiones Wi-Fi"</string>
-    <string name="permdesc_accessWifiState" msgid="5002798077387803726">"Permite que la aplicación vea información sobre la conexión a redes Wi-Fi, por ejemplo, si la conexión Wi-Fi está habilitada y cuál es el nombre de los dispositivos Wi-Fi conectados."</string>
+    <string name="permdesc_accessWifiState" msgid="5002798077387803726">"Permite que la aplicación vea información sobre la conexión a redes Wi-Fi, por ejemplo, si la conexión Wi-Fi está activada y cuál es el nombre de los dispositivos Wi-Fi conectados."</string>
     <string name="permlab_changeWifiState" msgid="6550641188749128035">"conectarse y desconectarse de la red Wi-Fi"</string>
     <string name="permdesc_changeWifiState" msgid="7137950297386127533">"Permite que la aplicación se conecte a puntos de acceso Wi-Fi y se desconecte de ellos, y que realice modificaciones en la configuración de las redes Wi-Fi."</string>
     <string name="permlab_changeWifiMulticastState" msgid="1368253871483254784">"permitir recepción de multidifusión Wi-Fi"</string>
-    <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="7969774021256336548">"Permite que la aplicación reciba paquetes enviados a todos los dispositivos de una red Wi-Fi que utilicen direcciones de multidifusión, no solo a la tableta. Utiliza más batería que el modo de no multidifusión."</string>
-    <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"Permite que la aplicación reciba paquetes enviados a todos los dispositivos de una red Wi-Fi que utilicen direcciones de multidifusión, no solo al teléfono. Utiliza más batería que el modo de no multidifusión."</string>
+    <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="7969774021256336548">"Permite que la aplicación reciba paquetes enviados a todos los dispositivos de una red Wi-Fi que utilicen direcciones de multidifusión, no solo a la tableta. Utiliza más batería que el modo que no es de multidifusión."</string>
+    <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"Permite que la aplicación reciba paquetes enviados a todos los dispositivos de una red Wi-Fi que utilicen direcciones de multidifusión, no solo a tu dispositivo. Utiliza más batería que el modo que no es de multidifusión."</string>
     <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"acceder a la configuración de Bluetooth"</string>
     <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"Permite que la aplicación configure la tableta Bluetooth local y descubra y se sincronice con dispositivos remotos."</string>
     <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"Permite que la aplicación configure el dispositivo Bluetooth local y descubra y se sincronice con dispositivos remotos."</string>
     <string name="permlab_accessWimaxState" msgid="7436749103151096452">"Ver conexiones WiMAX"</string>
-    <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"Permite que la aplicación determine si está habilitada la conexión WiMAX y que obtenga información sobre las redes WiMAX que están conectadas."</string>
+    <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"Permite que la aplicación determine si está activada la conexión WiMAX y que obtenga información sobre las redes WiMAX que están conectadas."</string>
     <string name="permlab_changeWimaxState" msgid="2405042267131496579">"Cambiar el estado de WiMAX"</string>
     <string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"Permite que la aplicación conecte la tableta a una red WiMAX y que la desconecte de ella."</string>
-    <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"Permite que la aplicación conecte el teléfono a una red WiMAX y que lo desconecte de ella."</string>
+    <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"Permite que la aplicación conecte el dispositivo a una red WiMAX y que lo desconecte de ella."</string>
     <string name="permlab_bluetooth" msgid="6127769336339276828">"vincular con dispositivos Bluetooth"</string>
     <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"Permite que la aplicación vea la configuración de Bluetooth de la tableta y que cree y acepte conexiones con los dispositivos sincronizados."</string>
-    <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Permite que la aplicación vea la configuración de Bluetooth del teléfono y que cree y acepte conexiones con los dispositivos sincronizados."</string>
+    <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Permite que la aplicación vea la configuración de Bluetooth del dispositivo y que cree y acepte conexiones con los dispositivos sincronizados."</string>
     <string name="permlab_nfc" msgid="4423351274757876953">"controlar la Transmisión de datos en proximidad"</string>
     <string name="permdesc_nfc" msgid="7120611819401789907">"Permite que la aplicación se comunique con lectores, tarjetas y etiquetas de Comunicación de campo cercano (NFC)."</string>
     <string name="permlab_disableKeyguard" msgid="3598496301486439258">"desactivar el bloqueo de pantalla"</string>
-    <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"Permite que la aplicación inhabilite el bloqueo del teclado y cualquier protección con contraseña asociada. Por ejemplo, el teléfono puede inhabilitar el bloqueo del teclado cuando recibe una llamada telefónica y volver a habilitarlo cuando finaliza la llamada."</string>
+    <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"Permite que la aplicación desactive el bloqueo del teclado y cualquier protección con contraseña asociada. Por ejemplo, el dispositivo puede desactivar el bloqueo del teclado cuando recibe una llamada telefónica y volver a activarlo cuando finaliza la llamada."</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"leer la configuración de sincronización"</string>
     <string name="permdesc_readSyncSettings" msgid="2706745674569678644">"Este permiso permite que la aplicación consulte la configuración de sincronización de una cuenta. Esto permite, por ejemplo, determinar si la aplicación Personas está sincronizada con una cuenta."</string>
     <string name="permlab_writeSyncSettings" msgid="5408694875793945314">"activar y desactivar la sincronización"</string>
-    <string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"Permite que la aplicación modifique la configuración de sincronización de una cuenta. Este permiso se puede utilizar, por ejemplo, para habilitar la sincronización de la aplicación Personas con una cuenta."</string>
+    <string name="permdesc_writeSyncSettings" msgid="8956262591306369868">"Permite que la aplicación modifique la configuración de sincronización de una cuenta. Este permiso se puede utilizar, por ejemplo, para activar la sincronización de la aplicación Personas con una cuenta."</string>
     <string name="permlab_readSyncStats" msgid="7396577451360202448">"leer estadística de sincronización"</string>
     <string name="permdesc_readSyncStats" msgid="1510143761757606156">"Permite que la aplicación consulte las estadísticas de sincronización de una cuenta, por ejemplo, el historial de eventos sincronizados y la cantidad de datos sincronizados."</string>
     <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"leer canales suscritos"</string>
@@ -787,7 +787,7 @@
     <string name="permdesc_readHistoryBookmarks" msgid="8462378226600439658">"Permite que la aplicación consulte el historial de todas las URL visitadas por el navegador, y todos sus marcadores. Nota: Este permiso no puede ser utilizado por navegadores externos ni otras aplicaciones que tengan funciones de navegación por Internet."</string>
     <string name="permlab_writeHistoryBookmarks" msgid="3714785165273314490">"escribir marcadores web e historial"</string>
     <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="6825527469145760922">"Permite que la aplicación modifique el historial o los marcadores del navegador almacenados en la tableta. La aplicación puede utilizar este permiso para borrar o modificar datos del navegador. Nota: Este permiso no puede ser utilizado por navegadores externos ni otras aplicaciones que tengan funciones de navegación por Internet."</string>
-    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permite que la aplicación modifique el historial o los marcadores del navegador almacenados en el teléfono. La aplicación puede utilizar este permiso para borrar o modificar los datos del navegador. Nota: Este permiso no puede ser utilizado por navegadores externos ni otras aplicaciones que tengan funciones de navegación por Internet."</string>
+    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permite que la aplicación modifique el historial o los marcadores del navegador almacenados en el dispositivo. La aplicación puede utilizar este permiso para borrar o modificar los datos del navegador. Nota: Este permiso no puede ser utilizado por navegadores externos ni otras aplicaciones que tengan funciones de navegación por Internet."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"programar una alarma"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que la aplicación establezca una alarma en una aplicación de alarma instalada. Es posible que algunas aplicaciones de alarma no incluyan esta función."</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"agregar correo de voz"</string>
@@ -1284,7 +1284,7 @@
     <string name="activity_resolver_use_always" msgid="8017770747801494933">"Siempre"</string>
     <string name="activity_resolver_use_once" msgid="2404644797149173758">"Solo una vez"</string>
     <string name="default_audio_route_name" product="tablet" msgid="6890731863195399186">"Altavoces de la tableta"</string>
-    <string name="default_audio_route_name" product="default" msgid="4551396562363128432">"Altavoz del teléfono"</string>
+    <string name="default_audio_route_name" product="default" msgid="4551396562363128432">"Altavoz del dispositivo"</string>
     <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auriculares"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altavoces del conector"</string>
     <string name="default_audio_route_name_hdmi" msgid="7986404173839007682">"Audio HDMI"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 0373fc7..cc5c02a 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1080,8 +1080,8 @@
     <string name="extmedia_format_message" product="nosdcard" msgid="3934016853425761078">"همه فایلهای ذخیره شده در حافظه USB پاک خواهد شد. این عمل را نمی‎توان برگرداند!"</string>
     <string name="extmedia_format_message" product="default" msgid="14131895027543830">"تمام اطلاعات روی کارت شما از بین می‎رود."</string>
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"قالب"</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"رفع عیب USB متصل شد"</string>
-    <string name="adb_active_notification_message" msgid="1016654627626476142">"غیرفعال کردن اشکال زدایی USB را لمس کنید."</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"اتصال رفع عیب USB"</string>
+    <string name="adb_active_notification_message" msgid="1016654627626476142">"برای غیرفعال کردن اشکال زدایی USB لمس کنید."</string>
     <string name="select_input_method" msgid="4653387336791222978">"انتخاب روش ورودی"</string>
     <string name="configure_input_methods" msgid="9091652157722495116">"تنظیم روش‌های ورودی"</string>
     <string name="use_physical_keyboard" msgid="6203112478095117625">"صفحه کلید فیزیکی"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index e5a096e..88c679c 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -196,9 +196,9 @@
     <string name="permlab_processOutgoingCalls" msgid="3906007831192990946">"preusmjeravanje odlaznih poziva"</string>
     <string name="permdesc_processOutgoingCalls" msgid="5331318931937402040">"Aplikaciji omogućuje obradu odlaznih poziva i promjenu broja za biranje. Ta dozvola aplikaciji omogućuje nadziranje, preusmjeravanje ili sprječavanje odlaznih poziva."</string>
     <string name="permlab_receiveSms" msgid="8673471768947895082">"primanje tekstnih poruka (SMS)"</string>
-    <string name="permdesc_receiveSms" msgid="6424387754228766939">"Aplikaciji omogućuje primanje i obradu SMS poruka. To znači da aplikacija može nadzirati ili izbrisati poruke poslane na vaš uređaj a da vam ih ne prikaže."</string>
+    <string name="permdesc_receiveSms" msgid="6424387754228766939">"Aplikaciji omogućuje primanje i obradu SMS poruka. To znači da aplikacija može nadzirati ili izbrisati poruke poslane na vaš uređaj, a da vam ih ne prikaže."</string>
     <string name="permlab_receiveMms" msgid="1821317344668257098">"primanje tekstnih poruka (MMS)"</string>
-    <string name="permdesc_receiveMms" msgid="533019437263212260">"Aplikaciji omogućuje primanje i obradu MMS poruka. To znači da aplikacija može nadzirati ili izbrisati poruke poslane na vaš uređaj a da vam ih ne prikaže."</string>
+    <string name="permdesc_receiveMms" msgid="533019437263212260">"Aplikaciji omogućuje primanje i obradu MMS poruka. To znači da aplikacija može nadzirati ili izbrisati poruke poslane na vaš uređaj, a da vam ih ne prikaže."</string>
     <string name="permlab_receiveEmergencyBroadcast" msgid="1803477660846288089">"primanje hitnih odašiljanja"</string>
     <string name="permdesc_receiveEmergencyBroadcast" msgid="848524070262431974">"Omogućuje aplikaciji primanje i obradu poruka hitnih odašiljanja. Ta je dozvola dostupna samo aplikacijama sustava."</string>
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"čitaj poruke koje se emitiraju unutar mobilne mreže"</string>
@@ -214,7 +214,7 @@
     <string name="permdesc_writeSms" product="tablet" msgid="5160413947794501538">"Omogućuje aplikaciji pisanje u SMS poruke pohranjene na vašem tabletnom računalu ili SIM kartici. Zlonamjerne aplikacije mogu izbrisati vaše poruke."</string>
     <string name="permdesc_writeSms" product="default" msgid="7268668709052328567">"Omogućuje aplikaciji pisanje u SMS poruke pohranjene na telefonu ili SIM kartici. Zlonamjerne aplikacije mogu izbrisati vaše poruke."</string>
     <string name="permlab_receiveWapPush" msgid="5991398711936590410">"primanje tekstnih poruka (WAP)"</string>
-    <string name="permdesc_receiveWapPush" msgid="748232190220583385">"Aplikaciji omogućuje primanje i obradu WAP poruka. Ta dozvola uključuje mogućnost nadziranja ili brisanja vama poslanih poruka a da vam ih ne prikaže."</string>
+    <string name="permdesc_receiveWapPush" msgid="748232190220583385">"Aplikaciji omogućuje primanje i obradu WAP poruka. Ta dozvola uključuje mogućnost nadziranja ili brisanja vama poslanih poruka, a da vam ih ne prikaže."</string>
     <string name="permlab_getTasks" msgid="6466095396623933906">"dohvaćanje pokrenutih aplikacija"</string>
     <string name="permdesc_getTasks" msgid="7454215995847658102">"Aplikaciji omogućuje dohvaćanje informacija o trenutačnim i nedavnim tekućim zadacima. To aplikaciji može omogućiti otkrivanje informacija o tome koje se aplikacije upotrebljavaju na uređaju."</string>
     <string name="permlab_getDetailedTasks" msgid="6229468674753529501">"dohvaćanje pojedinosti o pokrenutim aplikacijama"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 79fa856..a3c421c 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -388,9 +388,9 @@
     <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"ubah setelan audio Anda"</string>
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Memungkinkan aplikasi mengubah setelan audio global, misalnya volume dan pengeras suara mana yang digunakan untuk keluaran."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"rekam audio"</string>
-    <string name="permdesc_recordAudio" msgid="4906839301087980680">"Memungkinkan aplikasi merekam audio dengan mikrofon. Izin ini memungkinkan aplikasi merekam audio kapan pun tanpa konfirmasi Anda."</string>
+    <string name="permdesc_recordAudio" msgid="4906839301087980680">"Memungkinkan aplikasi merekam audio dengan mikrofon. Izin ini memungkinkan aplikasi merekam audio kapan saja tanpa konfirmasi Anda."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ambil gambar dan video"</string>
-    <string name="permdesc_camera" msgid="8497216524735535009">"Memungkinkan aplikasi mengambil gambar dan video dengan kamera. Izin ini memungkinkan aplikasi menggunakan kamera setiap saat tanpa konfirmasi Anda."</string>
+    <string name="permdesc_camera" msgid="8497216524735535009">"Memungkinkan aplikasi mengambil gambar dan video dengan kamera. Izin ini memungkinkan aplikasi menggunakan kamera kapan saja tanpa konfirmasi Anda."</string>
     <string name="permlab_brick" product="tablet" msgid="2961292205764488304">"noaktifkan tablet secara permanen"</string>
     <string name="permlab_brick" product="default" msgid="8337817093326370537">"nonaktifkan ponsel secara permanen"</string>
     <string name="permdesc_brick" product="tablet" msgid="4334818808001699530">"Mengizinkan apl menonaktifkan seluruh tablet secara permanen. Ini sangat berbahaya."</string>
@@ -501,8 +501,8 @@
     <string name="permlab_accessWimaxState" msgid="7436749103151096452">"Lihat sambungan WiMAX"</string>
     <string name="permdesc_accessWimaxState" msgid="6360102877261978887">"Memungkinkan aplikasi menentukan apakah WiMAX diaktifkan dan informasi tentang jaringan WiMAX apa saja yang tersambung."</string>
     <string name="permlab_changeWimaxState" msgid="2405042267131496579">"Ubah status WiMAX"</string>
-    <string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"Memungkinkan aplikasi menyambung tablet ke dan memutus tablet dari jaringan WiMAX."</string>
-    <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"Memungkinkan aplikasi menyambung ke dan memutus ponsel dari jaringan WiMAX."</string>
+    <string name="permdesc_changeWimaxState" product="tablet" msgid="3156456504084201805">"Memungkinkan aplikasi menyambungkan tablet ke dan memutus tablet dari jaringan WiMAX."</string>
+    <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"Memungkinkan aplikasi menyambungkan ponsel ke dan memutus ponsel dari jaringan WiMAX."</string>
     <string name="permlab_bluetooth" msgid="6127769336339276828">"sandingkan dengan perangkat Bluetooth"</string>
     <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"Memungkinkan aplikasi melihat konfigurasi Bluetooth di tablet, dan membuat serta menerima sambungan dengan perangkat yang disandingkan."</string>
     <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Memungkinkan aplikasi melihat konfigurasi Bluetooth di ponsel, dan membuat serta menerima sambungan dengan perangkat yang disandingkan."</string>
diff --git a/core/res/res/values-land/arrays.xml b/core/res/res/values-land/arrays.xml
index 7095c02..f2df3fa 100644
--- a/core/res/res/values-land/arrays.xml
+++ b/core/res/res/values-land/arrays.xml
@@ -19,7 +19,7 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <!-- Resources for MultiWaveView in LockScreen -->
+    <!-- Resources for GlowPadView in LockScreen -->
     <array name="lockscreen_targets_when_silent">
         <item>@null</item>"
         <item>@drawable/ic_lockscreen_unlock</item>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 2e88f0a..e5b9288 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -204,9 +204,9 @@
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"läsa SMS-meddelanden"</string>
     <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Appen tillåts läsa SMS som skickas till din enhet. På vissa platser skickas SMS för att varna för nödsituationer. Skadliga appar kan påverka enhetens prestanda eller funktionalitet när du får ett meddelande om en nödsituation via SMS."</string>
     <string name="permlab_sendSms" msgid="5600830612147671529">"skicka SMS"</string>
-    <string name="permdesc_sendSms" msgid="7094729298204937667">"Tillåter att appen skickar SMS. Detta kan leda till ovänta avgifter. Skadliga appar kan skicka meddelanden utan ditt godkännande vilket kan kosta pengar."</string>
+    <string name="permdesc_sendSms" msgid="7094729298204937667">"Tillåter att appen skickar SMS. Detta kan leda till oväntade avgifter. Skadliga appar kan skicka meddelanden utan ditt godkännande vilket kan kosta pengar."</string>
     <string name="permlab_sendSmsNoConfirmation" msgid="4781483105951730228">"skicka SMS utan bekräftelse"</string>
-    <string name="permdesc_sendSmsNoConfirmation" msgid="402569800862935907">"Tillåter att appen skickar SMS. Detta kan leda till ovänta avgifter. Skadliga appar kan skicka meddelanden utan ditt godkännande vilket kan kosta pengar."</string>
+    <string name="permdesc_sendSmsNoConfirmation" msgid="402569800862935907">"Tillåter att appen skickar SMS. Detta kan leda till oväntade avgifter. Skadliga appar kan skicka meddelanden utan ditt godkännande vilket kan kosta pengar."</string>
     <string name="permlab_readSms" msgid="8745086572213270480">"läsa dina textmeddelanden (SMS eller MMS)"</string>
     <string name="permdesc_readSms" product="tablet" msgid="2467981548684735522">"Tillåter att appen läser SMS som sparats på pekdatorn eller på SIM-kortet. Med den här behörigheten tillåts appen att läsa alla SMS oavsett innehåll eller sekretess."</string>
     <string name="permdesc_readSms" product="default" msgid="3695967533457240550">"Tillåter att appen läser SMS som sparats på mobilen eller på SIM-kortet. Med den här behörigheten tillåts appen att läsa alla SMS oavsett innehåll eller sekretess."</string>
diff --git a/core/res/res/values-sw600dp-land/arrays.xml b/core/res/res/values-sw600dp-land/arrays.xml
index 6a09cf8..2b5fd99 100644
--- a/core/res/res/values-sw600dp-land/arrays.xml
+++ b/core/res/res/values-sw600dp-land/arrays.xml
@@ -19,7 +19,7 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <!-- Resources for MultiWaveView in LockScreen -->
+    <!-- Resources for GlowPadView in LockScreen -->
     <array name="lockscreen_targets_when_silent">
         <item>@drawable/ic_lockscreen_unlock</item>
         <item>@null</item>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index d26666e..8937c2a 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -45,8 +45,8 @@
     <!-- Size of lockscreen outerring on unsecure unlock LockScreen -->
     <dimen name="keyguard_lockscreen_outerring_diameter">364dp</dimen>
 
-    <!-- target placement radius for MultiWaveView -->
-    <dimen name="multiwaveview_target_placement_radius">182dip</dimen>
+    <!-- target placement radius for GlowPadView. Should be 1/2 of outerring diameter. -->
+    <dimen name="glowpadview_target_placement_radius">182dip</dimen>
 
     <!-- Size of status line font in LockScreen. -->
     <dimen name="keyguard_pattern_unlock_status_line_font_size">14sp</dimen>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index b83cb7f..f4c6a66 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -459,7 +459,7 @@
     <string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"Uygulamaya, sistem duvar kağıdı için boyut ipuçlarını ayarlama izni verir."</string>
     <string name="permlab_masterClear" msgid="2315750423139697397">"sistemi fabrika değerlerine sıfırla"</string>
     <string name="permdesc_masterClear" msgid="3665380492633910226">"Uygulamaya, tüm verileri, yapılandırmayı ve yüklü uygulamaları silerek sistemi tamamen fabrika ayarlarına sıfırlama izni verir."</string>
-    <string name="permlab_setTime" msgid="2021614829591775646">"saati ayarla"</string>
+    <string name="permlab_setTime" msgid="2021614829591775646">"saati ayarlayın"</string>
     <string name="permdesc_setTime" product="tablet" msgid="1896341438151152881">"Uygulamaya, tablet saatini değiştirme izni verir."</string>
     <string name="permdesc_setTime" product="default" msgid="1855702730738020">"Uygulamaya, telefon saatini değiştirme izni verir."</string>
     <string name="permlab_setTimeZone" msgid="2945079801013077340">"saat dilimini ayarla"</string>
@@ -1040,8 +1040,8 @@
     <string name="sim_added_title" msgid="3719670512889674693">"SIM kart eklendi"</string>
     <string name="sim_added_message" msgid="6599945301141050216">"Mobil ağa erişmek için cihazınızı yeniden başlatın."</string>
     <string name="sim_restart_button" msgid="4722407842815232347">"Yeniden başlat"</string>
-    <string name="time_picker_dialog_title" msgid="8349362623068819295">"Saati ayarla"</string>
-    <string name="date_picker_dialog_title" msgid="5879450659453782278">"Tarihi ayarla"</string>
+    <string name="time_picker_dialog_title" msgid="8349362623068819295">"Saati ayarlayın"</string>
+    <string name="date_picker_dialog_title" msgid="5879450659453782278">"Tarihi ayarlayın"</string>
     <string name="date_time_set" msgid="5777075614321087758">"Ayarla"</string>
     <string name="date_time_done" msgid="2507683751759308828">"Tamamlandı"</string>
     <string name="default_permission_group" msgid="2690160991405646128">"Varsayılan"</string>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 1eeca59..aeb6b4f 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -350,7 +350,7 @@
         <item>中文 (繁體)</item>
     </string-array>
 
-    <!-- Resources for MultiWaveView in LockScreen -->
+    <!-- Resources for GlowPadView in LockScreen -->
     <array name="lockscreen_targets_when_silent">
         <item>@drawable/ic_lockscreen_unlock</item>
         <item>@drawable/ic_lockscreen_search</item>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a68011d..00077512 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5377,6 +5377,54 @@
     </declare-styleable>
 
     <!-- =============================== -->
+    <!-- GlowPadView class attributes -->
+    <!-- =============================== -->
+    <eat-comment />
+    <declare-styleable name="GlowPadView">
+        <!-- Reference to an array resource that be shown as targets around a circle. -->
+        <attr name="targetDrawables"/>
+
+        <!-- Reference to an array resource that be used as description for the targets around the circle. -->
+        <attr name="targetDescriptions"/>
+
+        <!-- Reference to an array resource that be used to announce the directions with targets around the circle. -->
+        <attr name="directionDescriptions"/>
+
+        <!-- Sets a drawable as the center. -->
+        <attr name="handleDrawable"/>
+
+        <!-- Drawable to use for wave ripple animation. -->
+        <attr name="outerRingDrawable" format="reference"/>
+
+        <!-- Drawble used for drawing points -->
+        <attr name="pointDrawable" format="reference" />
+
+        <!-- Inner radius of glow area. -->
+        <attr name="innerRadius"/>
+
+        <!-- Outer radius of glow area. Target icons will be drawn on this circle. -->
+        <attr name="outerRadius"/>
+
+        <!-- Size of target radius. Points within this distance of target center is a "hit". -->
+        <attr name="hitRadius"/>
+
+        <!-- Radius of glow under finger. -->
+        <attr name="glowRadius" format="dimension" />
+
+        <!-- Tactile feedback duration for actions. Set to '0' for no vibration. -->
+        <attr name="vibrationDuration"/>
+
+        <!-- How close we need to be before snapping to a target. -->
+        <attr name="snapMargin"/>
+
+        <!-- Number of waves/chevrons to show in animation. -->
+        <attr name="feedbackCount"/>
+
+        <!-- Used when the handle shouldn't wait to be hit before following the finger -->
+        <attr name="alwaysTrackFinger"/>
+    </declare-styleable>
+
+    <!-- =============================== -->
     <!-- MultiWaveView class attributes -->
     <!-- =============================== -->
     <eat-comment />
@@ -5393,22 +5441,6 @@
         <!-- Sets a drawable as the drag center. -->
         <attr name="handleDrawable" format="reference" />
 
-        <!-- Drawable to use for chevron animation on the left. May be null.
-            @deprecated use chevronDrawables instead -->
-        <attr name="leftChevronDrawable" format="reference" />
-
-        <!-- Drawable to use for chevron animation on the right. May be null.
-            @deprecated use chevronDrawables instead -->
-        <attr name="rightChevronDrawable" format="reference" />
-
-        <!-- Drawable to use for chevron animation on the top. May be null.
-            @deprecated use chevronDrawables instead -->
-        <attr name="topChevronDrawable" format="reference" />
-
-        <!-- Drawable to use for chevron animation on the bottom. May be null.
-            @deprecated use chevronDrawables instead -->
-        <attr name="bottomChevronDrawable" format="reference" />
-
         <!-- Drawables to use for chevron animations. May be null. -->
         <attr name="chevronDrawables" format="reference"/>
 
@@ -5430,17 +5462,6 @@
         <!-- Number of waves/chevrons to show in animation. -->
         <attr name="feedbackCount" format="integer" />
 
-        <!-- {@deprecated Not used by the framework. Use android:gravity instead}
-            Used to shift center of pattern vertically. -->
-        <attr name="verticalOffset" format="dimension" />
-
-        <!-- {@deprecated Not used by the framework. Use android:gravity instead}
-            Used to shift center of pattern horizontally. -->
-        <attr name="horizontalOffset" format="dimension" />
-
-        <!-- How the items in this layout should be positioned -->
-        <attr name="gravity" />
-
         <!-- Used when the handle shouldn't wait to be hit before following the finger -->
         <attr name="alwaysTrackFinger" format="boolean" />
     </declare-styleable>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d549644..ffbcb95 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -73,14 +73,20 @@
     <!-- Size of lockscreen outerring on unsecure unlock LockScreen -->
     <dimen name="keyguard_lockscreen_outerring_diameter">270dp</dimen>
 
-    <!-- Default target placement radius for MultiWaveView -->
-    <dimen name="multiwaveview_target_placement_radius">135dip</dimen>
+    <!-- Default target placement radius for GlowPadView. Should be 1/2 of outerring diameter. -->
+    <dimen name="glowpadview_target_placement_radius">135dip</dimen>
 
-    <!-- Default distance beyond which MultiWaveView snaps to the target radius -->
-    <dimen name="multiwaveview_snap_margin">20dip</dimen>
+    <!-- Default glow radius for GlowPadView -->
+    <dimen name="glowpadview_glow_radius">75dip</dimen>
 
-    <!-- Default distance from each snap target that MultiWaveView considers a "hit" -->
-    <dimen name="multiwaveview_hit_radius">60dip</dimen>
+    <!-- Default distance beyond which GlowPadView snaps to the target radius -->
+    <dimen name="glowpadview_snap_margin">20dip</dimen>
+
+    <!-- Default distance from each snap target that GlowPadView considers a "hit" -->
+    <dimen name="glowpadview_hit_radius">60dip</dimen>
+
+    <!-- Default distance from each snap target that GlowPadView considers a "hit" -->
+    <dimen name="glowpadview_inner_radius">15dip</dimen>
 
     <!-- Preference activity side margins -->
     <dimen name="preference_screen_side_margin">0dp</dimen>
@@ -228,10 +234,10 @@
          a few are present. -->
     <dimen name="action_bar_stacked_tab_max_width">180dp</dimen>
 
-	<!-- Size of notification text (see TextAppearance.StatusBar.EventContent) -->
+    <!-- Size of notification text (see TextAppearance.StatusBar.EventContent) -->
     <dimen name="notification_text_size">14dp</dimen>
-	<!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) -->
+    <!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) -->
     <dimen name="notification_title_text_size">18dp</dimen>
-	<!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
+    <!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
     <dimen name="notification_subtext_size">12dp</dimen>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9861bae..53914a8 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2705,11 +2705,9 @@
     <string name="editTextMenuTitle">Text actions</string>
 
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the title of that notification. -->
-    <string name="low_internal_storage_view_title">Low on space</string>
+    <string name="low_internal_storage_view_title">Storage space running out</string>
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the message of that notification. -->
-    <string name="low_internal_storage_view_text" product="tablet">Tablet storage space is getting low.</string>
-    <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the message of that notification. -->
-    <string name="low_internal_storage_view_text" product="default">Phone storage space is getting low.</string>
+    <string name="low_internal_storage_view_text">Some system functions may not work</string>
 
     <!-- Preference framework strings. -->
     <string name="ok">OK</string>
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index a0bf8e3..0d6e62a 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -598,6 +598,7 @@
         glGenTextures(1, &cacheTexture->mTextureId);
     }
 
+    Caches::getInstance().activeTexture(0);
     glBindTexture(GL_TEXTURE_2D, cacheTexture->mTextureId);
     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
     // Initialize texture dimensions
@@ -826,6 +827,7 @@
         }
     }
 
+    caches.activeTexture(0);
     glBindTexture(GL_TEXTURE_2D, mCurrentCacheTexture->mTextureId);
     if (mLinearFiltering != mCurrentCacheTexture->mLinearFiltering) {
         const GLenum filtering = mLinearFiltering ? GL_LINEAR : GL_NEAREST;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 0d857bf..c92bead 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2422,6 +2422,7 @@
         linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
     }
 
+    // The font renderer will always use texture unit 0
     mCaches.activeTexture(0);
     setupDraw();
     setupDrawDirtyRegionsDisabled();
@@ -2432,6 +2433,8 @@
     setupDrawBlending(true, mode);
     setupDrawProgram();
     setupDrawModelView(x, y, x, y, pureTranslate, true);
+    // See comment above; the font renderer must use texture unit 0
+    // assert(mTextureUnit == 0)
     setupDrawTexture(fontRenderer.getTexture(linearFilter));
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 133f30b..70fc623 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -97,7 +97,7 @@
 
     boolean isBluetoothScoOn();
 
-    oneway void setBluetoothA2dpOn(boolean on);
+    void setBluetoothA2dpOn(boolean on);
 
     boolean isBluetoothA2dpOn();
 
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 7e4ca6a..38fe363 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -21,37 +21,76 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * MediaRouter allows applications to control the routing of media channels
  * and streams from the current device to external speakers and destination devices.
  *
- * <p>Media routes should only be modified on your application's main thread.</p>
+ * <p>A MediaRouter is retrieved through {@link Context#getSystemService(String)
+ * Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE
+ * Context.MEDIA_ROUTER_SERVICE}.
+ *
+ * <p>The media router API is not thread-safe; all interactions with it must be
+ * done from the main thread of the process.</p>
  */
 public class MediaRouter {
     private static final String TAG = "MediaRouter";
 
-    private Context mAppContext;
-    private AudioManager mAudioManager;
-    private Handler mHandler;
-    private final ArrayList<CallbackInfo> mCallbacks = new ArrayList<CallbackInfo>();
+    static class Static {
+        final Resources mResources;
+        final AudioManager mAudioManager;
+        final Handler mHandler;
+        final ArrayList<CallbackInfo> mCallbacks = new ArrayList<CallbackInfo>();
 
-    private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
-    private final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
+        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
+        final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
 
-    private final RouteCategory mSystemCategory;
-    private RouteInfo mDefaultAudio;
-    private RouteInfo mBluetoothA2dpRoute;
+        final RouteCategory mSystemCategory;
+        final HeadphoneChangedBroadcastReceiver mHeadphoneChangedReceiver;
 
-    private RouteInfo mSelectedRoute;
+        RouteInfo mDefaultAudio;
+        RouteInfo mBluetoothA2dpRoute;
 
-    // These get removed when an activity dies
-    final ArrayList<BroadcastReceiver> mRegisteredReceivers = new ArrayList<BroadcastReceiver>();
+        RouteInfo mSelectedRoute;
+
+        Static(Context appContext) {
+            mResources = Resources.getSystem();
+            mHandler = new Handler(appContext.getMainLooper());
+
+            mAudioManager = (AudioManager)appContext.getSystemService(Context.AUDIO_SERVICE);
+
+            // XXX this doesn't deal with locale changes!
+            mSystemCategory = new RouteCategory(mResources.getText(
+                    com.android.internal.R.string.default_audio_route_category_name),
+                    ROUTE_TYPE_LIVE_AUDIO, false);
+
+            final IntentFilter speakerFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
+            speakerFilter.addAction(Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG);
+            speakerFilter.addAction(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG);
+            speakerFilter.addAction(Intent.ACTION_HDMI_AUDIO_PLUG);
+            mHeadphoneChangedReceiver = new HeadphoneChangedBroadcastReceiver();
+            appContext.registerReceiver(mHeadphoneChangedReceiver, speakerFilter);
+        }
+
+        // Called after sStatic is initialized
+        void initDefaultRoutes() {
+            mDefaultAudio = new RouteInfo(mSystemCategory);
+            mDefaultAudio.mName = mResources.getText(
+                    com.android.internal.R.string.default_audio_route_name);
+            mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
+            addRoute(mDefaultAudio);
+        }
+    }
+
+    static Static sStatic;
 
     /**
      * Route type flag for live audio.
@@ -78,25 +117,6 @@
     // Maps application contexts
     static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
 
-    /**
-     * Return a MediaRouter for the application that the specified Context belongs to.
-     * The behavior or availability of media routing may depend on
-     * various parameters of the context.
-     *
-     * @param context Context for the desired router
-     * @return Router for the supplied Context
-     */
-    public static MediaRouter forApplication(Context context) {
-        final Context appContext = context.getApplicationContext();
-        if (!sRouters.containsKey(appContext)) {
-            final MediaRouter r = new MediaRouter(appContext);
-            sRouters.put(appContext, r);
-            return r;
-        } else {
-            return sRouters.get(appContext);
-        }
-    }
-
     static String typesToString(int types) {
         final StringBuilder result = new StringBuilder();
         if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
@@ -108,63 +128,21 @@
         return result.toString();
     }
 
-    private MediaRouter(Context context) {
-        mAppContext = context;
-        mHandler = new Handler(mAppContext.getMainLooper());
-
-        mAudioManager = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
-
-        mSystemCategory = new RouteCategory(mAppContext.getText(
-                com.android.internal.R.string.default_audio_route_category_name),
-                ROUTE_TYPE_LIVE_AUDIO, false);
-
-        registerReceivers();
-
-        createDefaultRoutes();
-    }
-
-    private void registerReceivers() {
-        final BroadcastReceiver volumeReceiver = new VolumeChangedBroadcastReceiver();
-        mAppContext.registerReceiver(volumeReceiver,
-                new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
-        mRegisteredReceivers.add(volumeReceiver);
-
-        final IntentFilter speakerFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
-        speakerFilter.addAction(Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG);
-        speakerFilter.addAction(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG);
-        speakerFilter.addAction(Intent.ACTION_HDMI_AUDIO_PLUG);
-        final BroadcastReceiver plugReceiver = new HeadphoneChangedBroadcastReceiver();
-        mAppContext.registerReceiver(plugReceiver, speakerFilter);
-        mRegisteredReceivers.add(plugReceiver);
-    }
-
-    void unregisterReceivers() {
-        final int count = mRegisteredReceivers.size();
-        for (int i = 0; i < count; i++) {
-            final BroadcastReceiver r = mRegisteredReceivers.get(i);
-            mAppContext.unregisterReceiver(r);
+    /** @hide */
+    public MediaRouter(Context context) {
+        synchronized (Static.class) {
+            if (sStatic == null) {
+                sStatic = new Static(context.getApplicationContext());
+                sStatic.initDefaultRoutes();
+            }
         }
-        mRegisteredReceivers.clear();
-    }
-
-    private void createDefaultRoutes() {
-        mDefaultAudio = new RouteInfo(mSystemCategory);
-        mDefaultAudio.mName = mAppContext.getText(
-                com.android.internal.R.string.default_audio_route_name);
-        mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
-        final int maxMusicVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        if (maxMusicVolume > 0) {
-            mDefaultAudio.mVolume =
-                    mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC) / maxMusicVolume;
-        }
-        addRoute(mDefaultAudio);
     }
 
     /**
      * @hide for use by framework routing UI
      */
     public RouteInfo getSystemAudioRoute() {
-        return mDefaultAudio;
+        return sStatic.mDefaultAudio;
     }
 
     /**
@@ -174,30 +152,15 @@
      * @return the selected route
      */
     public RouteInfo getSelectedRoute(int type) {
-        return mSelectedRoute;
+        return sStatic.mSelectedRoute;
     }
 
-    void onHeadphonesPlugged(boolean headphonesPresent, String headphonesName) {
-        mDefaultAudio.mName = headphonesPresent ? headphonesName : mAppContext.getText(
-                com.android.internal.R.string.default_audio_route_name);
-        dispatchRouteChanged(mDefaultAudio);
-    }
-
-    /**
-     * Set volume for the specified selected route types.
-     *
-     * @param types Volume will be set for these route types
-     * @param volume Volume to set in the range 0.f (inaudible) to 1.f (full volume).
-     */
-    public void setSelectedRouteVolume(int types, float volume) {
-        if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0 && mSelectedRoute == mDefaultAudio) {
-            final int index = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0);
-        }
-        if ((types & ROUTE_TYPE_USER) != 0 && mSelectedRoute instanceof UserRouteInfo) {
-            mSelectedRoute.mVolume = volume;
-            dispatchVolumeChanged(ROUTE_TYPE_USER, volume);
-        }
+    static void onHeadphonesPlugged(boolean headphonesPresent, String headphonesName) {
+        sStatic.mDefaultAudio.mName = headphonesPresent
+                ? headphonesName
+                : sStatic.mResources.getText(
+                        com.android.internal.R.string.default_audio_route_name);
+        dispatchRouteChanged(sStatic.mDefaultAudio);
     }
 
     /**
@@ -209,15 +172,15 @@
      * @param cb Callback to add
      */
     public void addCallback(int types, Callback cb) {
-        final int count = mCallbacks.size();
+        final int count = sStatic.mCallbacks.size();
         for (int i = 0; i < count; i++) {
-            final CallbackInfo info = mCallbacks.get(i);
+            final CallbackInfo info = sStatic.mCallbacks.get(i);
             if (info.cb == cb) {
                 info.type &= types;
                 return;
             }
         }
-        mCallbacks.add(new CallbackInfo(cb, types));
+        sStatic.mCallbacks.add(new CallbackInfo(cb, types, this));
     }
 
     /**
@@ -226,24 +189,36 @@
      * @param cb Callback to remove
      */
     public void removeCallback(Callback cb) {
-        final int count = mCallbacks.size();
+        final int count = sStatic.mCallbacks.size();
         for (int i = 0; i < count; i++) {
-            if (mCallbacks.get(i).cb == cb) {
-                mCallbacks.remove(i);
+            if (sStatic.mCallbacks.get(i).cb == cb) {
+                sStatic.mCallbacks.remove(i);
                 return;
             }
         }
         Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
     }
 
+    /**
+     * Select the specified route to use for output of the given media types.
+     *
+     * @param types type flags indicating which types this route should be used for.
+     *              The route must support at least a subset.
+     * @param route Route to select
+     */
     public void selectRoute(int types, RouteInfo route) {
-        if (mSelectedRoute == route) return;
+        selectRouteStatic(types, route);
+    }
 
-        if (mSelectedRoute != null) {
+    static void selectRouteStatic(int types, RouteInfo route) {
+        if (sStatic.mSelectedRoute == route) return;
+
+        if (sStatic.mSelectedRoute != null) {
             // TODO filter types properly
-            dispatchRouteUnselected(types & mSelectedRoute.getSupportedTypes(), mSelectedRoute);
+            dispatchRouteUnselected(types & sStatic.mSelectedRoute.getSupportedTypes(),
+                    sStatic.mSelectedRoute);
         }
-        mSelectedRoute = route;
+        sStatic.mSelectedRoute = route;
         if (route != null) {
             // TODO filter types properly
             dispatchRouteSelected(types & route.getSupportedTypes(), route);
@@ -262,22 +237,30 @@
         addRoute(info);
     }
 
-    void addRoute(RouteInfo info) {
+    static void addRoute(RouteInfo info) {
         final RouteCategory cat = info.getCategory();
-        if (!mCategories.contains(cat)) {
-            mCategories.add(cat);
+        if (!sStatic.mCategories.contains(cat)) {
+            sStatic.mCategories.add(cat);
         }
-        if (info.getCategory().isGroupable() && !(info instanceof RouteGroup)) {
+        final boolean onlyRoute = sStatic.mRoutes.isEmpty();
+        if (cat.isGroupable() && !(info instanceof RouteGroup)) {
             // Enforce that any added route in a groupable category must be in a group.
             final RouteGroup group = new RouteGroup(info.getCategory());
+            sStatic.mRoutes.add(group);
+            dispatchRouteAdded(group);
+
+            final int at = group.getRouteCount();
             group.addRoute(info);
+            dispatchRouteGrouped(info, group, at);
+
             info = group;
+        } else {
+            sStatic.mRoutes.add(info);
+            dispatchRouteAdded(info);
         }
-        final boolean onlyRoute = mRoutes.isEmpty();
-        mRoutes.add(info);
-        dispatchRouteAdded(info);
+
         if (onlyRoute) {
-            selectRoute(info.getSupportedTypes(), info);
+            selectRouteStatic(info.getSupportedTypes(), info);
         }
     }
 
@@ -297,8 +280,8 @@
      * @see #removeUserRoute(UserRouteInfo)
      */
     public void clearUserRoutes() {
-        for (int i = 0; i < mRoutes.size(); i++) {
-            final RouteInfo info = mRoutes.get(i);
+        for (int i = 0; i < sStatic.mRoutes.size(); i++) {
+            final RouteInfo info = sStatic.mRoutes.get(i);
             if (info instanceof UserRouteInfo) {
                 removeRouteAt(i);
                 i--;
@@ -306,40 +289,40 @@
         }
     }
 
-    void removeRoute(RouteInfo info) {
-        if (mRoutes.remove(info)) {
+    static void removeRoute(RouteInfo info) {
+        if (sStatic.mRoutes.remove(info)) {
             final RouteCategory removingCat = info.getCategory();
-            final int count = mRoutes.size();
+            final int count = sStatic.mRoutes.size();
             boolean found = false;
             for (int i = 0; i < count; i++) {
-                final RouteCategory cat = mRoutes.get(i).getCategory();
+                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
                 if (removingCat == cat) {
                     found = true;
                     break;
                 }
             }
             if (!found) {
-                mCategories.remove(removingCat);
+                sStatic.mCategories.remove(removingCat);
             }
             dispatchRouteRemoved(info);
         }
     }
 
     void removeRouteAt(int routeIndex) {
-        if (routeIndex >= 0 && routeIndex < mRoutes.size()) {
-            final RouteInfo info = mRoutes.remove(routeIndex);
+        if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) {
+            final RouteInfo info = sStatic.mRoutes.remove(routeIndex);
             final RouteCategory removingCat = info.getCategory();
-            final int count = mRoutes.size();
+            final int count = sStatic.mRoutes.size();
             boolean found = false;
             for (int i = 0; i < count; i++) {
-                final RouteCategory cat = mRoutes.get(i).getCategory();
+                final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
                 if (removingCat == cat) {
                     found = true;
                     break;
                 }
             }
             if (!found) {
-                mCategories.remove(removingCat);
+                sStatic.mCategories.remove(removingCat);
             }
             dispatchRouteRemoved(info);
         }
@@ -352,7 +335,7 @@
      * @return the number of unique categories represented by this MediaRouter's known routes
      */
     public int getCategoryCount() {
-        return mCategories.size();
+        return sStatic.mCategories.size();
     }
 
     /**
@@ -363,7 +346,7 @@
      * @return the category at index
      */
     public RouteCategory getCategoryAt(int index) {
-        return mCategories.get(index);
+        return sStatic.mCategories.get(index);
     }
 
     /**
@@ -373,7 +356,7 @@
      * @return the number of routes tracked by this router
      */
     public int getRouteCount() {
-        return mRoutes.size();
+        return sStatic.mRoutes.size();
     }
 
     /**
@@ -383,7 +366,15 @@
      * @return the route at index
      */
     public RouteInfo getRouteAt(int index) {
-        return mRoutes.get(index);
+        return sStatic.mRoutes.get(index);
+    }
+
+    static int getRouteCountStatic() {
+        return sStatic.mRoutes.size();
+    }
+
+    static RouteInfo getRouteAtStatic(int index) {
+        return sStatic.mRoutes.get(index);
     }
 
     /**
@@ -411,96 +402,105 @@
         return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
     }
 
-    void updateRoute(final RouteInfo info) {
+    static void updateRoute(final RouteInfo info) {
         dispatchRouteChanged(info);
     }
 
-    void dispatchRouteSelected(int type, RouteInfo info) {
-        final int count = mCallbacks.size();
+    static void dispatchRouteSelected(int type, RouteInfo info) {
+        final int count = sStatic.mCallbacks.size();
         for (int i = 0; i < count; i++) {
-            final CallbackInfo cbi = mCallbacks.get(i);
+            final CallbackInfo cbi = sStatic.mCallbacks.get(i);
             if ((cbi.type & type) != 0) {
-                cbi.cb.onRouteSelected(type, info);
+                cbi.cb.onRouteSelected(cbi.router, type, info);
             }
         }
     }
 
-    void dispatchRouteUnselected(int type, RouteInfo info) {
-        final int count = mCallbacks.size();
+    static void dispatchRouteUnselected(int type, RouteInfo info) {
+        final int count = sStatic.mCallbacks.size();
         for (int i = 0; i < count; i++) {
-            final CallbackInfo cbi = mCallbacks.get(i);
+            final CallbackInfo cbi = sStatic.mCallbacks.get(i);
             if ((cbi.type & type) != 0) {
-                cbi.cb.onRouteUnselected(type, info);
+                cbi.cb.onRouteUnselected(cbi.router, type, info);
             }
         }
     }
 
-    void dispatchRouteChanged(RouteInfo info) {
-        final int count = mCallbacks.size();
+    static void dispatchRouteChanged(RouteInfo info) {
+        final int count = sStatic.mCallbacks.size();
         for (int i = 0; i < count; i++) {
-            final CallbackInfo cbi = mCallbacks.get(i);
+            final CallbackInfo cbi = sStatic.mCallbacks.get(i);
             if ((cbi.type & info.mSupportedTypes) != 0) {
-                cbi.cb.onRouteChanged(info);
+                cbi.cb.onRouteChanged(cbi.router, info);
             }
         }
     }
 
-    void dispatchRouteAdded(RouteInfo info) {
-        final int count = mCallbacks.size();
+    static void dispatchRouteAdded(RouteInfo info) {
+        final int count = sStatic.mCallbacks.size();
         for (int i = 0; i < count; i++) {
-            final CallbackInfo cbi = mCallbacks.get(i);
+            final CallbackInfo cbi = sStatic.mCallbacks.get(i);
             if ((cbi.type & info.mSupportedTypes) != 0) {
-                cbi.cb.onRouteAdded(info.mSupportedTypes, info);
+                cbi.cb.onRouteAdded(cbi.router, info);
             }
         }
     }
 
-    void dispatchRouteRemoved(RouteInfo info) {
-        final int count = mCallbacks.size();
+    static void dispatchRouteRemoved(RouteInfo info) {
+        final int count = sStatic.mCallbacks.size();
         for (int i = 0; i < count; i++) {
-            final CallbackInfo cbi = mCallbacks.get(i);
+            final CallbackInfo cbi = sStatic.mCallbacks.get(i);
             if ((cbi.type & info.mSupportedTypes) != 0) {
-                cbi.cb.onRouteRemoved(info.mSupportedTypes, info);
+                cbi.cb.onRouteRemoved(cbi.router, info);
             }
         }
     }
 
-    void dispatchVolumeChanged(int type, float volume) {
-        final int count = mCallbacks.size();
+    static void dispatchRouteGrouped(RouteInfo info, RouteGroup group, int index) {
+        final int count = sStatic.mCallbacks.size();
         for (int i = 0; i < count; i++) {
-            final CallbackInfo cbi = mCallbacks.get(i);
-            if ((cbi.type & type) != 0) {
-                cbi.cb.onVolumeChanged(type, volume);
+            final CallbackInfo cbi = sStatic.mCallbacks.get(i);
+            if ((cbi.type & group.mSupportedTypes) != 0) {
+                cbi.cb.onRouteGrouped(cbi.router, info, group, index);
             }
         }
     }
 
-    void onA2dpDeviceConnected() {
-        final RouteInfo info = new RouteInfo(mSystemCategory);
+    static void dispatchRouteUngrouped(RouteInfo info, RouteGroup group) {
+        final int count = sStatic.mCallbacks.size();
+        for (int i = 0; i < count; i++) {
+            final CallbackInfo cbi = sStatic.mCallbacks.get(i);
+            if ((cbi.type & group.mSupportedTypes) != 0) {
+                cbi.cb.onRouteUngrouped(cbi.router, info, group);
+            }
+        }
+    }
+
+    static void onA2dpDeviceConnected() {
+        final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
         info.mName = "Bluetooth"; // TODO Fetch the real name of the device
-        mBluetoothA2dpRoute = info;
-        addRoute(mBluetoothA2dpRoute);
+        sStatic.mBluetoothA2dpRoute = info;
+        addRoute(sStatic.mBluetoothA2dpRoute);
     }
 
-    void onA2dpDeviceDisconnected() {
-        removeRoute(mBluetoothA2dpRoute);
-        mBluetoothA2dpRoute = null;
+    static void onA2dpDeviceDisconnected() {
+        removeRoute(sStatic.mBluetoothA2dpRoute);
+        sStatic.mBluetoothA2dpRoute = null;
     }
 
     /**
      * Information about a media route.
      */
-    public class RouteInfo {
+    public static class RouteInfo {
         CharSequence mName;
         private CharSequence mStatus;
         int mSupportedTypes;
         RouteGroup mGroup;
         final RouteCategory mCategory;
-        float mVolume;
+        Drawable mIcon;
 
         RouteInfo(RouteCategory category) {
             mCategory = category;
-            category.mRoutes.add(this);
         }
 
         /**
@@ -541,10 +541,13 @@
         }
 
         /**
-         * @return This route's current volume setting.
+         * Get the icon representing this route.
+         * This icon will be used in picker UIs if available.
+         *
+         * @return the icon representing this route or null if no icon is available
          */
-        public float getVolume() {
-            return mVolume;
+        public Drawable getIconDrawable() {
+            return mIcon;
         }
 
         void setStatusInt(CharSequence status) {
@@ -576,7 +579,8 @@
      *
      * @see MediaRouter.RouteInfo
      */
-    public class UserRouteInfo extends RouteInfo {
+    public static class UserRouteInfo extends RouteInfo {
+        RemoteControlClient mRcc;
 
         UserRouteInfo(RouteCategory category) {
             super(category);
@@ -600,12 +604,46 @@
         public void setStatus(CharSequence status) {
             setStatusInt(status);
         }
+
+        /**
+         * Set the RemoteControlClient responsible for reporting playback info for this
+         * user route.
+         *
+         * <p>If this route manages remote playback, the data exposed by this
+         * RemoteControlClient will be used to reflect and update information
+         * such as route volume info in related UIs.</p>
+         *
+         * @param rcc RemoteControlClient associated with this route
+         */
+        public void setRemoteControlClient(RemoteControlClient rcc) {
+            mRcc = rcc;
+        }
+
+        /**
+         * Set an icon that will be used to represent this route.
+         * The system may use this icon in picker UIs or similar.
+         *
+         * @param icon icon drawable to use to represent this route
+         */
+        public void setIconDrawable(Drawable icon) {
+            mIcon = icon;
+        }
+
+        /**
+         * Set an icon that will be used to represent this route.
+         * The system may use this icon in picker UIs or similar.
+         *
+         * @param resId Resource ID of an icon drawable to use to represent this route
+         */
+        public void setIconResource(int resId) {
+            setIconDrawable(sStatic.mResources.getDrawable(resId));
+        }
     }
 
     /**
      * Information about a route that consists of multiple other routes in a group.
      */
-    public class RouteGroup extends RouteInfo {
+    public static class RouteGroup extends RouteInfo {
         final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
         private boolean mUpdateName;
 
@@ -634,8 +672,10 @@
                             "(Route category=" + route.getCategory() +
                             " group category=" + mCategory + ")");
             }
+            final int at = mRoutes.size();
             mRoutes.add(route);
             mUpdateName = true;
+            dispatchRouteGrouped(route, this, at);
             routeUpdated();
         }
 
@@ -657,6 +697,7 @@
             }
             mRoutes.add(insertAt, route);
             mUpdateName = true;
+            dispatchRouteGrouped(route, this, insertAt);
             routeUpdated();
         }
 
@@ -672,6 +713,7 @@
             }
             mRoutes.remove(route);
             mUpdateName = true;
+            dispatchRouteUngrouped(route, this);
             routeUpdated();
         }
 
@@ -681,11 +723,49 @@
          * @param index index of the route to remove
          */
         public void removeRoute(int index) {
-            mRoutes.remove(index);
+            RouteInfo route = mRoutes.remove(index);
             mUpdateName = true;
+            dispatchRouteUngrouped(route, this);
             routeUpdated();
         }
 
+        /**
+         * @return The number of routes in this group
+         */
+        public int getRouteCount() {
+            return mRoutes.size();
+        }
+
+        /**
+         * Return the route in this group at the specified index
+         *
+         * @param index Index to fetch
+         * @return The route at index
+         */
+        public RouteInfo getRouteAt(int index) {
+            return mRoutes.get(index);
+        }
+
+        /**
+         * Set an icon that will be used to represent this group.
+         * The system may use this icon in picker UIs or similar.
+         *
+         * @param icon icon drawable to use to represent this group
+         */
+        public void setIconDrawable(Drawable icon) {
+            mIcon = icon;
+        }
+
+        /**
+         * Set an icon that will be used to represent this group.
+         * The system may use this icon in picker UIs or similar.
+         *
+         * @param resId Resource ID of an icon drawable to use to represent this group
+         */
+        public void setIconResource(int resId) {
+            setIconDrawable(sStatic.mResources.getDrawable(resId));
+        }
+
         void memberNameChanged(RouteInfo info, CharSequence name) {
             mUpdateName = true;
             routeUpdated();
@@ -711,8 +791,7 @@
     /**
      * Definition of a category of routes. All routes belong to a category.
      */
-    public class RouteCategory {
-        final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
+    public static class RouteCategory {
         CharSequence mName;
         int mTypes;
         final boolean mGroupable;
@@ -731,20 +810,33 @@
         }
 
         /**
-         * @return the number of routes in this category
-         */
-        public int getRouteCount() {
-            return mRoutes.size();
-        }
-
-        /**
-         * Return a route from this category
+         * Return the current list of routes in this category that have been added
+         * to the MediaRouter.
          *
-         * @param index Index from [0-getRouteCount)
-         * @return the route at the given index
+         * <p>This list will not include routes that are nested within RouteGroups.
+         * A RouteGroup is treated as a single route within its category.</p>
+         *
+         * @param out a List to fill with the routes in this category. If this parameter is
+         *            non-null, it will be cleared, filled with the current routes with this
+         *            category, and returned. If this parameter is null, a new List will be
+         *            allocated to report the category's current routes.
+         * @return A list with the routes in this category that have been added to the MediaRouter.
          */
-        public RouteInfo getRouteAt(int index) {
-            return mRoutes.get(index);
+        public List<RouteInfo> getRoutes(List<RouteInfo> out) {
+            if (out == null) {
+                out = new ArrayList<RouteInfo>();
+            } else {
+                out.clear();
+            }
+
+            final int count = getRouteCountStatic();
+            for (int i = 0; i < count; i++) {
+                final RouteInfo route = getRouteAtStatic(i);
+                if (route.mCategory == this) {
+                    out.add(route);
+                }
+            }
+            return out;
         }
 
         /**
@@ -758,7 +850,7 @@
          * Return whether or not this category supports grouping.
          *
          * <p>If this method returns true, all routes obtained from this category
-         * via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.
+         * via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.</p>
          *
          * @return true if this category supports
          */
@@ -768,17 +860,19 @@
 
         public String toString() {
             return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
-                    " groupable=" + mGroupable + " routes=" + mRoutes.size() + " }";
+                    " groupable=" + mGroupable + " routes=" + sStatic.mRoutes.size() + " }";
         }
     }
 
     static class CallbackInfo {
         public int type;
-        public Callback cb;
+        public final Callback cb;
+        public final MediaRouter router;
 
-        public CallbackInfo(Callback cb, int type) {
+        public CallbackInfo(Callback cb, int type, MediaRouter router) {
             this.cb = cb;
             this.type = type;
+            this.router = router;
         }
     }
 
@@ -797,43 +891,37 @@
          * Called when the supplied route becomes selected as the active route
          * for the given route type.
          *
+         * @param router the MediaRouter reporting the event
          * @param type Type flag set indicating the routes that have been selected
          * @param info Route that has been selected for the given route types
          */
-        public void onRouteSelected(int type, RouteInfo info);
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo info);
 
         /**
          * Called when the supplied route becomes unselected as the active route
          * for the given route type.
          *
+         * @param router the MediaRouter reporting the event
          * @param type Type flag set indicating the routes that have been unselected
          * @param info Route that has been unselected for the given route types
          */
-        public void onRouteUnselected(int type, RouteInfo info);
-
-        /**
-         * Called when the volume is changed for the specified route types.
-         *
-         * @param type Type flags indicating which volume type was changed
-         * @param volume New volume value in the range 0 (inaudible) to 1 (full)
-         */
-        public void onVolumeChanged(int type, float volume);
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
 
         /**
          * Called when a route for the specified type was added.
          *
-         * @param type Type flags indicating which types the added route supports
+         * @param router the MediaRouter reporting the event
          * @param info Route that has become available for use
          */
-        public void onRouteAdded(int type, RouteInfo info);
+        public void onRouteAdded(MediaRouter router, RouteInfo info);
 
         /**
          * Called when a route for the specified type was removed.
          *
-         * @param type Type flags indicating which types the removed route supported
+         * @param router the MediaRouter reporting the event
          * @param info Route that has been removed from availability
          */
-        public void onRouteRemoved(int type, RouteInfo info);
+        public void onRouteRemoved(MediaRouter router, RouteInfo info);
 
         /**
          * Called when an aspect of the indicated route has changed.
@@ -841,9 +929,29 @@
          * <p>This will not indicate that the types supported by this route have
          * changed, only that cosmetic info such as name or status have been updated.</p>
          *
+         * @param router the MediaRouter reporting the event
          * @param info The route that was changed
          */
-        public void onRouteChanged(RouteInfo info);
+        public void onRouteChanged(MediaRouter router, RouteInfo info);
+
+        /**
+         * Called when a route is added to a group.
+         *
+         * @param router the MediaRouter reporting the event
+         * @param info The route that was added
+         * @param group The group the route was added to
+         * @param index The route index within group that info was added at
+         */
+        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index);
+
+        /**
+         * Called when a route is removed from a group.
+         *
+         * @param router the MediaRouter reporting the event
+         * @param info The route that was removed
+         * @param group The group the route was removed from
+         */
+        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
     }
 
     /**
@@ -854,51 +962,34 @@
     public static class SimpleCallback implements Callback {
 
         @Override
-        public void onRouteSelected(int type, RouteInfo info) {
-
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
         }
 
         @Override
-        public void onRouteUnselected(int type, RouteInfo info) {
-
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
         }
 
         @Override
-        public void onVolumeChanged(int type, float volume) {
-
+        public void onRouteAdded(MediaRouter router, RouteInfo info) {
         }
 
         @Override
-        public void onRouteAdded(int type, RouteInfo info) {
-
+        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
         }
 
         @Override
-        public void onRouteRemoved(int type, RouteInfo info) {
-
+        public void onRouteChanged(MediaRouter router, RouteInfo info) {
         }
 
         @Override
-        public void onRouteChanged(RouteInfo info) {
-
+        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
+                int index) {
         }
 
-    }
-
-    class VolumeChangedBroadcastReceiver extends BroadcastReceiver {
         @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (AudioManager.VOLUME_CHANGED_ACTION.equals(action) &&
-                    AudioManager.STREAM_MUSIC == intent.getIntExtra(
-                            AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1)) {
-                final int maxVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-                final int volExtra = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
-                final float volume = (float) volExtra / maxVol;
-                mDefaultAudio.mVolume = volume;
-                dispatchVolumeChanged(ROUTE_TYPE_LIVE_AUDIO, volume);
-            }
+        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
         }
+
     }
 
     class BtChangedBroadcastReceiver extends BroadcastReceiver {
@@ -917,24 +1008,24 @@
         }
     }
 
-    class HeadphoneChangedBroadcastReceiver extends BroadcastReceiver {
+    static class HeadphoneChangedBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
             if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
                 final boolean plugged = intent.getIntExtra("state", 0) != 0;
-                final String name = mAppContext.getString(
+                final String name = sStatic.mResources.getString(
                         com.android.internal.R.string.default_audio_route_name_headphones);
                 onHeadphonesPlugged(plugged, name);
             } else if (Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG.equals(action) ||
                     Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG.equals(action)) {
                 final boolean plugged = intent.getIntExtra("state", 0) != 0;
-                final String name = mAppContext.getString(
+                final String name = sStatic.mResources.getString(
                         com.android.internal.R.string.default_audio_route_name_dock_speakers);
                 onHeadphonesPlugged(plugged, name);
             } else if (Intent.ACTION_HDMI_AUDIO_PLUG.equals(action)) {
                 final boolean plugged = intent.getIntExtra("state", 0) != 0;
-                final String name = mAppContext.getString(
+                final String name = sStatic.mResources.getString(
                         com.android.internal.R.string.default_audio_route_name_hdmi);
                 onHeadphonesPlugged(plugged, name);
             }
diff --git a/media/jni/mediaeditor/MODULE_LICENSE_APACHE2 b/media/jni/mediaeditor/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media/jni/mediaeditor/MODULE_LICENSE_APACHE2
diff --git a/media/jni/mediaeditor/NOTICE b/media/jni/mediaeditor/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/media/jni/mediaeditor/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/packages/SystemUI/res/drawable-hdpi/navbar_search_bg_scrim.9.png b/packages/SystemUI/res/drawable-hdpi/navbar_search_bg_scrim.9.png
deleted file mode 100644
index 4c163a2..0000000
--- a/packages/SystemUI/res/drawable-hdpi/navbar_search_bg_scrim.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/navbar_search_bg_scrim.9.png b/packages/SystemUI/res/drawable-mdpi/navbar_search_bg_scrim.9.png
deleted file mode 100644
index 21c5abd..0000000
--- a/packages/SystemUI/res/drawable-mdpi/navbar_search_bg_scrim.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/navbar_search_bg_scrim.9.png b/packages/SystemUI/res/drawable-xhdpi/navbar_search_bg_scrim.9.png
deleted file mode 100644
index 7874c63..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/navbar_search_bg_scrim.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
index 8a21117..00e3e27 100644
--- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
@@ -26,7 +26,11 @@
     android:layout_width="match_parent"
     systemui:recentItemLayout="@layout/status_bar_recent_item"
     >
-
+    <View
+        android:id="@+id/recents_transition_background"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:visibility="invisible" />
     <FrameLayout
         android:id="@+id/recents_bg_protect"
         android:background="@drawable/status_bar_recents_background"
diff --git a/packages/SystemUI/res/layout-land/status_bar_search_panel.xml b/packages/SystemUI/res/layout-land/status_bar_search_panel.xml
index ae81167..e6c0087 100644
--- a/packages/SystemUI/res/layout-land/status_bar_search_panel.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_search_panel.xml
@@ -38,25 +38,29 @@
             android:layout_height="match_parent"
             android:layout_alignParentRight="true">
 
-            <com.android.internal.widget.multiwaveview.MultiWaveView
-                android:id="@+id/multi_wave_view"
+            <com.android.internal.widget.multiwaveview.GlowPadView
+                android:id="@+id/glow_pad_view"
                 android:orientation="vertical"
                 android:layout_width="@dimen/navbar_search_panel_height"
                 android:layout_height="match_parent"
                 android:layout_alignParentBottom="true"
-                android:background="@drawable/navbar_search_bg_scrim"
                 android:gravity="left"
 
                 prvandroid:targetDrawables="@array/navbar_search_targets"
                 prvandroid:targetDescriptions="@array/navbar_search_target_descriptions"
                 prvandroid:directionDescriptions="@array/navbar_search_direction_descriptions"
                 prvandroid:handleDrawable="@drawable/navbar_search_handle"
-                prvandroid:waveDrawable="@drawable/navbar_search_outerring"
+                prvandroid:outerRingDrawable="@drawable/navbar_search_outerring"
+                prvandroid:outerRadius="@dimen/navbar_search_outerring_radius"
+                prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
                 prvandroid:snapMargin="@dimen/navbar_search_snap_margin"
                 prvandroid:hitRadius="@dimen/navbar_search_hit_radius"
                 prvandroid:feedbackCount="0"
                 prvandroid:vibrationDuration="@integer/config_vibration_duration"
-                prvandroid:alwaysTrackFinger="true"/>
+                prvandroid:alwaysTrackFinger="true"
+                prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius"
+                prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"
+                />
 
         </RelativeLayout>
 
diff --git a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
index 1d29c5a..a7e5db1 100644
--- a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml
@@ -26,7 +26,11 @@
     android:layout_width="match_parent"
     systemui:recentItemLayout="@layout/status_bar_recent_item"
     >
-
+    <View
+        android:id="@+id/recents_transition_background"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:visibility="invisible" />
     <FrameLayout
         android:id="@+id/recents_bg_protect"
         android:background="@drawable/status_bar_recents_background"
diff --git a/packages/SystemUI/res/layout-port/status_bar_search_panel.xml b/packages/SystemUI/res/layout-port/status_bar_search_panel.xml
index 785d5dd..3828136 100644
--- a/packages/SystemUI/res/layout-port/status_bar_search_panel.xml
+++ b/packages/SystemUI/res/layout-port/status_bar_search_panel.xml
@@ -38,25 +38,29 @@
             android:layout_height="wrap_content"
             android:layout_alignParentBottom="true">
 
-            <com.android.internal.widget.multiwaveview.MultiWaveView
-                android:id="@+id/multi_wave_view"
+            <com.android.internal.widget.multiwaveview.GlowPadView
+                android:id="@+id/glow_pad_view"
                 android:orientation="horizontal"
                 android:layout_width="match_parent"
                 android:layout_height="@dimen/navbar_search_panel_height"
                 android:layout_alignParentBottom="true"
-                android:background="@drawable/navbar_search_bg_scrim"
                 android:gravity="top"
 
                 prvandroid:targetDrawables="@array/navbar_search_targets"
                 prvandroid:targetDescriptions="@array/navbar_search_target_descriptions"
                 prvandroid:directionDescriptions="@array/navbar_search_direction_descriptions"
                 prvandroid:handleDrawable="@drawable/navbar_search_handle"
-                prvandroid:waveDrawable="@drawable/navbar_search_outerring"
+                prvandroid:outerRingDrawable="@drawable/navbar_search_outerring"
+                prvandroid:outerRadius="@dimen/navbar_search_outerring_radius"
+                prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
                 prvandroid:snapMargin="@dimen/navbar_search_snap_margin"
                 prvandroid:hitRadius="@dimen/navbar_search_hit_radius"
                 prvandroid:feedbackCount="0"
                 prvandroid:vibrationDuration="@integer/config_vibration_duration"
-                prvandroid:alwaysTrackFinger="true"/>
+                prvandroid:alwaysTrackFinger="true"
+                prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius"
+                prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"
+               />
 
         </RelativeLayout>
 
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml
index 74a15f2..c17f858 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_search_panel.xml
@@ -25,23 +25,26 @@
     android:layout_height="match_parent"
     android:layout_width="match_parent">
 
-    <com.android.internal.widget.multiwaveview.MultiWaveView
-        android:id="@+id/multi_wave_view"
+    <com.android.internal.widget.multiwaveview.GlowPadView
+        android:id="@+id/glow_pad_view"
         android:layout_width="wrap_content"
         android:layout_height="@dimen/navbar_search_panel_height"
         android:layout_gravity="center_horizontal|bottom"
         android:gravity="center_horizontal|top"
-        android:background="@drawable/navbar_search_bg_scrim"
 
         prvandroid:targetDrawables="@array/navbar_search_targets"
         prvandroid:targetDescriptions="@array/navbar_search_target_descriptions"
         prvandroid:directionDescriptions="@array/navbar_search_direction_descriptions"
         prvandroid:handleDrawable="@drawable/navbar_search_handle"
-        prvandroid:waveDrawable="@drawable/navbar_search_outerring"
+        prvandroid:outerRingDrawable="@drawable/navbar_search_outerring"
+        prvandroid:outerRadius="@dimen/navbar_search_outerring_radius"
+        prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
         prvandroid:snapMargin="@dimen/navbar_search_snap_margin"
         prvandroid:hitRadius="@dimen/navbar_search_hit_radius"
         prvandroid:feedbackCount="0"
         prvandroid:vibrationDuration="@integer/config_vibration_duration"
-        prvandroid:alwaysTrackFinger="true"/>
+        prvandroid:alwaysTrackFinger="true"
+        prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius"
+        prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"/>
 
 </com.android.systemui.SearchPanelView>
diff --git a/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml b/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml
index 2a97307..100f81d 100644
--- a/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml
+++ b/packages/SystemUI/res/layout-sw720dp/status_bar_search_panel.xml
@@ -25,24 +25,27 @@
     android:layout_height="match_parent"
     android:layout_width="match_parent">
 
-    <com.android.internal.widget.multiwaveview.MultiWaveView
-        android:id="@+id/multi_wave_view"
+    <com.android.internal.widget.multiwaveview.GlowPadView
+        android:id="@+id/glow_pad_view"
         android:layout_width="wrap_content"
         android:layout_height="@dimen/navbar_search_panel_height"
         android:layout_gravity="left|bottom"
         android:gravity="top|right"
         android:layout_marginLeft="-150dip"
-        android:background="@drawable/navbar_search_bg_scrim"
 
         prvandroid:targetDrawables="@array/navbar_search_targets"
         prvandroid:targetDescriptions="@array/navbar_search_target_descriptions"
         prvandroid:directionDescriptions="@array/navbar_search_direction_descriptions"
         prvandroid:handleDrawable="@drawable/navbar_search_handle"
-        prvandroid:waveDrawable="@drawable/navbar_search_outerring"
+        prvandroid:outerRingDrawable="@drawable/navbar_search_outerring"
+        prvandroid:outerRadius="@dimen/navbar_search_outerring_radius"
+        prvandroid:innerRadius="@*android:dimen/glowpadview_inner_radius"
         prvandroid:snapMargin="@dimen/navbar_search_snap_margin"
         prvandroid:hitRadius="@dimen/navbar_search_hit_radius"
         prvandroid:feedbackCount="0"
         prvandroid:vibrationDuration="@integer/config_vibration_duration"
-        prvandroid:alwaysTrackFinger="true"/>
+        prvandroid:alwaysTrackFinger="true"
+        prvandroid:glowRadius="@*android:dimen/glowpadview_glow_radius"
+        prvandroid:pointDrawable="@*android:drawable/ic_lockscreen_glowdot"/>
 
 </com.android.systemui.SearchPanelView>
diff --git a/packages/SystemUI/res/layout/system_bar_recent_panel.xml b/packages/SystemUI/res/layout/system_bar_recent_panel.xml
index d5745c8..127551d 100644
--- a/packages/SystemUI/res/layout/system_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout/system_bar_recent_panel.xml
@@ -28,7 +28,11 @@
     android:clipChildren="false"
     systemui:recentItemLayout="@layout/system_bar_recent_item"
     >
-
+    <View
+        android:id="@+id/recents_transition_background"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:visibility="invisible" />
     <FrameLayout
         android:id="@+id/recents_bg_protect"
         android:background="@drawable/recents_bg_protect_tile"
@@ -38,7 +42,6 @@
         android:layout_marginBottom="@*android:dimen/system_bar_height"
         android:clipToPadding="false"
         android:clipChildren="false">
-
         <ImageView
             android:id="@+id/recents_transition_placeholder_icon"
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 1bf59b0..2b5248f 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -30,6 +30,9 @@
     <!-- Diameter of outer shape drawable shown in navbar search-->
     <dimen name="navbar_search_outerring_diameter">430dip</dimen>
 
+    <!-- Diameter of outer shape drawable shown in navbar search. Should be 1/2 of above value. -->
+    <dimen name="navbar_search_outerring_radius">215dip</dimen>
+
     <!-- Height of search panel including navigation bar height -->
     <dimen name="navbar_search_panel_height">280dip</dimen>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3ed63ed..c88ae2a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -87,6 +87,8 @@
     <dimen name="fling_collapse_min_velocity">200dp</dimen>
     <!-- Cap on contribution of x dimension of gesture to overall velocity -->
     <dimen name="fling_gesture_max_x_velocity">200dp</dimen>
+    <!-- Cap on overall resulting fling speed (s^-1) -->
+    <dimen name="fling_gesture_max_output_velocity">3000dp</dimen>
 
     <!-- Minimum fraction of the display a gesture must travel, at any velocity, to qualify as a
          collapse request -->
@@ -117,6 +119,9 @@
     <!-- Diameter of outer shape drawable shown in navbar search-->
     <dimen name="navbar_search_outerring_diameter">340dp</dimen>
 
+    <!-- Diameter of outer shape drawable shown in navbar search. Should be 1/2 of above value -->
+    <dimen name="navbar_search_outerring_radius">170dp</dimen>
+
     <!-- Threshold for swipe-up gesture to activate search dialog -->
     <dimen name="navbar_search_up_threshhold">40dip</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
index 8b8a814..923bcba 100644
--- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java
@@ -35,8 +35,8 @@
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.widget.FrameLayout;
-import com.android.internal.widget.multiwaveview.MultiWaveView;
-import com.android.internal.widget.multiwaveview.MultiWaveView.OnTriggerListener;
+import com.android.internal.widget.multiwaveview.GlowPadView;
+import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
 import com.android.systemui.R;
 import com.android.systemui.recent.StatusBarTouchProxy;
 import com.android.systemui.statusbar.BaseStatusBar;
@@ -58,7 +58,7 @@
 
     private boolean mShowing;
     private View mSearchTargetsContainer;
-    private MultiWaveView mMultiWaveView;
+    private GlowPadView mGlowPadView;
 
     public SearchPanelView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -125,7 +125,7 @@
         }
     }
 
-    class MultiWaveTriggerListener implements MultiWaveView.OnTriggerListener {
+    class GlowPadTriggerListener implements GlowPadView.OnTriggerListener {
         boolean mWaitingForLaunch;
 
         public void onGrabbed(View v, int handle) {
@@ -141,7 +141,7 @@
         }
 
         public void onTrigger(View v, final int target) {
-            final int resId = mMultiWaveView.getResourceIdForTarget(target);
+            final int resId = mGlowPadView.getResourceIdForTarget(target);
             switch (resId) {
                 case com.android.internal.R.drawable.ic_lockscreen_search:
                     mWaitingForLaunch = true;
@@ -154,13 +154,13 @@
         public void onFinishFinalAnimation() {
         }
     }
-    final MultiWaveTriggerListener mMultiWaveViewListener = new MultiWaveTriggerListener();
+    final GlowPadTriggerListener mGlowPadViewListener = new GlowPadTriggerListener();
 
     @Override
     public void onAnimationStarted() {
         postDelayed(new Runnable() {
             public void run() {
-                mMultiWaveViewListener.mWaitingForLaunch = false;
+                mGlowPadViewListener.mWaitingForLaunch = false;
                 mBar.hideSearchPanel();
             }
         }, SEARCH_PANEL_HOLD_DURATION);
@@ -173,13 +173,13 @@
         mSearchTargetsContainer = findViewById(R.id.search_panel_container);
         mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy);
         // TODO: fetch views
-        mMultiWaveView = (MultiWaveView) findViewById(R.id.multi_wave_view);
-        mMultiWaveView.setOnTriggerListener(mMultiWaveViewListener);
+        mGlowPadView = (GlowPadView) findViewById(R.id.glow_pad_view);
+        mGlowPadView.setOnTriggerListener(mGlowPadViewListener);
         SearchManager searchManager = getSearchManager();
         if (searchManager != null) {
             ComponentName component = searchManager.getGlobalSearchActivity();
             if (component != null) {
-                if (!mMultiWaveView.replaceTargetDrawablesIfPresent(component,
+                if (!mGlowPadView.replaceTargetDrawablesIfPresent(component,
                         ASSIST_ICON_METADATA_NAME,
                         com.android.internal.R.drawable.ic_lockscreen_search)) {
                     Slog.w(TAG, "Couldn't grab icon from component " + component);
@@ -214,7 +214,7 @@
     private final OnPreDrawListener mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
         public boolean onPreDraw() {
             getViewTreeObserver().removeOnPreDrawListener(this);
-            mMultiWaveView.resumeAnimations();
+            mGlowPadView.resumeAnimations();
             return false;
         }
     };
@@ -240,7 +240,8 @@
                 setVisibility(View.VISIBLE);
                 // Don't start the animation until we've created the layer, which is done
                 // right before we are drawn
-                mMultiWaveView.suspendAnimations();
+                mGlowPadView.suspendAnimations();
+                mGlowPadView.ping();
                 getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
                 vibrate();
             }
@@ -299,7 +300,7 @@
     public void setStatusBarView(final View statusBarView) {
         if (mStatusBarTouchProxy != null) {
             mStatusBarTouchProxy.setStatusBar(statusBarView);
-//            mMultiWaveView.setOnTouchListener(new OnTouchListener() {
+//            mGlowPadView.setOnTouchListener(new OnTouchListener() {
 //                public boolean onTouch(View v, MotionEvent event) {
 //                    return statusBarView.onTouchEvent(event);
 //                }
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
index ccdf038..296b640 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
@@ -20,15 +20,19 @@
 import android.animation.AnimatorSet;
 import android.animation.AnimatorSet.Builder;
 import android.animation.ObjectAnimator;
+import android.content.res.Resources;
+import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.util.Slog;
 import android.view.View;
 import android.view.ViewRootImpl;
 
+import com.android.systemui.R;
+
 /* package */ class Choreographer implements Animator.AnimatorListener {
     // should group this into a multi-property animation
     private static final int OPEN_DURATION = 136;
-    private static final int CLOSE_DURATION = 130;
+    private static final int CLOSE_DURATION = 230;
     private static final int SCRIM_DURATION = 400;
     private static final String TAG = RecentsPanelView.TAG;
     private static final boolean DEBUG = RecentsPanelView.DEBUG;
@@ -81,7 +85,7 @@
                 mContentView.getAlpha(), appearing ? 1.0f : 0.0f);
         fadeAnim.setInterpolator(appearing
                 ? new android.view.animation.AccelerateInterpolator(1.0f)
-                : new android.view.animation.DecelerateInterpolator(1.0f));
+                : new android.view.animation.AccelerateInterpolator(2.5f));
         fadeAnim.setDuration(appearing ? OPEN_DURATION : CLOSE_DURATION);
 
         Animator noRecentAppsFadeAnim = null;
@@ -110,6 +114,20 @@
                 bgAnim.setDuration(appearing ? SCRIM_DURATION : CLOSE_DURATION);
                 builder.with(bgAnim);
             }
+        } else {
+            final Resources res = mRootView.getResources();
+            boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
+            if (!isTablet) {
+                View recentsTransitionBackground =
+                        mRootView.findViewById(R.id.recents_transition_background);
+                recentsTransitionBackground.setVisibility(View.VISIBLE);
+                Drawable bgDrawable = new ColorDrawable(0xFF000000);
+                recentsTransitionBackground.setBackground(bgDrawable);
+                Animator bgAnim = ObjectAnimator.ofInt(bgDrawable, "alpha", 0, 255);
+                bgAnim.setDuration(CLOSE_DURATION);
+                bgAnim.setInterpolator(new android.view.animation.AccelerateInterpolator(1f));
+                builder.with(bgAnim);
+            }
         }
         mContentAnim.addListener(this);
         if (mListener != null) {
@@ -139,6 +157,10 @@
         if (mScrimView.getBackground() != null) {
             mScrimView.getBackground().setAlpha(appearing ? 255 : 0);
         }
+        View recentsTransitionBackground =
+                mRootView.findViewById(R.id.recents_transition_background);
+        recentsTransitionBackground.setVisibility(View.INVISIBLE);
+        mRootView.requestLayout();
     }
 
     public void setPanelHeight(int h) {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 34cd397..bec9aa2 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -34,11 +34,13 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Display;
 import android.view.KeyEvent;
+import android.view.IWindowManager;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
@@ -86,7 +88,8 @@
     OnRecentsPanelVisibilityChangedListener mVisibilityChangedListener;
 
     ImageView mPlaceholderThumbnail;
-    boolean mHideWindowAfterPlaceholderThumbnailIsHidden;
+    View mTransitionBg;
+    boolean mHideRecentsAfterThumbnailScaleUpStarted;
 
     private RecentTasksLoader mRecentTasksLoader;
     private ArrayList<TaskDescription> mRecentTaskDescriptions;
@@ -97,6 +100,7 @@
     private boolean mFitThumbnailToXY;
     private int mRecentItemLayoutId;
     private boolean mFirstScreenful = true;
+    private boolean mHighEndGfx;
 
     public static interface OnRecentsPanelVisibilityChangedListener {
         public void onRecentsPanelVisibilityChanged(boolean visible);
@@ -248,7 +252,7 @@
     @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK && !event.isCanceled()) {
-            show(false, true);
+            show(false, false);
             return true;
         }
         return super.onKeyUp(keyCode, event);
@@ -305,10 +309,6 @@
             ArrayList<TaskDescription> recentTaskDescriptions, boolean firstScreenful) {
         sendCloseSystemWindows(mContext, BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
 
-        // For now, disable animations. We may want to re-enable in the future
-        if (show) {
-            animate = false;
-        }
         if (show) {
             // Need to update list of recent apps before we set visibility so this view's
             // content description is updated before it gets focus for TalkBack mode
@@ -318,6 +318,7 @@
             // quit early
             boolean noApps = !mFirstScreenful && (mRecentTaskDescriptions.size() == 0);
             if (mRecentsNoApps != null) {
+                mRecentsNoApps.setAlpha(1f);
                 mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE);
             } else {
                 if (noApps) {
@@ -339,7 +340,6 @@
             mRecentTasksDirty = true;
             mWaitingToShow = false;
             mReadyToShow = false;
-            mRecentsNoApps.setVisibility(View.INVISIBLE);
         }
         if (animate) {
             if (mShowing != show) {
@@ -488,7 +488,8 @@
         if (mRecentsScrim != null) {
             Display d = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
                 .getDefaultDisplay();
-            if (!ActivityManager.isHighEndGfx(d)) {
+            mHighEndGfx = ActivityManager.isHighEndGfx(d);
+            if (!mHighEndGfx) {
                 mRecentsScrim.setBackground(null);
             } else if (mRecentsScrim.getBackground() instanceof BitmapDrawable) {
                 // In order to save space, we make the background texture repeat in the Y direction
@@ -704,22 +705,57 @@
         setContentDescription(recentAppsAccessibilityDescription);
     }
 
+
+    boolean mThumbnailScaleUpStarted;
     public void handleOnClick(View view) {
         ViewHolder holder = (ViewHolder)view.getTag();
         TaskDescription ad = holder.taskDescription;
         final Context context = view.getContext();
         final ActivityManager am = (ActivityManager)
                 context.getSystemService(Context.ACTIVITY_SERVICE);
-        holder.thumbnailViewImage.setDrawingCacheEnabled(true);
-        Bitmap bm = holder.thumbnailViewImage.getDrawingCache();
-        mPlaceholderThumbnail = (ImageView) findViewById(R.id.recents_transition_placeholder_icon);
+        Bitmap bm = holder.thumbnailViewImageBitmap;
+        boolean usingDrawingCache;
+        if (bm.getWidth() == holder.thumbnailViewImage.getWidth() &&
+                bm.getHeight() == holder.thumbnailViewImage.getHeight()) {
+            usingDrawingCache = false;
+        } else {
+            holder.thumbnailViewImage.setDrawingCacheEnabled(true);
+            bm = holder.thumbnailViewImage.getDrawingCache();
+            usingDrawingCache = true;
+        }
+
+        if (mPlaceholderThumbnail == null) {
+            mPlaceholderThumbnail =
+                    (ImageView) findViewById(R.id.recents_transition_placeholder_icon);
+        }
+        if (mTransitionBg == null) {
+            mTransitionBg = (View) findViewById(R.id.recents_transition_background);
+
+            IWindowManager wm = IWindowManager.Stub.asInterface(
+                    ServiceManager.getService(Context.WINDOW_SERVICE));
+            try {
+                if (!wm.hasSystemNavBar()) {
+                    FrameLayout.LayoutParams lp =
+                            (FrameLayout.LayoutParams) mTransitionBg.getLayoutParams();
+                    int statusBarHeight = getResources().
+                            getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+                    lp.setMargins(0, statusBarHeight, 0, 0);
+                    mTransitionBg.setLayoutParams(lp);
+                }
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failing checking whether status bar is visible", e);
+            }
+        }
 
         final ImageView placeholderThumbnail = mPlaceholderThumbnail;
-        mHideWindowAfterPlaceholderThumbnailIsHidden = false;
+        mHideRecentsAfterThumbnailScaleUpStarted = false;
         placeholderThumbnail.setVisibility(VISIBLE);
-        Bitmap b2 = bm.copy(bm.getConfig(), true);
-        placeholderThumbnail.setImageBitmap(b2);
-
+        if (!usingDrawingCache) {
+            placeholderThumbnail.setImageBitmap(bm);
+        } else {
+            Bitmap b2 = bm.copy(bm.getConfig(), true);
+            placeholderThumbnail.setImageBitmap(b2);
+        }
         Rect r = new Rect();
         holder.thumbnailViewImage.getGlobalVisibleRect(r);
 
@@ -728,13 +764,16 @@
 
         show(false, true);
 
+        mThumbnailScaleUpStarted = false;
         ActivityOptions opts = ActivityOptions.makeDelayedThumbnailScaleUpAnimation(
                 holder.thumbnailViewImage, bm, 0, 0,
                 new ActivityOptions.OnAnimationStartedListener() {
                     @Override public void onAnimationStarted() {
-                        mPlaceholderThumbnail = null;
-                        placeholderThumbnail.setVisibility(INVISIBLE);
-                        if (mHideWindowAfterPlaceholderThumbnailIsHidden) {
+                        mThumbnailScaleUpStarted = true;
+                        if (!mHighEndGfx) {
+                            mPlaceholderThumbnail.setVisibility(INVISIBLE);
+                        }
+                        if (mHideRecentsAfterThumbnailScaleUpStarted) {
                             hideWindow();
                         }
                     }
@@ -751,15 +790,19 @@
             if (DEBUG) Log.v(TAG, "Starting activity " + intent);
             context.startActivity(intent, opts.toBundle());
         }
-        holder.thumbnailViewImage.setDrawingCacheEnabled(false);
+        if (!usingDrawingCache) {
+            holder.thumbnailViewImage.setDrawingCacheEnabled(false);
+        }
     }
 
     public void hideWindow() {
-        if (mPlaceholderThumbnail != null) {
-            mHideWindowAfterPlaceholderThumbnailIsHidden = true;
+        if (!mThumbnailScaleUpStarted) {
+            mHideRecentsAfterThumbnailScaleUpStarted = true;
         } else {
             setVisibility(GONE);
-            mHideWindowAfterPlaceholderThumbnailIsHidden = false;
+            mTransitionBg.setVisibility(INVISIBLE);
+            mPlaceholderThumbnail.setVisibility(INVISIBLE);
+            mHideRecentsAfterThumbnailScaleUpStarted = false;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
index d387515..e802985 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
@@ -76,9 +76,6 @@
     }
 
     private void addToRecycledViews(View v) {
-        if (mRecycledViews.contains(v)) {
-            throw new RuntimeException("Child was already recycled");
-        }
         if (mRecycledViews.size() < mNumItemsInOneScreenful) {
             mRecycledViews.add(v);
         }
@@ -105,9 +102,6 @@
                 old = recycledViews.next();
                 recycledViews.remove();
                 old.setVisibility(VISIBLE);
-                if (old.getParent() != null) {
-                    throw new RuntimeException("Recycled child has parent (i: " + i + ", recycled i: " + mRecycledViews.size());
-                }
             }
             final View view = mAdapter.getView(i, old, mLinearLayout);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index cf2690b..750c6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -440,13 +440,13 @@
              case MSG_OPEN_RECENTS_PANEL:
                   if (DEBUG) Slog.d(TAG, "opening recents panel");
                   if (mRecentsPanel != null) {
-                      mRecentsPanel.show(true, true);
+                      mRecentsPanel.show(true, false);
                   }
                   break;
              case MSG_CLOSE_RECENTS_PANEL:
                   if (DEBUG) Slog.d(TAG, "closing recents panel");
                   if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
-                      mRecentsPanel.show(false, true);
+                      mRecentsPanel.show(false, false);
                   }
                   break;
              case MSG_PRELOAD_RECENT_APPS:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CarrierLabel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CarrierLabel.java
index d8441f2..491fad1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CarrierLabel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CarrierLabel.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.provider.Telephony;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Slog;
 import android.view.View;
@@ -91,24 +92,20 @@
             Slog.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn
                     + " showPlmn=" + showPlmn + " plmn=" + plmn);
         }
-        StringBuilder str = new StringBuilder();
-        boolean something = false;
-        if (showPlmn && plmn != null) {
-            str.append(plmn);
-            something = true;
-        }
-        if (showSpn && spn != null) {
-            if (something) {
-                str.append('\n');
-            }
-            str.append(spn);
-            something = true;
-        }
-        if (something) {
-            setText(str.toString());
+        final String str;
+        // match logic in KeyguardStatusViewManager
+        final boolean plmnValid = showPlmn && !TextUtils.isEmpty(plmn);
+        final boolean spnValid = showSpn && !TextUtils.isEmpty(spn);
+        if (plmnValid && spnValid) {
+            str = plmn + "|" + spn;
+        } else if (plmnValid) {
+            str = plmn;
+        } else if (spnValid) {
+            str = spn;
         } else {
-            setText(com.android.internal.R.string.lockscreen_carrier_default);
+            str = "";
         }
+        setText(str);
     }
 
     
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 87e95da..56de506 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -134,6 +134,9 @@
     private float mExpandAccelPx; // classic value: 2000px/s/s
     private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
 
+    private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little 
+                                                    // faster than mSelfCollapseVelocityPx)
+
     PhoneStatusBarPolicy mIconPolicy;
 
     // These are no longer handled by the policy, because we need custom strategies for them
@@ -165,6 +168,7 @@
     ScrollView mScrollView;
     View mExpandedContents;
     int mNotificationPanelMarginBottomPx, mNotificationPanelMarginLeftPx;
+    final Rect mNotificationPanelBackgroundPadding = new Rect();
     int mNotificationPanelGravity;
     int mNotificationPanelMinHeight;
 
@@ -267,6 +271,13 @@
         }
     };
 
+    private final Runnable mPerformSelfExpandFling = new Runnable() {
+        @Override
+        public void run() {
+            performFling(0, mSelfExpandVelocityPx, true);
+        }
+    };
+
     private final Runnable mPerformFling = new Runnable() {
         @Override
         public void run() {
@@ -392,12 +403,13 @@
         mTickerView = mStatusBarView.findViewById(R.id.ticker);
 
         mPile = (NotificationRowLayout)mStatusBarWindow.findViewById(R.id.latestItems);
+        mPile.setLayoutTransitionsEnabled(false);
         mPile.setLongPressListener(getNotificationLongClicker());
         if (SHOW_CARRIER_LABEL) {
             mPile.setOnSizeChangedListener(new OnSizeChangedListener() {
                 @Override
                 public void onSizeChanged(View view, int w, int h, int oldw, int oldh) {
-                    updateCarrierLabelVisibility();
+                    updateCarrierLabelVisibility(false);
                 }
             });
         }
@@ -443,12 +455,14 @@
 
         mNetworkController.addSignalCluster(signalCluster);
         signalCluster.setNetworkController(mNetworkController);
-        
-        // for wifi-only devices, we show SSID; otherwise, we show PLMN
-        if (mNetworkController.hasMobileDataFeature()) {
-            mNetworkController.addMobileLabelView(mCarrierLabel);
-        } else {
-            mNetworkController.addWifiLabelView(mCarrierLabel);
+
+        if (SHOW_CARRIER_LABEL) {
+            // for wifi-only devices, we show SSID; otherwise, we show PLMN
+            if (mNetworkController.hasMobileDataFeature()) {
+                mNetworkController.addMobileLabelView(mCarrierLabel);
+            } else {
+                mNetworkController.addWifiLabelView(mCarrierLabel);
+            }
         }
 
 //        final ImageView wimaxRSSI =
@@ -889,7 +903,7 @@
         }
     }
 
-    protected void updateCarrierLabelVisibility() {
+    protected void updateCarrierLabelVisibility(boolean force) {
         if (!SHOW_CARRIER_LABEL) return;
         // The idea here is to only show the carrier label when there is enough room to see it, 
         // i.e. when there aren't enough notifications to fill the panel.
@@ -901,7 +915,7 @@
         final boolean makeVisible = 
             mPile.getHeight() < (mScrollView.getHeight() - mCarrierLabelHeight);
         
-        if (mCarrierLabelVisible != makeVisible) {
+        if (force || mCarrierLabelVisible != makeVisible) {
             mCarrierLabelVisible = makeVisible;
             if (DEBUG) {
                 Slog.d(TAG, "making carrier label " + (makeVisible?"visible":"invisible"));
@@ -986,7 +1000,7 @@
                 .start();
         }
 
-        updateCarrierLabelVisibility();
+        updateCarrierLabelVisibility(false);
     }
 
     public void showClock(boolean show) {
@@ -1159,9 +1173,10 @@
         }
 
         mExpandedVisible = true;
+        mPile.setLayoutTransitionsEnabled(true);
         makeSlippery(mNavigationBarView, true);
 
-        updateCarrierLabelVisibility();
+        updateCarrierLabelVisibility(true);
 
         updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
 
@@ -1206,7 +1221,7 @@
         }
 
         prepareTracking(0, true);
-        performFling(0, mSelfExpandVelocityPx, true);
+        mHandler.post(mPerformSelfExpandFling);
     }
 
     public void animateCollapse() {
@@ -1279,6 +1294,7 @@
             return;
         }
         mExpandedVisible = false;
+        mPile.setLayoutTransitionsEnabled(false);
         visibilityChanged(false);
         makeSlippery(mNavigationBarView, false);
 
@@ -1521,12 +1537,15 @@
                 mViewDelta = statusBarSize - y;
             } else {
                 mCloseView.getLocationOnScreen(mAbsPos);
-                mViewDelta = mAbsPos[1] + statusBarSize + getCloseViewHeight() - y; // XXX: not closeViewHeight, but paddingBottom from the 9patch
+                mViewDelta = mAbsPos[1]
+                           + getCloseViewHeight() // XXX: not closeViewHeight, but paddingBottom from the 9patch
+                           + mNotificationPanelBackgroundPadding.top
+                           + mNotificationPanelBackgroundPadding.bottom
+                           - y;
             }
             if ((!mExpanded && y < hitSize) ||
                     // @@ add taps outside the panel if it's not full-screen
                     (mExpanded && y > (getExpandedViewMaxHeight()-hitSize))) {
-
                 // We drop events at the edge of the screen to make the windowshade come
                 // down by accident less, especially when pushing open a device with a keyboard
                 // that rotates (like g1 and droid)
@@ -1562,6 +1581,9 @@
                 }
 
                 float vel = (float)Math.hypot(yVel, xVel);
+                if (vel > mFlingGestureMaxOutputVelocityPx) {
+                    vel = mFlingGestureMaxOutputVelocityPx;
+                }
                 if (negative) {
                     vel = -vel;
                 }
@@ -1943,9 +1965,7 @@
                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                 PixelFormat.TRANSLUCENT);
 
-        if (ActivityManager.isHighEndGfx(mDisplay)) {
-            lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-        }
+        lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 
         lp.gravity = getStatusBarGravity();
         lp.setTitle("StatusBar");
@@ -2041,7 +2061,7 @@
             mStatusBarWindow.setBackgroundColor(color);
         }
         
-        updateCarrierLabelVisibility();
+        updateCarrierLabelVisibility(false);
     }
 
     void updateDisplaySize() {
@@ -2268,6 +2288,8 @@
 
         mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
 
+        mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
+
         mNotificationPanelMarginBottomPx
             = (int) res.getDimension(R.dimen.notification_panel_margin_bottom);
         mNotificationPanelMarginLeftPx
@@ -2276,10 +2298,12 @@
         if (mNotificationPanelGravity <= 0) {
             mNotificationPanelGravity = Gravity.CENTER_VERTICAL | Gravity.TOP;
         }
+        getNinePatchPadding(res.getDrawable(R.drawable.notification_panel_bg), mNotificationPanelBackgroundPadding);
         final int notificationPanelDecorationHeight =
               res.getDimensionPixelSize(R.dimen.notification_panel_padding_top)
             + res.getDimensionPixelSize(R.dimen.notification_panel_header_height)
-            + getNinePatchPadding(res.getDrawable(R.drawable.notification_panel_bg)).bottom;
+            + mNotificationPanelBackgroundPadding.top
+            + mNotificationPanelBackgroundPadding.bottom;
         mNotificationPanelMinHeight = 
               notificationPanelDecorationHeight 
             + res.getDimensionPixelSize(R.dimen.close_handle_underlap);
@@ -2289,13 +2313,11 @@
         if (false) Slog.v(TAG, "updateResources");
     }
 
-    private static Rect getNinePatchPadding(Drawable d) {
-        Rect padding = new Rect();
+    private static void getNinePatchPadding(Drawable d, Rect outPadding) {
         if (d instanceof NinePatchDrawable) {
             NinePatchDrawable ninePatch = (NinePatchDrawable) d;
-            ninePatch.getPadding(padding);
+            ninePatch.getPadding(outPadding);
         }
-        return padding;
     }
 
     //
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
index 42db8cf..61e5ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
@@ -68,6 +68,8 @@
     // animation is done
     boolean mRemoveViews = true;
 
+    private LayoutTransition mRealLayoutTransition;
+
     public NotificationRowLayout(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -75,7 +77,8 @@
     public NotificationRowLayout(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        setLayoutTransition(new LayoutTransition());
+        mRealLayoutTransition = new LayoutTransition();
+        setLayoutTransitionsEnabled(true);
         
         setOrientation(LinearLayout.VERTICAL);
 
@@ -121,9 +124,9 @@
 
     private void logLayoutTransition() {
         Log.v(TAG, "layout " +
-              (getLayoutTransition().isChangingLayout() ? "is " : "is not ") +
+              (mRealLayoutTransition.isChangingLayout() ? "is " : "is not ") +
               "in transition and animations " +
-              (getLayoutTransition().isRunning() ? "are " : "are not ") +
+              (mRealLayoutTransition.isRunning() ? "are " : "are not ") +
               "running.");
     }
 
@@ -225,6 +228,18 @@
         mRemoveViews = removeViews;
     }
 
+    // Suppress layout transitions for a little while.
+    public void setLayoutTransitionsEnabled(boolean b) {
+        if (b) {
+            setLayoutTransition(mRealLayoutTransition);
+        } else {
+            if (mRealLayoutTransition.isRunning()) {
+                mRealLayoutTransition.cancel();
+            }
+            setLayoutTransition(null);
+        }
+    }
+
     public void dismissRowAnimated(View child) {
         dismissRowAnimated(child, 0);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
index 71657f5..d8166f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationPanel.java
@@ -104,8 +104,6 @@
         mClearButton.setOnClickListener(mClearButtonListener);
 
         mShowing = false;
-
-        setContentFrameVisible(mNotificationCount > 0, false);
     }
 
     @Override
@@ -129,10 +127,6 @@
     }
 
     public void show(boolean show, boolean animate) {
-        if (show && !mShowing) {
-            setContentFrameVisible(mSettingsView != null || mNotificationCount > 0, false);
-        }
-
         if (animate) {
             if (mShowing != show) {
                 mShowing = show;
@@ -226,53 +220,10 @@
     }
 
     public void setNotificationCount(int n) {
-//        Slog.d(TAG, "notificationCount=" + n);
-        if (!mShowing) {
-            // just do it, already
-            setContentFrameVisible(n > 0, false);
-        } else if (mSettingsView == null) {
-            // we're looking at the notifications; time to maybe make some changes
-            if ((mNotificationCount > 0) != (n > 0)) {
-                setContentFrameVisible(n > 0, true);
-            }
-        }
         mNotificationCount = n;
     }
 
     public void setContentFrameVisible(final boolean showing, boolean animate) {
-        if (!animate) {
-            mContentFrame.setVisibility(showing ? View.VISIBLE : View.GONE);
-            mContentFrame.setAlpha(1f);
-            // the translation will be patched up when the window is slid into place
-            return;
-        }
-
-        if (showing) {
-            mContentFrame.setVisibility(View.VISIBLE);
-        }
-        AnimatorSet set = new AnimatorSet();
-        set.play(ObjectAnimator.ofFloat(
-                mContentFrame, "alpha",
-                showing ? 0f : 1f,
-                showing ? 1f : 0f))
-            .with(ObjectAnimator.ofFloat(
-                mContentParent, "translationY",
-                showing ? mContentFrameMissingTranslation : 0f,
-                showing ? 0f : mContentFrameMissingTranslation))
-              ;
-
-        set.setDuration(200);
-        set.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator _a) {
-                if (!showing) {
-                    mContentFrame.setVisibility(View.GONE);
-                    mContentFrame.setAlpha(1f);
-                }
-                updateClearButton();
-            }
-        });
-        set.start();
     }
 
     public void swapPanels() {
@@ -292,11 +243,6 @@
             public void onAnimationEnd(Animator _a) {
                 toHide.setVisibility(View.GONE);
                 if (toShow != null) {
-                    if (mNotificationCount == 0) {
-                        // show the frame for settings, hide for notifications
-                        setContentFrameVisible(toShow == mSettingsView, true);
-                    }
-
                     toShow.setVisibility(View.VISIBLE);
                     if (toShow == mSettingsView || mNotificationCount > 0) {
                         ObjectAnimator.ofFloat(toShow, "alpha", 0f, 1f)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index 8df9b85..9b46af8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -404,6 +404,7 @@
         mRecentsPanel.updateValuesFromResources();
         mShowSearchHoldoff = mContext.getResources().getInteger(
                 R.integer.config_show_search_delay);
+        updateSearchPanel();
     }
 
     protected void loadDimens() {
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java
index d37207c..c9388cb 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/LockScreen.java
@@ -23,7 +23,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.SlidingTab;
 import com.android.internal.widget.WaveView;
-import com.android.internal.widget.multiwaveview.MultiWaveView;
+import com.android.internal.widget.multiwaveview.GlowPadView;
 
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
@@ -286,16 +286,16 @@
         return mSearchManager;
     }
 
-    class MultiWaveViewMethods implements MultiWaveView.OnTriggerListener,
+    class GlowPadViewMethods implements GlowPadView.OnTriggerListener,
             UnlockWidgetCommonMethods {
-        private final MultiWaveView mMultiWaveView;
+        private final GlowPadView mGlowPadView;
 
-        MultiWaveViewMethods(MultiWaveView multiWaveView) {
-            mMultiWaveView = multiWaveView;
+        GlowPadViewMethods(GlowPadView glowPadView) {
+            mGlowPadView = glowPadView;
         }
 
         public boolean isTargetPresent(int resId) {
-            return mMultiWaveView.getTargetPosition(resId) != -1;
+            return mGlowPadView.getTargetPosition(resId) != -1;
         }
 
         public void updateResources() {
@@ -307,8 +307,8 @@
             } else {
                 resId = R.array.lockscreen_targets_with_camera;
             }
-            if (mMultiWaveView.getTargetResourceId() != resId) {
-                mMultiWaveView.setTargetResources(resId);
+            if (mGlowPadView.getTargetResourceId() != resId) {
+                mGlowPadView.setTargetResources(resId);
             }
 
             // Update the search icon with drawable from the search .apk
@@ -317,7 +317,7 @@
                 if (searchManager != null) {
                     ComponentName component = searchManager.getGlobalSearchActivity();
                     if (component != null) {
-                        if (!mMultiWaveView.replaceTargetDrawablesIfPresent(component,
+                        if (!mGlowPadView.replaceTargetDrawablesIfPresent(component,
                                 ASSIST_ICON_METADATA_NAME,
                                 com.android.internal.R.drawable.ic_lockscreen_search)) {
                             Slog.w(TAG, "Couldn't grab icon from package " + component);
@@ -343,7 +343,7 @@
         }
 
         public void onTrigger(View v, int target) {
-            final int resId = mMultiWaveView.getResourceIdForTarget(target);
+            final int resId = mGlowPadView.getResourceIdForTarget(target);
             switch (resId) {
                 case com.android.internal.R.drawable.ic_lockscreen_search:
                     Intent assistIntent = getAssistIntent();
@@ -393,33 +393,33 @@
             // Don't poke the wake lock when returning to a state where the handle is
             // not grabbed since that can happen when the system (instead of the user)
             // cancels the grab.
-            if (handle != MultiWaveView.OnTriggerListener.NO_HANDLE) {
+            if (handle != GlowPadView.OnTriggerListener.NO_HANDLE) {
                 mCallback.pokeWakelock();
             }
         }
 
         public View getView() {
-            return mMultiWaveView;
+            return mGlowPadView;
         }
 
         public void reset(boolean animate) {
-            mMultiWaveView.reset(animate);
+            mGlowPadView.reset(animate);
         }
 
         public void ping() {
-            mMultiWaveView.ping();
+            mGlowPadView.ping();
         }
 
         public void setEnabled(int resourceId, boolean enabled) {
-            mMultiWaveView.setEnableTarget(resourceId, enabled);
+            mGlowPadView.setEnableTarget(resourceId, enabled);
         }
 
         public int getTargetPosition(int resourceId) {
-            return mMultiWaveView.getTargetPosition(resourceId);
+            return mGlowPadView.getTargetPosition(resourceId);
         }
 
         public void cleanUp() {
-            mMultiWaveView.setOnTriggerListener(null);
+            mGlowPadView.setOnTriggerListener(null);
         }
 
         public void onFinishFinalAnimation() {
@@ -531,11 +531,11 @@
             WaveViewMethods waveViewMethods = new WaveViewMethods(waveView);
             waveView.setOnTriggerListener(waveViewMethods);
             return waveViewMethods;
-        } else if (unlockWidget instanceof MultiWaveView) {
-            MultiWaveView multiWaveView = (MultiWaveView) unlockWidget;
-            MultiWaveViewMethods multiWaveViewMethods = new MultiWaveViewMethods(multiWaveView);
-            multiWaveView.setOnTriggerListener(multiWaveViewMethods);
-            return multiWaveViewMethods;
+        } else if (unlockWidget instanceof GlowPadView) {
+            GlowPadView glowPadView = (GlowPadView) unlockWidget;
+            GlowPadViewMethods glowPadViewMethods = new GlowPadViewMethods(glowPadView);
+            glowPadView.setOnTriggerListener(glowPadViewMethods);
+            return glowPadViewMethods;
         } else {
             throw new IllegalStateException("Unrecognized unlock widget: " + unlockWidget);
         }
@@ -545,12 +545,12 @@
         boolean disabledByAdmin = mLockPatternUtils.getDevicePolicyManager()
                 .getCameraDisabled(null);
         boolean disabledBySimState = mUpdateMonitor.isSimLocked();
-        boolean cameraTargetPresent = (mUnlockWidgetMethods instanceof MultiWaveViewMethods)
-                ? ((MultiWaveViewMethods) mUnlockWidgetMethods)
+        boolean cameraTargetPresent = (mUnlockWidgetMethods instanceof GlowPadViewMethods)
+                ? ((GlowPadViewMethods) mUnlockWidgetMethods)
                         .isTargetPresent(com.android.internal.R.drawable.ic_lockscreen_camera)
                         : false;
-        boolean searchTargetPresent = (mUnlockWidgetMethods instanceof MultiWaveViewMethods)
-                ? ((MultiWaveViewMethods) mUnlockWidgetMethods)
+        boolean searchTargetPresent = (mUnlockWidgetMethods instanceof GlowPadViewMethods)
+                ? ((GlowPadViewMethods) mUnlockWidgetMethods)
                         .isTargetPresent(com.android.internal.R.drawable.ic_lockscreen_search)
                         : false;
 
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index c8e11c2..7e1a80c 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -56,6 +56,8 @@
 
 #define INDENT "  "
 #define INDENT2 "    "
+#define INDENT3 "      "
+#define INDENT4 "        "
 
 namespace android {
 
@@ -78,6 +80,9 @@
 // queue of waiting unfinished events, then ANRs will similarly be delayed by one second.
 const nsecs_t STREAM_AHEAD_EVENT_TIMEOUT = 500 * 1000000LL; // 0.5sec
 
+// Log a warning when an event takes longer than this to process, even if an ANR does not occur.
+const nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2sec
+
 
 static inline nsecs_t now() {
     return systemTime(SYSTEM_TIME_MONOTONIC);
@@ -897,11 +902,11 @@
         const EventEntry* entry,
         const sp<InputApplicationHandle>& applicationHandle,
         const sp<InputWindowHandle>& windowHandle,
-        nsecs_t* nextWakeupTime) {
+        nsecs_t* nextWakeupTime, const char* reason) {
     if (applicationHandle == NULL && windowHandle == NULL) {
         if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
 #if DEBUG_FOCUS
-            ALOGD("Waiting for system to become ready for input.");
+            ALOGD("Waiting for system to become ready for input.  Reason: %s", reason);
 #endif
             mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
             mInputTargetWaitStartTime = currentTime;
@@ -912,8 +917,9 @@
     } else {
         if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
 #if DEBUG_FOCUS
-            ALOGD("Waiting for application to become ready for input: %s",
-                    getApplicationWindowLabelLocked(applicationHandle, windowHandle).string());
+            ALOGD("Waiting for application to become ready for input: %s.  Reason: %s",
+                    getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
+                    reason);
 #endif
             nsecs_t timeout;
             if (windowHandle != NULL) {
@@ -946,7 +952,7 @@
 
     if (currentTime >= mInputTargetWaitTimeoutTime) {
         onANRLocked(currentTime, applicationHandle, windowHandle,
-                entry->eventTime, mInputTargetWaitStartTime);
+                entry->eventTime, mInputTargetWaitStartTime, reason);
 
         // Force poll loop to wake up immediately on next iteration once we get the
         // ANR response back from the policy.
@@ -1017,13 +1023,11 @@
     // then drop the event.
     if (mFocusedWindowHandle == NULL) {
         if (mFocusedApplicationHandle != NULL) {
-#if DEBUG_FOCUS
-            ALOGD("Waiting because there is no focused window but there is a "
-                    "focused application that may eventually add a window: %s.",
-                    getApplicationWindowLabelLocked(mFocusedApplicationHandle, NULL).string());
-#endif
             injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
-                    mFocusedApplicationHandle, NULL, nextWakeupTime);
+                    mFocusedApplicationHandle, NULL, nextWakeupTime,
+                    "Waiting because no window has focus but there is a "
+                    "focused application that may eventually add a window "
+                    "when it finishes starting up.");
             goto Unresponsive;
         }
 
@@ -1040,21 +1044,18 @@
 
     // If the currently focused window is paused then keep waiting.
     if (mFocusedWindowHandle->getInfo()->paused) {
-#if DEBUG_FOCUS
-        ALOGD("Waiting because focused window is paused.");
-#endif
         injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
-                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime);
+                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime,
+                "Waiting because the focused window is paused.");
         goto Unresponsive;
     }
 
     // If the currently focused window is still working on previous events then keep waiting.
     if (!isWindowReadyForMoreInputLocked(currentTime, mFocusedWindowHandle, entry)) {
-#if DEBUG_FOCUS
-        ALOGD("Waiting because focused window still processing previous input.");
-#endif
         injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
-                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime);
+                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime,
+                "Waiting because the focused window has not finished "
+                "processing the input events that were previously delivered to it.");
         goto Unresponsive;
     }
 
@@ -1210,11 +1211,9 @@
         // it is invisible) then wait for it.  Any other focused window may in
         // fact be in ANR state.
         if (topErrorWindowHandle != NULL && newTouchedWindowHandle != topErrorWindowHandle) {
-#if DEBUG_FOCUS
-            ALOGD("Waiting because system error window is pending.");
-#endif
             injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
-                    NULL, NULL, nextWakeupTime);
+                    NULL, NULL, nextWakeupTime,
+                    "Waiting because a system error window is about to be displayed.");
             injectionPermission = INJECTION_PERMISSION_UNKNOWN;
             goto Unresponsive;
         }
@@ -1241,14 +1240,11 @@
                 // but not yet put up a window and the user is starting to get impatient.
                 if (maskedAction == AMOTION_EVENT_ACTION_DOWN
                         && mFocusedApplicationHandle != NULL) {
-#if DEBUG_FOCUS
-                    ALOGD("Waiting because there is no touched window but there is a "
-                            "focused application that may eventually add a new window: %s.",
-                            getApplicationWindowLabelLocked(
-                                    mFocusedApplicationHandle, NULL).string());
-#endif
                     injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
-                            mFocusedApplicationHandle, NULL, nextWakeupTime);
+                            mFocusedApplicationHandle, NULL, nextWakeupTime,
+                            "Waiting because there is no touchable window that can "
+                            "handle the event but there is focused application that may "
+                            "eventually add a new window when it finishes starting up.");
                     goto Unresponsive;
                 }
 
@@ -1412,21 +1408,18 @@
         if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
             // If the touched window is paused then keep waiting.
             if (touchedWindow.windowHandle->getInfo()->paused) {
-#if DEBUG_FOCUS
-                ALOGD("Waiting because touched window is paused.");
-#endif
                 injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
-                        NULL, touchedWindow.windowHandle, nextWakeupTime);
+                        NULL, touchedWindow.windowHandle, nextWakeupTime,
+                        "Waiting because the touched window is paused.");
                 goto Unresponsive;
             }
 
             // If the touched window is still working on previous events then keep waiting.
             if (!isWindowReadyForMoreInputLocked(currentTime, touchedWindow.windowHandle, entry)) {
-#if DEBUG_FOCUS
-                ALOGD("Waiting because touched window still processing previous input.");
-#endif
                 injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
-                        NULL, touchedWindow.windowHandle, nextWakeupTime);
+                        NULL, touchedWindow.windowHandle, nextWakeupTime,
+                        "Waiting because the touched window has not finished "
+                        "processing the input events that were previously delivered to it.");
                 goto Unresponsive;
             }
         }
@@ -1897,6 +1890,7 @@
     while (connection->status == Connection::STATUS_NORMAL
             && !connection->outboundQueue.isEmpty()) {
         DispatchEntry* dispatchEntry = connection->outboundQueue.head;
+        dispatchEntry->deliveryTime = currentTime;
 
         // Publish the event.
         status_t status;
@@ -3099,7 +3093,65 @@
         dump.append(INDENT "MonitoringChannels: <none>\n");
     }
 
-    dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count());
+    nsecs_t currentTime = now();
+
+    if (!mInboundQueue.isEmpty()) {
+        dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count());
+        for (EventEntry* entry = mInboundQueue.head; entry; entry = entry->next) {
+            dump.append(INDENT2);
+            entry->appendDescription(dump);
+            dump.appendFormat(", age=%01.1fms\n",
+                    (currentTime - entry->eventTime) * 0.000001f);
+        }
+    } else {
+        dump.append(INDENT "InboundQueue: <empty>\n");
+    }
+
+    if (!mConnectionsByFd.isEmpty()) {
+        dump.append(INDENT "Connections:\n");
+        for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
+            const sp<Connection>& connection = mConnectionsByFd.valueAt(i);
+            dump.appendFormat(INDENT2 "%d: channelName='%s', windowName='%s', "
+                    "status=%s, monitor=%s, inputPublisherBlocked=%s\n",
+                    i, connection->getInputChannelName(), connection->getWindowName(),
+                    connection->getStatusLabel(), toString(connection->monitor),
+                    toString(connection->inputPublisherBlocked));
+
+            if (!connection->outboundQueue.isEmpty()) {
+                dump.appendFormat(INDENT3 "OutboundQueue: length=%u\n",
+                        connection->outboundQueue.count());
+                for (DispatchEntry* entry = connection->outboundQueue.head; entry;
+                        entry = entry->next) {
+                    dump.append(INDENT4);
+                    entry->eventEntry->appendDescription(dump);
+                    dump.appendFormat(", targetFlags=0x%08x, resolvedAction=%d, age=%01.1fms\n",
+                            entry->targetFlags, entry->resolvedAction,
+                            (currentTime - entry->eventEntry->eventTime) * 0.000001f);
+                }
+            } else {
+                dump.append(INDENT3 "OutboundQueue: <empty>\n");
+            }
+
+            if (!connection->waitQueue.isEmpty()) {
+                dump.appendFormat(INDENT3 "WaitQueue: length=%u\n",
+                        connection->waitQueue.count());
+                for (DispatchEntry* entry = connection->waitQueue.head; entry;
+                        entry = entry->next) {
+                    dump.append(INDENT4);
+                    entry->eventEntry->appendDescription(dump);
+                    dump.appendFormat(", targetFlags=0x%08x, resolvedAction=%d, "
+                            "age=%01.1fms, wait=%01.1fms\n",
+                            entry->targetFlags, entry->resolvedAction,
+                            (currentTime - entry->eventEntry->eventTime) * 0.000001f,
+                            (currentTime - entry->deliveryTime) * 0.000001f);
+                }
+            } else {
+                dump.append(INDENT3 "WaitQueue: <empty>\n");
+            }
+        }
+    } else {
+        dump.append(INDENT "Connections: <none>\n");
+    }
 
     if (isAppSwitchPendingLocked()) {
         dump.appendFormat(INDENT "AppSwitch: pending, due in %01.1fms\n",
@@ -3214,6 +3266,7 @@
     CommandEntry* commandEntry = postCommandLocked(
             & InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
     commandEntry->connection = connection;
+    commandEntry->eventTime = currentTime;
     commandEntry->seq = seq;
     commandEntry->handled = handled;
 }
@@ -3231,12 +3284,12 @@
 void InputDispatcher::onANRLocked(
         nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
         const sp<InputWindowHandle>& windowHandle,
-        nsecs_t eventTime, nsecs_t waitStartTime) {
+        nsecs_t eventTime, nsecs_t waitStartTime, const char* reason) {
     ALOGI("Application is not responding: %s.  "
-            "%01.1fms since event, %01.1fms since wait started",
+            "It has been %01.1fms since event, %01.1fms since wait started.  Reason: %s",
             getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
             (currentTime - eventTime) / 1000000.0,
-            (currentTime - waitStartTime) / 1000000.0);
+            (currentTime - waitStartTime) / 1000000.0, reason);
 
     CommandEntry* commandEntry = postCommandLocked(
             & InputDispatcher::doNotifyANRLockedInterruptible);
@@ -3308,12 +3361,22 @@
 void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
         CommandEntry* commandEntry) {
     sp<Connection> connection = commandEntry->connection;
+    nsecs_t finishTime = commandEntry->eventTime;
     uint32_t seq = commandEntry->seq;
     bool handled = commandEntry->handled;
 
     // Handle post-event policy actions.
     DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq);
     if (dispatchEntry) {
+        nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
+        if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
+            String8 msg;
+            msg.appendFormat("Window '%s' spent %01.1fms processing the last input event: ",
+                    connection->getWindowName(), eventDuration * 0.000001f);
+            dispatchEntry->eventEntry->appendDescription(msg);
+            ALOGI("%s", msg.string());
+        }
+
         bool restartEvent;
         if (dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) {
             KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
@@ -3656,6 +3719,10 @@
 InputDispatcher::ConfigurationChangedEntry::~ConfigurationChangedEntry() {
 }
 
+void InputDispatcher::ConfigurationChangedEntry::appendDescription(String8& msg) const {
+    msg.append("ConfigurationChangedEvent()");
+}
+
 
 // --- InputDispatcher::DeviceResetEntry ---
 
@@ -3667,6 +3734,10 @@
 InputDispatcher::DeviceResetEntry::~DeviceResetEntry() {
 }
 
+void InputDispatcher::DeviceResetEntry::appendDescription(String8& msg) const {
+    msg.appendFormat("DeviceResetEvent(deviceId=%d)", deviceId);
+}
+
 
 // --- InputDispatcher::KeyEntry ---
 
@@ -3685,6 +3756,11 @@
 InputDispatcher::KeyEntry::~KeyEntry() {
 }
 
+void InputDispatcher::KeyEntry::appendDescription(String8& msg) const {
+    msg.appendFormat("KeyEvent(action=%d, deviceId=%d, source=0x%08x)",
+            action, deviceId, source);
+}
+
 void InputDispatcher::KeyEntry::recycle() {
     releaseInjectionState();
 
@@ -3718,6 +3794,11 @@
 InputDispatcher::MotionEntry::~MotionEntry() {
 }
 
+void InputDispatcher::MotionEntry::appendDescription(String8& msg) const {
+    msg.appendFormat("MotionEvent(action=%d, deviceId=%d, source=0x%08x)",
+            action, deviceId, source);
+}
+
 
 // --- InputDispatcher::DispatchEntry ---
 
@@ -3728,7 +3809,7 @@
         seq(nextSeq()),
         eventEntry(eventEntry), targetFlags(targetFlags),
         xOffset(xOffset), yOffset(yOffset), scaleFactor(scaleFactor),
-        resolvedAction(0), resolvedFlags(0) {
+        deliveryTime(0), resolvedAction(0), resolvedFlags(0) {
     eventEntry->refCount += 1;
 }
 
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index 06b8d64..c5b8cd7 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -440,6 +440,8 @@
 
         void release();
 
+        virtual void appendDescription(String8& msg) const = 0;
+
     protected:
         EventEntry(int32_t type, nsecs_t eventTime, uint32_t policyFlags);
         virtual ~EventEntry();
@@ -448,6 +450,7 @@
 
     struct ConfigurationChangedEntry : EventEntry {
         ConfigurationChangedEntry(nsecs_t eventTime);
+        virtual void appendDescription(String8& msg) const;
 
     protected:
         virtual ~ConfigurationChangedEntry();
@@ -457,6 +460,7 @@
         int32_t deviceId;
 
         DeviceResetEntry(nsecs_t eventTime, int32_t deviceId);
+        virtual void appendDescription(String8& msg) const;
 
     protected:
         virtual ~DeviceResetEntry();
@@ -488,6 +492,7 @@
                 int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
                 int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
                 int32_t repeatCount, nsecs_t downTime);
+        virtual void appendDescription(String8& msg) const;
         void recycle();
 
     protected:
@@ -516,6 +521,7 @@
                 float xPrecision, float yPrecision,
                 nsecs_t downTime, uint32_t pointerCount,
                 const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
+        virtual void appendDescription(String8& msg) const;
 
     protected:
         virtual ~MotionEntry();
@@ -530,6 +536,7 @@
         float xOffset;
         float yOffset;
         float scaleFactor;
+        nsecs_t deliveryTime; // time when the event was actually delivered
 
         // Set to the resolved action and flags when the event is enqueued.
         int32_t resolvedAction;
@@ -978,7 +985,7 @@
     int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry,
             const sp<InputApplicationHandle>& applicationHandle,
             const sp<InputWindowHandle>& windowHandle,
-            nsecs_t* nextWakeupTime);
+            nsecs_t* nextWakeupTime, const char* reason);
     void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
             const sp<InputChannel>& inputChannel);
     nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime);
@@ -1056,7 +1063,7 @@
     void onANRLocked(
             nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
             const sp<InputWindowHandle>& windowHandle,
-            nsecs_t eventTime, nsecs_t waitStartTime);
+            nsecs_t eventTime, nsecs_t waitStartTime, const char* reason);
 
     // Outbound policy interactions.
     void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry);
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 6af7a88..8a86fe7 100755
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -3282,7 +3282,9 @@
                 requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
                         "no-history");
             }
-        } else if (r.app != null && r.app.thread != null) {
+        }
+
+        if (r.app != null && r.app.thread != null) {
             if (mMainStack) {
                 if (mService.mFocusedActivity == r) {
                     mService.setFocusedActivityLocked(topRunningActivityLocked(null));
diff --git a/services/java/com/android/server/am/UriPermission.java b/services/java/com/android/server/am/UriPermission.java
index e3347cb..c5b1c7b 100644
--- a/services/java/com/android/server/am/UriPermission.java
+++ b/services/java/com/android/server/am/UriPermission.java
@@ -59,11 +59,11 @@
         if ((modeFlagsToClear&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
             globalModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
             modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
-            if (readOwners.size() > 0) {
+            if (writeOwners.size() > 0) {
                 for (UriPermissionOwner r : writeOwners) {
                     r.removeWritePermission(this);
                 }
-                readOwners.clear();
+                writeOwners.clear();
             }
         }
     }
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 49d2ebd..6643d8c 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -9397,4 +9397,15 @@
             return true;
         }
     }
+
+    public boolean isStorageLow() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager
+                    .getService(DeviceStorageMonitorService.SERVICE);
+            return dsm.isMemoryLow();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
 }
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index d25a960..efed0a4 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -85,12 +85,19 @@
         mPolicy = policy;
     }
 
-    void hideWallpapersLocked() {
-        for (final WindowToken token : mService.mWallpaperTokens) {
-            for (final WindowState wallpaper : token.windows) {
-                wallpaper.mWinAnimator.hide();
+    void hideWallpapersLocked(final WindowState w) {
+        if ((mService.mWallpaperTarget == w && mService.mLowerWallpaperTarget == null)
+                || mService.mWallpaperTarget == null) {
+            for (final WindowToken token : mService.mWallpaperTokens) {
+                for (final WindowState wallpaper : token.windows) {
+                    final WindowStateAnimator winAnimator = wallpaper.mWinAnimator;
+                    if (!winAnimator.mLastHidden) {
+                        winAnimator.hide();
+                        mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+                    }
+                }
+                token.hidden = true;
             }
-            token.hidden = true;
         }
     }
 
@@ -489,11 +496,7 @@
 
             final int N = mWinAnimators.size();
             for (int i = 0; i < N; i++) {
-                final WindowStateAnimator winAnimator = mWinAnimators.get(i);
-                if (winAnimator.mWin.mIsWallpaper && mService.mWallpaperTarget == null) {
-                    continue;
-                }
-                winAnimator.prepareSurfaceLocked(true);
+                mWinAnimators.get(i).prepareSurfaceLocked(true);
             }
 
             if (mDimParams != null) {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index ba4df96b..b85573f 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -127,6 +127,7 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.animation.ScaleAnimation;
 
@@ -280,6 +281,8 @@
     private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager
     private int mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; // sync'd by mKeyguardTokenWatcher
 
+    private static final float THUMBNAIL_ANIMATION_DECELERATE_FACTOR = 1.5f;
+
     final TokenWatcher mKeyguardTokenWatcher = new TokenWatcher(
             new Handler(), "WindowManagerService.mKeyguardTokenWatcher") {
         @Override
@@ -1737,8 +1740,6 @@
                             mWallpaperTarget = oldW;
                             foundW = oldW;
                             foundI = oldI;
-                            mLowerWallpaperTarget = null;
-                            mUpperWallpaperTarget = null;
                         } 
                         // Now set the upper and lower wallpaper targets
                         // correctly, and make sure that we are positioning
@@ -3159,6 +3160,7 @@
             set.addAnimation(scale);
             alpha.setDuration(duration);
             set.addAnimation(alpha);
+            set.setDetachWallpaper(true);
             a = set;
         } else {
             a = createExitAnimationLocked(transit, duration);
@@ -3183,7 +3185,7 @@
         // it  is the standard duration for that.  Otherwise we use the longer
         // task transition duration.
         int duration;
-        int delayDuration = delayed ? 200 : 0;
+        int delayDuration = delayed ? 270 : 0;
         switch (transit) {
             case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
             case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE:
@@ -3191,7 +3193,7 @@
                         com.android.internal.R.integer.config_shortAnimTime);
                 break;
             default:
-                duration = delayed ? 200 : 300;
+                duration = delayed ? 250 : 300;
                 break;
         }
         if (thumb) {
@@ -3206,6 +3208,8 @@
             AnimationSet set = new AnimationSet(true);
             Animation alpha = new AlphaAnimation(1, 0);
             scale.setDuration(duration);
+            scale.setInterpolator(
+                    new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR));
             set.addAnimation(scale);
             alpha.setDuration(duration);
             set.addAnimation(alpha);
@@ -3222,6 +3226,8 @@
                     computePivot(mNextAppTransitionStartX, scaleW),
                     computePivot(mNextAppTransitionStartY, scaleH));
             scale.setDuration(duration);
+            scale.setInterpolator(
+                    new DecelerateInterpolator(THUMBNAIL_ANIMATION_DECELERATE_FACTOR));
             scale.setFillBefore(true);
             if (delayDuration > 0) {
                 scale.setStartOffset(delayDuration);
@@ -3959,15 +3965,6 @@
         }
     }
 
-    private void cancelWindowAnimations(final AppWindowToken wtoken) {
-        for (int i = wtoken.windows.size() - 1; i >= 0; i--) {
-            final WindowStateAnimator winAnimator = wtoken.windows.get(i).mWinAnimator;
-            if (winAnimator.isAnimating()) {
-                winAnimator.clearAnimation();
-            }
-        }
-    }
-
     public void executeAppTransition() {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                 "executeAppTransition()")) {
@@ -3983,12 +3980,6 @@
             }
             if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
                 mAppTransitionReady = true;
-                for (int i = mOpeningApps.size() - 1; i >= 0; i--) {
-                    cancelWindowAnimations(mOpeningApps.get(i));
-                }
-                for (int i = mClosingApps.size() - 1; i >= 0; i--) {
-                    cancelWindowAnimations(mClosingApps.get(i));
-                }
                 final long origId = Binder.clearCallingIdentity();
                 performLayoutAndPlaceSurfacesLocked();
                 Binder.restoreCallingIdentity(origId);
@@ -4336,7 +4327,6 @@
 
                 if (DEBUG_APP_TRANSITIONS) Slog.v(
                         TAG, "Setting dummy animation on: " + wtoken);
-                cancelWindowAnimations(wtoken);
                 wtoken.mAppAnimator.setDummyAnimation();
                 mOpeningApps.remove(wtoken);
                 mClosingApps.remove(wtoken);
diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java
index 5908958..bdacb6e 100644
--- a/services/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,7 +16,6 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Debug;
-import android.os.RemoteException;
 import android.util.Slog;
 import android.view.Surface;
 import android.view.SurfaceSession;
@@ -379,10 +378,7 @@
             mService.mPendingRemove.add(mWin);
             mWin.mRemoveOnExit = false;
         }
-        if (mService.mWallpaperTarget == mWin && mService.mLowerWallpaperTarget == null) {
-            mAnimator.hideWallpapersLocked();
-            mAnimator.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-        }
+        mAnimator.hideWallpapersLocked(mWin);
     }
 
     void hide() {
@@ -738,6 +734,7 @@
                     }
                     mSurface.destroy();
                 }
+                mAnimator.hideWallpapersLocked(mWin);
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Exception thrown when destroying Window " + this
                     + " surface " + mSurface + " session " + mSession
@@ -763,6 +760,7 @@
                     WindowManagerService.logSurface(mWin, "DESTROY PENDING", e);
                 }
                 mPendingDestroySurface.destroy();
+                mAnimator.hideWallpapersLocked(mWin);
             }
         } catch (RuntimeException e) {
             Slog.w(TAG, "Exception thrown when destroying Window "
@@ -1065,13 +1063,13 @@
 
         setSurfaceBoundaries(recoveringMemory);
 
-        if (w.mAttachedHidden || !w.isReadyForDisplay()) {
+        if (mWin.mIsWallpaper && !mWin.mWallpaperVisible) {
+            // Wallpaper is no longer visible and there is no wp target => hide it.
             hide();
-            // TODO: Consider moving the following into hide() and out of finishExit() as well.
-            if (mService.mWallpaperTarget == mWin && mService.mLowerWallpaperTarget == null) {
-                mAnimator.hideWallpapersLocked();
-                mAnimator.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-            }
+        } else if (w.mAttachedHidden || !w.isReadyForDisplay()) {
+            hide();
+            mAnimator.hideWallpapersLocked(w);
+
             // If we are waiting for this window to handle an
             // orientation change, well, it is hidden, so
             // doesn't really matter.  Note that this does
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index d428fef..0a5e590 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -1204,7 +1204,9 @@
             }
 
             // If the application has requested WRITE_EXTERNAL_STORAGE, we will
-            // force them to always take READ_EXTERNAL_STORAGE as well.
+            // force them to always take READ_EXTERNAL_STORAGE as well.  We always
+            // do this (regardless of target API version) because we can't have
+            // an app with write permission but not read permission.
             if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) {
                 printf("uses-permission:'android.permission.READ_EXTERNAL_STORAGE'\n");
                 printf("uses-implied-permission:'android.permission.READ_EXTERNAL_STORAGE'," \