Merge "Make setting the session token in MediaBrowserService synchronous" into lmp-mr1-dev
diff --git a/Android.mk b/Android.mk
index ba45e6f..1416472 100644
--- a/Android.mk
+++ b/Android.mk
@@ -100,6 +100,7 @@
 	core/java/android/bluetooth/IBluetoothA2dpSink.aidl \
 	core/java/android/bluetooth/IBluetoothAvrcpController.aidl \
 	core/java/android/bluetooth/IBluetoothCallback.aidl \
+	core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl \
 	core/java/android/bluetooth/IBluetoothHeadset.aidl \
 	core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl \
 	core/java/android/bluetooth/IBluetoothHealth.aidl \
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index e18aa5c..5a44a74 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -114,6 +114,15 @@
     private boolean mPlayingBackwards = false;
 
     /**
+     * Flag to indicate whether this animator is playing in reverse mode, specifically
+     * by being started or interrupted by a call to reverse(). This flag is different than
+     * mPlayingBackwards, which indicates merely whether the current iteration of the
+     * animator is playing in reverse. It is used in corner cases to determine proper end
+     * behavior.
+     */
+    private boolean mReversing;
+
+    /**
      * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the
      * repeatCount (if repeatCount!=INFINITE), the animation ends
      */
@@ -545,21 +554,51 @@
      * Sets the position of the animation to the specified fraction. This fraction should
      * be between 0 and the total fraction of the animation, including any repetition. That is,
      * a fraction of 0 will position the animation at the beginning, a value of 1 at the end,
-     * and a value of 2 at the beginning of a reversing animator that repeats once. If
+     * and a value of 2 at the end of a reversing animator that repeats once. If
      * the animation has not yet been started, then it will not advance forward after it is
      * set to this fraction; it will simply set the fraction to this value and perform any
      * appropriate actions based on that fraction. If the animation is already running, then
      * setCurrentFraction() will set the current fraction to this value and continue
-     * playing from that point.
+     * playing from that point. {@link AnimatorListener} events are not called
+     * due to changing the fraction; those events are only processed while the animation
+     * is running.
      *
-     * @param fraction The fraction to which the animation is advanced or rewound.
+     * @param fraction The fraction to which the animation is advanced or rewound. Values
+     * outside the range of 0 to the maximum fraction for the animator will be clamped to
+     * the correct range.
      */
     public void setCurrentFraction(float fraction) {
         initAnimation();
+        if (fraction < 0) {
+            fraction = 0;
+        }
+        int iteration = (int) fraction;
+        if (fraction == 1) {
+            iteration -= 1;
+        } else if (fraction > 1) {
+            if (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE) {
+                if (mRepeatMode == REVERSE) {
+                    mPlayingBackwards = (iteration % 2) != 0;
+                }
+                fraction = fraction % 1f;
+            } else {
+                fraction = 1;
+                iteration -= 1;
+            }
+        } else {
+            mPlayingBackwards = mReversing;
+        }
+        mCurrentIteration = iteration;
+        long seekTime = (long) (mDuration * fraction);
+        long currentTime = AnimationUtils.currentAnimationTimeMillis();
+        mStartTime = currentTime - seekTime;
         if (mPlayingState != RUNNING) {
             mSeekFraction = fraction;
             mPlayingState = SEEKED;
         }
+        if (mPlayingBackwards) {
+            fraction = 1f - fraction;
+        }
         animateValue(fraction);
     }
 
@@ -962,8 +1001,30 @@
         if (Looper.myLooper() == null) {
             throw new AndroidRuntimeException("Animators may only be run on Looper threads");
         }
+        mReversing = playBackwards;
         mPlayingBackwards = playBackwards;
-        mCurrentIteration = 0;
+        if (playBackwards && mSeekFraction != -1) {
+            if (mSeekFraction == 0 && mCurrentIteration == 0) {
+                // special case: reversing from seek-to-0 should act as if not seeked at all
+                mSeekFraction = 0;
+            } else if (mRepeatCount == INFINITE) {
+                mSeekFraction = 1 - (mSeekFraction % 1);
+            } else {
+                mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
+            }
+            mCurrentIteration = (int) mSeekFraction;
+            mSeekFraction = mSeekFraction % 1;
+        }
+        if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
+                (mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
+            // if we were seeked to some other iteration in a reversing animator,
+            // figure out the correct direction to start playing based on the iteration
+            if (playBackwards) {
+                mPlayingBackwards = (mCurrentIteration % 2) == 0;
+            } else {
+                mPlayingBackwards = (mCurrentIteration % 2) != 0;
+            }
+        }
         int prevPlayingState = mPlayingState;
         mPlayingState = STOPPED;
         mStarted = true;
@@ -1071,6 +1132,7 @@
             long currentPlayTime = currentTime - mStartTime;
             long timeLeft = mDuration - currentPlayTime;
             mStartTime = currentTime - timeLeft;
+            mReversing = !mReversing;
         } else if (mStarted) {
             end();
         } else {
@@ -1113,6 +1175,8 @@
         mStarted = false;
         mStartListenersCalled = false;
         mPlayingBackwards = false;
+        mReversing = false;
+        mCurrentIteration = 0;
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                     System.identityHashCode(this));
@@ -1201,8 +1265,16 @@
         case RUNNING:
         case SEEKED:
             float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
