Merge "Don't assert on unexpected surface flinger dequeue/enqueueBuffer errors, log a warning and ignore them instead."
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index 4dc88b3..d7a0412 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -3,8 +3,6 @@
 import com.android.internal.view.IInputMethodCallback;
 import com.android.internal.view.IInputMethodSession;
 
-import dalvik.system.PathClassLoader;
-
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -168,17 +166,14 @@
             // If the application does not have (Java) code, then no ClassLoader
             // has been set up for it.  We will need to do our own search for
             // the native code.
-            path = ai.applicationInfo.dataDir + "/lib/" + System.mapLibraryName(libname);
-            if (!(new File(path)).exists()) {
-                path = null;
+            File libraryFile = new File(ai.applicationInfo.nativeLibraryDir,
+                    System.mapLibraryName(libname));
+            if (libraryFile.exists()) {
+                path = libraryFile.getPath();
             }
         }
         
         if (path == null) {
-            path = ((PathClassLoader)getClassLoader()).findLibrary(libname);
-        }
-        
-        if (path == null) {
             throw new IllegalArgumentException("Unable to find native library: " + libname);
         }
         
diff --git a/core/java/android/app/backup/SharedPreferencesBackupHelper.java b/core/java/android/app/backup/SharedPreferencesBackupHelper.java
index 23b1703..213bd31 100644
--- a/core/java/android/app/backup/SharedPreferencesBackupHelper.java
+++ b/core/java/android/app/backup/SharedPreferencesBackupHelper.java
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.app.QueuedWork;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.ParcelFileDescriptor;
@@ -94,7 +95,11 @@
     public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
             ParcelFileDescriptor newState) {
         Context context = mContext;
-        
+
+        // If a SharedPreference has an outstanding write in flight,
+        // wait for it to finish flushing to disk.
+        QueuedWork.waitToFinish();
+
         // make filenames for the prefGroups
         String[] prefGroups = mPrefGroups;
         final int N = prefGroups.length;
@@ -123,4 +128,3 @@
         }
     }
 }
-
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index aa0a57d..9541380 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -144,6 +144,22 @@
         }
     }
 
+    @Override public String toString() {
+        synchronized(this) {
+            StringBuilder buff = new StringBuilder();
+            buff.append(" nStatement=");
+            buff.append(nStatement);
+            buff.append(", db=");
+            buff.append(mDatabase.getPath());
+            buff.append(", db_connectionNum=");
+            buff.append(mDatabase.mConnectionNum);
+            buff.append(", sql=");
+            int len = mSqlStmt.length();
+            buff.append(mSqlStmt.substring(0, (len > 100) ? 100 : len));
+            return buff.toString();
+        }
+    }
+
     /**
      * Compiles SQL into a SQLite program.
      *
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 0137ea6..6937da0 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1066,7 +1066,7 @@
             closePendingStatements();
             releaseCustomFunctions();
             // close this database instance - regardless of its reference count value
-            dbclose();
+            closeDatabase();
             if (mConnectionPool != null) {
                 if (Log.isLoggable(TAG, Log.DEBUG)) {
                     assert mConnectionPool != null;
@@ -1075,7 +1075,7 @@
                 mConnectionPool.close();
             }
         } finally {
-            unlock();
+            unlock();            
         }
     }
 
@@ -1100,6 +1100,47 @@
     }
 
     /**
+     * package level access for testing purposes
+     */
+    /* package */ void closeDatabase() throws SQLiteException {
+        try {
+            dbclose();
+        } catch (SQLiteUnfinalizedObjectsException e)  {
+            String msg = e.getMessage();
+            String[] tokens = msg.split(",", 2);
+            int stmtId = Integer.parseInt(tokens[0]);
+            // get extra info about this statement, if it is still to be released by closeClosable()
+            Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator();
+            boolean found = false;
+            while (iter.hasNext()) {
+                Map.Entry<SQLiteClosable, Object> entry = iter.next();
+                SQLiteClosable program = entry.getKey();
+                if (program != null && program instanceof SQLiteProgram) {
+                        SQLiteCompiledSql compiledSql = ((SQLiteProgram)program).mCompiledSql;
+                        if (compiledSql.nStatement == stmtId) {
+                            msg = compiledSql.toString();
+                            found = true;
+                        }
+                }
+            }
+            if (!found) {
+                // the statement is already released by closeClosable(). is it waiting to be
+                // finalized?
+                if (mClosedStatementIds.contains(stmtId)) {
+                    Log.w(TAG, "this shouldn't happen. finalizing the statement now: ");
+                    closePendingStatements();
+                    // try to close the database again
+                    closeDatabase();
+                }
+            } else {
+                // the statement is not yet closed. most probably programming error in the app.
+                Log.w(TAG, "dbclose failed due to un-close()d SQL statements: " + msg);
+                throw e;
+            }
+        }
+    }
+
+    /**
      * Native call to close the database.
      */
     private native void dbclose();
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 9b7d823..5e08aed 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -52,7 +52,7 @@
     /**
      * the SQLiteCompiledSql object for the given sql statement.
      */
-    private SQLiteCompiledSql mCompiledSql;
+    /* package */ SQLiteCompiledSql mCompiledSql;
 
     /**
      * SQLiteCompiledSql statement id is populated with the corresponding object from the above
diff --git a/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java b/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java
new file mode 100644
index 0000000..bcf95e2
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+/**
+ * Thrown if the database can't be closed because of some un-closed
+ * Cursor or SQLiteStatement objects. Could happen when a thread is trying to close
+ * the database while another thread still hasn't closed a Cursor on that database.
+ * @hide
+ */
+public class SQLiteUnfinalizedObjectsException extends SQLiteException {
+    public SQLiteUnfinalizedObjectsException() {}
+
+    public SQLiteUnfinalizedObjectsException(String error) {
+        super(error);
+    }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7355353..f8d79e5 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7298,9 +7298,6 @@
             if (ViewDebug.TRACE_HIERARCHY) {
                 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE);
             }
-            if (Config.DEBUG && ViewDebug.profileDrawing) {
-                EventLog.writeEvent(60002, hashCode());
-            }
 
             int width = mRight - mLeft;
             int height = mBottom - mTop;
@@ -7738,7 +7735,8 @@
         saveCount = canvas.getSaveCount();
 
         int solidColor = getSolidColor();
