Merge "Add new sample code for writing a ViewGroup." into jb-mr2-dev
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index da398ef..22ce841 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -1094,7 +1094,7 @@
     private boolean deletePackage(String pkg, int unInstallFlags) {
         PackageDeleteObserver obs = new PackageDeleteObserver();
         try {
-            mPm.deletePackage(pkg, obs, unInstallFlags);
+            mPm.deletePackageAsUser(pkg, obs, UserHandle.USER_OWNER, unInstallFlags);
 
             synchronized (obs) {
                 while (!obs.finished) {
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index e9535ab..fa46689 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -275,6 +275,38 @@
                 handleException(response, "getAccountRemovalAllowed", account.toString(), e);
             }
         }
+
+        public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
+                Account account) throws RemoteException {
+            checkBinderPermission();
+            try {
+                final Bundle result =
+                        AbstractAccountAuthenticator.this.getAccountCredentialsForCloning(
+                                new AccountAuthenticatorResponse(response), account);
+                if (result != null) {
+                    response.onResult(result);
+                }
+            } catch (Exception e) {
+                handleException(response, "getAccountCredentialsForCloning", account.toString(), e);
+            }
+        }
+
+        public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
+                Account account,
+                Bundle accountCredentials) throws RemoteException {
+            checkBinderPermission();
+            try {
+                final Bundle result =
+                        AbstractAccountAuthenticator.this.addAccountFromCredentials(
+                                new AccountAuthenticatorResponse(response), account,
+                                accountCredentials);
+                if (result != null) {
+                    response.onResult(result);
+                }
+            } catch (Exception e) {
+                handleException(response, "addAccountFromCredentials", account.toString(), e);
+            }
+        }
     }
 
     private void handleException(IAccountAuthenticatorResponse response, String method,
@@ -471,4 +503,54 @@
         result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
         return result;
     }
+
+    /**
+     * @hide
+     * Returns a Bundle that contains whatever is required to clone the account on a different
+     * user. The Bundle is passed to the authenticator instance in the target user via
+     * {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}.
+     * The default implementation returns null, indicating that cloning is not supported.
+     * @param response to send the result back to the AccountManager, will never be null
+     * @param account the account to clone, will never be null
+     * @return a Bundle result or null if the result is to be returned via the response.
+     * @throws NetworkErrorException
+     * @see {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}
+     */
+    public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
+            final Account account) throws NetworkErrorException {
+        new Thread(new Runnable() {
+            public void run() {
+                Bundle result = new Bundle();
+                result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+                response.onResult(result);
+            }
+        }).start();
+        return null;
+    }
+
+    /**
+     * @hide
+     * Creates an account based on credentials provided by the authenticator instance of another
+     * user on the device, who has chosen to share the account with this user.
+     * @param response to send the result back to the AccountManager, will never be null
+     * @param account the account to clone, will never be null
+     * @param accountCredentials the Bundle containing the required credentials to create the
+     * account. Contents of the Bundle are only meaningful to the authenticator. This Bundle is
+     * provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}.
+     * @return a Bundle result or null if the result is to be returned via the response.
+     * @throws NetworkErrorException
+     * @see {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}
+     */
+    public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response,
+            Account account,
+            Bundle accountCredentials) throws NetworkErrorException {
+        new Thread(new Runnable() {
+            public void run() {
+                Bundle result = new Bundle();
+                result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+                response.onResult(result);
+            }
+        }).start();
+        return null;
+    }
 }
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 6d9bb1d..6aac723 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1123,6 +1123,57 @@
     }
 
     /**
+     * Adds a shared account from the primary user to a secondary user. Adding the shared account
+     * doesn't take effect immediately. When the target user starts up, any pending shared accounts
+     * are attempted to be copied to the target user from the primary via calls to the
+     * authenticator.
+     * @param account the account to share
+     * @param user the target user
+     * @return
+     * @hide
+     */
+    public boolean addSharedAccount(final Account account, UserHandle user) {
+        try {
+            boolean val = mService.addSharedAccountAsUser(account, user.getIdentifier());
+            return val;
+        } catch (RemoteException re) {
+            // won't ever happen
+            throw new RuntimeException(re);
+        }
+    }
+
+    /**
+     * @hide
+     * Removes the shared account.
+     * @param account the account to remove
+     * @param user the user to remove the account from
+     * @return
+     */
+    public boolean removeSharedAccount(final Account account, UserHandle user) {
+        try {
+            boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier());
+            return val;
+        } catch (RemoteException re) {
+            // won't ever happen
+            throw new RuntimeException(re);
+        }
+    }
+
+    /**
+     * @hide
+     * @param user
+     * @return
+     */
+    public Account[] getSharedAccounts(UserHandle user) {
+        try {
+            return mService.getSharedAccountsAsUser(user.getIdentifier());
+        } catch (RemoteException re) {
+            // won't ever happen
+            throw new RuntimeException(re);
+        }
+    }
+
+    /**
      * Confirms that the user knows the password for an account to make extra
      * sure they are the owner of the account.  The user-entered password can
      * be supplied directly, otherwise the authenticator for this account type
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index 8860710..58612da 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -70,4 +70,17 @@
      * Gets whether or not the account is allowed to be removed.
      */
     void getAccountRemovalAllowed(in IAccountAuthenticatorResponse response, in Account account);
+
+    /**
+     * Returns a Bundle containing the required credentials to copy the account across users.
+     */
+    void getAccountCredentialsForCloning(in IAccountAuthenticatorResponse response,
+            in Account account);
+
+    /**
+     * Uses the Bundle containing credentials from another instance of the authenticator to create
+     * a copy of the account on this user.
+     */
+    void addAccountFromCredentials(in IAccountAuthenticatorResponse response, in Account account,
+            in Bundle accountCredentials);
 }
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index dbb4924..47b257d 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -58,4 +58,9 @@
         in Bundle options, boolean expectActivityLaunch, int userId);
     void getAuthTokenLabel(in IAccountManagerResponse response, String accountType,
         String authTokenType);
+
+    /* Shared accounts */
+    boolean addSharedAccountAsUser(in Account account, int userId);
+    Account[] getSharedAccountsAsUser(int userId);
+    boolean removeSharedAccountAsUser(in Account account, int userId);
 }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index f09c2fe..6d55dd5 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -50,6 +50,7 @@
 import android.net.Uri;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.Display;
 
@@ -1064,7 +1065,7 @@
     public int installExistingPackage(String packageName)
             throws NameNotFoundException {
         try {
-            int res = mPM.installExistingPackage(packageName);
+            int res = mPM.installExistingPackageAsUser(packageName, UserHandle.myUserId());
             if (res == INSTALL_FAILED_INVALID_URI) {
                 throw new NameNotFoundException("Package " + packageName + " doesn't exist");
             }
@@ -1126,7 +1127,7 @@
     @Override
     public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {
         try {
-            mPM.deletePackage(packageName, observer, flags);
+            mPM.deletePackageAsUser(packageName, observer, UserHandle.myUserId(), flags);
         } catch (RemoteException e) {
             // Should never happen!
         }
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index c5a382d..b6aeb84 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -1019,7 +1019,7 @@
     }
 
     /**
-     * Call {@link Activity#startActivity(Intent)} on the fragment's
+     * Call {@link Activity#startActivity(Intent)} from the fragment's
      * containing Activity.
      *
      * @param intent The intent to start.
@@ -1029,7 +1029,7 @@
     }
     
     /**
-     * Call {@link Activity#startActivity(Intent, Bundle)} on the fragment's
+     * Call {@link Activity#startActivity(Intent, Bundle)} from the fragment's
      * containing Activity.
      *
      * @param intent The intent to start.
@@ -1051,7 +1051,7 @@
     }
 
     /**
-     * Call {@link Activity#startActivityForResult(Intent, int)} on the fragment's
+     * Call {@link Activity#startActivityForResult(Intent, int)} from the fragment's
      * containing Activity.
      */
     public void startActivityForResult(Intent intent, int requestCode) {
@@ -1059,7 +1059,7 @@
     }
 
     /**
-     * Call {@link Activity#startActivityForResult(Intent, int, Bundle)} on the fragment's
+     * Call {@link Activity#startActivityForResult(Intent, int, Bundle)} from the fragment's
      * containing Activity.
      */
     public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index a368451..a32a201 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -194,20 +194,22 @@
     void setInstallerPackageName(in String targetPackage, in String installerPackageName);
 
     /**
-     * Delete a package.
+     * Delete a package for a specific user.
      *
      * @param packageName The fully qualified name of the package to delete.
      * @param observer a callback to use to notify when the package deletion in finished.
+     * @param userId the id of the user for whom to delete the package
      * @param flags - possible values: {@link #DONT_DELETE_DATA}
      */
-    void deletePackage(in String packageName, IPackageDeleteObserver observer, int flags);
+    void deletePackageAsUser(in String packageName, IPackageDeleteObserver observer,
+            int userId, int flags);
 
     String getInstallerPackageName(in String packageName);
 
     void addPackageToPreferred(String packageName);
-    
+
     void removePackageFromPreferred(String packageName);
-    
+
     List<PackageInfo> getPreferredPackages(int flags);
 
     void resetPreferredActivities(int userId);
@@ -381,7 +383,7 @@
             in VerificationParams verificationParams,
             in ContainerEncryptionParams encryptionParams);
 
-    int installExistingPackage(String packageName);
+    int installExistingPackageAsUser(String packageName, int userId);
 
     void verifyPendingInstall(int id, int verificationCode);
     void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay);
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 593f826..4c87830 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -97,6 +97,10 @@
         return (flags & FLAG_GUEST) == FLAG_GUEST;
     }
 
+    public boolean isRestricted() {
+        return (flags & FLAG_RESTRICTED) == FLAG_RESTRICTED;
+    }
+
     public UserInfo() {
     }
 
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 1bcf3e0..4379418 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -36,6 +36,7 @@
 
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Set;
@@ -656,19 +657,18 @@
         }
     }
 
-    private class SynthesisToFileSpeechDescriptorItem extends SynthesisSpeechItem {
-        private final FileDescriptor mFileDescriptor;
+    private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
+        private final FileOutputStream mFileOutputStream;
 
-        public SynthesisToFileSpeechDescriptorItem(Object callerIdentity, int callerUid,
-                int callerPid, Bundle params, String text, FileDescriptor fileDescriptor) {
+        public SynthesisToFileOutputStreamSpeechItem(Object callerIdentity, int callerUid,
+                int callerPid, Bundle params, String text, FileOutputStream fileOutputStream) {
             super(callerIdentity, callerUid, callerPid, params, text);
-            mFileDescriptor = fileDescriptor;
+            mFileOutputStream = fileOutputStream;
         }
 
         @Override
         protected AbstractSynthesisCallback createSynthesisCallback() {
-            FileOutputStream fileOutputStream = new FileOutputStream(mFileDescriptor);
-            return new FileSynthesisCallback(fileOutputStream.getChannel());
+            return new FileSynthesisCallback(mFileOutputStream.getChannel());
         }
 
         @Override
@@ -680,6 +680,11 @@
             } else {
                 dispatchOnError();
             }
+            try {
+              mFileOutputStream.close();
+            } catch(IOException e) {
+              Log.w(TAG, "Failed to close output file", e);
+            }
             return status;
         }
     }
@@ -805,9 +810,9 @@
                 return TextToSpeech.ERROR;
             }
 
-            SpeechItem item = new SynthesisToFileSpeechDescriptorItem(caller, Binder.getCallingUid(),
-                    Binder.getCallingPid(), params, text,
-                    fileDescriptor.getFileDescriptor());
+            SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller,
+                    Binder.getCallingUid(), Binder.getCallingPid(), params, text,
+                    new ParcelFileDescriptor.AutoCloseOutputStream(fileDescriptor));
             return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
         }
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a213b2f..df07dcd 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -159,17 +159,6 @@
     public static final int FX_SURFACE_NORMAL   = 0x00000000;
 
     /**
-     * Surface creation flag: Creates a Blur surface.
-     * Everything behind this surface is blurred by some amount.
-     * The quality and refresh speed of the blur effect is not settable or guaranteed.
-     * It is an error to lock a Blur surface, since it doesn't have a backing store.
-     *
-     * @deprecated
-     */
-    @Deprecated
-    public static final int FX_SURFACE_BLUR = 0x00010000;
-
-    /**
      * Surface creation flag: Creates a Dim surface.
      * Everything behind this surface is dimmed by the amount specified
      * in {@link #setAlpha}.  It is an error to lock a Dim surface, since it
@@ -179,11 +168,6 @@
     public static final int FX_SURFACE_DIM = 0x00020000;
 
     /**
-     *
-     */
-    public static final int FX_SURFACE_SCREENSHOT = 0x00030000;
-
-    /**
      * Mask used for FX values above.
      *
      */
diff --git a/core/java/android/webkit/HTML5Audio.java b/core/java/android/webkit/HTML5Audio.java
index fc5df2d..684ec07 100644
--- a/core/java/android/webkit/HTML5Audio.java
+++ b/core/java/android/webkit/HTML5Audio.java
@@ -54,14 +54,15 @@
     // The private status of the view that created this player
     private IsPrivateBrowsingEnabledGetter mIsPrivateBrowsingEnabledGetter;
 
-    private static int IDLE        =  0;
-    private static int INITIALIZED =  1;
-    private static int PREPARED    =  2;
-    private static int STARTED     =  4;
-    private static int COMPLETE    =  5;
-    private static int PAUSED      =  6;
-    private static int STOPPED     = -2;
-    private static int ERROR       = -1;
+    private static int IDLE                =  0;
+    private static int INITIALIZED         =  1;
+    private static int PREPARED            =  2;
+    private static int STARTED             =  4;
+    private static int COMPLETE            =  5;
+    private static int PAUSED              =  6;
+    private static int PAUSED_TRANSITORILY =  7;
+    private static int STOPPED             = -2;
+    private static int ERROR               = -1;
 
     private int mState = IDLE;
 
@@ -247,7 +248,7 @@
             // resume playback
             if (mMediaPlayer == null) {
                 resetMediaPlayer();
-            } else if (mState != ERROR && !mMediaPlayer.isPlaying()) {
+            } else if (mState == PAUSED_TRANSITORILY && !mMediaPlayer.isPlaying()) {
                 mMediaPlayer.start();
                 mState = STARTED;
             }
@@ -265,7 +266,9 @@
         case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
             // Lost focus for a short time, but we have to stop
             // playback.
-            if (mState != ERROR && mMediaPlayer.isPlaying()) pause();
+            if (mState != ERROR && mMediaPlayer.isPlaying()) {
+                pause(PAUSED_TRANSITORILY);
+            }
             break;
         }
     }