+            if (mDuration == 0 && mRepeatCount != INFINITE) {
+                // Skip to the end
+                mCurrentIteration = mRepeatCount;
+                if (!mReversing) {
+                    mPlayingBackwards = false;
+                }
+            }
             if (fraction >= 1f) {
-                if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
+                if (mCurrentIteration < mRepeatCount ||
+                        (mRepeatCount == INFINITE && mDuration != 0)) {
                     // Time to repeat
                     if (mListeners != null) {
                         int numListeners = mListeners.size();
@@ -1213,7 +1285,7 @@
                     if (mRepeatMode == REVERSE) {
                         mPlayingBackwards = !mPlayingBackwards;
                     }
-                    mCurrentIteration += (int)fraction;
+                    mCurrentIteration += (int) fraction;
                     fraction = fraction % 1f;
                     mStartTime += mDuration;
                 } else {
@@ -1313,6 +1385,7 @@
         }
         anim.mSeekFraction = -1;
         anim.mPlayingBackwards = false;
+        anim.mReversing = false;
         anim.mCurrentIteration = 0;
         anim.mInitialized = false;
         anim.mPlayingState = STOPPED;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 06a26ec..e8d08b8 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2363,6 +2363,13 @@
             reply.writeNoException();
             return true;
         }
+
+        case SYSTEM_BACKUP_RESTORED: {
+            data.enforceInterface(IActivityManager.descriptor);
+            systemBackupRestored();
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -5458,5 +5465,16 @@
         reply.recycle();
     }
 
+    @Override
+    public void systemBackupRestored() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(SYSTEM_BACKUP_RESTORED, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index 49ab7c1..6c2511e 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -168,10 +168,20 @@
         PackageManager pm = context.getPackageManager();
 
         // look for receiver in the installer package
-        String candidate = pm.getInstallerPackageName(packageName);
-        ComponentName result = getErrorReportReceiver(pm, packageName, candidate);
-        if (result != null) {
-            return result;
+        String candidate = null;
+        ComponentName result = null;
+
+        try {
+            candidate = pm.getInstallerPackageName(packageName);
+        } catch (IllegalArgumentException e) {
+            // the package could already removed
+        }
+
+        if (candidate != null) {
+            result = getErrorReportReceiver(pm, packageName, candidate);
+            if (result != null) {
+                return result;
+            }
         }
 
         // if the error app is on the system image, look for system apps
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1ccbd27..e505d69 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -469,6 +469,8 @@
     public void notifyLaunchTaskBehindComplete(IBinder token) throws RemoteException;
     public void notifyEnterAnimationComplete(IBinder token) throws RemoteException;
 
+    public void systemBackupRestored() throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -790,4 +792,5 @@
     int START_IN_PLACE_ANIMATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+240;
     int CHECK_PERMISSION_WITH_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+241;
     int REGISTER_TASK_STACK_LISTENER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+242;
+    int SYSTEM_BACKUP_RESTORED = IBinder.FIRST_CALL_TRANSACTION+243;
 }
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index c203a8e..ea2dca0 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -928,7 +928,7 @@
                 BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false;
 
         if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
-        if (mService == null || mClientIf == 0) return false;
+        if (mService == null || mClientIf == 0 || characteristic.getValue() == null) return false;
 
         BluetoothGattService service = characteristic.getService();
         if (service == null) return false;
@@ -1015,7 +1015,7 @@
      */
     public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
         if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
-        if (mService == null || mClientIf == 0) return false;
+        if (mService == null || mClientIf == 0 || descriptor.getValue() == null) return false;
 
         BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
         if (characteristic == null) return false;
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 353f0fb..546a50e 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -20,9 +20,10 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -221,11 +222,14 @@
      */
     public static final int STATE_AUDIO_CONNECTED = 12;
 
+    private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
+    private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
 
     private Context mContext;
     private ServiceListener mServiceListener;
     private IBluetoothHeadset mService;
     private BluetoothAdapter mAdapter;
+    private boolean mIsClosed;
 
     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
             new IBluetoothStateChangeCallback.Stub() {
@@ -233,14 +237,7 @@
                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
                     if (!up) {
                         if (VDBG) Log.d(TAG,"Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG,"",re);
-                            }
-                        }
+                        doUnbind();
                     } else {
                         synchronized (mConnection) {
                             try {
@@ -263,6 +260,7 @@
         mContext = context;
         mServiceListener = l;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mIsClosed = false;
 
         IBluetoothManager mgr = mAdapter.getBluetoothManager();
         if (mgr != null) {
@@ -277,15 +275,26 @@
     }
 
     boolean doBind() {
-        Intent intent = new Intent(IBluetoothHeadset.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                android.os.Process.myUserHandle())) {
-            Log.e(TAG, "Could not bind to Bluetooth Headset Service with " + intent);
-            return false;
+        try {
+            return mAdapter.getBluetoothManager().bindBluetoothProfileService(
+                    BluetoothProfile.HEADSET, mConnection);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to bind HeadsetService", e);
         }
-        return true;
+        return false;
+    }
+
+    void doUnbind() {
+        synchronized (mConnection) {
+            if (mService != null) {
+                try {
+                    mAdapter.getBluetoothManager().unbindBluetoothProfileService(
+                            BluetoothProfile.HEADSET, mConnection);
+                } catch (RemoteException e) {
+                    Log.e(TAG,"Unable to unbind HeadsetService", e);
+                }
+            }
+        }
     }
 
     /**
@@ -305,18 +314,8 @@
                 Log.e(TAG,"",e);
             }
         }
-
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG,"",re);
-                }
-            }
-        }
-        mServiceListener = null;
+        mIsClosed = true;
+        doUnbind();
     }
 
     /**
@@ -930,21 +929,21 @@
         return false;
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
+    private final IBluetoothProfileServiceConnection mConnection
+            = new IBluetoothProfileServiceConnection.Stub()  {
+        @Override
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
             mService = IBluetoothHeadset.Stub.asInterface(service);
-
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
-            }
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    MESSAGE_HEADSET_SERVICE_CONNECTED));
         }
+        @Override
         public void onServiceDisconnected(ComponentName className) {
             if (DBG) Log.d(TAG, "Proxy object disconnected");
             mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
-            }
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    MESSAGE_HEADSET_SERVICE_DISCONNECTED));
         }
     };
 
@@ -968,4 +967,28 @@
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_HEADSET_SERVICE_CONNECTED: {
+                    if (mServiceListener != null) {
+                        mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
+                                BluetoothHeadset.this);
+                    }
+                    break;
+                }
+                case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
+                    if (mServiceListener != null) {
+                        mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
+                    }
+                    if (mIsClosed){
+                        mServiceListener = null;
+                    }
+                    break;
+                }
+            }
+        }
+    };
 }
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
index 493d2f8..7411d3f 100644
--- a/core/java/android/bluetooth/IBluetoothManager.aidl
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -19,6 +19,7 @@
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothGatt;
 import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothProfileServiceConnection;
 import android.bluetooth.IBluetoothStateChangeCallback;
 
 /**
@@ -38,6 +39,9 @@
     boolean disable(boolean persist);
     IBluetoothGatt getBluetoothGatt();
 
+    boolean bindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);
+    void unbindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);
+
     String getAddress();
     String getName();
 }
diff --git a/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl b/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl
new file mode 100755
index 0000000..96c59e2
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.bluetooth;
+
+import android.content.ComponentName;
+import android.os.IBinder;
+
+/**
+ * Callback for bluetooth profile connections.
+ *
+ * {@hide}
+ */
+interface IBluetoothProfileServiceConnection {
+    void onServiceConnected(in ComponentName comp, in IBinder service);
+    void onServiceDisconnected(in ComponentName comp);
+}
diff --git a/core/java/android/hardware/location/GeofenceHardwareImpl.java b/core/java/android/hardware/location/GeofenceHardwareImpl.java
index 5c7a8da..6e5d064 100644
--- a/core/java/android/hardware/location/GeofenceHardwareImpl.java
+++ b/core/java/android/hardware/location/GeofenceHardwareImpl.java
@@ -23,6 +23,7 @@
 import android.location.Location;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IInterface;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -30,6 +31,7 @@
 import android.util.SparseArray;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 
 /**
  * This class manages the geofences which are handled by hardware.
@@ -558,8 +560,34 @@
                         try {
                             callback.onGeofenceRemove(geofenceId, msg.arg2);
                         } catch (RemoteException e) {}
+                        IBinder callbackBinder = callback.asBinder();
+                        boolean callbackInUse = false;
                         synchronized (mGeofences) {
                             mGeofences.remove(geofenceId);
+                            // Check if the underlying binder is still useful for other geofences,
+                            // if no, unlink the DeathRecipient to avoid memory leak.
+                            for (int i = 0; i < mGeofences.size(); i++) {
+                                 if (mGeofences.valueAt(i).asBinder() == callbackBinder) {
+                                     callbackInUse = true;
+                                     break;
+                                 }
+                            }
+                        }
+
+                        // Remove the reaper associated with this binder.
+                        if (!callbackInUse) {
+                            for (Iterator<Reaper> iterator = mReapers.iterator();
+                                    iterator.hasNext();) {
+                                Reaper reaper = iterator.next();
+                                if (reaper.mCallback != null &&
+                                        reaper.mCallback.asBinder() == callbackBinder) {
+                                    iterator.remove();
+                                    reaper.unlinkToDeath();
+                                    if (DEBUG) Log.d(TAG, String.format("Removed reaper %s " +
+                                          "because binder %s is no longer needed.",
+                                          reaper, callbackBinder));
+                                }
+                            }
                         }
                     }
                     releaseWakeLock();
@@ -803,8 +831,9 @@
         @Override
         public int hashCode() {
             int result = 17;
-            result = 31 * result + (mCallback != null ? mCallback.hashCode() : 0);
-            result = 31 * result + (mMonitorCallback != null ? mMonitorCallback.hashCode() : 0);
+            result = 31 * result + (mCallback != null ? mCallback.asBinder().hashCode() : 0);
+            result = 31 * result + (mMonitorCallback != null
+                    ? mMonitorCallback.asBinder().hashCode() : 0);
             result = 31 * result + mMonitoringType;
             return result;
         }
@@ -815,9 +844,38 @@
             if (obj == this) return true;
 
             Reaper rhs = (Reaper) obj;
-            return rhs.mCallback == mCallback && rhs.mMonitorCallback == mMonitorCallback &&
+            return binderEquals(rhs.mCallback, mCallback) &&
+                    binderEquals(rhs.mMonitorCallback, mMonitorCallback) &&
                     rhs.mMonitoringType == mMonitoringType;
         }
+
+        /**
+         * Compares the underlying Binder of the given two IInterface objects and returns true if
+         * they equals. null values are accepted.
+         */
+        private boolean binderEquals(IInterface left, IInterface right) {
+          if (left == null) {
+            return right == null;
+          } else {
+            return right == null ? false : left.asBinder() == right.asBinder();
+          }
+        }
+
+        /**
+         * Unlinks this DeathRecipient.
+         */
+        private boolean unlinkToDeath() {
+          if (mMonitorCallback != null) {
+            return mMonitorCallback.asBinder().unlinkToDeath(this, 0);
+          } else if (mCallback != null) {
+            return mCallback.asBinder().unlinkToDeath(this, 0);
+          }
+          return true;
+        }
+
+        private boolean callbackEquals(IGeofenceHardwareCallback cb) {
+          return mCallback != null && mCallback.asBinder() == cb.asBinder();
+        }
     }
 
     int getAllowedResolutionLevel(int pid, int uid) {
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 02297e3..74b7b69 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -416,7 +416,11 @@
                             currentTextWidth = widths[here - paraStart];
                         }
 
-                        v = out(source, here, endPos,
+                        int ellipseEnd = endPos;
+                        if (mMaximumVisibleLineCount == 1 && ellipsize == TextUtils.TruncateAt.MIDDLE) {
+                            ellipseEnd = paraEnd;
+                        }
+                        v = out(source, here, ellipseEnd,
                                 above, below, top, bottom,
                                 v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji,
                                 needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
@@ -704,7 +708,7 @@
                 int left = 0, right = len;
 
                 float ravail = (avail - ellipsisWidth) / 2;
-                for (right = len; right >= 0; right--) {
+                for (right = len; right > 0; right--) {
                     float w = widths[right - 1 + lineStart - widthStart];
 
                     if (w + rsum > ravail) {
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index c1341e1..14be269 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -402,7 +402,7 @@
         }
 
         boolean hasPrefix = false;
-        
+
         for (int i = 0; i < prefixes.length; i++) {
             if (url.regionMatches(true, 0, prefixes[i], 0,
                                   prefixes[i].length())) {
@@ -450,7 +450,7 @@
     private static final void gatherTelLinks(ArrayList<LinkSpec> links, Spannable s) {
         PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
         Iterable<PhoneNumberMatch> matches = phoneUtil.findNumbers(s.toString(),
-                Locale.getDefault().getCountry(), Leniency.POSSIBLE, Long.MAX_VALUE);
+                Locale.getDefault().getCountry(), Leniency.VALID, Long.MAX_VALUE);
         for (PhoneNumberMatch match : matches) {
             LinkSpec spec = new LinkSpec();
             spec.url = "tel:" + PhoneNumberUtils.normalizeNumber(match.rawString());
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 2e5c1e0..6944c53 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -364,7 +364,7 @@
         /**
          * This hook is called whenever the window focus changes.  See
          * {@link View#onWindowFocusChanged(boolean)
-         * View.onWindowFocusChanged(boolean)} for more information.
+         * View.onWindowFocusChangedNotLocked(boolean)} for more information.
          *
          * @param hasFocus Whether the window now has focus.
          */
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f4f047e..094a8a1 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1102,6 +1102,13 @@
         public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400;
 
         /**
+         * Flag that prevents the wallpaper behind the current window from receiving touch events.
+         *
+         * {@hide}
+         */
+        public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;
+
+        /**
          * Control flags that are private to the platform.
          * @hide
          */
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index ed7ce63..35a1a5a 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -17,6 +17,7 @@
 package com.android.server.backup;
 
 
+import android.app.ActivityManagerNative;
 import android.app.IWallpaperManager;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
@@ -186,4 +187,13 @@
             }
         }
     }
+
+    @Override
+    public void onRestoreFinished() {
+        try {
+            ActivityManagerNative.getDefault().systemBackupRestored();
+        } catch (RemoteException e) {
+            // Not possible since this code is running in the system process.
+        }
+    }
 }
diff --git a/core/res/res/drawable/btn_default_mtrl_shape.xml b/core/res/res/drawable/btn_default_mtrl_shape.xml
index 6d0f7f8..8a31d5e 100644
--- a/core/res/res/drawable/btn_default_mtrl_shape.xml
+++ b/core/res/res/drawable/btn_default_mtrl_shape.xml
@@ -21,9 +21,10 @@
        android:insetTop="@dimen/button_inset_vertical_material"
        android:insetRight="@dimen/button_inset_horizontal_material"
        android:insetBottom="@dimen/button_inset_vertical_material">
-    <shape android:shape="rectangle">
+    <shape android:shape="rectangle"
+           android:tint="?attr/colorButtonNormal">
         <corners android:radius="@dimen/control_corner_material" />
-        <solid android:color="?attr/colorButtonNormal" />
+        <solid android:color="@color/white" />
         <padding android:left="@dimen/button_padding_horizontal_material"
                  android:top="@dimen/button_padding_vertical_material"
                  android:right="@dimen/button_padding_horizontal_material"
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index dd1f815..8cc2a8a 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -89,7 +89,7 @@
     <!-- Elevation when button is pressed -->
     <dimen name="button_elevation_material">4dp</dimen>
     <!-- Z translation to apply when button is pressed -->
-    <dimen name="button_pressed_z_material">6dp</dimen>
+    <dimen name="button_pressed_z_material">2dp</dimen>
     <!-- Default insets (outer padding) around buttons -->
     <dimen name="button_inset_vertical_material">6dp</dimen>
     <dimen name="button_inset_horizontal_material">@dimen/control_inset_material</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e861ab2..90b8383 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2139,6 +2139,7 @@
   <java-symbol type="string" name="system_error_manufacturer" />
   <java-symbol type="dimen" name="fast_scroller_minimum_touch_target" />
   <java-symbol type="array" name="config_cdma_international_roaming_indicators" />
+  <java-symbol type="string" name="kg_text_message_separator" />
 
   <java-symbol type="bool" name="config_use_sim_language_file" />
 </resources>
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
index 0244891..f94fe66 100644
--- a/data/fonts/fallback_fonts.xml
+++ b/data/fonts/fallback_fonts.xml
@@ -229,6 +229,12 @@
     </family>
     <family>
         <fileset>
+            <file>NotoSansCham-Regular.ttf</file>
+            <file>NotoSansCham-Bold.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
             <file>NotoSansBalinese-Regular.ttf</file>
         </fileset>
     </family>
@@ -259,6 +265,16 @@
     </family>
     <family>
         <fileset>
+            <file>NotoSansCoptic-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansGlagolitic-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
             <file>NotoSansHanunoo-Regular.ttf</file>
         </fileset>
     </family>
@@ -269,6 +285,11 @@
     </family>
     <family>
         <fileset>
+            <file>NotoSansKayahLi-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
             <file>NotoSansLepcha-Regular.ttf</file>
         </fileset>
     </family>
@@ -314,6 +335,26 @@
     </family>
     <family>
         <fileset>
+            <file>NotoSansTaiLe-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansTaiTham-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansTaiViet-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansTifinagh-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
             <file>NotoSansYi-Regular.ttf</file>
         </fileset>
     </family>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index a62e972..02bf877 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -224,6 +224,10 @@
         <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
     </family>
     <family>
+        <font weight="400" style="normal">NotoSansCham-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+    </family>
+    <family>
         <font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font>
     </family>
     <family>
@@ -242,12 +246,21 @@
         <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
     </family>
     <family>
+        <font weight="400" style="normal">NotoSansCoptic-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font>
+    </family>
+    <family>
         <font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansJavanese-Regular.ttf</font>
     </family>
     <family>
+        <font weight="400" style="normal">NotoSansKayahLi-Regular.ttf</font>
+    </family>
+    <family>
         <font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font>
     </family>
     <family>
@@ -275,6 +288,18 @@
         <font weight="400" style="normal">NotoSansTagbanwa-Regular.ttf</font>
     </family>
     <family>
+        <font weight="400" style="normal">NotoSansTaiLe-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansTaiTham-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansTaiViet-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansTifinagh-Regular.ttf</font>
+    </family>
+    <family>
         <font weight="400" style="normal">NotoSansYi-Regular.ttf</font>
     </family>
     <family>
diff --git a/docs/html/about/versions/kitkat.jd b/docs/html/about/versions/kitkat.jd
index 4237c98..dff3508 100644
--- a/docs/html/about/versions/kitkat.jd
+++ b/docs/html/about/versions/kitkat.jd
@@ -148,7 +148,7 @@
 </p>
 
 <p>
-  New tools give also give you powerful insight into your app's memory use. The
+  New tools also give you powerful insight into your app's memory use. The
   <strong>procstats tool</strong> details memory use over time, with run times
   and memory footprint for foreground apps and background services. An
   on-device view is also available as a new developer option. The
diff --git a/docs/html/guide/topics/ui/settings.jd b/docs/html/guide/topics/ui/settings.jd
index f454c4e..02f1255 100644
--- a/docs/html/guide/topics/ui/settings.jd
+++ b/docs/html/guide/topics/ui/settings.jd
@@ -801,7 +801,7 @@
 
 <h3 id="Listening">Listening for preference changes</h3>
 
-<p>There are several reasons you might want to be notified as soon as the use changes one of the
+<p>There are several reasons you might want to be notified as soon as the user changes one of the
 preferences. In order to receive a callback when a change happens to any one of the preferences,
 implement the {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener
 SharedPreference.OnSharedPreferenceChangeListener} interface and register the listener for the
diff --git a/docs/html/preview/index.html b/docs/html/preview/index.html
index 4f7722c..7cd029f 100644
--- a/docs/html/preview/index.html
+++ b/docs/html/preview/index.html
@@ -272,13 +272,11 @@
   style="position:absolute;z-index:100;right:215px;top:375px">Android 5.0 API Overview</a>
 
       <div style="width:440px">
-<p>Android 5.0 (Lollipop) is almost here and users will begin receiving
-device updates in November. To help you prepare, the Android 5.0 SDK is now available
-with final APIs.</p>
+<p>Android 5.0 (Lollipop) is now out of preview and available to users.</p>
 
-<p>Since the L Developer Preview began, various APIs and behaviors have changed,
-so if you've been using the Preview SDK
-you should update now to test your apps and take advantage of new features.</p>
+<p>If you previously developed apps with the L Developer Preview, be aware that various APIs and
+behaviors have changed, so you should update your SDK now to test your apps and take advantage of
+new features in Android 5.0.</p>
 
 
         <p>To get the latest Android 5.0 SDK:</p>
diff --git a/docs/html/robots.txt b/docs/html/robots.txt
index f522220..ab379bb 100644
--- a/docs/html/robots.txt
+++ b/docs/html/robots.txt
@@ -15,4 +15,5 @@
 Disallow: /guide/tutorials/
 Disallow: /guide/samples/
 Disallow: /community/
+Disallow: /preview/
 Sitemap: http://developer.android.com/sitemap.txt
diff --git a/docs/html/tools/sdk/ndk/index.jd b/docs/html/tools/sdk/ndk/index.jd
index 06474dc..48dceb6 100644
--- a/docs/html/tools/sdk/ndk/index.jd
+++ b/docs/html/tools/sdk/ndk/index.jd
@@ -3,28 +3,28 @@
 
 
 ndk.mac64_download=android-ndk-r10c-darwin-x86_64.bin
-ndk.mac64_bytes=436952863
-ndk.mac64_checksum=bc04ef44b920cf6cd2157b6f2c3531d6
+ndk.mac64_bytes=442691567
+ndk.mac64_checksum=cb101e1e62d56ea75b215f6bc6c27fae
 
 ndk.mac32_download=android-ndk-r10c-darwin-x86.bin
-ndk.mac32_bytes=435858709
-ndk.mac32_checksum=6b3e143f7e64d5cd337b727513e27913
+ndk.mac32_bytes=441545213
+ndk.mac32_checksum=0aeb3dc062dc457a4cd01e72eadb2379
 
 ndk.linux64_download=android-ndk-r10c-linux-x86_64.bin
-ndk.linux64_bytes=449013322
-ndk.linux64_checksum=792c61706cd9ec6713fa1b69b2f42996
+ndk.linux64_bytes=459151600
+ndk.linux64_checksum=263b83071e6bca15f67898548d8d236e
 
 ndk.linux32_download=android-ndk-r10c-linux-x86.bin
-ndk.linux32_bytes=438555265
-ndk.linux32_checksum=d1595d9ca5e15484e047f1ac326c4ceb
+ndk.linux32_bytes=449997190
+ndk.linux32_checksum=70ed6d8c34e7e620c145b791e8eeef89
 
 ndk.win64_download=android-ndk-r10c-windows-x86_64.exe
-ndk.win64_bytes=458925419
-ndk.win64_checksum=af8edf5d316e1bf1a5a72e04a9faec41
+ndk.win64_bytes=472613732
+ndk.win64_checksum=9a33f96da58a7e0b70e47d27b4a880b4
 
 ndk.win32_download=android-ndk-r10c-windows-x86.exe
-ndk.win32_bytes=433102815
-ndk.win32_checksum=805a04810719886674d3c7bff5eca53f
+ndk.win32_bytes=455427281
+ndk.win32_checksum=c0930abfae0c990c4d191cc4ebd46b68
 
 
 
@@ -388,6 +388,133 @@
  <p>
    <a href="#" onclick="return toggleContent(this)"> <img
      src="/assets/images/triangle-opened.png" class="toggle-content-img" alt=""
+   >Android NDK, Revision 10d</a> <em>(December 2014)</em>
+ </p>
+ <div class="toggle-content-toggleme">
+    <dl>
+      <dt>Important changes:</dt>
+      <dd>
+      <ul>
+        <li>Made GCC 4.8 the default for all 32-bit ABIs.  Deprecated GCC 4.6, and
+            will remove it next release. To restore previous behavior, either add
+            <code>NDK_TOOLCHAIN_VERSION=4.6</code> to ndk-build, or
+            add <code>--toolchain=arm-linux-androideabi-4.6</code> when executing
+            <code>make-standalone-toolchain.sh</code> on the command line. GCC 4.9 remains the
+            default for 64-bit ABIs.</li>
+
+         <li>Stopped all x86[_64] toolchains from adding <code>-mstackrealign</code> by default. The
+             NDK toolchain assumes a 16-byte stack alignment. The tools and options used by default
+             enforce this rule. A user writing assembly code must make sure to preserve stack
+             alignment, and ensure that other compilers also comply with this rule.
+             (GCC bug <a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38496">38496</a>)</li>
+
+         <li>Added Address Sanitizer functionality to Clang 3.5 support to the ARM and x86 ABIs.
+             For more information on this change, see the
+             <a href="https://code.google.com/p/address-sanitizer/wiki/Android">Address
+             Sanitizer</a> project.</li>
+
+         <li>Introduced the requirement, starting from API level 21, to use <code>-fPIE -pie
+             </code> when building. In API levels 16 and higher, ndk-build uses <code>PIE</code>
+             when building. This change has a number of implications, which are discussed in
+             <a href="https://code.google.com/p/android-developer-preview/issues/detail?id=888">
+             Developer Preview Issue 888</a>.
+             These implications do not apply to shared libraries.</li>
+      </ul>
+      </dd>
+   <dl>
+
+
+     <dt>Important bug fixes:</dt>
+     <dd>
+     <ul>
+        <li>Made more fixes related to
+            <a href="https://gcc.gnu.org/ml/gcc-patches/2014-10/msg00906.html">
+            A53 Errata #835769</a> in the aarch64-linux-android-4.9 linker. As part of this, GCC
+            passes a new option, <code>--fix-cortex-a53-835769</code>, when
+            <code>-mfix-cortex-a53-835769</code> (enabled by default) is specified.
+            For more information, see this
+            <a href="https://sourceware.org/ml/binutils/2014-10/msg00198.html">binutils message</a>
+            and this
+            <a href="https://sourceware.org/ml/binutils/2014-11/msg00287.html">binutils message</a>.
+            </li>
+
+        <li>Documented a fix to a libc++ <code>sscanf/vsscanf</code> hang that occurred in API level
+            21. The fix itself had been implemented in r10c.
+            (Issue <a href="http://b.android.com/77988">77988</a>)</li>
+
+        <li>Fixed an AutoFDO (<code>-fauto-profile</code>) crash that occurred with GCC 4.9 when
+            <code>-Os</code> was specified. (Issue <a href="http://b.android.com/77571">77571</a>)</li>
+     </ul>
+     </dd>
+
+
+     <dt>Other bug fixes:</dt>
+     <dd>
+     <ul>
+        <li>Made the following header and library fixes:</li>
+           <ul>
+        <li>Added <code>posix_memalign</code> to API level 16. Also, added a prototype in
+            <code>stdlib.h</code> to API levels 16 to 19.
+            (Issue <a href="http://b.android.com/77861">77861</a>)</li>
+        <li>Fixed <code>stdatomic.h</code> so that it includes <code>&lt;atomic&gt;</code> only for
+            C++11.</li>
+        <li>Modified the following headers for standalone use: <code>sys/user.h</code>, and
+            <code>gl2ext.h</code>, <code>dlext.h</code>, <code>fts.h</code>, <code>sgidefs.h</code>
+            for API level 21.</li>
+        <li>Modified <code>sys/user.h</code> to rename <code>mxcsr_mask</code> as <code>mxcr_mask</code>,
+            and to change the data type for <code>u_ar0</code></li> from <code>unsigned long</code>
+            to </code>struct user_regs_struct*</code>.
+        <li>Changed <code>sysconf()</code> return value type from <code>int</code> to
+            <code>long</code>.</li>
+           </ul>
+
+        <li>Fixed ndk-build's handling of <code>thumb</code> for <code>LOCAL_ARM_MODE</code>: In
+            r10d, ndk-build adds <code>LOCAL_LDFLAGS+=-mthumb</code> by default, unless one of the
+            following conditions applies:</li>
+          <ul>
+            <li>You have set <code>LOCAL_ARM_MODE</code> equal to <code>arm</code>.</li>
+            <li>You are doing a debug build (with settings such as <code>APP_OPTIM=debug</code> and
+            <code>AndroidManifest.xml</code> containing <code>android:debuggable="true"</code>),
+            where ARM mode is the default in order to retain compatibility with earlier toolchains.
+            (Issue <a href="http://b.android.com/74040">74040</a>)</li>
+          </ul>
+
+        <li>Fixed <code>LOCAL_SRC_FILES</code> in ndk-build to use Windows absolute paths.
+            (Issue <a href="http://b.android.com/74333">74333</a>)</li>
+
+        <li>Removed bash-specific code from ndk-gdb. (Issue <a href="http://b.android.com/73338">73338</a>)</li>
+
+        <li>Removed bash-specific code from <code>make-standalone-toolchain.sh</code>.
+            (Issue <a href="http://b.android.com/74145">74145)</a></li>
+
+        <li>Revised documentation concerning a fix for <code>System.loadLibrary()</code> transitive
+            dependencies. (Issue <a href="http://b.android.com/41790">41790</a>)</li>
+
+        <li>Fixed a problem that was preventing 64-bit packages from extracting on Ubuntu 14.04 and
+            OS X 10.10 (Yosemite). (Issue <a href="http://b.android.com/78148">78148</a>)</li>
+
+        <li>Fixed an issue with <code>LOCAL_PCH</code> to improve Clang support. (Issue
+            <a href="http://b.android.com/77575">77575</a>)</li>
+
+        <li>Clarified "requires executable stack" warning from ld.gold. (Issue
+            <a href="http://b.android.com/79115">79115</a>)</li>
+     </ul>
+     </dd>
+
+   </dl>
+ </div>
+</div>
+
+
+
+
+
+
+
+<div class="toggle-content closed">
+ <p>
+   <a href="#" onclick="return toggleContent(this)"> <img
+     src="/assets/images/triangle-closed.png" class="toggle-content-img" alt=""
    >Android NDK, Revision 10c</a> <em>(October 2014)</em>
  </p>
  <div class="toggle-content-toggleme">
@@ -570,10 +697,6 @@
  </div>
 </div>
 
-
-
-
-
 <div class="toggle-content closed">
  <p>
    <a href="#" onclick="return toggleContent(this)"> <img
diff --git a/docs/html/training/app-indexing/deep-linking.jd b/docs/html/training/app-indexing/deep-linking.jd
index a52ae95..2679937 100644
--- a/docs/html/training/app-indexing/deep-linking.jd
+++ b/docs/html/training/app-indexing/deep-linking.jd
@@ -61,13 +61,15 @@
         &lt;action android:name="android.intent.action.VIEW" /&gt;
         &lt;category android:name="android.intent.category.DEFAULT" /&gt;
         &lt;category android:name="android.intent.category.BROWSABLE" /&gt;
-        &lt;!-- Accepts URIs that begin with "example://gizmos” --&gt;
-        &lt;data android:scheme="example"
-              android:host="gizmos" /&gt;
         &lt;!-- Accepts URIs that begin with "http://www.example.com/gizmos” --&gt;
         &lt;data android:scheme="http"
               android:host="www.example.com"
-              android:pathPrefix="gizmos" /&gt;
+              android:pathPrefix="/gizmos" /&gt;
+        &lt;!-- note that the leading "/" is required for pathPrefix--&gt;
+        &lt;!-- Accepts URIs that begin with "example://gizmos”
+        &lt;data android:scheme="example"
+              android:host="gizmos" /&gt;
+        --&gt;
     &lt;/intent-filter&gt;
 &lt;/activity&gt;
 </pre>
@@ -76,6 +78,11 @@
 manifest, Android is able to route any {@link android.content.Intent}
 that has matching URIs to your app at runtime.</p>
 
+<p class="note">
+  <strong>Note:</strong> Intent filters may only contain a single {@code data} element
+  for a URI pattern. Create separate intent filters to capture additional URI patterns.
+</p>
+
 <p>To learn more about defining intent filters, see <a href="{@docRoot}training/basics/intents/filters.html">Allow Other Apps to Start Your Activity</a>.</p>
 
 <h2 id="handling-intents">Read Data from Incoming Intents</h2>
diff --git a/docs/html/training/app-indexing/index.jd b/docs/html/training/app-indexing/index.jd
index 7e7241b..45afea8 100644
--- a/docs/html/training/app-indexing/index.jd
+++ b/docs/html/training/app-indexing/index.jd
@@ -24,7 +24,7 @@
 </ul>
 </div>
 </div>
-<a class="notice-developers-video wide" href="http://www.youtube.com/watch?v=Xh_W82JgOms">
+<a class="notice-developers-video wide" href="http://www.youtube.com/watch?v=aISUYHTkTOU">
 <div>
     <h3>Video</h3>
     <p>DevBytes: App Indexing</p>
diff --git a/docs/html/training/wearables/data-layer/index.jd b/docs/html/training/wearables/data-layer/index.jd
index 8d42ae3..85b2c33 100644
--- a/docs/html/training/wearables/data-layer/index.jd
+++ b/docs/html/training/wearables/data-layer/index.jd
@@ -30,9 +30,12 @@
   <dd>The <a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.html"><code>MessageApi</code></a> class
   can send messages and is good for remote procedure calls (RPC), such as controlling a handheld's
   media player from the wearable or starting an intent on the wearable from the handheld.
-  The system always delivers the message when the handheld and wearable are connected and delivers
-  an error when the devices are disconnected. Messages are great for one-way requests or for a
-  request/response communication model.</dd>
+  Messages are also great for one-way requests or for a request/response communication model.
+  If the handheld and wearable are connected, the system queues the message for delivery and
+  returns a successful result code. If the devices are not connected, an error is returned. A
+  successful result code does not indicate that the message was delivered successfully as the
+  devices may disconnect after receiving the result code.
+</p></dd>
 
   <dt><b>Asset</b></dt>
   <dd><a href="{@docRoot}reference/com/google/android/gms/wearable/Asset.html"><code>Asset</code></a> objects are for
diff --git a/docs/html/training/wearables/ui/exit.jd b/docs/html/training/wearables/ui/exit.jd
index 6b205a57..84e1e45 100644
--- a/docs/html/training/wearables/ui/exit.jd
+++ b/docs/html/training/wearables/ui/exit.jd
@@ -67,7 +67,7 @@
         android:id="@+id/dismiss_overlay"
         android:layout_height="match_parent"
         android:layout_width="match_parent"/>
-&lt;/FrameLayout>
+&lt;FrameLayout>
 </pre>
 
 <p>In your activity, obtain the <code>DismissOverlayView</code> element and set some introductory
@@ -100,8 +100,8 @@
 
     // Capture long presses
     &#64;Override
-    public boolean dispatchTouchEvent(MotionEvent e) {
-        return mDetector.onTouchEvent(e) || super.dispatchTouchEvent(e);
+    public boolean onTouchEvent(MotionEvent ev) {
+        return mDetector.onTouchEvent(ev) || super.onTouchEvent(ev);
     }
 }
 </pre>
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 68fd296..2748030 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -54,19 +54,20 @@
     private DrawableContainerState mDrawableContainerState;
     private Rect mHotspotBounds;
     private Drawable mCurrDrawable;
+    private Drawable mLastDrawable;
     private int mAlpha = 0xFF;
 
     /** Whether setAlpha() has been called at least once. */
     private boolean mHasAlpha;
 
     private int mCurIndex = -1;
+    private int mLastIndex = -1;
     private boolean mMutated;
 
     // Animations.
     private Runnable mAnimationRunnable;
     private long mEnterAnimationEnd;
     private long mExitAnimationEnd;
-    private Drawable mLastDrawable;
 
     // overrides from Drawable
 
@@ -255,6 +256,7 @@
         if (mLastDrawable != null) {
             mLastDrawable.jumpToCurrentState();
             mLastDrawable = null;
+            mLastIndex = -1;
             changed = true;
         }
         if (mCurrDrawable != null) {
@@ -426,9 +428,11 @@
             }
             if (mCurrDrawable != null) {
                 mLastDrawable = mCurrDrawable;
+                mLastIndex = mCurIndex;
                 mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration;
             } else {
                 mLastDrawable = null;
+                mLastIndex = -1;
                 mExitAnimationEnd = 0;
             }
         } else if (mCurrDrawable != null) {
@@ -522,6 +526,7 @@
                 if (mExitAnimationEnd <= now) {
                     mLastDrawable.setVisible(false, false);
                     mLastDrawable = null;
+                    mLastIndex = -1;
                     mExitAnimationEnd = 0;
                 } else {
                     int animAlpha = (int)((mExitAnimationEnd-now)*255)
@@ -1103,5 +1108,13 @@
 
     protected void setConstantState(DrawableContainerState state) {
         mDrawableContainerState = state;
+
+        // The locally cached drawables may have changed.
+        if (mCurIndex >= 0) {
+            mCurrDrawable = state.getChild(mCurIndex);
+        }
+        if (mLastIndex >= 0) {
+            mLastDrawable = state.getChild(mLastIndex);
+        }
     }
 }
diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java
index b88d9e6..acfd427 100644
--- a/graphics/java/android/graphics/drawable/InsetDrawable.java
+++ b/graphics/java/android/graphics/drawable/InsetDrawable.java
@@ -30,6 +30,7 @@
 import android.graphics.ColorFilter;
 import android.graphics.Insets;
 import android.graphics.Outline;
+import android.graphics.PixelFormat;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -317,7 +318,13 @@
 
     @Override
     public int getOpacity() {
-        return mState.mDrawable.getOpacity();
+        final InsetState state = mState;
+        final int opacity = state.mDrawable.getOpacity();
+        if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0
+                || state.mInsetRight > 0 || state.mInsetBottom > 0)) {
+            return PixelFormat.TRANSLUCENT;
+        }
+        return opacity;
     }
 
     @Override
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 6731366..a3a220c 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -22,11 +22,8 @@
 import android.animation.TimeInterpolator;
 import android.graphics.Canvas;
 import android.graphics.CanvasProperty;
-import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.Paint.Style;
 import android.graphics.Rect;
-import android.graphics.Xfermode;
 import android.util.MathUtils;
 import android.view.HardwareCanvas;
 import android.view.RenderNodeAnimator;
@@ -51,19 +48,12 @@
     // Hardware animators.
     private final ArrayList<RenderNodeAnimator> mRunningAnimations =
             new ArrayList<RenderNodeAnimator>();
-    private final ArrayList<RenderNodeAnimator> mPendingAnimations =
-            new ArrayList<RenderNodeAnimator>();
 
     private final RippleDrawable mOwner;
 
     /** Bounds used for computing max radius. */
     private final Rect mBounds;
 
-    /** ARGB color for drawing this ripple. */
-    private int mColor;
-
-    private Xfermode mXfermode;
-
     /** Maximum ripple radius. */
     private float mOuterRadius;
 
@@ -112,6 +102,10 @@
     /** Whether we were canceled externally and should avoid self-removal. */
     private boolean mCanceled;
 
+    private boolean mHasPendingHardwareExit;
+    private int mPendingRadiusDuration;
+    private int mPendingOpacityDuration;
+
     /**
      * Creates a new ripple.
      */
@@ -217,10 +211,6 @@
      * Draws the ripple centered at (0,0) using the specified paint.
      */
     public boolean draw(Canvas c, Paint p) {
-        // Store the color and xfermode, we might need them later.
-        mColor = p.getColor();
-        mXfermode = p.getXfermode();
-
         final boolean canUseHardware = c.isHardwareAccelerated();
         if (mCanUseHardware != canUseHardware && mCanUseHardware) {
             // We've switched from hardware to non-hardware mode. Panic.
@@ -229,8 +219,8 @@
         mCanUseHardware = canUseHardware;
 
         final boolean hasContent;
-        if (canUseHardware && mHardwareAnimating) {
-            hasContent = drawHardware((HardwareCanvas) c);
+        if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
+            hasContent = drawHardware((HardwareCanvas) c, p);
         } else {
             hasContent = drawSoftware(c, p);
         }
@@ -238,24 +228,10 @@
         return hasContent;
     }
 
-    private boolean drawHardware(HardwareCanvas c) {
-        // If we have any pending hardware animations, cancel any running
-        // animations and start those now.
-        final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
-        final int N = pendingAnimations.size();
-        if (N > 0) {
+    private boolean drawHardware(HardwareCanvas c, Paint p) {
+        if (mHasPendingHardwareExit) {
             cancelHardwareAnimations(false);
-
-            // We canceled old animations, but we're about to run new ones.
-            mHardwareAnimating = true;
-
-            for (int i = 0; i < N; i++) {
-                pendingAnimations.get(i).setTarget(c);
-                pendingAnimations.get(i).start();
-            }
-
-            mRunningAnimations.addAll(pendingAnimations);
-            pendingAnimations.clear();
+            startPendingHardwareExit(c, p);
         }
 
         c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
@@ -347,8 +323,6 @@
      * Starts the exit animation.
      */
     public void exit() {
-        cancel();
-
         final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
         final float remaining;
         if (mAnimRadius != null && mAnimRadius.isRunning()) {
@@ -357,19 +331,33 @@
             remaining = mOuterRadius;
         }
 
+        cancel();
+
         final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
                 + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
         final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
 
         if (mCanUseHardware) {
-            exitHardware(radiusDuration, opacityDuration);
+            createPendingHardwareExit(radiusDuration, opacityDuration);
         } else {
             exitSoftware(radiusDuration, opacityDuration);
         }
     }
 
-    private void exitHardware(int radiusDuration, int opacityDuration) {
-        mPendingAnimations.clear();
+    private void createPendingHardwareExit(int radiusDuration, int opacityDuration) {
+        mHasPendingHardwareExit = true;
+        mPendingRadiusDuration = radiusDuration;
+        mPendingOpacityDuration = opacityDuration;
+
+        // The animation will start on the next draw().
+        invalidateSelf();
+    }
+
+    private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
+        mHasPendingHardwareExit = false;
+
+        final int radiusDuration = mPendingRadiusDuration;
+        final int opacityDuration = mPendingOpacityDuration;
 
         final float startX = MathUtils.lerp(
                 mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
@@ -377,12 +365,8 @@
                 mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
 
         final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
-        final Paint paint = getTempPaint();
-        paint.setAntiAlias(true);
-        paint.setColor(mColor);
-        paint.setXfermode(mXfermode);
-        paint.setAlpha((int) (Color.alpha(mColor) * mOpacity + 0.5f));
-        paint.setStyle(Style.FILL);
+        final Paint paint = getTempPaint(p);
+        paint.setAlpha((int) (paint.getAlpha() * mOpacity + 0.5f));
         mPropPaint = CanvasProperty.createPaint(paint);
         mPropRadius = CanvasProperty.createFloat(startRadius);
         mPropX = CanvasProperty.createFloat(startX);
@@ -391,25 +375,33 @@
         final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius);
         radiusAnim.setDuration(radiusDuration);
         radiusAnim.setInterpolator(DECEL_INTERPOLATOR);
+        radiusAnim.setTarget(c);
+        radiusAnim.start();
 
         final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX);
         xAnim.setDuration(radiusDuration);
         xAnim.setInterpolator(DECEL_INTERPOLATOR);
+        xAnim.setTarget(c);
+        xAnim.start();
 
         final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY);
         yAnim.setDuration(radiusDuration);
         yAnim.setInterpolator(DECEL_INTERPOLATOR);
+        yAnim.setTarget(c);
+        yAnim.start();
 
         final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint,
                 RenderNodeAnimator.PAINT_ALPHA, 0);
         opacityAnim.setDuration(opacityDuration);
         opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
         opacityAnim.addListener(mAnimationListener);
+        opacityAnim.setTarget(c);
+        opacityAnim.start();
 
-        mPendingAnimations.add(radiusAnim);
-        mPendingAnimations.add(opacityAnim);
-        mPendingAnimations.add(xAnim);
-        mPendingAnimations.add(yAnim);
+        mRunningAnimations.add(radiusAnim);
+        mRunningAnimations.add(opacityAnim);
+        mRunningAnimations.add(xAnim);
+        mRunningAnimations.add(yAnim);
 
         mHardwareAnimating = true;
 
@@ -418,8 +410,6 @@
         mTweenX = 1;
         mTweenY = 1;
         mTweenRadius = 1;
-
-        invalidateSelf();
     }
 
     /**
@@ -455,10 +445,11 @@
         }
     }
 
-    private Paint getTempPaint() {
+    private Paint getTempPaint(Paint original) {
         if (mTempPaint == null) {
             mTempPaint = new Paint();
         }
+        mTempPaint.set(original);
         return mTempPaint;
     }
 
@@ -539,10 +530,7 @@
         }
         runningAnimations.clear();
 
-        if (cancelPending && !mPendingAnimations.isEmpty()) {
-            mPendingAnimations.clear();
-        }
-
+        mHasPendingHardwareExit = false;
         mHardwareAnimating = false;
     }
 
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 69847b5..665d736 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -24,9 +24,7 @@
 import android.graphics.CanvasProperty;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.Paint.Style;
 import android.graphics.Rect;
-import android.graphics.Xfermode;
 import android.util.MathUtils;
 import android.view.HardwareCanvas;
 import android.view.RenderNodeAnimator;
@@ -53,8 +51,6 @@
     // Hardware animators.
     private final ArrayList<RenderNodeAnimator> mRunningAnimations =
             new ArrayList<RenderNodeAnimator>();
-    private final ArrayList<RenderNodeAnimator> mPendingAnimations =
-            new ArrayList<RenderNodeAnimator>();
 
     private final RippleDrawable mOwner;
 
@@ -64,8 +60,6 @@
     /** ARGB color for drawing this ripple. */
     private int mColor;
 
-    private Xfermode mXfermode;
-
     /** Maximum ripple radius. */
     private float mOuterRadius;
 
@@ -98,6 +92,11 @@
     /** Whether we have an explicit maximum radius. */
     private boolean mHasMaxRadius;
 
+    private boolean mHasPendingHardwareExit;
+    private int mPendingOpacityDuration;
+    private int mPendingInflectionDuration;
+    private int mPendingInflectionOpacity;
+
     /**
      * Creates a new ripple.
      */
@@ -144,9 +143,7 @@
      * Draws the ripple centered at (0,0) using the specified paint.
      */
     public boolean draw(Canvas c, Paint p) {
-        // Store the color and xfermode, we might need them later.
         mColor = p.getColor();
-        mXfermode = p.getXfermode();
 
         final boolean canUseHardware = c.isHardwareAccelerated();
         if (mCanUseHardware != canUseHardware && mCanUseHardware) {
@@ -156,8 +153,8 @@
         mCanUseHardware = canUseHardware;
 
         final boolean hasContent;
-        if (canUseHardware && mHardwareAnimating) {
-            hasContent = drawHardware((HardwareCanvas) c);
+        if (canUseHardware && (mHardwareAnimating || mHasPendingHardwareExit)) {
+            hasContent = drawHardware((HardwareCanvas) c, p);
         } else {
             hasContent = drawSoftware(c, p);
         }
@@ -169,24 +166,10 @@
         return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0);
     }
 
-    private boolean drawHardware(HardwareCanvas c) {
-        // If we have any pending hardware animations, cancel any running
-        // animations and start those now.
-        final ArrayList<RenderNodeAnimator> pendingAnimations = mPendingAnimations;
-        final int N = pendingAnimations.size();
-        if (N > 0) {
+    private boolean drawHardware(HardwareCanvas c, Paint p) {
+        if (mHasPendingHardwareExit) {
             cancelHardwareAnimations(false);
-
-            // We canceled old animations, but we're about to run new ones.
-            mHardwareAnimating = true;
-
-            for (int i = 0; i < N; i++) {
-                pendingAnimations.get(i).setTarget(c);
-                pendingAnimations.get(i).start();
-            }
-
-            mRunningAnimations.addAll(pendingAnimations);
-            pendingAnimations.clear();
+            startPendingHardwareExit(c, p);
         }
 
         c.drawCircle(mPropOuterX, mPropOuterY, mPropOuterRadius, mPropOuterPaint);
@@ -263,21 +246,32 @@
                 + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
 
         if (mCanUseHardware) {
-            exitHardware(opacityDuration, inflectionDuration, inflectionOpacity);
+            createPendingHardwareExit(opacityDuration, inflectionDuration, inflectionOpacity);
         } else {
             exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity);
         }
     }
 
-    private void exitHardware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
-        mPendingAnimations.clear();
+    private void createPendingHardwareExit(
+            int opacityDuration, int inflectionDuration, int inflectionOpacity) {
+        mHasPendingHardwareExit = true;
+        mPendingOpacityDuration = opacityDuration;
+        mPendingInflectionDuration = inflectionDuration;
+        mPendingInflectionOpacity = inflectionOpacity;
 
-        final Paint outerPaint = getTempPaint();
-        outerPaint.setAntiAlias(true);
-        outerPaint.setXfermode(mXfermode);
-        outerPaint.setColor(mColor);
-        outerPaint.setAlpha((int) (Color.alpha(mColor) * mOuterOpacity + 0.5f));
-        outerPaint.setStyle(Style.FILL);
+        // The animation will start on the next draw().
+        invalidateSelf();
+    }
+
+    private void startPendingHardwareExit(HardwareCanvas c, Paint p) {
+        mHasPendingHardwareExit = false;
+
+        final int opacityDuration = mPendingOpacityDuration;
+        final int inflectionDuration = mPendingInflectionDuration;
+        final int inflectionOpacity = mPendingInflectionOpacity;
+
+        final Paint outerPaint = getTempPaint(p);
+        outerPaint.setAlpha((int) (outerPaint.getAlpha() * mOuterOpacity + 0.5f));
         mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
         mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
         mPropOuterX = CanvasProperty.createFloat(mOuterX);
@@ -301,8 +295,10 @@
                 outerFadeOutAnim.setStartDelay(inflectionDuration);
                 outerFadeOutAnim.setStartValue(inflectionOpacity);
                 outerFadeOutAnim.addListener(mAnimationListener);
+                outerFadeOutAnim.setTarget(c);
+                outerFadeOutAnim.start();
 
-                mPendingAnimations.add(outerFadeOutAnim);
+                mRunningAnimations.add(outerFadeOutAnim);
             } else {
                 outerOpacityAnim.addListener(mAnimationListener);
             }
@@ -314,14 +310,15 @@
             outerOpacityAnim.addListener(mAnimationListener);
         }
 
-        mPendingAnimations.add(outerOpacityAnim);
+        outerOpacityAnim.setTarget(c);
+        outerOpacityAnim.start();
+
+        mRunningAnimations.add(outerOpacityAnim);
 
         mHardwareAnimating = true;
 
         // Set up the software values to match the hardware end values.
         mOuterOpacity = 0;
-
-        invalidateSelf();
     }
 
     /**
@@ -340,10 +337,11 @@
         }
     }
 
-    private Paint getTempPaint() {
+    private Paint getTempPaint(Paint original) {
         if (mTempPaint == null) {
             mTempPaint = new Paint();
         }
+        mTempPaint.set(original);
         return mTempPaint;
     }
 
@@ -422,10 +420,7 @@
         }
         runningAnimations.clear();
 
-        if (cancelPending && !mPendingAnimations.isEmpty()) {
-            mPendingAnimations.clear();
-        }
-
+        mHasPendingHardwareExit = false;
         mHardwareAnimating = false;
     }
 
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 8cbc239..13e3d54 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -27,15 +27,19 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
+import android.graphics.Matrix;
 import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
+import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 
@@ -56,7 +60,7 @@
  * &ltripple android:color="#ffff0000">
  *   &ltitem android:id="@android:id/mask"
  *         android:drawable="@android:color/white" />
- * &ltripple /></code>
+ * &lt/ripple></code>
  * </pre>
  * <p>
  * If a mask layer is set, the ripple effect will be masked against that layer
@@ -65,15 +69,15 @@
  * If no mask layer is set, the ripple effect is masked against the composite
  * of the child layers.
  * <pre>
- * <code>&lt!-- A blue ripple drawn atop a black rectangle. --/>
+ * <code>&lt!-- A green ripple drawn atop a black rectangle. --/>
  * &ltripple android:color="#ff00ff00">
  *   &ltitem android:drawable="@android:color/black" />
- * &ltripple />
+ * &lt/ripple>
  *
- * &lt!-- A red ripple drawn atop a drawable resource. --/>
- * &ltripple android:color="#ff00ff00">
+ * &lt!-- A blue ripple drawn atop a drawable resource. --/>
+ * &ltripple android:color="#ff0000ff">
  *   &ltitem android:drawable="@drawable/my_drawable" />
- * &ltripple /></code>
+ * &lt/ripple></code>
  * </pre>
  * <p>
  * If no child layers or mask is specified and the ripple is set as a View
@@ -81,16 +85,17 @@
  * background within the View's hierarchy. In this case, the drawing region
  * may extend outside of the Drawable bounds.
  * <pre>
- * <code>&lt!-- An unbounded green ripple. --/>
- * &ltripple android:color="#ff0000ff" /></code>
+ * <code>&lt!-- An unbounded red ripple. --/>
+ * &ltripple android:color="#ffff0000" /></code>
  * </pre>
  *
  * @attr ref android.R.styleable#RippleDrawable_color
  */
 public class RippleDrawable extends LayerDrawable {
-    private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
-    private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP);
-    private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER);
+    private static final int MASK_UNKNOWN = -1;
+    private static final int MASK_NONE = 0;
+    private static final int MASK_CONTENT = 1;
+    private static final int MASK_EXPLICIT = 2;
 
     /**
      * Constant for automatically determining the maximum ripple radius.
@@ -123,6 +128,13 @@
     /** The current background. May be actively animating or pending entry. */
     private RippleBackground mBackground;
 
+    private Bitmap mMaskBuffer;
+    private BitmapShader mMaskShader;
+    private Canvas mMaskCanvas;
+    private Matrix mMaskMatrix;
+    private PorterDuffColorFilter mMaskColorFilter;
+    private boolean mHasValidMask;
+
     /** Whether we expect to draw a background when visible. */
     private boolean mBackgroundActive;
 
@@ -147,9 +159,6 @@
     /** Paint used to control appearance of ripples. */
     private Paint mRipplePaint;
 
-    /** Paint used to control reveal layer masking. */
-    private Paint mMaskingPaint;
-
     /** Target density of the display into which ripples are drawn. */
     private float mDensity = 1.0f;
 
@@ -615,37 +624,116 @@
      */
     @Override
     public void draw(@NonNull Canvas canvas) {
-        final boolean hasMask = mMask != null;
-        final boolean hasRipples = mRipple != null || mExitingRipplesCount > 0
-                || (mBackground != null && mBackground.shouldDraw());
-
         // Clip to the dirty bounds, which will be the drawable bounds if we
         // have a mask or content and the ripple bounds if we're projecting.
         final Rect bounds = getDirtyBounds();
         final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
         canvas.clipRect(bounds);
 
-        // If we have content, draw it first. If we have ripples and no mask,
-        // we'll draw it into a SRC_OVER layer so that we can mask ripples
-        // against it using SRC_IN.
-        final boolean hasContentLayer = drawContent(canvas, bounds, hasRipples, hasMask);
+        drawContent(canvas);
+        drawBackgroundAndRipples(canvas);
 
-        // Next, try to draw the ripples. If we have a non-opaque mask, we'll
-        // draw the ripples into a SRC_OVER layer, draw the mask into a DST_IN
-        // layer, and blend.
-        if (hasRipples) {
-            final boolean hasNonOpaqueMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
-            final boolean hasRippleLayer = drawBackgroundAndRipples(canvas, bounds,
-                    hasNonOpaqueMask, hasContentLayer);
+        canvas.restoreToCount(saveCount);
+    }
 
-            // If drawing ripples created a layer, we have a non-opaque mask
-            // that needs to be blended on top of the ripples with DST_IN.
-            if (hasRippleLayer) {
-                drawMaskingLayer(canvas, bounds, DST_IN);
+    @Override
+    public void invalidateSelf() {
+        super.invalidateSelf();
+
+        // Force the mask to update on the next draw().
+        mHasValidMask = false;
+    }
+
+    /**
+     * @return whether we need to use a mask
+     */
+    private void updateMaskShaderIfNeeded() {
+        if (mHasValidMask) {
+            return;
+        }
+
+        final int maskType = getMaskType();
+        if (maskType == MASK_UNKNOWN) {
+            return;
+        }
+
+        mHasValidMask = true;
+
+        if (maskType == MASK_NONE) {
+            if (mMaskBuffer != null) {
+                mMaskBuffer.recycle();
+                mMaskBuffer = null;
+                mMaskShader = null;
+                mMaskCanvas = null;
+            }
+            mMaskMatrix = null;
+            mMaskColorFilter = null;
+            return;
+        }
+
+        // Ensure we have a correctly-sized buffer.
+        final Rect bounds = getBounds();
+        if (mMaskBuffer == null
+                || mMaskBuffer.getWidth() != bounds.width()
+                || mMaskBuffer.getHeight() != bounds.height()) {
+            if (mMaskBuffer != null) {
+                mMaskBuffer.recycle();
+            }
+
+            mMaskBuffer = Bitmap.createBitmap(
+                    bounds.width(), bounds.height(), Bitmap.Config.ALPHA_8);
+            mMaskShader = new BitmapShader(mMaskBuffer,
+                    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+            mMaskCanvas = new Canvas(mMaskBuffer);
+        } else {
+            mMaskBuffer.eraseColor(Color.TRANSPARENT);
+        }
+
+        if (mMaskMatrix == null) {
+            mMaskMatrix = new Matrix();
+        } else {
+            mMaskMatrix.reset();
+        }
+
+        if (mMaskColorFilter == null) {
+            mMaskColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_IN);
+        }
+
+        // Draw the appropriate mask.
+        if (maskType == MASK_EXPLICIT) {
+            drawMask(mMaskCanvas);
+        } else if (maskType == MASK_CONTENT) {
+            drawContent(mMaskCanvas);
+        }
+    }
+
+    private int getMaskType() {
+        if (mRipple == null && mExitingRipplesCount <= 0
+                && (mBackground == null || !mBackground.shouldDraw())) {
+            // We might need a mask later.
+            return MASK_UNKNOWN;
+        }
+
+        if (mMask != null) {
+            if (mMask.getOpacity() == PixelFormat.OPAQUE) {
+                // Clipping handles opaque explicit masks.
+                return MASK_NONE;
+            } else {
+                return MASK_EXPLICIT;
             }
         }
 
-        canvas.restoreToCount(saveCount);
+        // Check for non-opaque, non-mask content.
+        final ChildDrawable[] array = mLayerState.mChildren;
+        final int count = mLayerState.mNum;
+        for (int i = 0; i < count; i++) {
+            if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
+                return MASK_CONTENT;
+            }
+        }
+
+        // Clipping handles opaque content.
+        return MASK_NONE;
     }
 
     /**
@@ -678,65 +766,65 @@
         return -1;
     }
 
-    private boolean drawContent(Canvas canvas, Rect bounds, boolean hasRipples, boolean hasMask) {
+    private void drawContent(Canvas canvas) {
+        // Draw everything except the mask.
         final ChildDrawable[] array = mLayerState.mChildren;
         final int count = mLayerState.mNum;
-
-        boolean needsLayer = false;
-
-        if (hasRipples && !hasMask) {
-            // If we only have opaque content, we don't really need a layer
-            // because the ripples will be clipped to the drawable bounds.
-            for (int i = 0; i < count; i++) {
-                if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
-                    needsLayer = true;
-                    break;
-                }
-            }
-        }
-
-        if (needsLayer) {
-            canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
-                    getMaskingPaint(SRC_OVER));
-        }
-
-        // Draw everything except the mask.
         for (int i = 0; i < count; i++) {
             if (array[i].mId != R.id.mask) {
                 array[i].mDrawable.draw(canvas);
             }
         }
-
-        return needsLayer;
     }
 
-    private boolean drawBackgroundAndRipples(
-            Canvas canvas, Rect bounds, boolean hasNonOpaqueMask, boolean hasContentLayer) {
-        if (hasNonOpaqueMask) {
-            final Paint p = getMaskingPaint(SRC_OVER);
-            canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, p);
+    private void drawBackgroundAndRipples(Canvas canvas) {
+        final Ripple active = mRipple;
+        final RippleBackground background = mBackground;
+        final int count = mExitingRipplesCount;
+        if (active == null && count <= 0 && (background == null || !background.shouldDraw())) {
+            // Move along, nothing to draw here.
+            return;
         }
 
-        final PorterDuffXfermode mode = hasContentLayer ? SRC_ATOP : SRC_OVER;
         final float x = mHotspotBounds.exactCenterX();
         final float y = mHotspotBounds.exactCenterY();
         canvas.translate(x, y);
 
-        final Paint p = getRipplePaint();
-        p.setXfermode(mode);
+        updateMaskShaderIfNeeded();
+
+        // Position the shader to account for canvas translation.
+        if (mMaskShader != null) {
+            mMaskMatrix.setTranslate(-x, -y);
+            mMaskShader.setLocalMatrix(mMaskMatrix);
+        }
 
         // Grab the color for the current state and cut the alpha channel in
         // half so that the ripple and background together yield full alpha.
         final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
-        final int alpha = (Color.alpha(color) / 2) << 24;
-        p.setColor(color & 0xFFFFFF | alpha);
+        final int halfAlpha = (Color.alpha(color) / 2) << 24;
+        final Paint p = getRipplePaint();
 
-        final RippleBackground background = mBackground;
+        if (mMaskColorFilter != null) {
+            // The ripple timing depends on the paint's alpha value, so we need
+            // to push just the alpha channel into the paint and let the filter
+            // handle the full-alpha color.
+            final int fullAlphaColor = color | (0xFF << 24);
+            mMaskColorFilter.setColor(fullAlphaColor);
+
+            p.setColor(halfAlpha);
+            p.setColorFilter(mMaskColorFilter);
+            p.setShader(mMaskShader);
+        } else {
+            final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
+            p.setColor(halfAlphaColor);
+            p.setColorFilter(null);
+            p.setShader(null);
+        }
+
         if (background != null && background.shouldDraw()) {
             background.draw(canvas, p);
         }
 
-        final int count = mExitingRipplesCount;
         if (count > 0) {
             final Ripple[] ripples = mExitingRipples;
             for (int i = 0; i < count; i++) {
@@ -744,27 +832,15 @@
             }
         }
 
-        final Ripple active = mRipple;
         if (active != null) {
             active.draw(canvas, p);
         }
 
         canvas.translate(-x, -y);
-
-        // Returns true if a layer was created.
-        return hasNonOpaqueMask;
     }
 
-    private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
-        final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
-                bounds.right, bounds.bottom, getMaskingPaint(mode));
-
-        // Ensure that DST_IN blends using the entire layer.
-        canvas.drawColor(Color.TRANSPARENT);
-
+    private void drawMask(Canvas canvas) {
         mMask.draw(canvas);
-
-        return restoreToCount;
     }
 
     private Paint getRipplePaint() {
@@ -776,15 +852,6 @@
         return mRipplePaint;
     }
 
-    private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
-        if (mMaskingPaint == null) {
-            mMaskingPaint = new Paint();
-        }
-        mMaskingPaint.setXfermode(xfermode);
-        mMaskingPaint.setAlpha(0xFF);
-        return mMaskingPaint;
-    }
-
     @Override
     public Rect getDirtyBounds() {
         if (isProjected()) {
@@ -832,6 +899,10 @@
         // LayerDrawable creates a new state using createConstantState, so
         // this should always be a safe cast.
         mState = (RippleState) mLayerState;
+
+        // The locally cached drawable may have changed.
+        mMask = findDrawableByLayerId(R.id.mask);
+
         return this;
     }
 
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 6b84494..5a28be5 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -1828,6 +1828,9 @@
         const ResTable_config* config,
         Entry* outEntry) const;
 
+    uint32_t findEntry(const PackageGroup* group, ssize_t typeIndex, const char16_t* name,
+            size_t nameLen, uint32_t* outTypeSpecFlags) const;
+
     status_t parsePackage(
         const ResTable_package* const pkg, const Header* const header);
 
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 6dfb4dc..d7b9765 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4173,6 +4173,9 @@
                  String8(name, nameLen).string(),
                  String8(package, packageLen).string()));
 
+    const String16 attr("attr");
+    const String16 attrPrivate("^attr-private");
+
     const size_t NG = mPackageGroups.size();
     for (size_t ig=0; ig<NG; ig++) {
         const PackageGroup* group = mPackageGroups[ig];
@@ -4185,64 +4188,72 @@
 
         const size_t packageCount = group->packages.size();
         for (size_t pi = 0; pi < packageCount; pi++) {
-            ssize_t ti = group->packages[pi]->typeStrings.indexOfString(type, typeLen);
-            if (ti < 0) {
-                continue;
-            }
+            const char16_t* targetType = type;
+            size_t targetTypeLen = typeLen;
 
-            ti += group->packages[pi]->typeIdOffset;
-
-            const TypeList& typeList = group->types[ti];
-            if (typeList.isEmpty()) {
-                TABLE_NOISY(printf("Expected type structure not found in package %s for index %d\n",
-                                   String8(group->name).string(), ti));
-                continue;
-            }
-
-            const size_t typeCount = typeList.size();
-            for (size_t i = 0; i < typeCount; i++) {
-                const Type* t = typeList[i];
-                const ssize_t ei = t->package->keyStrings.indexOfString(name, nameLen);
-                if (ei < 0) {
+            do {
+                ssize_t ti = group->packages[pi]->typeStrings.indexOfString(
+                        targetType, targetTypeLen);
+                if (ti < 0) {
                     continue;
                 }
 
-                const size_t configCount = t->configs.size();
-                for (size_t j = 0; j < configCount; j++) {
-                    const TypeVariant tv(t->configs[j]);
-                    for (TypeVariant::iterator iter = tv.beginEntries();
-                         iter != tv.endEntries();
-                         iter++) {
-                        const ResTable_entry* entry = *iter;
-                        if (entry == NULL) {
-                            continue;
-                        }
+                ti += group->packages[pi]->typeIdOffset;
 
-                        if (dtohl(entry->key.index) == (size_t) ei) {
-                            uint32_t resId = Res_MAKEID(group->id - 1, ti, iter.index());
-                            if (outTypeSpecFlags) {
-                                Entry result;
-                                if (getEntry(group, ti, iter.index(), NULL, &result) != NO_ERROR) {
-                                    ALOGW("Failed to find spec flags for %s:%s/%s (0x%08x)",
-                                            String8(group->name).string(),
-                                            String8(String16(type, typeLen)).string(),
-                                            String8(String16(name, nameLen)).string(),
-                                            resId);
-                                    return 0;
-                                }
-                                *outTypeSpecFlags = result.specFlags;
-
-                                if (fakePublic) {
-                                    *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
-                                }
-                            }
-                            return resId;
-                        }
+                const uint32_t identifier = findEntry(group, ti, name, nameLen,
+                        outTypeSpecFlags);
+                if (identifier != 0) {
+                    if (fakePublic && outTypeSpecFlags) {
+                        *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
                     }
+                    return identifier;
+                }
+            } while (strzcmp16(attr.string(), attr.size(), targetType, targetTypeLen) == 0
+                    && (targetType = attrPrivate.string())
+                    && (targetTypeLen = attrPrivate.size())
+            );
+        }
+        break;
+    }
+    return 0;
+}
+
+uint32_t ResTable::findEntry(const PackageGroup* group, ssize_t typeIndex, const char16_t* name,
+        size_t nameLen, uint32_t* outTypeSpecFlags) const {
+    const TypeList& typeList = group->types[typeIndex];
+    const size_t typeCount = typeList.size();
+    for (size_t i = 0; i < typeCount; i++) {
+        const Type* t = typeList[i];
+        const ssize_t ei = t->package->keyStrings.indexOfString(name, nameLen);
+        if (ei < 0) {
+            continue;
+        }
+
+        const size_t configCount = t->configs.size();
+        for (size_t j = 0; j < configCount; j++) {
+            const TypeVariant tv(t->configs[j]);
+            for (TypeVariant::iterator iter = tv.beginEntries();
+                 iter != tv.endEntries();
+                 iter++) {
+                const ResTable_entry* entry = *iter;
+                if (entry == NULL) {
+                    continue;
+                }
+
+                if (dtohl(entry->key.index) == (size_t) ei) {
+                    uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
+                    if (outTypeSpecFlags) {
+                        Entry result;
+                        if (getEntry(group, typeIndex, iter.index(), NULL, &result) != NO_ERROR) {
+                            ALOGW("Failed to find spec flags for 0x%08x", resId);
+                            return 0;
+                        }
+                        *outTypeSpecFlags = result.specFlags;
+                    }
+                    return resId;
                 }
             }
         }
-        break;
     }
     return 0;
 }
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 1029fcc..4513643 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -648,7 +648,7 @@
                 maxChannels = 48;
             } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS)) {
                 bitRates = Range.create(32000, 500000);
-                sampleRates = new int[] { 8000, 12000, 16000, 24000, 48000, 192000 };
+                sampleRateRange = Range.create(8000, 192000);
                 maxChannels = 255;
             } else if (mime.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS)) {
                 bitRates = Range.create(6000, 510000);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index da6f3fc..94c9690 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -42,7 +42,7 @@
     private static final String TAG = "TvInputSessionWrapper";
 
     private static final int MESSAGE_HANDLING_DURATION_THRESHOLD_MILLIS = 50;
-    private static final int MESSAGE_TUNE_DURATION_THRESHOLD_MILLIS = 1000;
+    private static final int MESSAGE_TUNE_DURATION_THRESHOLD_MILLIS = 2000;
 
     private static final int DO_RELEASE = 1;
     private static final int DO_SET_MAIN = 2;
diff --git a/packages/Keyguard/res/values/strings.xml b/packages/Keyguard/res/values/strings.xml
index b97b5a39..368d789 100644
--- a/packages/Keyguard/res/values/strings.xml
+++ b/packages/Keyguard/res/values/strings.xml
@@ -380,9 +380,6 @@
        you will be asked to unlock your phone using an email account.\n\n
        Try again in <xliff:g id="number">%d</xliff:g> seconds.
     </string>
-    <!-- Sequence of characters used to separate message strings in keyguard. Typically just em-dash
-         with spaces on either side. [CHAR LIMIT=3] -->
-    <string name="kg_text_message_separator" product="default">" \u2014 "</string>
     <!-- The delete-widget drop target button text -->
     <string name="kg_reordering_delete_drop_target_text">Remove</string>
 
diff --git a/packages/Keyguard/src/com/android/keyguard/CarrierText.java b/packages/Keyguard/src/com/android/keyguard/CarrierText.java
index 55bfe49..7f4ce59 100644
--- a/packages/Keyguard/src/com/android/keyguard/CarrierText.java
+++ b/packages/Keyguard/src/com/android/keyguard/CarrierText.java
@@ -16,11 +16,17 @@
 
 package com.android.keyguard;
 
+import java.util.List;
+import java.util.Locale;
+
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.text.method.SingleLineTransformationMethod;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
+import android.text.method.SingleLineTransformationMethod;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.widget.TextView;
 
@@ -28,29 +34,19 @@
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.widget.LockPatternUtils;
 
-import java.util.Locale;
-
 public class CarrierText extends TextView {
+    private static final boolean DEBUG = KeyguardConstants.DEBUG;
+    private static final String TAG = "CarrierText";
+
     private static CharSequence mSeparator;
 
     private LockPatternUtils mLockPatternUtils;
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
-        private CharSequence mPlmn;
-        private CharSequence mSpn;
-        private State mSimState;
-
         @Override
-        public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
-            mPlmn = plmn;
-            mSpn = spn;
-            updateCarrierText(mSimState, mPlmn, mSpn);
-        }
-
-        @Override
-        public void onSimStateChanged(int subId, int slotId, State simState) {
-            mSimState = simState;
-            updateCarrierText(mSimState, mPlmn, mSpn);
+        public void onRefreshCarrierInfo() {
+            updateCarrierText();
         }
 
         public void onScreenTurnedOff(int why) {
@@ -93,14 +89,50 @@
         setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps));
     }
 
-    protected void updateCarrierText(State simState, CharSequence plmn, CharSequence spn) {
-        setText(getCarrierTextForSimState(simState, plmn, spn));
+    protected void updateCarrierText() {
+        boolean allSimsMissing = true;
+        CharSequence displayText = null;
+
+        List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+        final int N = subs.size();
+        if (DEBUG) Log.d(TAG, "updateCarrierText(): " + N);
+        for (int i = 0; i < N; i++) {
+            State simState = mKeyguardUpdateMonitor.getSimState(subs.get(i).getSubscriptionId());
+            CharSequence carrierName = subs.get(i).getCarrierName();
+            CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
+            if (DEBUG) Log.d(TAG, "Handling " + simState + " " + carrierName);
+            if (carrierTextForSimState != null) {
+                allSimsMissing = false;
+                displayText = concatenate(displayText, carrierTextForSimState);
+            }
+        }
+        if (allSimsMissing) {
+            if (N != 0) {
+                // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
+                // This depends on mPlmn containing the text "Emergency calls only" when the radio
+                // has some connectivity. Otherwise, it should be null or empty and just show
+                // "No SIM card"
+                // Grab the first subscripton, because they all should contain the emergency text,
+                // described above.
+                displayText =  makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(R.string.keyguard_missing_sim_message_short),
+                        subs.get(0).getCarrierName());
+            } else {
+                // We don't have a SubscriptionInfo to get the emergency calls only from.
+                // Lets just make it ourselves.
+                displayText =  makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(R.string.keyguard_missing_sim_message_short),
+                        getContext().getText(com.android.internal.R.string.emergency_calls_only));
+            }
+        }
+        setText(displayText);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mSeparator = getResources().getString(R.string.kg_text_message_separator);
+        mSeparator = getResources().getString(
+                com.android.internal.R.string.kg_text_message_separator);
         final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
         setSelected(screenOn); // Allow marquee to work.
     }
@@ -108,13 +140,14 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mCallback);
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor.registerCallback(mCallback);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mCallback);
+        mKeyguardUpdateMonitor.removeCallback(mCallback);
     }
 
     /**
@@ -122,36 +155,31 @@
      * and SPN as well as device capabilities, such as being emergency call capable.
      *
      * @param simState
-     * @param plmn
+     * @param text
      * @param spn
-     * @return
+     * @return Carrier text if not in missing state, null otherwise.
      */
     private CharSequence getCarrierTextForSimState(IccCardConstants.State simState,
-            CharSequence plmn, CharSequence spn) {
+            CharSequence text) {
         CharSequence carrierText = null;
         StatusMode status = getStatusForIccState(simState);
         switch (status) {
             case Normal:
-                carrierText = concatenate(plmn, spn);
+                carrierText = text;
                 break;
 
             case SimNotReady:
-                carrierText = null; // nothing to display yet.
+                // Null is reserved for denoting missing, in this case we have nothing to display.
+                carrierText = ""; // nothing to display yet.
                 break;
 
             case NetworkLocked:
                 carrierText = makeCarrierStringOnEmergencyCapable(
-                        mContext.getText(R.string.keyguard_network_locked_message), plmn);
+                        mContext.getText(R.string.keyguard_network_locked_message), text);
                 break;
 
             case SimMissing:
-                // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
-                // This depends on mPlmn containing the text "Emergency calls only" when the radio
-                // has some connectivity. Otherwise, it should be null or empty and just show
-                // "No SIM card"
-                carrierText =  makeCarrierStringOnEmergencyCapable(
-                        getContext().getText(R.string.keyguard_missing_sim_message_short),
-                        plmn);
+                carrierText = null;
                 break;
 
             case SimPermDisabled:
@@ -160,21 +188,19 @@
                 break;
 
             case SimMissingLocked:
-                carrierText =  makeCarrierStringOnEmergencyCapable(
-                        getContext().getText(R.string.keyguard_missing_sim_message_short),
-                        plmn);
+                carrierText = null;
                 break;
 
             case SimLocked:
                 carrierText = makeCarrierStringOnEmergencyCapable(
                         getContext().getText(R.string.keyguard_sim_locked_message),
-                        plmn);
+                        text);
                 break;
 
             case SimPukLocked:
                 carrierText = makeCarrierStringOnEmergencyCapable(
                         getContext().getText(R.string.keyguard_sim_puk_locked_message),
-                        plmn);
+                        text);
                 break;
         }
 
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
index 9bc2a4d..236cbf68 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java
@@ -153,7 +153,8 @@
         mUpdateMonitor.registerCallback(mInfoCallback);
         mHandler = new Handler(Looper.myLooper());
 
-        mSeparator = getResources().getString(R.string.kg_text_message_separator);
+        mSeparator = getResources().getString(
+                com.android.internal.R.string.kg_text_message_separator);
 
         update();
     }
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ff07dd7..8458ae0 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -97,7 +97,6 @@
     // Callback messages
     private static final int MSG_TIME_UPDATE = 301;
     private static final int MSG_BATTERY_UPDATE = 302;
-    private static final int MSG_CARRIER_INFO_UPDATE = 303;
     private static final int MSG_SIM_STATE_CHANGE = 304;
     private static final int MSG_RINGER_MODE_CHANGED = 305;
     private static final int MSG_PHONE_STATE_CHANGED = 306;
@@ -126,8 +125,6 @@
     private final Context mContext;
     HashMap<Integer, SimData> mSimDatas = new HashMap<Integer, SimData>();
 
-    private CharSequence mTelephonyPlmn;
-    private CharSequence mTelephonySpn;
     private int mRingMode;
     private int mPhoneState;
     private boolean mKeyguardIsVisible;
@@ -168,9 +165,6 @@
                 case MSG_BATTERY_UPDATE:
                     handleBatteryUpdate((BatteryStatus) msg.obj);
                     break;
-                case MSG_CARRIER_INFO_UPDATE:
-                    handleCarrierInfoUpdate();
-                    break;
                 case MSG_SIM_STATE_CHANGE:
                     handleSimStateChange(msg.arg1, msg.arg2, (State) msg.obj);
                     break;
@@ -290,6 +284,7 @@
                 KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
                 if (cb != null) {
                     cb.onSimStateChanged(data.subId, data.slotId, data.simState);
+                    cb.onRefreshCarrierInfo();
                 }
             }
         }
@@ -435,10 +430,6 @@
                     || Intent.ACTION_TIME_CHANGED.equals(action)
                     || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
                 mHandler.sendEmptyMessage(MSG_TIME_UPDATE);
-            } else if (TelephonyIntents.SPN_STRINGS_UPDATED_ACTION.equals(action)) {
-                mTelephonyPlmn = getTelephonyPlmnFrom(intent);
-                mTelephonySpn = getTelephonySpnFrom(intent);
-                mHandler.sendEmptyMessage(MSG_CARRIER_INFO_UPDATE);
             } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
                 final int status = intent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
                 final int plugged = intent.getIntExtra(EXTRA_PLUGGED, 0);
@@ -682,7 +673,6 @@
 
         // Take a guess at initial SIM state, battery status and PLMN until we get an update
         mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0);