-        if (solidColor == 0) {
+        // TODO: Temporarily disable fading edges with hardware acceleration
+        if (solidColor == 0 && !canvas.isHardwareAccelerated()) {
             final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
 
             if (drawTop) {
@@ -7946,6 +7944,7 @@
      * @param r Right position, relative to parent
      * @param b Bottom position, relative to parent
      */
+    @SuppressWarnings({"unchecked"})
     public final void layout(int l, int t, int r, int b) {
         int oldL = mLeft;
         int oldT = mTop;
@@ -10129,11 +10128,6 @@
         final RectF mTmpTransformRect = new RectF();
 
         /**
-         * Temporary for use in computing invalidation areas with transformed views
-         */
-        final float[] mTmpTransformBounds = new float[8];
-
-        /**
          * Temporary list for use in collecting focusable descendents of a view.
          */
         final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24);
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 22cc3a8..b1d5272 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -119,24 +119,21 @@
      *
      * @hide
      */
-    @Debug.DebugProperty
-    public static boolean profileDrawing = false;
+    public static final boolean DEBUG_PROFILE_DRAWING = false;
 
     /**
      * Profiles layout times in the events log.
      *
      * @hide
      */
-    @Debug.DebugProperty
-    public static boolean profileLayout = false;
+    public static final boolean DEBUG_PROFILE_LAYOUT = false;
 
     /**
      * Profiles real fps (times between draws) and displays the result.
      *
      * @hide
      */
-    @Debug.DebugProperty
-    public static boolean showFps = false;
+    public static final boolean DEBUG_SHOW_FPS = false;
 
     /**
      * <p>Enables or disables views consistency check. Even when this property is enabled,
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index dee6e73..363ccd6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -32,8 +32,6 @@
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.util.AttributeSet;
-import android.util.Config;
-import android.util.EventLog;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.accessibility.AccessibilityEvent;
@@ -2020,9 +2018,6 @@
                 cachePaint.setAlpha(255);
                 mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE;
             }
-            if (Config.DEBUG && ViewDebug.profileDrawing) {
-                EventLog.writeEvent(60003, hashCode());
-            }
             canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
         }
 
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 5d4ac37..e61eaed 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -1008,7 +1008,7 @@
                 TAG, "Laying out " + host + " to (" +
                 host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")");
             long startTime = 0L;
-            if (ViewDebug.profileLayout) {
+            if (ViewDebug.DEBUG_PROFILE_LAYOUT) {
                 startTime = SystemClock.elapsedRealtime();
             }
             host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
@@ -1021,7 +1021,7 @@
                 }
             }
 
-            if (ViewDebug.profileLayout) {
+            if (ViewDebug.DEBUG_PROFILE_LAYOUT) {
                 EventLog.writeEvent(60001, SystemClock.elapsedRealtime() - startTime);
             }
 
@@ -1321,7 +1321,7 @@
                         //canvas.drawARGB(255, 255, 0, 0);
                     }
 
-                    if (ViewDebug.profileDrawing) {
+                    if (ViewDebug.DEBUG_PROFILE_DRAWING) {
                         startTime = SystemClock.elapsedRealtime();
                     }
 
@@ -1364,7 +1364,7 @@
                         mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                     }
 
-                    if (SHOW_FPS || ViewDebug.showFps) {
+                    if (SHOW_FPS || ViewDebug.DEBUG_SHOW_FPS) {
                         int now = (int)SystemClock.elapsedRealtime();
                         if (sDrawTime != 0) {
                             nativeShowFPS(canvas, now - sDrawTime);
@@ -1372,7 +1372,7 @@
                         sDrawTime = now;
                     }
 
-                    if (ViewDebug.profileDrawing) {
+                    if (ViewDebug.DEBUG_PROFILE_DRAWING) {
                         EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
                     }
                 }
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 0ed5fc5..d81d7f2 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -211,6 +211,7 @@
     private boolean         mNavDump = false;
     private boolean         mSupportZoom = true;
     private boolean         mBuiltInZoomControls = false;
+    private boolean         mDisplayZoomControls = true;
     private boolean         mAllowFileAccess = true;
     private boolean         mLoadWithOverviewMode = false;
     private boolean         mEnableSmoothTransition = false;
@@ -508,6 +509,26 @@
     }
 
     /**
+     * Sets whether the on screen zoom buttons are used.
+     * A combination of built in zoom controls enabled
+     * and on screen zoom controls disabled allows for pinch to zoom
+     * to work without the on screen controls
+     * @hide
+     */
+    public void setDisplayZoomControls(boolean enabled) {
+        mDisplayZoomControls = enabled;
+        mWebView.updateMultiTouchSupport(mContext);
+    }
+
+    /**
+     * Returns true if the on screen zoom buttons are being used.
+     * @hide
+     */
+    public boolean getDisplayZoomControls() {
+        return mDisplayZoomControls;
+    }
+
+    /**
      * Enable or disable file access within WebView. File access is enabled by
      * default.
      */
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index 33ebcf5..1323217 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -856,7 +856,8 @@
     private ZoomControlBase getCurrentZoomControl() {
         if (mWebView.getSettings() != null && mWebView.getSettings().supportZoom()) {
             if (mWebView.getSettings().getBuiltInZoomControls()) {
-                if (mEmbeddedZoomControl == null) {
+                if ((mEmbeddedZoomControl == null)
+                        && mWebView.getSettings().getDisplayZoomControls()) {
                     mEmbeddedZoomControl = new ZoomControlEmbedded(this, mWebView);
                 }
                 return mEmbeddedZoomControl;
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 0636d72..830e899 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -31,6 +31,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 
@@ -128,6 +129,13 @@
     boolean mShouldLoop = true;
 
     /**
+     * The width and height of some child, used as a size reference in-case our
+     * dimensions are unspecified by the parent.
+     */
+    int mReferenceChildWidth = -1;
+    int mReferenceChildHeight = -1;
+
+    /**
      * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit.
      */
     Animation mInAnimation;
@@ -414,7 +422,7 @@
                         FrameLayout fl = new FrameLayout(mContext);
                         fl.addView(newView);
                         mActiveViews[index] = fl;
-                        addViewInLayout(fl, -1, createOrReuseLayoutParams(fl));
+                        addChild(fl);
                         applyTransformForChildAtIndex(fl, newRelativeIndex);
                         animateViewForTransition(-1, newRelativeIndex, fl);
                     }
@@ -451,6 +459,64 @@
         }
     }
 
+    private void addChild(View child) {
+        addViewInLayout(child, -1, createOrReuseLayoutParams(child));
+
+        // This code is used to obtain a reference width and height of a child in case we need
+        // to decide our own size. TODO: Do we want to update the size of the child that we're
+        // using for reference size? If so, when?
+        if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) {
+            int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            child.measure(measureSpec, measureSpec);
+            mReferenceChildWidth = child.getMeasuredWidth();
+            mReferenceChildHeight = child.getMeasuredHeight();
+        }
+    }
+
+    private void measureChildren() {
+        final int count = getChildCount();
+        final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+        final int childHeight = mMeasuredHeight - mPaddingTop - mPaddingBottom;
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
+
+        // We need to deal with the case where our parent hasn't told us how
+        // big we should be. In this case we try to use the desired size of the first
+        // child added.
+        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
+            heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop +
+                    mPaddingBottom : 0;
+        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+            heightSpecSize = haveChildRefSize ? Math.min(mReferenceChildHeight + mPaddingTop +
+                    mPaddingBottom, heightSpecSize) : 0;
+        }
+
+        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
+            widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
+                    mPaddingRight : 0;
+        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+            widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft +
+                    mPaddingRight, widthSpecSize) : 0;
+        }
+
+        setMeasuredDimension(widthSpecSize, heightSpecSize);
+        measureChildren();
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         boolean dataChanged = mDataChanged;
@@ -472,8 +538,7 @@
             int childRight = mPaddingLeft + child.getMeasuredWidth();
             int childBottom = mPaddingTop + child.getMeasuredHeight();
 
-            child.layout(mPaddingLeft, mPaddingTop,
-                    childRight, childBottom);
+            child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
         }
         mDataChanged = false;
     }
@@ -538,31 +603,6 @@
         setDisplayedChild(mWhichChild);
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final int count = getChildCount();
-
-        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
-        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
-
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            lp.width = widthSpecSize - mPaddingLeft - mPaddingRight;
-            lp.height = heightSpecSize - mPaddingTop - mPaddingBottom;
-
-            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
-                    MeasureSpec.EXACTLY);
-            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
-                    MeasureSpec.EXACTLY);
-
-            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
-        }
-        setMeasuredDimension(widthSpecSize, heightSpecSize);
-    }
-
     /**
      * Shows only the specified child. The other displays Views exit the screen
      * with the {@link #getOutAnimation() out animation} and the specified child
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 9816b39..9025b83 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -16,11 +16,11 @@
 
 package android.widget;
 
-import java.util.WeakHashMap;
-
 import android.animation.PropertyAnimator;
+import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Paint;
@@ -28,6 +28,7 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.TableMaskFilter;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -35,6 +36,8 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.LayoutParams;
 import android.view.animation.LinearInterpolator;
 import android.widget.RemoteViews.RemoteView;
 
@@ -53,6 +56,19 @@
     private final int MINIMUM_ANIMATION_DURATION = 50;
 
     /**
+     * Parameters effecting the perspective visuals
+     */
+    private static float PERSPECTIVE_SHIFT_FACTOR = 0.12f;
+    private static float PERSPECTIVE_SCALE_FACTOR = 0.35f;
+
+    /**
+     * Represent the two possible stack modes, one where items slide up, and the other
+     * where items slide down. The perspective is also inverted between these two modes.
+     */
+    private static final int ITEMS_SLIDE_UP = 0;
+    private static final int ITEMS_SLIDE_DOWN = 1;
+
+    /**
      * These specify the different gesture states
      */
     private static final int GESTURE_NONE = 0;