@@ -298,12 +301,16 @@
     }
 
     private void pause() {
+        pause(PAUSED);
+    }
+
+    private void pause(int state) {
         if (mState == STARTED) {
             if (mTimer != null) {
                 mTimer.purge();
             }
             mMediaPlayer.pause();
-            mState = PAUSED;
+            mState = state;
         }
     }
 
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 6fefcca..67041ac 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -8368,8 +8368,10 @@
             mListBoxDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                 @Override
                 public void onCancel(DialogInterface dialog) {
+                 if (mWebViewCore != null) {
                     mWebViewCore.sendMessage(
                                 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
+                    }
                     mListBoxDialog = null;
                 }
             });
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index a379157..f26527f 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -200,9 +200,7 @@
             if (view != null) {
                 // Put in recycler for re-measuring and/or layout
                 mRecycler.put(selectedPosition, view);
-            }
 
-            if (view != null) {
                 if (view.getLayoutParams() == null) {
                     mBlockLayoutRequests = true;
                     view.setLayoutParams(generateDefaultLayoutParams());
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index deec41c..5d6ec1e 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -37,7 +37,6 @@
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.RemoteViews.RemoteView;
@@ -221,13 +220,13 @@
     // Some apps came to rely on them. :(
     private boolean mAllowBrokenMeasureSpecs = false;
 
-    private int mDisplayWidth;
+    // A default width used for RTL measure pass
+    private static int DEFAULT_WIDTH = Integer.MAX_VALUE / 2;
 
     public RelativeLayout(Context context) {
         super(context);
         mAllowBrokenMeasureSpecs = context.getApplicationInfo().targetSdkVersion <=
                 Build.VERSION_CODES.JELLY_BEAN_MR1;
-        getDisplayWidth();
     }
 
     public RelativeLayout(Context context, AttributeSet attrs) {
@@ -235,7 +234,6 @@
         initFromAttributes(context, attrs);
         mAllowBrokenMeasureSpecs = context.getApplicationInfo().targetSdkVersion <=
                 Build.VERSION_CODES.JELLY_BEAN_MR1;
-        getDisplayWidth();
     }
 
     public RelativeLayout(Context context, AttributeSet attrs, int defStyle) {
@@ -243,7 +241,6 @@
         initFromAttributes(context, attrs);
         mAllowBrokenMeasureSpecs = context.getApplicationInfo().targetSdkVersion <=
                 Build.VERSION_CODES.JELLY_BEAN_MR1;
-        getDisplayWidth();
     }
 
     private void initFromAttributes(Context context, AttributeSet attrs) {
@@ -253,11 +250,6 @@
         a.recycle();
     }
 
-    private void getDisplayWidth() {
-        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
-        mDisplayWidth = wm.getDefaultDisplay().getWidth();
-    }
-
     @Override
     public boolean shouldDelayChildPressedState() {
         return false;
@@ -451,12 +443,12 @@
 
         // We need to know our size for doing the correct computation of children positioning in RTL
         // mode but there is no practical way to get it instead of running the code below.
-        // So, instead of running the code twice, we just set the width to the "display width"
+        // So, instead of running the code twice, we just set the width to a "default display width"
         // before the computation and then, as a last pass, we will update their real position with
-        // an offset equals to "displayWidth - width".
+        // an offset equals to "DEFAULT_WIDTH - width".
         final int layoutDirection = getLayoutDirection();
         if (isLayoutRtl() && myWidth == -1) {
-            myWidth = mDisplayWidth;
+            myWidth = DEFAULT_WIDTH;
         }
 
         View[] views = mSortedHorizontalChildren;
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index fa64fd3..b6895a5 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -196,7 +196,7 @@
             break;
         }
         }
-        
+
         mGravity = a.getInt(com.android.internal.R.styleable.Spinner_gravity, Gravity.CENTER);
 
         mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
@@ -608,7 +608,7 @@
             handled = true;
 
             if (!mPopup.isShowing()) {
-                mPopup.show();
+                mPopup.show(getTextDirection(), getTextAlignment());
             }
         }
 