-        mTelephonyPlmn = getDefaultPlmn();
 
         // Watch for interesting updates
         final IntentFilter filter = new IntentFilter();
@@ -692,7 +682,6 @@
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
-        filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(Intent.ACTION_USER_REMOVED);
         context.registerReceiver(mBroadcastReceiver, filter);
@@ -940,21 +929,6 @@
     }
 
     /**
-     * Handle {@link #MSG_CARRIER_INFO_UPDATE}
-     */
-    private void handleCarrierInfoUpdate() {
-        if (DEBUG) Log.d(TAG, "handleCarrierInfoUpdate: plmn = " + mTelephonyPlmn
-            + ", spn = " + mTelephonySpn);
-
-        for (int i = 0; i < mCallbacks.size(); i++) {
-            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-            if (cb != null) {
-                cb.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
-            }
-        }
-    }
-
-    /**
      * Handle {@link #MSG_SIM_STATE_CHANGE}
      */
     private void handleSimStateChange(int subId, int slotId, State state) {
@@ -1087,18 +1061,6 @@
     }
 
     /**
-     * @param intent The intent with action {@link TelephonyIntents#SPN_STRINGS_UPDATED_ACTION}
-     * @return The string to use for the plmn, or null if it should not be shown.
-     */
-    private CharSequence getTelephonyPlmnFrom(Intent intent) {
-        if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false)) {
-            final String plmn = intent.getStringExtra(TelephonyIntents.EXTRA_PLMN);
-            return (plmn != null) ? plmn : getDefaultPlmn();
-        }
-        return null;
-    }
-
-    /**
      * @return The default plmn (no service)
      */
     private CharSequence getDefaultPlmn() {
@@ -1106,20 +1068,6 @@
     }
 
     /**
-     * @param intent The intent with action {@link Telephony.Intents#SPN_STRINGS_UPDATED_ACTION}
-     * @return The string to use for the plmn, or null if it should not be shown.
-     */
-    private CharSequence getTelephonySpnFrom(Intent intent) {
-        if (intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false)) {
-            final String spn = intent.getStringExtra(TelephonyIntents.EXTRA_SPN);
-            if (spn != null) {
-                return spn;
-            }
-        }
-        return null;
-    }
-
-    /**
      * Remove the given observer's callback.
      *
      * @param callback The callback to remove
@@ -1159,7 +1107,7 @@
         callback.onTimeChanged();
         callback.onRingerModeChanged(mRingMode);
         callback.onPhoneStateChanged(mPhoneState);
-        callback.onRefreshCarrierInfo(mTelephonyPlmn, mTelephonySpn);
+        callback.onRefreshCarrierInfo();
         callback.onClockVisibilityChanged();
         for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
             final SimData state = data.getValue();
@@ -1219,14 +1167,6 @@
         }
     }
 
-    public CharSequence getTelephonyPlmn() {
-        return mTelephonyPlmn;
-    }
-
-    public CharSequence getTelephonySpn() {
-        return mTelephonySpn;
-    }
-
     /**
      * @return Whether the device is provisioned (whether they have gone through
      *   the setup wizard)
@@ -1289,7 +1229,7 @@
         return false;
     }
 
-    private State getSimState(int subId) {
+    public State getSimState(int subId) {
         if (mSimDatas.containsKey(subId)) {
             return mSimDatas.get(subId).simState;
         } else {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index de72ddd..c2f355a 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -49,12 +49,8 @@
 
     /**
      * Called when the carrier PLMN or SPN changes.
-     *
-     * @param plmn The operator name of the registered network.  May be null if it shouldn't
-     *   be displayed.
-     * @param spn The service provider name.  May be null if it shouldn't be displayed.
      */
