Merge "Add support for a circular bitmap overlay for round android wear emulator." into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index ef9d879..9b53c05 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3792,7 +3792,6 @@
 
   public class ActivityOptions {
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
-    method public static deprecated android.app.ActivityOptions makeLaunchTaskBehindAnimation();
     method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, java.lang.String);
     method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View, java.lang.String>...);
@@ -27267,6 +27266,7 @@
     method public android.view.SurfaceHolder getSurfaceHolder();
     method public boolean isPreview();
     method public boolean isVisible();
+    method public void onApplyWindowInsets(android.view.WindowInsets);
     method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean);
     method public void onCreate(android.view.SurfaceHolder);
     method public void onDesiredSizeChanged(int, int);
@@ -34975,12 +34975,18 @@
 
   public final class WindowInsets {
     ctor public WindowInsets(android.view.WindowInsets);
+    method public android.view.WindowInsets consumeStableInsets();
     method public android.view.WindowInsets consumeSystemWindowInsets();
+    method public int getStableInsetBottom();
+    method public int getStableInsetLeft();
+    method public int getStableInsetRight();
+    method public int getStableInsetTop();
     method public int getSystemWindowInsetBottom();
     method public int getSystemWindowInsetLeft();
     method public int getSystemWindowInsetRight();
     method public int getSystemWindowInsetTop();
     method public boolean hasInsets();
+    method public boolean hasStableInsets();
     method public boolean hasSystemWindowInsets();
     method public boolean isConsumed();
     method public boolean isRound();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index ffffb6c..213c7f6 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -520,11 +520,6 @@
         return opts;
     }
 
-    @Deprecated
-    public static ActivityOptions makeLaunchTaskBehindAnimation() {
-        return makeTaskLaunchBehind();
-    }
-
     /** @hide */
     public boolean getLaunchTaskBehind() {
         return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 181eb63..3b5900b 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.app.IWallpaperManagerCallback;
@@ -73,6 +74,11 @@
     int getHeightHint();
 
     /**
+     * Sets extra padding that we would like the wallpaper to have outside of the display.
+     */
+    void setDisplayPadding(in Rect padding);
+
+    /**
      * Returns the name of the wallpaper. Private API.
      */
     String getName();
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 48ff5b6..8bfe6d3 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -951,6 +952,48 @@
     }
 
     /**
+     * Specify extra padding that the wallpaper should have outside of the display.
+     * That is, the given padding supplies additional pixels the wallpaper should extend
+     * outside of the display itself.
+     * @param padding The number of pixels the wallpaper should extend beyond the display,
+     * on its left, top, right, and bottom sides.
+     * @hide
+     */
+    @SystemApi
+    public void setDisplayPadding(Rect padding) {
+        try {
+            if (sGlobals.mService == null) {
+                Log.w(TAG, "WallpaperService not running");
+            } else {
+                sGlobals.mService.setDisplayPadding(padding);
+            }
+        } catch (RemoteException e) {
+            // Ignore
+        }
+    }
+
+    /**
+     * Apply a raw offset to the wallpaper window.  Should only be used in
+     * combination with {@link #setDisplayPadding(android.graphics.Rect)} when you
+     * have ensured that the wallpaper will extend outside of the display area so that
+     * it can be moved without leaving part of the display uncovered.
+     * @param x The offset, in pixels, to apply to the left edge.
+     * @param y The offset, in pixels, to apply to the top edge.
+     * @hide
+     */
+    @SystemApi
+    public void setDisplayOffset(IBinder windowToken, int x, int y) {
+        try {
+            //Log.v(TAG, "Sending new wallpaper display offsets from app...");
+            WindowManagerGlobal.getWindowSession().setWallpaperDisplayOffset(
+                    windowToken, x, y);
+            //Log.v(TAG, "...app returning after sending display offset!");
+        } catch (RemoteException e) {
+            // Ignore.
+        }
+    }
+
+    /**
      * Set the position of the current wallpaper within any larger space, when
      * that wallpaper is visible behind the given window.  The X and Y offsets
      * are floating point numbers ranging from 0 to 1, representing where the
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index fde8b2e..2853c58 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -639,12 +639,14 @@
      * @param authorities the semi-colon separated authorities of the ContentProvider.
      */
     protected final void setAuthorities(String authorities) {
-        if (authorities.indexOf(';') == -1) {
-            mAuthority = authorities;
-            mAuthorities = null;
-        } else {
-            mAuthority = null;
-            mAuthorities = authorities.split(";");
+        if (authorities != null) {
+            if (authorities.indexOf(';') == -1) {
+                mAuthority = authorities;
+                mAuthorities = null;
+            } else {
+                mAuthority = null;
+                mAuthorities = authorities.split(";");
+            }
         }
     }
 
@@ -653,9 +655,11 @@
         if (mAuthority != null) {
             return mAuthority.equals(authority);
         }
-        int length = mAuthorities.length;
-        for (int i = 0; i < length; i++) {
-            if (mAuthorities[i].equals(authority)) return true;
+        if (mAuthorities != null) {
+            int length = mAuthorities.length;
+            for (int i = 0; i < length; i++) {
+                if (mAuthorities[i].equals(authority)) return true;
+            }
         }
         return false;
     }
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index faccde2..de527e9 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -16,6 +16,7 @@
 
 package android.service.wallpaper;
 
+import android.graphics.Rect;
 import android.view.MotionEvent;
 import android.os.Bundle;
 
@@ -24,6 +25,7 @@
  */
 oneway interface IWallpaperEngine {
     void setDesiredSize(int width, int height);
+    void setDisplayPadding(in Rect padding);
     void setVisibility(boolean visible);
     void dispatchPointer(in MotionEvent event);
     void dispatchWallpaperCommand(String action, int x, int y,
diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl
index bc7a1d7..5fd0157 100644
--- a/core/java/android/service/wallpaper/IWallpaperService.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperService.aidl
@@ -16,6 +16,7 @@
 
 package android.service.wallpaper;
 
+import android.graphics.Rect;
 import android.service.wallpaper.IWallpaperConnection;
 
 /**
@@ -24,5 +25,5 @@
 oneway interface IWallpaperService {
     void attach(IWallpaperConnection connection,
     		IBinder windowToken, int windowType, boolean isPreview,
-    		int reqWidth, int reqHeight);
+    		int reqWidth, int reqHeight, in Rect padding);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f3c26c8..26e9a30 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -16,6 +16,14 @@
 
 package android.service.wallpaper;
 
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.ViewRootImpl;
+import android.view.WindowInsets;
+import com.android.internal.R;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.view.BaseIWindow;
 import com.android.internal.view.BaseSurfaceHolder;
@@ -56,6 +64,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+
 /**
  * A wallpaper service is responsible for showing a live wallpaper behind
  * applications that would like to sit on top of it.  This service object
@@ -90,7 +100,8 @@
     private static final int DO_ATTACH = 10;
     private static final int DO_DETACH = 20;
     private static final int DO_SET_DESIRED_SIZE = 30;
-    
+    private static final int DO_SET_DISPLAY_PADDING = 40;
+
     private static final int MSG_UPDATE_SURFACE = 10000;
     private static final int MSG_VISIBILITY_CHANGED = 10010;
     private static final int MSG_WALLPAPER_OFFSETS = 10020;
@@ -150,13 +161,23 @@
                 WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
         int mCurWindowFlags = mWindowFlags;
         int mCurWindowPrivateFlags = mWindowPrivateFlags;
+        TypedValue mOutsetBottom;
         final Rect mVisibleInsets = new Rect();
         final Rect mWinFrame = new Rect();
         final Rect mOverscanInsets = new Rect();
         final Rect mContentInsets = new Rect();
         final Rect mStableInsets = new Rect();
+        final Rect mDispatchedOverscanInsets = new Rect();
+        final Rect mDispatchedContentInsets = new Rect();
+        final Rect mDispatchedStableInsets = new Rect();
+        final Rect mFinalSystemInsets = new Rect();
+        final Rect mFinalStableInsets = new Rect();
         final Configuration mConfiguration = new Configuration();
-        
+
+        private boolean mIsEmulator;
+        private boolean mIsCircularEmulator;
+        private boolean mWindowIsRound;
+
         final WindowManager.LayoutParams mLayout
                 = new WindowManager.LayoutParams();
         IWindowSession mSession;
@@ -406,7 +427,7 @@
          */
         public void onCreate(SurfaceHolder surfaceHolder) {
         }
-        
+
         /**
          * Called right before the engine is going away.  After this the
          * surface will be destroyed and this Engine object is no longer
@@ -414,7 +435,7 @@
          */
         public void onDestroy() {
         }
-        
+
         /**
          * Called to inform you of the wallpaper becoming visible or
          * hidden.  <em>It is very important that a wallpaper only use
@@ -422,7 +443,17 @@
          */
         public void onVisibilityChanged(boolean visible) {
         }
-        
+
+        /**
+         * Called with the current insets that are in effect for the wallpaper.
+         * This gives you the part of the overall wallpaper surface that will
+         * generally be visible to the user (ignoring position offsets applied to it).
+         *
+         * @param insets Insets to apply.
+         */
+        public void onApplyWindowInsets(WindowInsets insets) {
+        }
+
         /**
          * Called as the user performs touch-screen interaction with the
          * window that is currently showing this wallpaper.  Note that the
@@ -432,7 +463,7 @@
          */
         public void onTouchEvent(MotionEvent event) {
         }
-        
+
         /**
          * Called to inform you of the wallpaper's offsets changing
          * within its contain, corresponding to the container's
@@ -443,7 +474,7 @@
                 float xOffsetStep, float yOffsetStep,
                 int xPixelOffset, int yPixelOffset) {
         }
-        
+
         /**
          * Process a command that was sent to the wallpaper with
          * {@link WallpaperManager#sendWallpaperCommand}.
@@ -465,14 +496,14 @@
                 Bundle extras, boolean resultRequested) {
             return null;
         }
-        
+
         /**
          * Called when an application has changed the desired virtual size of
          * the wallpaper.
          */
         public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
         }
-        
+
         /**
          * Convenience for {@link SurfaceHolder.Callback#surfaceChanged
          * SurfaceHolder.Callback.surfaceChanged()}.
@@ -561,16 +592,20 @@
             if (mDestroyed) {
                 Log.w(TAG, "Ignoring updateSurface: destroyed");
             }
-            
+
+            boolean fixedSize = false;
             int myWidth = mSurfaceHolder.getRequestedWidth();
             if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.MATCH_PARENT;
+            else fixedSize = true;
             int myHeight = mSurfaceHolder.getRequestedHeight();
             if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.MATCH_PARENT;
-            
+            else fixedSize = true;
+
             final boolean creating = !mCreated;
             final boolean surfaceCreating = !mSurfaceCreated;
             final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat();
             boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
+            boolean insetsChanged = !mCreated;
             final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();
             final boolean flagsChanged = mCurWindowFlags != mWindowFlags ||
                     mCurWindowPrivateFlags != mWindowPrivateFlags;
@@ -607,6 +642,32 @@
                     mLayout.token = mWindowToken;
 
                     if (!mCreated) {
+                        // Retrieve watch round and outset info
+                        final WindowManager windowService = (WindowManager)getSystemService(
+                                Context.WINDOW_SERVICE);
+                        TypedArray windowStyle = obtainStyledAttributes(
+                                com.android.internal.R.styleable.Window);
+                        final Display display = windowService.getDefaultDisplay();
+                        final boolean shouldUseBottomOutset =
+                                display.getDisplayId() == Display.DEFAULT_DISPLAY;
+                        if (shouldUseBottomOutset && windowStyle.hasValue(
+                                R.styleable.Window_windowOutsetBottom)) {
+                            if (mOutsetBottom == null) mOutsetBottom = new TypedValue();
+                            windowStyle.getValue(R.styleable.Window_windowOutsetBottom,
+                                    mOutsetBottom);
+                        } else {
+                            mOutsetBottom = null;
+                        }
+                        mWindowIsRound = getResources().getBoolean(
+                                com.android.internal.R.bool.config_windowIsRound);
+                        windowStyle.recycle();
+
+                        // detect emulator
+                        mIsEmulator = Build.HARDWARE.contains("goldfish");
+                        mIsCircularEmulator = SystemProperties.getBoolean(
+                                ViewRootImpl.PROPERTY_EMULATOR_CIRCULAR, false);
+
+                        // Add window
                         mLayout.type = mIWallpaperEngine.mWindowType;
                         mLayout.gravity = Gravity.START|Gravity.TOP;
                         mLayout.setTitle(WallpaperService.this.getClass().getName());
@@ -627,6 +688,11 @@
                     mSurfaceHolder.mSurfaceLock.lock();
                     mDrawingAllowed = true;
 
+                    if (!fixedSize) {
+                        mLayout.surfaceInsets.set(mIWallpaperEngine.mDisplayPadding);
+                    } else {
+                        mLayout.surfaceInsets.set(0, 0, 0, 0);
+                    }
                     final int relayoutResult = mSession.relayout(
                         mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
                             View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets,
@@ -636,16 +702,39 @@
                             + ", frame=" + mWinFrame);
                     
                     int w = mWinFrame.width();
+                    int h = mWinFrame.height();
+
+                    if (!fixedSize) {
+                        final Rect padding = mIWallpaperEngine.mDisplayPadding;
+                        w += padding.left + padding.right;
+                        h += padding.top + padding.bottom;
+                        mOverscanInsets.left += padding.left;
+                        mOverscanInsets.top += padding.top;
+                        mOverscanInsets.right += padding.right;
+                        mOverscanInsets.bottom += padding.bottom;
+                        mContentInsets.left += padding.left;
+                        mContentInsets.top += padding.top;
+                        mContentInsets.right += padding.right;
+                        mContentInsets.bottom += padding.bottom;
+                        mStableInsets.left += padding.left;
+                        mStableInsets.top += padding.top;
+                        mStableInsets.right += padding.right;
+                        mStableInsets.bottom += padding.bottom;
+                    }
+
                     if (mCurWidth != w) {
                         sizeChanged = true;
                         mCurWidth = w;
                     }
-                    int h = mWinFrame.height();
                     if (mCurHeight != h) {
                         sizeChanged = true;
                         mCurHeight = h;
                     }
 
+                    insetsChanged |= !mDispatchedOverscanInsets.equals(mOverscanInsets);
+                    insetsChanged |= !mDispatchedContentInsets.equals(mContentInsets);
+                    insetsChanged |= !mDispatchedStableInsets.equals(mStableInsets);
+
                     mSurfaceHolder.setSurfaceFrameSize(w, h);
                     mSurfaceHolder.mSurfaceLock.unlock();
 
@@ -702,6 +791,25 @@
                             }
                         }
 
+                        if (insetsChanged) {
+                            mDispatchedOverscanInsets.set(mOverscanInsets);
+                            mDispatchedContentInsets.set(mContentInsets);
+                            mDispatchedStableInsets.set(mStableInsets);
+                            final boolean isRound = (mIsEmulator && mIsCircularEmulator)
+                                    || mWindowIsRound;
+                            mFinalSystemInsets.set(mDispatchedOverscanInsets);
+                            mFinalStableInsets.set(mDispatchedStableInsets);
+                            if (mOutsetBottom != null) {
+                                final DisplayMetrics metrics = getResources().getDisplayMetrics();
+                                mFinalSystemInsets.bottom =
+                                        ( (int) mOutsetBottom.getDimension(metrics) )
+                                        + mIWallpaperEngine.mDisplayPadding.bottom;
+                            }
+                            WindowInsets insets = new WindowInsets(mFinalSystemInsets,
+                                    null, mFinalStableInsets, isRound);
+                            onApplyWindowInsets(insets);
+                        }
+
                         if (redrawNeeded) {
                             onSurfaceRedrawNeeded(mSurfaceHolder);
                             SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -781,7 +889,7 @@
             mReportedVisible = false;
             updateSurface(false, false, false);
         }
-        
+
         void doDesiredSizeChanged(int desiredWidth, int desiredHeight) {
             if (!mDestroyed) {
                 if (DEBUG) Log.v(TAG, "onDesiredSizeChanged("
@@ -792,14 +900,24 @@
                 doOffsetsChanged(true);
             }
         }
-        
+
+        void doDisplayPaddingChanged(Rect padding) {
+            if (!mDestroyed) {
+                if (DEBUG) Log.v(TAG, "onDisplayPaddingChanged(" + padding + "): " + this);
+                if (!mIWallpaperEngine.mDisplayPadding.equals(padding)) {
+                    mIWallpaperEngine.mDisplayPadding.set(padding);
+                    updateSurface(true, false, false);
+                }
+            }
+        }
+
         void doVisibilityChanged(boolean visible) {
             if (!mDestroyed) {
                 mVisible = visible;
                 reportVisibility();
             }
         }
-        
+
         void reportVisibility() {
             if (!mDestroyed) {
                 boolean visible = mVisible && mScreenOn;
@@ -956,12 +1074,13 @@
         boolean mShownReported;
         int mReqWidth;
         int mReqHeight;
-        
+        final Rect mDisplayPadding = new Rect();
+
         Engine mEngine;
-        
+
         IWallpaperEngineWrapper(WallpaperService context,
                 IWallpaperConnection conn, IBinder windowToken,
-                int windowType, boolean isPreview, int reqWidth, int reqHeight) {
+                int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
             mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
             mConnection = conn;
             mWindowToken = windowToken;
@@ -969,16 +1088,22 @@
             mIsPreview = isPreview;
             mReqWidth = reqWidth;
             mReqHeight = reqHeight;
+            mDisplayPadding.set(padding);
             
             Message msg = mCaller.obtainMessage(DO_ATTACH);
             mCaller.sendMessage(msg);
         }
-        
+
         public void setDesiredSize(int width, int height) {
             Message msg = mCaller.obtainMessageII(DO_SET_DESIRED_SIZE, width, height);
             mCaller.sendMessage(msg);
         }
-        
+
+        public void setDisplayPadding(Rect padding) {
+            Message msg = mCaller.obtainMessageO(DO_SET_DISPLAY_PADDING, padding);
+            mCaller.sendMessage(msg);
+        }
+
         public void setVisibility(boolean visible) {
             Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED,
                     visible ? 1 : 0);
@@ -1041,6 +1166,9 @@
                     mEngine.doDesiredSizeChanged(message.arg1, message.arg2);
                     return;
                 }
+                case DO_SET_DISPLAY_PADDING: {
+                    mEngine.doDisplayPaddingChanged((Rect) message.obj);
+                }
                 case MSG_UPDATE_SURFACE:
                     mEngine.updateSurface(true, false, false);
                     break;
@@ -1102,9 +1230,9 @@
 
         @Override
         public void attach(IWallpaperConnection conn, IBinder windowToken,
-                int windowType, boolean isPreview, int reqWidth, int reqHeight) {
+                int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
             new IWallpaperEngineWrapper(mTarget, conn, windowToken,
-                    windowType, isPreview, reqWidth, reqHeight);
+                    windowType, isPreview, reqWidth, reqHeight, padding);
         }
     }
 
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 0f3f182..037ed28 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -177,6 +177,11 @@
 
     void wallpaperOffsetsComplete(IBinder window);
 
+    /**
+     * Apply a raw offset to the wallpaper service when shown behind this window.
+     */
+    void setWallpaperDisplayOffset(IBinder windowToken, int x, int y);
+
     Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
             int z, in Bundle extras, boolean sync);
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 80b9ade..43ab4ef 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -121,7 +121,7 @@
     private static final String PROPERTY_MEDIA_DISABLED = "config.disable_media";
 
     // property used by emulator to determine display shape
-    private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
+    public static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
 
     /**
      * Maximum time we allow the user to roll the trackball enough to generate
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 571a8f0..24c3c1a 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -378,35 +378,75 @@
     }
 
     /**
-     * @hide
+     * Returns the top stable inset in pixels.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return The top stable inset
      */
     public int getStableInsetTop() {
         return mStableInsets.top;
     }
 
     /**
-     * @hide
+     * Returns the left stable inset in pixels.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return The left stable inset
      */
     public int getStableInsetLeft() {
         return mStableInsets.left;
     }
 
     /**
-     * @hide
+     * Returns the right stable inset in pixels.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return The right stable inset
      */
     public int getStableInsetRight() {
         return mStableInsets.right;
     }
 
     /**
-     * @hide
+     * Returns the bottom stable inset in pixels.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return The bottom stable inset
      */
     public int getStableInsetBottom() {
         return mStableInsets.bottom;
     }
 
     /**
-     * @hide
+     * Returns true if this WindowInsets has nonzero stable insets.
+     *
+     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
+     * partially or fully obscured by the system UI elements.  This value does not change
+     * based on the visibility state of those elements; for example, if the status bar is
+     * normally shown, but temporarily hidden, the stable inset will still provide the inset
+     * associated with the status bar being shown.</p>
+     *
+     * @return true if any of the stable inset values are nonzero
      */
     public boolean hasStableInsets() {
         return mStableInsets.top != 0 || mStableInsets.left != 0 || mStableInsets.right != 0
@@ -414,7 +454,9 @@
     }
 
     /**
-     * @hide
+     * Returns a copy of this WindowInsets with the stable insets fully consumed.
+     *
+     * @return A modified copy of this WindowInsets
      */
     public WindowInsets consumeStableInsets() {
         final WindowInsets result = new WindowInsets(this);
diff --git a/core/java/com/android/internal/util/ParcelableString.java b/core/java/com/android/internal/util/ParcelableString.java
new file mode 100644
index 0000000..6bd856f
--- /dev/null
+++ b/core/java/com/android/internal/util/ParcelableString.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Helper class to adapt a simple String to cases where a Parcelable is expected.
+ * @hide
+ */
+public class ParcelableString implements Parcelable {
+    public String string;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(string);
+    }
+
+    public static final Parcelable.Creator<ParcelableString> CREATOR =
+            new Parcelable.Creator<ParcelableString>() {
+                @Override
+                public ParcelableString createFromParcel(Parcel in) {
+                    ParcelableString ret = new ParcelableString();
+                    ret.string = in.readString();
+                    return ret;
+                }
+                @Override
+                public ParcelableString[] newArray(int size) {
+                    return new ParcelableString[size];
+                }
+    };
+}
\ No newline at end of file
diff --git a/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java b/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java
index b6a03d9..d4244ba 100644
--- a/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java
+++ b/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java
@@ -116,16 +116,16 @@
         assertEquals(TRANSFORM_MATRIX, info.getMatrix());
         for (int i = 0; i < MANY_BOUNDS.length; i++) {
             final RectF expectedBounds = MANY_BOUNDS[i];
-            assertEquals(expectedBounds, info.getCharacterRect(i));
+            assertEquals(expectedBounds, info.getCharacterBounds(i));
         }
-        assertNull(info.getCharacterRect(-1));
-        assertNull(info.getCharacterRect(MANY_BOUNDS.length + 1));
+        assertNull(info.getCharacterBounds(-1));
+        assertNull(info.getCharacterBounds(MANY_BOUNDS.length + 1));
         for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) {
             final int expectedFlags = MANY_FLAGS_ARRAY[i];
-            assertEquals(expectedFlags, info.getCharacterRectFlags(i));
+            assertEquals(expectedFlags, info.getCharacterBoundsFlags(i));
         }