@@ -719,7 +719,7 @@
                     @Override
                     public void onGlobalLayout() {
                         if (!mPopup.isShowing()) {
-                            mPopup.show();
+                            mPopup.show(getTextDirection(), getTextAlignment());
                         }
                         final ViewTreeObserver vto = getViewTreeObserver();
                         if (vto != null) {
@@ -799,8 +799,7 @@
         }
 
         public View getDropDownView(int position, View convertView, ViewGroup parent) {
-            return mAdapter == null ? null :
-                    mAdapter.getDropDownView(position, convertView, parent);
+            return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
         }
 
         public boolean hasStableIds() {
@@ -868,8 +867,8 @@
         /**
          * Show the popup
          */
-        public void show();
-        
+        public void show(int textDirection, int textAlignment);
+
         /**
          * Dismiss the popup
          */
@@ -922,13 +921,17 @@
             return mPrompt;
         }
 
-        public void show() {
+        public void show(int textDirection, int textAlignment) {
             AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
             if (mPrompt != null) {
                 builder.setTitle(mPrompt);
             }
             mPopup = builder.setSingleChoiceItems(mListAdapter,
-                    getSelectedItemPosition(), this).show();
+                    getSelectedItemPosition(), this).create();
+            final ListView listView = mPopup.getListView();
+            listView.setTextDirection(textDirection);
+            listView.setTextAlignment(textAlignment);
+            mPopup.show();
         }
         
         public void onClick(DialogInterface dialog, int which) {
@@ -1044,15 +1047,17 @@
             setHorizontalOffset(hOffset);
         }
 
-        @Override
-        public void show() {
+        public void show(int textDirection, int textAlignment) {
             final boolean wasShowing = isShowing();
 
             computeContentWidth();
 
             setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
             super.show();
-            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+            final ListView listView = getListView();
+            listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+            listView.setTextDirection(textDirection);
+            listView.setTextAlignment(textAlignment);
             setSelection(Spinner.this.getSelectedItemPosition());
 
             if (wasShowing) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a1ced6e..adacef2 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -432,10 +432,6 @@
 
     private int mMarqueeRepeatLimit = 3;
 
-    // The alignment to pass to Layout, or null if not resolved.
-    private Layout.Alignment mLayoutAlignment;
-    private int mResolvedTextAlignment;
-
     private int mLastLayoutDirection = -1;
 
     /**
@@ -2859,7 +2855,6 @@
 
         if (gravity != mGravity) {
             invalidate();
-            mLayoutAlignment = null;
         }
 
         mGravity = gravity;
@@ -5805,68 +5800,56 @@
                       physicalWidth, false);
     }
 
-    @Override
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        if (mLayoutAlignment != null) {
-            if (mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START ||
-                    mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) {
-                mLayoutAlignment = null;
-            }
-        }
-    }
-
     private Layout.Alignment getLayoutAlignment() {
-        if (mLayoutAlignment == null) {
-            mResolvedTextAlignment = getTextAlignment();
-            switch (mResolvedTextAlignment) {
-                case TEXT_ALIGNMENT_GRAVITY:
-                    switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
-                        case Gravity.START:
-                            mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
-                            break;
-                        case Gravity.END:
-                            mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE;
-                            break;
-                        case Gravity.LEFT:
-                            mLayoutAlignment = Layout.Alignment.ALIGN_LEFT;
-                            break;
-                        case Gravity.RIGHT:
-                            mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT;
-                            break;
-                        case Gravity.CENTER_HORIZONTAL:
-                            mLayoutAlignment = Layout.Alignment.ALIGN_CENTER;
-                            break;
-                        default:
-                            mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
-                            break;
-                    }
-                    break;
-                case TEXT_ALIGNMENT_TEXT_START:
-                    mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
-                    break;
-                case TEXT_ALIGNMENT_TEXT_END:
-                    mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE;
-                    break;
-                case TEXT_ALIGNMENT_CENTER:
-                    mLayoutAlignment = Layout.Alignment.ALIGN_CENTER;
-                    break;
-                case TEXT_ALIGNMENT_VIEW_START:
-                    mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
-                            Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
-                    break;
-                case TEXT_ALIGNMENT_VIEW_END:
-                    mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
-                            Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
-                    break;
-                case TEXT_ALIGNMENT_INHERIT:
-                    // This should never happen as we have already resolved the text alignment
-                    // but better safe than sorry so we just fall through
-                default:
-                    mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
-                    break;
-            }
+        Layout.Alignment alignment;
+        switch (getTextAlignment()) {
+            case TEXT_ALIGNMENT_GRAVITY:
+                switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.START:
+                        alignment = Layout.Alignment.ALIGN_NORMAL;
+                        break;
+                    case Gravity.END:
+                        alignment = Layout.Alignment.ALIGN_OPPOSITE;
+                        break;
+                    case Gravity.LEFT:
+                        alignment = Layout.Alignment.ALIGN_LEFT;
+                        break;
+                    case Gravity.RIGHT:
+                        alignment = Layout.Alignment.ALIGN_RIGHT;
+                        break;
+                    case Gravity.CENTER_HORIZONTAL:
+                        alignment = Layout.Alignment.ALIGN_CENTER;
+                        break;
+                    default:
+                        alignment = Layout.Alignment.ALIGN_NORMAL;
+                        break;
+                }
+                break;
+            case TEXT_ALIGNMENT_TEXT_START:
+                alignment = Layout.Alignment.ALIGN_NORMAL;
+                break;
+            case TEXT_ALIGNMENT_TEXT_END:
+                alignment = Layout.Alignment.ALIGN_OPPOSITE;
+                break;
+            case TEXT_ALIGNMENT_CENTER:
+                alignment = Layout.Alignment.ALIGN_CENTER;
+                break;
+            case TEXT_ALIGNMENT_VIEW_START:
+                alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+                        Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
+                break;
+            case TEXT_ALIGNMENT_VIEW_END:
+                alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+                        Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
+                break;
+            case TEXT_ALIGNMENT_INHERIT:
+                // This should never happen as we have already resolved the text alignment
+                // but better safe than sorry so we just fall through
+            default:
+                alignment = Layout.Alignment.ALIGN_NORMAL;
+                break;
         }
-        return mLayoutAlignment;
+        return alignment;
     }
 
     /**
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index 699fa6e..f359146 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -73,10 +73,6 @@
         ta.recycle();
     }
 
-    public void setOverlayMode(boolean mode) {
-        mOverlayMode = mode;
-    }
-
     public void setActionBar(ActionBarImpl impl, boolean overlayMode) {
         mActionBar = impl;
         mOverlayMode = overlayMode;
@@ -177,7 +173,9 @@
 
         // The top and bottom action bars are always within the content area.
         boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true);
-        changed |= applyInsets(mActionBarBottom, insets, true, false, true, true);
+        if (mActionBarBottom != null) {
+            changed |= applyInsets(mActionBarBottom, insets, true, false, true, true);
+        }
 
         mBaseInnerInsets.set(insets);
         computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
@@ -208,7 +206,19 @@
     }
 
     @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams;
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        pullChildren();
+
         int maxHeight = 0;
         int maxWidth = 0;
         int childState = 0;
@@ -224,13 +234,16 @@
                 mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
         childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState());
 
-        measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
-        lp = (LayoutParams) mActionBarBottom.getLayoutParams();
-        maxWidth = Math.max(maxWidth,
-                mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
-        maxHeight = Math.max(maxHeight,
-                mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
-        childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState());
+        // xlarge screen layout doesn't have bottom action bar.
+        if (mActionBarBottom != null) {
+            measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
+            lp = (LayoutParams) mActionBarBottom.getLayoutParams();
+            maxWidth = Math.max(maxWidth,
+                    mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+            maxHeight = Math.max(maxHeight,
+                    mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+            childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState());
+        }
 
         final int vis = getWindowSystemUiVisibility();
         final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
@@ -254,10 +267,12 @@
 
         if (mActionView.isSplitActionBar()) {
             // If action bar is split, adjust bottom insets for it.
-            if (stable) {
-                bottomInset = mActionBarHeight;
-            } else {
-                bottomInset = mActionBarBottom.getMeasuredHeight();
+            if (mActionBarBottom != null) {
+                if (stable) {
+                    bottomInset = mActionBarHeight;
+                } else {
+                    bottomInset = mActionBarBottom.getMeasuredHeight();
+                }
             }
         }
 
@@ -336,6 +351,11 @@
         }
     }
 
+    @Override
+    public boolean shouldDelayChildPressedState() {
+        return false;
+    }
+
     void pullChildren() {
         if (mContent == null) {
             mContent = findViewById(com.android.internal.R.id.content);
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 2544c54..1e27be8 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -11,10 +11,6 @@
 	LOCAL_CFLAGS += -DPACKED=""
 endif
 
-ifneq ($(USE_CUSTOM_RUNTIME_HEAP_MAX),)
-  LOCAL_CFLAGS += -DCUSTOM_RUNTIME_HEAP_MAX=$(USE_CUSTOM_RUNTIME_HEAP_MAX)
-endif
-
 ifeq ($(USE_OPENGL_RENDERER),true)
 	LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER
 endif
diff --git a/core/jni/android_opengl_EGL14.cpp b/core/jni/android_opengl_EGL14.cpp
index 664af07..ac4bc1d 100644
--- a/core/jni/android_opengl_EGL14.cpp
+++ b/core/jni/android_opengl_EGL14.cpp
@@ -1202,6 +1202,22 @@
     return (EGLBoolean) 0;
 }
 
+/* EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time ) */
+static jboolean
+android_eglPresentationTimeANDROID
+  (JNIEnv *_env, jobject _this, jobject dpy, jobject sur, jlong time) {
+    EGLBoolean _returnValue = (EGLBoolean) 0;
+    EGLDisplay dpy_native = (EGLDisplay) fromEGLHandle(_env, egldisplayGetHandleID, dpy);
+    EGLSurface sur_native = (EGLSurface) fromEGLHandle(_env, eglsurfaceGetHandleID, sur);
+
+    _returnValue = eglPresentationTimeANDROID(
+        (EGLDisplay)dpy_native,
+        (EGLSurface)sur_native,
+        (EGLnsecsANDROID)time
+    );
+    return _returnValue;
+}
+
 static const char *classPathName = "android/opengl/EGL14";
 
 static JNINativeMethod methods[] = {
@@ -1240,6 +1256,7 @@
 {"eglWaitNative", "(I)Z", (void *) android_eglWaitNative },
 {"eglSwapBuffers", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;)Z", (void *) android_eglSwapBuffers },
 {"eglCopyBuffers", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;I)Z", (void *) android_eglCopyBuffers },
+{"eglPresentationTimeANDROID", "(Landroid/opengl/EGLDisplay;Landroid/opengl/EGLSurface;J)Z", (void *) android_eglPresentationTimeANDROID },
 };
 
 int register_android_opengl_jni_EGL14(JNIEnv *_env)
diff --git a/core/res/res/layout-xlarge/screen_action_bar.xml b/core/res/res/layout-xlarge/screen_action_bar.xml
index 0b6122d..4f286780 100644
--- a/core/res/res/layout-xlarge/screen_action_bar.xml
+++ b/core/res/res/layout-xlarge/screen_action_bar.xml
@@ -15,33 +15,43 @@
 -->
 
 <!--
-This is an optimized layout for a screen with the Action Bar enabled.
+This is an optimized layout for a screen with
+the Action Bar enabled overlaying application content.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:fitsSystemWindows="true"
+<com.android.internal.widget.ActionBarOverlayLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/action_bar_overlay_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:splitMotionEvents="false">
-    <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="?android:attr/actionBarStyle">
-        <com.android.internal.widget.ActionBarView
-            android:id="@+id/action_bar"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            style="?android:attr/actionBarStyle" />
-        <com.android.internal.widget.ActionBarContextView
-            android:id="@+id/action_context_bar"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone"
-            style="?android:attr/actionModeStyle" />
-    </com.android.internal.widget.ActionBarContainer>
     <FrameLayout android:id="@android:id/content"
         android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1"
-        android:foregroundGravity="fill_horizontal|top"
-        android:foreground="?android:attr/windowContentOverlay" />
-</LinearLayout>
+        android:layout_height="match_parent" />
+    <LinearLayout android:id="@+id/top_action_bar"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content">
+        <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            style="?android:attr/actionBarStyle"
+            android:gravity="top">
+            <com.android.internal.widget.ActionBarView
+                android:id="@+id/action_bar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                style="?android:attr/actionBarStyle" />
+            <com.android.internal.widget.ActionBarContextView
+                android:id="@+id/action_context_bar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:visibility="gone"
+                style="?android:attr/actionModeStyle" />
+        </com.android.internal.widget.ActionBarContainer>
+        <ImageView android:src="?android:attr/windowContentOverlay"
+                   android:scaleType="fitXY"
+                   android:layout_width="match_parent"
+                   android:layout_height="wrap_content" />
+    </LinearLayout>
+</com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/layout-xlarge/screen_action_bar_overlay.xml b/core/res/res/layout-xlarge/screen_action_bar_overlay.xml
deleted file mode 100644
index a95635e..0000000
--- a/core/res/res/layout-xlarge/screen_action_bar_overlay.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!--
-This is an optimized layout for a screen with
-the Action Bar enabled overlaying application content.
--->
-
-<com.android.internal.widget.ActionBarOverlayLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/action_bar_overlay_layout"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:splitMotionEvents="false">
-    <FrameLayout android:id="@android:id/content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-    <LinearLayout android:id="@+id/top_action_bar"
-                  android:layout_width="match_parent"
-                  android:layout_height="wrap_content"
-                  android:layout_gravity="top">
-        <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            style="?android:attr/actionBarStyle"
-            android:gravity="top">
-            <com.android.internal.widget.ActionBarView
-                android:id="@+id/action_bar"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                style="?android:attr/actionBarStyle" />
-            <com.android.internal.widget.ActionBarContextView
-                android:id="@+id/action_context_bar"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:visibility="gone"
-                style="?android:attr/actionModeStyle" />
-        </com.android.internal.widget.ActionBarContainer>
-        <ImageView android:src="?android:attr/windowContentOverlay"
-                   android:scaleType="fitXY"
-                   android:layout_width="match_parent"
-                   android:layout_height="wrap_content" />
-    </LinearLayout>
-</com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/layout/simple_spinner_dropdown_item.xml b/core/res/res/layout/simple_spinner_dropdown_item.xml
index cb999b6..e2bd474 100644
--- a/core/res/res/layout/simple_spinner_dropdown_item.xml
+++ b/core/res/res/layout/simple_spinner_dropdown_item.xml
@@ -23,4 +23,5 @@
     android:singleLine="true"
     android:layout_width="match_parent"
     android:layout_height="?android:attr/dropdownListPreferredItemHeight"
-    android:ellipsize="marquee" />
+    android:ellipsize="marquee"
+    android:textAlignment="inherit"/>
diff --git a/core/res/res/layout/simple_spinner_item.xml b/core/res/res/layout/simple_spinner_item.xml
index 61dc025..5c7685e 100644
--- a/core/res/res/layout/simple_spinner_item.xml
+++ b/core/res/res/layout/simple_spinner_item.xml
@@ -23,4 +23,5 @@
     android:singleLine="true"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:ellipsize="marquee" />
+    android:ellipsize="marquee"
+    android:textAlignment="inherit"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c87cb27..140ff70 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1363,7 +1363,6 @@
   <java-symbol type="layout" name="keyguard_account_view" />
   <java-symbol type="layout" name="recent_apps_dialog" />
   <java-symbol type="layout" name="screen_action_bar" />
-  <java-symbol type="layout" name="screen_action_bar_overlay" />
   <java-symbol type="layout" name="screen_custom_title" />
   <java-symbol type="layout" name="screen_progress" />
   <java-symbol type="layout" name="screen_simple" />
diff --git a/docs/html/about/dashboards/index.jd b/docs/html/about/dashboards/index.jd
index 14f3dd1..93876f8 100644
--- a/docs/html/about/dashboards/index.jd
+++ b/docs/html/about/dashboards/index.jd
@@ -31,32 +31,32 @@
   <th>Distribution</th>
 </tr>
 <tr><td><a href="/about/versions/android-1.6.html">1.6</a></td><td>Donut</td>    <td>4</td><td>0.2%</td></tr>
-<tr><td><a href="/about/versions/android-2.1.html">2.1</a></td><td>Eclair</td>   <td>7</td><td>2.2%</td></tr>
-<tr><td><a href="/about/versions/android-2.2.html">2.2</a></td><td>Froyo</td>    <td>8</td><td>8.1%</td></tr>
+<tr><td><a href="/about/versions/android-2.1.html">2.1</a></td><td>Eclair</td>   <td>7</td><td>1.9%</td></tr>
+<tr><td><a href="/about/versions/android-2.2.html">2.2</a></td><td>Froyo</td>    <td>8</td><td>7.6%</td></tr>
 <tr><td><a href="/about/versions/android-2.3.html">2.3 - 2.3.2</a>
                                    </td><td rowspan="2">Gingerbread</td>    <td>9</td><td>0.2%</td></tr>
 <tr><td><a href="/about/versions/android-2.3.3.html">2.3.3 - 2.3.7
-        </a></td><!-- Gingerbread -->                                       <td>10</td><td>45.4%</td></tr>
+        </a></td><!-- Gingerbread -->                                       <td>10</td><td>44%</td></tr>
 <tr><td><a href="/about/versions/android-3.1.html">3.1</a></td>
                                                    <td rowspan="2">Honeycomb</td>      <td>12</td><td>0.3%</td></tr>
-<tr><td><a href="/about/versions/android-3.2.html">3.2</a></td>      <!-- Honeycomb --><td>13</td><td>1.0%</td></tr>
+<tr><td><a href="/about/versions/android-3.2.html">3.2</a></td>      <!-- Honeycomb --><td>13</td><td>0.9%</td></tr>
 <tr><td><a href="/about/versions/android-4.0.3.html">4.0.3 - 4.0.4</a></td>
-                                                            <td>Ice Cream Sandwich</td><td>15</td><td>29.0%</td></tr> 
+                                                            <td>Ice Cream Sandwich</td><td>15</td><td>28.6%</td></tr> 
 <tr><td><a href="/about/versions/android-4.1.html">4.1</a></td>
-                                                   <td rowspan="2">Jelly Bean</td><td>16</td><td>12.2%</td></tr>
-<tr><td><a href="/about/versions/android-4.2.html">4.2</a></td><!--Jelly Bean-->  <td>17</td><td>1.4%</td></tr>  
+                                                   <td rowspan="2">Jelly Bean</td><td>16</td><td>14.9%</td></tr>
+<tr><td><a href="/about/versions/android-4.2.html">4.2</a></td><!--Jelly Bean-->  <td>17</td><td>1.6%</td></tr>  
 </table>
 
 </div>
 
 <div class="col-8" style="margin-right:0">
 <img style="margin-left:30px" alt=""
-src="//chart.apis.google.com/chart?&cht=p&chs=460x245&chf=bg,s,00000000&chd=t:2.4,8.1,45.4,0.3,29,13.6&chl=Eclair%20%26%20older|Froyo|Gingerbread|Honeycomb|Ice%20Cream%20Sandwich|Jelly%20Bean&chco=c4df9b,6fad0c"
+src="//chart.apis.google.com/chart?&cht=p&chs=460x245&chf=bg,s,00000000&chd=t:2.0,7.6,44.2,1.2,28.6,16.5&chl=Eclair%20%26%20older|Froyo|Gingerbread|Honeycomb|Ice%20Cream%20Sandwich|Jelly%20Bean&chco=c4df9b,6fad0c"
 />
 
 </div><!-- end dashboard-panel -->
 
-<p style="clear:both"><em>Data collected during a 14-day period ending on February 4, 2013</em></p>
+<p style="clear:both"><em>Data collected during a 14-day period ending on March 4, 2013</em></p>
 <!--
 <p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p>
 -->
@@ -81,9 +81,9 @@
 Google Play within a 14-day period ending on the date indicated on the x-axis.</p>
 
 <img alt="" height="250" width="660"
-src="//chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chf=bg,s,00000000&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C11/15%7C12/01%7C12/15%7C01/01%7C01/15%7C02/01%7C1%3A%7C2012%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2013%7C%7C2013%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.2,99.2,99.3,99.4,99.5,99.5,99.5,99.6,100.0,100.0,100.0,100.0,100.0|95.0,95.2,95.6,95.8,96.1,96.3,96.4,96.7,96.9,97.2,97.4,97.4,97.6|79.5,80.4,81.4,82.3,83.2,83.8,84.7,85.6,86.4,87.0,88.2,88.8,89.4|18.9,21.2,23.7,25.5,27.4,28.7,31.1,33.0,35.4,36.8,40.3,42.0,43.6|16.6,19.0,21.5,23.5,25.5,26.8,29.4,31.4,33.8,35.2,38.8,40.7,42.3|0.8,0.9,1.1,1.4,1.8,2.1,3.2,4.8,6.5,7.5,9.9,11.7,13.3&chm=b,c3df9b,0,1,0|tFroyo,689326,1,0,15,,t::-5|b,b4db77,1,2,0|tGingerbread,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|b,96dd28,3,4,0|tIce%20Cream%20Sandwich,293f07,4,0,15,,t::-5|b,83c916,4,5,0|tJelly%20Bean,131d02,5,11,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Eclair|Froyo|Gingerbread|Honeycomb|Ice%20Cream%20Sandwich|Jelly%20Bean&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08"
+src="//chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chf=bg,s,00000000&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C11/15%7C12/01%7C12/15%7C01/01%7C01/15%7C02/01%7C02/15%7C03/01%7C1%3A%7C2012%7C%7C%7C%7C%7C%7C%7C%7C2013%7C%7C%7C%7C2013%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.3,99.4,99.5,99.5,99.5,99.6,100.0,100.0,100.0,100.0,100.0,100.0,100.0|95.6,95.8,96.1,96.3,96.4,96.7,96.9,97.2,97.4,97.4,97.6,97.7,97.9|81.4,82.3,83.2,83.8,84.7,85.6,86.4,87.0,88.2,88.8,89.4,89.9,90.3|23.7,25.5,27.4,28.7,31.1,33.0,35.4,36.8,40.3,42.0,43.6,45.1,46.0|21.5,23.5,25.5,26.8,29.4,31.4,33.8,35.2,38.8,40.7,42.3,43.9,44.8|1.1,1.4,1.8,2.1,3.2,4.8,6.5,7.5,9.9,11.7,13.3,14.8,16.1&chm=b,c3df9b,0,1,0|tFroyo,689326,1,0,15,,t::-5|b,b4db77,1,2,0|tGingerbread,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|b,96dd28,3,4,0|tIce%20Cream%20Sandwich,293f07,4,0,15,,t::-5|b,83c916,4,5,0|tJelly%20Bean,131d02,5,9,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Eclair|Froyo|Gingerbread|Honeycomb|Ice%20Cream%20Sandwich|Jelly%20Bean&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08"
 />
-<p><em>Last historical dataset collected during a 14-day period ending on February 1, 2013</em></p>
+<p><em>Last historical dataset collected during a 14-day period ending on March 1, 2013</em></p>
 
 
 
diff --git a/docs/html/google/play/billing/billing_integrate.jd b/docs/html/google/play/billing/billing_integrate.jd
index 297e906..3365cfc 100644
--- a/docs/html/google/play/billing/billing_integrate.jd
+++ b/docs/html/google/play/billing/billing_integrate.jd
@@ -40,31 +40,63 @@
 
 <p class="note"><strong>Note:</strong> To see a complete implementation and learn how to test your application, see the <a href="{@docRoot}training/in-app-billing/index.html">Selling In-app Products</a> training class. The training class provides a complete sample In-app Billing application, including convenience classes to handle key tasks related to setting up your connection, sending billing requests and processing responses from Google Play, and managing background threading so that you can make In-app Billing calls from your main activity.</p>
 
-<p>Before you start, be sure that you read the <a href="{@docRoot}google/play/billing/billing_overview.html">In-app Billing Overview</a> to familiarize yourself with concepts that will make it easier for you to implement In-app Billing.</p>
+<p>Before you start, be sure that you read the <a href="{@docRoot}google/play/billing/billing_overview.html">In-app Billing Overview</a> to familiarize yourself with 
+concepts that will make it easier for you to implement In-app Billing.</p>
 
-<p>To implement In-app Billing in your application, you need to do the following:</p>
+<p>To implement In-app Billing in your application, you need to do the 
+following:</p>
 <ol>
   <li>Add the In-app Billing library to your project.</li>
   <li>Update your {@code AndroidManifest.xml} file.</li>
-  <li>Create a {@code ServiceConnection} and bind it to {@code IInAppBillingService}.</li>
-  <li>Send In-app Billing requests from your application to {@code IInAppBillingService}.</li>
+  <li>Create a {@code ServiceConnection} and bind it to 
+{@code IInAppBillingService}.</li>
+  <li>Send In-app Billing requests from your application to 
+{@code IInAppBillingService}.</li>
   <li>Handle In-app Billing responses from Google Play.</li>
 </ol>
 
 <h2 id="billing-add-aidl">Adding the AIDL file to your project</h2>
 
-<p>The {@code TriviaDriva} sample application contains an Android Interface Definition Language (AIDL) file which defines the interface to Google Play's In-app Billing service. When you add this file to your project, the Android build environment creates an interface file (<code>IIAppBillingService.java</code>). You can then use this interface to make billing requests by invoking IPC method calls.</p>
+<p>{@code IInAppBillingService.aidl} is an Android Interface Definition 
+Language (AIDL) file that defines the interface to the In-app Billing Version 
+3 service. You will use this interface to make billing requests by invoking IPC 
+method calls.</p>
+<p>To get the AIDL file:</p>
+<ol>
+<li>Open the <a href="{@docRoot}tools/help/sdk-manager.html">Android SDK Manager</a>.</li>
+<li>In the SDK Manager, expand the {@code Extras} section.</li>
+<li>Select <strong>Google Play Billing Library</strong>.</li>
+<li>Click <strong>Install packages</strong> to complete the download.</li>
+</ol>
+<p>The {@code IInAppBillingService.aidl} file will be installed to {@code &lt;sdk&gt;/extras/google/play_billing/}.</p>
 
-<p>To add the In-app Billing Version 3 library to your project:</p>
+<p>To add the AIDL to your project:</p>
 <ol>
 <li>Copy the {@code IInAppBillingService.aidl} file to your Android project.
   <ul>
-  <li>If you are using Eclipse: Import the {@code IInAppBillingService.aidl} file into your {@code /src} directory. Eclipse automatically generates the interface file when you build your project.</li>
-  <li>If you are developing in a non-Eclipse environment: Create the following directory {@code /src/com/android/vending/billing} and copy the {@code IInAppBillingService.aidl} file into this directory. Put the AIDL file into your project and use the Ant tool to build your project so that the
+  <li>If you are using Eclipse: 
+     <ol type="a">
+        <li>If you are starting from an existing Android project, open the project 
+in Eclipse. If you are creating a new Android project from scratch, click 
+<strong>File</strong> &gt; <strong>New</strong> &gt; <strong>Android Application 
+Project</strong>, then follow the instructions in the <strong>New Android 
+Application</strong> wizard to create a new project in your workspace.</li>
+	<li>In the {@code /src} directory, click <strong>File</strong> &gt; 
+<strong>New</strong> &gt; <strong>Package</strong>, then create a package named {@code com.android.vending.billing}.</li>
+	<li>Copy the {@code IInAppBillingService.aidl} file from {@code &lt;sdk&gt;/extras/google/play_billing/} and paste it into the {@code src/com.android.vending.billing/} 
+folder in your workspace.</li>
+     </ol>
+  </li>
+  <li>If you are developing in a non-Eclipse environment: Create the following 
+directory {@code /src/com/android/vending/billing} and copy the 
+{@code IInAppBillingService.aidl} file into this directory. Put the AIDL file 
+into your project and use the Ant tool to build your project so that the
 <code>IInAppBillingService.java</code> file gets generated.</li>
   </ul>
 </li>
-<li>Build your application. You should see a generated file named {@code IInAppBillingService.java} in the {@code /gen} directory of your project.</li>
+<li>Build your application. You should see a generated file named 
+{@code IInAppBillingService.java} in the {@code /gen} directory of your 
+project.</li>
 </ol>
 
 
diff --git a/docs/html/google/play/billing/billing_subscriptions.jd b/docs/html/google/play/billing/billing_subscriptions.jd
index c2bbb49..2840dbc 100644
--- a/docs/html/google/play/billing/billing_subscriptions.jd
+++ b/docs/html/google/play/billing/billing_subscriptions.jd
@@ -3,26 +3,7 @@
 parent.link=index.html
 @jd:body
 
-<!--notice block -->
-    <div style="background-color:#fffbd9;width:100%;margin-bottom:1em;padding:8px 8px 1px;">
-      <p><em>15 February 2013</em></p>
-      <p>In-app Billing V3 now supports subscriptions and you can get
-        started developing today. A small app update is currently being
-        rolled out to Android devices. This process is automatic and
-        most devices will get the update in the next few days. However,
-        if you wish to get the update today to start developing right
-        away, simply reboot your device. </p>
-
-      <p>However, we recommend that you <em>do not publish</em> an app with 
-        V3 subscriptions until all Android devices have received the update. We'll
-        notify you here that all devices have received the update and its safe
-        to publish your apps that use V3 subscriptions. </p>
-    </div>
-
-<!-- Use non-standard wrapper to support notice block. Restore standard 
-     wrapper when notice is removed. -->
-<!--<div id="qv-wrapper"> -->
-<div id="qv-wrapper" style="margin-top:.25em;">
+<div id="qv-wrapper">
 <div id="qv">
   <h2>Quickview</h2>
   <ul>
diff --git a/docs/html/google/play/filters.jd b/docs/html/google/play/filters.jd
index 6ab223c..1ec68c6 100644
--- a/docs/html/google/play/filters.jd
+++ b/docs/html/google/play/filters.jd
@@ -386,13 +386,10 @@
 must have a SIM card and be running Android 1.1 or later, and it must be in a
 country (as determined by SIM carrier) in which paid apps are available.</p></td>
 </tr> <tr>
-  <td valign="top">Country / Carrier Targeting</td> <td valign="top"> <p>When you upload your app to
-    Google Play, you can select specific countries to target. The app will only
-    be visible to the countries (carriers) that you select, as follows:</p>
-    <ul><li><p>A device's country is determined based on the carrier, if a carrier is
-      available. If no carrier can be determined, Google Play tries to
-      determine the country based on IP.</p></li> <li><p>Carrier is determined based on
-      the device's SIM (for GSM devices), not the current roaming carrier.</p></li></ul>
+  <td valign="top">Country Targeting</td> <td valign="top"> <p>When you upload your app to
+    Google Play, you can select the countries in which to distribute your app
+    under <strong>Pricing and Distribution</strong>. The app will then
+    be available to users in only the countries you select.</p>
 </td> </tr> <tr>
   <td valign="top" style="white-space:nowrap;">CPU Architecture (ABI)</td>
   <td valign="top"><p>An application that includes native
diff --git a/docs/html/guide/topics/providers/content-provider-basics.jd b/docs/html/guide/topics/providers/content-provider-basics.jd
index 527e713..199a671b 100644
--- a/docs/html/guide/topics/providers/content-provider-basics.jd
+++ b/docs/html/guide/topics/providers/content-provider-basics.jd
@@ -143,7 +143,7 @@
 <p>
     A content provider presents data to external applications as one or more tables that are
     similar to the tables found in a relational database. A row represents an instance of some type
-    of data the provider collects, and each row in the column represents an individual piece of
+    of data the provider collects, and each column in the row represents an individual piece of
     data collected for an instance.
 </p>
 <p>
diff --git a/docs/html/training/in-app-billing/preparing-iab-app.jd b/docs/html/training/in-app-billing/preparing-iab-app.jd
index ab33ccc..de2dac5 100644
--- a/docs/html/training/in-app-billing/preparing-iab-app.jd
+++ b/docs/html/training/in-app-billing/preparing-iab-app.jd
@@ -33,15 +33,15 @@
 <p>Before you can start using the In-app Billing service, you'll need to add the library that contains the In-app Billing Version 3 API to your Android project. You also need to setting the permissions for your application to communicate with Google Play. In addition, you'll need to establish a connection between your application and  Google Play. You should also verify that the In-app Billing API version that you are using in your application is supported by Google Play.</p>
 
 <h2 id="GetSample">Download the Sample Application</h2>
-<p>In this training class, you will use a reference implementation for the In-app Billing Version 3 API called the {@code TrivialDrive} sample application. The sample includes convenience classes to quickly set up the In-app Billing service, marshal and unmarshal data types, and handle In-app Billing requests from the main thread of your application.  </p>
+<p>In this training class, you will use a reference implementation for the In-app Billing Version 3 API called the {@code TrivialDrive} sample application. The sample includes convenience classes to quickly set up the In-app Billing service, marshal and unmarshal data types, and handle In-app Billing requests from the main thread of your application.</p>
 <p>To download the sample application:</p>
 <ol>
 <li>Open the <a href="{@docRoot}tools/help/sdk-manager.html">Android SDK Manager</a>.</li>
 <li>In the SDK Manager, expand the {@code Extras} section.</li>
-<li>Select <strong>Google Play Billing Library</strong>. Make sure to select the download for In-app Billing Version 3 or above.</li>
-<li>Click <strong>Install</strong> to complete the download.</li>
+<li>Select <strong>Google Play Billing Library</strong>.</li>
+<li>Click <strong>Install packages</strong> to complete the download.</li>
 </ol>
-<p>The sample files will be installed to {@code /your/sdk/location/extras/google/play_billing/in-app-billing-v03}.</p>
+<p>The sample files will be installed to {@code &lt;sdk&gt;/extras/google/play_billing/}.</p>
 
 <h2 id="AddToDevConsole">Add Your Application to the Developer Console</h2>
 <p>The Google Play Developer Console is where you publish your In-app Billing application and  manage the various digital goods that are available for purchase from your application. When you create a new application entry in the Developer Console, it automatically generates a public license key for your application. You will need this key to establish a trusted connection from your application to the Google Play servers. You only need to generate this key once per application, and don’t need to repeat these steps when you update the APK file for your application.</p>
@@ -133,6 +133,7 @@
 <pre>
 &#64;Override
 public void onDestroy() {
+   super.onDestroy();
    if (mHelper != null) mHelper.dispose();
    mHelper = null;
 }
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 4a72477..d231907 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1175,8 +1175,8 @@
         SkPaint* paint = getPaint(renderer);
         FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
         const bool pureTranslate = state.mMatrix.isPureTranslate();
-        fontRenderer.precache(paint, mText, mCount,
-                pureTranslate ? mat4::identity() : state.mMatrix);
+        const mat4 transform = renderer.findBestFontTransform(state.mMatrix);
+        fontRenderer.precache(paint, mText, mCount, transform);
     }
 
     virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level,
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index d5ea0f9..77b8df1 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -242,35 +242,31 @@
     uint8_t* cacheBuffer = cacheTexture->getTexture();
     uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
 
-    // Zero out the borders
-    for (cacheX = startX - TEXTURE_BORDER_SIZE; cacheX < endX + TEXTURE_BORDER_SIZE; cacheX++) {
-        cacheBuffer[(startY - TEXTURE_BORDER_SIZE) * cacheWidth + cacheX] = 0;
-        cacheBuffer[(endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + cacheX] = 0;
-    }
-
-    for (cacheY = startY - TEXTURE_BORDER_SIZE + 1;
-            cacheY < endY + TEXTURE_BORDER_SIZE - 1; cacheY++) {
-        cacheBuffer[cacheY * cacheWidth + startX - TEXTURE_BORDER_SIZE] = 0;
-        cacheBuffer[cacheY * cacheWidth + endX + TEXTURE_BORDER_SIZE - 1] = 0;
-    }
-
     // Copy the glyph image, taking the mask format into account
     uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
     int stride = glyph.rowBytes();
 
+    uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
+    memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+
     switch (format) {
         case SkMask::kA8_Format: {
             if (mGammaTable) {
                 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
+                    row = cacheY * cacheWidth;
+                    cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
                     for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
                         uint8_t tempCol = bitmapBuffer[bY + bX];
-                        cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
+                        cacheBuffer[row + cacheX] = mGammaTable[tempCol];
                     }
+                    cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
                 }
             } else {
                 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
-                    memcpy(&cacheBuffer[cacheY * cacheWidth + startX], &bitmapBuffer[bY],
-                            glyph.fWidth);
+                    row = cacheY * cacheWidth;
+                    memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth);
+                    cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
+                    cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
                 }
             }
             break;
@@ -283,12 +279,15 @@
                 int rowBytes = stride;
                 uint8_t* buffer = bitmapBuffer;
 
+                row = cacheY * cacheWidth;
+                cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
                 while (--rowBytes >= 0) {
                     uint8_t b = *buffer++;
                     for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) {
                         cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1];
                     }
                 }