-    public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { }
+    public void onRefreshCarrierInfo() { }
 
     /**
      * Called when the ringer mode changes.
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index 6337956..191cba5 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -9,6 +9,7 @@
 36001 sysui_heads_up_status (key|3),(visible|1)
 36002 sysui_fullscreen_notification (key|3)
 36003 sysui_heads_up_escalation (key|3)
+36004 sysui_status_bar_state (state|1)
 
 # ---------------------------
 # PhoneStatusBarView.java
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 206fc43..7ac0daf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -72,6 +72,7 @@
 import com.android.systemui.statusbar.phone.StatusBarWindowManager;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
 
@@ -1217,7 +1218,12 @@
         synchronized (this) {
             if (mBootCompleted) {
                 final UserHandle currentUser = new UserHandle(mLockPatternUtils.getCurrentUser());
-                mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, currentUser);
+                final UserManager um = (UserManager) mContext.getSystemService(
+                        Context.USER_SERVICE);
+                List <UserInfo> userHandles = um.getProfiles(currentUser.getIdentifier());
+                for (UserInfo ui : userHandles) {
+                    mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, ui.getUserHandle());
+                }
             } else {
                 mBootSendUserPresent = true;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
index fa44551..0428b48 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java
@@ -50,7 +50,6 @@
     private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
     private int DEFAULT_ESCAPE_ANIMATION_DURATION = 75; // ms
     private int MAX_ESCAPE_ANIMATION_DURATION = 150; // ms
-    private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
     private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms
 
     public static float ALPHA_FADE_START = 0.15f; // fraction of thumbnail width
@@ -350,8 +349,7 @@
     }
 
     private void endSwipe(VelocityTracker velocityTracker) {
-        float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale;
-        velocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity);
+        velocityTracker.computeCurrentVelocity(1000 /* px/sec */);
         float velocity = getVelocity(velocityTracker);
         float perpendicularVelocity = getPerpendicularVelocity(velocityTracker);
         float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 2b173a9..c59f82c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -143,22 +143,21 @@
                 // Initialize the velocity tracker
                 initOrResetVelocityTracker();
                 mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
-                // Check if the scroller is finished yet
-                mIsScrolling = mScroller.isScrolling();
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
                 if (mActivePointerId == INACTIVE_POINTER_ID) break;
 
+                // Initialize the velocity tracker if necessary
+                initVelocityTrackerIfNotExists();
+                mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
+
                 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
                 int y = (int) ev.getY(activePointerIndex);
                 int x = (int) ev.getX(activePointerIndex);
                 if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
                     // Save the touch move info
                     mIsScrolling = true;
-                    // Initialize the velocity tracker if necessary
-                    initVelocityTrackerIfNotExists();
-                    mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
                     // Disallow parents from intercepting touch events
                     final ViewParent parent = mSv.getParent();
                     if (parent != null) {
@@ -237,6 +236,8 @@
             case MotionEvent.ACTION_MOVE: {
                 if (mActivePointerId == INACTIVE_POINTER_ID) break;
 
+                mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
+
                 int activePointerIndex = ev.findPointerIndex(mActivePointerId);
                 int x = (int) ev.getX(activePointerIndex);
                 int y = (int) ev.getY(activePointerIndex);
@@ -246,9 +247,6 @@
                 if (!mIsScrolling) {
                     if (yTotal > mScrollTouchSlop) {
                         mIsScrolling = true;
-                        // Initialize the velocity tracker
-                        initOrResetVelocityTracker();
-                        mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
                         // Disallow parents from intercepting touch events
                         final ViewParent parent = mSv.getParent();
                         if (parent != null) {
@@ -267,11 +265,6 @@
                                 / maxOverScroll));
                     }
                     mScroller.setStackScroll(curStackScroll + deltaP);
-                    if (mScroller.isScrollOutOfBounds()) {
-                        mVelocityTracker.clear();
-                    } else {
-                        mVelocityTracker.addMovement(createMotionEventForStackScroll(ev));
-                    }
                 }
                 mLastMotionX = x;
                 mLastMotionY = y;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 57ac4b0..2b6ac26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -683,15 +683,12 @@
             Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
                     n, thisUserId, notificationUserId));
         }
-        synchronized (mCurrentProfiles) {
-            return notificationUserId == UserHandle.USER_ALL
-                    || mCurrentProfiles.get(notificationUserId) != null;
-        }
+        return isCurrentProfile(notificationUserId);
     }
 
     protected boolean isCurrentProfile(int userId) {
         synchronized (mCurrentProfiles) {
-            return mCurrentProfiles.get(userId) != null;
+            return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 7a3b5e4..ec2d30c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -3795,14 +3795,19 @@
      * @param state The {@link StatusBarState} to set.
      */
     public void setBarState(int state) {
-        // If we're visible and switched to SHADE_LOCKED (the user dragged down
-        // on the lockscreen), clear notification LED, vibration, ringing.
-        // Other transitions are covered in handleVisibleToUserChanged().
-        if (mVisible && mState != state && state == StatusBarState.SHADE_LOCKED) {
-            try {
-                mBarService.clearNotificationEffects();
-            } catch (RemoteException e) {
-                // Ignore.
+        if (state != mState) {
+            EventLogTags.writeSysuiStatusBarState(state);
+
+            // If we're visible and switched to SHADE_LOCKED (the user dragged
+            // down on the lockscreen), clear notification LED, vibration,
+            // ringing.
+            // Other transitions are covered in handleVisibleToUserChanged().
+            if (mVisible && state == StatusBarState.SHADE_LOCKED) {
+                try {
+                    mBarService.clearNotificationEffects();
+                } catch (RemoteException e) {
+                    // Ignore.
+                }
             }
         }
         mState = state;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 2781890..89aebe8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3314,8 +3314,6 @@
         public int mAccessibilityFocusedWindowId = INVALID_WINDOW_ID;
         public long mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
 
-        public AccessibilityEvent mShowingFocusedWindowEvent;
-
         private boolean mTouchInteractionInProgress;
 
         private boolean canDispatchAccessibilityEventLocked(AccessibilityEvent event) {
@@ -3324,19 +3322,6 @@
                 // All events that are for changes in a global window
                 // state should *always* be dispatched.
                 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
-                    if (mWindowsForAccessibilityCallback != null) {
-                        // OK, this is fun. Sometimes the focused window is notified
-                        // it has focus before being shown. Historically this event
-                        // means that the window is focused and can be introspected.
-                        // But we still have not gotten the window state from the
-                        // window manager, so delay the notification until then.
-                        AccessibilityWindowInfo window = findWindowById(event.getWindowId());
-                        if (window == null) {
-                            mShowingFocusedWindowEvent = AccessibilityEvent.obtain(event);
-                            return false;
-                        }
-                    }
-                // $fall-through$
                 case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
                 case AccessibilityEvent.TYPE_ANNOUNCEMENT:
                 // All events generated by the user touching the
@@ -3428,18 +3413,6 @@
             }
 
             notifyWindowsChanged();
-
-            // If we are delaying a window state change event as the window
-            // source was showing when it was fired, now is the time to send.
-            if (mShowingFocusedWindowEvent != null) {
-                final int windowId = mShowingFocusedWindowEvent.getWindowId();
-                AccessibilityWindowInfo window = findWindowById(windowId);
-                if (window != null) {
-                    // Sending does the recycle.
-                    sendAccessibilityEvent(mShowingFocusedWindowEvent, mCurrentUserId);
-                }
-                mShowingFocusedWindowEvent = null;
-            }
         }
 
         public boolean computePartialInteractiveRegionForWindowLocked(int windowId,
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index ebdd386..3117a17 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -18,11 +18,14 @@
 
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothGatt;
 import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.IBluetoothHeadset;
 import android.bluetooth.IBluetoothManager;
 import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothProfileServiceConnection;
 import android.bluetooth.IBluetoothStateChangeCallback;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -32,6 +35,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -42,12 +46,18 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.Vector;
+
 class BluetoothManagerService extends IBluetoothManager.Stub {
     private static final String TAG = "BluetoothManagerService";
     private static final boolean DBG = true;
@@ -67,6 +77,8 @@
     private static final int ERROR_RESTART_TIME_MS = 3000;
     //Maximum msec to delay MESSAGE_USER_SWITCHED
     private static final int USER_SWITCHED_TIME_MS = 200;
+    // Delay for the addProxy function in msec
+    private static final int ADD_PROXY_DELAY_MS = 100;
 
     private static final int MESSAGE_ENABLE = 1;
     private static final int MESSAGE_DISABLE = 2;
@@ -83,6 +95,8 @@
     private static final int MESSAGE_GET_NAME_AND_ADDRESS=200;
     private static final int MESSAGE_SAVE_NAME_AND_ADDRESS=201;
     private static final int MESSAGE_USER_SWITCHED = 300;
+    private static final int MESSAGE_ADD_PROXY_DELAYED = 400;
+    private static final int MESSAGE_BIND_PROFILE_SERVICE = 401;
     private static final int MAX_SAVE_RETRIES=3;
     private static final int MAX_ERROR_RESTART_RETRIES=6;
 
@@ -127,6 +141,11 @@
     private int mErrorRecoveryRetryCounter;
     private final int mSystemUiUid;
 
+    // Save a ProfileServiceConnections object for each of the bound
+    // bluetooth profile services
+    private final Map <Integer, ProfileServiceConnections> mProfileServices =
+            new HashMap <Integer, ProfileServiceConnections>();
+
     private void registerForAirplaneMode(IntentFilter filter) {
         final ContentResolver resolver = mContext.getContentResolver();
         final String airplaneModeRadios = Settings.Global.getString(resolver,
@@ -499,6 +518,187 @@
         return mBluetoothGatt;
     }
 
+    @Override
+    public boolean bindBluetoothProfileService(int bluetoothProfile,
+            IBluetoothProfileServiceConnection proxy) {
+        if (!mEnable) {
+            if (DBG) {
+                Log.d(TAG, "Trying to bind to profile: " + bluetoothProfile +
+                        ", while Bluetooth was disabled");
+            }
+            return false;
+        }
+        synchronized (mProfileServices) {
+            ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile));
+            if (psc == null) {
+                if (DBG) {
+                    Log.d(TAG, "Creating new ProfileServiceConnections object for"
+                            + " profile: " + bluetoothProfile);
+                }
+                Intent intent = null;
+                if (bluetoothProfile == BluetoothProfile.HEADSET) {
+                    intent = new Intent(IBluetoothHeadset.class.getName());
+                } else {
+                    return false;
+                }
+                psc = new ProfileServiceConnections(intent);
+                mProfileServices.put(new Integer(bluetoothProfile), psc);
+                psc.bindService();
+            }
+        }
+
+        // Introducing a delay to give the client app time to prepare
+        Message addProxyMsg = mHandler.obtainMessage(MESSAGE_ADD_PROXY_DELAYED);
+        addProxyMsg.arg1 = bluetoothProfile;
+        addProxyMsg.obj = proxy;
+        mHandler.sendMessageDelayed(addProxyMsg, ADD_PROXY_DELAY_MS);
+        return true;
+    }
+
+    @Override
+    public void unbindBluetoothProfileService(int bluetoothProfile,
+            IBluetoothProfileServiceConnection proxy) {
+        synchronized (mProfileServices) {
+            ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile));
+            if (psc == null) {
+                return;
+            }
+            psc.removeProxy(proxy);
+        }
+    }
+
+    private void unbindAllBluetoothProfileServices() {
+        synchronized (mProfileServices) {
+            for (Integer i : mProfileServices.keySet()) {
+                ProfileServiceConnections psc = mProfileServices.get(i);
+                mContext.unbindService(psc);
+                psc.removeAllProxies();
+            }
+            mProfileServices.clear();
+        }
+    }
+
+    /**
+     * This class manages the clients connected to a given ProfileService
+     * and maintains the connection with that service.
+     */
+    final private class ProfileServiceConnections implements ServiceConnection,
+            IBinder.DeathRecipient {
+        final RemoteCallbackList<IBluetoothProfileServiceConnection> mProxies =
+                new RemoteCallbackList <IBluetoothProfileServiceConnection>();
+        IBinder mService;
+        ComponentName mClassName;
+        Intent mIntent;
+
+        ProfileServiceConnections(Intent intent) {
+            mService = null;
+            mClassName = null;
+            mIntent = intent;
+        }
+
+        private void bindService() {
+            if (mIntent != null && mService == null) {
+                if (!doBind(mIntent, this, 0, UserHandle.CURRENT_OR_SELF)) {
+                    Log.w(TAG, "Unable to bind with intent: " + mIntent
+                            + ". Triggering retry.");
+                }
+                Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+                msg.obj = this;
+                mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS);
+            }
+        }
+
+        private void addProxy(IBluetoothProfileServiceConnection proxy) {
+            mProxies.register(proxy);
+            if (mService != null) {
+                try{
+                    proxy.onServiceConnected(mClassName, mService);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to connect to proxy", e);
+                }
+            } else {
+                if (!mHandler.hasMessages(MESSAGE_BIND_PROFILE_SERVICE, this)) {
+                    Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+                    msg.obj = this;
+                    mHandler.sendMessage(msg);
+                }
+            }
+        }
+
+        private void removeProxy(IBluetoothProfileServiceConnection proxy) {
+            if (proxy != null) {
+                if (mProxies.unregister(proxy)) {
+                    try {
+                        proxy.onServiceDisconnected(mClassName);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Unable to disconnect proxy", e);
+                    }
+                }
+            } else {
+                Log.w(TAG, "Trying to remove a null proxy");
+            }
+        }
+
+        private void removeAllProxies() {
+            onServiceDisconnected(mClassName);
+            mProxies.kill();
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // remove timeout message
+            mHandler.removeMessages(MESSAGE_BIND_PROFILE_SERVICE, this);
+            mService = service;
+            mClassName = className;
+            try {
+                mService.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to linkToDeath", e);
+            }
+            int n = mProxies.beginBroadcast();
+            for (int i = 0; i < n; i++) {
+                try {
+                    mProxies.getBroadcastItem(i).onServiceConnected(className, service);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to connect to proxy", e);
+                }
+            }
+            mProxies.finishBroadcast();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            if (mService == null) {
+                return;
+            }
+            mService.unlinkToDeath(this, 0);
+            mService = null;
+            mClassName = null;
+            int n = mProxies.beginBroadcast();
+            for (int i = 0; i < n; i++) {
+                try {
+                    mProxies.getBroadcastItem(i).onServiceDisconnected(className);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to disconnect from proxy", e);
+                }
+            }
+            mProxies.finishBroadcast();
+        }
+
+        @Override
+        public void binderDied() {
+            if (DBG) {
+                Log.w(TAG, "Profile service for profile: " + mClassName
+                        + " died.");
+            }
+            onServiceDisconnected(mClassName);
+            // Trigger rebind
+            Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+            msg.obj = this;
+            mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS);
+        }
+    }
+
     private void sendBluetoothStateCallback(boolean isUp) {
         int n = mStateChangeCallbacks.beginBroadcast();
         if (DBG) Log.d(TAG,"Broadcasting onBluetoothStateChange("+isUp+") to " + n + " receivers.");
@@ -803,6 +1003,28 @@
                     }
                     break;
                 }