@@ -66,8 +82,6 @@
     private static final float SWIPE_THRESHOLD_RATIO = 0.35f;
     private static final float SLIDE_UP_RATIO = 0.7f;
 
-    private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>();
-
     /**
      * Sentinel value for no current active pointer.
      * Used by {@link #mActivePointerId}.
@@ -75,6 +89,12 @@
     private static final int INVALID_POINTER = -1;
 
     /**
+     * Number of active views in the stack. One fewer view is actually visible, as one is hidden.
+     */
+    private static final int NUM_ACTIVE_VIEWS = 5;
+
+
+    /**
      * These variables are all related to the current state of touch interaction
      * with the stack
      */
@@ -95,6 +115,7 @@
     private boolean mFirstLayoutHappened = false;
     private ViewGroup mAncestorContainingAllChildren = null;
     private int mAncestorHeight = 0;
+    private int mStackMode;
 
     public StackView(Context context) {
         super(context);
@@ -107,7 +128,7 @@
     }
 
     private void initStackView() {
-        configureViewAnimator(4, 2, false);
+        configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2, false);
         setStaticTransformationsEnabled(true);
         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
         mTouchSlop = configuration.getScaledTouchSlop();
@@ -125,6 +146,11 @@
         setClipChildren(false);
         setClipToPadding(false);
 
+        // This sets the form of the StackView, which is currently to have the perspective-shifted
+        // views above the active view, and have items slide down when sliding out. The opposite is
+        // available by using ITEMS_SLIDE_UP.
+        mStackMode = ITEMS_SLIDE_DOWN;
+
         // This is a flag to indicate the the stack is loading for the first time
         mWhichChild = -1;
     }
@@ -140,7 +166,7 @@
             }
             view.setVisibility(VISIBLE);
 
-            PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
+            PropertyAnimator<Float> fadeIn = new PropertyAnimator<Float>(DEFAULT_ANIMATION_DURATION,
                     view, "alpha", view.getAlpha(), 1.0f);
             fadeIn.start();
         } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) {
@@ -148,49 +174,32 @@
             view.setVisibility(VISIBLE);
 
             LayoutParams lp = (LayoutParams) view.getLayoutParams();
-            int largestDuration =
-                Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION);
-
-            int duration = largestDuration;
-            if (mYVelocity != 0) {
-                duration = 1000*(0 - lp.verticalOffset)/Math.abs(mYVelocity);
-            }
-
-            duration = Math.min(duration, largestDuration);
-            duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);
+            int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity));
 
             StackSlider animationSlider = new StackSlider(mStackSlider);
-            PropertyAnimator slideInY = new PropertyAnimator(duration, animationSlider,
-                    "YProgress", mStackSlider.getYProgress(), 0);
-            slideInY.setInterpolator(new LinearInterpolator());
-            slideInY.start();
-            PropertyAnimator slideInX = new PropertyAnimator(duration, animationSlider,
-                    "XProgress", mStackSlider.getXProgress(), 0);
-            slideInX.setInterpolator(new LinearInterpolator());
-            slideInX.start();
+            PropertyValuesHolder<Float> slideInY =
+                    new PropertyValuesHolder<Float>("YProgress", 0.0f);
+            PropertyValuesHolder<Float> slideInX =
+                    new PropertyValuesHolder<Float>("XProgress", 0.0f);
+            PropertyAnimator pa = new PropertyAnimator(duration, animationSlider,
+                    slideInX, slideInY);
+            pa.setInterpolator(new LinearInterpolator());
+            pa.start();
         } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) {
             // Slide item out
             LayoutParams lp = (LayoutParams) view.getLayoutParams();
 
-            int largestDuration = Math.round(mStackSlider.getDurationForOffscreenPosition()*
-                    DEFAULT_ANIMATION_DURATION);
-            int duration = largestDuration;
-            if (mYVelocity != 0) {
-                duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity);
-            }
-
-            duration = Math.min(duration, largestDuration);
-            duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);
+            int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity));
 
             StackSlider animationSlider = new StackSlider(mStackSlider);
-            PropertyAnimator slideOutY = new PropertyAnimator(duration, animationSlider,
-                    "YProgress", mStackSlider.getYProgress(), 1);
-            slideOutY.setInterpolator(new LinearInterpolator());
-            slideOutY.start();
-            PropertyAnimator slideOutX = new PropertyAnimator(duration, animationSlider,
-                    "XProgress", mStackSlider.getXProgress(), 0);
-            slideOutX.setInterpolator(new LinearInterpolator());
-            slideOutX.start();
+            PropertyValuesHolder<Float> slideOutY =
+                    new PropertyValuesHolder<Float>("YProgress", 1.0f);
+            PropertyValuesHolder<Float> slideOutX =
+                    new PropertyValuesHolder<Float>("XProgress", 0.0f);
+            PropertyAnimator pa = new PropertyAnimator(duration, animationSlider,
+                   slideOutX, slideOutY);
+            pa.setInterpolator(new LinearInterpolator());
+            pa.start();
         } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) {
             // Make sure this view that is "waiting in the wings" is invisible
             view.setAlpha(0.0f);
@@ -199,28 +208,41 @@
             lp.setVerticalOffset(-mViewHeight);
         } else if (toIndex == -1) {
             // Fade item out
-            PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
-                    view, "alpha", view.getAlpha(), 0);
+            PropertyAnimator<Float> fadeOut = new PropertyAnimator<Float>
+                    (DEFAULT_ANIMATION_DURATION, view, "alpha", view.getAlpha(), 0.0f);
             fadeOut.start();
         }
+
+        // Implement the faked perspective
+        if (toIndex != -1) {
+            float maxPerpectiveShift = mViewHeight * PERSPECTIVE_SHIFT_FACTOR;
+            int index = toIndex;
+
+            if (toIndex == mNumActiveViews -1) index--;
+
+            float r = (index * 1.0f) / (mNumActiveViews - 2);
+
+            float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
+            PropertyValuesHolder<Float> scaleX = new PropertyValuesHolder<Float>("scaleX", scale);
+            PropertyValuesHolder<Float> scaleY = new PropertyValuesHolder<Float>("scaleY", scale);
+
+            r = (float) Math.pow(r, 2);
+
+            int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
+            float transY = -stackDirection * r * maxPerpectiveShift +
+                    stackDirection * (1 - scale) * (mViewHeight / 2.0f);
+
+            PropertyValuesHolder<Float> translationY =
+                    new PropertyValuesHolder<Float>("translationY", transY);
+            PropertyAnimator pa = new PropertyAnimator(100, view, scaleX, scaleY, translationY);
+            pa.start();
+        }
     }
 
     /**
      * Apply any necessary tranforms for the child that is being added.
      */
     void applyTransformForChildAtIndex(View child, int relativeIndex) {
-        if (!mRotations.containsKey(child)) {
-            float rotation = (float) (Math.random()*26 - 13);
-            mRotations.put(child, rotation);
-            child.setRotation(rotation);
-        }
-
-        // Child has been removed
-        if (relativeIndex == -1) {
-            if (mRotations.containsKey(child)) {
-                mRotations.remove(child);
-            }
-        }
     }
 
     @Override