+                cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
 
                 bitmapBuffer += stride;
             }
@@ -299,6 +298,9 @@
             break;
     }
 
+    row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
+    memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
+
     cachedGlyph->mIsValid = true;
 }
 
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 14c7c39..9aa9615 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -92,11 +92,11 @@
     // who will invoke OpenGLRenderer::resume()
 }
 
-GLint LayerRenderer::getTargetFbo() {
+GLint LayerRenderer::getTargetFbo() const {
     return mLayer->getFbo();
 }
 
-bool LayerRenderer::suppressErrorChecks() {
+bool LayerRenderer::suppressErrorChecks() const {
     return true;
 }
 
@@ -104,7 +104,7 @@
 // Layer support
 ///////////////////////////////////////////////////////////////////////////////
 
-bool LayerRenderer::hasLayer() {
+bool LayerRenderer::hasLayer() const {
     return true;
 }
 
@@ -116,7 +116,7 @@
 // Dirty region tracking
 ///////////////////////////////////////////////////////////////////////////////
 
-Region* LayerRenderer::getRegion() {
+Region* LayerRenderer::getRegion() const {
     if (getSnapshot()->flags & Snapshot::kFlagFboTarget) {
         return OpenGLRenderer::getRegion();
     }
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index 7a8bdc5..5f86731 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -65,10 +65,10 @@
 
 protected:
     virtual void ensureStencilBuffer();
-    virtual bool hasLayer();
-    virtual Region* getRegion();
-    virtual GLint getTargetFbo();
-    virtual bool suppressErrorChecks();
+    virtual bool hasLayer() const;
+    virtual Region* getRegion() const;
+    virtual GLint getTargetFbo() const;
+    virtual bool suppressErrorChecks() const;
 
 private:
     void generateMesh();
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index 2d017df..6a5ea51 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -232,11 +232,11 @@
     memcpy(v, data, sizeof(data));
 }
 
-float Matrix4::getTranslateX() {
+float Matrix4::getTranslateX() const {
     return data[kTranslateX];
 }
 
-float Matrix4::getTranslateY() {
+float Matrix4::getTranslateY() const {
     return data[kTranslateY];
 }
 
@@ -454,6 +454,14 @@
     }
 }
 
+void Matrix4::decomposeScale(float& sx, float& sy) const {
+    float len;
+    len = data[mat4::kScaleX] * data[mat4::kScaleX] + data[mat4::kSkewX] * data[mat4::kSkewX];
+    sx = copysignf(sqrtf(len), data[mat4::kScaleX]);
+    len = data[mat4::kScaleY] * data[mat4::kScaleY] + data[mat4::kSkewY] * data[mat4::kSkewY];
+    sy = copysignf(sqrtf(len), data[mat4::kScaleY]);
+}
+
 void Matrix4::dump() const {
     ALOGD("Matrix4[simple=%d, type=0x%x", isSimple(), getType());
     ALOGD("  %f %f %f %f", data[kScaleX], data[kSkewX], data[8], data[kTranslateX]);
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index 2fe96bc..7b7357e 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -79,6 +79,20 @@
         load(v);
     }
 
+    float operator[](int index) const {
+        return data[index];
+    }
+
+    float& operator[](int index) {
+        mType = kTypeUnknown;
+        return data[index];
+    }
+
+    Matrix4& operator=(const SkMatrix& v) {
+        load(v);
+        return *this;
+    }
+
     void loadIdentity();
 
     void load(const float* v);
@@ -147,8 +161,10 @@
     void mapRect(Rect& r) const;
     void mapPoint(float& x, float& y) const;
 
-    float getTranslateX();
-    float getTranslateY();
+    float getTranslateX() const;
+    float getTranslateY() const;
+
+    void decomposeScale(float& sx, float& sy) const;
 
     void dump() const;
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index c81bf7a..22ec93b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -723,7 +723,7 @@
     Rect clip;
     Rect bounds(left, top, right, bottom);
     Rect untransformedBounds(bounds);
-    mSnapshot->transform->mapRect(bounds);
+    currentTransform().mapRect(bounds);
 
     // Layers only make sense if they are in the framebuffer's bounds
     if (bounds.intersect(*mSnapshot->clipRect)) {
@@ -738,7 +738,7 @@
         } else if (fboLayer) {
             clip.set(bounds);
             mat4 inverse;
-            inverse.loadInverse(*mSnapshot->transform);
+            inverse.loadInverse(currentTransform());
             inverse.mapRect(clip);
             clip.snapToPixelBoundaries();
             if (clip.intersect(untransformedBounds)) {
@@ -938,11 +938,11 @@
     } else {
         setupDrawExternalTexture(layer->getTexture());
     }
-    if (mSnapshot->transform->isPureTranslate() &&
+    if (currentTransform().isPureTranslate() &&
             layer->getWidth() == (uint32_t) rect.getWidth() &&
             layer->getHeight() == (uint32_t) rect.getHeight()) {
-        const float x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
-        const float y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+        const float x = (int) floorf(rect.left + currentTransform().getTranslateX() + 0.5f);
+        const float y = (int) floorf(rect.top + currentTransform().getTranslateY() + 0.5f);
 
         layer->setFilter(GL_NEAREST);
         setupDrawModelView(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
@@ -966,15 +966,15 @@
 
         float x = rect.left;
         float y = rect.top;
-        bool simpleTransform = mSnapshot->transform->isPureTranslate() &&
+        bool simpleTransform = currentTransform().isPureTranslate() &&
                 layer->getWidth() == (uint32_t) rect.getWidth() &&
                 layer->getHeight() == (uint32_t) rect.getHeight();
 
         if (simpleTransform) {
             // When we're swapping, the layer is already in screen coordinates
             if (!swap) {
-                x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
-                y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+                x = (int) floorf(rect.left + currentTransform().getTranslateX() + 0.5f);
+                y = (int) floorf(rect.top + currentTransform().getTranslateY() + 0.5f);
             }
 
             layer->setFilter(GL_NEAREST, true);
@@ -1041,9 +1041,9 @@
         setupDrawPureColorUniforms();
         setupDrawColorFilterUniforms();
         setupDrawTexture(layer->getTexture());
-        if (mSnapshot->transform->isPureTranslate()) {
-            const float x = (int) floorf(rect.left + mSnapshot->transform->getTranslateX() + 0.5f);
-            const float y = (int) floorf(rect.top + mSnapshot->transform->getTranslateY() + 0.5f);
+        if (currentTransform().isPureTranslate()) {
+            const float x = (int) floorf(rect.left + currentTransform().getTranslateX() + 0.5f);
+            const float y = (int) floorf(rect.top + currentTransform().getTranslateY() + 0.5f);
 
             layer->setFilter(GL_NEAREST);
             setupDrawModelViewTranslate(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
@@ -1241,7 +1241,7 @@
 }
 
 void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state) {
-    mSnapshot->transform->load(state.mMatrix);
+    currentTransform().load(state.mMatrix);
 
     // NOTE: a clip RECT will be saved and restored, but DeferredDisplayState doesn't support
     // complex clips. In the future, we should add support for deferral of operations clipped by
@@ -1256,42 +1256,42 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void OpenGLRenderer::translate(float dx, float dy) {
-    mSnapshot->transform->translate(dx, dy, 0.0f);
+    currentTransform().translate(dx, dy, 0.0f);
 }
 
 void OpenGLRenderer::rotate(float degrees) {
-    mSnapshot->transform->rotate(degrees, 0.0f, 0.0f, 1.0f);
+    currentTransform().rotate(degrees, 0.0f, 0.0f, 1.0f);
 }
 
 void OpenGLRenderer::scale(float sx, float sy) {
-    mSnapshot->transform->scale(sx, sy, 1.0f);
+    currentTransform().scale(sx, sy, 1.0f);
 }
 
 void OpenGLRenderer::skew(float sx, float sy) {
-    mSnapshot->transform->skew(sx, sy);
+    currentTransform().skew(sx, sy);
 }
 
 void OpenGLRenderer::setMatrix(SkMatrix* matrix) {
     if (matrix) {
-        mSnapshot->transform->load(*matrix);
+        currentTransform().load(*matrix);
     } else {
-        mSnapshot->transform->loadIdentity();
+        currentTransform().loadIdentity();
     }
 }
 
 bool OpenGLRenderer::hasRectToRectTransform() {
-    return CC_LIKELY(mSnapshot->transform->rectToRect());
+    return CC_LIKELY(currentTransform().rectToRect());
 }
 
 void OpenGLRenderer::getMatrix(SkMatrix* matrix) {
-    mSnapshot->transform->copyTo(*matrix);
+    currentTransform().copyTo(*matrix);
 }
 
 void OpenGLRenderer::concatMatrix(SkMatrix* matrix) {
     SkMatrix transform;
-    mSnapshot->transform->copyTo(transform);
+    currentTransform().copyTo(transform);
     transform.preConcat(*matrix);
-    mSnapshot->transform->load(transform);
+    currentTransform().load(transform);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1385,7 +1385,7 @@
     }
 
     Rect r(left, top, right, bottom);
-    mSnapshot->transform->mapRect(r);
+    currentTransform().mapRect(r);
     r.snapToPixelBoundaries();
 
     Rect clipRect(*mSnapshot->clipRect);
@@ -1401,7 +1401,7 @@
     }
 
     transformed.set(left, top, right, bottom);
-    mSnapshot->transform->mapRect(transformed);
+    currentTransform().mapRect(transformed);
     transformed.snapToPixelBoundaries();
 
     clip.set(*mSnapshot->clipRect);
@@ -1426,7 +1426,7 @@
     }
 
     Rect r(left, top, right, bottom);
-    mSnapshot->transform->mapRect(r);
+    currentTransform().mapRect(r);
     r.snapToPixelBoundaries();
 
     Rect clipRect(*mSnapshot->clipRect);
@@ -1449,7 +1449,7 @@
 }
 
 bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
-    if (CC_LIKELY(mSnapshot->transform->rectToRect())) {
+    if (CC_LIKELY(currentTransform().rectToRect())) {
         bool clipped = mSnapshot->clip(left, top, right, bottom, op);
         if (clipped) {
             dirtyClip();
@@ -1465,7 +1465,7 @@
 
 bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) {
     SkMatrix transform;
-    mSnapshot->transform->copyTo(transform);
+    currentTransform().copyTo(transform);
 
     SkPath transformed;
     path->transform(transform, &transformed);
@@ -1642,8 +1642,8 @@
         bool ignoreTransform) {
     mModelView.loadTranslate(left, top, 0.0f);
     if (!ignoreTransform) {
-        mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
-        if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
+        mCaches.currentProgram->set(mOrthoMatrix, mModelView, currentTransform());
+        if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom, currentTransform());
     } else {
         mCaches.currentProgram->set(mOrthoMatrix, mModelView, mat4::identity());
         if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom);
@@ -1651,7 +1651,7 @@
 }
 
 void OpenGLRenderer::setupDrawModelViewIdentity(bool offset) {
-    mCaches.currentProgram->set(mOrthoMatrix, mat4::identity(), *mSnapshot->transform, offset);
+    mCaches.currentProgram->set(mOrthoMatrix, mat4::identity(), currentTransform(), offset);
 }
 
 void OpenGLRenderer::setupDrawModelView(float left, float top, float right, float bottom,
@@ -1664,9 +1664,9 @@
     }
     bool dirty = right - left > 0.0f && bottom - top > 0.0f;
     if (!ignoreTransform) {
-        mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+        mCaches.currentProgram->set(mOrthoMatrix, mModelView, currentTransform());
         if (mTrackDirtyRegions && dirty) {
-            dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
+            dirtyLayer(left, top, right, bottom, currentTransform());
         }
     } else {
         mCaches.currentProgram->set(mOrthoMatrix, mModelView, mat4::identity());
@@ -1694,7 +1694,7 @@
 void OpenGLRenderer::setupDrawShaderUniforms(bool ignoreTransform) {
     if (mDrawModifiers.mShader) {
         if (ignoreTransform) {
-            mModelView.loadInverse(*mSnapshot->transform);
+            mModelView.loadInverse(currentTransform());
         }
         mDrawModifiers.mShader->setupProgram(mCaches.currentProgram,
                 mModelView, *mSnapshot, &mTextureUnit);
@@ -1833,9 +1833,9 @@
     texture->setWrap(GL_CLAMP_TO_EDGE, true);
 
     bool ignoreTransform = false;
-    if (mSnapshot->transform->isPureTranslate()) {
-        x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
-        y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
+    if (currentTransform().isPureTranslate()) {
+        x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
+        y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
         ignoreTransform = true;
 
         texture->setFilter(GL_NEAREST, true);
@@ -1999,7 +1999,7 @@
     float a = alpha / 255.0f;
 
     if (hasLayer()) {
-        dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
+        dirtyLayer(left, top, right, bottom, currentTransform());
     }
 
     setupDraw();
@@ -2069,9 +2069,9 @@
     bool useScaleTransform = mDrawModifiers.mShader && scaled;
     bool ignoreTransform = false;
 
-    if (CC_LIKELY(mSnapshot->transform->isPureTranslate() && !useScaleTransform)) {
-        float x = (int) floorf(dstLeft + mSnapshot->transform->getTranslateX() + 0.5f);
-        float y = (int) floorf(dstTop + mSnapshot->transform->getTranslateY() + 0.5f);
+    if (CC_LIKELY(currentTransform().isPureTranslate() && !useScaleTransform)) {
+        float x = (int) floorf(dstLeft + currentTransform().getTranslateX() + 0.5f);
+        float y = (int) floorf(dstTop + currentTransform().getTranslateY() + 0.5f);
 
         dstRight = x + (dstRight - dstLeft);
         dstBottom = y + (dstBottom - dstTop);
@@ -2150,11 +2150,11 @@
         texture->setWrap(GL_CLAMP_TO_EDGE, true);
         texture->setFilter(GL_LINEAR, true);
 
-        const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+        const bool pureTranslate = currentTransform().isPureTranslate();
         // Mark the current layer dirty where we are going to draw the patch
         if (hasLayer() && mesh->hasEmptyQuads) {
-            const float offsetX = left + mSnapshot->transform->getTranslateX();
-            const float offsetY = top + mSnapshot->transform->getTranslateY();
+            const float offsetX = left + currentTransform().getTranslateX();
+            const float offsetY = top + currentTransform().getTranslateY();
             const size_t count = mesh->quads.size();
             for (size_t i = 0; i < count; i++) {
                 const Rect& bounds = mesh->quads.itemAt(i);
@@ -2164,14 +2164,14 @@
                     dirtyLayer(x, y, x + bounds.getWidth(), y + bounds.getHeight());
                 } else {
                     dirtyLayer(left + bounds.left, top + bounds.top,
-                            left + bounds.right, top + bounds.bottom, *mSnapshot->transform);
+                            left + bounds.right, top + bounds.bottom, currentTransform());
                 }
             }
         }
 
         if (CC_LIKELY(pureTranslate)) {
-            const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
-            const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
+            const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
+            const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
 
             drawTextureMesh(x, y, x + right - left, y + bottom - top, texture->id, alpha / 255.0f,
                     mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset,
@@ -2253,7 +2253,7 @@
 
     SkRect bounds = path.getBounds();
     PathTessellator::expandBoundsForStroke(bounds, paint, false);
-    dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *mSnapshot->transform);
+    dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, currentTransform());
 
     return drawVertexBuffer(vertexBuffer, paint);
 }
@@ -2282,7 +2282,7 @@
         return DrawGlInfo::kStatusDone;
     }
 
-    dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, *mSnapshot->transform);
+    dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, currentTransform());
 
     bool useOffset = !paint->isAntiAlias();
     return drawVertexBuffer(buffer, paint, useOffset);
@@ -2342,7 +2342,7 @@
         float top = points[i + 1] - halfWidth;
         float bottom = points [i + 1] + halfWidth;
 
-        dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
+        dirtyLayer(left, top, right, bottom, currentTransform());
     }
 
     glDrawArrays(GL_POINTS, 0, generatedVerticesCount);
@@ -2502,7 +2502,7 @@
         return drawConvexPath(path, p);
     }
 
-    if (p->isAntiAlias() && !mSnapshot->transform->isSimple()) {
+    if (p->isAntiAlias() && !currentTransform().isSimple()) {
         SkPath path;
         path.addRect(left, top, right, bottom);
         return drawConvexPath(path, p);
@@ -2562,16 +2562,16 @@
     }
 
     // NOTE: Skia does not support perspective transform on drawPosText yet
-    if (!mSnapshot->transform->isSimple()) {
+    if (!currentTransform().isSimple()) {
         return DrawGlInfo::kStatusDone;
     }
 
     float x = 0.0f;
     float y = 0.0f;
-    const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+    const bool pureTranslate = currentTransform().isPureTranslate();
     if (pureTranslate) {
-        x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
-        y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
+        x = (int) floorf(x + currentTransform().getTranslateX() + 0.5f);
+        y = (int) floorf(y + currentTransform().getTranslateY() + 0.5f);
     }
 
     FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
@@ -2587,7 +2587,7 @@
     }
 
     // Pick the appropriate texture filtering
-    bool linearFilter = mSnapshot->transform->changesBounds();
+    bool linearFilter = currentTransform().changesBounds();
     if (pureTranslate && !linearFilter) {
         linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
     }
@@ -2618,7 +2618,7 @@
             positions, hasActiveLayer ? &bounds : NULL)) {
         if (hasActiveLayer) {
             if (!pureTranslate) {
-                mSnapshot->transform->mapRect(bounds);
+                currentTransform().mapRect(bounds);
             }
             dirtyLayerUnchecked(bounds, getRegion());
         }
@@ -2627,6 +2627,31 @@
     return DrawGlInfo::kStatusDrew;
 }
 
+mat4 OpenGLRenderer::findBestFontTransform(const mat4& transform) const {
+    mat4 fontTransform;
+    if (CC_LIKELY(transform.isPureTranslate())) {
+        fontTransform = mat4::identity();
+    } else {
+        if (CC_UNLIKELY(transform.isPerspective())) {
+            // When the below condition is true, we are rendering text with a
+            // perspective transform inside a layer (either an inline layer
+            // created by Canvas.saveLayer() or a hardware layer.)
+            if (hasLayer() || getTargetFbo() != 0) {
+                float sx, sy;
+                currentTransform().decomposeScale(sx, sy);
+                fontTransform.loadScale(sx, sy, 1.0f);
+            } else {
+                fontTransform = mat4::identity();
+            }
+        } else {
+            float sx, sy;
+            currentTransform().decomposeScale(sx, sy);
+            fontTransform.loadScale(sx, sy, 1.0f);
+        }
+    }
+    return fontTransform;
+}
+
 status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
         float x, float y, const float* positions, SkPaint* paint, float length) {
     if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
@@ -2653,12 +2678,13 @@
 
     const float oldX = x;
     const float oldY = y;
-    const bool pureTranslate = mSnapshot->transform->isPureTranslate();
-    const bool isPerspective = mSnapshot->transform->isPerspective();
+
+    const mat4& transform = currentTransform();
+    const bool pureTranslate = transform.isPureTranslate();
 
     if (CC_LIKELY(pureTranslate)) {
-        x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
-        y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
+        x = (int) floorf(x + transform.getTranslateX() + 0.5f);
+        y = (int) floorf(y + transform.getTranslateY() + 0.5f);
     }
 
     int alpha;
@@ -2675,24 +2701,19 @@
 
     const bool hasActiveLayer = hasLayer();
 
-    const mat4* fontTransform;
-    if (CC_LIKELY(pureTranslate)) {
-        fontTransform = &mat4::identity();
-    } else {
-        if (CC_UNLIKELY(isPerspective)) {
-            // When the below condition is true, we are rendering text with a
-            // perspective transform inside a layer (either an inline layer
-            // created by Canvas.saveLayer() or a hardware layer.)
-            if (hasActiveLayer || getTargetFbo() != 0) {
-                fontTransform = mSnapshot->transform;
-            } else {
-                fontTransform = &mat4::identity();
-            }
-        } else {
-            fontTransform = mSnapshot->transform;
-        }
-    }
-    fontRenderer.setFont(paint, *fontTransform);
+    // We only pass a partial transform to the font renderer. That partial
+    // matrix defines how glyphs are rasterized. Typically we want glyphs
+    // to be rasterized at their final size on screen, which means the partial
+    // matrix needs to take the scale factor into account.
+    // When a partial matrix is used to transform glyphs during rasterization,
+    // the mesh is generated with the inverse transform (in the case of scale,
+    // the mesh is generated at 1.0 / scale for instance.) This allows us to
+    // apply the full transform matrix at draw time in the vertex shader.
+    // Applying the full matrix in the shader is the easiest way to handle
+    // rotation and perspective and allows us to always generated quads in the
+    // font renderer which greatly simplifies the code, clipping in particular.
+    mat4 fontTransform = findBestFontTransform(transform);
+    fontRenderer.setFont(paint, fontTransform);
 
     // Pick the appropriate texture filtering
     bool linearFilter = !pureTranslate || fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
@@ -2708,16 +2729,17 @@
     setupDrawShader();
     setupDrawBlending(true, mode);
     setupDrawProgram();
-    setupDrawModelView(x, y, x, y, !isPerspective, true);
+    setupDrawModelView(x, y, x, y, pureTranslate, true);
     // See comment above; the font renderer must use texture unit 0
     // assert(mTextureUnit == 0)
     setupDrawTexture(fontRenderer.getTexture(linearFilter));
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
-    setupDrawShaderUniforms(!isPerspective);
+    setupDrawShaderUniforms(pureTranslate);
     setupDrawTextGammaUniforms();
 
-    const Rect* clip = isPerspective ? NULL : mSnapshot->clipRect;
+    // TODO: Implement better clipping for scaled/rotated text
+    const Rect* clip = !pureTranslate ? NULL : mSnapshot->clipRect;
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
     bool status;
@@ -2732,8 +2754,8 @@
     }
 
     if (status && hasActiveLayer) {
-        if (isPerspective) {
-            mSnapshot->transform->mapRect(bounds);
+        if (!pureTranslate) {
+            transform.mapRect(bounds);
         }
         dirtyLayerUnchecked(bounds, getRegion());
     }
@@ -2781,7 +2803,7 @@
     if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
             hOffset, vOffset, hasActiveLayer ? &bounds : NULL)) {
         if (hasActiveLayer) {
-            mSnapshot->transform->mapRect(bounds);
+            currentTransform().mapRect(bounds);
             dirtyLayerUnchecked(bounds, getRegion());
         }
     }
@@ -2816,7 +2838,7 @@
         transform = &layer->getTransform();
         if (!transform->isIdentity()) {
             save(0);
-            mSnapshot->transform->multiply(*transform);
+            currentTransform().multiply(*transform);
         }
     }
 
@@ -2854,9 +2876,9 @@
             setupDrawPureColorUniforms();
             setupDrawColorFilterUniforms();
             setupDrawTexture(layer->getTexture());
-            if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) {
-                int tx = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
-                int ty = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
+            if (CC_LIKELY(currentTransform().isPureTranslate())) {
+                int tx = (int) floorf(x + currentTransform().getTranslateX() + 0.5f);
+                int ty = (int) floorf(y + currentTransform().getTranslateY() + 0.5f);
 
                 layer->setFilter(GL_NEAREST);
                 setupDrawModelViewTranslate(tx, ty,
@@ -3074,6 +3096,9 @@
 
 status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color,
         SkXfermode::Mode mode, bool ignoreTransform, bool dirty, bool clip) {
+    if (count == 0) {
+        return DrawGlInfo::kStatusDone;
+    }
 
     float left = FLT_MAX;
     float top = FLT_MAX;
@@ -3090,24 +3115,22 @@
         float r = rects[index + 2];
         float b = rects[index + 3];
 
-        if (ignoreTransform || !quickRejectNoScissor(left, top, right, bottom)) {
-            Vertex::set(vertex++, l, b);
-            Vertex::set(vertex++, l, t);
-            Vertex::set(vertex++, r, t);
-            Vertex::set(vertex++, l, b);
-            Vertex::set(vertex++, r, t);
-            Vertex::set(vertex++, r, b);
+        Vertex::set(vertex++, l, b);
+        Vertex::set(vertex++, l, t);
+        Vertex::set(vertex++, r, t);
+        Vertex::set(vertex++, l, b);
+        Vertex::set(vertex++, r, t);
+        Vertex::set(vertex++, r, b);
 
-            vertexCount += 6;
+        vertexCount += 6;
 
-            left = fminf(left, l);
-            top = fminf(top, t);
-            right = fmaxf(right, r);
-            bottom = fmaxf(bottom, b);
-        }
+        left = fminf(left, l);
+        top = fminf(top, t);
+        right = fmaxf(right, r);
+        bottom = fmaxf(bottom, b);
     }
 
-    if (count == 0 || (clip && quickReject(left, top, right, bottom))) {
+    if (clip && quickReject(left, top, right, bottom)) {
         return DrawGlInfo::kStatusDone;
     }
 
@@ -3126,7 +3149,7 @@
     setupDrawVertices((GLvoid*) &mesh[0].position[0]);
 
     if (dirty && hasLayer()) {
-        dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
+        dirtyLayer(left, top, right, bottom, currentTransform());
     }
 
     glDrawArrays(GL_TRIANGLES, 0, vertexCount);
@@ -3165,9 +3188,9 @@
 
     texture->setWrap(GL_CLAMP_TO_EDGE, true);
 
-    if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) {
-        const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
-        const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
+    if (CC_LIKELY(currentTransform().isPureTranslate())) {
+        const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
+        const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
 
         texture->setFilter(GL_NEAREST, true);
         drawTextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 1bfd3c0..e961af2 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -335,6 +335,12 @@
         }
     }
 
+    /**
+     * Return the best transform to use to rasterize text given a full
+     * transform matrix.
+     */
+    mat4 findBestFontTransform(const mat4& transform) const;
+
 protected:
     /**
      * Computes the projection matrix, initialize the first snapshot
@@ -384,28 +390,28 @@
     /**
      * Returns the current snapshot.
      */
-    sp<Snapshot> getSnapshot() {
+    sp<Snapshot> getSnapshot() const {
         return mSnapshot;
     }
 
     /**
      * Returns the region of the current layer.
      */
-    virtual Region* getRegion() {
+    virtual Region* getRegion() const {
         return mSnapshot->region;
     }
 
     /**
      * Indicates whether rendering is currently targeted at a layer.
      */
-    virtual bool hasLayer() {
+    virtual bool hasLayer() const {
         return (mSnapshot->flags & Snapshot::kFlagFboTarget) && mSnapshot->region;
     }
 
     /**
      * Returns the name of the FBO this renderer is rendering into.
      */
-    virtual GLint getTargetFbo() {
+    virtual GLint getTargetFbo() const {
         return 0;
     }
 
@@ -442,7 +448,7 @@
     /**
      * Set to true to suppress error checks at the end of a frame.
      */
-    virtual bool suppressErrorChecks() {
+    virtual bool suppressErrorChecks() const {
         return false;
     }
 
@@ -903,6 +909,10 @@
         mDirtyClip = true;
     }
 
+    inline mat4& currentTransform() const {
+        return *mSnapshot->transform;
+    }
+
     // Dimensions of the drawing surface
     int mWidth, mHeight;
 
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index bf522b7..9307f11 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -53,10 +53,8 @@
     mStrokeWidth = paint->getStrokeWidth();
     mAntiAliasing = paint->isAntiAlias();
     mLookupTransform.reset();
-    mLookupTransform[SkMatrix::kMScaleX] = matrix.data[mat4::kScaleX];
-    mLookupTransform[SkMatrix::kMScaleY] = matrix.data[mat4::kScaleY];
-    mLookupTransform[SkMatrix::kMSkewX] = matrix.data[mat4::kSkewX];
-    mLookupTransform[SkMatrix::kMSkewY] = matrix.data[mat4::kSkewY];
+    mLookupTransform[SkMatrix::kMScaleX] = matrix[mat4::kScaleX];
+    mLookupTransform[SkMatrix::kMScaleY] = matrix[mat4::kScaleY];
     if (!mLookupTransform.invert(&mInverseLookupTransform)) {
         ALOGW("Could not query the inverse lookup transform for this font");
     }
@@ -81,8 +79,6 @@
     hash = JenkinsHashMix(hash, int(mAntiAliasing));
     hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleX]));
     hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleY]));
-    hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMSkewX]));
-    hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMSkewY]));
     return JenkinsHashWhiten(hash);
 }
 