-        assertEquals(0, info.getCharacterRectFlags(-1));
-        assertEquals(0, info.getCharacterRectFlags(MANY_BOUNDS.length + 1));
+        assertEquals(0, info.getCharacterBoundsFlags(-1));
+        assertEquals(0, info.getCharacterBoundsFlags(MANY_BOUNDS.length + 1));
 
         // Make sure that the builder can reproduce the same object.
         final CursorAnchorInfo info2 = builder.build();
@@ -141,16 +141,16 @@
         assertEquals(TRANSFORM_MATRIX, info2.getMatrix());
         for (int i = 0; i < MANY_BOUNDS.length; i++) {
             final RectF expectedBounds = MANY_BOUNDS[i];
-            assertEquals(expectedBounds, info2.getCharacterRect(i));
+            assertEquals(expectedBounds, info2.getCharacterBounds(i));
         }
-        assertNull(info2.getCharacterRect(-1));
-        assertNull(info2.getCharacterRect(MANY_BOUNDS.length + 1));
+        assertNull(info2.getCharacterBounds(-1));
+        assertNull(info2.getCharacterBounds(MANY_BOUNDS.length + 1));
         for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) {
             final int expectedFlags = MANY_FLAGS_ARRAY[i];
-            assertEquals(expectedFlags, info2.getCharacterRectFlags(i));
+            assertEquals(expectedFlags, info2.getCharacterBoundsFlags(i));
         }
-        assertEquals(0, info2.getCharacterRectFlags(-1));
-        assertEquals(0, info2.getCharacterRectFlags(MANY_BOUNDS.length + 1));
+        assertEquals(0, info2.getCharacterBoundsFlags(-1));
+        assertEquals(0, info2.getCharacterBoundsFlags(MANY_BOUNDS.length + 1));
         assertEquals(info, info2);
         assertEquals(info.hashCode(), info2.hashCode());
 
@@ -168,16 +168,16 @@
         assertEquals(TRANSFORM_MATRIX, info3.getMatrix());
         for (int i = 0; i < MANY_BOUNDS.length; i++) {
             final RectF expectedBounds = MANY_BOUNDS[i];
-            assertEquals(expectedBounds, info3.getCharacterRect(i));
+            assertEquals(expectedBounds, info3.getCharacterBounds(i));
         }
-        assertNull(info3.getCharacterRect(-1));
-        assertNull(info3.getCharacterRect(MANY_BOUNDS.length + 1));
+        assertNull(info3.getCharacterBounds(-1));
+        assertNull(info3.getCharacterBounds(MANY_BOUNDS.length + 1));
         for (int i = 0; i < MANY_FLAGS_ARRAY.length; i++) {
             final int expectedFlags = MANY_FLAGS_ARRAY[i];
-            assertEquals(expectedFlags, info3.getCharacterRectFlags(i));
+            assertEquals(expectedFlags, info3.getCharacterBoundsFlags(i));
         }
-        assertEquals(0, info3.getCharacterRectFlags(-1));
-        assertEquals(0, info3.getCharacterRectFlags(MANY_BOUNDS.length + 1));
+        assertEquals(0, info3.getCharacterBoundsFlags(-1));
+        assertEquals(0, info3.getCharacterBoundsFlags(MANY_BOUNDS.length + 1));
         assertEquals(info.hashCode(), info3.hashCode());
 
         builder.reset();
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index 60fd7f7..a93891a4 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -15,6 +15,8 @@
  */
 package android.security;
 
