Merge "Change Animators to reset values when restarted if their target changes" into lmp-dev
diff --git a/api/current.txt b/api/current.txt
index cbb4e7b..6da3365 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7191,6 +7191,7 @@
     field public static final java.lang.String SEARCH_SERVICE = "search";
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
     field public static final java.lang.String STORAGE_SERVICE = "storage";
+    field public static final java.lang.String TELECOMM_SERVICE = "telecomm";
     field public static final java.lang.String TELEPHONY_SERVICE = "phone";
     field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
     field public static final java.lang.String TV_INPUT_SERVICE = "tv_input";
@@ -15619,7 +15620,7 @@
     method public final int getMaxVolume();
     method public final int getVolumeControl();
     method public final void notifyVolumeChanged();
-    method public void onAdjustVolumeBy(int);
+    method public void onAdjustVolume(int);
     method public abstract int onGetCurrentVolume();
     method public void onSetVolumeTo(int);
     field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
@@ -16221,12 +16222,12 @@
 package android.media.session {
 
   public final class MediaController {
+    ctor public MediaController(android.media.session.MediaSession.Token);
     method public void addCallback(android.media.session.MediaController.Callback);
     method public void addCallback(android.media.session.MediaController.Callback, android.os.Handler);
-    method public void adjustVolumeBy(int, int);
+    method public void adjustVolume(int, int);
     method public android.media.routing.MediaRouter.Delegate createMediaRouterDelegate();
     method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
-    method public static android.media.session.MediaController fromToken(android.media.session.MediaSession.Token);
     method public android.media.MediaMetadata getMetadata();
     method public android.media.session.PlaybackState getPlaybackState();
     method public int getRatingType();
@@ -16266,6 +16267,7 @@
   }
 
   public final class MediaSession {
+    ctor public MediaSession(android.content.Context, java.lang.String);
     method public void addCallback(android.media.session.MediaSession.Callback);
     method public void addCallback(android.media.session.MediaSession.Callback, android.os.Handler);
     method public void addTransportControlsCallback(android.media.session.MediaSession.TransportControlsCallback);
@@ -16317,7 +16319,6 @@
 
   public final class MediaSessionManager {
     method public void addActiveSessionsListener(android.media.session.MediaSessionManager.SessionListener, android.content.ComponentName);
-    method public android.media.session.MediaSession createSession(java.lang.String);
     method public java.util.List<android.media.session.MediaController> getActiveSessions(android.content.ComponentName);
   }
 
@@ -25492,6 +25493,8 @@
   public static final class Telephony.Mms.Intents {
     field public static final java.lang.String CONTENT_CHANGED_ACTION = "android.intent.action.CONTENT_CHANGED";
     field public static final java.lang.String DELETED_CONTENTS = "deleted_contents";
+    field public static final java.lang.String MMS_DOWNLOAD_ACTION = "android.provider.Telephony.MMS_DOWNLOAD";
+    field public static final java.lang.String MMS_SEND_ACTION = "android.provider.Telephony.MMS_SEND";
   }
 
   public static final class Telephony.Mms.Outbox implements android.provider.Telephony.BaseMmsColumns {
@@ -25595,8 +25598,10 @@
     field public static final java.lang.String SMS_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_CB_RECEIVED";
     field public static final java.lang.String SMS_DELIVER_ACTION = "android.provider.Telephony.SMS_DELIVER";
     field public static final java.lang.String SMS_EMERGENCY_CB_RECEIVED_ACTION = "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
+    field public static final java.lang.String SMS_FILTER_ACTION = "android.provider.Telephony.SMS_FILTER";
     field public static final java.lang.String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED";
     field public static final java.lang.String SMS_REJECTED_ACTION = "android.provider.Telephony.SMS_REJECTED";
+    field public static final java.lang.String SMS_SEND_ACTION = "android.provider.Telephony.SMS_SEND";
     field public static final java.lang.String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION = "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED";
     field public static final java.lang.String WAP_PUSH_DELIVER_ACTION = "android.provider.Telephony.WAP_PUSH_DELIVER";
     field public static final java.lang.String WAP_PUSH_RECEIVED_ACTION = "android.provider.Telephony.WAP_PUSH_RECEIVED";
@@ -28421,6 +28426,14 @@
     field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.intent.extra.START_CALL_WITH_VIDEO_STATE";
   }
 
+  public class TelecommManager {
+    method public void clearAccounts(java.lang.String);
+    method public java.util.List<android.telecomm.PhoneAccount> getEnabledPhoneAccounts();
+    method public android.telecomm.PhoneAccountMetadata getPhoneAccountMetadata(android.telecomm.PhoneAccount);
+    method public void registerPhoneAccount(android.telecomm.PhoneAccount, android.telecomm.PhoneAccountMetadata);
+    method public void unregisterPhoneAccount(android.telecomm.PhoneAccount);
+  }
+
   public class VideoCallProfile implements android.os.Parcelable {
     ctor public VideoCallProfile(int, int);
     method public int describeContents();
@@ -28782,9 +28795,13 @@
   public final class SmsManager {
     method public java.util.ArrayList<java.lang.String> divideMessage(java.lang.String);
     method public static android.telephony.SmsManager getDefault();
+    method public void injectSmsPdu(byte[], java.lang.String, android.app.PendingIntent);
     method public void sendDataMessage(java.lang.String, java.lang.String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
     method public void sendMultipartTextMessage(java.lang.String, java.lang.String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
     method public void sendTextMessage(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
+    method public void updateMmsDownloadStatus(int, byte[]);
+    method public void updateMmsSendStatus(int, boolean);
+    method public void updateSmsSendStatus(int, boolean);
     field public static final int RESULT_ERROR_GENERIC_FAILURE = 1; // 0x1
     field public static final int RESULT_ERROR_NO_SERVICE = 4; // 0x4
     field public static final int RESULT_ERROR_NULL_PDU = 3; // 0x3
diff --git a/cmds/media/src/com/android/commands/media/Media.java b/cmds/media/src/com/android/commands/media/Media.java
index 800b925..c771f65 100644
--- a/cmds/media/src/com/android/commands/media/Media.java
+++ b/cmds/media/src/com/android/commands/media/Media.java
@@ -114,7 +114,7 @@
             List<IBinder> sessions = mSessionService
                     .getSessions(null, ActivityManager.getCurrentUser());
             for (IBinder session : sessions) {
-                MediaController controller = MediaController.fromBinder(ISessionController.Stub
+                MediaController controller = new MediaController(ISessionController.Stub
                         .asInterface(session));
                 if (controller != null && controller.getSessionInfo().getId().equals(id)) {
                     ControllerMonitor monitor = new ControllerMonitor(controller);
@@ -248,7 +248,7 @@
             List<IBinder> sessions = mSessionService
                     .getSessions(null, ActivityManager.getCurrentUser());
             for (IBinder session : sessions) {
-                MediaController controller = MediaController.fromBinder(ISessionController.Stub
+                MediaController controller = new MediaController(ISessionController.Stub
                         .asInterface(session));
                 if (controller != null) {
                     MediaSessionInfo info = controller.getSessionInfo();
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index b7f1ff9..d6c17ae 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -998,8 +998,8 @@
 
         final InstallSessionParams params = new InstallSessionParams();
         params.installFlags = PackageManager.INSTALL_ALL_USERS;
-        params.mode = InstallSessionParams.MODE_FULL_INSTALL;
-        params.progressMax = -1;
+        params.setModeFullInstall();
+        params.setProgressMax(-1);
 
         String opt;
         while ((opt = nextOption()) != null) {
@@ -1021,10 +1021,11 @@
             } else if (opt.equals("-d")) {
                 params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
             } else if (opt.equals("-p")) {
-                params.mode = InstallSessionParams.MODE_INHERIT_EXISTING;
+                params.setModeInheritExisting();
             } else if (opt.equals("-S")) {
-                params.deltaSize = Long.parseLong(nextOptionData());
-                params.progressMax = (int) params.deltaSize;
+                final long deltaSize = Long.parseLong(nextOptionData());
+                params.setDeltaSize(deltaSize);
+                params.setProgressMax((int) params.deltaSize);
             } else if (opt.equals("--abi")) {
                 params.abiOverride = checkAbiArgument(nextOptionData());
             } else {
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 129e52c..95f83ac 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -356,6 +356,22 @@
     public void setTarget(Object target) {
     }
 
+    // Hide reverse() and canReverse() for now since reverse() only work for simple
+    // cases, like we don't support sequential, neither startDelay.
+    // TODO: make reverse() works for all the Animators.
+    /**
+     * @hide
+     */
+    public boolean canReverse() {
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public void reverse() {
+    }
+
     /**
      * <p>An animation listener receives notifications from an animation.
      * Notifications indicate animation related events, such as the end or the
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 018a2d6..9156eeb 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -124,7 +124,7 @@
     // was set on this AnimatorSet, so it should not be passed down to the children.
     private TimeInterpolator mInterpolator = null;
 
-
+    private boolean mReversible = true;
     /**
      * Sets up this AnimatorSet to play all of the supplied animations at the same time.
      * This is equivalent to calling {@link #play(Animator)} with the first animator in the
@@ -177,6 +177,7 @@
             if (items.length == 1) {
                 play(items[0]);
             } else {
+                mReversible = false;
                 for (int i = 0; i < items.length - 1; ++i) {
                     play(items[i]).before(items[i+1]);
                 }
@@ -196,6 +197,7 @@
             if (items.size() == 1) {
                 play(items.get(0));
             } else {
+                mReversible = false;
                 for (int i = 0; i < items.size() - 1; ++i) {
                     play(items.get(i)).before(items.get(i+1));
                 }
@@ -407,6 +409,9 @@
      */
     @Override
     public void setStartDelay(long startDelay) {
+        if (mStartDelay > 0) {
+            mReversible = false;
+        }
         mStartDelay = startDelay;
     }
 
@@ -512,7 +517,7 @@
                 node.animation.setInterpolator(mInterpolator);
             }
         }
-            // First, sort the nodes (if necessary). This will ensure that sortedNodes
+        // First, sort the nodes (if necessary). This will ensure that sortedNodes
         // contains the animation nodes in the correct order.
         sortNodes();
 
@@ -626,6 +631,7 @@
         anim.mNodeMap = new HashMap<Animator, Node>();
         anim.mNodes = new ArrayList<Node>();
         anim.mSortedNodes = new ArrayList<Node>();
+        anim.mReversible = mReversible;
 
         // Walk through the old nodes list, cloning each node and adding it to the new nodemap.
         // One problem is that the old node dependencies point to nodes in the old AnimatorSet.
@@ -908,6 +914,35 @@
     }
 
     /**
+     * @hide
+     */
+    @Override
+    public boolean canReverse() {
+        if (!mReversible)  {
+            return false;
+        }
+        // Loop to make sure all the Nodes can reverse.
+        for (Node node : mNodes) {
+            if (!node.animation.canReverse() || node.animation.getStartDelay() > 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void reverse() {
+        if (canReverse()) {
+            for (Node node : mNodes) {
+                node.animation.reverse();
+            }
+        }
+    }
+
+    /**
      * Dependency holds information about the node that some other node is
      * dependent upon and the nature of that dependency.
      *
@@ -1124,6 +1159,7 @@
          * {@link AnimatorSet#play(Animator)} method ends.
          */
         public Builder before(Animator anim) {
+            mReversible = false;
             Node node = mNodeMap.get(anim);
             if (node == null) {
                 node = new Node(anim);
@@ -1144,6 +1180,7 @@
          * {@link AnimatorSet#play(Animator)} method to play.
          */
         public Builder after(Animator anim) {
+            mReversible = false;
             Node node = mNodeMap.get(anim);
             if (node == null) {
                 node = new Node(anim);
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 5338dd0..e3380a9 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1038,6 +1038,7 @@
      * play backwards. This behavior is only set for the current animation; future playing
      * of the animation will use the default behavior of playing forward.
      */
+    @Override
     public void reverse() {
         mPlayingBackwards = !mPlayingBackwards;
         if (mPlayingState == RUNNING) {
@@ -1053,6 +1054,14 @@
     }
 
     /**
+     * @hide
+     */
+    @Override
+    public boolean canReverse() {
+        return true;
+    }
+
+    /**
      * Called internally to end an animation by removing it from the animations list. Must be
      * called on the UI thread.
      */
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 12e18cc..38614a0 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -56,6 +56,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
+import java.util.Objects;
 
 final class IntentReceiverLeaked extends AndroidRuntimeException {
     public IntentReceiverLeaked(String msg) {
@@ -252,6 +253,22 @@
             }
 
             if (mIncludeCode && !mPackageName.equals("android")) {
+                // Avoid the binder call when the package is the current application package.
+                // The activity manager will perform ensure that dexopt is performed before
+                // spinning up the process.
+                if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) {
+                    final String isa = VMRuntime.getRuntime().vmInstructionSet();
+                    try {
+                        // TODO: We can probably do away with the isa argument since
+                        // the AM and PM have enough information to figure this out
+                        // themselves. If we do need it, we should match it against the
+                        // list of devices ISAs before sending it down to installd.
+                        ActivityThread.getPackageManager().performDexOptIfNeeded(mPackageName, isa);
+                    } catch (RemoteException re) {
+                        // Ignored.
+                    }
+                }
+
                 final ArrayList<String> zipPaths = new ArrayList<>();
                 final ArrayList<String> libPaths = new ArrayList<>();
 
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index ac74ca1..8a53e08 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -64,6 +64,8 @@
     int mInitialMinute;
     boolean mIs24HourView;
 
+    private boolean mIsCanceled;
+
     /**
      * @param context Parent.
      * @param callBack How parent is notified.
@@ -124,10 +126,13 @@
         mTimePicker.setDismissCallback(new TimePicker.TimePickerDismissCallback() {
             @Override
             public void dismiss(TimePicker view, boolean isCancel, int hourOfDay, int minute) {
+                mIsCanceled = isCancel;
                 if (!isCancel) {
                     mTimeSetCallback.onTimeSet(view, hourOfDay, minute);
+                    TimePickerDialog.this.dismiss();
+                } else {
+                    TimePickerDialog.this.cancel();
                 }
-                TimePickerDialog.this.dismiss();
             }
         });
         mTimePicker.setIs24HourView(mIs24HourView);
@@ -150,7 +155,7 @@
     }
 
     private void tryNotifyTimeSet() {
-        if (mTimeSetCallback != null) {
+        if (mTimeSetCallback != null && !mIsCanceled) {
             mTimePicker.clearFocus();
             mTimeSetCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
                     mTimePicker.getCurrentMinute());
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index ba2930b..28108a0 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -331,6 +331,10 @@
      * its datastore, if appropriate, and close the socket that had been provided in
      * {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}.
      *
+     * <p class="note">If the transport returns TRANSPORT_OK from this method, then the
+     * OS will always provide a matching call to {@link #finishBackup()} even if sending
+     * data via {@link #sendBackupData(int)} failed at some point.
+     *
      * @param targetPackage The package whose data is to follow.
      * @param socket The socket file descriptor through which the data will be provided.
      *    If the transport returns {@link #TRANSPORT_PACKAGE_REJECTED} here, it must still
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d068b1f..1569b9f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2539,7 +2539,6 @@
      *
      * @see #getSystemService
      * @see android.telecomm.TelecommManager
-     * @hide
      */
     public static final String TELECOMM_SERVICE = "telecomm";
 
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 3a98f5d..eb46cf0 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -384,10 +384,16 @@
 
     /**
      * Ask the package manager to perform dex-opt (if needed) on the given
-     * package, if it already hasn't done mode.  Only does this if running
-     * in the special development "no pre-dexopt" mode.
+     * package and for the given instruction set if it already hasn't done
+     * so.
+     *
+     * If the supplied instructionSet is null, the package manager will use
+     * the packages default instruction set.
+     *
+     * In most cases, apps are dexopted in advance and this function will
+     * be a no-op.
      */
-    boolean performDexOpt(String packageName);
+    boolean performDexOptIfNeeded(String packageName, String instructionSet);
 
     /**
      * Update status of external media on the package manager to scan and
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 43c2b15..ab33d75 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -4201,7 +4201,7 @@
         public int mPreferredOrder = 0;
 
         // For use by package manager to keep track of where it needs to do dexopt.
-        public boolean mDexOptNeeded = true;
+        public final ArraySet<String> mDexOptPerformed = new ArraySet<>(4);
 
         // For use by package manager to keep track of when a package was last used.
         public long mLastPackageUsageTimeInMills;
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 6cda905..3f10b92 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -75,9 +75,7 @@
     public static final int DENSITY_400 = 400;
 
     /**
-     * Standard quantized DPI for extra-extra-high-density screens.  Applications
-     * should not generally worry about this density; relying on XHIGH graphics
-     * being scaled up to it should be sufficient for almost all cases.
+     * Standard quantized DPI for extra-extra-high-density screens.
      */
     public static final int DENSITY_XXHIGH = 480;
 
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index f2f363a..8c9b819 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -327,6 +327,12 @@
     }
 
     /**
+     *  Indicates that the content drawn by HardwareDrawCallbacks needs to
+     *  be updated, which will be done by the next call to draw()
+     */
+    abstract void invalidateRoot();
+
+    /**
      * Draws the specified view.
      *
      * @param view The view to draw.
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index e2ebf6e..b033780 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -96,6 +96,7 @@
     private RenderNode mRootNode;
     private Choreographer mChoreographer;
     private boolean mProfilingEnabled;
+    private boolean mRootNodeNeedsUpdate;
 
     ThreadedRenderer(Context context, boolean translucent) {
         final TypedArray a = context.obtainStyledAttributes(
@@ -255,30 +256,41 @@
         return changed;
     }
 
-    private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
+    private void updateViewTreeDisplayList(View view) {
         view.mPrivateFlags |= View.PFLAG_DRAWN;
-
         view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                 == View.PFLAG_INVALIDATED;
         view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
-
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
-        HardwareCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
-        try {
-            canvas.save();
-            canvas.translate(mInsetLeft, mInsetTop);
-            callbacks.onHardwarePreDraw(canvas);
-            canvas.drawRenderNode(view.getDisplayList());
-            callbacks.onHardwarePostDraw(canvas);
-            canvas.restore();
-        } finally {
-            mRootNode.end(canvas);
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-        }
-
+        view.getDisplayList();
         view.mRecreateDisplayList = false;
     }
 
+    private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
+        updateViewTreeDisplayList(view);
+
+        if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
+            HardwareCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
+            try {
+                canvas.save();
+                canvas.translate(mInsetLeft, mInsetTop);
+                callbacks.onHardwarePreDraw(canvas);
+                canvas.drawRenderNode(view.getDisplayList());
+                callbacks.onHardwarePostDraw(canvas);
+                canvas.restore();
+                mRootNodeNeedsUpdate = false;
+            } finally {
+                mRootNode.end(canvas);
+            }
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+    }
+
+    @Override
+    void invalidateRoot() {
+        mRootNodeNeedsUpdate = true;
+    }
+
     @Override
     void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
         attachInfo.mIgnoreDirtyState = true;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b554548..341419c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2440,8 +2440,11 @@
             if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
                 // Draw with hardware renderer.
                 mIsAnimating = false;
-                mHardwareYOffset = yOffset;
-                mHardwareXOffset = xOffset;
+                if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
+                    mHardwareYOffset = yOffset;
+                    mHardwareXOffset = xOffset;
+                    mAttachInfo.mHardwareRenderer.invalidateRoot();
+                }
                 mResizeAlpha = resizeAlpha;
 
                 dirty.setEmpty();
@@ -2827,6 +2830,10 @@
         // Set the new focus host and node.
         mAccessibilityFocusedHost = view;
         mAccessibilityFocusedVirtualView = node;
+
+        if (mAttachInfo.mHardwareRenderer != null) {
+            mAttachInfo.mHardwareRenderer.invalidateRoot();
+        }
     }
 
     @Override
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index aab48b3..988d461 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -138,13 +138,6 @@
         }
     }
 
-protected:
-    virtual void damageSelf(TreeInfo& info) {
-        // Intentionally a no-op. As RootRenderNode gets a new DisplayListData
-        // every frame this would result in every draw push being a full inval,
-        // which is wrong. Only RootRenderNode has this issue.
-    }
-
 private:
     sp<Looper> mLooper;
     std::vector<OnFinishedEvent> mOnFinishedEvents;
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 64366e5..1f7acec 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -39,6 +39,7 @@
 #include <cutils/fs.h>
 #include <cutils/multiuser.h>
 #include <cutils/sched_policy.h>
+#include <private/android_filesystem_config.h>
 #include <utils/String8.h>
 #include <selinux/android.h>
 #include <processgroup/processgroup.h>
@@ -536,8 +537,15 @@
         jint debug_flags, jobjectArray rlimits,
         jint mount_external, jstring se_info, jstring se_name,
         jintArray fdsToClose) {
+    // Grant CAP_WAKE_ALARM to the Bluetooth process.
+    jlong capabilities = 0;
+    if (uid == AID_BLUETOOTH) {
+        capabilities |= (1LL << CAP_WAKE_ALARM);
+    }
+
     return ForkAndSpecializeCommon(env, uid, gid, gids, debug_flags,
-            rlimits, 0, 0, mount_external, se_info, se_name, false, fdsToClose);
+            rlimits, capabilities, capabilities, mount_external, se_info,
+            se_name, false, fdsToClose);
 }
 
 static jint com_android_internal_os_Zygote_nativeForkSystemServer(
diff --git a/core/res/res/drawable/btn_check_material_anim.xml b/core/res/res/drawable/btn_check_material_anim.xml
index 73b8a3e..1e05e84 100644
--- a/core/res/res/drawable/btn_check_material_anim.xml
+++ b/core/res/res/drawable/btn_check_material_anim.xml
@@ -30,28 +30,28 @@
     <transition android:fromId="@+id/off" android:toId="@+id/on">
         <animation-list>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_000" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_000" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_001" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_001" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_002" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_002" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_003" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_003" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_004" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_004" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_005" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_005" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_006" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_006" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_on_mtrl_007" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_on_mtrl_007" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
                 <bitmap android:src="@drawable/btn_check_to_on_mtrl_008" android:tint="?attr/colorControlActivated" />
@@ -106,28 +106,28 @@
                 <bitmap android:src="@drawable/btn_check_to_off_mtrl_007" android:tint="?attr/colorControlActivated" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_008" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_008" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_009" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_009" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_010" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_010" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_011" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_011" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_012" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_012" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_013" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_013" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_014" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_014" android:tint="?attr/colorControlNormal" />
             </item>
             <item android:duration="15">
-                <bitmap android:src="@drawable/btn_check_to_off_mtrl_015" android:tint="?attr/colorControlActivated" />
+                <bitmap android:src="@drawable/btn_check_to_off_mtrl_015" android:tint="?attr/colorControlNormal" />
             </item>
         </animation-list>
     </transition>
diff --git a/docs/html/google/gcm/ccs.jd b/docs/html/google/gcm/ccs.jd
index 4389e3d..90d8d4c 100644
--- a/docs/html/google/gcm/ccs.jd
+++ b/docs/html/google/gcm/ccs.jd
@@ -535,6 +535,8 @@
 import org.jivesoftware.smack.ConnectionListener;
 import org.jivesoftware.smack.PacketInterceptor;
 import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.SmackException.NotConnectedException;
 import org.jivesoftware.smack.XMPPConnection;
 import org.jivesoftware.smack.XMPPException;
 import org.jivesoftware.smack.filter.PacketTypeFilter;
@@ -544,352 +546,378 @@
 import org.jivesoftware.smack.packet.PacketExtension;
 import org.jivesoftware.smack.provider.PacketExtensionProvider;
 import org.jivesoftware.smack.provider.ProviderManager;
+import org.jivesoftware.smack.tcp.XMPPTCPConnection;
 import org.jivesoftware.smack.util.StringUtils;
 import org.json.simple.JSONValue;
 import org.json.simple.parser.ParseException;
 import org.xmlpull.v1.XmlPullParser;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Random;
+import java.util.UUID;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.net.ssl.SSLSocketFactory;
+
 /**
- * Sample Smack implementation of a client for GCM Cloud Connection Server.
+ * Sample Smack implementation of a client for GCM Cloud Connection Server. This
+ * code can be run as a standalone CCS client.
  *
  * &lt;p&gt;For illustration purposes only.
  */
 public class SmackCcsClient {
 
-  Logger logger = Logger.getLogger(&quot;SmackCcsClient&quot;);
+    private static final Logger logger = Logger.getLogger(&quot;SmackCcsClient&quot;);
 
-  public static final String GCM_SERVER = &quot;gcm.googleapis.com&quot;;
-  public static final int GCM_PORT = 5235;
+    private static final String GCM_SERVER = &quot;gcm.googleapis.com&quot;;
+    private static final int GCM_PORT = 5235;
 
-  public static final String GCM_ELEMENT_NAME = &quot;gcm&quot;;
-  public static final String GCM_NAMESPACE = &quot;google:mobile:data&quot;;
+    private static final String GCM_ELEMENT_NAME = &quot;gcm&quot;;
+    private static final String GCM_NAMESPACE = &quot;google:mobile:data&quot;;
 
-  static Random random = new Random();
-  XMPPConnection connection;
-  ConnectionConfiguration config;
+    static {
 
-  /**
-   * XMPP Packet Extension for GCM Cloud Connection Server.
-   */
-  class GcmPacketExtension extends DefaultPacketExtension {
-    String json;
-
-    public GcmPacketExtension(String json) {
-      super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
-      this.json = json;
+        ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE,
+            new PacketExtensionProvider() {
+                &#64;Override
+                public PacketExtension parseExtension(XmlPullParser parser) throws
+                        Exception {
+                    String json = parser.nextText();
+                    return new GcmPacketExtension(json);
+                }
+            });
     }
 
-    public String getJson() {
-      return json;
+    private XMPPConnection connection;
+
+    /**
+     * Indicates whether the connection is in draining state, which means that it
+     * will not accept any new downstream messages.
+     */
+    protected volatile boolean connectionDraining = false;
+
+    /**
+     * Sends a downstream message to GCM.
+     *
+     * &#64;return true if the message has been successfully sent.
+     */
+    public boolean sendDownstreamMessage(String jsonRequest) throws
+            NotConnectedException {
+        if (!connectionDraining) {
+            send(jsonRequest);
+            return true;
+        }
+        logger.info(&quot;Dropping downstream message since the connection is draining&quot;);
+        return false;
     }
 
-    &#64;Override
-    public String toXML() {
-      return String.format(&quot;&lt;%s xmlns=\&quot;%s\&quot;&gt;%s&lt;/%s&gt;&quot;, GCM_ELEMENT_NAME,
-          GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
+    /**
+     * Returns a random message id to uniquely identify a message.
+     *
+     * &lt;p&gt;Note: This is generated by a pseudo random number generator for
+     * illustration purpose, and is not guaranteed to be unique.
+     */
+    public String nextMessageId() {
+        return &quot;m-&quot; + UUID.randomUUID().toString();
     }
 
-    &#64;SuppressWarnings(&quot;unused&quot;)
-    public Packet toPacket() {
-      return new Message() {
-        // Must override toXML() because it includes a &lt;body&gt;
+    /**
+     * Sends a packet with contents provided.
+     */
+    protected void send(String jsonRequest) throws NotConnectedException {
+        Packet request = new GcmPacketExtension(jsonRequest).toPacket();
+        connection.sendPacket(request);
+    }
+
+    /**
+     * Handles an upstream data message from a device application.
+     *
+     * &lt;p&gt;This sample echo server sends an echo message back to the device.
+     * Subclasses should override this method to properly process upstream messages.
+     */
+    protected void handleUpstreamMessage(Map&lt;String, Object&gt; jsonObject) {
+        // PackageName of the application that sent this message.
+        String category = (String) jsonObject.get(&quot;category&quot;);
+        String from = (String) jsonObject.get(&quot;from&quot;);
+        &#64;SuppressWarnings(&quot;unchecked&quot;)
+        Map&lt;String, String&gt; payload = (Map&lt;String, String&gt;) jsonObject.get(&quot;data&quot;);
+        payload.put(&quot;ECHO&quot;, &quot;Application: &quot; + category);
+
+        // Send an ECHO response back
+        String echo = createJsonMessage(from, nextMessageId(), payload,
+                &quot;echo:CollapseKey&quot;, null, false);
+
+        try {
+            sendDownstreamMessage(echo);
+        } catch (NotConnectedException e) {
+            logger.log(Level.WARNING, &quot;Not connected anymore, echo message is
+                    not sent&quot;, e);
+        }
+    }
+
+    /**
+     * Handles an ACK.
+     *
+     * &lt;p&gt;Logs a {@code INFO} message, but subclasses could override it to
+     * properly handle ACKs.
+     */
+    protected void handleAckReceipt(Map&lt;String, Object&gt; jsonObject) {
+        String messageId = (String) jsonObject.get(&quot;message_id&quot;);
+        String from = (String) jsonObject.get(&quot;from&quot;);
+        logger.log(Level.INFO, &quot;handleAckReceipt() from: &quot; + from + &quot;,
+                messageId: &quot; + messageId);
+    }
+
+    /**
+     * Handles a NACK.
+     *
+     * &lt;p&gt;Logs a {@code INFO} message, but subclasses could override it to
+     * properly handle NACKs.
+     */
+    protected void handleNackReceipt(Map&lt;String, Object&gt; jsonObject) {
+        String messageId = (String) jsonObject.get(&quot;message_id&quot;);
+        String from = (String) jsonObject.get(&quot;from&quot;);
+        logger.log(Level.INFO, &quot;handleNackReceipt() from: &quot; + from + &quot;,
+                messageId: &quot; + messageId);
+    }
+
+    protected void handleControlMessage(Map&lt;String, Object&gt; jsonObject) {
+        logger.log(Level.INFO, &quot;handleControlMessage(): &quot; + jsonObject);
+        String controlType = (String) jsonObject.get(&quot;control_type&quot;);
+        if (&quot;CONNECTION_DRAINING&quot;.equals(controlType)) {
+            connectionDraining = true;
+        } else {
+            logger.log(Level.INFO, &quot;Unrecognized control type: %s. This could
+                    happen if new features are &quot; + &quot;added to the CCS protocol.&quot;,
+                    controlType);
+        }
+    }
+
+    /**
+     * Creates a JSON encoded GCM message.
+     *
+     * &#64;param to RegistrationId of the target device (Required).
+     * &#64;param messageId Unique messageId for which CCS will send an
+     *         &quot;ack/nack&quot; (Required).
+     * &#64;param payload Message content intended for the application. (Optional).
+     * &#64;param collapseKey GCM collapse_key parameter (Optional).
+     * &#64;param timeToLive GCM time_to_live parameter (Optional).
+     * &#64;param delayWhileIdle GCM delay_while_idle parameter (Optional).
+     * &#64;return JSON encoded GCM message.
+     */
+    public static String createJsonMessage(String to, String messageId,
+            Map&lt;String, String&gt; payload, String collapseKey, Long timeToLive,
+            Boolean delayWhileIdle) {
+        Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
+        message.put(&quot;to&quot;, to);
+        if (collapseKey != null) {
+            message.put(&quot;collapse_key&quot;, collapseKey);
+        }
+        if (timeToLive != null) {
+            message.put(&quot;time_to_live&quot;, timeToLive);
+        }
+        if (delayWhileIdle != null &amp;&amp; delayWhileIdle) {
+            message.put(&quot;delay_while_idle&quot;, true);
+        }
+      message.put(&quot;message_id&quot;, messageId);
+      message.put(&quot;data&quot;, payload);
+      return JSONValue.toJSONString(message);
+    }
+
+    /**
+     * Creates a JSON encoded ACK message for an upstream message received
+     * from an application.
+     *
+     * &#64;param to RegistrationId of the device who sent the upstream message.
+     * &#64;param messageId messageId of the upstream message to be acknowledged to CCS.
+     * &#64;return JSON encoded ack.
+     */
+        protected static String createJsonAck(String to, String messageId) {
+        Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
+        message.put(&quot;message_type&quot;, &quot;ack&quot;);
+        message.put(&quot;to&quot;, to);
+        message.put(&quot;message_id&quot;, messageId);
+        return JSONValue.toJSONString(message);
+    }
+
+    /**
+     * Connects to GCM Cloud Connection Server using the supplied credentials.
+     *
+     * &#64;param senderId Your GCM project number
+     * &#64;param apiKey API Key of your project
+     */
+    public void connect(long senderId, String apiKey)
+            throws XMPPException, IOException, SmackException {
+        ConnectionConfiguration config =
+                new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
+        config.setSecurityMode(SecurityMode.enabled);
+        config.setReconnectionAllowed(true);
+        config.setRosterLoadedAtLogin(false);
+        config.setSendPresence(false);
+        config.setSocketFactory(SSLSocketFactory.getDefault());
+
+        connection = new XMPPTCPConnection(config);
+        connection.connect();
+
+        connection.addConnectionListener(new LoggingConnectionListener());
+
+        // Handle incoming packets
+        connection.addPacketListener(new PacketListener() {
+
+            &#64;Override
+            public void processPacket(Packet packet) {
+                logger.log(Level.INFO, &quot;Received: &quot; + packet.toXML());
+                Message incomingMessage = (Message) packet;
+                GcmPacketExtension gcmPacket =
+                        (GcmPacketExtension) incomingMessage.
+                        getExtension(GCM_NAMESPACE);
+                String json = gcmPacket.getJson();
+                try {
+                    &#64;SuppressWarnings(&quot;unchecked&quot;)
+                    Map&lt;String, Object&gt; jsonObject =
+                            (Map&lt;String, Object&gt;) JSONValue.
+                            parseWithException(json);
+
+                    // present for &quot;ack&quot;/&quot;nack&quot;, null otherwise
+                    Object messageType = jsonObject.get(&quot;message_type&quot;);
+
+                    if (messageType == null) {
+                        // Normal upstream data message
+                        handleUpstreamMessage(jsonObject);
+
+                        // Send ACK to CCS
+                        String messageId = (String) jsonObject.get(&quot;message_id&quot;);
+                        String from = (String) jsonObject.get(&quot;from&quot;);
+                        String ack = createJsonAck(from, messageId);
+                        send(ack);
+                    } else if (&quot;ack&quot;.equals(messageType.toString())) {
+                          // Process Ack
+                          handleAckReceipt(jsonObject);
+                    } else if (&quot;nack&quot;.equals(messageType.toString())) {
+                          // Process Nack
+                          handleNackReceipt(jsonObject);
+                    } else if (&quot;control&quot;.equals(messageType.toString())) {
+                          // Process control message
+                          handleControlMessage(jsonObject);
+                    } else {
+                          logger.log(Level.WARNING,
+                                  &quot;Unrecognized message type (%s)&quot;,
+                                  messageType.toString());
+                    }
+                } catch (ParseException e) {
+                    logger.log(Level.SEVERE, &quot;Error parsing JSON &quot; + json, e);
+                } catch (Exception e) {
+                    logger.log(Level.SEVERE, &quot;Failed to process packet&quot;, e);
+                }
+            }
+        }, new PacketTypeFilter(Message.class));
+
+        // Log all outgoing packets
+        connection.addPacketInterceptor(new PacketInterceptor() {
+            &#64;Override
+                public void interceptPacket(Packet packet) {
+                    logger.log(Level.INFO, &quot;Sent: {0}&quot;, packet.toXML());
+                }
+            }, new PacketTypeFilter(Message.class));
+
+        connection.login(senderId + &quot;&#64;gcm.googleapis.com&quot;, apiKey);
+    }
+
+    public static void main(String[] args) throws Exception {
+        final long senderId = 1234567890L; // your GCM sender id
+        final String password = &quot;Your API key&quot;;
+
+        SmackCcsClient ccsClient = new SmackCcsClient();
+
+        ccsClient.connect(senderId, password);
+
+        // Send a sample hello downstream message to a device.
+        String toRegId = &quot;RegistrationIdOfTheTargetDevice&quot;;
+        String messageId = ccsClient.nextMessageId();
+        Map&lt;String, String&gt; payload = new HashMap&lt;String, String&gt;();
+        payload.put(&quot;Hello&quot;, &quot;World&quot;);
+        payload.put(&quot;CCS&quot;, &quot;Dummy Message&quot;);
+        payload.put(&quot;EmbeddedMessageId&quot;, messageId);
+        String collapseKey = &quot;sample&quot;;
+        Long timeToLive = 10000L;
+        String message = createJsonMessage(toRegId, messageId, payload,
+                collapseKey, timeToLive, true);
+
+        ccsClient.sendDownstreamMessage(message);
+    }
+
+    /**
+     * XMPP Packet Extension for GCM Cloud Connection Server.
+     */
+    private static final class GcmPacketExtension extends DefaultPacketExtension {
+
+        private final String json;
+
+        public GcmPacketExtension(String json) {
+            super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
+            this.json = json;
+        }
+
+        public String getJson() {
+            return json;
+        }
+
         &#64;Override
         public String toXML() {
-
-          StringBuilder buf = new StringBuilder();
-          buf.append(&quot;&lt;message&quot;);
-          if (getXmlns() != null) {
-            buf.append(&quot; xmlns=\&quot;&quot;).append(getXmlns()).append(&quot;\&quot;&quot;);
-          }
-          if (getLanguage() != null) {
-            buf.append(&quot; xml:lang=\&quot;&quot;).append(getLanguage()).append(&quot;\&quot;&quot;);
-          }
-          if (getPacketID() != null) {
-            buf.append(&quot; id=\&quot;&quot;).append(getPacketID()).append(&quot;\&quot;&quot;);
-          }
-          if (getTo() != null) {
-            buf.append(&quot; to=\&quot;&quot;).append(StringUtils.escapeForXML(getTo())).append(&quot;\&quot;&quot;);
-          }
-          if (getFrom() != null) {
-            buf.append(&quot; from=\&quot;&quot;).append(StringUtils.escapeForXML(getFrom())).append(&quot;\&quot;&quot;);
-          }
-          buf.append(&quot;&gt;&quot;);
-          buf.append(GcmPacketExtension.this.toXML());
-          buf.append(&quot;&lt;/message&gt;&quot;);
-          return buf.toString();
+            return String.format(&quot;&lt;%s xmlns=\&quot;%s\&quot;&gt;%s&lt;/%s&gt;&quot;,
+                    GCM_ELEMENT_NAME, GCM_NAMESPACE,
+                    StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
         }
-      };
-    }
-  }
 
-  public SmackCcsClient() {
-    // Add GcmPacketExtension
-    ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
-        GCM_NAMESPACE, new PacketExtensionProvider() {
-
-      &#64;Override
-      public PacketExtension parseExtension(XmlPullParser parser)
-          throws Exception {
-        String json = parser.nextText();
-        GcmPacketExtension packet = new GcmPacketExtension(json);
-        return packet;
-      }
-    });
-  }
-
-  /**
-   * Returns a random message id to uniquely identify a message.
-   *
-   * &lt;p&gt;Note:
-   * This is generated by a pseudo random number generator for illustration purpose,
-   * and is not guaranteed to be unique.
-   *
-   */
-  public String getRandomMessageId() {
-    return &quot;m-&quot; + Long.toString(random.nextLong());
-  }
-
-  /**
-   * Sends a downstream GCM message.
-   */
-  public void send(String jsonRequest) {
-    Packet request = new GcmPacketExtension(jsonRequest).toPacket();
-    connection.sendPacket(request);
-  }
-
-  /**
-   * Handles an upstream data message from a device application.
-   *
-   * &lt;p&gt;This sample echo server sends an echo message back to the device.
-   * Subclasses should override this method to process an upstream message.
-   */
-  public void handleIncomingDataMessage(Map&lt;String, Object&gt; jsonObject) {
-    String from = jsonObject.get(&quot;from&quot;).toString();
-
-    // PackageName of the application that sent this message.
-    String category = jsonObject.get(&quot;category&quot;).toString();
-
-    // Use the packageName as the collapseKey in the echo packet
-    String collapseKey = &quot;echo:CollapseKey&quot;;
-    &#64;SuppressWarnings(&quot;unchecked&quot;)
-    Map&lt;String, String&gt; payload = (Map&lt;String, String&gt;) jsonObject.get(&quot;data&quot;);
-    payload.put(&quot;ECHO&quot;, &quot;Application: &quot; + category);
-
-    // Send an ECHO response back
-    String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false);
-    send(echo);
-  }
-
-  /**
-   * Handles an ACK.
-   *
-   * &lt;p&gt;By default, it only logs a {@code INFO} message, but subclasses could override it to
-   * properly handle ACKS.
-   */
-  public void handleAckReceipt(Map&lt;String, Object&gt; jsonObject) {
-    String messageId = jsonObject.get(&quot;message_id&quot;).toString();
-    String from = jsonObject.get(&quot;from&quot;).toString();
-    logger.log(Level.INFO, &quot;handleAckReceipt() from: &quot; + from + &quot;, messageId: &quot; + messageId);
-  }
-
-  /**
-   * Handles a NACK.
-   *
-   * &lt;p&gt;By default, it only logs a {@code INFO} message, but subclasses could override it to
-   * properly handle NACKS.
-   */
-  public void handleNackReceipt(Map&lt;String, Object&gt; jsonObject) {
-    String messageId = jsonObject.get(&quot;message_id&quot;).toString();
-    String from = jsonObject.get(&quot;from&quot;).toString();
-    logger.log(Level.INFO, &quot;handleNackReceipt() from: &quot; + from + &quot;, messageId: &quot; + messageId);
-  }
-
-  /**
-   * Creates a JSON encoded GCM message.
-   *
-   * &#64;param to RegistrationId of the target device (Required).
-   * &#64;param messageId Unique messageId for which CCS will send an &quot;ack/nack&quot; (Required).
-   * &#64;param payload Message content intended for the application. (Optional).
-   * &#64;param collapseKey GCM collapse_key parameter (Optional).
-   * &#64;param timeToLive GCM time_to_live parameter (Optional).
-   * &#64;param delayWhileIdle GCM delay_while_idle parameter (Optional).
-   * &#64;return JSON encoded GCM message.
-   */
-  public static String createJsonMessage(String to, String messageId, Map&lt;String, String&gt; payload,
-      String collapseKey, Long timeToLive, Boolean delayWhileIdle) {
-    Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
-    message.put(&quot;to&quot;, to);
-    if (collapseKey != null) {
-      message.put(&quot;collapse_key&quot;, collapseKey);
-    }
-    if (timeToLive != null) {
-      message.put(&quot;time_to_live&quot;, timeToLive);
-    }
-    if (delayWhileIdle != null &amp;&amp; delayWhileIdle) {
-      message.put(&quot;delay_while_idle&quot;, true);
-    }
-    message.put(&quot;message_id&quot;, messageId);
-    message.put(&quot;data&quot;, payload);
-    return JSONValue.toJSONString(message);
-  }
-
-  /**
-   * Creates a JSON encoded ACK message for an upstream message received from an application.
-   *
-   * &#64;param to RegistrationId of the device who sent the upstream message.
-   * &#64;param messageId messageId of the upstream message to be acknowledged to CCS.
-   * &#64;return JSON encoded ack.
-   */
-  public static String createJsonAck(String to, String messageId) {
-    Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
-    message.put(&quot;message_type&quot;, &quot;ack&quot;);
-    message.put(&quot;to&quot;, to);
-    message.put(&quot;message_id&quot;, messageId);
-    return JSONValue.toJSONString(message);
-  }
-
-  /**
-   * Connects to GCM Cloud Connection Server using the supplied credentials.
-   *
-   * &#64;param username GCM_SENDER_ID&#64;gcm.googleapis.com
-   * &#64;param password API Key
-   * &#64;throws XMPPException
-   */
-  public void connect(String username, String password) throws XMPPException {
-    config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
-    config.setSecurityMode(SecurityMode.enabled);
-    config.setReconnectionAllowed(true);
-    config.setRosterLoadedAtLogin(false);
-    config.setSendPresence(false);
-    config.setSocketFactory(SSLSocketFactory.getDefault());
-
-    // NOTE: Set to true to launch a window with information about packets sent and received
-    config.setDebuggerEnabled(true);
-
-    // -Dsmack.debugEnabled=true
-    XMPPConnection.DEBUG_ENABLED = true;
-
-    connection = new XMPPConnection(config);
-    connection.connect();
-
-    connection.addConnectionListener(new ConnectionListener() {
-
-      &#64;Override
-      public void reconnectionSuccessful() {
-        logger.info(&quot;Reconnecting..&quot;);
-      }
-
-      &#64;Override
-      public void reconnectionFailed(Exception e) {
-        logger.log(Level.INFO, &quot;Reconnection failed.. &quot;, e);
-      }
-
-      &#64;Override
-      public void reconnectingIn(int seconds) {
-        logger.log(Level.INFO, &quot;Reconnecting in %d secs&quot;, seconds);
-      }
-
-      &#64;Override
-      public void connectionClosedOnError(Exception e) {
-        logger.log(Level.INFO, &quot;Connection closed on error.&quot;);
-      }
-
-      &#64;Override
-      public void connectionClosed() {
-        logger.info(&quot;Connection closed.&quot;);
-      }
-    });
-
-    // Handle incoming packets
-    connection.addPacketListener(new PacketListener() {
-
-      &#64;Override
-      public void processPacket(Packet packet) {
-        logger.log(Level.INFO, &quot;Received: &quot; + packet.toXML());
-        Message incomingMessage = (Message) packet;
-        GcmPacketExtension gcmPacket =
-            (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE);
-        String json = gcmPacket.getJson();
-        try {
-          &#64;SuppressWarnings(&quot;unchecked&quot;)
-          Map&lt;String, Object&gt; jsonObject =
-              (Map&lt;String, Object&gt;) JSONValue.parseWithException(json);
-
-          // present for &quot;ack&quot;/&quot;nack&quot;, null otherwise
-          Object messageType = jsonObject.get(&quot;message_type&quot;);
-
-          if (messageType == null) {
-            // Normal upstream data message
-            handleIncomingDataMessage(jsonObject);
-
-            // Send ACK to CCS
-            String messageId = jsonObject.get(&quot;message_id&quot;).toString();
-            String from = jsonObject.get(&quot;from&quot;).toString();
-            String ack = createJsonAck(from, messageId);
-            send(ack);
-          } else if (&quot;ack&quot;.equals(messageType.toString())) {
-            // Process Ack
-            handleAckReceipt(jsonObject);
-          } else if (&quot;nack&quot;.equals(messageType.toString())) {
-            // Process Nack
-            handleNackReceipt(jsonObject);
-          } else {
-            logger.log(Level.WARNING, &quot;Unrecognized message type (%s)&quot;,
-                messageType.toString());
-          }
-        } catch (ParseException e) {
-          logger.log(Level.SEVERE, &quot;Error parsing JSON &quot; + json, e);
-        } catch (Exception e) {
-          logger.log(Level.SEVERE, &quot;Couldn't send echo.&quot;, e);
+        public Packet toPacket() {
+            Message message = new Message();
+            message.addExtension(this);
+            return message;
         }
-      }
-    }, new PacketTypeFilter(Message.class));
-
-
-    // Log all outgoing packets
-    connection.addPacketInterceptor(new PacketInterceptor() {
-      &#64;Override
-      public void interceptPacket(Packet packet) {
-        logger.log(Level.INFO, &quot;Sent: {0}&quot;,  packet.toXML());
-      }
-    }, new PacketTypeFilter(Message.class));
-
-    connection.login(username, password);
-  }
-
-  public static void main(String [] args) {
-    final String userName = &quot;Your GCM Sender Id&quot; + &quot;&#64;gcm.googleapis.com&quot;;
-    final String password = &quot;API Key&quot;;
-
-    SmackCcsClient ccsClient = new SmackCcsClient();
-
-    try {
-      ccsClient.connect(userName, password);
-    } catch (XMPPException e) {
-      e.printStackTrace();
     }
 
-    // Send a sample hello downstream message to a device.
-    String toRegId = &quot;RegistrationIdOfTheTargetDevice&quot;;
-    String messageId = ccsClient.getRandomMessageId();
-    Map&lt;String, String&gt; payload = new HashMap&lt;String, String&gt;();
-    payload.put(&quot;Hello&quot;, &quot;World&quot;);
-    payload.put(&quot;CCS&quot;, &quot;Dummy Message&quot;);
-    payload.put(&quot;EmbeddedMessageId&quot;, messageId);
-    String collapseKey = &quot;sample&quot;;
-    Long timeToLive = 10000L;
-    Boolean delayWhileIdle = true;
-    ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey,
-        timeToLive, delayWhileIdle));
-  }
+    private static final class LoggingConnectionListener
+            implements ConnectionListener {
+
+        &#64;Override
+        public void connected(XMPPConnection xmppConnection) {
+            logger.info(&quot;Connected.&quot;);
+        }
+
+        &#64;Override
+        public void authenticated(XMPPConnection xmppConnection) {
+            logger.info(&quot;Authenticated.&quot;);
+        }
+
+        &#64;Override
+        public void reconnectionSuccessful() {
+            logger.info(&quot;Reconnecting..&quot;);
+        }
+
+        &#64;Override
+        public void reconnectionFailed(Exception e) {
+            logger.log(Level.INFO, &quot;Reconnection failed.. &quot;, e);
+        }
+
+        &#64;Override
+        public void reconnectingIn(int seconds) {
+            logger.log(Level.INFO, &quot;Reconnecting in %d secs&quot;, seconds);
+        }
+
+        &#64;Override
+        public void connectionClosedOnError(Exception e) {
+            logger.info(&quot;Connection closed on error.&quot;);
+        }
+
+        &#64;Override
+        public void connectionClosed() {
+            logger.info(&quot;Connection closed.&quot;);
+        }
+    }
 }</pre>
+
 <h3 id="python">Python sample</h3>
 
 <p>Here is an example of a CCS app server written in Python. This sample echo
diff --git a/docs/html/google/play/billing/billing_overview.jd b/docs/html/google/play/billing/billing_overview.jd
index 301d911..12f8c9a 100644
--- a/docs/html/google/play/billing/billing_overview.jd
+++ b/docs/html/google/play/billing/billing_overview.jd
@@ -38,6 +38,16 @@
 features that you need to understand in order to add In-app 
 Billing features into your application.</p>
 
+<p class="note"><b>Note</b>: Ensure that you comply with applicable laws in the countries where you
+distribute apps. For example, in EU countries, laws based on the
+<a href="http://ec.europa.eu/justice/consumer-marketing/unfair-trade/unfair-practices/">Unfair
+Commercial Practices Directive</a> prohibit direct exhortations to children to buy advertised
+products or to persuade their parents or other adults to buy advertised products for them.
+See the
+<a href="http://ec.europa.eu/consumers/enforcement/docs/common_position_on_online_games_en.pdf">position
+of the EU consumer protection authorities</a> for more information on this and other topics.
+</p>
+
 <h2 id="api">In-app Billing API</h2>
 <p>Your application accesses the In-app Billing service using an API that is 
 exposed by the Google Play app that is installed on the device. The Google Play 
diff --git a/docs/html/tools/sdk/ndk/index.jd b/docs/html/tools/sdk/ndk/index.jd
index b77a72a..71b15d5 100644
--- a/docs/html/tools/sdk/ndk/index.jd
+++ b/docs/html/tools/sdk/ndk/index.jd
@@ -349,8 +349,7 @@
 <input id="agree" type="checkbox" name="agree" value="1" onclick="onAgreeChecked()" />
 <label id="agreeLabel" for="agree">I have read and agree with the above terms and conditions</label>
 </p>
-<p><a href="" class="button disabled ndk" id="downloadForRealz" onclick="return
-onDownloadNdkForRealz(this);"></a></p>
+<p><a href="" class="button disabled ndk" id="downloadForRealz" onclick="return onDownloadNdkForRealz(this);"></a></p>
 </div>
 
 
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index 485b38a..2cb7b03 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -16,8 +16,6 @@
 
 package android.graphics.drawable;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.annotation.NonNull;
@@ -26,6 +24,7 @@
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.LongSparseLongArray;
 import android.util.SparseIntArray;
 import android.util.StateSet;
@@ -62,6 +61,8 @@
  * @attr ref android.R.styleable#DrawableStates_state_pressed
  */
 public class AnimatedStateListDrawable extends StateListDrawable {
+    private static final String LOGTAG = AnimatedStateListDrawable.class.getSimpleName();
+
     private static final String ELEMENT_TRANSITION = "transition";
     private static final String ELEMENT_ITEM = "item";
 
@@ -157,48 +158,51 @@
     }
 
     private boolean selectTransition(int toIndex) {
-        if (toIndex == mTransitionToIndex) {
-            // Already animating to that keyframe.
-            return true;
-        }
-
+        final int fromIndex;
         final Transition currentTransition = mTransition;
         if (currentTransition != null) {
             if (toIndex == mTransitionToIndex) {
+                // Already animating to that keyframe.
                 return true;
-            } else if (toIndex == mTransitionFromIndex) {
+            } else if (toIndex == mTransitionFromIndex && currentTransition.canReverse()) {
                 // Reverse the current animation.
                 currentTransition.reverse();
-                mTransitionFromIndex = mTransitionToIndex;
-                mTransitionToIndex = toIndex;
+                mTransitionToIndex = mTransitionFromIndex;
+                mTransitionFromIndex = toIndex;
                 return true;
             }
 
+            // Start the next transition from the end of the current one.
+            fromIndex = mTransitionToIndex;
+
             // Changing animation, end the current animation.
             currentTransition.stop();
-            mTransition = null;
+        } else {
+            fromIndex = getCurrentIndex();
         }
 
         // Reset state.
+        mTransition = null;
         mTransitionFromIndex = -1;
         mTransitionToIndex = -1;
 
         final AnimatedStateListState state = mState;
-        final int fromIndex = getCurrentIndex();
         final int fromId = state.getKeyframeIdAt(fromIndex);
         final int toId = state.getKeyframeIdAt(toIndex);
-
         if (toId == 0 || fromId == 0) {
             // Missing a keyframe ID.
             return false;
         }
 
         final int transitionIndex = state.indexOfTransition(fromId, toId);
-        if (transitionIndex < 0 || !selectDrawable(transitionIndex)) {
+        if (transitionIndex < 0) {
             // Couldn't select a transition.
             return false;
         }
 
+        // This may fail if we're already on the transition, but that's okay!
+        selectDrawable(transitionIndex);
+
         final Transition transition;
         final Drawable d = getCurrent();
         if (d instanceof AnimationDrawable) {
@@ -302,13 +306,13 @@
 
         @Override
         public boolean canReverse() {
-            return true;
+            return mAvd.canReverse();
         }
 
         @Override
         public void start() {
             if (mReversed) {
-                mAvd.reverse();
+                reverse();
             } else {
                 mAvd.start();
             }
@@ -316,7 +320,11 @@
 
         @Override
         public void reverse() {
-            mAvd.reverse();
+            if (canReverse()) {
+                mAvd.reverse();
+            } else {
+                Log.w(LOGTAG, "Reverse() is called on a drawable can't reverse");
+            }
         }
 
         @Override
@@ -493,17 +501,6 @@
         return this;
     }
 
-    private final AnimatorListenerAdapter mAnimListener = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator anim) {
-            selectDrawable(mTransitionToIndex);
-
-            mTransitionToIndex = -1;
-            mTransitionFromIndex = -1;
-            mTransition = null;
-        }
-    };
-
     static class AnimatedStateListState extends StateListState {
         private static final int REVERSE_SHIFT = 32;
         private static final int REVERSE_MASK = 0x1;
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 071d8ba4..11c2571 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -337,15 +337,33 @@
      * Reverses ongoing animations or starts pending animations in reverse.
      * <p>
      * NOTE: Only works of all animations are ValueAnimators.
+     * @hide
      */
-    void reverse() {
+    public void reverse() {
         final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
         final int size = animators.size();
         for (int i = 0; i < size; i++) {
             final Animator animator = animators.get(i);
-            if (animator instanceof ValueAnimator) {
-                ((ValueAnimator) animator).reverse();
+            if (animator.canReverse()) {
+                animator.reverse();
+            } else {
+                Log.w(LOGTAG, "AnimatedVectorDrawable can't reverse()");
             }
         }
     }
+
+    /**
+     * @hide
+     */
+    public boolean canReverse() {
+        final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
+        final int size = animators.size();
+        for (int i = 0; i < size; i++) {
+            final Animator animator = animators.get(i);
+            if (!animator.canReverse()) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 54fa143..8cc65b2 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -173,9 +173,6 @@
     // UI thread only!
     ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
 
-protected:
-    virtual void damageSelf(TreeInfo& info);
-
 private:
     typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
 
@@ -250,6 +247,7 @@
     void prepareLayer(TreeInfo& info);
     void pushLayerUpdate(TreeInfo& info);
     void deleteDisplayListData();
+    void damageSelf(TreeInfo& info);
 
     void incParentRefCount() { mParentCount++; }
     void decParentRefCount();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 57279b7..a4ac262 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -185,6 +185,11 @@
     } else if (!mDirtyRegionsEnabled || mHaveNewSurface) {
         dirty.setEmpty();
     } else {
+        if (!dirty.intersect(0, 0, width, height)) {
+            ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?",
+                    SK_RECT_ARGS(dirty), width, height);
+            dirty.setEmpty();
+        }
         profiler().unionDirty(&dirty);
     }
 
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 3336694..7d1de24 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -2249,12 +2249,12 @@
             }
 
             @Override
-            public void onAdjustVolumeBy(final int delta) {
+            public void onAdjustVolume(final int direction) {
                 sStatic.mHandler.post(new Runnable() {
                     @Override
                     public void run() {
                         if (mVcb != null) {
-                            mVcb.vcb.onVolumeUpdateRequest(mVcb.route, delta);
+                            mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
                         }
                     }
                 });
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index d151e66..9bda1d4 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -32,14 +32,14 @@
 
     /**
      * The volume control uses relative adjustment via
-     * {@link #onAdjustVolumeBy(int)}. Attempts to set the volume to a specific
+     * {@link #onAdjustVolume(int)}. Attempts to set the volume to a specific
      * value should be ignored.
      */
     public static final int VOLUME_CONTROL_RELATIVE = 1;
 
     /**
      * The volume control uses an absolute value. It may be adjusted using
-     * {@link #onAdjustVolumeBy(int)} or set directly using
+     * {@link #onAdjustVolume(int)} or set directly using
      * {@link #onSetVolumeTo(int)}.
      */
     public static final int VOLUME_CONTROL_ABSOLUTE = 2;
@@ -104,12 +104,13 @@
     }
 
     /**
-     * Override to handle requests to adjust the volume of the current
-     * output.
-     *
-     * @param delta The amount to change the volume
+     * Override to handle requests to adjust the volume of the current output.
+     * Direction will be one of {@link AudioManager#ADJUST_LOWER},
+     * {@link AudioManager#ADJUST_RAISE}, {@link AudioManager#ADJUST_SAME}.
+     * 
+     * @param direction The direction to change the volume in.
      */
-    public void onAdjustVolumeBy(int delta) {
+    public void onAdjustVolume(int direction) {
     }
 
     /**
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index e554e27..39391b6 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -39,6 +39,6 @@
     void onRate(in Rating rating);
 
     // These callbacks are for volume handling
-    void onAdjustVolumeBy(int delta);
+    void onAdjustVolume(int direction);
     void onSetVolumeTo(int value);
 }
diff --git a/media/java/android/media/session/ISessionController.aidl b/media/java/android/media/session/ISessionController.aidl
index 6cf5ef2..b555220 100644
--- a/media/java/android/media/session/ISessionController.aidl
+++ b/media/java/android/media/session/ISessionController.aidl
@@ -41,7 +41,7 @@
     MediaSessionInfo getSessionInfo();
     long getFlags();
     ParcelableVolumeInfo getVolumeAttributes();
-    void adjustVolumeBy(int delta, int flags);
+    void adjustVolume(int direction, int flags);
     void setVolumeTo(int value, int flags);
 
     IMediaRouterDelegate createMediaRouterDelegate(IMediaRouterStateCallback callback);
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index dce84d4..95c2d61 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -31,7 +31,7 @@
     ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
     List<IBinder> getSessions(in ComponentName compName, int userId);
     void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock);
-    void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags);
+    void dispatchAdjustVolume(int suggestedStream, int delta, int flags);
     void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
             int userId);
     void removeSessionsListener(in IActiveSessionsListener listener);
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index cc8b31a..7fedd82 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -66,28 +66,27 @@
 
     private final TransportControls mTransportControls;
 
-    private MediaController(ISessionController sessionBinder) {
+    /**
+     * Call for creating a MediaController directly from a binder. Should only
+     * be used by framework code.
+     *
+     * @hide
+     */
+    public MediaController(ISessionController sessionBinder) {
+        if (sessionBinder == null) {
+            throw new IllegalArgumentException("Session token cannot be null");
+        }
         mSessionBinder = sessionBinder;
         mTransportControls = new TransportControls();
     }
 
     /**
-     * @hide
-     */
-    public static MediaController fromBinder(ISessionController sessionBinder) {
-        return new MediaController(sessionBinder);
-    }
-
-    /**
-     * Get a new media controller from a session token which may have
-     * been obtained from another process.  If successful the controller returned
-     * will be connected to the session that generated the token.
+     * Create a new MediaController from a session's token.
      *
-     * @param token The session token to control.
-     * @return A controller for the session or null if inaccessible.
+     * @param token The token for the session.
      */
-    public static MediaController fromToken(@NonNull MediaSession.Token token) {
-        return fromBinder(token.getBinder());
+    public MediaController(@NonNull MediaSession.Token token) {
+        this(token.getBinder());
     }
 
     /**
@@ -235,19 +234,21 @@
     }
 
     /**
-     * Adjust the volume of the stream or output this session is playing on.
-     * Negative values will lower the volume. The command will be ignored if it
-     * does not support {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
+     * Adjust the volume of the stream or output this session is playing on. The
+     * direction must be one of {@link AudioManager#ADJUST_LOWER},
+     * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
+     * The command will be ignored if the session does not support
+     * {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
      * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
      * {@link AudioManager} may be used to affect the handling.
      *
      * @see #getVolumeInfo()
-     * @param delta The number of steps to adjust the volume by.
+     * @param direction The direction to adjust the volume in.
      * @param flags Any flags to pass with the command.
      */
-    public void adjustVolumeBy(int delta, int flags) {
+    public void adjustVolume(int direction, int flags) {
         try {
-            mSessionBinder.adjustVolumeBy(delta, flags);
+            mSessionBinder.adjustVolume(direction, flags);
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
         }
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 34997bd..4841360 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.media.AudioManager;
 import android.media.MediaMetadata;
@@ -37,10 +38,13 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.telephony.DctConstants.Activity;
+
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -59,8 +63,9 @@
  * create a {@link MediaController} to interact with the session.
  * <p>
  * To receive commands, media keys, and other events a {@link Callback} must be
- * set with {@link #addCallback(Callback)}. To receive transport control
- * commands a {@link TransportControlsCallback} must be set with
+ * set with {@link #addCallback(Callback)} and {@link #setActive(boolean)
+ * setActive(true)} must be called. To receive transport control commands a
+ * {@link TransportControlsCallback} must be set with
  * {@link #addTransportControlsCallback}.
  * <p>
  * When an app is finished performing playback it must call {@link #release()}
@@ -119,18 +124,45 @@
     private boolean mActive = false;
 
     /**
+     * Creates a new session. The session will automatically be registered with
+     * the system but will not be published until {@link #setActive(boolean)
+     * setActive(true)} is called. You must call {@link #release()} when
+     * finished with the session.
+     *
+     * @param context The context to use to create the session.
+     * @param tag A short name for debugging purposes.
+     */
+    public MediaSession(@NonNull Context context, @NonNull String tag) {
+        this(context, tag, UserHandle.myUserId());
+    }
+
+    /**
+     * Creates a new session as the specified user. To create a session as a
+     * user other than your own you must hold the
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+     * permission.
+     *
+     * @param context The context to use to create the session.
+     * @param tag A short name for debugging purposes.
+     * @param userId The user id to create the session as.
      * @hide
      */
-    public MediaSession(ISession binder, CallbackStub cbStub) {
-        mBinder = binder;
-        mCbStub = cbStub;
-        ISessionController controllerBinder = null;
-        try {
-            controllerBinder = mBinder.getController();
-        } catch (RemoteException e) {
-            throw new RuntimeException("Dead object in MediaSessionController constructor: ", e);
+    public MediaSession(@NonNull Context context, @NonNull String tag, int userId) {
+        if (context == null) {
+            throw new IllegalArgumentException("context cannot be null.");
         }
-        mSessionToken = new Token(controllerBinder);
+        if (TextUtils.isEmpty(tag)) {
+            throw new IllegalArgumentException("tag cannot be null or empty");
+        }
+        mCbStub = new CallbackStub();
+        MediaSessionManager manager = (MediaSessionManager) context
+                .getSystemService(Context.MEDIA_SESSION_SERVICE);
+        try {
+            mBinder = manager.createSession(mCbStub, tag, userId);
+            mSessionToken = new Token(mBinder.getController());
+        } catch (RemoteException e) {
+            throw new RuntimeException("Remote error creating session.", e);
+        }
     }
 
     /**
@@ -837,11 +869,11 @@
         }
 
         @Override
-        public void onAdjustVolumeBy(int delta) {
+        public void onAdjustVolume(int direction) {
             MediaSession session = mMediaSession.get();
             if (session != null) {
                 if (session.mVolumeProvider != null) {
-                    session.mVolumeProvider.onAdjustVolumeBy(delta);
+                    session.mVolumeProvider.onAdjustVolume(direction);
                 }
             }
         }
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index 11f7720..da1a6ed 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -46,6 +47,7 @@
     private static final Object sLock = new Object();
     private static MediaSessionLegacyHelper sInstance;
 
+    private Context mContext;
     private MediaSessionManager mSessionManager;
     private Handler mHandler = new Handler(Looper.getMainLooper());
     // The legacy APIs use PendingIntents to register/unregister media button
@@ -54,6 +56,7 @@
             = new ArrayMap<PendingIntent, SessionHolder>();
 
     private MediaSessionLegacyHelper(Context context) {
+        mContext = context;
         mSessionManager = (MediaSessionManager) context
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
     }
@@ -206,13 +209,13 @@
                 }
             }
 
-            mSessionManager.dispatchAdjustVolumeBy(AudioManager.USE_DEFAULT_STREAM_TYPE,
+            mSessionManager.dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
                     direction, flags);
         }
     }
 
     public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
-        mSessionManager.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+        mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags);
         if (DEBUG) {
             Log.d(TAG, "dispatched volume adjustment");
         }
@@ -225,6 +228,9 @@
             return;
         }
         SessionHolder holder = getHolder(pi, true);
+        if (holder == null) {
+            return;
+        }
         if (holder.mRccListener != null) {
             if (holder.mRccListener == listener) {
                 if (DEBUG) {
@@ -270,6 +276,9 @@
             return;
         }
         SessionHolder holder = getHolder(pi, true);
+        if (holder == null) {
+            return;
+        }
         if (holder.mMediaButtonListener != null) {
             // Already have this listener registered, but update it anyway as
             // the extras may have changed.
@@ -316,7 +325,8 @@
     private SessionHolder getHolder(PendingIntent pi, boolean createIfMissing) {
         SessionHolder holder = mSessions.get(pi);
         if (holder == null && createIfMissing) {
-            MediaSession session = mSessionManager.createSession(TAG);
+            MediaSession session;
+            session = new MediaSession(mContext, TAG);
             session.setActive(true);
             holder = new SessionHolder(session, pi);
             mSessions.put(pi, holder);
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index c477406..824b397 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.media.AudioManager;
 import android.media.IRemoteVolumeController;
 import android.media.session.ISessionManager;
 import android.os.IBinder;
@@ -64,42 +65,15 @@
     }
 
     /**
-     * Creates a new session.
+     * Create a new session in the system and get the binder for it.
      *
      * @param tag A short name for debugging purposes.
-     * @return A {@link MediaSession} for the new session.
-     */
-    public @NonNull MediaSession createSession(@NonNull String tag) {
-        return createSessionAsUser(tag, UserHandle.myUserId());
-    }
-
-    /**
-     * Creates a new session as the specified user. To create a session as a
-     * user other than your own you must hold the
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
-     * permission.
-     *
-     * @param tag A short name for debugging purposes.
-     * @param userId The user id to create the session as.
-     * @return A {@link MediaSession} for the new session.
+     * @return The binder object from the system
      * @hide
      */
-    public @NonNull MediaSession createSessionAsUser(@NonNull String tag, int userId) {
-        if (TextUtils.isEmpty(tag)) {
-            throw new IllegalArgumentException("tag must not be null or empty");
-        }
-
-        try {
-            MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub();
-            MediaSession session = new MediaSession(mService
-                    .createSession(mContext.getPackageName(), cbStub, tag, userId), cbStub);
-            cbStub.setMediaSession(session);
-
-            return session;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to create session: ", e);
-            return null;
-        }
+    public @NonNull ISession createSession(@NonNull MediaSession.CallbackStub cbStub,
+            @NonNull String tag, int userId) throws RemoteException {
+        return mService.createSession(mContext.getPackageName(), cbStub, tag, userId);
     }
 
     /**
@@ -142,7 +116,7 @@
             List<IBinder> binders = mService.getSessions(notificationListener, userId);
             int size = binders.size();
             for (int i = 0; i < size; i++) {
-                MediaController controller = MediaController.fromBinder(ISessionController.Stub
+                MediaController controller = new MediaController(ISessionController.Stub
                         .asInterface(binders.get(i)));
                 controllers.add(controller);
             }
@@ -256,17 +230,19 @@
 
     /**
      * Dispatch an adjust volume request to the system. It will be sent to the
-     * most relevant audio stream or media session.
+     * most relevant audio stream or media session. The direction must be one of
+     * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
+     * {@link AudioManager#ADJUST_SAME}.
      *
      * @param suggestedStream The stream to fall back to if there isn't a
      *            relevant stream
-     * @param delta The amount to adjust the volume by.
+     * @param direction The direction to adjust volume in.
      * @param flags Any flags to include with the volume change.
      * @hide
      */
-    public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags) {
+    public void dispatchAdjustVolume(int suggestedStream, int direction, int flags) {
         try {
-            mService.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+            mService.dispatchAdjustVolume(suggestedStream, direction, flags);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to send adjust volume.", e);
         }
@@ -295,7 +271,7 @@
                 ArrayList<MediaController> controllers = new ArrayList<MediaController>();
                 int size = tokens.size();
                 for (int i = 0; i < size; i++) {
-                    controllers.add(MediaController.fromToken(tokens.get(i)));
+                    controllers.add(new MediaController(tokens.get(i)));
                 }
                 SessionListener.this.onActiveSessionsChanged(controllers);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index b32d3dd..78a99e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -456,9 +456,11 @@
                 } else {
                     // Launch the activity anew with the desired animation
                     Intent i = new Intent(task.key.baseIntent);
-                    i.setFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
-                            | Intent.FLAG_ACTIVITY_TASK_ON_HOME
-                            | Intent.FLAG_ACTIVITY_NEW_TASK);
+                    i.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
+                            | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+                    if ((i.getFlags() & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) == 0) {
+                        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    }
                     try {
                         UserHandle taskUser = new UserHandle(task.userId);
                         if (launchOpts != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 1bb36dd..8b5ff67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -27,11 +27,12 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
+import android.telecomm.TelecommConstants;
+import android.telecomm.TelecommManager;
 import android.util.Log;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.cdma.TtyIntent;
 import com.android.systemui.R;
 
 /**
@@ -89,7 +90,7 @@
             else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
                 updateSimState(intent);
             }
-            else if (action.equals(TtyIntent.TTY_ENABLED_CHANGE_ACTION)) {
+            else if (action.equals(TelecommConstants.ACTION_CURRENT_TTY_MODE_CHANGED)) {
                 updateTTY(intent);
             }
             else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
@@ -110,7 +111,7 @@
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-        filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
+        filter.addAction(TelecommConstants.ACTION_CURRENT_TTY_MODE_CHANGED);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
 
@@ -269,7 +270,9 @@
     }
 
     private final void updateTTY(Intent intent) {
-        final boolean enabled = intent.getBooleanExtra(TtyIntent.TTY_ENABLED, false);
+        int currentTtyMode = intent.getIntExtra(TelecommConstants.EXTRA_CURRENT_TTY_MODE,
+                TelecommConstants.TTY_MODE_OFF);
+        boolean enabled = currentTtyMode != TelecommConstants.TTY_MODE_OFF;
 
         if (DEBUG) Log.v(TAG, "updateTTY: enabled: " + enabled);
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 375f94c..e38c2ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -189,7 +189,7 @@
         @Override
         public void remoteVolumeChanged(ISessionController binder, int flags)
                 throws RemoteException {
-            MediaController controller = MediaController.fromBinder(binder);
+            MediaController controller = new MediaController(binder);
             mPanel.postRemoteVolumeChanged(controller, flags);
         }
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index f431fdb..edeb5b4 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1700,7 +1700,7 @@
                 // If we have a session send it the volume command, otherwise
                 // use the suggested stream.
                 if (mMediaController != null) {
-                    mMediaController.adjustVolumeBy(direction, AudioManager.FLAG_SHOW_UI);
+                    mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
                 } else {
                     MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
                             mVolumeControlStreamType, direction, AudioManager.FLAG_SHOW_UI);
@@ -1787,7 +1787,7 @@
                 // If we have a session send it the volume command, otherwise
                 // use the suggested stream.
                 if (mMediaController != null) {
-                    mMediaController.adjustVolumeBy(0, AudioManager.FLAG_PLAY_SOUND
+                    mMediaController.adjustVolume(0, AudioManager.FLAG_PLAY_SOUND
                             | AudioManager.FLAG_VIBRATE);
                 } else {
                     MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index c3a9dbe..e8e2813 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3535,19 +3535,30 @@
                                 }
                             } while (nRead > 0 && result == BackupTransport.TRANSPORT_OK);
 
-                            // Done -- how did it turn out?
-                            if (result == BackupTransport.TRANSPORT_OK){
-                                result = transport.finishBackup();
-                            } else {
-                                Slog.w(TAG, "Error backing up " + target.packageName);
+                            // In all cases we need to give the transport its finish callback
+                            int finishResult = transport.finishBackup();
+
+                            // If we were otherwise in a good state, now interpret the final
+                            // result based on what finishBackup() returned.  If we're in a
+                            // failure case already, preserve that result and ignore whatever
+                            // finishBackup() reported.
+                            if (result == BackupTransport.TRANSPORT_OK) {
+                                result = finishResult;
                             }
-                        } else if (result == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
+
+                            if (result != BackupTransport.TRANSPORT_OK) {
+                                Slog.e(TAG, "Error " + result
+                                        + " backing up " + target.packageName);
+                            }
+                        }
+
+                        if (result == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
                             if (DEBUG) {
                                 Slog.i(TAG, "Transport rejected backup of " + target.packageName
                                         + ", skipping");
                             }
                             // do nothing, clean up, and continue looping
-                        } else {
+                        } else if (result != BackupTransport.TRANSPORT_OK) {
                             if (DEBUG) {
                                 Slog.i(TAG, "Transport failed; aborting backup");
                                 return;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index a19eb15..87084d5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1069,27 +1069,31 @@
         synchronized (mRecords) {
             final int recordCount = mRecords.size();
             pw.println("last known state:");
-            pw.println("  mCallState=" + mCallState);
-            pw.println("  mCallIncomingNumber=" + mCallIncomingNumber);
-            pw.println("  mServiceState=" + mServiceState);
-            pw.println("  mSignalStrength=" + mSignalStrength);
-            pw.println("  mMessageWaiting=" + mMessageWaiting);
-            pw.println("  mCallForwarding=" + mCallForwarding);
-            pw.println("  mDataActivity=" + mDataActivity);
-            pw.println("  mDataConnectionState=" + mDataConnectionState);
-            pw.println("  mDataConnectionPossible=" + mDataConnectionPossible);
-            pw.println("  mDataConnectionReason=" + mDataConnectionReason);
-            pw.println("  mDataConnectionApn=" + mDataConnectionApn);
-            pw.println("  mDataConnectionLinkProperties=" + mDataConnectionLinkProperties);
-            pw.println("  mDataConnectionNetworkCapabilities=" +
-                    mDataConnectionNetworkCapabilities);
+            for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+                pw.println("  Phone Id=" + i);
+                pw.println("  mCallState=" + mCallState[i]);
+                pw.println("  mCallIncomingNumber=" + mCallIncomingNumber[i]);
+                pw.println("  mServiceState=" + mServiceState[i]);
+                pw.println("  mSignalStrength=" + mSignalStrength[i]);
+                pw.println("  mMessageWaiting=" + mMessageWaiting[i]);
+                pw.println("  mCallForwarding=" + mCallForwarding[i]);
+                pw.println("  mDataActivity=" + mDataActivity[i]);
+                pw.println("  mDataConnectionState=" + mDataConnectionState[i]);
+                pw.println("  mDataConnectionPossible=" + mDataConnectionPossible[i]);
+                pw.println("  mDataConnectionReason=" + mDataConnectionReason[i]);
+                pw.println("  mDataConnectionApn=" + mDataConnectionApn[i]);
+                pw.println("  mDataConnectionLinkProperties=" + mDataConnectionLinkProperties[i]);
+                pw.println("  mDataConnectionNetworkCapabilities=" +
+                        mDataConnectionNetworkCapabilities[i]);
+                pw.println("  mCellLocation=" + mCellLocation[i]);
+                pw.println("  mCellInfo=" + mCellInfo.get(i));
+            }
             pw.println("  mDefaultSubId=" + mDefaultSubId);
-            pw.println("  mCellLocation=" + mCellLocation);
-            pw.println("  mCellInfo=" + mCellInfo);
             pw.println("  mDcRtInfo=" + mDcRtInfo);
             pw.println("registrations: count=" + recordCount);
             for (Record r : mRecords) {
                 pw.println("  " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events));
+                pw.println("is Legacy = " + r.isLegacyApp + " subId = " + r.subId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 79cb60e..1316da1 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2809,7 +2809,7 @@
     void ensurePackageDexOpt(String packageName) {
         IPackageManager pm = AppGlobals.getPackageManager();
         try {
-            if (pm.performDexOpt(packageName)) {
+            if (pm.performDexOptIfNeeded(packageName, null /* instruction set */)) {
                 mDidDexOpt = true;
             }
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 01a21f4..ede3dab 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -203,39 +203,32 @@
     }
 
     /**
-     * Send a volume adjustment to the session owner.
+     * Send a volume adjustment to the session owner. Direction must be one of
+     * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
+     * {@link AudioManager#ADJUST_SAME}.
      *
-     * @param delta The amount to adjust the volume by.
+     * @param direction The direction to adjust volume in.
      */
-    public void adjustVolumeBy(int delta, int flags) {
+    public void adjustVolume(int direction, int flags) {
         if (isPlaybackActive(false)) {
             flags &= ~AudioManager.FLAG_PLAY_SOUND;
         }
+        if (direction > 1) {
+            direction = 1;
+        } else if (direction < -1) {
+            direction = -1;
+        }
         if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) {
-            if (delta == 0) {
-                mAudioManager.adjustStreamVolume(mAudioStream, delta, flags);
-            } else {
-                int direction = 0;
-                int steps = delta;
-                if (delta > 0) {
-                    direction = 1;
-                } else if (delta < 0) {
-                    direction = -1;
-                    steps = -delta;
-                }
-                for (int i = 0; i < steps; i++) {
-                    mAudioManager.adjustStreamVolume(mAudioStream, direction, flags);
-                }
-            }
+            mAudioManager.adjustStreamVolume(mAudioStream, direction, flags);
         } else {
             if (mVolumeControlType == VolumeProvider.VOLUME_CONTROL_FIXED) {
                 // Nothing to do, the volume cannot be changed
                 return;
             }
-            mSessionCb.adjustVolumeBy(delta);
+            mSessionCb.adjustVolume(direction);
 
             int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
-            mOptimisticVolume = volumeBefore + delta;
+            mOptimisticVolume = volumeBefore + direction;
             mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume));
             mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
             mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
@@ -752,11 +745,11 @@
             }
         }
 
-        public void adjustVolumeBy(int delta) {
+        public void adjustVolume(int direction) {
             try {
-                mCb.onAdjustVolumeBy(delta);
+                mCb.onAdjustVolume(direction);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
+                Slog.e(TAG, "Remote failure in adjustVolume.", e);
             }
         }
 
@@ -764,7 +757,7 @@
             try {
                 mCb.onSetVolumeTo(value);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
+                Slog.e(TAG, "Remote failure in setVolumeTo.", e);
             }
         }
     }
@@ -838,10 +831,10 @@
         }
 
         @Override
-        public void adjustVolumeBy(int delta, int flags) {
+        public void adjustVolume(int direction, int flags) {
             final long token = Binder.clearCallingIdentity();
             try {
-                MediaSessionRecord.this.adjustVolumeBy(delta, flags);
+                MediaSessionRecord.this.adjustVolume(direction, flags);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 4c475d9..a2dd15e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -681,7 +681,7 @@
         }
 
         @Override
-        public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
+        public void dispatchAdjustVolume(int suggestedStream, int delta, int flags)
                 throws RemoteException {
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
@@ -690,7 +690,7 @@
                 synchronized (mLock) {
                     MediaSessionRecord session = mPriorityStack
                             .getDefaultVolumeSession(mCurrentUserId);
-                    dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
+                    dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -764,11 +764,11 @@
             return resolvedUserId;
         }
 
-        private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
+        private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
                 MediaSessionRecord session) {
             if (DEBUG) {
                 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
-                Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
+                Log.d(TAG, "Adjusting session " + sessionInfo + " by " + direction + ". flags=" + flags
                         + ", suggestedStream=" + suggestedStream);
 
             }
@@ -780,28 +780,13 @@
                     }
                 }
                 try {
-                    if (delta == 0) {
-                        mAudioService.adjustSuggestedStreamVolume(delta, suggestedStream, flags,
-                                getContext().getOpPackageName());
-                    } else {
-                        int direction = 0;
-                        int steps = delta;
-                        if (delta > 0) {
-                            direction = 1;
-                        } else if (delta < 0) {
-                            direction = -1;
-                            steps = -delta;
-                        }
-                        for (int i = 0; i < steps; i++) {
-                            mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
-                                    flags, getContext().getOpPackageName());
-                        }
-                    }
+                    mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
+                            getContext().getOpPackageName());
                 } catch (RemoteException e) {
                     Log.e(TAG, "Error adjusting default volume.", e);
                 }
             } else {
-                session.adjustVolumeBy(delta, flags);
+                session.adjustVolume(direction, flags);
                 if (session.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE
                         && mRvc != null) {
                     try {
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 1d53016..355f34f 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -75,7 +75,7 @@
                         schedule(BackgroundDexOptService.this);
                         return;
                     }
-                    pm.performDexOpt(pkg, false);
+                    pm.performDexOpt(pkg, null /* instruction set */, false);
                 }
                 // ran to completion, so we abandon our timeslice and do not reschedule
                 jobFinished(jobParams, false);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 190e87c..db915e2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -76,6 +76,10 @@
     @GuardedBy("mSessions")
     private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
 
+    /** Historical sessions kept around for debugging purposes */
+    @GuardedBy("mSessions")
+    private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>();
+
     private RemoteCallbackList<IPackageInstallerObserver> mObservers = new RemoteCallbackList<>();
 
     private static final FilenameFilter sStageFilter = new FilenameFilter() {
@@ -344,18 +348,29 @@
     }
 
     void dump(IndentingPrintWriter pw) {
-        pw.println("Active install sessions:");
-        pw.increaseIndent();
         synchronized (mSessions) {
-            final int N = mSessions.size();
+            pw.println("Active install sessions:");
+            pw.increaseIndent();
+            int N = mSessions.size();
             for (int i = 0; i < N; i++) {
                 final PackageInstallerSession session = mSessions.valueAt(i);
                 session.dump(pw);
                 pw.println();
             }
+            pw.println();
+            pw.decreaseIndent();
+
+            pw.println("Historical install sessions:");
+            pw.increaseIndent();
+            N = mHistoricalSessions.size();
+            for (int i = 0; i < N; i++) {
+                final PackageInstallerSession session = mHistoricalSessions.valueAt(i);
+                session.dump(pw);
+                pw.println();
+            }
+            pw.println();
+            pw.decreaseIndent();
         }
-        pw.println();
-        pw.decreaseIndent();
     }
 
     class Callback {
@@ -367,6 +382,7 @@
             notifySessionFinished(session.sessionId, success);
             synchronized (mSessions) {
                 mSessions.remove(session.sessionId);
+                mHistoricalSessions.put(session.sessionId, session);
             }
             writeSessionsAsync();
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 31d9704..0e6a3f0 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -65,6 +65,7 @@
 
 public class PackageInstallerSession extends IPackageInstallerSession.Stub {
     private static final String TAG = "PackageInstaller";
+    private static final boolean LOGD = true;
 
     // TODO: enforce INSTALL_ALLOW_TEST
     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -435,35 +436,25 @@
      */
     private void spliceExistingFilesIntoStage() throws PackageManagerException {
         final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
-        final File existingDir = new File(app.getBaseCodePath());
 
-        try {
-            linkTreeIgnoringExisting(existingDir, sessionStageDir);
-        } catch (ErrnoException e) {
-            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
-                    "Failed to splice into stage");
-        }
-    }
+        int n = 0;
+        final File[] oldFiles = new File(app.getCodePath()).listFiles();
+        if (!ArrayUtils.isEmpty(oldFiles)) {
+            for (File oldFile : oldFiles) {
+                if (!PackageParser.isApkFile(oldFile)) continue;
 
-    /**
-     * Recursively hard link all files from source directory tree to target.
-     * When a file already exists in the target tree, it leaves that file
-     * intact.
-     */
-    private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException {
-        final File[] sourceContents = sourceDir.listFiles();
-        if (ArrayUtils.isEmpty(sourceContents)) return;
-
-        for (File sourceFile : sourceContents) {
-            final File targetFile = new File(targetDir, sourceFile.getName());
-
-            if (sourceFile.isDirectory()) {
-                targetFile.mkdir();
-                linkTreeIgnoringExisting(sourceFile, targetFile);
-            } else {
-                Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
+                final File newFile = new File(sessionStageDir, oldFile.getName());
+                try {
+                    Os.link(oldFile.getAbsolutePath(), newFile.getAbsolutePath());
+                    n++;
+                } catch (ErrnoException e) {
+                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                            "Failed to splice into stage", e);
+                }
             }
         }
+
+        if (LOGD) Slog.d(TAG, "Spliced " + n + " existing APKs into stage");
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6513c88..727cff0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -26,7 +26,6 @@
 import static android.content.pm.PackageManager.INSTALL_EXTERNAL;
 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
 import static android.content.pm.PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER;
-import static android.content.pm.PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DEXOPT;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
@@ -4542,24 +4541,29 @@
                 }
                 PackageParser.Package p = pkg;
                 synchronized (mInstallLock) {
-                    if (p.mDexOptNeeded) {
-                        performDexOptLI(p, false /* force dex */, false /* defer */,
-                                true /* include dependencies */);
-                    }
+                    performDexOptLI(p, null /* instruction sets */, false /* force dex */, false /* defer */,
+                            true /* include dependencies */);
                 }
             }
         }
     }
 
     @Override
-    public boolean performDexOpt(String packageName) {
-        enforceSystemOrRoot("Only the system can request dexopt be performed");
-        return performDexOpt(packageName, true);
+    public boolean performDexOptIfNeeded(String packageName, String instructionSet) {
+        return performDexOpt(packageName, instructionSet, true);
     }
 
-    public boolean performDexOpt(String packageName, boolean updateUsage) {
+    private static String getPrimaryInstructionSet(ApplicationInfo info) {
+        if (info.primaryCpuAbi == null) {
+            return getPreferredInstructionSet();
+        }
 
+        return VMRuntime.getInstructionSet(info.primaryCpuAbi);
+    }
+
+    public boolean performDexOpt(String packageName, String instructionSet, boolean updateUsage) {
         PackageParser.Package p;
+        final String targetInstructionSet;
         synchronized (mPackages) {
             p = mPackages.get(packageName);
             if (p == null) {
@@ -4569,13 +4573,17 @@
                 p.mLastPackageUsageTimeInMills = System.currentTimeMillis();
             }
             mPackageUsage.write(false);
-            if (!p.mDexOptNeeded) {
+
+            targetInstructionSet = instructionSet != null ? instructionSet :
+                    getPrimaryInstructionSet(p.applicationInfo);
+            if (p.mDexOptPerformed.contains(targetInstructionSet)) {
                 return false;
             }
         }
 
         synchronized (mInstallLock) {
-            return performDexOptLI(p, false /* force dex */, false /* defer */,
+            final String[] instructionSets = new String[] { targetInstructionSet };
+            return performDexOptLI(p, instructionSets, false /* force dex */, false /* defer */,
                     true /* include dependencies */) == DEX_OPT_PERFORMED;
         }
     }
@@ -4585,9 +4593,9 @@
         synchronized (mPackages) {
             for (PackageParser.Package p : mPackages.values()) {
                 if (DEBUG_DEXOPT) {
-                    Log.i(TAG, p.packageName + " mDexOptNeeded=" + p.mDexOptNeeded);
+                    Log.i(TAG, p.packageName + " mDexOptPerformed=" + p.mDexOptPerformed.toArray());
                 }
-                if (!p.mDexOptNeeded) {
+                if (!p.mDexOptPerformed.isEmpty()) {
                     continue;
                 }
                 if (pkgs == null) {
@@ -4655,6 +4663,10 @@
         // 3.) we are skipping an unneeded dexopt
         for (String path : paths) {
             for (String instructionSet : instructionSets) {
+                if (!forceDex && pkg.mDexOptPerformed.contains(instructionSet)) {
+                    continue;
+                }
+
                 try {
                     final boolean isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
                             pkg.packageName, instructionSet, defer);
@@ -4669,10 +4681,10 @@
                             // Don't bother running dexopt again if we failed, it will probably
                             // just result in an error again. Also, don't bother dexopting for other
                             // paths & ISAs.
-                            pkg.mDexOptNeeded = false;
                             return DEX_OPT_FAILED;
                         } else {
                             performedDexOpt = true;
+                            pkg.mDexOptPerformed.add(instructionSet);
                         }
                     }
 
@@ -4706,7 +4718,6 @@
         // deferred dex-opt. We've either dex-opted one more paths or instruction sets or
         // we've skipped all of them because they are up to date. In both cases this
         // package doesn't need dexopt any longer.
-        pkg.mDexOptNeeded = false;
         return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
     }
 
@@ -4762,8 +4773,8 @@
         return allInstructionSets;
     }
 
-    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
-            boolean inclDependencies) {
+    private int performDexOptLI(PackageParser.Package pkg, String[] instructionSets,
+                                boolean forceDex, boolean defer, boolean inclDependencies) {
         HashSet<String> done;
         if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
             done = new HashSet<String>();
@@ -4771,7 +4782,7 @@
         } else {
             done = null;
         }
-        return performDexOptLI(pkg, null /* target instruction sets */,  forceDex, defer, done);
+        return performDexOptLI(pkg, instructionSets,  forceDex, defer, done);
     }
 
     private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) {
@@ -5569,7 +5580,7 @@
         }
 
         if ((scanMode&SCAN_NO_DEX) == 0) {
-            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+            if (performDexOptLI(pkg, null /* instruction sets */, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                     == DEX_OPT_FAILED) {
                 if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
                     removeDataDirsLI(pkg.packageName);
@@ -5648,7 +5659,8 @@
             if ((scanMode&SCAN_NO_DEX) == 0) {
                 for (int i=0; i<clientLibPkgs.size(); i++) {
                     PackageParser.Package clientPkg = clientLibPkgs.get(i);
-                    if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+                    if (performDexOptLI(clientPkg, null /* instruction sets */,
+                            forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                             == DEX_OPT_FAILED) {
                         if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
                             removeDataDirsLI(pkg.packageName);
@@ -6128,7 +6140,8 @@
                         ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;
                         Slog.i(TAG, "Adjusting ABI for : " + ps.name + " to " + adjustedAbi);
 
-                        if (performDexOptLI(ps.pkg, forceDexOpt, deferDexOpt, true) == DEX_OPT_FAILED) {
+                        if (performDexOptLI(ps.pkg, null /* instruction sets */, forceDexOpt,
+                                deferDexOpt, true) == DEX_OPT_FAILED) {
                             ps.primaryCpuAbiString = null;
                             ps.pkg.applicationInfo.primaryCpuAbi = null;
                             return;
@@ -10013,9 +10026,6 @@
             }
         }
 
-        // Nuke any cached code
-        deleteCodeCacheDirsLI(pkgName);
-
         boolean sysPkg = (isSystemApp(oldPackage));
         if (sysPkg) {
             replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
@@ -10053,6 +10063,7 @@
             deletedPkg = false;
         } else {
             // Successfully deleted the old package. Now proceed with re-installation
+            deleteCodeCacheDirsLI(pkgName);
             try {
                 final PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags,
                         scanMode | SCAN_UPDATE_TIME, System.currentTimeMillis(), user, abiOverride);
@@ -10164,6 +10175,8 @@
         }
 
         // Successfully disabled the old package. Now proceed with re-installation
+        deleteCodeCacheDirsLI(packageName);
+
         res.returnCode = PackageManager.INSTALL_SUCCEEDED;
         pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
 
@@ -10235,7 +10248,7 @@
                  * remove the target to make sure there isn't a stale
                  * file from a previous version of the package.
                  */
-                    newPackage.mDexOptNeeded = true;
+                    newPackage.mDexOptPerformed.clear();
                     mInstaller.rmdex(oldCodePath, instructionSet);
                     mInstaller.rmdex(newPackage.baseCodePath, instructionSet);
                 }
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index fb820f0..db834a4 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -44,9 +44,9 @@
         public void onDestroyed(Connection c) {}
         public void onCallCapabilitiesChanged(Connection c, int callCapabilities) {}
         public void onParentConnectionChanged(Connection c, Connection parent) {}
-        public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {}
-        public void onSetAudioModeIsVoip(Connection c, boolean isVoip) {}
-        public void onSetStatusHints(Connection c, StatusHints statusHints) {}
+        public void onCallVideoProviderChanged(Connection c, CallVideoProvider callVideoProvider) {}
+        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {}
+        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {}
     }
 
     public final class State {
@@ -321,7 +321,7 @@
     public final void setCallVideoProvider(CallVideoProvider callVideoProvider) {
         mCallVideoProvider = callVideoProvider;
         for (Listener l : mListeners) {
-            l.onSetCallVideoProvider(this, callVideoProvider);
+            l.onCallVideoProviderChanged(this, callVideoProvider);
         }
     }
 
@@ -415,7 +415,7 @@
     public final void setAudioModeIsVoip(boolean isVoip) {
         mAudioModeIsVoip = isVoip;
         for (Listener l : mListeners) {
-            l.onSetAudioModeIsVoip(this, isVoip);
+            l.onAudioModeIsVoipChanged(this, isVoip);
         }
     }
 
@@ -427,7 +427,7 @@
     public final void setStatusHints(StatusHints statusHints) {
         mStatusHints = statusHints;
         for (Listener l : mListeners) {
-            l.onSetStatusHints(this, statusHints);
+            l.onStatusHintsChanged(this, statusHints);
         }
     }
 
@@ -516,7 +516,6 @@
      */
     protected void onPhoneAccountClicked() {}
 
-
     private void addChild(Connection connection) {
         Log.d(this, "adding child %s", connection);
         mChildConnections.add(connection);
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 178cee8..2a6804b 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -364,19 +364,19 @@
         }
 
         @Override
-        public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {
+        public void onCallVideoProviderChanged(Connection c, CallVideoProvider callVideoProvider) {
             String id = mIdByConnection.get(c);
             mAdapter.setCallVideoProvider(id, callVideoProvider);
         }
 
         @Override
-        public void onSetAudioModeIsVoip(Connection c, boolean isVoip) {
+        public void onAudioModeIsVoipChanged(Connection c, boolean isVoip) {
             String id = mIdByConnection.get(c);
             mAdapter.setAudioModeIsVoip(id, isVoip);
         }
 
         @Override
-        public void onSetStatusHints(Connection c, StatusHints statusHints) {
+        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
             String id = mIdByConnection.get(c);
             mAdapter.setStatusHints(id, statusHints);
         }
diff --git a/telecomm/java/android/telecomm/TelecommConstants.java b/telecomm/java/android/telecomm/TelecommConstants.java
index a94841f..b50c1d7 100644
--- a/telecomm/java/android/telecomm/TelecommConstants.java
+++ b/telecomm/java/android/telecomm/TelecommConstants.java
@@ -121,4 +121,83 @@
      * wait for user confirmation before proceeding.
      */
     public static final char DTMF_CHARACTER_WAIT = ';';
+
+    /**
+     * TTY (teletypewriter) mode is off.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_OFF = 0;
+
+    /**
+     * TTY (teletypewriter) mode is on. The speaker is off and the microphone is muted. The user
+     * will communicate with the remote party by sending and receiving text messages.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_FULL = 1;
+
+    /**
+     * TTY (teletypewriter) mode is in hearing carryover mode (HCO). The microphone is muted but the
+     * speaker is on. The user will communicate with the remote party by sending text messages and
+     * hearing an audible reply.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_HCO = 2;
+
+    /**
+     * TTY (teletypewriter) mode is in voice carryover mode (VCO). The speaker is off but the
+     * microphone is still on. User will communicate with the remote party by speaking and receiving
+     * text message replies.
+     *
+     * @hide
+     */
+    public static final int TTY_MODE_VCO = 3;
+
+    /**
+     * Broadcast intent action indicating that the current TTY mode has changed. An intent extra
+     * provides this state as an int.
+     * @see #EXTRA_CURRENT_TTY_MODE
+     *
+     * @hide
+     */
+    public static final String ACTION_CURRENT_TTY_MODE_CHANGED =
+            "android.telecomm.intent.action.CURRENT_TTY_MODE_CHANGED";
+
+    /**
+     * The lookup key for an int that indicates the current TTY mode.
+     * Valid modes are:
+     * - {@link #TTY_MODE_OFF}
+     * - {@link #TTY_MODE_FULL}
+     * - {@link #TTY_MODE_HCO}
+     * - {@link #TTY_MODE_VCO}
+     *
+     * @hide
+     */
+    public static final String EXTRA_CURRENT_TTY_MODE =
+            "android.telecomm.intent.extra.CURRENT_TTY_MODE";
+
+    /**
+     * Broadcast intent action indicating that the TTY preferred operating mode
+     * has changed. An intent extra provides the new mode as an int.
+     * @see #EXTRA_TTY_PREFERRED_MODE
+     *
+     * @hide
+     */
+    public static final String ACTION_TTY_PREFERRED_MODE_CHANGED =
+            "android.telecomm.intent.action.TTY_PREFERRED_MODE_CHANGED";
+
+    /**
+     * The lookup key for an int that indicates preferred TTY mode.
+     * Valid modes are:
+     * - {@link #TTY_MODE_OFF}
+     * - {@link #TTY_MODE_FULL}
+     * - {@link #TTY_MODE_HCO}
+     * - {@link #TTY_MODE_VCO}
+     *
+     * @hide
+     */
+    public static final String EXTRA_TTY_PREFERRED_MODE =
+            "android.telecomm.intent.extra.TTY_PREFERRED";
 }
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
index fcd2eba..8bf80bb 100644
--- a/telecomm/java/android/telecomm/TelecommManager.java
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -28,21 +28,9 @@
 /**
  * Provides access to Telecomm-related functionality.
  * TODO(santoscordon): Move this all into PhoneManager.
- * @hide
  */
 public class TelecommManager {
 
-    /**
-     * The extra used with an {@link android.content.Intent#ACTION_CALL} or
-     * {@link android.content.Intent#ACTION_DIAL} {@code Intent} to specify a {@link PhoneAccount}
-     * to use when making the call.
-     *
-     * <p class="note">
-     * Retrieve with
-     * {@link android.content.Intent#getParcelableExtra(String)}.
-     */
-    public static final String EXTRA_PHONE_ACCOUNT = "account";
-
     private static final String TAG = "TelecommManager";
     private static final String TELECOMM_SERVICE_NAME = "telecomm";
 
@@ -138,8 +126,6 @@
      * Remove all Accounts for a given package from the system.
      *
      * @param packageName A package name that may have registered Accounts.
-     *
-     * @hide
      */
     @SystemApi
     public void clearAccounts(String packageName) {
@@ -254,6 +240,44 @@
         }
     }
 
+    /**
+     * Returns whether TTY is supported on this device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isTtySupported() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecommService().isTtySupported();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException attempting to get TTY supported state.", e);
+        }
+        return false;
+    }
+
+    /**
+     * Returns the current TTY mode of the device. For TTY to be on the user must enable it in
+     * settings and have a wired headset plugged in. Valid modes are:
+     * - {@link android.telecomm.TelecommConstants#TTY_MODE_OFF}
+     * - {@link android.telecomm.TelecommConstants#TTY_MODE_FULL}
+     * - {@link android.telecomm.TelecommConstants#TTY_MODE_HCO}
+     * - {@link android.telecomm.TelecommConstants#TTY_MODE_VCO}
+     *
+     * @hide
+     */
+    public int getCurrentTtyMode() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecommService().getCurrentTtyMode();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e);
+        }
+        return TelecommConstants.TTY_MODE_OFF;
+    }
+
     private ITelecommService getTelecommService() {
         return ITelecommService.Stub.asInterface(ServiceManager.getService(TELECOMM_SERVICE_NAME));
     }
diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
index 3334385..43caa1e 100644
--- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
@@ -101,4 +101,14 @@
      * @see PhoneManager#handlePinMmi
      */
     boolean handlePinMmi(String dialString);
+
+    /**
+     * @see TelecomManager#isTtySupported
+     */
+    boolean isTtySupported();
+
+    /**
+     * @see TelecomManager#getCurrentTtyMode
+     */
+    int getCurrentTtyMode();
 }
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerController.java b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
index 9cbb455..b47cc3c 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerController.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerController.java
@@ -161,7 +161,7 @@
                 Log.e(TAG, "Error getting session", e);
                 return;
             }
-            mController = MediaController.fromToken(token);
+            mController = new MediaController(token);
             mContext.setMediaController(mController);
             mController.addCallback(mControllerCb, mHandler);
             mTransportControls = mController.getTransportControls();
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index 78353b2..feecfde 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -28,6 +28,7 @@
 import android.os.Bundle;
 import android.support.media.protocols.MediaPlayerProtocol;
 import android.support.media.protocols.MediaPlayerProtocol.MediaStatus;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -84,7 +85,7 @@
                 .build());
         mRouter.setRoutingCallback(new RoutingCallback(), null);
 
-        mSession = man.createSession("OneMedia");
+        mSession = new MediaSession(mContext, "OneMedia");
         mSession.addCallback(mCallback);
         mSession.addTransportControlsCallback(new TransportCallback());
         mSession.setPlaybackState(mPlaybackState);
diff --git a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable.xml b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable01.xml
similarity index 98%
rename from tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable.xml
rename to tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable01.xml
index 30fb1b8..18d7755 100644
--- a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable.xml
+++ b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable01.xml
@@ -19,7 +19,7 @@
         android:drawable="@drawable/vector_drawable12" />
     <item android:id="@+id/off"
         android:drawable="@drawable/vector_drawable12" />
-    <transition android:fromId="@+id/off" android:toId="@+id/on">
+    <transition android:fromId="@+id/off" android:toId="@+id/on" android:reversible="true">
         <animated-vector android:drawable="@drawable/vector_drawable12">
             <target
                 android:name="pie1"
diff --git a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable02.xml b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable02.xml
new file mode 100644
index 0000000..6a67b02
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable02.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/on" android:state_checked="true"
+        android:drawable="@drawable/vector_drawable_grouping_1" />
+    <item android:id="@+id/off"
+        android:drawable="@drawable/vector_drawable_grouping_1" />
+    <transition android:fromId="@+id/off" android:toId="@+id/on"
+        android:drawable="@drawable/animation_vector_drawable_grouping_1"
+        android:reversible="true">
+    </transition>
+</animated-selector>
diff --git a/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable03.xml b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable03.xml
new file mode 100644
index 0000000..65cf25b
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/state_animation_vector_drawable03.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/on" android:state_checked="true"
+        android:drawable="@drawable/vector_drawable_favorite" />
+    <item android:id="@+id/off"
+        android:drawable="@drawable/vector_drawable_favorite" />
+    <transition android:fromId="@+id/off" android:toId="@+id/on"
+        android:drawable="@drawable/animation_vector_drawable_favorite"
+        android:reversible="true">
+    </transition>
+</animated-selector>
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java
index 0ae0136..566cc4b 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedStateVectorDrawableTest.java
@@ -27,7 +27,9 @@
     private static final String LOGCAT = "AnimatedStateVectorDrawableTest";
 
     protected int[] icon = {
-            R.drawable.state_animation_vector_drawable
+            R.drawable.state_animation_vector_drawable01,
+            R.drawable.state_animation_vector_drawable02,
+            R.drawable.state_animation_vector_drawable03,
     };
 
     @Override
@@ -37,7 +39,7 @@
         ScrollView scrollView = new ScrollView(this);
         GridLayout container = new GridLayout(this);
         scrollView.addView(container);
-        container.setColumnCount(1);
+        container.setColumnCount(5);
 
         for (int i = 0; i < icon.length; i++) {
             CheckBox button = new CheckBox(this);
diff --git a/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java
new file mode 100644
index 0000000..a3ec2cc
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java
@@ -0,0 +1,761 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import java.awt.Composite;
+import java.awt.CompositeContext;
+import java.awt.RenderingHints;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+
+/*
+ * (non-Javadoc)
+ * The class is adapted from a demo tool for Blending Modes written by
+ * Romain Guy (romainguy@android.com). The tool is available at
+ * http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
+ */
+public final class BlendComposite implements Composite {
+    public enum BlendingMode {
+        NORMAL,
+        AVERAGE,
+        MULTIPLY,
+        SCREEN,
+        DARKEN,
+        LIGHTEN,
+        OVERLAY,
+        HARD_LIGHT,
+        SOFT_LIGHT,
+        DIFFERENCE,
+        NEGATION,
+        EXCLUSION,
+        COLOR_DODGE,
+        INVERSE_COLOR_DODGE,
+        SOFT_DODGE,
+        COLOR_BURN,
+        INVERSE_COLOR_BURN,
+        SOFT_BURN,
+        REFLECT,
+        GLOW,
+        FREEZE,
+        HEAT,
+        ADD,
+        SUBTRACT,
+        STAMP,
+        RED,
+        GREEN,
+        BLUE,
+        HUE,
+        SATURATION,
+        COLOR,
+        LUMINOSITY
+    }
+
+    public static final BlendComposite Normal = new BlendComposite(BlendingMode.NORMAL);
+    public static final BlendComposite Average = new BlendComposite(BlendingMode.AVERAGE);
+    public static final BlendComposite Multiply = new BlendComposite(BlendingMode.MULTIPLY);
+    public static final BlendComposite Screen = new BlendComposite(BlendingMode.SCREEN);
+    public static final BlendComposite Darken = new BlendComposite(BlendingMode.DARKEN);
+    public static final BlendComposite Lighten = new BlendComposite(BlendingMode.LIGHTEN);
+    public static final BlendComposite Overlay = new BlendComposite(BlendingMode.OVERLAY);
+    public static final BlendComposite HardLight = new BlendComposite(BlendingMode.HARD_LIGHT);
+    public static final BlendComposite SoftLight = new BlendComposite(BlendingMode.SOFT_LIGHT);
+    public static final BlendComposite Difference = new BlendComposite(BlendingMode.DIFFERENCE);
+    public static final BlendComposite Negation = new BlendComposite(BlendingMode.NEGATION);
+    public static final BlendComposite Exclusion = new BlendComposite(BlendingMode.EXCLUSION);
+    public static final BlendComposite ColorDodge = new BlendComposite(BlendingMode.COLOR_DODGE);
+    public static final BlendComposite InverseColorDodge = new BlendComposite(BlendingMode.INVERSE_COLOR_DODGE);
+    public static final BlendComposite SoftDodge = new BlendComposite(BlendingMode.SOFT_DODGE);
+    public static final BlendComposite ColorBurn = new BlendComposite(BlendingMode.COLOR_BURN);
+    public static final BlendComposite InverseColorBurn = new BlendComposite(BlendingMode.INVERSE_COLOR_BURN);
+    public static final BlendComposite SoftBurn = new BlendComposite(BlendingMode.SOFT_BURN);
+    public static final BlendComposite Reflect = new BlendComposite(BlendingMode.REFLECT);
+    public static final BlendComposite Glow = new BlendComposite(BlendingMode.GLOW);
+    public static final BlendComposite Freeze = new BlendComposite(BlendingMode.FREEZE);
+    public static final BlendComposite Heat = new BlendComposite(BlendingMode.HEAT);
+    public static final BlendComposite Add = new BlendComposite(BlendingMode.ADD);
+    public static final BlendComposite Subtract = new BlendComposite(BlendingMode.SUBTRACT);
+    public static final BlendComposite Stamp = new BlendComposite(BlendingMode.STAMP);
+    public static final BlendComposite Red = new BlendComposite(BlendingMode.RED);
+    public static final BlendComposite Green = new BlendComposite(BlendingMode.GREEN);
+    public static final BlendComposite Blue = new BlendComposite(BlendingMode.BLUE);
+    public static final BlendComposite Hue = new BlendComposite(BlendingMode.HUE);
+    public static final BlendComposite Saturation = new BlendComposite(BlendingMode.SATURATION);
+    public static final BlendComposite Color = new BlendComposite(BlendingMode.COLOR);
+    public static final BlendComposite Luminosity = new BlendComposite(BlendingMode.LUMINOSITY);
+
+    private float alpha;
+    private BlendingMode mode;
+
+    private BlendComposite(BlendingMode mode) {
+        this(mode, 1.0f);
+    }
+
+    private BlendComposite(BlendingMode mode, float alpha) {
+        this.mode = mode;
+        setAlpha(alpha);
+    }
+
+    public static BlendComposite getInstance(BlendingMode mode) {
+        return new BlendComposite(mode);
+    }
+
+    public static BlendComposite getInstance(BlendingMode mode, float alpha) {
+        return new BlendComposite(mode, alpha);
+    }
+
+    public BlendComposite derive(BlendingMode mode) {
+        return this.mode == mode ? this : new BlendComposite(mode, getAlpha());
+    }
+
+    public BlendComposite derive(float alpha) {
+        return this.alpha == alpha ? this : new BlendComposite(getMode(), alpha);
+    }
+
+    public float getAlpha() {
+        return alpha;
+    }
+
+    public BlendingMode getMode() {
+        return mode;
+    }
+
+    private void setAlpha(float alpha) {
+        if (alpha < 0.0f || alpha > 1.0f) {
+            throw new IllegalArgumentException(
+                    "alpha must be comprised between 0.0f and 1.0f");
+        }
+
+        this.alpha = alpha;
+    }
+
+    @Override
+    public int hashCode() {
+        return Float.floatToIntBits(alpha) * 31 + mode.ordinal();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof BlendComposite)) {
+            return false;
+        }
+
+        BlendComposite bc = (BlendComposite) obj;
+
+        if (mode != bc.mode) {
+            return false;
+        }
+
+        return alpha == bc.alpha;
+    }
+
+    public CompositeContext createContext(ColorModel srcColorModel,
+                                          ColorModel dstColorModel,
+                                          RenderingHints hints) {
+        return new BlendingContext(this);
+    }
+
+    private static final class BlendingContext implements CompositeContext {
+        private final Blender blender;
+        private final BlendComposite composite;
+
+        private BlendingContext(BlendComposite composite) {
+            this.composite = composite;
+            this.blender = Blender.getBlenderFor(composite);
+        }
+
+        public void dispose() {
+        }
+
+        public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
+            if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT ||
+                dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT ||
+                dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) {
+                throw new IllegalStateException(
+                        "Source and destination must store pixels as INT.");
+            }
+
+            int width = Math.min(src.getWidth(), dstIn.getWidth());
+            int height = Math.min(src.getHeight(), dstIn.getHeight());
+
+            float alpha = composite.getAlpha();
+
+            int[] srcPixel = new int[4];
+            int[] dstPixel = new int[4];
+            int[] result = new int[4];
+            int[] srcPixels = new int[width];
+            int[] dstPixels = new int[width];
+
+            for (int y = 0; y < height; y++) {
+                dstIn.getDataElements(0, y, width, 1, dstPixels);
+                if (alpha != 0) {
+                    src.getDataElements(0, y, width, 1, srcPixels);
+                    for (int x = 0; x < width; x++) {
+                        // pixels are stored as INT_ARGB
+                        // our arrays are [R, G, B, A]
+                        int pixel = srcPixels[x];
+                        srcPixel[0] = (pixel >> 16) & 0xFF;
+                        srcPixel[1] = (pixel >>  8) & 0xFF;
+                        srcPixel[2] = (pixel      ) & 0xFF;
+                        srcPixel[3] = (pixel >> 24) & 0xFF;
+
+                        pixel = dstPixels[x];
+                        dstPixel[0] = (pixel >> 16) & 0xFF;
+                        dstPixel[1] = (pixel >>  8) & 0xFF;
+                        dstPixel[2] = (pixel      ) & 0xFF;
+                        dstPixel[3] = (pixel >> 24) & 0xFF;
+
+                        result = blender.blend(srcPixel, dstPixel, result);
+
+                        // mixes the result with the opacity
+                        if (alpha == 1) {
+                            dstPixels[x] = (result[3] & 0xFF) << 24 |
+                                           (result[0] & 0xFF) << 16 |
+                                           (result[1] & 0xFF) <<  8 |
+                                           result[2] & 0xFF;
+                        } else {
+                            dstPixels[x] =
+                                    ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24 |
+                                    ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16 |
+                                    ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) <<  8 |
+                                    (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF;
+                        }
+
+                    }
+            }
+                dstOut.setDataElements(0, y, width, 1, dstPixels);
+            }
+        }
+    }
+
+    private static abstract class Blender {
+        public abstract int[] blend(int[] src, int[] dst, int[] result);
+
+        private static void RGBtoHSL(int r, int g, int b, float[] hsl) {
+            float var_R = (r / 255f);
+            float var_G = (g / 255f);
+            float var_B = (b / 255f);
+
+            float var_Min;
+            float var_Max;
+            float del_Max;
+
+            if (var_R > var_G) {
+                var_Min = var_G;
+                var_Max = var_R;
+            } else {
+                var_Min = var_R;
+                var_Max = var_G;
+            }
+            if (var_B > var_Max) {
+                var_Max = var_B;
+            }
+            if (var_B < var_Min) {
+                var_Min = var_B;
+            }
+
+            del_Max = var_Max - var_Min;
+
+            float H, S, L;
+            L = (var_Max + var_Min) / 2f;
+
+            if (del_Max - 0.01f <= 0.0f) {
+                H = 0;
+                S = 0;
+            } else {
+                if (L < 0.5f) {
+                    S = del_Max / (var_Max + var_Min);
+                } else {
+                    S = del_Max / (2 - var_Max - var_Min);
+                }
+
+                float del_R = (((var_Max - var_R) / 6f) + (del_Max / 2f)) / del_Max;
+                float del_G = (((var_Max - var_G) / 6f) + (del_Max / 2f)) / del_Max;
+                float del_B = (((var_Max - var_B) / 6f) + (del_Max / 2f)) / del_Max;
+
+                if (var_R == var_Max) {
+                    H = del_B - del_G;
+                } else if (var_G == var_Max) {
+                    H = (1 / 3f) + del_R - del_B;
+                } else {
+                    H = (2 / 3f) + del_G - del_R;
+                }
+                if (H < 0) {
+                    H += 1;
+                }
+                if (H > 1) {
+                    H -= 1;
+                }
+            }
+
+            hsl[0] = H;
+            hsl[1] = S;
+            hsl[2] = L;
+        }
+
+        private static void HSLtoRGB(float h, float s, float l, int[] rgb) {
+            int R, G, B;
+
+            if (s - 0.01f <= 0.0f) {
+                R = (int) (l * 255.0f);
+                G = (int) (l * 255.0f);
+                B = (int) (l * 255.0f);
+            } else {
+                float var_1, var_2;
+                if (l < 0.5f) {
+                    var_2 = l * (1 + s);
+                } else {
+                    var_2 = (l + s) - (s * l);
+                }
+                var_1 = 2 * l - var_2;
+
+                R = (int) (255.0f * hue2RGB(var_1, var_2, h + (1.0f / 3.0f)));
+                G = (int) (255.0f * hue2RGB(var_1, var_2, h));
+                B = (int) (255.0f * hue2RGB(var_1, var_2, h - (1.0f / 3.0f)));
+            }
+
+            rgb[0] = R;
+            rgb[1] = G;
+            rgb[2] = B;
+        }
+
+        private static float hue2RGB(float v1, float v2, float vH) {
+            if (vH < 0.0f) {
+                vH += 1.0f;
+            }
+            if (vH > 1.0f) {
+                vH -= 1.0f;
+            }
+            if ((6.0f * vH) < 1.0f) {
+                return (v1 + (v2 - v1) * 6.0f * vH);
+            }
+            if ((2.0f * vH) < 1.0f) {
+                return (v2);
+            }
+            if ((3.0f * vH) < 2.0f) {
+                return (v1 + (v2 - v1) * ((2.0f / 3.0f) - vH) * 6.0f);
+            }
+            return (v1);
+        }
+
+        public static Blender getBlenderFor(BlendComposite composite) {
+            switch (composite.getMode()) {
+                case NORMAL:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            System.arraycopy(src, 0, result, 0, 4);
+                            return result;
+                        }
+                    };
+                case ADD:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 4; i++) {
+                                result[i] = Math.min(255, src[i] + dst[i]);
+                            }
+                            return result;
+                        }
+                    };
+                case AVERAGE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = (src[i] + dst[i]) >> 1;
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case BLUE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            System.arraycopy(dst, 0, result, 0, 3);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case COLOR:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            float[] srcHSL = new float[3];
+                            RGBtoHSL(src[0], src[1], src[2], srcHSL);
+                            float[] dstHSL = new float[3];
+                            RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+                            HSLtoRGB(srcHSL[0], srcHSL[1], dstHSL[2], result);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+
+                            return result;
+                        }
+                    };
+                case COLOR_BURN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = src[i] == 0 ? 0 :
+                                    Math.max(0, 255 - (((255 - dst[i]) << 8) / src[i]));
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case COLOR_DODGE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = src[i] == 255 ? 255 :
+                                    Math.min((dst[i] << 8) / (255 - src[i]), 255);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case DARKEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = Math.min(src[i], dst[i]);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case DIFFERENCE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = dst[i] + src[i] - (dst[i] * src[i] >> 7);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case EXCLUSION:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = dst[i] + src[i] - (dst[i] * src[i] >> 7);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case FREEZE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = src[i] == 0 ? 0 :
+                                    Math.max(0, 255 - (255 - dst[i]) * (255 - dst[i]) / src[i]);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case GLOW:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = dst[i] == 255 ? 255 :
+                                    Math.min(255, src[i] * src[i] / (255 - dst[i]));
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case GREEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0],
+                                dst[1],
+                                src[2],
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case HARD_LIGHT:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                src[0] < 128 ? dst[0] * src[0] >> 7 :
+                                    255 - ((255 - src[0]) * (255 - dst[0]) >> 7),
+                                src[1] < 128 ? dst[1] * src[1] >> 7 :
+                                    255 - ((255 - src[1]) * (255 - dst[1]) >> 7),
+                                src[2] < 128 ? dst[2] * src[2] >> 7 :
+                                    255 - ((255 - src[2]) * (255 - dst[2]) >> 7),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case HEAT:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0] == 0 ? 0 : Math.max(0, 255 - (255 - src[0]) * (255 - src[0]) / dst[0]),
+                                dst[1] == 0 ? 0 : Math.max(0, 255 - (255 - src[1]) * (255 - src[1]) / dst[1]),
+                                dst[2] == 0 ? 0 : Math.max(0, 255 - (255 - src[2]) * (255 - src[2]) / dst[2]),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case HUE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            float[] srcHSL = new float[3];
+                            RGBtoHSL(src[0], src[1], src[2], srcHSL);
+                            float[] dstHSL = new float[3];
+                            RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+                            HSLtoRGB(srcHSL[0], dstHSL[1], dstHSL[2], result);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+
+                            return result;
+                        }
+                    };
+                case INVERSE_COLOR_BURN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0] == 0 ? 0 :
+                                    Math.max(0, 255 - (((255 - src[0]) << 8) / dst[0])),
+                                dst[1] == 0 ? 0 :
+                                    Math.max(0, 255 - (((255 - src[1]) << 8) / dst[1])),
+                                dst[2] == 0 ? 0 :
+                                    Math.max(0, 255 - (((255 - src[2]) << 8) / dst[2])),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case INVERSE_COLOR_DODGE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0] == 255 ? 255 :
+                                    Math.min((src[0] << 8) / (255 - dst[0]), 255),
+                                dst[1] == 255 ? 255 :
+                                    Math.min((src[1] << 8) / (255 - dst[1]), 255),
+                                dst[2] == 255 ? 255 :
+                                    Math.min((src[2] << 8) / (255 - dst[2]), 255),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case LIGHTEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = Math.max(src[i], dst[i]);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case LUMINOSITY:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            float[] srcHSL = new float[3];
+                            RGBtoHSL(src[0], src[1], src[2], srcHSL);
+                            float[] dstHSL = new float[3];
+                            RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+                            HSLtoRGB(dstHSL[0], dstHSL[1], srcHSL[2], result);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+
+                            return result;
+                        }
+                    };
+                case MULTIPLY:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = (src[i] * dst[i]) >> 8;
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case NEGATION:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                255 - Math.abs(255 - dst[0] - src[0]),
+                                255 - Math.abs(255 - dst[1] - src[1]),
+                                255 - Math.abs(255 - dst[2] - src[2]),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case OVERLAY:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            for (int i = 0; i < 3; i++) {
+                                result[i] = dst[i] < 128 ? dst[i] * src[i] >> 7 :
+                                    255 - ((255 - dst[i]) * (255 - src[i]) >> 7);
+                            }
+                            result[3] = Math.min(255, src[3] + dst[3]);
+                            return result;
+                        }
+                    };
+                case RED:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                src[0],
+                                dst[1],
+                                dst[2],
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case REFLECT:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                src[0] == 255 ? 255 : Math.min(255, dst[0] * dst[0] / (255 - src[0])),
+                                src[1] == 255 ? 255 : Math.min(255, dst[1] * dst[1] / (255 - src[1])),
+                                src[2] == 255 ? 255 : Math.min(255, dst[2] * dst[2] / (255 - src[2])),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case SATURATION:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            float[] srcHSL = new float[3];
+                            RGBtoHSL(src[0], src[1], src[2], srcHSL);
+                            float[] dstHSL = new float[3];
+                            RGBtoHSL(dst[0], dst[1], dst[2], dstHSL);
+
+                            HSLtoRGB(dstHSL[0], srcHSL[1], dstHSL[2], result);
+                            result[3] = Math.min(255, src[3] + dst[3]);
+
+                            return result;
+                        }
+                    };
+                case SCREEN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                255 - ((255 - src[0]) * (255 - dst[0]) >> 8),
+                                255 - ((255 - src[1]) * (255 - dst[1]) >> 8),
+                                255 - ((255 - src[2]) * (255 - dst[2]) >> 8),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case SOFT_BURN:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0] + src[0] < 256 ?
+	                                (dst[0] == 255 ? 255 :
+                                        Math.min(255, (src[0] << 7) / (255 - dst[0]))) :
+                                            Math.max(0, 255 - (((255 - dst[0]) << 7) / src[0])),
+                                dst[1] + src[1] < 256 ?
+	                                (dst[1] == 255 ? 255 :
+                                        Math.min(255, (src[1] << 7) / (255 - dst[1]))) :
+                                            Math.max(0, 255 - (((255 - dst[1]) << 7) / src[1])),
+                                dst[2] + src[2] < 256 ?
+	                                (dst[2] == 255 ? 255 :
+                                        Math.min(255, (src[2] << 7) / (255 - dst[2]))) :
+                                            Math.max(0, 255 - (((255 - dst[2]) << 7) / src[2])),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case SOFT_DODGE:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                dst[0] + src[0] < 256 ?
+                                    (src[0] == 255 ? 255 :
+                                        Math.min(255, (dst[0] << 7) / (255 - src[0]))) :
+                                            Math.max(0, 255 - (((255 - src[0]) << 7) / dst[0])),
+                                dst[1] + src[1] < 256 ?
+                                    (src[1] == 255 ? 255 :
+                                        Math.min(255, (dst[1] << 7) / (255 - src[1]))) :
+                                            Math.max(0, 255 - (((255 - src[1]) << 7) / dst[1])),
+                                dst[2] + src[2] < 256 ?
+                                    (src[2] == 255 ? 255 :
+                                        Math.min(255, (dst[2] << 7) / (255 - src[2]))) :
+                                            Math.max(0, 255 - (((255 - src[2]) << 7) / dst[2])),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case SOFT_LIGHT:
+                    break;
+                case STAMP:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                Math.max(0, Math.min(255, dst[0] + 2 * src[0] - 256)),
+                                Math.max(0, Math.min(255, dst[1] + 2 * src[1] - 256)),
+                                Math.max(0, Math.min(255, dst[2] + 2 * src[2] - 256)),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+                case SUBTRACT:
+                    return new Blender() {
+                        @Override
+                        public int[] blend(int[] src, int[] dst, int[] result) {
+                            return new int[] {
+                                Math.max(0, src[0] + dst[0] - 256),
+                                Math.max(0, src[1] + dst[1] - 256),
+                                Math.max(0, src[2] + dst[2] - 256),
+                                Math.min(255, src[3] + dst[3])
+                            };
+                        }
+                    };
+            }
+            throw new IllegalArgumentException("Blender not implement for " +
+                                               composite.getMode().name());
+        }
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 7c8ef70..2ff0fc1 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -20,6 +20,7 @@
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.layoutlib.bridge.impl.GcSnapshot;
+import com.android.layoutlib.bridge.impl.PorterDuffUtility;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
 import android.graphics.Bitmap.Config;
@@ -534,7 +535,8 @@
                 // set the color
                 graphics.setColor(new Color(color, true /*alpha*/));
 
-                Composite composite = PorterDuffXfermode_Delegate.getComposite(mode, 0xFF);
+                Composite composite = PorterDuffUtility.getComposite(
+                        PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
                 if (composite != null) {
                     graphics.setComposite(composite);
                 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
index ee90595..4ac376c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
@@ -21,11 +21,10 @@
 
 import android.graphics.PorterDuff.Mode;
 
-import java.awt.AlphaComposite;
 import java.awt.Graphics2D;
 import java.awt.image.BufferedImage;
 
-import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getAlphaCompositeRule;
+import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getComposite;
 import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
 
 /**
@@ -57,7 +56,7 @@
 
     @Override
     public boolean isSupported() {
-        return getAlphaCompositeRule(mMode) != -1;
+        return true;
     }
 
     @Override
@@ -68,7 +67,7 @@
     @Override
     public void applyFilter(Graphics2D g, int width, int height) {
         BufferedImage image = createFilterImage(width, height);
-        g.setComposite(getComposite());
+        g.setComposite(getComposite(mMode, 0xFF));
         g.drawImage(image, 0, 0, null);
     }
 
@@ -101,49 +100,36 @@
         return image;
     }
 
-    private AlphaComposite getComposite() {
-        return AlphaComposite.getInstance(getAlphaCompositeRule(mMode));
-    }
-
     // For filtering the colors, the src image should contain the "color" only for pixel values
     // which are not transparent in the target image. But, we are using a simple rectangular image
-    // completely filled with color. Hence some AlphaComposite rules do not apply as intended.
-    // However, in such cases, they can usually be mapped to some other mode, which produces an
+    // completely filled with color. Hence some Composite rules do not apply as intended. However,
+    // in such cases, they can usually be mapped to some other mode, which produces an
     // equivalent result.
     private Mode getCompatibleMode(Mode mode) {
         Mode m = mode;
+        // Modes that are directly supported:
+        // CLEAR, DST, SRC_IN, DST_IN, DST_OUT, SRC_ATOP, DARKEN, LIGHTEN, MULTIPLY, SCREEN,
+        // ADD, OVERLAY
         switch (mode) {
-            // Modes that are directly supported.
-            case CLEAR:
-            case DST:
-            case SRC_IN:
-            case DST_IN:
-            case DST_OUT:
-            case SRC_ATOP:
-                break;
-            // Modes that can be mapped to one of the supported modes.
-            case SRC:
-                m = Mode.SRC_IN;
-                break;
-            case SRC_OVER:
-                m = Mode.SRC_ATOP;
-                break;
-            case DST_OVER:
-                m = Mode.DST;
-                break;
-            case SRC_OUT:
-                m = Mode.CLEAR;
-                break;
-            case DST_ATOP:
-                m = Mode.DST_IN;
-                break;
-            case XOR:
-                m = Mode.DST_OUT;
-                break;
-            // This mode is not supported, but used by Action Bar Overflow Popup Menus. We map this
-            // to the closest supported mode, to prevent showing excessive warnings to the user.
-            case MULTIPLY:
-                m = Mode.SRC_IN;
+        // Modes that can be mapped to one of the supported modes.
+        case SRC:
+            m = Mode.SRC_IN;
+            break;
+        case SRC_OVER:
+            m = Mode.SRC_ATOP;
+            break;
+        case DST_OVER:
+            m = Mode.DST;
+            break;
+        case SRC_OUT:
+            m = Mode.CLEAR;
+            break;
+        case DST_ATOP:
+            m = Mode.DST_IN;
+            break;
+        case XOR:
+            m = Mode.DST_OUT;
+            break;
         }
         return m;
     }
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
index f6c36b6..8825f84 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
@@ -16,17 +16,14 @@
 
 package android.graphics;
 
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.PorterDuffUtility;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
 import android.graphics.PorterDuff.Mode;
 
-import java.awt.AlphaComposite;
 import java.awt.Composite;
 
-import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getAlphaCompositeRule;
 import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
 
 /**
@@ -58,7 +55,7 @@
 
     @Override
     public Composite getComposite(int alpha) {
-        return getComposite(mMode, alpha);
+        return PorterDuffUtility.getComposite(mMode, alpha);
     }
 
     @Override
@@ -72,9 +69,6 @@
         return null;
     }
 
-    public static Composite getComposite(int mode, int alpha) {
-        return getComposite(getPorterDuffMode(mode), alpha);
-    }
 
     // ---- native methods ----
 
@@ -90,17 +84,4 @@
         mMode = getPorterDuffMode(mode);
     }
 
-    private static Composite getComposite(Mode mode, int alpha255) {
-        float alpha1 = alpha255 != 0xFF ? alpha255 / 255.f : 1.f;
-        int rule = getAlphaCompositeRule(mode);
-        if (rule >= 0) {
-            return AlphaComposite.getInstance(rule, alpha1);
-        }
-
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
-                String.format("Unsupported PorterDuff Mode: %1$s", mode.name()),
-                null, null /*data*/);
-
-        return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
-    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
index bc53e93..9588035 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
@@ -19,11 +19,14 @@
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.layoutlib.bridge.Bridge;
 
+import android.graphics.BlendComposite;
+import android.graphics.BlendComposite.BlendingMode;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter_Delegate;
 import android.graphics.PorterDuffXfermode_Delegate;
 
 import java.awt.AlphaComposite;
+import java.awt.Composite;
 
 /**
  * Provides various utility methods for {@link PorterDuffColorFilter_Delegate} and {@link
@@ -51,46 +54,54 @@
     }
 
     /**
-     * A utility method to convert the porterDuffMode to an int to be used as a rule for {@link
-     * AlphaComposite}. If {@code AlphaComposite} doesn't support the mode, -1 is returned.
+     * A utility method to get the {@link Composite} that represents the filter for the given
+     * PorterDuff mode and the alpha. Defaults to {@link Mode#SRC_OVER} for invalid modes.
      */
-    public static int getAlphaCompositeRule(Mode porterDuffMode) {
-        switch (porterDuffMode) {
+    public static Composite getComposite(Mode mode, int alpha255) {
+        float alpha1 = alpha255 != 0xFF ? alpha255 / 255.f : 1.f;
+        switch (mode) {
             case CLEAR:
-                return AlphaComposite.CLEAR;
-            case DARKEN:
-                break;
-            case DST:
-                return AlphaComposite.DST;
-            case DST_ATOP:
-                return AlphaComposite.DST_ATOP;
-            case DST_IN:
-                return AlphaComposite.DST_IN;
-            case DST_OUT:
-                return AlphaComposite.DST_OUT;
-            case DST_OVER:
-                return AlphaComposite.DST_OVER;
-            case LIGHTEN:
-                break;
-            case MULTIPLY:
-                break;
-            case SCREEN:
-                break;
+                return AlphaComposite.getInstance(AlphaComposite.CLEAR, alpha1);
             case SRC:
-                return AlphaComposite.SRC;
-            case SRC_ATOP:
-                return AlphaComposite.SRC_ATOP;
-            case SRC_IN:
-                return AlphaComposite.SRC_IN;
-            case SRC_OUT:
-                return AlphaComposite.SRC_OUT;
+                return AlphaComposite.getInstance(AlphaComposite.SRC, alpha1);
+            case DST:
+                return AlphaComposite.getInstance(AlphaComposite.DST, alpha1);
             case SRC_OVER:
-                return AlphaComposite.SRC_OVER;
+                return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
+            case DST_OVER:
+                return AlphaComposite.getInstance(AlphaComposite.DST_OVER, alpha1);
+            case SRC_IN:
+                return AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha1);
+            case DST_IN:
+                return AlphaComposite.getInstance(AlphaComposite.DST_IN, alpha1);
+            case SRC_OUT:
+                return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, alpha1);
+            case DST_OUT:
+                return AlphaComposite.getInstance(AlphaComposite.DST_OUT, alpha1);
+            case SRC_ATOP:
+                return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha1);
+            case DST_ATOP:
+                return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, alpha1);
             case XOR:
-                return AlphaComposite.XOR;
-        }
-        // This is an unsupported mode.
-        return -1;
+                return AlphaComposite.getInstance(AlphaComposite.XOR, alpha1);
+            case DARKEN:
+                return BlendComposite.getInstance(BlendingMode.DARKEN, alpha1);
+            case LIGHTEN:
+                return BlendComposite.getInstance(BlendingMode.LIGHTEN, alpha1);
+            case MULTIPLY:
+                return BlendComposite.getInstance(BlendingMode.MULTIPLY, alpha1);
+            case SCREEN:
+                return BlendComposite.getInstance(BlendingMode.SCREEN, alpha1);
+            case ADD:
+                return BlendComposite.getInstance(BlendingMode.ADD, alpha1);
+            case OVERLAY:
+                return BlendComposite.getInstance(BlendingMode.OVERLAY, alpha1);
+            default:
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
+                        String.format("Unsupported PorterDuff Mode: %1$s", mode.name()),
+                        null, null /*data*/);
 
+                return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
+        }
     }
 }