@@ -122,16 +118,6 @@
     if (lhs.mLookupTransform[SkMatrix::kMScaleY] >
             rhs.mLookupTransform[SkMatrix::kMScaleY]) return +1;
 
-    if (lhs.mLookupTransform[SkMatrix::kMSkewX] <
-            rhs.mLookupTransform[SkMatrix::kMSkewX]) return -1;
-    if (lhs.mLookupTransform[SkMatrix::kMSkewX] >
-            rhs.mLookupTransform[SkMatrix::kMSkewX]) return +1;
-
-    if (lhs.mLookupTransform[SkMatrix::kMSkewY] <
-            rhs.mLookupTransform[SkMatrix::kMSkewY]) return -1;
-    if (lhs.mLookupTransform[SkMatrix::kMSkewY] >
-            rhs.mLookupTransform[SkMatrix::kMSkewY]) return +1;
-
     return 0;
 }
 
@@ -185,7 +171,7 @@
             nPenX, nPenY - height, u1, v1, glyph->mCacheTexture);
 }
 
-void Font::drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y,
+void Font::drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y,
         uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
     SkPoint p[4];
     p[0].iset(glyph->mBitmapLeft, glyph->mBitmapTop + glyph->mBitmapHeight);
@@ -388,18 +374,17 @@
 
     static RenderGlyph gRenderGlyph[] = {
             &android::uirenderer::Font::drawCachedGlyph,
-            &android::uirenderer::Font::drawCachedGlyphPerspective,
+            &android::uirenderer::Font::drawCachedGlyphTransformed,
             &android::uirenderer::Font::drawCachedGlyphBitmap,
             &android::uirenderer::Font::drawCachedGlyphBitmap,
             &android::uirenderer::Font::measureCachedGlyph,
             &android::uirenderer::Font::measureCachedGlyph
     };