+import android.content.pm.ParceledListSlice;
+
 /**
  * Caller is required to ensure that {@link KeyStore#unlock
  * KeyStore.unlock} was successful.
@@ -32,6 +34,11 @@
     // APIs used by Settings
     boolean deleteCaCertificate(String alias);
     boolean reset();
+    ParceledListSlice getUserCaAliases();
+    ParceledListSlice getSystemCaAliases();
+    boolean containsCaAlias(String alias);
+    byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem);
+    List<String> getCaCertificateChainAliases(String rootAlias, boolean includeDeletedSystem);
 
     // APIs used by KeyChainActivity
     void setGrant(int uid, String alias, boolean value);
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 0da2b99..131e689 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -397,7 +397,8 @@
         return KeyStore.getInstance().isHardwareBacked(algorithm);
     }
 
-    private static X509Certificate toCertificate(byte[] bytes) {
+    /** @hide */
+    public static X509Certificate toCertificate(byte[] bytes) {
         if (bytes == null) {
             throw new IllegalArgumentException("bytes == null");
         }
diff --git a/media/java/android/media/tv/TvContentRating.java b/media/java/android/media/tv/TvContentRating.java
index 7d1c7c6..b4ec2b3 100644
--- a/media/java/android/media/tv/TvContentRating.java
+++ b/media/java/android/media/tv/TvContentRating.java
@@ -247,6 +247,10 @@
  *         <td>TV content rating system for Iceland</td>
  *     </tr>
  *     <tr>
+ *         <td>JP_TV</td>
+ *         <td>TV content rating system for Japan</td>
+ *     </tr>
+ *     <tr>
  *         <td>KR_TV</td>
  *         <td>TV content rating system for South Korea</td>
  *     </tr>
@@ -912,6 +916,23 @@
  *         <td>Programs suitable for ages 18 and older</td>
  *     </tr>
  *     <tr>
+ *         <td valign="top" rowspan="4">JP_TV</td>
+ *         <td>JP_TV_G</td>
+ *         <td>General, suitable for all ages</td>
+ *     </tr>
+ *     <tr>
+ *         <td>JP_TV_PG12</td>
+ *         <td>Parental guidance requested for young people under 12 years</td>
+ *     </tr>
+ *     <tr>
+ *         <td>JP_TV_R15</td>
+ *         <td>For persons aged 15 and above only</td>
+ *     </tr>
+ *     <tr>
+ *         <td>JP_TV_R18</td>
+ *         <td>For persons aged 18 and above only</td>
+ *     </tr>
+ *     <tr>
  *         <td valign="top" rowspan="5">KR_TV</td>
  *         <td>KR_TV_ALL</td>
  *         <td>Appropriate for all ages</td>
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
index 3a8a17d..5889c55 100644
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ b/packages/SystemUI/res/layout/signal_cluster_view.xml
@@ -43,6 +43,12 @@
             android:layout_width="wrap_content"
             />
     </FrameLayout>
+    <View
+        android:id="@+id/wifi_signal_spacer"
+        android:layout_width="4dp"
+        android:layout_height="4dp"
+        android:visibility="gone"
+        />
     <FrameLayout
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8cd4ce6..4495318 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -476,4 +476,12 @@
     <fraction name="battery_subpixel_smoothing_right">0%</fraction>
 
     <dimen name="battery_margin_bottom">0dp</dimen>
+
+    <!-- Extra padding between the mobile data type icon and the strength indicator when the data
+         type icon is wide. -->
+    <dimen name="wide_type_icon_start_padding">2dp</dimen>
+
+    <!-- Extra padding between the mobile data type icon and the strength indicator when the data
+         type icon is wide for the tile in quick settings. -->
+    <dimen name="wide_type_icon_start_padding_qs">3dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 23d9748..147efaf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -798,6 +798,12 @@
     <!-- Notification when resuming an existing guest session: Action that continues with the current session [CHAR LIMIT=35] -->
     <string name="guest_wipe_session_dontwipe">Yes, continue</string>
 
+    <!-- Title for add user confirmation dialog [CHAR LIMIT=30] -->
+    <string name="user_add_user_title" msgid="2108112641783146007">Add new user?</string>
+
+    <!-- Message for add user confirmation dialog - short version. [CHAR LIMIT=none] -->
+    <string name="user_add_user_message_short" msgid="1511354412249044381">When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. </string>
+
 
     <!-- Zen mode condition: time duration in minutes. [CHAR LIMIT=NONE] -->
     <plurals name="zen_mode_duration_minutes">
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 8d35eb0..50ba68e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -487,7 +488,7 @@
         mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
 
         mLockPatternUtils = new LockPatternUtils(mContext);
-        mLockPatternUtils.setCurrentUser(UserHandle.USER_OWNER);
+        mLockPatternUtils.setCurrentUser(ActivityManager.getCurrentUser());
 
         // Assume keyguard is showing (unless it's disabled) until we know for sure...
         mShowing = (mUpdateMonitor.isDeviceProvisioned() || mLockPatternUtils.isSecure())
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 2b071cc..cb685fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -373,6 +373,7 @@
         public boolean activityOut;
         public int overlayIconId;
         public boolean filter;
+        public boolean isOverlayIconWide;
 
         @Override
         public boolean copyTo(State other) {
@@ -380,13 +381,15 @@
             final boolean changed = o.enabled != enabled
                     || o.connected != connected || o.activityIn != activityIn
                     || o.activityOut != activityOut
-                    || o.overlayIconId != overlayIconId;
+                    || o.overlayIconId != overlayIconId
+                    || o.isOverlayIconWide != isOverlayIconWide;
             o.enabled = enabled;
             o.connected = connected;
             o.activityIn = activityIn;
             o.activityOut = activityOut;
             o.overlayIconId = overlayIconId;
             o.filter = filter;
+            o.isOverlayIconWide = isOverlayIconWide;
             return super.copyTo(other) || changed;
         }
 
@@ -399,6 +402,7 @@
             rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
             rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
             rt.insert(rt.length() - 1, ",filter=" + filter);
+            rt.insert(rt.length() - 1, ",wideOverlayIcon=" + isOverlayIconWide);
             return rt;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index e6175d2..8d7edb9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -230,7 +230,7 @@
         final int w = MeasureSpec.getSize(widthMeasureSpec);
         final int h = MeasureSpec.getSize(heightMeasureSpec);
         final int iconSpec = exactly(mIconSizePx);
-        mIcon.measure(iconSpec, iconSpec);
+        mIcon.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.AT_MOST), iconSpec);
         labelView().measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST));
         if (mDual) {
             mDivider.measure(widthMeasureSpec, exactly(mDivider.getLayoutParams().height));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
index 0ecdeaa..cfcd74e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
@@ -37,11 +37,16 @@
     private ImageView mIn;
     private ImageView mOut;
 
+    private int mWideOverlayIconStartPadding;
+
     public SignalTileView(Context context) {
         super(context);
 
         mIn = addTrafficView(R.drawable.ic_qs_signal_in);
         mOut = addTrafficView(R.drawable.ic_qs_signal_out);
+
+        mWideOverlayIconStartPadding = context.getResources().getDimensionPixelSize(
+                R.dimen.wide_type_icon_start_padding_qs);
     }
 
     private ImageView addTrafficView(int icon) {
@@ -106,6 +111,11 @@
         } else {
             mOverlay.setVisibility(GONE);
         }
+        if (s.overlayIconId > 0 && s.isOverlayIconWide) {
+            mSignal.setPaddingRelative(mWideOverlayIconStartPadding, 0, 0, 0);
+        } else {
+            mSignal.setPaddingRelative(0, 0, 0, 0);
+        }
         Drawable drawable = mSignal.getDrawable();
         if (state.autoMirrorDrawable && drawable != null) {
             drawable.setAutoMirrored(true);
@@ -122,10 +132,9 @@
             view.animate()
                 .setDuration(visible ? SHORT_DURATION : DEFAULT_DURATION)
                 .alpha(newAlpha)
-                .withLayer()
                 .start();
         } else {
             view.setAlpha(newAlpha);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 8743207..359a259 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -23,10 +23,8 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.TextView;
 
 import com.android.systemui.R;
-import com.android.systemui.qs.DataUsageGraph;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.QSTileView;
 import com.android.systemui.qs.SignalTileView;
@@ -34,8 +32,6 @@
 import com.android.systemui.statusbar.policy.NetworkController.DataUsageInfo;
 import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
 
-import java.text.DecimalFormat;
-
 /** Quick settings tile: Cellular **/
 public class CellularTile extends QSTile<QSTile.SignalState> {
     private static final Intent CELLULAR_SETTINGS = new Intent().setComponent(new ComponentName(
@@ -95,6 +91,7 @@
                 : !cb.enabled || cb.airplaneModeEnabled ? R.drawable.ic_qs_signal_disabled
                 : cb.mobileSignalIconId > 0 ? cb.mobileSignalIconId
                 : R.drawable.ic_qs_signal_no_signal;
+        state.isOverlayIconWide = cb.isDataTypeIconWide;
         state.autoMirrorDrawable = !cb.noSim;
         state.overlayIconId = cb.enabled && (cb.dataTypeIconId > 0) && !cb.wifiConnected
                 ? cb.dataTypeIconId
@@ -142,6 +139,7 @@
         boolean activityOut;
         String enabledDesc;
         boolean noSim;
+        boolean isDataTypeIconWide;
     }
 
     private final NetworkSignalChangedCallback mCallback = new NetworkSignalChangedCallback() {
@@ -162,7 +160,8 @@
                 int mobileSignalIconId,
                 String mobileSignalContentDescriptionId, int dataTypeIconId,
                 boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description, boolean noSim) {
+                String dataTypeContentDescriptionId, String description, boolean noSim,
+                boolean isDataTypeIconWide) {
             final CallbackInfo info = new CallbackInfo();  // TODO pool?
             info.enabled = enabled;
             info.wifiEnabled = mWifiEnabled;
@@ -176,6 +175,7 @@
             info.activityOut = activityOut;
             info.enabledDesc = description;
             info.noSim = noSim;
+            info.isDataTypeIconWide = isDataTypeIconWide;
             refreshState(info);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 4fc2ee4..0985812 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -206,7 +206,8 @@
                 int mobileSignalIconId,
                 String mobileSignalContentDescriptionId, int dataTypeIconId,
                 boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description, boolean noSim) {
+                String dataTypeContentDescriptionId, String description, boolean noSim,
+                boolean isDataTypeIconWide) {
             // noop
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index f4587db..4d85352 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -39,6 +39,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -70,6 +71,7 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
 import android.widget.DateTimeView;
 import android.widget.ImageView;
@@ -160,6 +162,7 @@
     final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
 
     protected int mLayoutDirection = -1; // invalid
+    protected AccessibilityManager mAccessibilityManager;
     private Locale mLocale;
     private float mFontScale;
 
@@ -441,6 +444,9 @@
 
         mNotificationData = new NotificationData(this);
 
+        mAccessibilityManager = (AccessibilityManager)
+                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
         mDreamManager = IDreamManager.Stub.asInterface(
                 ServiceManager.checkService(DreamService.DREAM_SERVICE));
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -681,19 +687,11 @@
 
     protected void applyColorsAndBackgrounds(StatusBarNotification sbn,
             NotificationData.Entry entry) {
-        PackageManager pmUser = getPackageManagerForUser(
-                entry.notification.getUser().getIdentifier());
-        int version = 0;
-        try {
-            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
-            version = info.targetSdkVersion;
-        } catch (NameNotFoundException ex) {
-            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
-        }
 
         if (entry.expanded.getId() != com.android.internal.R.id.status_bar_latest_event_content) {
             // Using custom RemoteViews
-            if (version >= Build.VERSION_CODES.GINGERBREAD && version < Build.VERSION_CODES.L) {
+            if (entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
+                    && entry.targetSdk < Build.VERSION_CODES.L) {
                 entry.row.setShowingLegacyBackground(true);
                 entry.legacy = true;
             }
@@ -709,7 +707,7 @@
         }
 
         if (entry.icon != null) {
-            if (version >= Build.VERSION_CODES.L) {
+            if (entry.targetSdk >= Build.VERSION_CODES.L) {
                 entry.icon.setColorFilter(mContext.getResources().getColor(android.R.color.white));
             } else {
                 entry.icon.setColorFilter(null);
@@ -1322,6 +1320,14 @@
             }
         }
 
+        // Extract target SDK version.
+        try {
+            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
+            entry.targetSdk = info.targetSdkVersion;
+        } catch (NameNotFoundException ex) {
+            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
+        }
+
         if (publicViewLocal == null) {
             // Add a basic notification template
             publicViewLocal = LayoutInflater.from(mContext).inflate(
@@ -1352,12 +1358,17 @@
 
             Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);
             icon.setImageDrawable(iconDrawable);
-            if (mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) {
+            if (entry.targetSdk >= Build.VERSION_CODES.L
+                    || mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) {
                 icon.setBackgroundResource(
                         com.android.internal.R.drawable.notification_icon_legacy_bg);
                 int padding = mContext.getResources().getDimensionPixelSize(
                         com.android.internal.R.dimen.notification_large_icon_circle_padding);
                 icon.setPadding(padding, padding, padding, padding);
+                if (sbn.getNotification().color != Notification.COLOR_DEFAULT) {
+                    icon.getBackground().setColorFilter(
+                            sbn.getNotification().color, PorterDuff.Mode.SRC_ATOP);
+                }
             }
 
             if (profileBadge != null) {
@@ -1918,9 +1929,6 @@
                 : null;
 
         // Reapply the RemoteViews
-        if (entry.row != null) {
-            entry.row.resetHeight();
-        }
         contentView.reapply(mContext, entry.expanded, mOnClickHandler);
         if (bigContentView != null && entry.getBigContentView() != null) {
             bigContentView.reapply(mContext, entry.getBigContentView(),
@@ -1939,6 +1947,7 @@
             entry.row.setOnClickListener(null);
         }
         entry.row.notifyContentUpdated();
+        entry.row.resetHeight();
     }
 
     protected void notifyHeadsUpScreenOn(boolean screenOn) {
@@ -1971,10 +1980,13 @@
         boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText);
         boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
                 Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
+        boolean accessibilityForcesLaunch = isFullscreen
+                && mAccessibilityManager.isTouchExplorationEnabled();
 
         final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext);
         boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
                 && isAllowed
+                && !accessibilityForcesLaunch
                 && mPowerManager.isScreenOn()
                 && !keyguard.isShowingAndNotOccluded()
                 && !keyguard.isInputRestricted();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index ca1fbe0..34c458a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -48,6 +48,7 @@
         private boolean interruption;
         public boolean autoRedacted; // whether the redacted notification was generated by us
         public boolean legacy; // whether the notification has a legacy, dark background
+        public int targetSdk;
 
         public Entry(StatusBarNotification n, StatusBarIconView ic) {
             this.key = n.getKey();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 740211f..18ef024 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -50,10 +50,14 @@
     private int mAirplaneIconId = 0;
     private String mWifiDescription, mMobileDescription, mMobileTypeDescription;
     private boolean mRoaming;
+    private boolean mIsMobileTypeIconWide;
 
     ViewGroup mWifiGroup, mMobileGroup;
     ImageView mVpn, mWifi, mMobile, mMobileType, mAirplane;
     View mWifiAirplaneSpacer;
+    View mWifiSignalSpacer;
+
+    private int mWideTypeIconStartPadding;
 
     public SignalClusterView(Context context) {
         this(context, null);
@@ -80,6 +84,13 @@
     }
 
     @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mWideTypeIconStartPadding = getContext().getResources().getDimensionPixelSize(
+                R.dimen.wide_type_icon_start_padding);
+    }
+
+    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
@@ -91,6 +102,7 @@
         mMobileType     = (ImageView) findViewById(R.id.mobile_type);
         mAirplane       = (ImageView) findViewById(R.id.airplane);
         mWifiAirplaneSpacer =         findViewById(R.id.wifi_airplane_spacer);
+        mWifiSignalSpacer =           findViewById(R.id.wifi_signal_spacer);
 
         apply();
     }
@@ -131,13 +143,15 @@
 
     @Override
     public void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
-            String contentDescription, String typeContentDescription, boolean roaming) {
+            String contentDescription, String typeContentDescription, boolean roaming,
+            boolean isTypeIconWide) {
         mMobileVisible = visible;
         mMobileStrengthId = strengthIcon;
         mMobileTypeId = typeIcon;
         mMobileDescription = contentDescription;
         mMobileTypeDescription = typeContentDescription;
         mRoaming = roaming;
+        mIsMobileTypeIconWide = isTypeIconWide;
 
         apply();
     }
@@ -230,6 +244,14 @@
             mWifiAirplaneSpacer.setVisibility(View.GONE);
         }
 
+        if (mRoaming && mMobileVisible && mWifiVisible) {
+            mWifiSignalSpacer.setVisibility(View.VISIBLE);
+        } else {
+            mWifiSignalSpacer.setVisibility(View.GONE);
+        }
+
+        mMobile.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0, 0, 0, 0);
+
         if (DEBUG) Log.d(TAG,
                 String.format("mobile: %s sig=%d typ=%d",
                     (mMobileVisible ? "VISIBLE" : "GONE"),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
index c620046..64d80cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
@@ -90,7 +90,6 @@
                     .alpha(endValue)
                     .setInterpolator(interpolator)
                     .setDuration(260)
-                    .withLayer()
                     .withEndAction(new Runnable() {
                         @Override
                         public void run() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index dc49118..685c184 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -74,8 +74,10 @@
                     mKeyguardUserSwitcher.show(true /* animate */);
                 }
             } else {
-                mQsPanel.showDetailAdapter(true,
-                        mQsPanel.getHost().getUserSwitcherController().userDetailAdapter);
+                if (mQsPanel != null) {
+                    mQsPanel.showDetailAdapter(true,
+                            mQsPanel.getHost().getUserSwitcherController().userDetailAdapter);
+                }
             }
         } else {
             Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
@@ -93,9 +95,12 @@
             final UserManager um = UserManager.get(getContext());
             String text;
             if (um.isUserSwitcherEnabled()) {
-                UserSwitcherController controller = mQsPanel.getHost()
-                        .getUserSwitcherController();
-                String currentUser = controller.getCurrentUserName(mContext);
+                String currentUser = null;
+                if (mQsPanel != null) {
+                    UserSwitcherController controller = mQsPanel.getHost()
+                            .getUserSwitcherController();
+                    currentUser = controller.getCurrentUserName(mContext);
+                }
                 if (TextUtils.isEmpty(currentUser)) {
                     text = mContext.getString(R.string.accessibility_multi_user_switch_switcher);
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index cf5aebc..0499b14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -559,7 +559,7 @@
     }
 
     private boolean isBelowFalsingThreshold() {
-        return !mQsTouchAboveFalsingThreshold && mStatusBar.isFalsingThresholdNeeded();
+        return !mQsTouchAboveFalsingThreshold && mStatusBarState == StatusBarState.KEYGUARD;
     }
 
     private float getQsExpansionFraction() {
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 128c687..e939057 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1082,7 +1082,6 @@
     };
 
     private long mLastLockToAppLongPress;
-    private AccessibilityManager mAccessibilityManager;
     private View.OnLongClickListener mLongPressBackRecentsListener =
             new View.OnLongClickListener() {
         @Override
@@ -3874,10 +3873,6 @@
         try {
             boolean sendBackLongPress = false;
             IActivityManager activityManager = ActivityManagerNative.getDefault();
-            if (mAccessibilityManager == null) {
-                mAccessibilityManager = (AccessibilityManager)
-                        mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
-            }
             boolean isAccessiblityEnabled = mAccessibilityManager.isEnabled();
             if (activityManager.isInLockTaskMode() && !isAccessiblityEnabled) {
                 long time = System.currentTimeMillis();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index b2009c3..7f155a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -28,6 +28,7 @@
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 
 import com.android.systemui.ExpandHelper;
@@ -111,6 +112,7 @@
             mContentHolder.setVisibility(View.VISIBLE);
             mContentHolder.setAlpha(mMaxAlpha);
             mContentHolder.addView(mHeadsUp.row);
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
 
             mSwipeHelper.snapChild(mContentHolder, 1f);
             mStartTouchTime = System.currentTimeMillis() + mTouchSensitivityDelay;
@@ -126,6 +128,14 @@
         return true;
     }
 
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        if (changedView.getVisibility() == VISIBLE) {
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        }
+    }
+
     public boolean isShowing(String key) {
         return mHeadsUp != null && mHeadsUp.key.equals(key);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index b64dcbe..2ed9366 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -30,7 +30,8 @@
         void onMobileDataSignalChanged(boolean enabled, int mobileSignalIconId,
                 String mobileSignalContentDescriptionId, int dataTypeIconId,
                 boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description, boolean noSim);
+                String dataTypeContentDescriptionId, String description, boolean noSim,
+                boolean isDataTypeIconWide);
         void onAirplaneModeChanged(boolean enabled);
         void onMobileDataEnabled(boolean enabled);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f04d6a5..5088089 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -63,9 +63,6 @@
     static final boolean DEBUG = false;
     static final boolean CHATTY = false; // additional diagnostics, but not logspew
 
-    private static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;
-    private static final int ROAMING_ICON = R.drawable.stat_sys_data_fully_connected_roam;
-
     // telephony
     boolean mHspaDataDistinguishable;
     final TelephonyManager mPhone;
@@ -165,7 +162,8 @@
     public interface SignalCluster {
         void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
         void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
-                String contentDescription, String typeContentDescription, boolean roaming);
+                String contentDescription, String typeContentDescription, boolean roaming,
+                boolean isTypeIconWide);
         void setIsAirplaneMode(boolean is, int airplaneIcon);
     }
 
@@ -358,6 +356,16 @@
         mMobileDataController.setMobileDataEnabled(enabled);
     }
 
+    private boolean isTypeIconWide(int iconId) {
+        return TelephonyIcons.ICON_LTE == iconId || TelephonyIcons.ICON_1X == iconId
+                || TelephonyIcons.ICON_3G == iconId || TelephonyIcons.ICON_4G == iconId;
+    }
+
+    private boolean isQsTypeIconWide(int iconId) {
+        return TelephonyIcons.QS_ICON_LTE == iconId || TelephonyIcons.QS_ICON_1X == iconId
+                || TelephonyIcons.QS_ICON_3G == iconId || TelephonyIcons.QS_ICON_4G == iconId;
+    }
+
     public void refreshSignalCluster(SignalCluster cluster) {
         if (mDemoMode) return;
         cluster.setWifiIndicators(
@@ -374,7 +382,8 @@
                     mDataTypeIconId,
                     mContentDescriptionWimax,
                     mContentDescriptionDataType,
-                    mDataTypeIconId == ROAMING_ICON);
+                    mDataTypeIconId == TelephonyIcons.ROAMING_ICON,
+                    false /* isTypeIconWide */ );
         } else {
             // normal mobile data
             cluster.setMobileDataIndicators(
@@ -383,7 +392,8 @@
                     mDataTypeIconId,
                     mContentDescriptionPhoneSignal,
                     mContentDescriptionDataType,
-                    mDataTypeIconId == ROAMING_ICON);
+                    mDataTypeIconId == TelephonyIcons.ROAMING_ICON,
+                    isTypeIconWide(mDataTypeIconId));
         }
         cluster.setIsAirplaneMode(mAirplaneMode, mAirplaneIconId);
     }
@@ -409,18 +419,20 @@
         if (isEmergencyOnly()) {
             cb.onMobileDataSignalChanged(false, mQSPhoneSignalIconId,
                     mContentDescriptionPhoneSignal, mQSDataTypeIconId, mobileIn, mobileOut,
-                    mContentDescriptionDataType, null, mNoSim);
+                    mContentDescriptionDataType, null, mNoSim, isQsTypeIconWide(mQSDataTypeIconId));
         } else {
             if (mIsWimaxEnabled && mWimaxConnected) {
                 // Wimax is special
                 cb.onMobileDataSignalChanged(true, mQSPhoneSignalIconId,
                         mContentDescriptionPhoneSignal, mQSDataTypeIconId, mobileIn, mobileOut,
-                        mContentDescriptionDataType, mNetworkName, mNoSim);
+                        mContentDescriptionDataType, mNetworkName, mNoSim,
+                        isQsTypeIconWide(mQSDataTypeIconId));
             } else {
                 // Normal mobile data
                 cb.onMobileDataSignalChanged(mHasMobileDataFeature, mQSPhoneSignalIconId,
                         mContentDescriptionPhoneSignal, mQSDataTypeIconId, mobileIn, mobileOut,
-                        mContentDescriptionDataType, mNetworkName, mNoSim);
+                        mContentDescriptionDataType, mNetworkName, mNoSim,
+                        isQsTypeIconWide(mQSDataTypeIconId));
             }
         }
         cb.onAirplaneModeChanged(mAirplaneMode);
@@ -754,7 +766,7 @@
                                 R.string.accessibility_data_connection_4g);
                     } else {
                         mDataIconList = TelephonyIcons.DATA_LTE[mInetCondition];
-                        mDataTypeIconId = R.drawable.stat_sys_data_fully_connected_lte;
+                        mDataTypeIconId = TelephonyIcons.ICON_LTE;
                         mQSDataTypeIconId = TelephonyIcons.QS_DATA_LTE[mInetCondition];
                         mContentDescriptionDataType = mContext.getString(
                                 R.string.accessibility_data_connection_lte);
@@ -780,11 +792,11 @@
 
         if (isCdma()) {
             if (isCdmaEri()) {
-                mDataTypeIconId = ROAMING_ICON;
+                mDataTypeIconId = TelephonyIcons.ROAMING_ICON;
                 mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition];
             }
         } else if (mPhone.isNetworkRoaming()) {
-                mDataTypeIconId = ROAMING_ICON;
+                mDataTypeIconId = TelephonyIcons.ROAMING_ICON;
                 mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition];
         }
     }