@@ -248,8 +270,8 @@
 
     private void onLayout() {
         if (!mFirstLayoutHappened) {
-            mViewHeight = Math.round(SLIDE_UP_RATIO*getMeasuredHeight());
-            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight);
+            mViewHeight = Math.round(SLIDE_UP_RATIO * getMeasuredHeight());
+            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mViewHeight);
             mFirstLayoutHappened = true;
         }
     }
@@ -299,8 +321,14 @@
             cancelLongPress();
             requestDisallowInterceptTouchEvent(true);
 
-            int activeIndex = swipeGestureType == GESTURE_SLIDE_DOWN ? mNumActiveViews - 1
-                    : mNumActiveViews - 2;
+            int activeIndex;
+            if (mStackMode == ITEMS_SLIDE_UP) {
+                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ?
+                        mNumActiveViews - 1 : mNumActiveViews - 2;
+            } else {
+                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ?
+                        mNumActiveViews - 2 : mNumActiveViews - 1;
+            }
 
             if (mAdapter == null) return;
 
@@ -317,6 +345,8 @@
             if (v == null) return;
 
             mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
+            mHighlight.setRotation(v.getRotation());
+            mHighlight.setTranslationY(v.getTranslationY());
             mHighlight.bringToFront();
             v.bringToFront();
             mStackSlider.setView(v);
@@ -352,14 +382,16 @@
             case MotionEvent.ACTION_MOVE: {
                 beginGestureIfNeeded(deltaY);
 
-                float rx = deltaX/(mViewHeight*1.0f);
+                float rx = deltaX / (mViewHeight * 1.0f);
                 if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
-                    float r = (deltaY-mTouchSlop*1.0f)/mViewHeight*1.0f;
+                    float r = (deltaY - mTouchSlop * 1.0f) / mViewHeight * 1.0f;
+                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
                     mStackSlider.setYProgress(1 - r);
                     mStackSlider.setXProgress(rx);
                     return true;
                 } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
-                    float r = -(deltaY + mTouchSlop*1.0f)/mViewHeight*1.0f;
+                    float r = -(deltaY + mTouchSlop * 1.0f) / mViewHeight * 1.0f;
+                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
                     mStackSlider.setYProgress(r);
                     mStackSlider.setXProgress(rx);
                     return true;
@@ -447,41 +479,59 @@
         if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
                 && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
             // Swipe threshold exceeded, swipe down
-            showNext();
+            if (mStackMode == ITEMS_SLIDE_UP) {
+                showNext();
+            } else {
+                showPrevious();
+            }
             mHighlight.bringToFront();
         } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
                 && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
             // Swipe threshold exceeded, swipe up
-            showPrevious();
+            if (mStackMode == ITEMS_SLIDE_UP) {
+                showPrevious();
+            } else {
+                showNext();
+            }
+
             mHighlight.bringToFront();
-        } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
+        } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) {
             // Didn't swipe up far enough, snap back down
-            int duration =
-                Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION);
+            int duration;
+            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0;
+            if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
+                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
+            } else {
+                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
+            }
 
             StackSlider animationSlider = new StackSlider(mStackSlider);
-            PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider,
-                    "YProgress", mStackSlider.getYProgress(), 0);
-            snapBackY.setInterpolator(new LinearInterpolator());
-            snapBackY.start();
-            PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider,
-                    "XProgress", mStackSlider.getXProgress(), 0);
-            snapBackX.setInterpolator(new LinearInterpolator());
-            snapBackX.start();
+            PropertyValuesHolder<Float> snapBackY =
+                    new PropertyValuesHolder<Float>("YProgress", finalYProgress);
+            PropertyValuesHolder<Float> snapBackX =
+                    new PropertyValuesHolder<Float>("XProgress", 0.0f);
+            PropertyAnimator pa = new PropertyAnimator(duration, animationSlider,
+                    snapBackX, snapBackY);
+            pa.setInterpolator(new LinearInterpolator());
+            pa.start();
         } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
             // Didn't swipe down far enough, snap back up
-            int duration = Math.round(mStackSlider.getDurationForOffscreenPosition()*
-                    DEFAULT_ANIMATION_DURATION);
+            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1;
+            int duration;
+            if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
+                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
+            } else {
+                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
+            }
 
             StackSlider animationSlider = new StackSlider(mStackSlider);
-            PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider,
-                    "YProgress", mStackSlider.getYProgress(), 1);
-            snapBackY.setInterpolator(new LinearInterpolator());
-            snapBackY.start();
-            PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider,
-                    "XProgress", mStackSlider.getXProgress(), 0);
-            snapBackX.setInterpolator(new LinearInterpolator());
-            snapBackX.start();
+            PropertyValuesHolder<Float> snapBackY =
+                    new PropertyValuesHolder<Float>("YProgress", finalYProgress);
+            PropertyValuesHolder<Float> snapBackX =
+                    new PropertyValuesHolder<Float>("XProgress", 0.0f);
+            PropertyAnimator pa = new PropertyAnimator(duration, animationSlider,
+                    snapBackX, snapBackY);
+            pa.start();
         }
 
         mActivePointerId = INVALID_POINTER;
@@ -510,22 +560,22 @@
         }
 
         private float cubic(float r) {
-            return (float) (Math.pow(2*r-1, 3) + 1)/2.0f;
+            return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f;
         }
 
         private float highlightAlphaInterpolator(float r) {
             float pivot = 0.4f;
             if (r < pivot) {
-                return 0.85f*cubic(r/pivot);
+                return 0.85f * cubic(r / pivot);
             } else {
-                return 0.85f*cubic(1 - (r-pivot)/(1-pivot));
+                return 0.85f * cubic(1 - (r - pivot) / (1 - pivot));
             }
         }
 
         private float viewAlphaInterpolator(float r) {
             float pivot = 0.3f;
             if (r > pivot) {
-                return (r - pivot)/(1 - pivot);
+                return (r - pivot) / (1 - pivot);
             } else {
                 return 0;
             }
@@ -536,7 +586,7 @@
             if (r < pivot) {
                 return 0;
             } else {
-                return (r-pivot)/(1-pivot);
+                return (r - pivot) / (1 - pivot);
             }
         }
 
@@ -553,13 +603,15 @@
             final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
             final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
 
+            int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
+
             switch (mMode) {
                 case NORMAL_MODE:
-                    viewLp.setVerticalOffset(Math.round(-r*mViewHeight));
-                    highlightLp.setVerticalOffset(Math.round(-r*mViewHeight));
+                    viewLp.setVerticalOffset(Math.round(-r * stackDirection * mViewHeight));
+                    highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mViewHeight));
                     mHighlight.setAlpha(highlightAlphaInterpolator(r));
 
-                    float alpha = viewAlphaInterpolator(1-r);
+                    float alpha = viewAlphaInterpolator(1 - r);
 
                     // We make sure that views which can't be seen (have 0 alpha) are also invisible
                     // so that they don't interfere with click events.
@@ -571,19 +623,19 @@
                     }
 
                     mView.setAlpha(alpha);
-                    mView.setRotationX(90.0f*rotationInterpolator(r));
-                    mHighlight.setRotationX(90.0f*rotationInterpolator(r));
+                    mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
+                    mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
                     break;
                 case BEGINNING_OF_STACK_MODE:
-                    r = r*0.2f;
-                    viewLp.setVerticalOffset(Math.round(-r*mViewHeight));
-                    highlightLp.setVerticalOffset(Math.round(-r*mViewHeight));
+                    r = r * 0.2f;
+                    viewLp.setVerticalOffset(Math.round(-stackDirection * r * mViewHeight));
+                    highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mViewHeight));
                     mHighlight.setAlpha(highlightAlphaInterpolator(r));
                     break;
                 case END_OF_STACK_MODE:
-                    r = (1-r)*0.2f;
-                    viewLp.setVerticalOffset(Math.round(r*mViewHeight));
-                    highlightLp.setVerticalOffset(Math.round(r*mViewHeight));
+                    r = (1-r) * 0.2f;
+                    viewLp.setVerticalOffset(Math.round(stackDirection * r * mViewHeight));
+                    highlightLp.setVerticalOffset(Math.round(stackDirection * r * mViewHeight));
                     mHighlight.setAlpha(highlightAlphaInterpolator(r));
                     break;
             }
@@ -600,8 +652,8 @@
             final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
 
             r *= 0.2f;
-            viewLp.setHorizontalOffset(Math.round(r*mViewHeight));
-            highlightLp.setHorizontalOffset(Math.round(r*mViewHeight));
+            viewLp.setHorizontalOffset(Math.round(r * mViewHeight));
+            highlightLp.setHorizontalOffset(Math.round(r * mViewHeight));
         }
 
         void setMode(int mode) {
@@ -609,31 +661,51 @@
         }
 
         float getDurationForNeutralPosition() {
-            return getDuration(false);
+            return getDuration(false, 0);
         }
 
         float getDurationForOffscreenPosition() {
-            return getDuration(mMode == END_OF_STACK_MODE ? false : true);
+            return getDuration(true, 0);
         }
 
-        private float getDuration(boolean invert) {
+        float getDurationForNeutralPosition(float velocity) {
+            return getDuration(false, velocity);
+        }
+
+        float getDurationForOffscreenPosition(float velocity) {
+            return getDuration(true, velocity);
+        }
+
+        private float getDuration(boolean invert, float velocity) {
             if (mView != null) {
                 final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
 
-                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset,2) +
-                        Math.pow(viewLp.verticalOffset,2));
+                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) +
+                        Math.pow(viewLp.verticalOffset, 2));
                 float maxd = (float) Math.sqrt(Math.pow(mViewHeight, 2) +
-                        Math.pow(0.4f*mViewHeight, 2));
-                return invert ? (1-d/maxd) : d/maxd;
+                        Math.pow(0.4f * mViewHeight, 2));
+
+                if (velocity == 0) {
+                    return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION;
+                } else {
+                    float duration = invert ? d / Math.abs(velocity) :
+                            (maxd - d) / Math.abs(velocity);
+                    if (duration < MINIMUM_ANIMATION_DURATION ||
+                            duration > DEFAULT_ANIMATION_DURATION) {
+                        return getDuration(invert, 0);
+                    } else {
+                        return duration;
+                    }
+                }
             }
             return 0;
         }
 
-        float getYProgress() {
+        public float getYProgress() {
             return mYProgress;
         }
 
-        float getXProgress() {
+        public float getXProgress() {
             return mXProgress;
         }
     }
@@ -654,6 +726,8 @@
             LayoutParams lp = (LayoutParams) currentLp;
             lp.setHorizontalOffset(0);
             lp.setVerticalOffset(0);
+            lp.width = 0;
+            lp.width = 0;
             return lp;
         }
         return new LayoutParams(v);
@@ -684,15 +758,59 @@
             child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
                     childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
 
-            //TODO: temp until fix in View
-            child.setPivotX(child.getMeasuredWidth()/2);
-            child.setPivotY(child.getMeasuredHeight()/2);
         }
 
         mDataChanged = false;
         onLayout();
     }
 