+                case MESSAGE_ADD_PROXY_DELAYED:
+                {
+                    ProfileServiceConnections psc = mProfileServices.get(
+                            new Integer(msg.arg1));
+                    if (psc == null) {
+                        break;
+                    }
+                    IBluetoothProfileServiceConnection proxy =
+                            (IBluetoothProfileServiceConnection) msg.obj;
+                    psc.addProxy(proxy);
+                    break;
+                }
+                case MESSAGE_BIND_PROFILE_SERVICE:
+                {
+                    ProfileServiceConnections psc = (ProfileServiceConnections) msg.obj;
+                    removeMessages(MESSAGE_BIND_PROFILE_SERVICE, msg.obj);
+                    if (psc == null) {
+                        break;
+                    }
+                    psc.bindService();
+                    break;
+                }
                 case MESSAGE_BLUETOOTH_SERVICE_CONNECTED:
                 {
                     if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_CONNECTED: " + msg.arg1);
@@ -1005,6 +1227,7 @@
                             bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_ON);
                         }
 
+                        unbindAllBluetoothProfileServices();
                         // disable
                         handleDisable();
                         // Pbap service need receive STATE_TURNING_OFF intent to close
@@ -1129,16 +1352,21 @@
         int callingUser = UserHandle.getCallingUserId();
         int callingUid = Binder.getCallingUid();
         long callingIdentity = Binder.clearCallingIdentity();
+        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        UserInfo ui = um.getProfileParent(callingUser);
+        int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL;
         int callingAppId = UserHandle.getAppId(callingUid);
         boolean valid = false;
         try {
             foregroundUser = ActivityManager.getCurrentUser();
             valid = (callingUser == foregroundUser) ||
+                    parentUser == foregroundUser    ||
                     callingAppId == Process.NFC_UID ||
                     callingAppId == mSystemUiUid;
             if (DBG) {
                 Log.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid
                     + " callingUser=" + callingUser
+                    + " parentUser=" + parentUser
                     + " foregroundUser=" + foregroundUser);
             }
         } finally {
@@ -1165,6 +1393,7 @@
                 } else {
                     //If Bluetooth is off, send service down event to proxy objects, and unbind
                     if (!isUp && canUnbindBluetoothService()) {
+                        unbindAllBluetoothProfileServices();
                         sendBluetoothServiceDownCallback();
                         unbindAndFinish();
                     }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 84fddd7..0f1ed0a 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2076,6 +2076,17 @@
         }
     }
 
+    // Cancel any lingering so the linger timeout doesn't teardown a network.
+    // This should be called when a network begins satisfying a NetworkRequest.
+    // Note: depending on what state the NetworkMonitor is in (e.g.,
+    // if it's awaiting captive portal login, or if validation failed), this
+    // may trigger a re-evaluation of the network.
+    private void unlinger(NetworkAgentInfo nai) {
+        if (VDBG) log("Canceling linger of " + nai.name());
+        nai.networkLingered.clear();
+        nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+    }
+
     private void handleAsyncChannelHalfConnect(Message msg) {
         AsyncChannel ac = (AsyncChannel) msg.obj;
         if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
@@ -2111,6 +2122,7 @@
             }
         }
     }