@@ -1164,7 +1176,7 @@
             // look again; your radios are now airplanes
             mContentDescriptionPhoneSignal = mContext.getString(
                     R.string.accessibility_airplane_mode);
-            mAirplaneIconId = FLIGHT_MODE_ICON;
+            mAirplaneIconId = TelephonyIcons.FLIGHT_MODE_ICON;
             mPhoneSignalIconId = mDataSignalIconId = mDataTypeIconId = mQSDataTypeIconId = 0;
             mQSPhoneSignalIconId = 0;
 
@@ -1198,11 +1210,11 @@
             mQSDataTypeIconId = 0;
             if (isCdma()) {
                 if (isCdmaEri()) {
-                    mDataTypeIconId = ROAMING_ICON;
+                    mDataTypeIconId = TelephonyIcons.ROAMING_ICON;
                     mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition];
                 }
             } else if (mPhone.isNetworkRoaming()) {
-                mDataTypeIconId = ROAMING_ICON;
+                mDataTypeIconId = TelephonyIcons.ROAMING_ICON;
                 mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition];
             }
         }
@@ -1509,7 +1521,7 @@
             if (airplane != null) {
                 boolean show = airplane.equals("show");
                 for (SignalCluster cluster : mSignalClusters) {
-                    cluster.setIsAirplaneMode(show, FLIGHT_MODE_ICON);
+                    cluster.setIsAirplaneMode(show, TelephonyIcons.FLIGHT_MODE_ICON);
                 }
             }
             String fully = args.getString("fully");