+    private void measureChildren() {
+        final int count = getChildCount();
+        final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
+        final int childHeight = Math.round(mMeasuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR))
+                - mPaddingTop - mPaddingBottom;
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
+
+        // We need to deal with the case where our parent hasn't told us how
+        // big we should be. In this case we should
+        float factor = 1/(1 - PERSPECTIVE_SHIFT_FACTOR);
+        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
+            heightSpecSize = haveChildRefSize ?
+                    Math.round(mReferenceChildHeight * (1 + factor)) +
+                    mPaddingTop + mPaddingBottom : 0;
+        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+            heightSpecSize = haveChildRefSize ? Math.min(
+                    Math.round(mReferenceChildHeight * (1 + factor)) + mPaddingTop +
+                    mPaddingBottom, heightSpecSize) : 0;
+        }
+
+        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
+            widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
+                    mPaddingRight : 0;
+        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
+            widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft +
+                    mPaddingRight, widthSpecSize) : 0;
+        }
+
+        setMeasuredDimension(widthSpecSize, heightSpecSize);
+        measureChildren();
+    }
+
     class LayoutParams extends ViewGroup.LayoutParams {
         int horizontalOffset;
         int verticalOffset;
@@ -700,6 +818,8 @@
 
         LayoutParams(View view) {
             super(0, 0);
+            width = 0;
+            height = 0;
             horizontalOffset = 0;
             verticalOffset = 0;
             mView = view;
@@ -709,6 +829,8 @@
             super(c, attrs);
             horizontalOffset = 0;
             verticalOffset = 0;
+            width = 0;
+            height = 0;
         }
 
         private Rect parentRect = new Rect();
@@ -731,6 +853,10 @@
                 gp = (View) p.getParent();
                 parentRect.set(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY(),
                         p.getRight() - gp.getScrollX(), p.getBottom() - gp.getScrollY());
+
+                // TODO: we need to stop early here if we've hit the edge of the screen
+                // so as to prevent us from walking too high in the hierarchy. A lot of this
+                // code might become a lot more straightforward.
             }
 
             if (depth > mAncestorHeight) {
@@ -799,7 +925,7 @@
     private static class HolographicHelper {
         private final Paint mHolographicPaint = new Paint();
         private final Paint mErasePaint = new Paint();
-        private final float STROKE_WIDTH = 3.0f;
+        private final Paint mBlurPaint = new Paint();
 
         HolographicHelper() {
             initializePaints();
@@ -808,8 +934,10 @@
         void initializePaints() {
             mHolographicPaint.setColor(0xff6699ff);
             mHolographicPaint.setFilterBitmap(true);
+            mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
             mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
             mErasePaint.setFilterBitmap(true);
+            mBlurPaint.setMaskFilter(new BlurMaskFilter(2, BlurMaskFilter.Blur.NORMAL));
         }
 
         Bitmap createOutline(View v) {
@@ -822,31 +950,31 @@
             Canvas canvas = new Canvas(bitmap);
 
             float rotationX = v.getRotationX();
+            float rotation = v.getRotation();
+            float translationY = v.getTranslationY();
             v.setRotationX(0);
+            v.setRotation(0);
+            v.setTranslationY(0);
             canvas.concat(v.getMatrix());
             v.draw(canvas);
-
             v.setRotationX(rotationX);
+            v.setRotation(rotation);
+            v.setTranslationY(translationY);
+            canvas.setMatrix(id);
 
             drawOutline(canvas, bitmap);
             return bitmap;
         }
 
         final Matrix id = new Matrix();
-        final Matrix scaleMatrix = new Matrix();
         void drawOutline(Canvas dest, Bitmap src) {
-            Bitmap mask = src.extractAlpha();
-
+            int[] xy = new int[2];
+            Bitmap mask = src.extractAlpha(mBlurPaint, xy);
+            Canvas maskCanvas = new Canvas(mask);
+            maskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint);
             dest.drawColor(0, PorterDuff.Mode.CLEAR);
-
-            float xScale = STROKE_WIDTH*2/(dest.getWidth());
-            float yScale = STROKE_WIDTH*2/(dest.getHeight());
-
-            scaleMatrix.reset();
-            scaleMatrix.preScale(1+xScale, 1+yScale, dest.getWidth()/2, dest.getHeight()/2);
             dest.setMatrix(id);
-            dest.drawBitmap(mask, scaleMatrix, mHolographicPaint);
-            dest.drawBitmap(mask, id, mErasePaint);
+            dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
             mask.recycle();
         }
     }
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
index 4b3f1c0..36e9089 100644
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -593,6 +593,9 @@
         case SQLITE_MISMATCH:
            exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException";
            break;
+        case SQLITE_UNCLOSED:
+           exceptionClass = "android/database/sqlite/SQLiteUnfinalizedObjectsException";
+           break;
         default:
            exceptionClass = "android/database/sqlite/SQLiteException";
            break;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java
new file mode 100644
index 0000000..cd2005d
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.File;
+
+public class SQLiteUnfinalizedExceptionTest extends AndroidTestCase {
+    private SQLiteDatabase mDatabase;
+    private File mDatabaseFile;
+    private static final String TABLE_NAME = "testCursor";
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
+        mDatabaseFile = new File(dbDir, "UnfinalizedExceptionTest.db");
+        if (mDatabaseFile.exists()) {
+            mDatabaseFile.delete();
+        }
+        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+        assertNotNull(mDatabase);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDatabase.close();
+        mDatabaseFile.delete();
+        super.tearDown();
+    }
+
+    @SmallTest
+    public void testUnfinalizedExceptionNotExcpected() {
+        mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
+        // the above statement should be in SQLiteDatabase.mPrograms
+        // and should automatically be finalized when database is closed
+        mDatabase.lock();
+        try {
+            mDatabase.closeDatabase();
+        } finally {
+            mDatabase.unlock();
+        }
+    }
+
+    @SmallTest
+    public void testUnfinalizedException() {
+        mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
+        mDatabase.lock();
+        mDatabase.closePendingStatements(); // clears the above from finalizer queue in mdatabase
+        mDatabase.unlock();
+        ClassToTestSqlCompilationAndCaching.create(mDatabase, "select * from "  + TABLE_NAME);
+        // since the above is NOT closed, closing database should fail
+        mDatabase.lock();
+        try {
+            mDatabase.closeDatabase();
+            fail("exception expected");
+        } catch (SQLiteUnfinalizedObjectsException e) {
+            // expected
+        } finally {
+            mDatabase.unlock();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
index 60c3c76..c8ad60d 100644
--- a/core/tests/coretests/src/android/net/UriTest.java
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -285,6 +285,7 @@
         assertEquals("d", uri.getQueryParameter("c"));
     }
 
+    // http://b/2337042
     @SmallTest
     public void testHostWithTrailingDot() {
         Uri uri = Uri.parse("http://google.com./b/c/g");
diff --git a/core/tests/coretests/src/android/net/WebAddressTest.java b/core/tests/coretests/src/android/net/WebAddressTest.java
index 7ca1e62..f0af35d 100644
--- a/core/tests/coretests/src/android/net/WebAddressTest.java
+++ b/core/tests/coretests/src/android/net/WebAddressTest.java
@@ -22,10 +22,19 @@
 
 public class WebAddressTest extends TestCase {
 
+    // http://b/2337042
     @SmallTest
     public void testHostWithTrailingDot() {
         WebAddress webAddress = new WebAddress("http://google.com./b/c/g");
         assertEquals("google.com.", webAddress.mHost);
         assertEquals("/b/c/g", webAddress.mPath);
     }
+
+    // http://b/1011602
+    @SmallTest
+    public void testPathWithoutLeadingSlash() {
+        WebAddress webAddress = new WebAddress("http://www.myspace.com?si=1");
+        assertEquals("www.myspace.com", webAddress.mHost);
+        assertEquals("/?si=1", webAddress.mPath);
+    }
 }
diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java
index ed09f89..05b2d60 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -27,6 +27,7 @@
     int mSize;
     Element[] mElements;
     String[] mElementNames;
+    int[] mArraySizes;
 
     DataType mType;
     DataKind mKind;
@@ -313,11 +314,12 @@
         return rs.mElement_MATRIX_2X2;
     }
 
-    Element(int id, RenderScript rs, Element[] e, String[] n) {
+    Element(int id, RenderScript rs, Element[] e, String[] n, int[] as) {
         super(id, rs);
         mSize = 0;
         mElements = e;
         mElementNames = n;
+        mArraySizes = as;
         for (int ct = 0; ct < mElements.length; ct++ ) {
             mSize += mElements[ct].mSize;
         }
@@ -441,6 +443,7 @@
         RenderScript mRS;
         Element[] mElements;
         String[] mElementNames;
+        int[] mArraySizes;
         int mCount;
 
         public Builder(RenderScript rs) {
@@ -448,35 +451,49 @@
             mCount = 0;
             mElements = new Element[8];
             mElementNames = new String[8];
+            mArraySizes = new int[8];
         }
 
-        public void add(Element element, String name) {
+        public void add(Element element, String name, int arraySize) {
+            if (arraySize < 1) {
+                throw new IllegalArgumentException("Array size cannot be less than 1.");
+            }
             if(mCount == mElements.length) {
                 Element[] e = new Element[mCount + 8];
                 String[] s = new String[mCount + 8];
+                int[] as = new int[mCount + 8];
                 System.arraycopy(mElements, 0, e, 0, mCount);
                 System.arraycopy(mElementNames, 0, s, 0, mCount);
+                System.arraycopy(mArraySizes, 0, as, 0, mCount);
                 mElements = e;
                 mElementNames = s;
+                mArraySizes = as;
             }
             mElements[mCount] = element;
             mElementNames[mCount] = name;
+            mArraySizes[mCount] = arraySize;
             mCount++;
         }
 
+        public void add(Element element, String name) {
+            add(element, name, 1);
+        }
+
         public Element create() {
             mRS.validate();
             Element[] ein = new Element[mCount];
             String[] sin = new String[mCount];
+            int[] asin = new int[mCount];
             java.lang.System.arraycopy(mElements, 0, ein, 0, mCount);
             java.lang.System.arraycopy(mElementNames, 0, sin, 0, mCount);
+            java.lang.System.arraycopy(mArraySizes, 0, asin, 0, mCount);
 
             int[] ids = new int[ein.length];
             for (int ct = 0; ct < ein.length; ct++ ) {
                 ids[ct] = ein[ct].mID;
             }
-            int id = mRS.nElementCreate2(ids, sin);
-            return new Element(id, mRS, ein, sin);
+            int id = mRS.nElementCreate2(ids, sin, asin);
+            return new Element(id, mRS, ein, sin, asin);
         }
     }
 
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 62d70a7..1f3e159 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -152,9 +152,9 @@
     synchronized int nElementCreate(int type, int kind, boolean norm, int vecSize) {
         return rsnElementCreate(mContext, type, kind, norm, vecSize);
     }
-    native int  rsnElementCreate2(int con, int[] elements, String[] names);
-    synchronized int nElementCreate2(int[] elements, String[] names) {
-        return rsnElementCreate2(mContext, elements, names);
+    native int  rsnElementCreate2(int con, int[] elements, String[] names, int[] arraySizes);
+    synchronized int nElementCreate2(int[] elements, String[] names, int[] arraySizes) {
+        return rsnElementCreate2(mContext, elements, names, arraySizes);
     }
     native void rsnElementGetNativeData(int con, int id, int[] elementData);
     synchronized void nElementGetNativeData(int id, int[] elementData) {
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index c8e6656..f07dbfd8 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -253,12 +253,13 @@
 }
 
 static jint
-nElementCreate2(JNIEnv *_env, jobject _this, RsContext con, jintArray _ids, jobjectArray _names)
+nElementCreate2(JNIEnv *_env, jobject _this, RsContext con, jintArray _ids, jobjectArray _names, jintArray _arraySizes)
 {
     int fieldCount = _env->GetArrayLength(_ids);
     LOG_API("nElementCreate2, con(%p)", con);
 
     jint *ids = _env->GetIntArrayElements(_ids, NULL);
+    jint *arraySizes = _env->GetIntArrayElements(_arraySizes, NULL);
     const char ** nameArray = (const char **)calloc(fieldCount, sizeof(char *));
     size_t* sizeArray = (size_t*)calloc(fieldCount, sizeof(size_t));
 
@@ -267,12 +268,13 @@
         nameArray[ct] = _env->GetStringUTFChars(s, NULL);
         sizeArray[ct] = _env->GetStringUTFLength(s);
     }
-    jint id = (jint)rsElementCreate2(con, fieldCount, (RsElement *)ids, nameArray, sizeArray);
+    jint id = (jint)rsElementCreate2(con, fieldCount, (RsElement *)ids, nameArray, sizeArray, (const uint32_t *)arraySizes);
     for (int ct=0; ct < fieldCount; ct++) {
         jstring s = (jstring)_env->GetObjectArrayElement(_names, ct);
         _env->ReleaseStringUTFChars(s, nameArray[ct]);
     }
     _env->ReleaseIntArrayElements(_ids, ids, JNI_ABORT);
+    _env->ReleaseIntArrayElements(_arraySizes, arraySizes, JNI_ABORT);
     free(nameArray);
     free(sizeArray);
     return (jint)id;
@@ -1230,7 +1232,7 @@
 {"rsnFontCreateFromFile",            "(ILjava/lang/String;II)I",             (void*)nFontCreateFromFile },
 
 {"rsnElementCreate",                 "(IIIZI)I",                              (void*)nElementCreate },
-{"rsnElementCreate2",                "(I[I[Ljava/lang/String;)I",             (void*)nElementCreate2 },
+{"rsnElementCreate2",                "(I[I[Ljava/lang/String;[I)I",           (void*)nElementCreate2 },
 {"rsnElementGetNativeData",          "(II[I)V",                               (void*)nElementGetNativeData },
 {"rsnElementGetSubElements",         "(II[I[Ljava/lang/String;)V",           (void*)nElementGetSubElements },
 
diff --git a/libs/rs/java/ModelViewer/res/raw/robot.a3d b/libs/rs/java/ModelViewer/res/raw/robot.a3d
index 2d7d32b..f48895c 100644
--- a/libs/rs/java/ModelViewer/res/raw/robot.a3d
+++ b/libs/rs/java/ModelViewer/res/raw/robot.a3d
Binary files differ
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
index 9672a6a..85c1d42 100644
--- a/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SceneGraphRS.java
@@ -105,7 +105,7 @@
         bs.setMin(Sampler.Value.LINEAR);
         bs.setMag(Sampler.Value.LINEAR);
         bs.setWrapS(Sampler.Value.CLAMP);
-        bs.setWrapT(Sampler.Value.WRAP);
+        bs.setWrapT(Sampler.Value.CLAMP);
         mSampler = bs.create();
 
         ProgramFragment.Builder b = new ProgramFragment.Builder(mRS);
@@ -123,7 +123,6 @@
 
         mPVA = new ProgramVertex.MatrixAllocation(mRS);
         mPVBackground.bindAllocation(mPVA);
-        mPVA.setupProjectionNormalized(mWidth, mHeight);
 
         mScript.set_gPVBackground(mPVBackground);
     }
@@ -159,14 +158,14 @@
         mGroup1.addChild(mRobot1);
         mGroup1.addChild(mRobot2);
 
-        mGroup1.setTransform(0, new Float4(0.0f, 0.0f, 5.0f, 0.0f), TransformType.TRANSLATE);
+        mGroup1.setTransform(0, new Float4(0.0f, 0.0f, -15.0f, 0.0f), TransformType.TRANSLATE);
         mGroup1.setTransform(1, new Float4(0.0f, 1.0f, 0.0f, 15.0f), TransformType.ROTATE);
 
-        mRobot1.setTransform(0, new Float4(-2.0f, -0.5f, 0.0f, 0.0f), TransformType.TRANSLATE);
+        mRobot1.setTransform(0, new Float4(-3.0f, -0.5f, 0.0f, 0.0f), TransformType.TRANSLATE);
         mRobot1.setTransform(1, new Float4(0.0f, 1.0f, 0.0f, 20.0f), TransformType.ROTATE);
         mRobot1.setTransform(2, new Float4(0.2f, 0.2f, 0.2f, 0.0f), TransformType.SCALE);
 
-        mRobot2.setTransform(0, new Float4(2.0f, 0.0f, 0.0f, 0.0f), TransformType.TRANSLATE);
+        mRobot2.setTransform(0, new Float4(3.0f, 0.0f, 0.0f, 0.0f), TransformType.TRANSLATE);
         mRobot2.setTransform(1, new Float4(0.0f, 1.0f, 0.0f, -20.0f), TransformType.ROTATE);
         mRobot2.setTransform(2, new Float4(0.3f, 0.3f, 0.3f, 0.0f), TransformType.SCALE);
     }
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelRS.java b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelRS.java
index b8b0119..d8d1a6e 100644
--- a/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelRS.java
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/SimpleModelRS.java
@@ -79,7 +79,7 @@
             mRotation += 360;
         }
 
-        mScript.set_gRotate(-(float)mRotation);
+        mScript.set_gRotate((float)mRotation);
 
         mLastX = x;
         mLastY = y;
@@ -101,7 +101,7 @@
         bs.setMin(Sampler.Value.LINEAR);
         bs.setMag(Sampler.Value.LINEAR);
         bs.setWrapS(Sampler.Value.CLAMP);
-        bs.setWrapT(Sampler.Value.WRAP);
+        bs.setWrapT(Sampler.Value.CLAMP);
         mSampler = bs.create();
 
         ProgramFragment.Builder b = new ProgramFragment.Builder(mRS);
@@ -119,7 +119,6 @@
 
         mPVA = new ProgramVertex.MatrixAllocation(mRS);
         mPVBackground.bindAllocation(mPVA);
-        mPVA.setupProjectionNormalized(mWidth, mHeight);
 
         mScript.set_gPVBackground(mPVBackground);
     }
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/scenegraph.rs b/libs/rs/java/ModelViewer/src/com/android/modelviewer/scenegraph.rs
index c794438..ce6bb1e 100644
--- a/libs/rs/java/ModelViewer/src/com/android/modelviewer/scenegraph.rs
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/scenegraph.rs
@@ -71,6 +71,10 @@
     rsgClearDepth(1.0f);
 
     rsgBindProgramVertex(gPVBackground);
+    rs_matrix4x4 proj;
+    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
 
     rsgBindProgramFragment(gPFBackground);
     rsgBindProgramStore(gPFSBackground);
diff --git a/libs/rs/java/ModelViewer/src/com/android/modelviewer/simplemodel.rs b/libs/rs/java/ModelViewer/src/com/android/modelviewer/simplemodel.rs
index adb609c..43be266 100644
--- a/libs/rs/java/ModelViewer/src/com/android/modelviewer/simplemodel.rs
+++ b/libs/rs/java/ModelViewer/src/com/android/modelviewer/simplemodel.rs
@@ -46,6 +46,10 @@
     rsgClearDepth(1.0f);
 
     rsgBindProgramVertex(gPVBackground);
+    rs_matrix4x4 proj;
+    float aspect = (float)rsgGetWidth() / (float)rsgGetHeight();
+    rsMatrixLoadPerspective(&proj, 30.0f, aspect, 0.1f, 100.0f);
+    rsgProgramVertexLoadProjectionMatrix(&proj);
 
     rsgBindProgramFragment(gPFBackground);
     rsgBindProgramStore(gPFSBackground);
@@ -54,9 +58,9 @@
     rs_matrix4x4 matrix;
     rsMatrixLoadIdentity(&matrix);
     // Position our model on the screen
-    rsMatrixTranslate(&matrix, 0.0f, -0.3f, 1.2f);
+    rsMatrixTranslate(&matrix, 0.0f, -0.3f, -10.0f);
     rsMatrixScale(&matrix, 0.2f, 0.2f, 0.2f);
-    rsMatrixRotate(&matrix, -25.0f, 1.0f, 0.0f, 0.0f);
+    rsMatrixRotate(&matrix, 25.0f, 1.0f, 0.0f, 0.0f);
     rsMatrixRotate(&matrix, gRotate, 0.0f, 1.0f, 0.0f);
     rsgProgramVertexLoadModelMatrix(&matrix);
 
diff --git a/libs/rs/java/Samples/res/raw/torus.a3d b/libs/rs/java/Samples/res/raw/torus.a3d
index d09bc13..0322b01 100644
--- a/libs/rs/java/Samples/res/raw/torus.a3d
+++ b/libs/rs/java/Samples/res/raw/torus.a3d
Binary files differ
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index ad162bb..c7fb2af 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -80,6 +80,7 @@
 	param const RsElement * elements
 	param const char ** names
 	param const size_t * nameLengths
+	param const uint32_t * arraySize
 	ret RsElement
 	}
 