-    RenderGlyph render = gRenderGlyph[(mode << 1) + mTransform.isPerspective()];
+    RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform];
 
     text += start;
     int glyphsCount = 0;
 
-    const bool applyTransform = !mTransform.isIdentity() && !mTransform.isPerspective();
     const SkPaint::Align align = paint->getTextAlign();
 
     while (glyphsCount < numGlyphs) {
@@ -418,10 +403,6 @@
             float penX = x + positions[(glyphsCount << 1)];
             float penY = y + positions[(glyphsCount << 1) + 1];
 
-            if (applyTransform) {
-                mTransform.mapPoint(penX, penY);
-            }
-
             (*this.*render)(cachedGlyph, roundf(penX), roundf(penY),
                     bitmap, bitmapW, bitmapH, bounds, positions);
         }
@@ -495,7 +476,7 @@
         font = new Font(state, description);
         state->mActiveFonts.put(description, font);
     }
-    font->mTransform.load(matrix);
+    font->mIdentityTransform = matrix.isIdentity();
 
     return font;
 }
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index b2382f4..52cca1c 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -125,7 +125,7 @@
     void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
             Rect* bounds, const float* pos);
-    void drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y,
+    void drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y,
             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
             Rect* bounds, const float* pos);
     void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
@@ -142,7 +142,7 @@
     // Cache of glyphs
     DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs;
 
-    mat4 mTransform;
+    bool mIdentityTransform;
 };
 
 inline int strictly_order_type(const Font::FontDescription& lhs,
diff --git a/opengl/java/android/opengl/EGL14.java b/opengl/java/android/opengl/EGL14.java
index cd53c17..d1e2a9ed 100644
--- a/opengl/java/android/opengl/EGL14.java
+++ b/opengl/java/android/opengl/EGL14.java
@@ -445,4 +445,13 @@
         int target
     );
 
+    // C function EGLBoolean eglPresentationTimeANDROID ( EGLDisplay dpy, EGLSurface sur, EGLnsecsANDROID time )
+
+    /** @hide -- TODO(fadden) unhide this */
+    public static native boolean eglPresentationTimeANDROID(
+        EGLDisplay dpy,
+        EGLSurface sur,
+        long time
+    );
+
 }
diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
index f337600..b51b333 100644
--- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml
@@ -40,12 +40,13 @@
             android:layout_height="match_parent"
             android:fadingEdge="horizontal"
             android:scrollbars="none"