@@ -1540,23 +1552,23 @@
                 String datatype = args.getString("datatype");
                 if (datatype != null) {
                     mDemoDataTypeIconId =
-                            datatype.equals("1x") ? R.drawable.stat_sys_data_fully_connected_1x :
-                            datatype.equals("3g") ? R.drawable.stat_sys_data_fully_connected_3g :
-                            datatype.equals("4g") ? R.drawable.stat_sys_data_fully_connected_4g :
+                            datatype.equals("1x") ? TelephonyIcons.ICON_1X :
+                            datatype.equals("3g") ? TelephonyIcons.ICON_3G :
+                            datatype.equals("4g") ? TelephonyIcons.ICON_4G :
                             datatype.equals("e") ? R.drawable.stat_sys_data_fully_connected_e :
                             datatype.equals("g") ? R.drawable.stat_sys_data_fully_connected_g :
                             datatype.equals("h") ? R.drawable.stat_sys_data_fully_connected_h :
-                            datatype.equals("lte") ? R.drawable.stat_sys_data_fully_connected_lte :
-                            datatype.equals("roam") ? ROAMING_ICON :
+                            datatype.equals("lte") ? TelephonyIcons.ICON_LTE :
+                            datatype.equals("roam") ? TelephonyIcons.ROAMING_ICON :
                             0;
                     mDemoQSDataTypeIconId =
-                            datatype.equals("1x") ? R.drawable.ic_qs_signal_1x :
-                            datatype.equals("3g") ? R.drawable.ic_qs_signal_3g :
-                            datatype.equals("4g") ? R.drawable.ic_qs_signal_4g :
+                            datatype.equals("1x") ? TelephonyIcons.QS_ICON_1X :
+                            datatype.equals("3g") ? TelephonyIcons.QS_ICON_3G :
+                            datatype.equals("4g") ? TelephonyIcons.QS_ICON_4G :
                             datatype.equals("e") ? R.drawable.ic_qs_signal_e :
                             datatype.equals("g") ? R.drawable.ic_qs_signal_g :
                             datatype.equals("h") ? R.drawable.ic_qs_signal_h :
-                            datatype.equals("lte") ? R.drawable.ic_qs_signal_lte :
+                            datatype.equals("lte") ? TelephonyIcons.QS_ICON_LTE :
                             datatype.equals("roam") ? R.drawable.ic_qs_signal_r :
                             0;
                 }
@@ -1575,7 +1587,8 @@
                             mDemoDataTypeIconId,
                             "Demo",
                             "Demo",
-                            mDemoDataTypeIconId == ROAMING_ICON);
+                            mDemoDataTypeIconId == TelephonyIcons.ROAMING_ICON,
+                            isTypeIconWide(mDemoDataTypeIconId));
                 }
                 refreshViews();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index 84c53ce..1f2b918 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -188,5 +188,16 @@
         R.drawable.ic_qs_signal_lte
     };
 
+    static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;
+    static final int ROAMING_ICON = R.drawable.stat_sys_data_fully_connected_roam;
+    static final int ICON_LTE = R.drawable.stat_sys_data_fully_connected_lte;
+    static final int ICON_3G = R.drawable.stat_sys_data_fully_connected_3g;
+    static final int ICON_4G = R.drawable.stat_sys_data_fully_connected_4g;
+    static final int ICON_1X = R.drawable.stat_sys_data_fully_connected_1x;
+
+    static final int QS_ICON_LTE = R.drawable.ic_qs_signal_lte;
+    static final int QS_ICON_3G = R.drawable.ic_qs_signal_3g;
+    static final int QS_ICON_4G = R.drawable.ic_qs_signal_4g;
+    static final int QS_ICON_1X = R.drawable.ic_qs_signal_1x;
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 52fa621..d02826f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -73,6 +73,7 @@
 
     private ArrayList<UserRecord> mUsers = new ArrayList<>();
     private Dialog mExitGuestDialog;
+    private Dialog mAddUserDialog;
     private int mLastNonGuestUser = UserHandle.USER_OWNER;
     private boolean mSimpleUserSwitcher;
     private boolean mAddUsersWhenLocked;
@@ -228,8 +229,8 @@
             // No guest user. Create one.
             id = mUserManager.createGuest(mContext, mContext.getString(R.string.guest_nickname)).id;
         } else if (record.isAddUser) {
-            id = mUserManager.createUser(
-                    mContext.getString(R.string.user_new_user_name), 0 /* flags */).id;
+            showAddUserDialog();
+            return;
         } else {
             id = record.info.id;
         }
@@ -260,6 +261,14 @@
         mExitGuestDialog.show();
     }
 
+    private void showAddUserDialog() {
+        if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
+            mAddUserDialog.cancel();
+        }
+        mAddUserDialog = new AddUserDialog(mContext);
+        mAddUserDialog.show();
+    }
+
     private void exitGuest(int id) {
         int newId = UserHandle.USER_OWNER;
         if (mLastNonGuestUser != UserHandle.USER_OWNER) {
@@ -534,4 +543,30 @@
             }
         }
     }
+
+    private final class AddUserDialog extends SystemUIDialog implements
+            DialogInterface.OnClickListener {
+
+        public AddUserDialog(Context context) {
+            super(context);
+            setTitle(R.string.user_add_user_title);
+            setMessage(context.getString(R.string.user_add_user_message_short));
+            setButton(DialogInterface.BUTTON_NEGATIVE,
+                    context.getString(android.R.string.cancel), this);
+            setButton(DialogInterface.BUTTON_POSITIVE,
+                    context.getString(android.R.string.ok), this);
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            if (which == BUTTON_NEGATIVE) {
+                cancel();
+            } else {
+                dismiss();
+                int id = mUserManager.createUser(
+                        mContext.getString(R.string.user_new_user_name), 0 /* flags */).id;
+                switchToUserId(id);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 6f477ef..67ba8d20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -40,6 +40,7 @@
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.SpeedBumpView;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
@@ -645,7 +646,7 @@
 
     @Override
     public boolean isAntiFalsingNeeded() {
-        return mPhoneStatusBar.isFalsingThresholdNeeded();
+        return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
     }
 
     private void setSwipingInProgress(boolean isSwiped) {
@@ -1546,7 +1547,7 @@
         mStackScrollAlgorithm.notifyChildrenChanged(this);
         ((ExpandableView) child).setOnHeightChangedListener(this);
         generateAddAnimation(child, false /* fromMoreCard */);
-        updateAnimationState(mAnimationsEnabled && mIsExpanded, child);
+        updateAnimationState(child);
     }
 
     public void setAnimationsEnabled(boolean animationsEnabled) {
@@ -1563,6 +1564,11 @@
         }
     }
 
+    private void updateAnimationState(View child) {
+        updateAnimationState(mAnimationsEnabled && mIsExpanded, child);
+    }
+
+
     private void updateAnimationState(boolean running, View child) {
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
@@ -1960,6 +1966,7 @@
             mRequestViewResizeAnimationOnLayout = true;
         }
         mStackScrollAlgorithm.onReset(view);
+        updateAnimationState(view);
     }
 
     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 7c4c0e8..e4a1c27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -40,8 +40,6 @@
     private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
     private static final int MAX_ITEMS_IN_TOP_STACK = 3;
 
-    /** When a child is activated, the other cards' alpha fade to this value. */
-    private static final float ACTIVATED_INVERSE_ALPHA = 0.9f;
     public static final float DIMMED_SCALE = 0.95f;
 
     private int mPaddingBetweenElements;
@@ -270,12 +268,8 @@
             childViewState.scale = !mScaleDimmed || !dimmed || isActivatedChild
                     ? 1.0f
                     : DIMMED_SCALE;
-            if (dimmed && activatedChild != null) {
-                if (!isActivatedChild) {
-                    childViewState.alpha *= ACTIVATED_INVERSE_ALPHA;
-                } else {
-                    childViewState.zTranslation += 2.0f * mZDistanceBetweenElements;
-                }
+            if (dimmed && isActivatedChild) {
+                childViewState.zTranslation += 2.0f * mZDistanceBetweenElements;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index ffc10a9..6949ffb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -56,6 +56,7 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.ImageView;
 import android.widget.SeekBar;
@@ -1078,6 +1079,7 @@
             if (mCallback != null) {
                 mCallback.onVisible(true);
             }
+            announceDialogShown();
         }
 
         // Do a little vibrate if applicable (only when going into vibrate mode)
@@ -1094,6 +1096,10 @@
         }
     }
 
+    private void announceDialogShown() {
+        mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+    }
+
     private boolean isShowing() {
         return mDialog.isShowing();
     }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index f39727a..cce30c7 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -2755,12 +2755,14 @@
                         SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
                         mStatusBarColor, mLastTopInset, Gravity.TOP,
                         STATUS_BAR_BACKGROUND_TRANSITION_NAME,
-                        com.android.internal.R.id.statusBarBackground);
+                        com.android.internal.R.id.statusBarBackground,
+                        (getAttributes().flags & FLAG_FULLSCREEN) != 0);
                 mNavigationColorView = updateColorViewInt(mNavigationColorView,
                         SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
                         mNavigationBarColor, mLastBottomInset, Gravity.BOTTOM,
                         NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
-                        com.android.internal.R.id.navigationBarBackground);
+                        com.android.internal.R.id.navigationBarBackground,
+                        false /* hiddenByWindowFlag */);
             }
             if (insets != null) {
                 insets = insets.consumeStableInsets();
@@ -2769,8 +2771,10 @@
         }
 
         private View updateColorViewInt(View view, int systemUiHideFlag, int translucentFlag,
-                int color, int height, int verticalGravity, String transitionName, int id) {
+                int color, int height, int verticalGravity, String transitionName, int id,
+                boolean hiddenByWindowFlag) {
             boolean show = height > 0 && (mLastSystemUiVisibility & systemUiHideFlag) == 0
+                    && !hiddenByWindowFlag
                     && (getAttributes().flags & translucentFlag) == 0
                     && (color & Color.BLACK) != 0
                     && (getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index ab311c04..16bb00b 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -3533,13 +3533,16 @@
                     pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
                             + mOverscanScreenHeight;
                 } else if (attrs.type == TYPE_WALLPAPER) {
-                    // The wallpaper also has Real Ultimate Power.
-                    pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right
-                            = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom
-                            = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                    // The wallpaper also has Real Ultimate Power, but we want to tell
+                    // it about the overscan area.
+                    pf.left = df.left = mOverscanScreenLeft;
+                    pf.top = df.top = mOverscanScreenTop;
+                    pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
+                    pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
+                    of.left = cf.left = mUnrestrictedScreenLeft;
+                    of.top = cf.top = mUnrestrictedScreenTop;
+                    of.right = cf.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+                    of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
                 } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
                         && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
                         && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
@@ -3653,9 +3656,12 @@
 
         // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
         if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && attrs.type != TYPE_SYSTEM_ERROR) {
-            df.left = df.top = of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
-            df.right = df.bottom = of.right = of.bottom = cf.right = cf.bottom
-                    = vf.right = vf.bottom = 10000;
+            df.left = df.top = -10000;
+            df.right = df.bottom = 10000;
+            if (attrs.type != TYPE_WALLPAPER) {
+                of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
+                of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
+            }
         }
 
         if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 9412465..3062a92 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -793,14 +793,28 @@
     }
 
     /**
-     * Check if UID should be blocked from using the network represented by the
-     * given {@link NetworkStateTracker}.
+     * Check if UID should be blocked from using the network represented by the given networkType.
+     * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type.
      */
     private boolean isNetworkBlocked(int networkType, int uid) {
+        return isNetworkWithLinkPropertiesBlocked(getLinkPropertiesForType(networkType), uid);
+    }
+
+    /**
+     * Check if UID should be blocked from using the network represented by the given
+     * NetworkAgentInfo.
+     */
+    private boolean isNetworkBlocked(NetworkAgentInfo nai, int uid) {
+        return isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid);
+    }
+
+    /**
+     * Check if UID should be blocked from using the network with the given LinkProperties.
+     */
+    private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid) {
         final boolean networkCostly;
         final int uidRules;
 
-        LinkProperties lp = getLinkPropertiesForType(networkType);
         final String iface = (lp == null ? "" : lp.getInterfaceName());
         synchronized (mRulesLock) {
             networkCostly = mMeteredIfaces.contains(iface);
@@ -819,17 +833,36 @@
      * Return a filtered {@link NetworkInfo}, potentially marked
      * {@link DetailedState#BLOCKED} based on
      * {@link #isNetworkBlocked}.
+     * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type.
      */
     private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) {
         NetworkInfo info = getNetworkInfoForType(networkType);
         return getFilteredNetworkInfo(info, networkType, uid);
     }
 
+    /*
+     * @deprecated Uses mLegacyTypeTracker; cannot deal with multiple Networks of the same type.
+     */
     private NetworkInfo getFilteredNetworkInfo(NetworkInfo info, int networkType, int uid) {
         if (isNetworkBlocked(networkType, uid)) {
             // network is blocked; clone and override state
             info = new NetworkInfo(info);
             info.setDetailedState(DetailedState.BLOCKED, null, null);
+            if (VDBG) log("returning Blocked NetworkInfo");
+        }
+        if (mLockdownTracker != null) {
+            info = mLockdownTracker.augmentNetworkInfo(info);
+            if (VDBG) log("returning Locked NetworkInfo");
+        }
+        return info;
+    }
+
+    private NetworkInfo getFilteredNetworkInfo(NetworkAgentInfo nai, int uid) {
+        NetworkInfo info = nai.networkInfo;
+        if (isNetworkBlocked(nai, uid)) {
+            // network is blocked; clone and override state
+            info = new NetworkInfo(info);
+            info.setDetailedState(DetailedState.BLOCKED, null, null);
             if (DBG) log("returning Blocked NetworkInfo");
         }
         if (mLockdownTracker != null) {
@@ -946,7 +979,7 @@
         synchronized (nai) {
             if (nai.networkInfo == null) return null;
 
-            return getFilteredNetworkInfo(nai.networkInfo, nai.networkInfo.getType(), uid);
+            return getFilteredNetworkInfo(nai, uid);
         }
     }
 
@@ -1315,6 +1348,12 @@
 //        }
     }
 
+    private void enforceInternetPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.INTERNET,
+                "ConnectivityService");
+    }
+
     private void enforceAccessPermission() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_NETWORK_STATE,
@@ -2468,7 +2507,22 @@
     }
 
     public void reportBadNetwork(Network network) {
-        //TODO
+        enforceAccessPermission();
+        enforceInternetPermission();
+
+        if (network == null) return;
+
+        final int uid = Binder.getCallingUid();
+        NetworkAgentInfo nai = null;
+        synchronized (mNetworkForNetId) {
+            nai = mNetworkForNetId.get(network.netId);
+        }
+        if (nai == null) return;
+        synchronized (nai) {
+            if (isNetworkBlocked(nai, uid)) return;
+
+            nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid);
+        }
     }
 
     public ProxyInfo getProxy() {
@@ -4436,10 +4490,14 @@
             loge("Unknown NetworkAgentInfo in handleLingerComplete");
             return;
         }