diff --git a/libs/rs/rsElement.cpp b/libs/rs/rsElement.cpp
index 2602dd4..0b7bb27 100644
--- a/libs/rs/rsElement.cpp
+++ b/libs/rs/rsElement.cpp
@@ -65,7 +65,7 @@
 
     size_t total = 0;
     for (size_t ct=0; ct < mFieldCount; ct++) {
-        total += mFields[ct].e->mBits;
+        total += mFields[ct].e->mBits * mFields[ct].arraySize;;
     }
     return total;
 }
@@ -95,6 +95,7 @@
     stream->addU32(mFieldCount);
     for(uint32_t ct = 0; ct < mFieldCount; ct++) {
         stream->addString(&mFields[ct].name);
+        stream->addU32(mFields[ct].arraySize);
         mFields[ct].e->serialize(stream);
     }
 }
@@ -122,6 +123,7 @@
         elem->mFields = new ElementField_t [elem->mFieldCount];
         for(uint32_t ct = 0; ct < elem->mFieldCount; ct ++) {
             stream->loadString(&elem->mFields[ct].name);
+            elem->mFields[ct].arraySize = stream->loadU32();
             Element *fieldElem = Element::createFromStream(rsc, stream);
             elem->mFields[ct].e.set(fieldElem);
             elem->mFields[ct].offsetBits = offset;
@@ -155,7 +157,8 @@
             for (uint32_t i=0; i < elem->mFieldCount; i++) {
                 if ((ee->mFields[i].e.get() != elem->mFields[i].e.get()) ||
                     (ee->mFields[i].name.length() != elem->mFields[i].name.length()) ||
-                    (ee->mFields[i].name != elem->mFields[i].name)) {
+                    (ee->mFields[i].name != elem->mFields[i].name) ||
+                    (ee->mFields[i].arraySize != elem->mFields[i].arraySize)) {
                     match = false;
                     break;
                 }
@@ -200,7 +203,7 @@
 }
 
 const Element * Element::create(Context *rsc, size_t count, const Element **ein,
-                            const char **nin, const size_t * lengths)
+                            const char **nin, const size_t * lengths, const uint32_t *asin)
 {
     // Look for an existing match.
     for (uint32_t ct=0; ct < rsc->mStateElement.mElements.size(); ct++) {
@@ -210,7 +213,8 @@
             for (uint32_t i=0; i < count; i++) {
                 if ((ee->mFields[i].e.get() != ein[i]) ||
                     (ee->mFields[i].name.length() != lengths[i]) ||
-                    (ee->mFields[i].name != nin[i])) {
+                    (ee->mFields[i].name != nin[i]) ||
+                    (ee->mFields[i].arraySize != asin[i])) {
                     match = false;
                     break;
                 }
@@ -230,6 +234,7 @@
         e->mFields[ct].e.set(ein[ct]);
         e->mFields[ct].name.setTo(nin[ct], lengths[ct]);
         e->mFields[ct].offsetBits = bits;
+        e->mFields[ct].arraySize = asin[ct];
         bits += ein[ct]->getSizeBits();
 
         if (ein[ct]->mHasReference) {
@@ -274,7 +279,11 @@
     const uint8_t *p = static_cast<const uint8_t *>(ptr);
     for (uint32_t i=0; i < mFieldCount; i++) {
         if (mFields[i].e->mHasReference) {
-            mFields[i].e->incRefs(&p[mFields[i].offsetBits >> 3]);
+            p = &p[mFields[i].offsetBits >> 3];
+            for (uint32_t ct=0; ct < mFields[i].arraySize; ct++) {
+                mFields[i].e->incRefs(p);
+                p += mFields[i].e->getSizeBytes();
+            }
         }
     }
 }
@@ -293,7 +302,11 @@
     const uint8_t *p = static_cast<const uint8_t *>(ptr);
     for (uint32_t i=0; i < mFieldCount; i++) {
         if (mFields[i].e->mHasReference) {
-            mFields[i].e->decRefs(&p[mFields[i].offsetBits >> 3]);
+            p = &p[mFields[i].offsetBits >> 3];
+            for (uint32_t ct=0; ct < mFields[i].arraySize; ct++) {
+                mFields[i].e->decRefs(p);
+                p += mFields[i].e->getSizeBytes();
+            }
         }
     }
 }
@@ -331,10 +344,11 @@
                              size_t count,
                              const RsElement * ein,
                              const char ** names,
-                             const size_t * nameLengths)
+                             const size_t * nameLengths,
+                             const uint32_t * arraySizes)
 {
     //LOGE("rsi_ElementCreate2 %i", count);
-    const Element *e = Element::create(rsc, count, (const Element **)ein, names, nameLengths);
+    const Element *e = Element::create(rsc, count, (const Element **)ein, names, nameLengths, arraySizes);
     e->incUserRef();
     return (RsElement)e;
 }