-            android:layout_gravity="end"
+            android:layout_gravity="right"
             android:fadingEdgeLength="@dimen/status_bar_recents_scroll_fading_edge_length">
 
             <LinearLayout android:id="@+id/recents_linear_layout"
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
+                android:layoutDirection="ltr"
                 android:layout_gravity="left"
                 android:orientation="horizontal">
             </LinearLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 8e1773a..f824a8e 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -156,6 +156,12 @@
 
     private void updateAlphaFromOffset(View animView, boolean dismissable) {
         if (FADE_OUT_DURING_SWIPE && dismissable) {
+            float alpha = getAlphaForOffset(animView);
+            if (alpha != 0f && alpha != 1f) {
+                animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            } else {
+                animView.setLayerType(View.LAYER_TYPE_NONE, null);
+            }
             animView.setAlpha(getAlphaForOffset(animView));
         }
         invalidateGlobalRegion(animView);
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index e79b2c6..9c2bca9 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -83,6 +83,7 @@
     private boolean mAnimateIconOfFirstTask;
     private boolean mWaitingForWindowAnimation;
     private long mWindowAnimationStartTime;
+    private boolean mCallUiHiddenBeforeNextReload;
 
     private RecentTasksLoader mRecentTasksLoader;
     private ArrayList<TaskDescription> mRecentTaskDescriptions;
@@ -180,17 +181,18 @@
             }
             if (index == 0) {
                 if (mAnimateIconOfFirstTask) {
-                    if (mItemToAnimateInWhenWindowAnimationIsFinished != null) {
-                        holder.iconView.setAlpha(1f);
-                        holder.iconView.setTranslationX(0f);
-                        holder.iconView.setTranslationY(0f);
-                        holder.labelView.setAlpha(1f);
-                        holder.labelView.setTranslationX(0f);
-                        holder.labelView.setTranslationY(0f);
-                        if (holder.calloutLine != null) {
-                            holder.calloutLine.setAlpha(1f);
-                            holder.calloutLine.setTranslationX(0f);
-                            holder.calloutLine.setTranslationY(0f);
+                    ViewHolder oldHolder = mItemToAnimateInWhenWindowAnimationIsFinished;
+                    if (oldHolder != null) {
+                        oldHolder.iconView.setAlpha(1f);
+                        oldHolder.iconView.setTranslationX(0f);
+                        oldHolder.iconView.setTranslationY(0f);
+                        oldHolder.labelView.setAlpha(1f);
+                        oldHolder.labelView.setTranslationX(0f);
+                        oldHolder.labelView.setTranslationY(0f);
+                        if (oldHolder.calloutLine != null) {
+                            oldHolder.calloutLine.setAlpha(1f);
+                            oldHolder.calloutLine.setTranslationX(0f);
+                            oldHolder.calloutLine.setTranslationY(0f);
                         }
                     }
                     mItemToAnimateInWhenWindowAnimationIsFinished = null;
@@ -198,17 +200,18 @@
                     final ViewTreeObserver observer = getViewTreeObserver();
                     final OnGlobalLayoutListener animateFirstIcon = new OnGlobalLayoutListener() {
                         public void onGlobalLayout() {
-                            if (mItemToAnimateInWhenWindowAnimationIsFinished != null) {
-                                holder.iconView.setAlpha(1f);
-                                holder.iconView.setTranslationX(0f);
-                                holder.iconView.setTranslationY(0f);
-                                holder.labelView.setAlpha(1f);
-                                holder.labelView.setTranslationX(0f);
-                                holder.labelView.setTranslationY(0f);
-                                if (holder.calloutLine != null) {
-                                    holder.calloutLine.setAlpha(1f);
-                                    holder.calloutLine.setTranslationX(0f);
-                                    holder.calloutLine.setTranslationY(0f);
+                            ViewHolder oldHolder = mItemToAnimateInWhenWindowAnimationIsFinished;
+                            if (oldHolder != null) {
+                                oldHolder.iconView.setAlpha(1f);
+                                oldHolder.iconView.setTranslationX(0f);
+                                oldHolder.iconView.setTranslationY(0f);
+                                oldHolder.labelView.setAlpha(1f);
+                                oldHolder.labelView.setTranslationX(0f);
+                                oldHolder.labelView.setTranslationY(0f);
+                                if (oldHolder.calloutLine != null) {
+                                    oldHolder.calloutLine.setAlpha(1f);
+                                    oldHolder.calloutLine.setTranslationX(0f);
+                                    oldHolder.calloutLine.setTranslationY(0f);
                                 }
                             }
                             mItemToAnimateInWhenWindowAnimationIsFinished = holder;
@@ -325,8 +328,15 @@
 
     public void show(boolean show, ArrayList<TaskDescription> recentTaskDescriptions,
             boolean firstScreenful, boolean animateIconOfFirstTask) {
-        mAnimateIconOfFirstTask = animateIconOfFirstTask;
-        mWaitingForWindowAnimation = animateIconOfFirstTask;
+        if (show && mCallUiHiddenBeforeNextReload) {
+            onUiHidden();
+            recentTaskDescriptions = null;
+            mAnimateIconOfFirstTask = false;
+            mWaitingForWindowAnimation = false;
+        } else {
+            mAnimateIconOfFirstTask = animateIconOfFirstTask;
+            mWaitingForWindowAnimation = animateIconOfFirstTask;
+        }
         if (show) {
             mWaitingToShow = true;
             refreshRecentTasksList(recentTaskDescriptions, firstScreenful);
@@ -372,6 +382,7 @@
         } else {
             mWaitingToShow = false;
             // call onAnimationEnd() and clearRecentTasksList() in onUiHidden()
+            mCallUiHiddenBeforeNextReload = true;
             if (mPopup != null) {
                 mPopup.dismiss();
             }
@@ -379,6 +390,7 @@
     }
 
     public void onUiHidden() {
+        mCallUiHiddenBeforeNextReload = false;
         if (!mShowing && mRecentTaskDescriptions != null) {
             onAnimationEnd(null);
             clearRecentTasksList();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 4599dd4..7bdcf6e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -559,6 +559,11 @@
                             + thumbBgPadding + thumbLeftMargin);
                     y = (int) (dm.heightPixels
                             - res.getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_height) - thumbBgPadding);
+                    if (mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
+                        x = dm.widthPixels - x - res
+                                .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width);
+                    }
+
                 } else { // if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
                     float thumbTopMargin = res
                             .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_top_margin);
@@ -596,10 +601,6 @@
                     y = (int) ((dm.heightPixels - statusBarHeight - height) / 2f + thumbTopMargin
                             + recentsItemTopPadding + thumbBgPadding + statusBarHeight);
                 }
-                if (mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
-                    x = dm.widthPixels - x - res
-                            .getDimensionPixelSize(R.dimen.status_bar_recents_thumbnail_width);
-                }
 
                 ActivityOptions opts = ActivityOptions.makeThumbnailScaleDownAnimation(
                         getStatusBarView(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 988951c..e351429 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -19,7 +19,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
-import java.util.ArrayList;
 import java.util.Iterator;
 
 import android.animation.ObjectAnimator;
@@ -30,7 +29,6 @@
 import android.util.AttributeSet;
 import android.util.Slog;
 import android.view.MotionEvent;
-import android.view.VelocityTracker;
 import android.view.View;
 import android.widget.FrameLayout;
 
@@ -39,6 +37,9 @@
 public class PanelView extends FrameLayout {
     public static final boolean DEBUG = PanelBar.DEBUG;
     public static final String TAG = PanelView.class.getSimpleName();
+
+    public static final boolean DEBUG_NAN = true; // http://b/7686690
+
     public final void LOG(String fmt, Object... args) {
         if (!DEBUG) return;
         Slog.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
@@ -141,8 +142,17 @@
                 last = event;
                 i++;
             }
-            mVX /= totalweight;
-            mVY /= totalweight;
+            if (totalweight > 0) {
+                mVX /= totalweight;
+                mVY /= totalweight;
+            } else {
+                if (DEBUG_NAN) {
+                    Slog.v("FlingTracker", "computeCurrentVelocity warning: totalweight=0",
+                            new Throwable());
+                }
+                // so as not to contaminate the velocities with NaN
+                mVX = mVY = 0;
+            }
 
             if (FlingTracker.DEBUG) {
                 Slog.v("FlingTracker", "computed: vx=" + mVX + " vy=" + mVY);
@@ -150,15 +160,19 @@
         }
         public float getXVelocity() {
             if (Float.isNaN(mVX)) {
-                Slog.v("FlingTracker", "warning: vx=NaN");
-                // XXX: should return 0
+                if (DEBUG_NAN) {
+                    Slog.v("FlingTracker", "warning: vx=NaN");
+                }
+                mVX = 0;
             }
             return mVX;
         }
         public float getYVelocity() {
             if (Float.isNaN(mVY)) {
-                Slog.v("FlingTracker", "warning: vx=NaN");
-                // XXX: should return 0
+                if (DEBUG_NAN) {
+                    Slog.v("FlingTracker", "warning: vx=NaN");
+                }
+                mVY = 0;
             }
             return mVY;
         }
@@ -531,8 +545,12 @@
 
     public void setExpandedHeightInternal(float h) {
         if (Float.isNaN(h)) {
-            Slog.v(TAG, "setExpandedHeightInternal: warning: h=NaN");
-            // XXX: should set h to 0
+            // If a NaN gets in here, it will freeze the Animators.
+            if (DEBUG_NAN) {
+                Slog.v(TAG, "setExpandedHeightInternal: warning: h=NaN, using 0 instead",
+                        new Throwable());
+            }
+            h = 0;
         }
 
         float fh = getFullHeight();
@@ -566,8 +584,12 @@
 
     public void setExpandedFraction(float frac) {
         if (Float.isNaN(frac)) {
-            Slog.v(TAG, "setExpandedFraction: frac=NaN");
-            // XXX: set frac to 0 to defend
+            // If a NaN gets in here, it will freeze the Animators.
+            if (DEBUG_NAN) {
+                Slog.v(TAG, "setExpandedFraction: frac=NaN, using 0 instead",
+                        new Throwable());
+            }
+            frac = 0;
         }
         setExpandedHeight(getFullHeight() * frac);
     }
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index 2a62c17..49295f5 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -21,7 +21,6 @@
 import android.accounts.AccountAndUser;
 import android.accounts.AccountAuthenticatorResponse;
 import android.accounts.AccountManager;
-import android.accounts.AccountManagerResponse;
 import android.accounts.AuthenticatorDescription;
 import android.accounts.GrantCredentialsPermissionActivity;
 import android.accounts.IAccountAuthenticator;
@@ -70,6 +69,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.google.android.collect.Lists;
 import com.google.android.collect.Sets;
@@ -103,7 +103,7 @@
 
     private static final int TIMEOUT_DELAY_MS = 1000 * 60;
     private static final String DATABASE_NAME = "accounts.db";
-    private static final int DATABASE_VERSION = 4;
+    private static final int DATABASE_VERSION = 5;
 
     private final Context mContext;
 
@@ -146,6 +146,8 @@
     private static final String META_KEY = "key";
     private static final String META_VALUE = "value";
 
+    private static final String TABLE_SHARED_ACCOUNTS = "shared_accounts";
+
     private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
             new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
     private static final Intent ACCOUNTS_CHANGED_INTENT;
@@ -249,12 +251,18 @@
 
         IntentFilter userFilter = new IntentFilter();
         userFilter.addAction(Intent.ACTION_USER_REMOVED);
-        mContext.registerReceiver(new BroadcastReceiver() {
+        userFilter.addAction(Intent.ACTION_USER_STARTED);
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                onUserRemoved(intent);
+                String action = intent.getAction();
+                if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                    onUserRemoved(intent);
+                } else if (Intent.ACTION_USER_STARTED.equals(action)) {
+                    onUserStarted(intent);
+                }
             }
-        }, userFilter);
+        }, UserHandle.ALL, userFilter, null, null);
     }
 
     public void systemReady() {
@@ -430,6 +438,21 @@
         }
     }
 
+    private void onUserStarted(Intent intent) {
+        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+        if (userId < 1) return;
+
+        // Check if there's a shared account that needs to be created as an account
+        Account[] sharedAccounts = getSharedAccountsAsUser(userId);
+        if (sharedAccounts == null || sharedAccounts.length == 0) return;
+        Account[] accounts = getAccountsAsUser(null, userId);
+        for (Account sa : sharedAccounts) {
+            if (ArrayUtils.contains(accounts, sa)) continue;
+            // Account doesn't exist. Copy it now.
+            copyAccountToUser(sa, UserHandle.USER_OWNER, userId);
+        }
+    }
+
     @Override
     public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) {
         Slog.d(TAG, "onServiceChanged() for userId " + userId);
@@ -535,14 +558,120 @@
         // fails if the account already exists
         long identityToken = clearCallingIdentity();
         try {
-            return addAccountInternal(accounts, account, password, extras);
+            return addAccountInternal(accounts, account, password, extras, false);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
+    private boolean copyAccountToUser(final Account account, int userFrom, int userTo) {
+        final UserAccounts fromAccounts = getUserAccounts(userFrom);
+        final UserAccounts toAccounts = getUserAccounts(userTo);
+        if (fromAccounts == null || toAccounts == null) {
+            return false;
+        }
+
+        long identityToken = clearCallingIdentity();
+        try {
+            new Session(fromAccounts, null, account.type, false,
+                    false /* stripAuthTokenFromResult */) {
+                protected String toDebugString(long now) {
+                    return super.toDebugString(now) + ", getAccountCredentialsForClone"
+                            + ", " + account.type;
+                }
+
+                public void run() throws RemoteException {
+                    mAuthenticator.getAccountCredentialsForCloning(this, account);
+                }
+
+                public void onResult(Bundle result) {
+                    if (result != null) {
+                        if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
+                            // Create a Session for the target user and pass in the bundle
+                            Slog.i(TAG, "getAccountCredentialsForCloning returned success, "
+                                    + "sending result to target user");
+                            completeCloningAccount(result, account, toAccounts);
+                        } else {
+                            Slog.e(TAG, "getAccountCredentialsForCloning returned failure");
+                            clonePassword(fromAccounts, toAccounts, account);
+                        }
+                        return;
+                    } else {
+                        Slog.e(TAG, "getAccountCredentialsForCloning returned null");
+                        clonePassword(fromAccounts, toAccounts, account);
+                        super.onResult(result);
+                    }
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return true;
+    }
+
+    // TODO: Remove fallback - move to authenticator
+    private void clonePassword(UserAccounts fromAccounts, UserAccounts toAccounts,
+            Account account) {
+        long id = clearCallingIdentity();
+        try {
+            String password = readPasswordInternal(fromAccounts, account);
+            String extraFlags = readUserDataInternal(fromAccounts, account, "flags");
+            String extraServices = readUserDataInternal(fromAccounts, account, "services");
+            Bundle extras = new Bundle();
+            extras.putString("flags", extraFlags);
+            extras.putString("services", extraServices);
+            addAccountInternal(toAccounts, account, password, extras, true);
+        } finally {
+            restoreCallingIdentity(id);
+        }
+    }
+
+    void completeCloningAccount(final Bundle result, final Account account,
+            final UserAccounts targetUser) {
+        long id = clearCallingIdentity();
+        try {
+            new Session(targetUser, null, account.type, false,
+                    false /* stripAuthTokenFromResult */) {
+                protected String toDebugString(long now) {
+                    return super.toDebugString(now) + ", getAccountCredentialsForClone"
+                            + ", " + account.type;
+                }
+
+                public void run() throws RemoteException {
+                    mAuthenticator.addAccountFromCredentials(this, account, result);
+                }
+
+                public void onResult(Bundle result) {
+                    if (result != null) {
+                        if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
+                            // TODO: Anything?
+                            Slog.i(TAG, "addAccount returned success");
+                        } else {
+                            // TODO: Show error notification
+                            // TODO: Should we remove the shadow account to avoid retries?
+                            Slog.e(TAG, "addAccountFromCredentials returned failure");
+                        }
+                        return;
+                    } else {
+                        Slog.e(TAG, "addAccountFromCredentials returned null");
+                        super.onResult(result);
+                    }
+                }
+
+                public void onError(int errorCode, String errorMessage) {
+                    super.onError(errorCode,  errorMessage);
+                    // TODO: Show error notification to user
+                    // TODO: Should we remove the shadow account so that it doesn't keep trying?
+                }
+
+            }.bind();
+        } finally {
+            restoreCallingIdentity(id);
+        }
+    }
+
     private boolean addAccountInternal(UserAccounts accounts, Account account, String password,
-            Bundle extras) {
+            Bundle extras, boolean restricted) {
         if (account == null) {
             return false;
         }
@@ -768,6 +897,21 @@
             removeAccountFromCacheLocked(accounts, account);
             sendAccountsChangedBroadcast(accounts.userId);
         }
+        if (accounts.userId == UserHandle.USER_OWNER) {
+            // Owner's account was removed, remove from any users that are sharing
+            // this account.
+            long id = Binder.clearCallingIdentity();
+            try {
+                List<UserInfo> users = mUserManager.getUsers(true);
+                for (UserInfo user : users) {
+                    if (!user.isPrimary() && user.isRestricted()) {
+                        removeSharedAccountAsUser(account, user.id);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(id);
+            }
+        }
     }
 
     public void invalidateAuthToken(String accountType, String authToken) {
@@ -1606,6 +1750,65 @@
     }
 
     @Override
+    public boolean addSharedAccountAsUser(Account account, int userId) {
+        userId = handleIncomingUser(userId);
+        SQLiteDatabase db = getUserAccounts(userId).openHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(ACCOUNTS_NAME, account.name);
+        values.put(ACCOUNTS_TYPE, account.type);
+        db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+                new String[] {account.name, account.type});
+        long accountId = db.insert(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME, values);
+        if (accountId < 0) {
+            Log.w(TAG, "insertAccountIntoDatabase: " + account
+                    + ", skipping the DB insert failed");
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean removeSharedAccountAsUser(Account account, int userId) {
+        userId = handleIncomingUser(userId);
+        UserAccounts accounts = getUserAccounts(userId);
+        SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+        int r = db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+                new String[] {account.name, account.type});
+        if (r > 0) {
+            removeAccountInternal(accounts, account);
+        }
+        return r > 0;
+    }
+
+    @Override
+    public Account[] getSharedAccountsAsUser(int userId) {
+        userId = handleIncomingUser(userId);
+        UserAccounts accounts = getUserAccounts(userId);
+        ArrayList<Account> accountList = new ArrayList<Account>();
+        Cursor cursor = null;
+        try {
+            cursor = accounts.openHelper.getReadableDatabase()
+                    .query(TABLE_SHARED_ACCOUNTS, new String[]{ACCOUNTS_NAME, ACCOUNTS_TYPE},
+                    null, null, null, null, null);
+            if (cursor != null && cursor.moveToFirst()) {
+                int nameIndex = cursor.getColumnIndex(ACCOUNTS_NAME);
+                int typeIndex = cursor.getColumnIndex(ACCOUNTS_TYPE);
+                do {
+                    accountList.add(new Account(cursor.getString(nameIndex),
+                            cursor.getString(typeIndex)));
+                } while (cursor.moveToNext());
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        Account[] accountArray = new Account[accountList.size()];
+        accountList.toArray(accountArray);
+        return accountArray;
+    }
+
+    @Override
     public Account[] getAccounts(String type) {
         return getAccountsAsUser(type, UserHandle.getCallingUserId());
     }
@@ -1679,7 +1882,6 @@
         private int mNumRequestContinued = 0;
         private int mNumErrors = 0;
 
-
         IAccountAuthenticator mAuthenticator = null;
 
         private final boolean mStripAuthTokenFromResult;
@@ -1688,7 +1890,7 @@
         public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
                 boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
             super();
-            if (response == null) throw new IllegalArgumentException("response is null");
+            //if (response == null) throw new IllegalArgumentException("response is null");
             if (accountType == null) throw new IllegalArgumentException("accountType is null");
             mAccounts = accounts;
             mStripAuthTokenFromResult = stripAuthTokenFromResult;
@@ -1699,11 +1901,13 @@
             synchronized (mSessions) {
                 mSessions.put(toString(), this);
             }
-            try {
-                response.asBinder().linkToDeath(this, 0 /* flags */);
-            } catch (RemoteException e) {
-                mResponse = null;
-                binderDied();
+            if (response != null) {
+                try {
+                    response.asBinder().linkToDeath(this, 0 /* flags */);
+                } catch (RemoteException e) {
+                    mResponse = null;
+                    binderDied();
+                }
             }
         }
 
@@ -2011,9 +2215,19 @@
                     + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
                     + META_VALUE + " TEXT)");
 
+            createSharedAccountsTable(db);
+
             createAccountsDeletionTrigger(db);
         }
 
+        private void createSharedAccountsTable(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
+                    + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+                    + ACCOUNTS_NAME + " TEXT NOT NULL, "
+                    + ACCOUNTS_TYPE + " TEXT NOT NULL, "
+                    + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
+        }
+
         private void createAccountsDeletionTrigger(SQLiteDatabase db) {
             db.execSQL(""
                     + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
@@ -2058,6 +2272,15 @@
                         " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
                 oldVersion++;
             }
+
+            if (oldVersion == 4) {
+                createSharedAccountsTable(db);
+                oldVersion++;
+            }
+
+            if (oldVersion != newVersion) {
+                Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
+            }
         }
 
         @Override
@@ -2216,6 +2439,16 @@
         throw new SecurityException(msg);
     }
 
+    private int handleIncomingUser(int userId) {
+        try {
+            return ActivityManagerNative.getDefault().handleIncomingUser(
+                    Binder.getCallingPid(), Binder.getCallingUid(), userId, true, true, "", null);
+        } catch (RemoteException re) {
+            // Shouldn't happen, local.
+        }
+        return userId;
+    }
+
     private boolean inSystemImage(int callingUid) {
         final int callingUserId = UserHandle.getUserId(callingUid);
 
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 7fb8902..51f001f 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -1614,16 +1614,9 @@
         }
         final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
         final PackageUserState state = ps.readUserState(userId);
-        pi = PackageParser.generatePackageInfo(p, gp.gids, flags,
+        return PackageParser.generatePackageInfo(p, gp.gids, flags,
                 ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions,
                 state, userId);
-        if (pi != null) {
-            pi.applicationInfo.enabledSetting = state.enabled;
-            pi.applicationInfo.enabled =
-                    pi.applicationInfo.enabledSetting == COMPONENT_ENABLED_STATE_DEFAULT
-                    || pi.applicationInfo.enabledSetting == COMPONENT_ENABLED_STATE_ENABLED;
-        }
-        return pi;
     }
 
     @Override
@@ -5652,7 +5645,7 @@
                 null);
 
         final int uid = Binder.getCallingUid();
-        if (!isUserAllowed(uid, UserManager.ALLOW_INSTALL_APPS)) {
+        if (!isUserAllowed(UserHandle.getUserId(uid), UserManager.ALLOW_INSTALL_APPS)) {
             try {
                 observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED);
             } catch (RemoteException re) {
@@ -5690,13 +5683,17 @@
      * @hide
      */
     @Override
-    public int installExistingPackage(String packageName) {
+    public int installExistingPackageAsUser(String packageName, int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
                 null);
         PackageSetting pkgSetting;
         final int uid = Binder.getCallingUid();
-        final int userId = UserHandle.getUserId(uid);
-        if (!isUserAllowed(uid, UserManager.ALLOW_INSTALL_APPS)) {
+        if (UserHandle.getUserId(uid) != userId) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "installExistingPackage for user " + userId);
+        }
+        if (!isUserAllowed(userId, UserManager.ALLOW_INSTALL_APPS)) {
             return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
         }
 
@@ -5730,14 +5727,11 @@
         return PackageManager.INSTALL_SUCCEEDED;
     }
 
-    private boolean isUserAllowed(int callingUid, String restrictionKey) {
-        if (callingUid != android.os.Process.myUid()) {
-            Bundle restrictions = sUserManager.getUserRestrictions(
-                    UserHandle.getUserId(callingUid));
-            if (!restrictions.getBoolean(UserManager.ALLOW_INSTALL_APPS)) {
-                Log.w(TAG, "User does not have permission to: " + restrictionKey);
-                return false;
-            }
+    private boolean isUserAllowed(int userId, String restrictionKey) {
+        Bundle restrictions = sUserManager.getUserRestrictions(userId);
+        if (!restrictions.getBoolean(UserManager.ALLOW_INSTALL_APPS)) {
+            Log.w(TAG, "User does not have permission to: " + restrictionKey);
+            return false;
         }
         return true;
     }
@@ -8090,14 +8084,19 @@
         return tmpPackageFile;
     }
 
-    public void deletePackage(final String packageName,
-                              final IPackageDeleteObserver observer,
-                              final int flags) {
+    @Override
+    public void deletePackageAsUser(final String packageName,
+                                    final IPackageDeleteObserver observer,
+                                    final int userId, final int flags) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.DELETE_PACKAGES, null);
-        // Queue up an async operation since the package deletion may take a little while.
         final int uid = Binder.getCallingUid();
-        if (!isUserAllowed(uid, UserManager.ALLOW_UNINSTALL_APPS)) {
+        if (UserHandle.getUserId(uid) != userId) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "deletePackage for user " + userId);
+        }
+        if (!isUserAllowed(userId, UserManager.ALLOW_UNINSTALL_APPS)) {
             try {
                 observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_USER_RESTRICTED);
             } catch (RemoteException re) {
@@ -8105,10 +8104,11 @@
             return;
         }
 
+        // Queue up an async operation since the package deletion may take a little while.
         mHandler.post(new Runnable() {
             public void run() {
                 mHandler.removeCallbacks(this);
-                final int returnCode = deletePackageX(packageName, uid, flags);
+                final int returnCode = deletePackageX(packageName, userId, flags);
                 if (observer != null) {
                     try {
                         observer.packageDeleted(packageName, returnCode);
@@ -8134,14 +8134,14 @@
      *  persisting settings for later use
      *  sending a broadcast if necessary
      */
-    private int deletePackageX(String packageName, int uid, int flags) {
+    private int deletePackageX(String packageName, int userId, int flags) {
         final PackageRemovedInfo info = new PackageRemovedInfo();
         final boolean res;
 
         IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
                 ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
         try {
-            if (dpm != null && dpm.packageHasActiveAdmins(packageName, UserHandle.getUserId(uid))) {
+            if (dpm != null && dpm.packageHasActiveAdmins(packageName, userId)) {
                 Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
                 return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
             }
@@ -8153,7 +8153,7 @@
         synchronized (mInstallLock) {
             res = deletePackageLI(packageName,
                     (flags & PackageManager.DELETE_ALL_USERS) != 0
-                            ? UserHandle.ALL : new UserHandle(UserHandle.getUserId(uid)),
+                            ? UserHandle.ALL : new UserHandle(userId),
                     true, flags | REMOVE_CHATTY, info, true);
             systemUpdate = info.isRemovedPackageSystemUpdate;
             if (res && !systemUpdate && mPackages.get(packageName) == null) {
@@ -8376,7 +8376,7 @@
                 Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
                 return false;
             }
-            if (!isSystemApp(ps) && user != null
+            if (user != null
                     && user.getIdentifier() != UserHandle.USER_ALL) {
                 // The caller is asking that the package only be deleted for a single
                 // user.  To do this, we just mark its uninstalled state and delete
@@ -8387,17 +8387,27 @@
                         true,  //stopped
                         true,  //notLaunched
                         null, null);
-                if (ps.isAnyInstalled(sUserManager.getUserIds())) {
-                    // Other user still have this package installed, so all
+                if (!isSystemApp(ps)) {
+                    if (ps.isAnyInstalled(sUserManager.getUserIds())) {
+                        // Other user still have this package installed, so all
+                        // we need to do is clear this user's data and save that
+                        // it is uninstalled.
+                        removeUser = user.getIdentifier();
+                        appId = ps.appId;
+                        mSettings.writePackageRestrictionsLPr(removeUser);
+                    } else {
+                        // We need to set it back to 'installed' so the uninstall
+                        // broadcasts will be sent correctly.
+                        ps.setInstalled(true, user.getIdentifier());
+                    }
+                } else {
+                    // This is a system app, so we assume that the
+                    // other users still have this package installed, so all
                     // we need to do is clear this user's data and save that
                     // it is uninstalled.
                     removeUser = user.getIdentifier();
                     appId = ps.appId;
                     mSettings.writePackageRestrictionsLPr(removeUser);
-                } else {
-                    // We need to set it back to 'installed' so the uninstall
-                    // broadcasts will be sent correctly.
-                    ps.setInstalled(true, user.getIdentifier());
                 }
             }
         }
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index c3f4256..1414cbd 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -542,16 +542,16 @@
 
     private void fallbackToSingleUserLocked() {
         // Create the primary user
-        UserInfo primary = new UserInfo(0,
+        UserInfo primary = new UserInfo(UserHandle.USER_OWNER,
                 mContext.getResources().getString(com.android.internal.R.string.owner_name), null,
                 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED);
         mUsers.put(0, primary);
         mNextSerialNumber = MIN_USER_ID;
-        
+
         Bundle restrictions = new Bundle();
         initRestrictionsToDefaults(restrictions);
-        mUserRestrictions.append(0, restrictions);
-        
+        mUserRestrictions.append(UserHandle.USER_OWNER, restrictions);
+
         updateUserIdsLocked();
 
         writeUserListLocked();
diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java
index ae110dd..3708df2 100644
--- a/services/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -216,14 +216,20 @@
         try {
             try {
                 if (WindowManagerService.DEBUG_SURFACE_TRACE) {
-                    mSurfaceControl = new SurfaceTrace(session, "FreezeSurface",
+                    mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface",
                             mWidth, mHeight,
-                            PixelFormat.OPAQUE, SurfaceControl.FX_SURFACE_SCREENSHOT | SurfaceControl.HIDDEN);
+                            PixelFormat.OPAQUE, SurfaceControl.HIDDEN);
                 } else {
-                    mSurfaceControl = new SurfaceControl(session, "FreezeSurface",
+                    mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
                             mWidth, mHeight,
-                            PixelFormat.OPAQUE, SurfaceControl.FX_SURFACE_SCREENSHOT | SurfaceControl.HIDDEN);
+                            PixelFormat.OPAQUE, SurfaceControl.HIDDEN);
                 }
+                // capture a screenshot into the surface we just created
+                Surface sur = new Surface();
+                sur.copyFrom(mSurfaceControl);
+                // FIXME: we should use the proper display
+                SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
+                        SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
                 mSurfaceControl.setLayerStack(mDisplay.getLayerStack());
                 mSurfaceControl.setLayer(FREEZE_LAYER + 1);
                 mSurfaceControl.setAlpha(0);
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index a0ed530..506fcec 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -997,6 +997,9 @@
                     Slog.i(TAG, "WIN DEATH: " + win);
                     if (win != null) {
                         mService.removeWindowLocked(mSession, win);
+                    } else if (WindowState.this.mHasSurface) {
+                        Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
+                        mService.removeWindowLocked(mSession, WindowState.this);
                     }
                 }
             } catch (IllegalArgumentException ex) {
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 4a489d5..e6a1df1 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -1745,6 +1745,8 @@
                     //Ignore more client requests
                     break;
                 case PEER_CONNECTION_USER_ACCEPT:
+                    //Stop discovery to avoid failure due to channel switch
+                    mWifiNative.p2pStopFind();
                     if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
                         mWifiNative.startWpsPbc(mGroup.getInterface(), null);
                     } else {