-        if (DBG) log("handleLingerComplete for " + oldNetwork.name());
         if (DBG) {
-            if (oldNetwork.networkRequests.size() != 0) {
-                loge("Dead network still had " + oldNetwork.networkRequests.size() + " requests");
+            log("handleLingerComplete for " + oldNetwork.name());
+            for (int i = 0; i < oldNetwork.networkRequests.size(); i++) {
+                NetworkRequest nr = oldNetwork.networkRequests.valueAt(i);
+                // Ignore listening requests.
+                if (mNetworkRequests.get(nr).isRequest == false) continue;
+                loge("Dead network still had at least " + nr);
+                break;
             }
         }
         oldNetwork.asyncChannel.disconnect();
@@ -4463,6 +4521,8 @@
             loge("Unknown NetworkAgentInfo in handleConnectionValidated");
             return;
         }
+        if (newNetwork.validated) return;
+        newNetwork.validated = true;
         boolean keep = newNetwork.isVPN();
         boolean isNewDefault = false;
         if (DBG) log("handleConnectionValidated for "+newNetwork.name());
@@ -4706,6 +4766,8 @@
         // code will fire.
         for (int i = 0; i < nai.networkRequests.size(); i++) {
             NetworkRequest nr = nai.networkRequests.valueAt(i);
+            // Don't send listening requests to factories. b/17393458
+            if (mNetworkRequests.get(nr).isRequest == false) continue;
             sendUpdatedScoreToFactories(nr, score);
         }
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 5a97aee..bba786d 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -47,6 +47,7 @@
     public final NetworkMonitor networkMonitor;
     public final NetworkMisc networkMisc;
     public boolean created;
+    public boolean validated;
 
     // The list of NetworkRequests being satisfied by this Network.
     public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>();
@@ -68,6 +69,7 @@
         networkMonitor = new NetworkMonitor(context, handler, this);
         networkMisc = misc;
         created = false;
+        validated = false;
     }
 
     public void addRequest(NetworkRequest networkRequest) {
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index ff319d3..96872a7 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -27,6 +27,7 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.net.TrafficStats;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
@@ -140,34 +141,28 @@
     private static final int CMD_REEVALUATE = BASE + 6;
 
     /**
-     * Message to self indicating network evaluation is complete.
-     * arg1 = Token to ignore old messages.
-     * arg2 = HTTP response code of network evaluation.
-     */
-    private static final int EVENT_REEVALUATION_COMPLETE = BASE + 7;
-
-    /**
      * Inform NetworkMonitor that the network has disconnected.
      */
-    public static final int CMD_NETWORK_DISCONNECTED = BASE + 8;
+    public static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
 
     /**
      * Force evaluation even if it has succeeded in the past.
+     * arg1 = UID responsible for requesting this reeval.  Will be billed for data.
      */
-    public static final int CMD_FORCE_REEVALUATION = BASE + 9;
+    public static final int CMD_FORCE_REEVALUATION = BASE + 8;
 
     /**
      * Message to self indicating captive portal login is complete.
      * arg1 = Token to ignore old messages.
      * arg2 = 1 if we should use this network, 0 otherwise.
      */
-    private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 10;
+    private static final int CMD_CAPTIVE_PORTAL_LOGGED_IN = BASE + 9;
 
     /**
      * Message to self indicating user desires to log into captive portal.
      * arg1 = Token to ignore old messages.
      */
-    private static final int CMD_USER_WANTS_SIGN_IN = BASE + 11;
+    private static final int CMD_USER_WANTS_SIGN_IN = BASE + 10;
 
     /**
      * Request ConnectivityService display provisioning notification.
@@ -175,22 +170,22 @@
      * arg2    = NetID.
      * obj     = Intent to be launched when notification selected by user, null if !arg1.
      */
-    public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 12;
+    public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 11;
 
     /**
      * Message to self indicating sign-in app bypassed captive portal.
      */
-    private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 13;
+    private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 12;
 
     /**
      * Message to self indicating no sign-in app responded.
      */
-    private static final int EVENT_NO_APP_RESPONSE = BASE + 14;
+    private static final int EVENT_NO_APP_RESPONSE = BASE + 13;
 
     /**
      * Message to self indicating sign-in app indicates sign-in is not possible.
      */
-    private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 15;
+    private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 14;
 
     private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
     // Default to 30s linger time-out.
@@ -205,6 +200,8 @@
     private static final int MAX_RETRIES = 10;
     private final int mReevaluateDelayMs;
     private int mReevaluateToken = 0;
+    private static final int INVALID_UID = -1;
+    private int mUidResponsibleForReeval = INVALID_UID;
 
     private int mCaptivePortalLoggedInToken = 0;
     private int mUserPromptedToken = 0;
@@ -282,6 +279,7 @@
                     return HANDLED;
                 case CMD_FORCE_REEVALUATION:
                     if (DBG) log("Forcing reevaluation");
+                    mUidResponsibleForReeval = message.arg1;
                     transitionTo(mEvaluatingState);
                     return HANDLED;
                 default:
@@ -322,20 +320,14 @@
     private class EvaluatingState extends State {
         private int mRetries;
 
-        private class EvaluateInternetConnectivity extends Thread {
-            private int mToken;
-            EvaluateInternetConnectivity(int token) {
-                mToken = token;
-            }
-            public void run() {
-                sendMessage(EVENT_REEVALUATION_COMPLETE, mToken, isCaptivePortal());
-            }
-        }
-
         @Override
         public void enter() {
             mRetries = 0;
             sendMessage(CMD_REEVALUATE, ++mReevaluateToken, 0);
+            if (mUidResponsibleForReeval != INVALID_UID) {
+                TrafficStats.setThreadStatsUid(mUidResponsibleForReeval);
+                mUidResponsibleForReeval = INVALID_UID;
+            }
         }
 
         @Override
@@ -356,14 +348,7 @@
                         transitionTo(mValidatedState);
                         return HANDLED;
                     }
-                    // Kick off a thread to perform internet connectivity evaluation.
-                    Thread thread = new EvaluateInternetConnectivity(mReevaluateToken);
-                    thread.run();
-                    return HANDLED;
-                case EVENT_REEVALUATION_COMPLETE:
-                    if (message.arg1 != mReevaluateToken)
-                        return HANDLED;
-                    int httpResponseCode = message.arg2;
+                    int httpResponseCode = isCaptivePortal();
                     if (httpResponseCode == 204) {
                         transitionTo(mValidatedState);
                     } else if (httpResponseCode >= 200 && httpResponseCode <= 399) {
@@ -375,10 +360,18 @@
                         sendMessageDelayed(msg, mReevaluateDelayMs);
                     }
                     return HANDLED;
+                case CMD_FORCE_REEVALUATION:
+                    // Ignore duplicate requests.
+                    return HANDLED;
                 default:
                     return NOT_HANDLED;
             }
         }
+
+        @Override
+        public void exit() {
+            TrafficStats.clearThreadStatsUid();
+        }
     }
 
     private class UserPromptedState extends State {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 802df95..e1ade63 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -42,6 +42,7 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
@@ -206,6 +207,8 @@
         int width = -1;
         int height = -1;
 
+        final Rect padding = new Rect(0, 0, 0, 0);
+
         WallpaperData(int userId) {
             this.userId = userId;
             wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
@@ -222,6 +225,7 @@
         IRemoteCallback mReply;
 
         boolean mDimensionsChanged = false;
+        boolean mPaddingChanged = false;
 
         public WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper) {
             mInfo = info;
@@ -283,6 +287,14 @@
                     }
                     mDimensionsChanged = false;
                 }
+                if (mPaddingChanged) {
+                    try {
+                        mEngine.setDisplayPadding(mWallpaper.padding);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to set wallpaper padding", e);
+                    }
+                    mPaddingChanged = false;
+                }
             }
         }
 
@@ -719,6 +731,40 @@
         }
     }
 
+    public void setDisplayPadding(Rect padding) {
+        checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
+        synchronized (mLock) {
+            int userId = UserHandle.getCallingUserId();
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper == null) {
+                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
+            }
+            if (padding.left < 0 || padding.top < 0 || padding.right < 0 || padding.bottom < 0) {
+                throw new IllegalArgumentException("padding must be positive: " + padding);
+            }
+
+            if (!padding.equals(wallpaper.padding)) {
+                wallpaper.padding.set(padding);
+                saveSettingsLocked(wallpaper);
+                if (mCurrentUserId != userId) return; // Don't change the properties now
+                if (wallpaper.connection != null) {
+                    if (wallpaper.connection.mEngine != null) {
+                        try {
+                            wallpaper.connection.mEngine.setDisplayPadding(padding);
+                        } catch (RemoteException e) {
+                        }
+                        notifyCallbacksLocked(wallpaper);
+                    } else if (wallpaper.connection.mService != null) {
+                        // We've attached to the service but the engine hasn't attached back to us
+                        // yet. This means it will be created with the previous dimensions, so we
+                        // need to update it to the new dimensions once it attaches.
+                        wallpaper.connection.mPaddingChanged = true;
+                    }
+                }
+            }
+        }
+    }
+
     public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
             Bundle outParams) {
         synchronized (mLock) {
@@ -1006,7 +1052,7 @@
         try {
             conn.mService.attach(conn, conn.mToken,
                     WindowManager.LayoutParams.TYPE_WALLPAPER, false,
-                    wallpaper.width, wallpaper.height);
+                    wallpaper.width, wallpaper.height, wallpaper.padding);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed attaching wallpaper; clearing", e);
             if (!wallpaper.wallpaperUpdating) {
@@ -1055,6 +1101,18 @@
             out.startTag(null, "wp");
             out.attribute(null, "width", Integer.toString(wallpaper.width));
             out.attribute(null, "height", Integer.toString(wallpaper.height));
+            if (wallpaper.padding.left != 0) {
+                out.attribute(null, "paddingLeft", Integer.toString(wallpaper.padding.left));
+            }
+            if (wallpaper.padding.top != 0) {
+                out.attribute(null, "paddingTop", Integer.toString(wallpaper.padding.top));
+            }
+            if (wallpaper.padding.right != 0) {
+                out.attribute(null, "paddingRight", Integer.toString(wallpaper.padding.right));
+            }
+            if (wallpaper.padding.bottom != 0) {
+                out.attribute(null, "paddingBottom", Integer.toString(wallpaper.padding.bottom));
+            }
             out.attribute(null, "name", wallpaper.name);
             if (wallpaper.wallpaperComponent != null
                     && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
@@ -1091,6 +1149,14 @@
         }
     }
 
+    private int getAttributeInt(XmlPullParser parser, String name, int defValue) {
+        String value = parser.getAttributeValue(null, name);
+        if (value == null) {
+            return defValue;
+        }
+        return Integer.parseInt(value);
+    }
+
     private void loadSettingsLocked(int userId) {
         if (DEBUG) Slog.v(TAG, "loadSettingsLocked");
         
@@ -1121,6 +1187,10 @@
                         wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width"));
                         wallpaper.height = Integer.parseInt(parser
                                 .getAttributeValue(null, "height"));
+                        wallpaper.padding.left = getAttributeInt(parser, "paddingLeft", 0);
+                        wallpaper.padding.top = getAttributeInt(parser, "paddingTop", 0);
+                        wallpaper.padding.right = getAttributeInt(parser, "paddingRight", 0);
+                        wallpaper.padding.bottom = getAttributeInt(parser, "paddingBottom", 0);
                         wallpaper.name = parser.getAttributeValue(null, "name");
                         String comp = parser.getAttributeValue(null, "component");
                         wallpaper.nextWallpaperComponent = comp != null
@@ -1167,6 +1237,7 @@
         if (!success) {
             wallpaper.width = -1;
             wallpaper.height = -1;
+            wallpaper.padding.set(0, 0, 0, 0);
             wallpaper.name = "";
         }
 
@@ -1330,13 +1401,12 @@
                 WallpaperData wallpaper = mWallpaperMap.valueAt(i);
                 pw.println(" User " + wallpaper.userId + ":");
                 pw.print("  mWidth=");
-                pw.print(wallpaper.width);
-                pw.print(" mHeight=");
-                pw.println(wallpaper.height);
-                pw.print("  mName=");
-                pw.println(wallpaper.name);
-                pw.print("  mWallpaperComponent=");
-                pw.println(wallpaper.wallpaperComponent);
+                    pw.print(wallpaper.width);
+                    pw.print(" mHeight=");
+                    pw.println(wallpaper.height);
+                pw.print("  mPadding="); pw.println(wallpaper.padding);
+                pw.print("  mName=");  pw.println(wallpaper.name);
+                pw.print("  mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
                 if (wallpaper.connection != null) {
                     WallpaperConnection conn = wallpaper.connection;
                     pw.print("  Wallpaper connection ");
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index f2703ad..d737e7f 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -415,6 +415,18 @@
         mService.wallpaperOffsetsComplete(window);
     }
 
+    public void setWallpaperDisplayOffset(IBinder window, int x, int y) {
+        synchronized(mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                mService.setWindowWallpaperDisplayOffsetLocked(
+                        mService.windowForClientLocked(this, window, true), x, y);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
     public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
             int z, Bundle extras, boolean sync) {
         synchronized(mService.mWindowMap) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fec02fe..434e48f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -576,6 +576,8 @@
     float mLastWallpaperY = -1;
     float mLastWallpaperXStep = -1;
     float mLastWallpaperYStep = -1;
+    int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE;
+    int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE;
     // This is set when we are waiting for a wallpaper to tell us it is done
     // changing its scroll position.
     WindowState mWaitingOnWallpaper;
@@ -1897,6 +1899,12 @@
                 mLastWallpaperY = mWallpaperTarget.mWallpaperY;
                 mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep;
             }
+            if (mWallpaperTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX;
+            }
+            if (mWallpaperTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY;
+            }
         }
 
         // Start stepping backwards from here, ensuring that our wallpaper windows
@@ -2035,6 +2043,9 @@
         float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
         int availw = wallpaperWin.mFrame.right-wallpaperWin.mFrame.left-dw;
         int offset = availw > 0 ? -(int)(availw*wpx+.5f) : 0;
+        if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+            offset += mLastWallpaperDisplayOffsetX;
+        }
         changed = wallpaperWin.mXOffset != offset;
         if (changed) {
             if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper "
@@ -2051,6 +2062,9 @@
         float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f;
         int availh = wallpaperWin.mFrame.bottom-wallpaperWin.mFrame.top-dh;
         offset = availh > 0 ? -(int)(availh*wpy+.5f) : 0;
+        if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            offset += mLastWallpaperDisplayOffsetY;
+        }
         if (wallpaperWin.mYOffset != offset) {
             if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper "
                     + wallpaperWin + " y: " + offset);
@@ -2135,6 +2149,16 @@
             } else if (changingTarget.mWallpaperY >= 0) {
                 mLastWallpaperY = changingTarget.mWallpaperY;
             }
+            if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX;
+            } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX;
+            }
+            if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY;
+            } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+                mLastWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY;
+            }
         }
 
         int curTokenIndex = mWallpaperTokens.size();