diff --git a/libs/rs/rsElement.h b/libs/rs/rsElement.h
index b5dad7a2..42eef4a 100644
--- a/libs/rs/rsElement.h
+++ b/libs/rs/rsElement.h
@@ -66,7 +66,7 @@
     static const Element * create(Context *rsc, RsDataType dt, RsDataKind dk,
                             bool isNorm, uint32_t vecSize);
     static const Element * create(Context *rsc, size_t count, const Element **,
-                            const char **, const size_t * lengths);
+                            const char **, const size_t * lengths, const uint32_t *asin);
 
     void incRefs(const void *) const;
     void decRefs(const void *) const;
@@ -80,6 +80,7 @@
         String8 name;
         ObjectBaseRef<const Element> e;
         uint32_t offsetBits;
+        uint32_t arraySize;
     } ElementField_t;
     ElementField_t *mFields;
     size_t mFieldCount;
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index 7661d499..5889bfb 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -488,8 +488,9 @@
     size_t lengths[2];
     lengths[0] = posName.size();
     lengths[1] = texName.size();
+    uint32_t arraySizes[2] = {1, 1};
 
-    const Element *vertexDataElem = Element::create(mRSC, 2, elemArray, nameArray, lengths);
+    const Element *vertexDataElem = Element::create(mRSC, 2, elemArray, nameArray, lengths, arraySizes);
 
     Type *vertexDataType = new Type(mRSC);
     vertexDataType->setDimX(mMaxNumberOfQuads * 4);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
index 33acecb..48243ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
@@ -39,6 +39,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
+import android.text.TextUtils;
 import android.util.Slog;
 import android.util.Log;
 import android.view.Display;
@@ -468,7 +469,11 @@
         }
 
         // Restart the ticker if it's still running
-        tick(notification);
+        if (notification.notification.tickerText != null
+                && !TextUtils.equals(notification.notification.tickerText,
+                    oldEntry.notification.notification.tickerText)) {
+            tick(notification);
+        }
 
         // Recalculate the position of the sliding windows and the titles.
         setAreThereNotifications();