+
     private void handleAsyncChannelDisconnected(Message msg) {
         NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
         if (nai != null) {
@@ -2160,11 +2172,8 @@
                     mNetworkForRequestId.remove(request.requestId);
                     sendUpdatedScoreToFactories(request, 0);
                     NetworkAgentInfo alternative = null;
-                    for (Map.Entry entry : mNetworkAgentInfos.entrySet()) {
-                        NetworkAgentInfo existing = (NetworkAgentInfo)entry.getValue();
-                        if (existing.networkInfo.isConnected() &&
-                                request.networkCapabilities.satisfiedByNetworkCapabilities(
-                                existing.networkCapabilities) &&
+                    for (NetworkAgentInfo existing : mNetworkAgentInfos.values()) {
+                        if (existing.satisfies(request) &&
                                 (alternative == null ||
                                  alternative.getCurrentScore() < existing.getCurrentScore())) {
                             alternative = existing;
@@ -2184,8 +2193,7 @@
                 requestNetworkTransitionWakelock(nai.name());
             }
             for (NetworkAgentInfo networkToActivate : toActivate) {
-                networkToActivate.networkLingered.clear();
-                networkToActivate.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+                unlinger(networkToActivate);
                 rematchNetworkAndRequests(networkToActivate, false);
             }
         }
@@ -2220,44 +2228,35 @@
 
     private void handleRegisterNetworkRequest(Message msg) {
         final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
-        final NetworkCapabilities newCap = nri.request.networkCapabilities;
-        int score = 0;
 
         mNetworkRequests.put(nri.request, nri);
 
+        // TODO: This logic may be better replaced with a call to rematchNetworkAndRequests
+
         // Check for the best currently alive network that satisfies this request
         NetworkAgentInfo bestNetwork = null;
         for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
             if (DBG) log("handleRegisterNetworkRequest checking " + network.name());
-            if (newCap.satisfiedByNetworkCapabilities(network.networkCapabilities)) {
+            if (network.satisfies(nri.request)) {
                 if (DBG) log("apparently satisfied.  currentScore=" + network.getCurrentScore());
-                if ((bestNetwork == null) ||
+                if (!nri.isRequest) {
+                    // Not setting bestNetwork here as a listening NetworkRequest may be
+                    // satisfied by multiple Networks.  Instead the request is added to
+                    // each satisfying Network and notified about each.
+                    network.addRequest(nri.request);
+                    notifyNetworkCallback(network, nri);
+                } else if (bestNetwork == null ||
                         bestNetwork.getCurrentScore() < network.getCurrentScore()) {
-                    if (!nri.isRequest) {
-                        // Not setting bestNetwork here as a listening NetworkRequest may be
-                        // satisfied by multiple Networks.  Instead the request is added to
-                        // each satisfying Network and notified about each.
-                        network.addRequest(nri.request);
-                        notifyNetworkCallback(network, nri);
-                    } else {
-                        bestNetwork = network;
-                    }
+                    bestNetwork = network;
                 }
             }
         }
         if (bestNetwork != null) {
             if (DBG) log("using " + bestNetwork.name());
-            if (bestNetwork.networkInfo.isConnected()) {
-                // Cancel any lingering so the linger timeout doesn't teardown this network
-                // even though we have a request for it.
-                bestNetwork.networkLingered.clear();
-                bestNetwork.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
-            }
-            // TODO: This logic may be better replaced with a call to rematchNetworkAndRequests
+            unlinger(bestNetwork);
             bestNetwork.addRequest(nri.request);
             mNetworkForRequestId.put(nri.request.requestId, bestNetwork);
             notifyNetworkCallback(bestNetwork, nri);
-            score = bestNetwork.getCurrentScore();
             if (nri.request.legacyType != TYPE_NONE) {
                 mLegacyTypeTracker.add(nri.request.legacyType, bestNetwork);
             }
@@ -2265,6 +2264,7 @@
 
         if (nri.isRequest) {
             if (DBG) log("sending new NetworkRequest to factories");
+            final int score = bestNetwork == null ? 0 : bestNetwork.getCurrentScore();
             for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
                 nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
                         0, nri.request);
@@ -3966,8 +3966,7 @@
 
             // check if it satisfies the NetworkCapabilities
             if (VDBG) log("  checking if request is satisfied: " + nri.request);
-            if (nri.request.networkCapabilities.satisfiedByNetworkCapabilities(
-                    newNetwork.networkCapabilities)) {
+            if (newNetwork.satisfies(nri.request)) {
                 if (!nri.isRequest) {
                     // This is not a request, it's a callback listener.
                     // Add it to newNetwork regardless of score.
@@ -4047,12 +4046,7 @@
                 nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
                 notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
             } else {
-                // not going to linger, so kill the list of linger networks..  only
-                // notify them of linger if it happens as the result of gaining another,
-                // but if they transition and old network stays up, don't tell them of linger
-                // or very delayed loss
-                nai.networkLingered.clear();
-                if (VDBG) log("Lingered for " + nai.name() + " cleared");
+                unlinger(nai);
             }
         }
         if (keep) {
diff --git a/services/core/java/com/android/server/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java
index bc851a3..e0b2307 100644
--- a/services/core/java/com/android/server/MountServiceIdler.java
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -18,12 +18,14 @@
 
 import java.util.Calendar;
 
+import android.app.ActivityManagerNative;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.RemoteException;
 import android.util.Slog;
 
 public class MountServiceIdler extends JobService {
@@ -53,6 +55,13 @@
 
     @Override
     public boolean onStartJob(JobParameters params) {
+        // First have the activity manager do its idle maintenance.  (Yes this job
+        // is really more than just mount, some day it should be renamed to be system
+        // idleer).
+        try {
+            ActivityManagerNative.getDefault().performIdleMaintenance();
+        } catch (RemoteException e) {
+        }
         // The mount service will run an fstrim operation asynchronously
         // on a designated separate thread, so we provide it with a callback
         // that lets us cleanly end our idle timeslice.  It's safe to call
@@ -98,7 +107,7 @@
     private static Calendar tomorrowMidnight() {
         Calendar calendar = Calendar.getInstance();
         calendar.setTimeInMillis(System.currentTimeMillis());
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.HOUR_OF_DAY, 3);
         calendar.set(Calendar.MINUTE, 0);
         calendar.set(Calendar.SECOND, 0);
         calendar.set(Calendar.MILLISECOND, 0);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6823119..5b1d212 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -30,6 +30,7 @@
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 
 import android.Manifest;
 import android.app.AppOpsManager;
@@ -1669,7 +1670,9 @@
                 break;
             }
             case REQUEST_ALL_PSS_MSG: {
-                requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
+                synchronized (ActivityManagerService.this) {
+                    requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
+                }
                 break;
             }
             case START_PROFILES_MSG: {
@@ -2808,12 +2811,12 @@
         if (app == null) {
             checkTime(startTime, "startProcess: creating new process record");
             app = newProcessRecordLocked(info, processName, isolated, isolatedUid);
-            app.crashHandler = crashHandler;
             if (app == null) {
                 Slog.w(TAG, "Failed making new process record for "
                         + processName + "/" + info.uid + " isolated=" + isolated);
                 return null;
             }
+            app.crashHandler = crashHandler;
             mProcessNames.put(processName, app.uid, app);
             if (isolated) {
                 mIsolatedProcesses.put(app.uid, app);
@@ -3905,7 +3908,7 @@
                     + endIndex + " " + cur);
             if (cur == top) {
                 // Verify start of the chain.
-                if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != -1) {
+                if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
                     Slog.wtf(TAG, "Bad chain @" + endIndex
                             + ": first task has next affiliate: " + prev);
                     sane = false;
@@ -3924,7 +3927,7 @@
                     break;
                 }
             }
-            if (cur.mPrevAffiliateTaskId == -1) {
+            if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) {
                 // Chain ends here.
                 if (cur.mPrevAffiliate != null) {
                     Slog.wtf(TAG, "Bad chain @" + endIndex
@@ -3989,7 +3992,8 @@
 
     final void addRecentTaskLocked(TaskRecord task) {
         final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
-                || task.mNextAffiliateTaskId != -1 || task.mPrevAffiliateTaskId != -1;
+                || task.mNextAffiliateTaskId != INVALID_TASK_ID
+                || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
 
         int N = mRecentTasks.size();
         // Quick case: check if the top-most recent task is the same.
@@ -5878,6 +5882,7 @@
         app.hasShownUi = false;
         app.debugging = false;
         app.cached = false;
+        app.killedByAm = false;
 
         mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
 
@@ -6219,6 +6224,17 @@
         }
     }
 
+    @Override
+    public void systemBackupRestored() {
+        synchronized (this) {
+            if (mSystemReady) {
+                mTaskPersister.restoreTasksFromOtherDeviceLocked();
+            } else {
+                Slog.w(TAG, "System backup restored before system is ready");
+            }
+        }
+    }
+
     final void ensureBootCompleted() {
         boolean booting;
         boolean enableScreen;
@@ -8033,7 +8049,7 @@
 
         // Compose the recent task info
         ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
-        rti.id = tr.getTopActivity() == null ? -1 : tr.taskId;
+        rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
         rti.persistentId = tr.taskId;
         rti.baseIntent = new Intent(tr.getBaseIntent());
         rti.origActivity = tr.origActivity;
@@ -8256,7 +8272,7 @@
                 if (trimIdx >= 0) {
                     // If this would have caused a trim, then we'll abort because that
                     // means it would be added at the end of the list but then just removed.
-                    return -1;
+                    return INVALID_TASK_ID;
                 }
 
                 final int N = mRecentTasks.size();
@@ -11143,6 +11159,7 @@
 
             if (mRecentTasks == null) {
                 mRecentTasks = mTaskPersister.restoreTasksLocked();
+                mTaskPersister.restoreTasksFromOtherDeviceLocked();
                 if (!mRecentTasks.isEmpty()) {
                     mStackSupervisor.createStackForRestoredTaskHistory(mRecentTasks);
                 }
@@ -12883,7 +12900,7 @@
                     + PowerManagerInternal.wakefulnessToString(mWakefulness));
             pw.println("  mSleeping=" + mSleeping + " mLockScreenShown="
                     + lockScreenShownToString());
-            pw.print("  mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice);
+            pw.println("  mShuttingDown=" + mShuttingDown + " mRunningVoice=" + mRunningVoice);
         }
         if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
                 || mOrigWaitForDebugger) {
@@ -15789,6 +15806,9 @@
                                     }
                                 } else {
                                     removeTasksByRemovedPackageComponentsLocked(ssp, userId);
+                                    if (userId == UserHandle.USER_OWNER) {
+                                        mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
+                                    }
                                 }
                             }
                             break;
@@ -15806,6 +15826,9 @@
                         if (replacing) {
                             removeTasksByRemovedPackageComponentsLocked(ssp, userId);
                         }
+                        if (userId == UserHandle.USER_OWNER) {
+                            mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
+                        }
                     }
                     break;
                 case Intent.ACTION_TIMEZONE_CHANGED:
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e37d5f3..912ca62 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -16,6 +16,10 @@
 
 package com.android.server.am;
 
+import static com.android.server.am.TaskPersister.DEBUG_PERSISTER;
+import static com.android.server.am.TaskPersister.DEBUG_RESTORER;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+
 import android.app.ActivityManager.TaskDescription;
 import android.os.PersistableBundle;
 import android.os.Trace;
@@ -1052,12 +1056,12 @@
     static int getTaskForActivityLocked(IBinder token, boolean onlyRoot) {
         final ActivityRecord r = ActivityRecord.forToken(token);
         if (r == null) {
-            return -1;
+            return INVALID_TASK_ID;
         }
         final TaskRecord task = r.task;
         final int activityNdx = task.mActivities.indexOf(r);
         if (activityNdx < 0 || (onlyRoot && activityNdx > task.findEffectiveRootIndex())) {
-            return -1;
+            return INVALID_TASK_ID;
         }
         return task.taskId;
     }
@@ -1139,7 +1143,7 @@
         }
     }
 
-    static ActivityRecord restoreFromXml(XmlPullParser in, int taskId,
+    static ActivityRecord restoreFromXml(XmlPullParser in,
             ActivityStackSupervisor stackSupervisor) throws IOException, XmlPullParserException {
         Intent intent = null;
         PersistableBundle persistentState = null;
@@ -1155,8 +1159,8 @@
         for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
             final String attrName = in.getAttributeName(attrNdx);
             final String attrValue = in.getAttributeValue(attrNdx);
-            if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "ActivityRecord: attribute name=" +
-                    attrName + " value=" + attrValue);
+            if (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
+                        "ActivityRecord: attribute name=" + attrName + " value=" + attrValue);
             if (ATTR_ID.equals(attrName)) {
                 createTime = Long.valueOf(attrValue);
             } else if (ATTR_LAUNCHEDFROMUID.equals(attrName)) {
@@ -1181,15 +1185,15 @@
                 (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
             if (event == XmlPullParser.START_TAG) {
                 final String name = in.getName();
-                if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
-                        "ActivityRecord: START_TAG name=" + name);
+                if (DEBUG_PERSISTER || DEBUG_RESTORER)
+                        Slog.d(TaskPersister.TAG, "ActivityRecord: START_TAG name=" + name);
                 if (TAG_INTENT.equals(name)) {
                     intent = Intent.restoreFromXml(in);
-                    if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
-                            "ActivityRecord: intent=" + intent);
+                    if (DEBUG_PERSISTER || DEBUG_RESTORER)
+                            Slog.d(TaskPersister.TAG, "ActivityRecord: intent=" + intent);
                 } else if (TAG_PERSISTABLEBUNDLE.equals(name)) {
                     persistentState = PersistableBundle.restoreFromXml(in);
-                    if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
+                    if (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
                             "ActivityRecord: persistentState=" + persistentState);
                 } else {
                     Slog.w(TAG, "restoreActivity: unexpected name=" + name);
@@ -1232,7 +1236,7 @@
     @Override
     public String toString() {
         if (stringName != null) {
-            return stringName + " t" + (task == null ? -1 : task.taskId) +
+            return stringName + " t" + (task == null ? INVALID_TASK_ID : task.taskId) +
                     (finishing ? " f}" : "}");
         }
         StringBuilder sb = new StringBuilder(128);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ad86aea..d98f03c 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1824,6 +1824,9 @@
                     // Do over!
                     mStackSupervisor.scheduleResumeTopActivities();
                 }
+                if (next == mLastScreenshotActivity) {
+                    invalidateLastScreenshot();
+                }
                 if (mStackSupervisor.reportResumedActivityLocked(next)) {
                     mNoAnimActivities.clear();
                     if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d9396d8..262b4f1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1314,7 +1314,6 @@
                 }
             }
         }
-        ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
 
         final int launchFlags = intent.getFlags();
 
@@ -1391,6 +1390,8 @@
             }
         }
 
+        final ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
+
         if (err != ActivityManager.START_SUCCESS) {
             if (resultRecord != null) {
                 resultStack.sendActivityResultLocked(-1,
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e01b983..9b7d0b2 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -296,7 +296,7 @@
     public void skipCurrentReceiverLocked(ProcessRecord app) {
         boolean reschedule = false;
         BroadcastRecord r = app.curReceiver;
-        if (r != null) {
+        if (r != null && r.queue == this) {
             // The current broadcast is waiting for this app's receiver
             // to be finished.  Looks like that's not going to happen, so
             // let the broadcast continue.
@@ -352,7 +352,7 @@
         }
         r.receiver = null;
         r.intent.setComponent(null);
-        if (r.curApp != null) {
+        if (r.curApp != null && r.curApp.curReceiver == r) {
             r.curApp.curReceiver = null;
         }
         if (r.curFilter != null) {
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 9311f25..629a05d 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,16 +16,27 @@
 
 package com.android.server.am;
 
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.IPackageManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Debug;
+import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.Xml;
+
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -38,11 +49,18 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.List;
+
+import libcore.io.IoUtils;
+
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 
 public class TaskPersister {
     static final String TAG = "TaskPersister";
-    static final boolean DEBUG = false;
+    static final boolean DEBUG_PERSISTER = false;
+    static final boolean DEBUG_RESTORER = false;
 
     /** When not flushing don't write out files faster than this */
     private static final long INTER_WRITE_DELAY_MS = 500;
@@ -67,12 +85,17 @@
     // contains subdirs named after TASKS_DIRNAME and IMAGES_DIRNAME mirroring the
     // ancestral device's dataset.  This needs to match the RECENTS_TASK_RESTORE_DIR
     // value in RecentsBackupHelper.
-    private static final String RESTORED_TASKS = "restored_" + TASKS_DIRNAME;
+    private static final String RESTORED_TASKS_DIRNAME = "restored_" + TASKS_DIRNAME;
+
+    // Max time to wait for the application/package of a restored task to be installed
+    // before giving up.
+    private static final long MAX_INSTALL_WAIT_TIME = DateUtils.DAY_IN_MILLIS;
 
     private static final String TAG_TASK = "task";
 
     static File sImagesDir;
     static File sTasksDir;
+    static File sRestoredTasksDir;
 
     private final ActivityManagerService mService;
     private final ActivityStackSupervisor mStackSupervisor;
@@ -105,10 +128,20 @@
 
     ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
 
+    // Map of tasks that were backed-up on a different device that can be restored on this device.
+    // Data organization: <packageNameOfAffiliateTask, listOfAffiliatedTasksChains>
+    private ArrayMap<String, List<List<OtherDeviceTask>>> mOtherDeviceTasksMap =
+                new ArrayMap<>(10);
+
+    // The next time in milliseconds we will remove expired task from
+    // {@link #mOtherDeviceTasksMap} and disk. Set to {@link Long.MAX_VALUE} to never clean-up
+    // tasks.
+    private long mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
     TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
         sTasksDir = new File(systemDir, TASKS_DIRNAME);
         if (!sTasksDir.exists()) {
-            if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
+            if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
             if (!sTasksDir.mkdir()) {
                 Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
             }
@@ -116,12 +149,14 @@
 
         sImagesDir = new File(systemDir, IMAGES_DIRNAME);
         if (!sImagesDir.exists()) {
-            if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
+            if (DEBUG_PERSISTER) Slog.d(TAG, "Creating images directory " + sTasksDir);
             if (!sImagesDir.mkdir()) {
                 Slog.e(TAG, "Failure creating images directory " + sImagesDir);
             }
         }
 
+        sRestoredTasksDir = new File(systemDir, RESTORED_TASKS_DIRNAME);
+
         mStackSupervisor = stackSupervisor;
         mService = stackSupervisor.mService;
 
@@ -138,8 +173,8 @@
             final WriteQueueItem item = mWriteQueue.get(queueNdx);
             if (item instanceof ImageWriteQueueItem &&
                     ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
-                if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilename +
-                        " from write queue");
+                if (DEBUG_PERSISTER) Slog.d(TAG, "Removing "
+                        + ((ImageWriteQueueItem) item).mFilename + " from write queue");
                 mWriteQueue.remove(queueNdx);
             }
         }
@@ -184,9 +219,9 @@
             } else if (mNextWriteTime == 0) {
                 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
             }
-            if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime="
-                    + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()
-                    + " Callers=" + Debug.getCallers(4));
+            if (DEBUG_PERSISTER) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush
+                    + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
+                    + mWriteQueue.size() + " Callers=" + Debug.getCallers(4));
             notifyAll();
         }
 
@@ -228,7 +263,7 @@
             } else if (mNextWriteTime == 0) {
                 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
             }
-            if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
+            if (DEBUG_PERSISTER) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
                     SystemClock.uptimeMillis() + " mNextWriteTime=" +
                     mNextWriteTime + " Callers=" + Debug.getCallers(4));
             notifyAll();
@@ -262,12 +297,12 @@
     }
 
     private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
-        if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
+        if (DEBUG_PERSISTER) Slog.d(TAG, "saveToXml: task=" + task);
         final XmlSerializer xmlSerializer = new FastXmlSerializer();
         StringWriter stringWriter = new StringWriter();
         xmlSerializer.setOutput(stringWriter);
 
-        if (DEBUG) xmlSerializer.setFeature(
+        if (DEBUG_PERSISTER) xmlSerializer.setFeature(
                     "http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
         // save task
@@ -326,7 +361,7 @@
 
         for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
             File taskFile = recentFiles[taskNdx];
-            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
+            if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
             BufferedReader reader = null;
             boolean deleteFile = false;
             try {
@@ -339,11 +374,12 @@
                         event != XmlPullParser.END_TAG) {
                     final String name = in.getName();
                     if (event == XmlPullParser.START_TAG) {
-                        if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
+                        if (DEBUG_PERSISTER)
+                                Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
                         if (TAG_TASK.equals(name)) {
                             final TaskRecord task =
                                     TaskRecord.restoreFromXml(in, mStackSupervisor);
-                            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
+                            if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: restored task=" +
                                     task);
                             if (task != null) {
                                 task.isPersistable = true;
@@ -371,20 +407,16 @@
                 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
                 deleteFile = true;
             } finally {
-                if (reader != null) {
-                    try {
-                        reader.close();
-                    } catch (IOException e) {
-                    }
-                }
-                if (!DEBUG && deleteFile) {
-                    if (true || DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
+                IoUtils.closeQuietly(reader);
+                if (!DEBUG_PERSISTER && deleteFile) {
+                    if (true || DEBUG_PERSISTER)
+                            Slog.d(TAG, "Deleting file=" + taskFile.getName());
                     taskFile.delete();
                 }
             }
         }
 
-        if (!DEBUG) {
+        if (!DEBUG_PERSISTER) {
             removeObsoleteFiles(recoveredTaskIds);
         }
 
@@ -415,8 +447,8 @@
     }
 
     private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
-        if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds +
-                " files=" + files);
+        if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds="
+                    + persistentTaskIds + " files=" + files);
         if (files == null) {
             Slog.e(TAG, "File error accessing recents directory (too many files open?).");
             return;
@@ -429,14 +461,14 @@
                 final int taskId;
                 try {
                     taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
-                    if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
+                    if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
                 } catch (Exception e) {
                     Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
                     file.delete();
                     continue;
                 }
                 if (!persistentTaskIds.contains(taskId)) {
-                    if (true || DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
+                    if (true || DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
                             file.getName());
                     file.delete();
                 }
@@ -450,10 +482,363 @@
     }
 
     static Bitmap restoreImage(String filename) {
-        if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
+        if (DEBUG_PERSISTER) Slog.d(TAG, "restoreImage: restoring " + filename);
         return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
     }
 
+    /**
+     * Tries to restore task that were backed-up on a different device onto this device.
+     */
+    void restoreTasksFromOtherDeviceLocked() {
+        readOtherDeviceTasksFromDisk();
+        addOtherDeviceTasksToRecentsLocked();
+    }
+
+    /**
+     * Read the tasks that were backed-up on a different device and can be restored to this device
+     * from disk and populated {@link #mOtherDeviceTasksMap} with the information. Also sets up
+     * time to clear out other device tasks that have not been restored on this device
+     * within the allotted time.
+     */
+    private void readOtherDeviceTasksFromDisk() {
+        synchronized (mOtherDeviceTasksMap) {
+            // Clear out current map and expiration time.
+            mOtherDeviceTasksMap.clear();
+            mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
+            final File[] taskFiles;
+            if (!sRestoredTasksDir.exists()
+                    || (taskFiles = sRestoredTasksDir.listFiles()) == null) {
+                // Nothing to do if there are no tasks to restore.
+                return;
+            }
+
+            long earliestMtime = System.currentTimeMillis();
+            SparseArray<List<OtherDeviceTask>> tasksByAffiliateIds =
+                        new SparseArray<>(taskFiles.length);
+
+            // Read new tasks from disk
+            for (int i = 0; i < taskFiles.length; ++i) {
+                final File taskFile = taskFiles[i];
+                if (DEBUG_RESTORER) Slog.d(TAG, "readOtherDeviceTasksFromDisk: taskFile="
+                            + taskFile.getName());
+
+                final OtherDeviceTask task = OtherDeviceTask.createFromFile(taskFile);
+
+                if (task == null) {
+                    // Go ahead and remove the file on disk if we are unable to create a task from
+                    // it.
+                    if (DEBUG_RESTORER) Slog.e(TAG, "Unable to create task for file="
+                                + taskFile.getName() + "...deleting file.");
+                    taskFile.delete();
+                    continue;
+                }
+
+                List<OtherDeviceTask> tasks = tasksByAffiliateIds.get(task.mAffiliatedTaskId);
+                if (tasks == null) {
+                    tasks = new ArrayList<>();
+                    tasksByAffiliateIds.put(task.mAffiliatedTaskId, tasks);
+                }
+                tasks.add(task);
+                final long taskMtime = taskFile.lastModified();
+                if (earliestMtime > taskMtime) {
+                    earliestMtime = taskMtime;
+                }
+            }
+
+            if (tasksByAffiliateIds.size() > 0) {
+                // Sort each affiliated tasks chain by taskId which is the order they were created
+                // that should always be correct...Then add to task map.
+                for (int i = 0; i < tasksByAffiliateIds.size(); i++) {
+                    List<OtherDeviceTask> chain = tasksByAffiliateIds.valueAt(i);
+                    Collections.sort(chain);
+                    // Package name of the root task in the affiliate chain.
+                    final String packageName =
+                            chain.get(chain.size()-1).mComponentName.getPackageName();
+                    List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
+                    if (chains == null) {
+                        chains = new ArrayList<>();
+                        mOtherDeviceTasksMap.put(packageName, chains);
+                    }
+                    chains.add(chain);
+                }
+
+                // Set expiration time.
+                mExpiredTasksCleanupTime = earliestMtime + MAX_INSTALL_WAIT_TIME;
+                if (DEBUG_RESTORER) Slog.d(TAG, "Set Expiration time to "
+                            + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
+                            DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
+            }
+        }
+    }
+
+    /**
+     * Removed any expired tasks from {@link #mOtherDeviceTasksMap} and disk if their expiration
+     * time is less than or equal to {@link #mExpiredTasksCleanupTime}.
+     */
+    private void removeExpiredTasksIfNeeded() {
+        synchronized (mOtherDeviceTasksMap) {
+            final long now = System.currentTimeMillis();
+            if (mOtherDeviceTasksMap.isEmpty() || now < mExpiredTasksCleanupTime) {
+                return;
+            }
+
+            long earliestNonExpiredMtime = now;
+            mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
+            // Remove expired backed-up tasks that have not been restored. We only want to
+            // remove task if it is safe to remove all tasks in the affiliation chain.
+            for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0 ; i--) {
+
+                List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.valueAt(i);
+                for (int j = chains.size() - 1; j >= 0 ; j--) {
+
+                    List<OtherDeviceTask> chain = chains.get(j);
+                    boolean removeChain = true;
+                    for (int k = chain.size() - 1; k >= 0 ; k--) {
+                        OtherDeviceTask task = chain.get(k);
+                        final long taskLastModified = task.mFile.lastModified();
+                        if ((taskLastModified + MAX_INSTALL_WAIT_TIME) > now) {
+                            // File has not expired yet...but we keep looping to get the earliest
+                            // mtime.
+                            if (earliestNonExpiredMtime > taskLastModified) {
+                                earliestNonExpiredMtime = taskLastModified;
+                            }
+                            removeChain = false;
+                        }
+                    }
+                    if (removeChain) {
+                        for (int k = chain.size() - 1; k >= 0; k--) {
+                            final File file = chain.get(k).mFile;
+                            if (DEBUG_RESTORER) Slog.d(TAG, "Deleting expired file="
+                                    + file.getName() + " mapped to not installed component="
+                                    + chain.get(k).mComponentName);
+                            file.delete();
+                        }
+                        chains.remove(j);
+                    }
+                }
+                if (chains.isEmpty()) {
+                    final String packageName = mOtherDeviceTasksMap.keyAt(i);
+                    mOtherDeviceTasksMap.removeAt(i);
+                    if (DEBUG_RESTORER) Slog.d(TAG, "Removed package=" + packageName
+                                + " from task map");
+                }
+            }
+
+            // Reset expiration time if there is any task remaining.
+            if (!mOtherDeviceTasksMap.isEmpty()) {
+                mExpiredTasksCleanupTime = earliestNonExpiredMtime + MAX_INSTALL_WAIT_TIME;
+                if (DEBUG_RESTORER) Slog.d(TAG, "Reset expiration time to "
+                            + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
+                            DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
+            }
+        }
+    }
+
+    /**
+     * Tries to add all backed-up tasks from another device to this device recent's list.
+     */
+    private void addOtherDeviceTasksToRecentsLocked() {
+        synchronized (mOtherDeviceTasksMap) {
+            for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0; i--) {
+                addOtherDeviceTasksToRecentsLocked(mOtherDeviceTasksMap.keyAt(i));
+            }
+        }
+    }
+
+    /**
+     * Tries to add backed-up tasks that are associated with the input package from
+     * another device to this device recent's list.
+     */
+    void addOtherDeviceTasksToRecentsLocked(String packageName) {
+        synchronized (mOtherDeviceTasksMap) {
+            List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
+            if (chains == null) {
+                return;
+            }
+
+            for (int i = chains.size() - 1; i >= 0; i--) {
+                List<OtherDeviceTask> chain = chains.get(i);
+                if (!canAddOtherDeviceTaskChain(chain)) {
+                    if (DEBUG_RESTORER) Slog.d(TAG, "Can't add task chain at index=" + i
+                            + " for package=" + packageName);
+                    continue;
+                }
+
+                // Generate task records for this chain.
+                List<TaskRecord> tasks = new ArrayList<>();
+                TaskRecord prev = null;
+                for (int j = chain.size() - 1; j >= 0; j--) {
+                    TaskRecord task = createTaskRecordLocked(chain.get(j));
+                    if (task == null) {
+                        // There was a problem in creating one of this task records in this chain.
+                        // There is no way we can continue...
+                        if (DEBUG_RESTORER) Slog.d(TAG, "Can't create task record for file="
+                                + chain.get(j).mFile + " for package=" + packageName);
+                        break;
+                    }
+
+                    // Wire-up affiliation chain.
+                    if (prev == null) {
+                        task.mPrevAffiliate = null;
+                        task.mPrevAffiliateTaskId = INVALID_TASK_ID;
+                        task.mAffiliatedTaskId = task.taskId;
+                    } else {
+                        prev.mNextAffiliate = task;
+                        prev.mNextAffiliateTaskId = task.taskId;
+                        task.mAffiliatedTaskId = prev.mAffiliatedTaskId;
+                        task.mPrevAffiliate = prev;
+                        task.mPrevAffiliateTaskId = prev.taskId;
+                    }
+                    prev = task;
+                    tasks.add(0, task);
+                }
+
+                // Add tasks to recent's if we were able to create task records for all the tasks
+                // in the chain.
+                if (tasks.size() == chain.size()) {
+                    // Make sure there is space in recent's to add the new task. If there is space
+                    // to the to the back.
+                    // TODO: Would be more fancy to interleave the new tasks into recent's based on
+                    // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just
+                    // adding to the back of the list.
+                    int spaceLeft =
+                            ActivityManager.getMaxRecentTasksStatic()
+                            - mService.mRecentTasks.size();
+                    if (spaceLeft >= tasks.size()) {
+                        mService.mRecentTasks.addAll(mService.mRecentTasks.size(), tasks);
+                        for (int k = tasks.size() - 1; k >= 0; k--) {
+                            // Persist new tasks.
+                            wakeup(tasks.get(k), false);
+                        }
+
+                        if (DEBUG_RESTORER) Slog.d(TAG, "Added " + tasks.size()
+                                    + " tasks to recent's for" + " package=" + packageName);
+                    } else {
+                        if (DEBUG_RESTORER) Slog.d(TAG, "Didn't add to recents. tasks.size("
+                                    + tasks.size() + ") != chain.size(" + chain.size()
+                                    + ") for package=" + packageName);
+                    }
+                } else {
+                    if (DEBUG_RESTORER) Slog.v(TAG, "Unable to add restored tasks to recents "
+                            + tasks.size() + " tasks for package=" + packageName);
+                }
+
+                // Clean-up structures
+                for (int j = chain.size() - 1; j >= 0; j--) {
+                    chain.get(j).mFile.delete();
+                }
+                chains.remove(i);
+                if (chains.isEmpty()) {
+                    // The fate of all backed-up tasks associated with this package has been
+                    // determine. Go ahead and remove it from the to-process list.
+                    mOtherDeviceTasksMap.remove(packageName);
+                    if (DEBUG_RESTORER)
+                            Slog.d(TAG, "Removed package=" + packageName + " from restore map");
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates and returns {@link TaskRecord} for the task from another device that can be used on
+     * this device. Returns null if the operation failed.
+     */
+    private TaskRecord createTaskRecordLocked(OtherDeviceTask other) {
+        File file = other.mFile;
+        BufferedReader reader = null;
+        TaskRecord task = null;
+        if (DEBUG_RESTORER) Slog.d(TAG, "createTaskRecordLocked: file=" + file.getName());
+
+        try {
+            reader = new BufferedReader(new FileReader(file));
+            final XmlPullParser in = Xml.newPullParser();
+            in.setInput(reader);
+
+            int event;
+            while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
+                    && event != XmlPullParser.END_TAG) {
+                final String name = in.getName();
+                if (event == XmlPullParser.START_TAG) {
+
+                    if (TAG_TASK.equals(name)) {
+                        // Create a task record using a task id that is valid for this device.
+                        task = TaskRecord.restoreFromXml(
+                                in, mStackSupervisor, mStackSupervisor.getNextTaskId());
+                        if (DEBUG_RESTORER)
+                                Slog.d(TAG, "createTaskRecordLocked: restored task=" + task);
+
+                        if (task != null) {
+                            task.isPersistable = true;
+                            task.inRecents = true;
+                            // Task can/should only be backed-up/restored for device owner.
+                            task.userId = UserHandle.USER_OWNER;
+                            // Clear out affiliated ids that are no longer valid on this device.
+                            task.mAffiliatedTaskId = INVALID_TASK_ID;
+                            task.mPrevAffiliateTaskId = INVALID_TASK_ID;
+                            task.mNextAffiliateTaskId = INVALID_TASK_ID;
+                        } else {
+                            Slog.e(TAG, "Unable to create task for backed-up file=" + file + ": "
+                                        + fileToString(file));
+                        }
+                    } else {
+                        Slog.wtf(TAG, "createTaskRecordLocked Unknown xml event=" + event
+                                    + " name=" + name);
+                    }
+                }
+                XmlUtils.skipCurrentTag(in);
+            }
+        } catch (Exception e) {
+            Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
+            Slog.e(TAG, "Failing file: " + fileToString(file));
+        } finally {
+            IoUtils.closeQuietly(reader);
+        }
+
+        return task;
+    }
+
+    /**
+     * Returns true if the input task chain backed-up from another device can be restored on this
+     * device.
+     */
+    private boolean canAddOtherDeviceTaskChain(List<OtherDeviceTask> chain) {
+
+        // Get component names of all the tasks in the chain.
+        // Mainly doing this to reduce checking for a component twice if two or more
+        // affiliations belong to the same component which is highly likely.
+        ArraySet<ComponentName> componentsToCheck = new ArraySet<>();
+        for (int i = 0; i < chain.size(); i++) {
+
+            OtherDeviceTask task = chain.get(i);
+            // Quick check, we can't add the task chain if any of its task files don't exist.
+            if (!task.mFile.exists()) {
+                if (DEBUG_RESTORER)
+                        Slog.d(TAG, "Can't add chain due to missing file=" + task.mFile);
+                return false;
+            }
+            componentsToCheck.add(task.mComponentName);
+        }
+
+        boolean canAdd = true;
+        try {
+            // Check to see if all the components for this task chain are installed.
+            final IPackageManager pm = AppGlobals.getPackageManager();
+            for (int i = 0; canAdd && i < componentsToCheck.size(); i++) {
+                ComponentName cn = componentsToCheck.valueAt(i);
+                canAdd &= pm.getActivityInfo(cn, 0, UserHandle.USER_OWNER) != null;
+                if (DEBUG_RESTORER) Slog.d(TAG, "ComponentName=" + cn + " installed=" + canAdd);
+            }
+        } catch (RemoteException e) {
+            // Should not happen???
+            canAdd = false;
+        }
+
+        if (DEBUG_RESTORER) Slog.d(TAG, "canAdd=" + canAdd);
+        return canAdd;
+    }
+
     private class LazyTaskWriterThread extends Thread {
 
         LazyTaskWriterThread(String name) {
@@ -472,21 +857,22 @@
                     probablyDone = mWriteQueue.isEmpty();
                 }
                 if (probablyDone) {
-                    if (DEBUG) Slog.d(TAG, "Looking for obsolete files.");
+                    if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files.");
                     persistentTaskIds.clear();
                     synchronized (mService) {
                         final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
-                        if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
+                        if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks);
                         for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
                             final TaskRecord task = tasks.get(taskNdx);
-                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
+                            if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task +
                                     " persistable=" + task.isPersistable);
                             if ((task.isPersistable || task.inRecents)
-                                    && !task.stack.isHomeStack()) {
-                                if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
+                                    && (task.stack == null || !task.stack.isHomeStack())) {
+                                if (DEBUG_PERSISTER)
+                                        Slog.d(TAG, "adding to persistentTaskIds task=" + task);
                                 persistentTaskIds.add(task.taskId);
                             } else {
-                                if (DEBUG) Slog.d(TAG,
+                                if (DEBUG_PERSISTER) Slog.d(TAG,
                                         "omitting from persistentTaskIds task=" + task);
                             }
                         }
@@ -500,7 +886,7 @@
                     if (mNextWriteTime != FLUSH_QUEUE) {
                         // The next write we don't have to wait so long.
                         mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
-                        if (DEBUG) Slog.d(TAG, "Next write time may be in " +
+                        if (DEBUG_PERSISTER) Slog.d(TAG, "Next write time may be in " +
                                 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
                     }
 
@@ -510,8 +896,13 @@
                             mNextWriteTime = 0; // idle.
                             TaskPersister.this.notifyAll(); // wake up flush() if needed.
                         }
+
+                        // See if we need to remove any expired back-up tasks before waiting.
+                        removeExpiredTasksIfNeeded();
+
                         try {
-                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
+                            if (DEBUG_PERSISTER)
+                                    Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
                             TaskPersister.this.wait();
                         } catch (InterruptedException e) {
                         }
@@ -521,11 +912,12 @@
                     item = mWriteQueue.remove(0);
 
                     long now = SystemClock.uptimeMillis();
-                    if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" +
-                            mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size());
+                    if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: now=" + now
+                                + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
+                                + mWriteQueue.size());
                     while (now < mNextWriteTime) {
                         try {
-                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
+                            if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: waiting " +
                                     (mNextWriteTime - now));
                             TaskPersister.this.wait(mNextWriteTime - now);
                         } catch (InterruptedException e) {
@@ -540,7 +932,7 @@
                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
                     final String filename = imageWriteQueueItem.mFilename;
                     final Bitmap bitmap = imageWriteQueueItem.mImage;
-                    if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename);
+                    if (DEBUG_PERSISTER) Slog.d(TAG, "writing bitmap: filename=" + filename);
                     FileOutputStream imageFile = null;
                     try {
                         imageFile = new FileOutputStream(new File(sImagesDir, filename));
@@ -548,23 +940,18 @@
                     } catch (Exception e) {
                         Slog.e(TAG, "saveImage: unable to save " + filename, e);
                     } finally {
-                        if (imageFile != null) {
-                            try {
-                                imageFile.close();
-                            } catch (IOException e) {
-                            }
-                        }
+                        IoUtils.closeQuietly(imageFile);
                     }
                 } else if (item instanceof TaskWriteQueueItem) {
                     // Write out one task.
                     StringWriter stringWriter = null;
                     TaskRecord task = ((TaskWriteQueueItem) item).mTask;
-                    if (DEBUG) Slog.d(TAG, "Writing task=" + task);
+                    if (DEBUG_PERSISTER) Slog.d(TAG, "Writing task=" + task);
                     synchronized (mService) {
                         if (task.inRecents) {
                             // Still there.
                             try {
-                                if (DEBUG) Slog.d(TAG, "Saving task=" + task);
+                                if (DEBUG_PERSISTER) Slog.d(TAG, "Saving task=" + task);
                                 stringWriter = saveToXml(task);
                             } catch (IOException e) {
                             } catch (XmlPullParserException e) {
@@ -594,4 +981,100 @@
             }
         }
     }
+
+    /**
+     * Helper class for holding essential information about task that were backed-up on a different
+     * device that can be restored on this device.
+     */
+    private static class OtherDeviceTask implements Comparable<OtherDeviceTask> {
+        final File mFile;
+        // See {@link TaskRecord} for information on the fields below.
+        final ComponentName mComponentName;
+        final int mTaskId;
+        final int mAffiliatedTaskId;
+
+        private OtherDeviceTask(
+                File file, ComponentName componentName, int taskId, int affiliatedTaskId) {
+            mFile = file;
+            mComponentName = componentName;
+            mTaskId = taskId;
+            mAffiliatedTaskId = (affiliatedTaskId == INVALID_TASK_ID) ? taskId: affiliatedTaskId;
+        }
+
+        @Override
+        public int compareTo(OtherDeviceTask another) {
+            return mTaskId - another.mTaskId;
+        }
+
+        /**
+         * Creates a new {@link OtherDeviceTask} object based on the contents of the input file.
+         *
+         * @param file input file that contains the complete task information.
+         * @return new {@link OtherDeviceTask} object or null if we failed to create the object.
+         */
+        static OtherDeviceTask createFromFile(File file) {
+            if (file == null || !file.exists()) {
+                if (DEBUG_RESTORER)
+                    Slog.d(TAG, "createFromFile: file=" + file + " doesn't exist.");
+                return null;
+            }
+
+            BufferedReader reader = null;
+
+            try {
+                reader = new BufferedReader(new FileReader(file));
+                final XmlPullParser in = Xml.newPullParser();
+                in.setInput(reader);
+
+                int event;
+                while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+                        event != XmlPullParser.START_TAG) {
+                    // Skip to the start tag or end of document
+                }
+
+                if (event == XmlPullParser.START_TAG) {
+                    final String name = in.getName();
+
+                    if (TAG_TASK.equals(name)) {
+                        ComponentName componentName = null;
+                        int taskId = INVALID_TASK_ID;
+                        int taskAffiliation = INVALID_TASK_ID;
+                        for (int j = in.getAttributeCount() - 1; j >= 0; --j) {
+                            final String attrName = in.getAttributeName(j);
+                            final String attrValue = in.getAttributeValue(j);
+                            if (TaskRecord.ATTR_REALACTIVITY.equals(attrName)) {
+                                componentName = ComponentName.unflattenFromString(attrValue);
+                            } else if (TaskRecord.ATTR_TASKID.equals(attrName)) {
+                                taskId = Integer.valueOf(attrValue);
+                            } else if (TaskRecord.ATTR_TASK_AFFILIATION.equals(attrName)) {
+                                taskAffiliation = Integer.valueOf(attrValue);
+                            }
+                        }
+                        if (componentName == null || taskId == INVALID_TASK_ID) {
+                            if (DEBUG_RESTORER) Slog.e(TAG,
+                                    "createFromFile: FAILED componentName=" + componentName
+                                    + " taskId=" + taskId + " file=" + file);
+                            return null;
+                        }
+                        if (DEBUG_RESTORER) Slog.d(TAG, "creating OtherDeviceTask from file="
+                                + file.getName() + " componentName=" + componentName
+                                + " taskId=" + taskId);
+                        return new OtherDeviceTask(file, componentName, taskId, taskAffiliation);
+                    } else {
+                        Slog.wtf(TAG,
+                                "createFromFile: Unknown xml event=" + event + " name=" + name);
+                    }
+                } else {
+                    Slog.wtf(TAG, "createFromFile: Unable to find start tag in file=" + file);
+                }
+            } catch (IOException | XmlPullParserException e) {
+                Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
+            } finally {
+                IoUtils.closeQuietly(reader);
+            }
+
+            // Something went wrong...
+            return null;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index d726685..c3eda71 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -21,6 +21,8 @@
 import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
+import static com.android.server.am.TaskPersister.DEBUG_PERSISTER;
+import static com.android.server.am.TaskPersister.DEBUG_RESTORER;
 
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -52,10 +54,10 @@
 import java.util.ArrayList;
 
 final class TaskRecord {
-    private static final String ATTR_TASKID = "task_id";
+    static final String ATTR_TASKID = "task_id";
     private static final String TAG_INTENT = "intent";
     private static final String TAG_AFFINITYINTENT = "affinity_intent";
-    private static final String ATTR_REALACTIVITY = "real_activity";
+    static final String ATTR_REALACTIVITY = "real_activity";
     private static final String ATTR_ORIGACTIVITY = "orig_activity";
     private static final String TAG_ACTIVITY = "activity";
     private static final String ATTR_AFFINITY = "affinity";
@@ -71,7 +73,7 @@
     private static final String ATTR_LASTDESCRIPTION = "last_description";
     private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
     private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
-    private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
+    static final String ATTR_TASK_AFFILIATION = "task_affiliation";
     private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
     private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
     private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
@@ -82,6 +84,8 @@
 
     static final boolean IGNORE_RETURN_TO_RECENTS = true;
 
+    static final int INVALID_TASK_ID = -1;
+
     final int taskId;       // Unique identifier for this task.
     String affinity;        // The affinity name for this task, or null; may change identity.
     String rootAffinity;    // Initial base affinity, or null; does not change from initial root.
@@ -151,9 +155,9 @@
     int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
     int mAffiliatedTaskColor; // color of the parent task affiliation.
     TaskRecord mPrevAffiliate; // previous task in affiliated chain.
-    int mPrevAffiliateTaskId = -1; // previous id for persistence.
+    int mPrevAffiliateTaskId = INVALID_TASK_ID; // previous id for persistence.
     TaskRecord mNextAffiliate; // next task in affiliated chain.
-    int mNextAffiliateTaskId = -1; // next id for persistence.
+    int mNextAffiliateTaskId = INVALID_TASK_ID; // next id for persistence.
 
     // For relaunching the task from recents as though it was launched by the original launcher.
     int mCallingUid;
@@ -363,12 +367,12 @@
 
     void setPrevAffiliate(TaskRecord prevAffiliate) {
         mPrevAffiliate = prevAffiliate;
-        mPrevAffiliateTaskId = prevAffiliate == null ? -1 : prevAffiliate.taskId;
+        mPrevAffiliateTaskId = prevAffiliate == null ? INVALID_TASK_ID : prevAffiliate.taskId;
     }
 
     void setNextAffiliate(TaskRecord nextAffiliate) {
         mNextAffiliate = nextAffiliate;
-        mNextAffiliateTaskId = nextAffiliate == null ? -1 : nextAffiliate.taskId;
+        mNextAffiliateTaskId = nextAffiliate == null ? INVALID_TASK_ID : nextAffiliate.taskId;
     }
 
     // Close up recents linked list.
@@ -875,6 +879,10 @@
 
     static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
             throws IOException, XmlPullParserException {
+        return restoreFromXml(in, stackSupervisor, INVALID_TASK_ID);
+    }
+    static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor,
+            int inTaskId) throws IOException, XmlPullParserException {
         Intent intent = null;
         Intent affinityIntent = null;
         ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
@@ -894,23 +902,23 @@
         long lastActiveTime = -1;
         long lastTimeOnTop = 0;
         boolean neverRelinquishIdentity = true;
-        int taskId = -1;
+        int taskId = inTaskId;
         final int outerDepth = in.getDepth();
         TaskDescription taskDescription = new TaskDescription();
-        int taskAffiliation = -1;
+        int taskAffiliation = INVALID_TASK_ID;
         int taskAffiliationColor = 0;
-        int prevTaskId = -1;
-        int nextTaskId = -1;
+        int prevTaskId = INVALID_TASK_ID;
+        int nextTaskId = INVALID_TASK_ID;
         int callingUid = -1;
         String callingPackage = "";
 
         for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
             final String attrName = in.getAttributeName(attrNdx);
             final String attrValue = in.getAttributeValue(attrNdx);
-            if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: attribute name=" +
-                    attrName + " value=" + attrValue);
+            if (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
+                        "TaskRecord: attribute name=" + attrName + " value=" + attrValue);
             if (ATTR_TASKID.equals(attrName)) {
-                taskId = Integer.valueOf(attrValue);
+                if (taskId == INVALID_TASK_ID) taskId = Integer.valueOf(attrValue);
             } else if (ATTR_REALACTIVITY.equals(attrName)) {
                 realActivity = ComponentName.unflattenFromString(attrValue);
             } else if (ATTR_ORIGACTIVITY.equals(attrName)) {
@@ -966,17 +974,16 @@
                 (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
             if (event == XmlPullParser.START_TAG) {
                 final String name = in.getName();
-                if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" +
-                        name);
+                if (DEBUG_PERSISTER || DEBUG_RESTORER)
+                        Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" + name);
                 if (TAG_AFFINITYINTENT.equals(name)) {
                     affinityIntent = Intent.restoreFromXml(in);
                 } else if (TAG_INTENT.equals(name)) {
                     intent = Intent.restoreFromXml(in);
                 } else if (TAG_ACTIVITY.equals(name)) {
-                    ActivityRecord activity =
-                            ActivityRecord.restoreFromXml(in, taskId, stackSupervisor);
-                    if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: activity=" +
-                            activity);
+                    ActivityRecord activity = ActivityRecord.restoreFromXml(in, stackSupervisor);
+                    if (DEBUG_PERSISTER || DEBUG_RESTORER)
+                            Slog.d(TaskPersister.TAG, "TaskRecord: activity=" + activity);
                     if (activity != null) {
                         activities.add(activity);
                     }
@@ -1082,8 +1089,9 @@
                     pw.print(" mNeverRelinquishIdentity="); pw.print(mNeverRelinquishIdentity);
                     pw.print(" mReuseTask="); pw.println(mReuseTask);
         }
-        if (mAffiliatedTaskId != taskId || mPrevAffiliateTaskId != -1 || mPrevAffiliate != null
-                || mNextAffiliateTaskId != -1 || mNextAffiliate != null) {
+        if (mAffiliatedTaskId != taskId || mPrevAffiliateTaskId != INVALID_TASK_ID
+                || mPrevAffiliate != null || mNextAffiliateTaskId != INVALID_TASK_ID
+                || mNextAffiliate != null) {
             pw.print(prefix); pw.print("affiliation="); pw.print(mAffiliatedTaskId);
                     pw.print(" prevAffiliation="); pw.print(mPrevAffiliateTaskId);
                     pw.print(" (");
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 4af920a..6feeb7c 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -96,6 +96,12 @@
         networkRequests.put(networkRequest.requestId, networkRequest);
     }
 
+    // Does this network satisfy request?
+    public boolean satisfies(NetworkRequest request) {
+        return created &&
+                request.networkCapabilities.satisfiedByNetworkCapabilities(networkCapabilities);
+    }
+
     public boolean isVPN() {
         return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
     }
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index da404c4..97a6e85 100644
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -38,6 +38,7 @@
  *   <li>Gather "OSD (display) name" of all acknowledge devices
  *   <li>Gather "Vendor id" of all acknowledge devices
  * </ol>
+ * We attempt to get OSD name/vendor ID up to 5 times in case the communication fails.
  */
 final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
     private static final String TAG = "DeviceDiscoveryAction";
@@ -87,6 +88,7 @@
     private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
     private final DeviceDiscoveryCallback mCallback;
     private int mProcessedDeviceCount = 0;
+    private int mTimeoutRetry = 0;
 
     /**
      * Constructor.
@@ -309,6 +311,7 @@
 
     private void increaseProcessedDeviceCount() {
         mProcessedDeviceCount++;
+        mTimeoutRetry = 0;
     }
 
     private void removeDevice(int index) {
@@ -353,19 +356,23 @@
                     return;
             }
         } else {
-            int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
-            switch (mState) {
-                case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
-                    queryPhysicalAddress(address);
-                    return;
-                case STATE_WAITING_FOR_OSD_NAME:
-                    queryOsdName(address);
-                    return;
-                case STATE_WAITING_FOR_VENDOR_ID:
-                    queryVendorId(address);
-                default:
-                    return;
-            }
+            sendQueryCommand();
+        }
+    }
+
+    private void sendQueryCommand() {
+        int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
+        switch (mState) {
+            case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+                queryPhysicalAddress(address);
+                return;
+            case STATE_WAITING_FOR_OSD_NAME:
+                queryOsdName(address);
+                return;
+            case STATE_WAITING_FOR_VENDOR_ID:
+                queryVendorId(address);
+            default:
+                return;
         }
     }
 
@@ -375,6 +382,11 @@
             return;
         }
 
+        if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
+            sendQueryCommand();
+            return;
+        }
+        mTimeoutRetry = 0;
         Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
         removeDevice(mProcessedDeviceCount);
         checkAndProceedStage();
diff --git a/services/core/java/com/android/server/hdmi/HdmiConfig.java b/services/core/java/com/android/server/hdmi/HdmiConfig.java
index 046f393..a787c12 100644
--- a/services/core/java/com/android/server/hdmi/HdmiConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiConfig.java
@@ -39,6 +39,10 @@
     // Number of retries for polling each device in address allocation mechanism.
     static final int ADDRESS_ALLOCATION_RETRY = 3;
 
+    // Number of retries for sendCommand in actions related to new device discovery.
+    // Number 5 comes from 10 seconds for Chromecast preparation time.
+    static final int TIMEOUT_RETRY = 5;
+
     // CEC spec said that it should try retransmission at least once.
     // The actual number of send request for a single command will be at most
     // RETRANSMISSION_COUNT + 1. Note that it affects only to normal commands
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 64f0703..3d64cc5 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -51,6 +51,7 @@
 
     private int mVendorId;
     private String mDisplayName;
+    private int mTimeoutRetry;
 
     /**
      * Constructor.
@@ -71,15 +72,22 @@
 
     @Override
     public boolean start() {
+        requestOsdName(true);
+        return true;
+    }
+
+    private void requestOsdName(boolean firstTry) {
+        if (firstTry) {
+            mTimeoutRetry = 0;
+        }
         mState = STATE_WAITING_FOR_SET_OSD_NAME;
         if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_SET_OSD_NAME)) {
-            return true;
+            return;
         }
 
         sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(),
                 mDeviceLogicalAddress));
         addTimer(mState, HdmiConfig.TIMEOUT_MS);
-        return true;
     }
 
     @Override
@@ -103,12 +111,12 @@
                 } catch (UnsupportedEncodingException e) {
                     Slog.e(TAG, "Failed to get OSD name: " + e.getMessage());
                 }
-                requestVendorId();
+                requestVendorId(true);
                 return true;
             } else if (opcode == Constants.MESSAGE_FEATURE_ABORT) {
                 int requestOpcode = params[0] & 0xFF;
                 if (requestOpcode == Constants.MESSAGE_GIVE_OSD_NAME) {
-                    requestVendorId();
+                    requestVendorId(true);
                     return true;
                 }
             }
@@ -138,7 +146,10 @@
         return false;
     }
 
-    private void requestVendorId() {
+    private void requestVendorId(boolean firstTry) {
+        if (firstTry) {
+            mTimeoutRetry = 0;
+        }
         // At first, transit to waiting status for <Device Vendor Id>.
         mState = STATE_WAITING_FOR_DEVICE_VENDOR_ID;
         // If the message is already in cache, process it.
@@ -176,9 +187,17 @@
             return;
         }
         if (state == STATE_WAITING_FOR_SET_OSD_NAME) {
+            if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
+                requestOsdName(false);
+                return;
+            }
             // Osd name request timed out. Try vendor id
-            requestVendorId();
+            requestVendorId(true);
         } else if (state == STATE_WAITING_FOR_DEVICE_VENDOR_ID) {
+            if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
+                requestVendorId(false);
+                return;
+            }
             // vendor id timed out. Go ahead creating the device info what we've got so far.
             addDeviceInfo();
             finish();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 323b34b..aec20bc 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -201,8 +201,8 @@
 
     private boolean mDisableNotificationEffects;
     private int mCallState;
-    NotificationRecord mSoundNotification;
-    NotificationRecord mVibrateNotification;
+    private String mSoundNotificationKey;
+    private String mVibrateNotificationKey;
 
     private final ArraySet<ManagedServiceInfo> mListenersDisablingEffects = new ArraySet<>();
     private ComponentName mEffectsSuppressor;
@@ -222,8 +222,8 @@
     final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
     final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
 
-    ArrayList<String> mLights = new ArrayList<String>();
-    NotificationRecord mLedNotification;
+    // The last key in this list owns the hardware.
+    ArrayList<String> mLights = new ArrayList<>();
 
     private AppOpsManager mAppOps;
 
@@ -595,7 +595,7 @@
                 if (DBG) Slog.d(TAG, "clearEffects");
 
                 // sound
-                mSoundNotification = null;
+                mSoundNotificationKey = null;
 
                 long identity = Binder.clearCallingIdentity();
                 try {
@@ -609,7 +609,7 @@
                 }
 
                 // vibrate
-                mVibrateNotification = null;
+                mVibrateNotificationKey = null;
                 identity = Binder.clearCallingIdentity();
                 try {
                     mVibrator.cancel();
@@ -619,7 +619,6 @@
 
                 // light
                 mLights.clear();
-                mLedNotification = null;
                 updateLightsLocked();
             }
         }
@@ -1624,14 +1623,19 @@
                     if (N > 0) {
                         pw.println("  Lights List:");
                         for (int i=0; i<N; i++) {
-                            pw.println("    " + mLights.get(i));
+                            if (i == N - 1) {
+                                pw.print("  > ");
+                            } else {
+                                pw.print("    ");
+                            }
+                            pw.println(mLights.get(i));
                         }
                         pw.println("  ");
                     }
                     pw.println("  mUseAttentionLight=" + mUseAttentionLight);
                     pw.println("  mNotificationPulseEnabled=" + mNotificationPulseEnabled);
-                    pw.println("  mSoundNotification=" + mSoundNotification);
-                    pw.println("  mVibrateNotification=" + mVibrateNotification);
+                    pw.println("  mSoundNotificationKey=" + mSoundNotificationKey);
+                    pw.println("  mVibrateNotificationKey=" + mVibrateNotificationKey);
                     pw.println("  mDisableNotificationEffects=" + mDisableNotificationEffects);
                     pw.println("  mCallState=" + callStateToString(mCallState));
                     pw.println("  mSystemReady=" + mSystemReady);
@@ -2058,7 +2062,7 @@
                 boolean looping =
                         (notification.flags & Notification.FLAG_INSISTENT) != 0;
                 AudioAttributes audioAttributes = audioAttributesForNotification(notification);
-                mSoundNotification = record;
+                mSoundNotificationKey = record.getKey();
                 // do not play notifications if stream volume is 0 (typically because
                 // ringer mode is silent) or if there is a user of exclusive audio focus
                 if ((mAudioManager.getStreamVolume(
@@ -2101,7 +2105,7 @@
             if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
                     && !(mAudioManager.getRingerModeInternal()
                             == AudioManager.RINGER_MODE_SILENT)) {
-                mVibrateNotification = record;
+                mVibrateNotificationKey = record.getKey();
 
                 if (useDefaultVibrate || convertSoundToVibration) {
                     // Escalate privileges so we can use the vibrator even if the
@@ -2132,9 +2136,6 @@
         // light
         // release the light
         boolean wasShowLights = mLights.remove(record.getKey());
-        if (mLedNotification != null && record.getKey().equals(mLedNotification.getKey())) {
-            mLedNotification = null;
-        }
         if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold) {
             mLights.add(record.getKey());
             updateLightsLocked();
@@ -2458,9 +2459,11 @@
             mListeners.notifyRemovedLocked(r.sbn);
         }
 
+        final String canceledKey = r.getKey();
+
         // sound
-        if (mSoundNotification == r) {
-            mSoundNotification = null;
+        if (canceledKey.equals(mSoundNotificationKey)) {
+            mSoundNotificationKey = null;
             final long identity = Binder.clearCallingIdentity();
             try {
                 final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
@@ -2474,8 +2477,8 @@
         }
 
         // vibrate
-        if (mVibrateNotification == r) {
-            mVibrateNotification = null;
+        if (canceledKey.equals(mVibrateNotificationKey)) {
+            mVibrateNotificationKey = null;
             long identity = Binder.clearCallingIdentity();
             try {
                 mVibrator.cancel();
@@ -2486,10 +2489,7 @@
         }
 
         // light
-        mLights.remove(r.getKey());
-        if (mLedNotification == r) {
-            mLedNotification = null;
-        }
+        mLights.remove(canceledKey);
 
         // Record usage stats
         switch (reason) {
@@ -2521,7 +2521,7 @@
         // Save it for users of getHistoricalNotifications()
         mArchive.record(r.sbn);
 
-        EventLogTags.writeNotificationCanceled(r.getKey(), reason);
+        EventLogTags.writeNotificationCanceled(canceledKey, reason);
     }
 
     /**
@@ -2727,20 +2727,22 @@
     void updateLightsLocked()
     {
         // handle notification lights
-        if (mLedNotification == null) {
-            // get next notification, if any
-            int n = mLights.size();
-            if (n > 0) {
-                mLedNotification = mNotificationsByKey.get(mLights.get(n-1));
+        NotificationRecord ledNotification = null;
+        while (ledNotification == null && !mLights.isEmpty()) {
+            final String owner = mLights.get(mLights.size() - 1);
+            ledNotification = mNotificationsByKey.get(owner);
+            if (ledNotification == null) {
+                Slog.wtfStack(TAG, "LED Notification does not exist: " + owner);
+                mLights.remove(owner);
             }
         }
 
         // Don't flash while we are in a call or screen is on
-        if (mLedNotification == null || mInCall || mScreenOn) {
+        if (ledNotification == null || mInCall || mScreenOn) {
             mNotificationLight.turnOff();
             mStatusBar.notificationLightOff();
         } else {
-            final Notification ledno = mLedNotification.sbn.getNotification();
+            final Notification ledno = ledNotification.sbn.getNotification();
             int ledARGB = ledno.ledARGB;
             int ledOnMS = ledno.ledOnMS;
             int ledOffMS = ledno.ledOffMS;
@@ -2956,6 +2958,7 @@
         protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
             if (mListenersDisablingEffects.remove(removed)) {
                 updateListenerHintsLocked();
+                updateEffectsSuppressorLocked();
             }
             mLightTrimListeners.remove(removed);
             updateNotificationGroupsDesiredLocked();
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 5ab3fa1..5375bfc 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -461,7 +461,7 @@
         UserState userState = getUserStateLocked(userId);
         SessionState sessionState = userState.sessionStateMap.get(sessionToken);
         if (sessionState == null) {
-            throw new IllegalArgumentException("Session state not found for token " + sessionToken);
+            throw new SessionNotFoundException("Session state not found for token " + sessionToken);
         }
         // Only the application that requested this session or the system can access it.
         if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
@@ -589,18 +589,22 @@
     }
 
     private void releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
-        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
-        if (sessionState.session != null) {
-            UserState userState = getUserStateLocked(userId);
-            if (sessionToken == userState.mainSessionToken) {
-                setMainLocked(sessionToken, false, callingUid, userId);
-            }
-            try {
+        SessionState sessionState = null;
+        try {
+            sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
+            if (sessionState.session != null) {
+                UserState userState = getUserStateLocked(userId);
+                if (sessionToken == userState.mainSessionToken) {
+                    setMainLocked(sessionToken, false, callingUid, userId);
+                }
                 sessionState.session.release();
-            } catch (RemoteException e) {
-                Slog.e(TAG, "session process has already died", e);
             }
-            sessionState.session = null;
+        } catch (RemoteException | SessionNotFoundException e) {
+            Slog.e(TAG, "error in releaseSession", e);
+        } finally {
+            if (sessionState != null) {
+                sessionState.session = null;
+            }
         }
         removeSessionStateLocked(sessionToken, userId);
     }
@@ -648,19 +652,19 @@
     }
 
     private void setMainLocked(IBinder sessionToken, boolean isMain, int callingUid, int userId) {
-        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
-        if (sessionState.hardwareSessionToken != null) {
-            sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
-                    Process.SYSTEM_UID, userId);
-        }
-        ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId);
-        if (!serviceState.isHardware) {
-            return;
-        }
-        ITvInputSession session = getSessionLocked(sessionState);
         try {
+            SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
+            if (sessionState.hardwareSessionToken != null) {
+                sessionState = getSessionStateLocked(sessionState.hardwareSessionToken,
+                        Process.SYSTEM_UID, userId);
+            }
+            ServiceState serviceState = getServiceStateLocked(sessionState.info.getComponent(), userId);
+            if (!serviceState.isHardware) {
+                return;
+            }
+            ITvInputSession session = getSessionLocked(sessionState);
             session.setMain(isMain);
-        } catch (RemoteException e) {
+        } catch (RemoteException | SessionNotFoundException e) {
             Slog.e(TAG, "error in setMain", e);
         }
     }
@@ -1085,7 +1089,7 @@
                             getSessionLocked(sessionState.hardwareSessionToken,
                                     Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
                         }
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in setSurface", e);
                     }
                 }
@@ -1116,7 +1120,7 @@
                             getSessionLocked(sessionState.hardwareSessionToken, Process.SYSTEM_UID,
                                     resolvedUserId).dispatchSurfaceChanged(format, width, height);
                         }
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in dispatchSurfaceChanged", e);
                     }
                 }
@@ -1146,7 +1150,7 @@
                                     Process.SYSTEM_UID, resolvedUserId).setVolume((volume > 0.0f)
                                             ? REMOTE_VOLUME_ON : REMOTE_VOLUME_OFF);
                         }
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in setVolume", e);
                     }
                 }
@@ -1183,7 +1187,7 @@
                         args.arg5 = sessionToken;
                         mWatchLogHandler.obtainMessage(WatchLogHandler.MSG_LOG_WATCH_START, args)
                                 .sendToTarget();
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in tune", e);
                         return;
                     }
@@ -1205,7 +1209,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
                                 .requestUnblockContent(unblockedRating);
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in requestUnblockContent", e);
                     }
                 }
@@ -1225,7 +1229,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
                                 .setCaptionEnabled(enabled);
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in setCaptionEnabled", e);
                     }
                 }
@@ -1245,7 +1249,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId).selectTrack(
                                 type, trackId);
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in selectTrack", e);
                     }
                 }
@@ -1266,7 +1270,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
                                 .appPrivateCommand(command, data);
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in appPrivateCommand", e);
                     }
                 }
@@ -1287,7 +1291,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
                                 .createOverlayView(windowToken, frame);
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in createOverlayView", e);
                     }
                 }
@@ -1307,7 +1311,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
                                 .relayoutOverlayView(frame);
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in relayoutOverlayView", e);
                     }
                 }
@@ -1327,7 +1331,7 @@
                     try {
                         getSessionLocked(sessionToken, callingUid, resolvedUserId)
                                 .removeOverlayView();
-                    } catch (RemoteException e) {
+                    } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in removeOverlayView", e);
                     }
                 }
@@ -2340,4 +2344,13 @@
             }
         }
     }
+
+    private static class SessionNotFoundException extends IllegalArgumentException {
+        public SessionNotFoundException() {
+        }
+
+        public SessionNotFoundException(String name) {
+            super(name);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 0cbf03a..08754f9 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -159,11 +159,15 @@
         }
     }
 
-    public void onWindowFocusChangedLocked() {
+    public void onWindowFocusChangedNotLocked() {
         // Not relevant for the display magnifier.
 
-        if (mWindowsForAccessibilityObserver != null) {
-            mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked();
+        WindowsForAccessibilityObserver observer = null;
+        synchronized (mWindowManagerService) {
+            observer = mWindowsForAccessibilityObserver;
+        }
+        if (observer != null) {
+            observer.performComputeChangedWindowsNotLocked();
         }
     }
 
@@ -937,14 +941,13 @@
             computeChangedWindows();
         }
 
+        public void performComputeChangedWindowsNotLocked() {
+            mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
+            computeChangedWindows();
+        }
+
         public void scheduleComputeChangedWindowsLocked() {
-            // If focus changed, compute changed windows immediately as the focused window
-            // is used by the accessibility manager service to determine the active window.
-            if (mWindowManagerService.mCurrentFocus != null
-                    && mWindowManagerService.mCurrentFocus != mWindowManagerService.mLastFocus) {
-                mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
-                computeChangedWindows();
-            } else if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
+            if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
                 mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS,
                         mRecurringAccessibilityEventsIntervalMillis);
             }
@@ -955,6 +958,9 @@
                 Slog.i(LOG_TAG, "computeChangedWindows()");
             }
 
+            boolean windowsChanged = false;
+            List<WindowInfo> windows = new ArrayList<WindowInfo>();
+
             synchronized (mWindowManagerService.mWindowMap) {
                 // Do not send the windows if there is no current focus as
                 // the window manager is still looking for where to put it.
@@ -975,8 +981,6 @@
                 SparseArray<WindowState> visibleWindows = mTempWindowStates;
                 populateVisibleWindowsOnScreenLocked(visibleWindows);
 
-                List<WindowInfo> windows = new ArrayList<WindowInfo>();
-
                 Set<IBinder> addedWindows = mTempBinderSet;
                 addedWindows.clear();
 
@@ -1074,7 +1078,6 @@
                 addedWindows.clear();
 
                 // We computed the windows and if they changed notify the client.
-                boolean windowsChanged = false;
                 if (mOldWindows.size() != windows.size()) {
                     // Different size means something changed.
                     windowsChanged = true;
@@ -1096,22 +1099,24 @@
                 }
 
                 if (windowsChanged) {
-                    if (DEBUG) {
-                        Log.i(LOG_TAG, "Windows changed:" + windows);
-                    }
-                    // Remember the old windows to detect changes.
                     cacheWindows(windows);
-                    // Announce the change.
-                    mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_WINDOWS_CHANGED,
-                            windows).sendToTarget();
-                } else {
-                    if (DEBUG) {
-                        Log.i(LOG_TAG, "No windows changed.");
-                    }
-                    // Recycle the nodes as we do not need them.
-                    clearAndRecycleWindows(windows);
                 }
             }
+
+            // Now we do not hold the lock, so send the windows over.
+            if (windowsChanged) {
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "Windows changed:" + windows);
+                }
+                mCallback.onWindowsForAccessibilityChanged(windows);
+            } else {
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "No windows changed.");
+                }
+            }
+
+            // Recycle the windows as we do not need them.
+            clearAndRecycleWindows(windows);
         }
 
         private void computeWindowBoundsInScreen(WindowState windowState, Rect outBounds) {
@@ -1217,7 +1222,7 @@
             return false;
         }
 
-        private void clearAndRecycleWindows(List<WindowInfo> windows) {
+        private static void clearAndRecycleWindows(List<WindowInfo> windows) {
             final int windowCount = windows.size();
             for (int i = windowCount - 1; i >= 0; i--) {
                 windows.remove(i).recycle();
@@ -1254,7 +1259,6 @@
 
         private class MyHandler extends Handler {
             public static final int MESSAGE_COMPUTE_CHANGED_WINDOWS = 1;
-            public static final int MESSAGE_NOTIFY_WINDOWS_CHANGED = 2;
 
             public MyHandler(Looper looper) {
                 super(looper, null, false);
@@ -1267,12 +1271,6 @@
                     case MESSAGE_COMPUTE_CHANGED_WINDOWS: {
                         computeChangedWindows();
                     } break;
-
-                    case MESSAGE_NOTIFY_WINDOWS_CHANGED: {
-                        List<WindowInfo> windows = (List<WindowInfo>) message.obj;
-                        mCallback.onWindowsForAccessibilityChanged(windows);
-                        clearAndRecycleWindows(windows);
-                    } break;
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 0327cb3..27ac32a 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -242,6 +242,7 @@
         final WindowStateAnimator universeBackground = mService.mAnimator.mUniverseBackground;
         final int aboveUniverseLayer = mService.mAnimator.mAboveUniverseLayer;
         boolean addedUniverse = false;
+        boolean disableWallpaperTouchEvents = false;
 
         // If there's a drag in flight, provide a pseudowindow to catch drag input
         final boolean inDrag = (mService.mDragState != null);
@@ -282,8 +283,14 @@
 
                 final boolean hasFocus = (child == mInputFocus);
                 final boolean isVisible = child.isVisibleLw();
+                if ((privateFlags
+                        & WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS)
+                            != 0) {
+                    disableWallpaperTouchEvents = true;
+                }
                 final boolean hasWallpaper = (child == mService.mWallpaperTarget)
-                        && (privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD) == 0;
+                        && (privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD) == 0
+                        && !disableWallpaperTouchEvents;
                 final boolean onDefaultDisplay = (child.getDisplayId() == Display.DEFAULT_DISPLAY);
 
                 // If there's a drag in progress and 'child' is a potential drop target,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b7857e1..2750941 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7610,7 +7610,15 @@
                     WindowState lastFocus;
                     WindowState newFocus;
 
+                    AccessibilityController accessibilityController = null;
+
                     synchronized(mWindowMap) {
+                        // TODO(multidisplay): Accessibility supported only of default desiplay.
+                        if (mAccessibilityController != null && getDefaultDisplayContentLocked()
+                                .getDisplayId() == Display.DEFAULT_DISPLAY) {
+                            accessibilityController = mAccessibilityController;
+                        }
+
                         lastFocus = mLastFocus;
                         newFocus = mCurrentFocus;
                         if (lastFocus == newFocus) {
@@ -7628,6 +7636,12 @@
                         }
                     }
 
+                    // First notify the accessibility manager for the change so it has
+                    // the windows before the newly focused one starts firing eventgs.
+                    if (accessibilityController != null) {
+                        accessibilityController.onWindowFocusChangedNotLocked();
+                    }
+
                     //System.out.println("Changing focus from " + lastFocus
                     //                   + " to " + newFocus);
                     if (newFocus != null) {
@@ -10402,12 +10416,6 @@
             mCurrentFocus = newFocus;
             mLosingFocus.remove(newFocus);
 
-            // TODO(multidisplay): Accessibilty supported only of default desiplay.
-            if (mAccessibilityController != null
-                    && displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) {
-                mAccessibilityController.onWindowFocusChangedLocked();
-            }
-
             int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);
 
             if (imWindowChanged && oldFocus != mInputMethodWindow) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 87d420f..c2d8004 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -455,6 +455,14 @@
             }
         }
 
+        if (!isWindowAnimating()) {
+            //TODO (multidisplay): Accessibility is supported only for the default display.
+            if (mService.mAccessibilityController != null
+                    && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+            }
+        }
+
         if (!mWin.mExiting) {
             return;
         }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 2a3d7ab8..6621726 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -732,6 +732,26 @@
     }
 
     /**
+     * Return whether a given phone account has a voicemail number configured.
+     *
+     * @param accountHandle The handle for the account to check for a voicemail number.
+     * @return {@code true} If the given phone account has a voicemail number.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean hasVoiceMailNumber(PhoneAccountHandle accountHandle) {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().hasVoiceMailNumber(accountHandle);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException calling isInCall().", e);
+        }
+        return false;
+    }
+
+    /**
      * Returns whether there is an ongoing phone call (can be in dialing, ringing, active or holding
      * states).
      * <p>
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index cbd9d69..f8d7539 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -121,6 +121,11 @@
     boolean isVoiceMailNumber(in PhoneAccountHandle accountHandle, String number);
 
     /**
+     * @see TelecomServiceImpl#hasVoiceMailNumber
+     */
+    boolean hasVoiceMailNumber(in PhoneAccountHandle accountHandle);
+
+    /**
      * @see TelecomServiceImpl#getDefaultPhoneApp
      */
     ComponentName getDefaultPhoneApp();
diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml
index 513f622..33d40ad 100644
--- a/tests/ActivityTests/AndroidManifest.xml
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -57,6 +57,8 @@
         </service>
         <receiver android:name="UserTarget">
         </receiver>
+        <service android:name="IsolatedService" android:isolatedProcess="true">
+        </service>
         <receiver android:name="StartEmpty" android:exported="true">
             <intent-filter>
                 <action android:name="com.example.START_EMPTY" />
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
index b065b88c..4281c68 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -62,6 +62,8 @@
 
     ArrayList<ServiceConnection> mConnections = new ArrayList<ServiceConnection>();
 
+    ServiceConnection mIsolatedConnection;
+
     static final int MSG_SPAM = 1;
 
     final Handler mHandler = new Handler() {
@@ -207,6 +209,34 @@
                 return true;
             }
         });
+        menu.add("Rebind Isolated!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+            @Override public boolean onMenuItemClick(MenuItem item) {
+                Intent intent = new Intent(ActivityTestMain.this, IsolatedService.class);
+                ServiceConnection conn = new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName name, IBinder service) {
+                        Log.i(TAG, "Isolated service connected " + name + " " + service);
+                    }
+                    @Override
+                    public void onServiceDisconnected(ComponentName name) {
+                        Log.i(TAG, "Isolated service disconnected " + name);
+                    }
+                };
+                if (mIsolatedConnection != null) {
+                    Log.i(TAG, "Unbinding existing service: " + mIsolatedConnection);
+                    unbindService(mIsolatedConnection);
+                    mIsolatedConnection = null;
+                }
+                Log.i(TAG, "Binding new service: " + conn);
+                if (bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
+                    mIsolatedConnection = conn;
+                } else {
+                    Toast.makeText(ActivityTestMain.this, "Failed to bind",
+                            Toast.LENGTH_LONG).show();
+                }
+                return true;
+            }
+        });
         menu.add("Send!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
             @Override public boolean onMenuItemClick(MenuItem item) {
                 Intent intent = new Intent(ActivityTestMain.this, SingleUserReceiver.class);
@@ -417,6 +447,10 @@
             unbindService(conn);
         }
         mConnections.clear();
+        if (mIsolatedConnection != null) {
+            unbindService(mIsolatedConnection);
+            mIsolatedConnection = null;
+        }
     }
 
     @Override
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/IsolatedService.java b/tests/ActivityTests/src/com/google/android/test/activity/IsolatedService.java
new file mode 100644
index 0000000..f3bf42e
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/IsolatedService.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.activity;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+public class IsolatedService extends Service {
+    Binder mBinder = new Binder();
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Log.i("IsolatedService", "Service created in pid " + android.os.Process.myPid());
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        Log.i("IsolatedService", "Service destroyed in pid " + android.os.Process.myPid());
+    }
+}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index bdc6586..6d03311 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -12,6 +12,7 @@
 #include "ResourceIdCache.h"
 #include "SdkConstants.h"
 
+#include <algorithm>
 #include <androidfw/ResourceTypes.h>
 #include <utils/ByteOrder.h>
 #include <utils/TypeHelpers.h>
@@ -19,6 +20,8 @@
 
 #define NOISY(x) //x
 
+static const char* kAttrPrivateType = "^attr-private";
+
 status_t compileXmlFile(const Bundle* bundle,
                         const sp<AaptAssets>& assets,
                         const String16& resourceName,
@@ -2131,8 +2134,16 @@
     if (p == NULL) return 0;
     sp<Type> t = p->getTypes().valueFor(type);
     if (t == NULL) return 0;
-    sp<ConfigList> c =  t->getConfigs().valueFor(name);
-    if (c == NULL) return 0;
+    sp<ConfigList> c = t->getConfigs().valueFor(name);
+    if (c == NULL) {
+        if (type != String16("attr")) {
+            return 0;
+        }
+        t = p->getTypes().valueFor(String16(kAttrPrivateType));
+        if (t == NULL) return 0;
+        c = t->getConfigs().valueFor(name);
+        if (c == NULL) return 0;
+    }
     int32_t ei = c->getEntryIndex();
     if (ei < 0) return 0;
 
@@ -2266,7 +2277,15 @@
     sp<Type> t = p->getTypes().valueFor(type);
     if (t == NULL) return 0;
     sp<ConfigList> c =  t->getConfigs().valueFor(name);
-    if (c == NULL) return 0;
+    if (c == NULL) {
+        if (type != String16("attr")) {
+            return 0;
+        }
+        t = p->getTypes().valueFor(String16(kAttrPrivateType));
+        if (t == NULL) return 0;
+        c = t->getConfigs().valueFor(name);
+        if (c == NULL) return 0;
+    }
     int32_t ei = c->getEntryIndex();
     if (ei < 0) return 0;
     return getResId(p, t, ei);
@@ -2470,6 +2489,10 @@
             continue;
         }
 
+        if (mPackageType == System) {
+            p->movePrivateAttrs();
+        }
+
         // This has no sense for packages being built as AppFeature (aka with a non-zero offset).
         status_t err = p->applyPublicTypeOrder();
         if (err != NO_ERROR && firstError == NO_ERROR) {
@@ -2540,15 +2563,20 @@
             }
         }
 
+
         // Assign resource IDs to keys in bags...
         for (size_t ti = 0; ti < typeCount; ti++) {
             sp<Type> t = p->getOrderedTypes().itemAt(ti);
             if (t == NULL) {
                 continue;
             }
+
             const size_t N = t->getOrderedConfigs().size();
             for (size_t ci=0; ci<N; ci++) {
                 sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+                if (c == NULL) {
+                    continue;
+                }
                 //printf("Ordered config #%d: %p\n", ci, c.get());
                 const size_t N = c->getEntries().size();
                 for (size_t ei=0; ei<N; ei++) {
@@ -2586,9 +2614,15 @@
             if (t == NULL) {
                 continue;
             }
+
             const size_t N = t->getOrderedConfigs().size();
-            sp<AaptSymbols> typeSymbols =
-                    outSymbols->addNestedSymbol(String8(t->getName()), t->getPos());
+            sp<AaptSymbols> typeSymbols;
+            if (t->getName() == String16(kAttrPrivateType)) {
+                typeSymbols = outSymbols->addNestedSymbol(String8("attr"), t->getPos());
+            } else {
+                typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos());
+            }
+
             if (typeSymbols == NULL) {
                 return UNKNOWN_ERROR;
             }
@@ -2954,6 +2988,10 @@
 
                 for (size_t ei=0; ei<N; ei++) {
                     sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
+                    if (cl == NULL) {
+                        continue;
+                    }
+
                     if (cl->getPublic()) {
                         typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC);
                     }
@@ -2984,12 +3022,13 @@
 
             // We need to write one type chunk for each configuration for
             // which we have entries in this type.
-            const size_t NC = t->getUniqueConfigs().size();
+            const SortedVector<ConfigDescription> uniqueConfigs(t->getUniqueConfigs());
+            const size_t NC = uniqueConfigs.size();
             
             const size_t typeSize = sizeof(ResTable_type) + sizeof(uint32_t)*N;
             
             for (size_t ci=0; ci<NC; ci++) {
-                ConfigDescription config = t->getUniqueConfigs().itemAt(ci);
+                const ConfigDescription& config = uniqueConfigs[ci];
 
                 NOISY(printf("Writing config %d config: imsi:%d/%d lang:%c%c cnt:%c%c "
                      "orien:%d ui:%d touch:%d density:%d key:%d inp:%d nav:%d sz:%dx%d "
@@ -3061,7 +3100,10 @@
                 // Build the entries inside of this type.
                 for (size_t ei=0; ei<N; ei++) {
                     sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
-                    sp<Entry> e = cl->getEntries().valueFor(config);
+                    sp<Entry> e = NULL;
+                    if (cl != NULL) {
+                        e = cl->getEntries().valueFor(config);
+                    }
 
                     // Set the offset for this entry in its type.
                     uint32_t* index = (uint32_t*)
@@ -3096,9 +3138,11 @@
                 for (size_t i = 0; i < N; ++i) {
                     if (!validResources[i]) {
                         sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
-                        fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix,
-                                String8(typeName).string(), String8(c->getName()).string(),
-                                Res_MAKEID(p->getAssignedId() - 1, ti, i));
+                        if (c != NULL) {
+                            fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix,
+                                    String8(typeName).string(), String8(c->getName()).string(),
+                                    Res_MAKEID(p->getAssignedId() - 1, ti, i));
+                        }
                         missing_entry = true;
                     }
                 }
@@ -3807,11 +3851,45 @@
         */
     }
     
-    mUniqueConfigs.add(cdesc);
-    
     return e;
 }
 
+sp<ResourceTable::ConfigList> ResourceTable::Type::removeEntry(const String16& entry) {
+    ssize_t idx = mConfigs.indexOfKey(entry);
+    if (idx < 0) {
+        return NULL;
+    }
+
+    sp<ConfigList> removed = mConfigs.valueAt(idx);
+    mConfigs.removeItemsAt(idx);
+
+    Vector<sp<ConfigList> >::iterator iter = std::find(
+            mOrderedConfigs.begin(), mOrderedConfigs.end(), removed);
+    if (iter != mOrderedConfigs.end()) {
+        mOrderedConfigs.erase(iter);
+    }
+
+    mPublic.removeItem(entry);
+    return removed;
+}
+
+SortedVector<ConfigDescription> ResourceTable::Type::getUniqueConfigs() const {
+    SortedVector<ConfigDescription> unique;
+    const size_t entryCount = mOrderedConfigs.size();
+    for (size_t i = 0; i < entryCount; i++) {
+        if (mOrderedConfigs[i] == NULL) {
+            continue;
+        }
+        const DefaultKeyedVector<ConfigDescription, sp<Entry> >& configs =
+                mOrderedConfigs[i]->getEntries();
+        const size_t configCount = configs.size();
+        for (size_t j = 0; j < configCount; j++) {
+            unique.add(configs.keyAt(j));
+        }
+    }
+    return unique;
+}
+
 status_t ResourceTable::Type::applyPublicEntryOrder()
 {
     size_t N = mOrderedConfigs.size();
@@ -3838,11 +3916,10 @@
             //printf("#%d: \"%s\"\n", i, String8(e->getName()).string());
             if (e->getName() == name) {
                 if (idx >= (int32_t)mOrderedConfigs.size()) {
-                    p.sourcePos.error("Public entry identifier 0x%x entry index "
-                            "is larger than available symbols (index %d, total symbols %d).\n",
-                            p.ident, idx, mOrderedConfigs.size());
-                    hasError = true;
-                } else if (mOrderedConfigs.itemAt(idx) == NULL) {
+                    mOrderedConfigs.resize(idx + 1);
+                }
+
+                if (mOrderedConfigs.itemAt(idx) == NULL) {
                     e->setPublic(true);
                     e->setPublicSourcePos(p.sourcePos);
                     mOrderedConfigs.replaceAt(e, idx);
@@ -4018,6 +4095,61 @@
     return NO_ERROR;
 }
 
+void ResourceTable::Package::movePrivateAttrs() {
+    sp<Type> attr = mTypes.valueFor(String16("attr"));
+    if (attr == NULL) {
+        // Nothing to do.
+        return;
+    }
+
+    Vector<sp<ConfigList> > privateAttrs;
+
+    bool hasPublic = false;
+    const Vector<sp<ConfigList> >& configs = attr->getOrderedConfigs();
+    const size_t configCount = configs.size();
+    for (size_t i = 0; i < configCount; i++) {
+        if (configs[i] == NULL) {
+            continue;
+        }
+
+        if (attr->isPublic(configs[i]->getName())) {
+            hasPublic = true;
+        } else {
+            privateAttrs.add(configs[i]);
+        }
+    }
+
+    // Only if we have public attributes do we create a separate type for
+    // private attributes.
+    if (!hasPublic) {
+        return;
+    }
+
+    // Create a new type for private attributes.
+    sp<Type> privateAttrType = getType(String16(kAttrPrivateType), SourcePos());
+
+    const size_t privateAttrCount = privateAttrs.size();
+    for (size_t i = 0; i < privateAttrCount; i++) {
+        const sp<ConfigList>& cl = privateAttrs[i];
+
+        // Remove the private attributes from their current type.
+        attr->removeEntry(cl->getName());
+
+        // Add it to the new type.
+        const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries = cl->getEntries();
+        const size_t entryCount = entries.size();
+        for (size_t j = 0; j < entryCount; j++) {
+            const sp<Entry>& oldEntry = entries[j];
+            sp<Entry> entry = privateAttrType->getEntry(
+                    cl->getName(), oldEntry->getPos(), &entries.keyAt(j));
+            *entry = *oldEntry;
+        }
+
+        // Move the symbols to the new type.
+
+    }
+}
+
 sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
 {
     if (package != mAssetsPackage) {
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index db392c89..81590bc 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -470,6 +470,14 @@
                            bool overlay = false,
                            bool autoAddOverlay = false);
 
+        bool isPublic(const String16& entry) const {
+            return mPublic.indexOfKey(entry) >= 0;
+        }
+
+        sp<ConfigList> removeEntry(const String16& entry);
+
+        SortedVector<ConfigDescription> getUniqueConfigs() const;
+
         const SourcePos& getFirstPublicSourcePos() const { return *mFirstPublicSourcePos; }
 
         int32_t getPublicIndex() const { return mPublicIndex; }
@@ -479,19 +487,16 @@
 
         status_t applyPublicEntryOrder();
 
-        const SortedVector<ConfigDescription>& getUniqueConfigs() const { return mUniqueConfigs; }
-        
         const DefaultKeyedVector<String16, sp<ConfigList> >& getConfigs() const { return mConfigs; }
         const Vector<sp<ConfigList> >& getOrderedConfigs() const { return mOrderedConfigs; }
-
         const SortedVector<String16>& getCanAddEntries() const { return mCanAddEntries; }
         
         const SourcePos& getPos() const { return mPos; }
+
     private:
         String16 mName;
         SourcePos* mFirstPublicSourcePos;
         DefaultKeyedVector<String16, Public> mPublic;
-        SortedVector<ConfigDescription> mUniqueConfigs;
         DefaultKeyedVector<String16, sp<ConfigList> > mConfigs;
         Vector<sp<ConfigList> > mOrderedConfigs;
         SortedVector<String16> mCanAddEntries;
@@ -527,6 +532,8 @@
         const DefaultKeyedVector<String16, sp<Type> >& getTypes() const { return mTypes; }
         const Vector<sp<Type> >& getOrderedTypes() const { return mOrderedTypes; }
 
+        void movePrivateAttrs();
+
     private:
         status_t setStrings(const sp<AaptFile>& data,
                             ResStringPool* strings,