@@ -2831,6 +2855,14 @@
         }
     }
 
+    public void setWindowWallpaperDisplayOffsetLocked(WindowState window, int x, int y) {
+        if (window.mWallpaperDisplayOffsetX != x || window.mWallpaperDisplayOffsetY != y)  {
+            window.mWallpaperDisplayOffsetX = x;
+            window.mWallpaperDisplayOffsetY = y;
+            updateWallpaperOffsetLocked(window, true);
+        }
+    }
+
     public Bundle sendWindowWallpaperCommandLocked(WindowState window,
             String action, int x, int y, int z, Bundle extras, boolean sync) {
         if (window == mWallpaperTarget || window == mLowerWallpaperTarget
@@ -10937,6 +10969,12 @@
             }
             pw.print("  mLastWallpaperX="); pw.print(mLastWallpaperX);
                     pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY);
+            if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE
+                    || mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+                pw.print("  mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX);
+                        pw.print(" mLastWallpaperDisplayOffsetY=");
+                        pw.println(mLastWallpaperDisplayOffsetY);
+            }
             if (mInputMethodAnimLayerAdjustment != 0 ||
                     mWallpaperAnimLayerAdjustment != 0) {
                 pw.print("  mInputMethodAnimLayerAdjustment=");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e74de38..0baa2be 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -247,6 +247,11 @@
     float mWallpaperXStep = -1;
     float mWallpaperYStep = -1;
 
+    // If a window showing a wallpaper: a raw pixel offset to forcibly apply
+    // to its window; if a wallpaper window: not used.
+    int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
+    int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
+
     // Wallpaper windows: pixels offset based on above variables.
     int mXOffset;
     int mYOffset;
@@ -1584,6 +1589,13 @@
             pw.print(prefix); pw.print("mWallpaperXStep="); pw.print(mWallpaperXStep);
                     pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep);
         }
+        if (mWallpaperDisplayOffsetX != Integer.MIN_VALUE
+                || mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
+            pw.print(prefix); pw.print("mWallpaperDisplayOffsetX=");
+                    pw.print(mWallpaperDisplayOffsetX);
+                    pw.print(" mWallpaperDisplayOffsetY=");
+                    pw.println(mWallpaperDisplayOffsetY);
+        }
     }
 
     String makeInputChannelName() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a871522..dd611ce 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1526,8 +1526,9 @@
     }
 
     void setWallpaperOffset(RectF shownFrame) {
-        final int left = (int) shownFrame.left;
-        final int top = (int) shownFrame.top;
+        final LayoutParams attrs = mWin.getAttrs();
+        final int left = ((int) shownFrame.left) - attrs.surfaceInsets.left;
+        final int top = ((int) shownFrame.top) - attrs.surfaceInsets.top;
         if (mSurfaceX != left || mSurfaceY != top) {
             mSurfaceX = left;
             mSurfaceY = top;
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
index 44787e7..6837d22 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -73,7 +73,7 @@
                     Intent intent = new Intent(ActivityTestMain.this, SpamActivity.class);
                     Bundle options = null;
                     if (fg) {
-                        ActivityOptions opts = ActivityOptions.makeLaunchTaskBehindAnimation();
+                        ActivityOptions opts = ActivityOptions.makeTaskLaunchBehind();
                         options = opts.toBundle();
                     }
                     startActivity(intent, options);
diff --git a/tests/WallpaperTest/Android.mk b/tests/WallpaperTest/Android.mk
new file mode 100644
index 0000000..b4259cd
--- /dev/null
+++ b/tests/WallpaperTest/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := WallpaperTest
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/WallpaperTest/AndroidManifest.xml b/tests/WallpaperTest/AndroidManifest.xml
new file mode 100644
index 0000000..4c914dd
--- /dev/null
+++ b/tests/WallpaperTest/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.wallpapertest" >
+
+    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
+
+    <application
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <service
+            android:label="@string/test_wallpaper"
+            android:name=".TestWallpaper"
+            android:permission="android.permission.BIND_WALLPAPER"
+            android:enabled="true">
+            <intent-filter>
+                <action android:name="android.service.wallpaper.WallpaperService" />
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper"
+                       android:resource="@xml/test_wallpaper" />
+        </service>
+    </application>
+</manifest>
diff --git a/tests/WallpaperTest/res/drawable-hdpi/test_wallpaper_thumb.png b/tests/WallpaperTest/res/drawable-hdpi/test_wallpaper_thumb.png
new file mode 100644
index 0000000..df92eb5
--- /dev/null
+++ b/tests/WallpaperTest/res/drawable-hdpi/test_wallpaper_thumb.png
Binary files differ
diff --git a/tests/WallpaperTest/res/layout/activity_main.xml b/tests/WallpaperTest/res/layout/activity_main.xml
new file mode 100644
index 0000000..d968396
--- /dev/null
+++ b/tests/WallpaperTest/res/layout/activity_main.xml
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/window_background">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/dimens"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="15dp"
+                android:textSize="17sp"
+                android:text="@string/width"/>
+            <EditText
+                android:id="@+id/dimen_width"
+                android:layout_width="60sp"
+                android:layout_height="wrap_content"
+                android:inputType="numberDecimal"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="15dp"
+                android:textSize="17sp"
+                android:text="@string/height"/>
+            <EditText
+                android:id="@+id/dimen_height"
+                android:layout_width="60sp"
+                android:layout_height="wrap_content"
+                android:inputType="numberDecimal"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/wallpaper_offset"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="15dp"
+                android:textSize="17sp"
+                android:text="@string/x"/>
+            <EditText
+                android:id="@+id/walloff_x"
+                android:layout_width="60sp"
+                android:layout_height="wrap_content"
+                android:inputType="numberDecimal"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="15dp"
+                android:textSize="17sp"
+                android:text="@string/y"/>
+            <EditText
+                android:id="@+id/walloff_y"
+                android:layout_width="60sp"
+                android:layout_height="wrap_content"
+                android:inputType="numberDecimal"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/padding"/>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal">
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="15dp"
+                        android:textSize="17sp"
+                        android:text="@string/left"/>
+                    <EditText
+                        android:id="@+id/padding_left"
+                        android:layout_width="60sp"
+                        android:layout_height="wrap_content"
+                        android:inputType="number"/>
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="15dp"
+                        android:textSize="17sp"
+                        android:text="@string/right"/>
+                    <EditText
+                        android:id="@+id/padding_right"
+                        android:layout_width="60sp"
+                        android:layout_height="wrap_content"
+                        android:inputType="number"/>
+                </LinearLayout>
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal">
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="15dp"
+                        android:textSize="17sp"
+                        android:text="@string/top"/>
+                    <EditText
+                        android:id="@+id/padding_top"
+                        android:layout_width="60sp"
+                        android:layout_height="wrap_content"
+                        android:inputType="number"/>
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginLeft="15dp"
+                        android:textSize="17sp"
+                        android:text="@string/bottom"/>
+                    <EditText
+                        android:id="@+id/padding_bottom"
+                        android:layout_width="60sp"
+                        android:layout_height="wrap_content"
+                        android:inputType="number"/>
+                </LinearLayout>
+            </LinearLayout>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/display_offset"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="15dp"
+                android:textSize="17sp"
+                android:text="@string/x"/>
+            <EditText
+                android:id="@+id/dispoff_x"
+                android:layout_width="60sp"
+                android:layout_height="wrap_content"
+                android:inputType="numberSigned"/>
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="15dp"
+                android:textSize="17sp"
+                android:text="@string/y"/>
+            <EditText
+                android:id="@+id/dispoff_y"
+                android:layout_width="60sp"
+                android:layout_height="wrap_content"
+                android:inputType="numberSigned"/>
+        </LinearLayout>
+    </LinearLayout>
+</ScrollView>
diff --git a/tests/WallpaperTest/res/values-v11/styles.xml b/tests/WallpaperTest/res/values-v11/styles.xml
new file mode 100644
index 0000000..95000b2
--- /dev/null
+++ b/tests/WallpaperTest/res/values-v11/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2013 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<resources>
+
+    <!--
+        Base application theme for API 11+. This theme completely replaces
+        AppBaseTheme from res/values/styles.xml on API 11+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Holo.Wallpaper">
+        <!-- API 11 theme customizations can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/tests/WallpaperTest/res/values-v21/styles.xml b/tests/WallpaperTest/res/values-v21/styles.xml
new file mode 100644
index 0000000..e42d526
--- /dev/null
+++ b/tests/WallpaperTest/res/values-v21/styles.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2013 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<resources>
+
+    <!--
+        Base application theme for API 21+. This theme completely replaces
+        AppBaseTheme from BOTH res/values/styles.xml and
+        res/values-v11/styles.xml on API 14+ devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Material.Wallpaper">
+        <!-- API 14 theme customizations can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/tests/WallpaperTest/res/values/colors.xml b/tests/WallpaperTest/res/values/colors.xml
new file mode 100644
index 0000000..8c08249
--- /dev/null
+++ b/tests/WallpaperTest/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<resources>
+    <color name="window_background">#80000000</color>
+</resources>
\ No newline at end of file
diff --git a/tests/WallpaperTest/res/values/strings.xml b/tests/WallpaperTest/res/values/strings.xml
new file mode 100644
index 0000000..fd21259
--- /dev/null
+++ b/tests/WallpaperTest/res/values/strings.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<resources>
+    <string name="app_name">Wallpaper Test</string>
+
+    <string name="test_wallpaper">Test Wallpaper</string>
+    <string name="test_wallpaper_author">Google</string>
+    <string name="test_wallpaper_desc">
+        Test wallpaper for use with the wallpaper test app.
+    </string>
+
+    <string name="dimens">Dimens: </string>
+    <string name="width">Width: </string>
+    <string name="height">Height: </string>
+
+    <string name="wallpaper_offset">Wall off: </string>
+    <string name="x">X: </string>
+    <string name="y">Y: </string>
+
+    <string name="padding">Padding: </string>
+    <string name="left">Left: </string>
+    <string name="right">Right: </string>
+    <string name="top">Top: </string>
+    <string name="bottom">Bottom: </string>
+
+    <string name="display_offset">Disp off: </string>
+</resources>
diff --git a/tests/WallpaperTest/res/values/styles.xml b/tests/WallpaperTest/res/values/styles.xml
new file mode 100644
index 0000000..d2b91d6
--- /dev/null
+++ b/tests/WallpaperTest/res/values/styles.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2013 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<resources>
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Wallpaper">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/tests/WallpaperTest/res/xml/test_wallpaper.xml b/tests/WallpaperTest/res/xml/test_wallpaper.xml
new file mode 100644
index 0000000..9f7d714b
--- /dev/null
+++ b/tests/WallpaperTest/res/xml/test_wallpaper.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2008, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+-->
+
+<!-- The attributes in this XML file provide configuration information -->
+<!-- about the polar clock. -->
+
+<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
+    android:author="@string/test_wallpaper_author"
+    android:description="@string/test_wallpaper_desc"
+    android:thumbnail="@drawable/test_wallpaper_thumb" />
diff --git a/tests/WallpaperTest/src/com/example/wallpapertest/MainActivity.java b/tests/WallpaperTest/src/com/example/wallpapertest/MainActivity.java
new file mode 100644
index 0000000..7880f67
--- /dev/null
+++ b/tests/WallpaperTest/src/com/example/wallpapertest/MainActivity.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.wallpapertest;
+
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+public class MainActivity extends Activity {
+    private static final String TAG = "MainActivity";
+
+    WallpaperManager mWallpaperManager;
+    WindowManager mWindowManager;
+
+    TextView mDimenWidthView;
+    TextView mDimenHeightView;
+
+    TextView mWallOffXView;
+    TextView mWallOffYView;
+
+    TextView mPaddingLeftView;
+    TextView mPaddingRightView;
+    TextView mPaddingTopView;
+    TextView mPaddingBottomView;
+
+    TextView mDispOffXView;
+    TextView mDispOffYView;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        mWallpaperManager = (WallpaperManager)getSystemService(Context.WALLPAPER_SERVICE);
+        mWindowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
+
+        mDimenWidthView = (TextView) findViewById(R.id.dimen_width);
+        mDimenWidthView.addTextChangedListener(mTextWatcher);
+        mDimenHeightView = (TextView) findViewById(R.id.dimen_height);
+        mDimenHeightView.addTextChangedListener(mTextWatcher);
+
+        mWallOffXView = (TextView) findViewById(R.id.walloff_x);
+        mWallOffXView.addTextChangedListener(mTextWatcher);
+        mWallOffYView = (TextView) findViewById(R.id.walloff_y);
+        mWallOffYView.addTextChangedListener(mTextWatcher);
+
+        mPaddingLeftView = (TextView) findViewById(R.id.padding_left);
+        mPaddingLeftView.addTextChangedListener(mTextWatcher);
+        mPaddingRightView = (TextView) findViewById(R.id.padding_right);
+        mPaddingRightView.addTextChangedListener(mTextWatcher);
+        mPaddingTopView = (TextView) findViewById(R.id.padding_top);
+        mPaddingTopView.addTextChangedListener(mTextWatcher);
+        mPaddingBottomView = (TextView) findViewById(R.id.padding_bottom);
+        mPaddingBottomView.addTextChangedListener(mTextWatcher);
+
+        mDispOffXView = (TextView) findViewById(R.id.dispoff_x);
+        mDispOffXView.addTextChangedListener(mTextWatcher);
+        mDispOffYView = (TextView) findViewById(R.id.dispoff_y);
+        mDispOffYView.addTextChangedListener(mTextWatcher);
+
+        updateDimens();
+        updateWallOff();
+        updatePadding();
+        updateDispOff();
+    }
+
+    private int loadPropIntText(TextView view, int baseVal) {
+        String str = view.getText().toString();
+        if (str != null && !TextUtils.isEmpty(str)) {
+            try {
+                float fval = Float.parseFloat(str);
+                return (int)(fval*baseVal);
+            } catch (NumberFormatException e) {
+                Log.i(TAG, "Bad number: " + str, e);
+            }
+        }
+        return baseVal;
+    }
+
+    private float loadFloatText(TextView view) {
+        String str = view.getText().toString();
+        if (str != null && !TextUtils.isEmpty(str)) {
+            try {
+                return Float.parseFloat(str);
+            } catch (NumberFormatException e) {
+                Log.i(TAG, "Bad number: " + str, e);
+            }
+        }
+        return 0;
+    }
+
+    private int loadIntText(TextView view) {
+        String str = view.getText().toString();
+        if (str != null && !TextUtils.isEmpty(str)) {
+            try {
+                return Integer.parseInt(str);
+            } catch (NumberFormatException e) {
+                Log.i(TAG, "Bad number: " + str, e);
+            }
+        }
+        return 0;
+    }
+
+    public void updateDimens() {
+        Point minDims = new Point();
+        Point maxDims = new Point();
+        mWindowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
+        mWallpaperManager.suggestDesiredDimensions(
+                loadPropIntText(mDimenWidthView, maxDims.x),
+                loadPropIntText(mDimenHeightView, maxDims.y));
+    }
+
+    public void updateWallOff() {
+        IBinder token = getWindow().getDecorView().getWindowToken();
+        if (token != null) {
+            mWallpaperManager.setWallpaperOffsets(token, loadFloatText(mWallOffXView),
+                    loadFloatText(mWallOffYView));
+        }
+    }
+
+    public void updatePadding() {
+        Rect padding = new Rect();
+        padding.left = loadIntText(mPaddingLeftView);
+        padding.top = loadIntText(mPaddingTopView);
+        padding.right = loadIntText(mPaddingRightView);
+        padding.bottom = loadIntText(mPaddingBottomView);
+        mWallpaperManager.setDisplayPadding(padding);
+    }
+
+    public void updateDispOff() {
+        IBinder token = getWindow().getDecorView().getWindowToken();
+        if (token != null) {
+            mWallpaperManager.setDisplayOffset(token, loadIntText(mDispOffXView),
+                    loadIntText(mDispOffYView));
+        }
+    }
+
+    final TextWatcher mTextWatcher = new TextWatcher() {
+        @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+        }
+
+        @Override public void onTextChanged(CharSequence s, int start, int before, int count) {
+            updateDimens();
+            updateWallOff();
+            updatePadding();
+            updateDispOff();
+        }
+
+        @Override public void afterTextChanged(Editable s) {
+        }
+    };
+}
diff --git a/tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java b/tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java
new file mode 100644
index 0000000..95db6d1
--- /dev/null
+++ b/tests/WallpaperTest/src/com/example/wallpapertest/TestWallpaper.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.wallpapertest;
+
+import android.service.wallpaper.WallpaperService;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Paint;
+import android.graphics.Color;
+import android.graphics.RectF;
+import android.text.TextPaint;
+import android.view.SurfaceHolder;
+import android.content.res.XmlResourceParser;
+
+import android.os.Handler;
+import android.util.Log;
+
+import android.view.WindowInsets;
+
+public class TestWallpaper extends WallpaperService {
+    private static final String LOG_TAG = "PolarClock";
+    
+    private final Handler mHandler = new Handler();
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    public Engine onCreateEngine() {
+        return new ClockEngine();
+    }
+
+    class ClockEngine extends Engine {
+        private static final int OUTER_COLOR = 0xffff0000;
+        private static final int INNER_COLOR = 0xff000080;
+        private static final int STABLE_COLOR = 0xa000ff00;
+        private static final int TEXT_COLOR = 0xa0ffffff;
+
+        private final Paint.FontMetrics mTextMetrics = new Paint.FontMetrics();
+
+        private int mPadding;
+
+        private final Rect mMainInsets = new Rect();
+        private final Rect mStableInsets = new Rect();
+        private boolean mRound = false;
+
+        private int mDesiredWidth;
+        private int mDesiredHeight;
+
+        private float mOffsetX;
+        private float mOffsetY;
+        private float mOffsetXStep;
+        private float mOffsetYStep;
+        private int mOffsetXPixels;
+        private int mOffsetYPixels;
+
+        private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        private final Paint mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        private final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+
+        private final Runnable mDrawClock = new Runnable() {
+            public void run() {
+                drawFrame();
+            }
+        };
+        private boolean mVisible;
+
+        ClockEngine() {
+        }
+
+        @Override
+        public void onCreate(SurfaceHolder surfaceHolder) {
+            super.onCreate(surfaceHolder);
+
+            mDesiredWidth = getDesiredMinimumWidth();
+            mDesiredHeight = getDesiredMinimumHeight();
+
+            Paint paint = mFillPaint;
+            paint.setStyle(Paint.Style.FILL);
+
+            paint = mStrokePaint;
+            paint.setStrokeWidth(3);
+            paint.setStrokeCap(Paint.Cap.ROUND);
+            paint.setStyle(Paint.Style.STROKE);
+
+            TextPaint tpaint = mTextPaint;
+            tpaint.density = getResources().getDisplayMetrics().density;
+            tpaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
+            tpaint.setColor(TEXT_COLOR);
+            tpaint.setTextSize(18 * getResources().getDisplayMetrics().scaledDensity);
+            tpaint.setShadowLayer(4 * getResources().getDisplayMetrics().density, 0, 0, 0xff000000);
+
+            mTextPaint.getFontMetrics(mTextMetrics);
+
+            mPadding = (int)(16 * getResources().getDisplayMetrics().density);
+
+            if (isPreview()) {
+                mOffsetX = 0.5f;
+                mOffsetY = 0.5f;
+            }
+        }
+
+        @Override
+        public void onDestroy() {
+            super.onDestroy();
+            mHandler.removeCallbacks(mDrawClock);
+        }
+
+        @Override
+        public void onVisibilityChanged(boolean visible) {
+            mVisible = visible;
+            if (!visible) {
+                mHandler.removeCallbacks(mDrawClock);
+            }
+            drawFrame();
+        }
+
+        @Override
+        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            super.onSurfaceChanged(holder, format, width, height);
+            drawFrame();
+        }
+
+        @Override
+        public void onSurfaceCreated(SurfaceHolder holder) {
+            super.onSurfaceCreated(holder);
+        }
+
+        @Override
+        public void onSurfaceDestroyed(SurfaceHolder holder) {
+            super.onSurfaceDestroyed(holder);
+            mVisible = false;
+            mHandler.removeCallbacks(mDrawClock);
+        }
+
+        @Override
+        public void onApplyWindowInsets(WindowInsets insets) {
+            super.onApplyWindowInsets(insets);
+            mMainInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
+                    insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
+            mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
+                    insets.getStableInsetRight(), insets.getStableInsetBottom());
+            mRound = insets.isRound();
+            drawFrame();
+        }
+
+        @Override
+        public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
+            super.onDesiredSizeChanged(desiredWidth, desiredHeight);
+            mDesiredWidth = desiredWidth;
+            mDesiredHeight = desiredHeight;
+            drawFrame();
+        }
+
+        @Override
+        public void onOffsetsChanged(float xOffset, float yOffset,
+                float xStep, float yStep, int xPixels, int yPixels) {
+            super.onOffsetsChanged(xOffset, yOffset, xStep, yStep, xPixels, yPixels);
+
+            if (isPreview()) return;
+
+            mOffsetX = xOffset;
+            mOffsetY = yOffset;
+            mOffsetXStep = xStep;
+            mOffsetYStep = yStep;
+            mOffsetXPixels = xPixels;
+            mOffsetYPixels = yPixels;
+
+            drawFrame();
+        }
+
+        void drawFrame() {
+            final SurfaceHolder holder = getSurfaceHolder();
+            final Rect frame = holder.getSurfaceFrame();
+            final int width = frame.width();
+            final int height = frame.height();
+
+            Canvas c = null;
+            try {
+                c = holder.lockCanvas();
+                if (c != null) {
+                    final Paint paint = mFillPaint;
+
+                    paint.setColor(OUTER_COLOR);
+                    c.drawRect(0, 0, width, height, paint);
+
+                    paint.setColor(INNER_COLOR);
+                    c.drawRect(0+mMainInsets.left, 0+mMainInsets.top,
+                            width-mMainInsets.right, height-mMainInsets.bottom, paint);
+
+                    mStrokePaint.setColor(STABLE_COLOR);
+                    c.drawRect(0 + mStableInsets.left, 0 + mStableInsets.top,
+                            width - mStableInsets.right, height - mStableInsets.bottom,
+                            mStrokePaint);
+
+                    final int ascdesc = (int)(-mTextMetrics.ascent + mTextMetrics.descent);
+                    final int linegap = (int)(-mTextMetrics.ascent + mTextMetrics.descent
+                            + mTextMetrics.leading);
+
+                    int x = mStableInsets.left + mPadding;
+                    int y = height - mStableInsets.bottom - mPadding - ascdesc;
+                    c.drawText("Surface Size: " + width + " x " + height,
+                            x, y, mTextPaint);
+                    y -= linegap;
+                    c.drawText("Desired Size: " + mDesiredWidth + " x " + mDesiredHeight,
+                            x, y, mTextPaint);
+                    y -= linegap;
+                    c.drawText("Cur Offset Raw: " + mOffsetX + ", " + mOffsetY,
+                            x, y, mTextPaint);
+                    y -= linegap;
+                    c.drawText("Cur Offset Step: " + mOffsetXStep + ", " + mOffsetYStep,
+                            x, y, mTextPaint);
+                    y -= linegap;
+                    c.drawText("Cur Offset Pixels: " + mOffsetXPixels + ", " + mOffsetYPixels,
+                            x, y, mTextPaint);
+                    y -= linegap;
+                    c.drawText("Stable Insets: (" + mStableInsets.left + ", " + mStableInsets.top
+                            + ") - (" + mStableInsets.right + ", " + mStableInsets.bottom + ")",
+                            x, y, mTextPaint);
+                    y -= linegap;
+                    c.drawText("System Insets: (" + mMainInsets.left + ", " + mMainInsets.top
+                            + ") - (" + mMainInsets.right + ", " + mMainInsets.bottom + ")",
+                            x, y, mTextPaint);
+
+                }
+            } finally {
+                if (c != null) holder.unlockCanvasAndPost(c);
+            }
+        }
+    }
+}