Merge "Subscription Info density-dependent text size and localized SIM slot index" into lmp-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index 707ddab..bbfbafd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6383,6 +6383,7 @@
     method public void onDescriptorReadRequest(android.bluetooth.BluetoothDevice, int, int, android.bluetooth.BluetoothGattDescriptor);
     method public void onDescriptorWriteRequest(android.bluetooth.BluetoothDevice, int, android.bluetooth.BluetoothGattDescriptor, boolean, boolean, int, byte[]);
     method public void onExecuteWrite(android.bluetooth.BluetoothDevice, int, boolean);
+    method public void onMtuChanged(android.bluetooth.BluetoothDevice, int);
     method public void onNotificationSent(android.bluetooth.BluetoothDevice, int);
     method public void onServiceAdded(int, android.bluetooth.BluetoothGattService);
   }
@@ -28721,7 +28722,7 @@
     method public void listen(android.telephony.PhoneStateListener, int);
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public boolean setGlobalPreferredNetworkType();
-    method public void setLine1NumberForDisplay(java.lang.String, java.lang.String);
+    method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
     method public boolean setOperatorBrandOverride(java.lang.String);
     method public boolean setVoiceMailNumber(java.lang.String, java.lang.String);
     field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b64e724..6b4db10 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2436,7 +2436,7 @@
     private void deliverNewIntents(ActivityClientRecord r, List<ReferrerIntent> intents) {
         final int N = intents.size();
         for (int i=0; i<N; i++) {
-            Intent intent = intents.get(i);
+            ReferrerIntent intent = intents.get(i);
             intent.setExtrasClassLoader(r.activity.getClassLoader());
             intent.prepareToEnterProcess();
             r.activity.mFragments.noteStateNotSaved();
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index d96153a..3c30404 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1208,14 +1208,17 @@
      * @param intent The new intent being received.
      */
     public void callActivityOnNewIntent(Activity activity, Intent intent) {
+        activity.onNewIntent(intent);
+    }
+
+    /**
+     * @hide
+     */
+    public void callActivityOnNewIntent(Activity activity, ReferrerIntent intent) {
         final String oldReferrer = activity.mReferrer;
         try {
-            try {
-                activity.mReferrer = ((ReferrerIntent)intent).mReferrer;
-            } catch (ClassCastException e) {
-                activity.mReferrer = null;
-            }
-            activity.onNewIntent(intent);
+            activity.mReferrer = intent.mReferrer;
+            callActivityOnNewIntent(activity, new Intent(intent));
         } finally {
             activity.mReferrer = oldReferrer;
         }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0e1c85c..4753099 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1481,8 +1481,8 @@
      * Flag for {@link #wipeData(int)}: also erase the factory reset protection
      * data.
      *
-     * This flag may only be set by device owner admins; if it is set by other
-     * admins a {@link SecurityException} will be thrown.
+     * <p>This flag may only be set by device owner admins; if it is set by
+     * other admins a {@link SecurityException} will be thrown.
      */
     public static final int WIPE_RESET_PROTECTION_DATA = 0x0002;
 
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index e94a8ce..f451340 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -284,6 +284,24 @@
                     Log.w(TAG, "Unhandled exception: " + ex);
                 }
             }
+
+            /**
+             * The MTU for a connection has changed
+             * @hide
+             */
+            public void onMtuChanged(String address, int mtu) {
+                if (DBG) Log.d(TAG, "onMtuChanged() - "
+                    + "device=" + address + ", mtu=" + mtu);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                if (device == null) return;
+
+                try {
+                    mCallback.onMtuChanged(device, mtu);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
         };
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index 1dd06f2..2afcf9a 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -145,4 +145,16 @@
      */
     public void onNotificationSent(BluetoothDevice device, int status) {
     }
+
+    /**
+     * Callback indicating the MTU for a given device connection has changed.
+     *
+     * <p>This callback will be invoked if a remote client has requested to change
+     * the MTU for a given connection.
+     *
+     * @param device The remote device that requested the MTU change
+     * @param mtu The new MTU size
+     */
+    public void onMtuChanged(BluetoothDevice device, int mtu) {
+    }
 }
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
index 5d4d6c6..8b202b2 100644
--- a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
@@ -59,4 +59,5 @@
                                      in byte[] value);
     void onExecuteWrite(in String address, in int transId, in boolean execWrite);
     void onNotificationSent(in String address, in int status);
+    void onMtuChanged(in String address, in int mtu);
 }
diff --git a/core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl b/core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl
index a16e878..c708d20 100644
--- a/core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl
@@ -22,7 +22,7 @@
  *
  * @hide
  */
-oneway interface IHdmiVendorCommandListener {
+interface IHdmiVendorCommandListener {
     void onReceived(int logicalAddress, int destAddress, in byte[] operands, boolean hasVendorId);
     void onControlStateChanged(boolean enabled, int reason);
 }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 19eca29..ff1a441 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -71,7 +71,6 @@
  */
 public class ConnectivityManager {
     private static final String TAG = "ConnectivityManager";
-    private static final boolean LEGACY_DBG = true; // STOPSHIP
 
     /**
      * A change in network connectivity has occurred. A default connection has either
@@ -880,14 +879,6 @@
 
         NetworkRequest request = null;
         synchronized (sLegacyRequests) {
-            if (LEGACY_DBG) {
-                Log.d(TAG, "Looking for legacyRequest for netCap with hash: " + netCap + " (" +
-                        netCap.hashCode() + ")");
-                Log.d(TAG, "sLegacyRequests has:");
-                for (NetworkCapabilities nc : sLegacyRequests.keySet()) {
-                    Log.d(TAG, "  " + nc + " (" + nc.hashCode() + ")");
-                }
-            }
             LegacyRequest l = sLegacyRequests.get(netCap);
             if (l != null) {
                 Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 4709443..11fc69e 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -91,11 +91,6 @@
      public static final int WIFI_MULTICAST_ENABLED = 7;
 
     /**
-     * A constant indicating an audio turn on timer
-     */
-    public static final int AUDIO_TURNED_ON = 7;
-
-    /**
      * A constant indicating a video turn on timer
      */
     public static final int VIDEO_TURNED_ON = 8;
@@ -131,6 +126,11 @@
     public static final int JOB = 14;
 
     /**
+     * A constant indicating an audio turn on timer
+     */
+    public static final int AUDIO_TURNED_ON = 15;
+
+    /**
      * Include all of the data in the stats, including previously saved data.
      */
     public static final int STATS_SINCE_CHARGED = 0;
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 56a05fe..9feb681 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -59,6 +59,11 @@
     public String name;
 
     /**
+     * Unique identifier for the display. Shouldn't be displayed to the user.
+     */
+    public String uniqueId;
+
+    /**
      * The width of the portion of the display that is available to applications, in pixels.
      * Represents the size of the display minus any system decorations.
      */
@@ -257,7 +262,7 @@
                 && flags == other.flags
                 && type == other.type
                 && Objects.equal(address, other.address)
-                && Objects.equal(name, other.name)
+                && Objects.equal(uniqueId, other.uniqueId)
                 && appWidth == other.appWidth
                 && appHeight == other.appHeight
                 && smallestNominalAppWidth == other.smallestNominalAppWidth
@@ -293,6 +298,7 @@
         type = other.type;
         address = other.address;
         name = other.name;
+        uniqueId = other.uniqueId;
         appWidth = other.appWidth;
         appHeight = other.appHeight;
         smallestNominalAppWidth = other.smallestNominalAppWidth;
@@ -348,6 +354,7 @@
         state = source.readInt();
         ownerUid = source.readInt();
         ownerPackageName = source.readString();
+        uniqueId = source.readString();
     }
 
     @Override
@@ -380,6 +387,7 @@
         dest.writeInt(state);
         dest.writeInt(ownerUid);
         dest.writeString(ownerPackageName);
+        dest.writeString(uniqueId);
     }
 
     @Override
@@ -445,6 +453,8 @@
         StringBuilder sb = new StringBuilder();
         sb.append("DisplayInfo{\"");
         sb.append(name);
+        sb.append("\", uniqueId \"");
+        sb.append(uniqueId);
         sb.append("\", app ");
         sb.append(appWidth);
         sb.append(" x ");
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 1a5ff26..1551504 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -5033,8 +5033,8 @@
             child.getMatrix().mapRect(rect);
         }
 
-        int dx = child.mLeft - mScrollX;
-        int dy = child.mTop - mScrollY;
+        final int dx = child.mLeft - mScrollX;
+        final int dy = child.mTop - mScrollY;
 
         rect.offset(dx, dy);
 
@@ -5052,21 +5052,23 @@
             offset.y += dy;
         }
 
+        final int width = mRight - mLeft;
+        final int height = mBottom - mTop;
+
         boolean rectIsVisible = true;
         if (mParent instanceof ViewGroup && ((ViewGroup)mParent).getClipChildren()) {
-            // clipChildren clips to the child's bounds
-            rectIsVisible = rect.intersect(0, 0, mRight - mLeft, mBottom - mTop);
+            // Clip to bounds.
+            rectIsVisible = rect.intersect(0, 0, width, height);
         }
 
         if (rectIsVisible && (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
-            // Clip to padding
+            // Clip to padding.
             rectIsVisible = rect.intersect(mPaddingLeft, mPaddingTop,
-                    mRight - mLeft - mPaddingLeft - mPaddingRight,
-                    mBottom - mTop - mPaddingTop - mPaddingBottom);
+                    width - mPaddingRight, height - mPaddingBottom);
         }
 
         if (rectIsVisible && mClipBounds != null) {
-            // Clip to clipBounds
+            // Clip to clipBounds.
             rectIsVisible = rect.intersect(mClipBounds.left, mClipBounds.top, mClipBounds.right,
                     mClipBounds.bottom);
         }
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
index 0cf9ddd..5e5ef29 100644
--- a/core/java/android/view/ViewOverlay.java
+++ b/core/java/android/view/ViewOverlay.java
@@ -130,8 +130,11 @@
             super(context);
             mHostView = hostView;
             mAttachInfo = mHostView.mAttachInfo;
+
             mRight = hostView.getWidth();
             mBottom = hostView.getHeight();
+            // pass right+bottom directly to RenderNode, since not going through setters
+            mRenderNode.setLeftTopRightBottom(0, 0, mRight, mBottom);
         }
 
         public void add(Drawable drawable) {
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 8b01dde..04b5616 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -1200,69 +1200,87 @@
         return degrees;
     }
 
+    boolean mChangedDuringTouch = false;
+
     @Override
     public boolean onTouch(View v, MotionEvent event) {
-        if(!mInputEnabled) {
+        if (!mInputEnabled) {
             return true;
         }
 
-        final float eventX = event.getX();
-        final float eventY = event.getY();
+        final int action = event.getActionMasked();
+        if (action == MotionEvent.ACTION_MOVE
+                || action == MotionEvent.ACTION_UP
+                || action == MotionEvent.ACTION_DOWN) {
+            boolean forceSelection = false;
+            boolean autoAdvance = false;
 
-        int degrees;
-        int snapDegrees;
-        boolean result = false;
+            if (action == MotionEvent.ACTION_DOWN) {
+                // This is a new event stream, reset whether the value changed.
+                mChangedDuringTouch = false;
+            } else if (action == MotionEvent.ACTION_UP) {
+                autoAdvance = true;
 
-        switch(event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-            case MotionEvent.ACTION_MOVE:
-                degrees = getDegreesFromXY(eventX, eventY);
-                if (degrees != -1) {
-                    snapDegrees = (mShowHours ?
-                            snapOnly30s(degrees, 0) : snapPrefer30s(degrees)) % 360;
-                    if (mShowHours) {
-                        mSelectionDegrees[HOURS] = snapDegrees;
-                        mSelectionDegrees[HOURS_INNER] = snapDegrees;
-                    } else {
-                        mSelectionDegrees[MINUTES] = snapDegrees;
-                    }
-                    performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
-                    if (mListener != null) {
-                        if (mShowHours) {
-                            mListener.onValueSelected(HOURS, getCurrentHour(), false);
-                        } else  {
-                            mListener.onValueSelected(MINUTES, getCurrentMinute(), false);
-                        }
-                    }
-                    result = true;
-                    invalidate();
+                // If we saw a down/up pair without the value changing, assume
+                // this is a single-tap selection and force a change.
+                if (!mChangedDuringTouch) {
+                    forceSelection = true;
                 }
-                break;
+            }
 
-            case MotionEvent.ACTION_UP:
-                degrees = getDegreesFromXY(eventX, eventY);
-                if (degrees != -1) {
-                    snapDegrees = (mShowHours ?
-                            snapOnly30s(degrees, 0) : snapPrefer30s(degrees)) % 360;
-                    if (mShowHours) {
-                        mSelectionDegrees[HOURS] = snapDegrees;
-                        mSelectionDegrees[HOURS_INNER] = snapDegrees;
-                    } else {
-                        mSelectionDegrees[MINUTES] = snapDegrees;
-                    }
-                    if (mListener != null) {
-                        if (mShowHours) {
-                            mListener.onValueSelected(HOURS, getCurrentHour(), true);
-                        } else  {
-                            mListener.onValueSelected(MINUTES, getCurrentMinute(), true);
-                        }
-                    }
-                    invalidate();
-                    result = true;
-                }
-                break;
+            mChangedDuringTouch |= handleTouchInput(
+                    event.getX(), event.getY(), forceSelection, autoAdvance);
         }
-        return result;
+
+        return true;
+    }
+
+    private boolean handleTouchInput(
+            float x, float y, boolean forceSelection, boolean autoAdvance) {
+        // Calling getDegreesFromXY has side effects, so cache
+        // whether we used to be on the inner circle.
+        final boolean wasOnInnerCircle = mIsOnInnerCircle;
+        final int degrees = getDegreesFromXY(x, y);
+        if (degrees == -1) {
+            return false;
+        }
+
+        final int[] selectionDegrees = mSelectionDegrees;
+        int type = -1;
+        int newValue = -1;
+
+        if (mShowHours) {
+            final int snapDegrees = snapOnly30s(degrees, 0) % 360;
+            if (forceSelection
+                    || selectionDegrees[HOURS] != snapDegrees
+                    || selectionDegrees[HOURS_INNER] != snapDegrees
+                    || wasOnInnerCircle != mIsOnInnerCircle) {
+                selectionDegrees[HOURS] = snapDegrees;
+                selectionDegrees[HOURS_INNER] = snapDegrees;
+
+                type = HOURS;
+                newValue = getCurrentHour();
+            }
+        } else {
+            final int snapDegrees = snapPrefer30s(degrees) % 360;
+            if (forceSelection || selectionDegrees[MINUTES] != snapDegrees) {
+                selectionDegrees[MINUTES] = snapDegrees;
+
+                type = MINUTES;
+                newValue = getCurrentMinute();
+            }
+        }
+
+        if (newValue != -1) {
+            if (mListener != null) {
+                mListener.onValueSelected(type, newValue, autoAdvance);
+            }
+            performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
+            invalidate();
+            return true;
+        }
+
+        return false;
     }
 
     @Override
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 2db466a..634d03d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -95,12 +95,14 @@
     private ListView mListView;
     private Button mAlwaysButton;
     private Button mOnceButton;
+    private View mProfileView;
     private int mIconDpi;
     private int mIconSize;
     private int mMaxColumns;
     private int mLastSelected = ListView.INVALID_POSITION;
     private boolean mResolvingHome = false;
     private int mProfileSwitchMessageId = -1;
+    private Intent mIntent;
 
     private UsageStatsManager mUsm;
     private Map<String, UsageStats> mStats;
@@ -110,6 +112,9 @@
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override public void onSomePackagesChanged() {
             mAdapter.handlePackagesChanged();
+            if (mProfileView != null) {
+                bindProfileView();
+            }
         }
     };
 
@@ -217,7 +222,6 @@
 
         final long sinceTime = System.currentTimeMillis() - USAGE_STATS_PERIOD;
         mStats = mUsm.queryAndAggregateUsageStats(sinceTime, System.currentTimeMillis());
-        Log.d(TAG, "sinceTime=" + sinceTime);
 
         mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns);
 
@@ -228,7 +232,8 @@
         mIconDpi = am.getLauncherLargeIconDensity();
         mIconSize = am.getLauncherLargeIconSize();
 
-        mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList,
+        mIntent = new Intent(intent);
+        mAdapter = new ResolveListAdapter(this, initialIntents, rList,
                 mLaunchedFromUid, alwaysUseOption);
 
         final int layoutId;
@@ -324,6 +329,40 @@
             setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
             mOnceButton.setEnabled(true);
         }
+
+        mProfileView = findViewById(R.id.profile_button);
+        if (mProfileView != null) {
+            mProfileView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    final DisplayResolveInfo dri = mAdapter.getOtherProfile();
+                    if (dri == null) {
+                        return;
+                    }
+
+                    final Intent intent = intentForDisplayResolveInfo(dri);
+                    onIntentSelected(dri.ri, intent, mAlwaysUseOption);
+                    finish();
+                }
+            });
+            bindProfileView();
+        }
+    }
+
+    void bindProfileView() {
+        final DisplayResolveInfo dri = mAdapter.getOtherProfile();
+        if (dri != null) {
+            mProfileView.setVisibility(View.VISIBLE);
+            final ImageView icon = (ImageView) mProfileView.findViewById(R.id.icon);
+            final TextView text = (TextView) mProfileView.findViewById(R.id.text1);
+            if (dri.displayIcon == null) {
+                new LoadIconTask().execute(dri);
+            }
+            icon.setImageDrawable(dri.displayIcon);
+            text.setText(dri.displayLabel);
+        } else {
+            mProfileView.setVisibility(View.GONE);
+        }
     }
 
     private void setProfileSwitchMessageId(int contentUserHint) {
@@ -416,6 +455,9 @@
             mRegistered = true;
         }
         mAdapter.handlePackagesChanged();
+        if (mProfileView != null) {
+            bindProfileView();
+        }
     }
 
     @Override
@@ -702,6 +744,17 @@
         startActivity(in);
     }
 
+    Intent intentForDisplayResolveInfo(DisplayResolveInfo dri) {
+        Intent intent = new Intent(dri.origIntent != null ? dri.origIntent :
+                getReplacementIntent(dri.ri.activityInfo, mIntent));
+        intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
+                |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+        ActivityInfo ai = dri.ri.activityInfo;
+        intent.setComponent(new ComponentName(
+                ai.applicationInfo.packageName, ai.name));
+        return intent;
+    }
+
     private final class DisplayResolveInfo {
         ResolveInfo ri;
         CharSequence displayLabel;
@@ -722,7 +775,7 @@
         private final Intent[] mInitialIntents;
         private final List<ResolveInfo> mBaseResolveList;
         private ResolveInfo mLastChosen;
-        private final Intent mIntent;
+        private DisplayResolveInfo mOtherProfile;
         private final int mLaunchedFromUid;
         private final LayoutInflater mInflater;
 
@@ -732,10 +785,8 @@
         private int mLastChosenPosition = -1;
         private boolean mFilterLastUsed;
 
-        public ResolveListAdapter(Context context, Intent intent,
-                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
-                boolean filterLastUsed) {
-            mIntent = new Intent(intent);
+        public ResolveListAdapter(Context context, Intent[] initialIntents,
+                List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) {
             mInitialIntents = initialIntents;
             mBaseResolveList = rList;
             mLaunchedFromUid = launchedFromUid;
@@ -764,6 +815,10 @@
             return null;
         }
 
+        public DisplayResolveInfo getOtherProfile() {
+            return mOtherProfile;
+        }
+
         public int getFilteredPosition() {
             if (mFilterLastUsed && mLastChosenPosition >= 0) {
                 return mLastChosenPosition;
@@ -870,7 +925,7 @@
                             ri.nonLocalizedLabel = li.getNonLocalizedLabel();
                             ri.icon = li.getIconResource();
                         }
-                        mList.add(new DisplayResolveInfo(ri,
+                        addResolveInfo(new DisplayResolveInfo(ri,
                                 ri.loadLabel(getPackageManager()), null, ii));
                     }
                 }
@@ -915,7 +970,7 @@
                     mLastChosenPosition = mList.size();
                 }
                 // No duplicate labels. Use label for entry at start
-                mList.add(new DisplayResolveInfo(ro, roLabel, null, null));
+                addResolveInfo(new DisplayResolveInfo(ro, roLabel, null, null));
             } else {
                 mShowExtended = true;
                 boolean usePkg = false;
@@ -951,32 +1006,34 @@
                     }
                     if (usePkg) {
                         // Use application name for all entries from start to end-1
-                        mList.add(new DisplayResolveInfo(add, roLabel,
+                        addResolveInfo(new DisplayResolveInfo(add, roLabel,
                                 add.activityInfo.packageName, null));
                     } else {
                         // Use package name for all entries from start to end-1
-                        mList.add(new DisplayResolveInfo(add, roLabel,
+                        addResolveInfo(new DisplayResolveInfo(add, roLabel,
                                 add.activityInfo.applicationInfo.loadLabel(mPm), null));
                     }
                 }
             }
         }
 
+        private void addResolveInfo(DisplayResolveInfo dri) {
+            if (dri.ri.targetUserId != UserHandle.USER_CURRENT && mOtherProfile == null) {
+                // So far we only support a single other profile at a time.
+                // The first one we see gets special treatment.
+                mOtherProfile = dri;
+            } else {
+                mList.add(dri);
+            }
+        }
+
         public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
             return (filtered ? getItem(position) : mList.get(position)).ri;
         }
 
         public Intent intentForPosition(int position, boolean filtered) {
             DisplayResolveInfo dri = filtered ? getItem(position) : mList.get(position);
-
-            Intent intent = new Intent(dri.origIntent != null ? dri.origIntent :
-                    getReplacementIntent(dri.ri.activityInfo, mIntent));
-            intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
-                    |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
-            ActivityInfo ai = dri.ri.activityInfo;
-            intent.setComponent(new ComponentName(
-                    ai.applicationInfo.packageName, ai.name));
-            return intent;
+            return intentForDisplayResolveInfo(dri);
         }
 
         public int getCount() {
@@ -1067,6 +1124,9 @@
 
         @Override
         protected void onPostExecute(DisplayResolveInfo info) {
+            if (mProfileView != null && mAdapter.getOtherProfile() == info) {
+                bindProfileView();
+            }
             mAdapter.notifyDataSetChanged();
         }
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index c00d209..0dfb11a 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -94,7 +94,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 114 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 115 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 5e610ed..40c009f 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -40,8 +40,10 @@
     // You need the STATUS_BAR_SERVICE permission
     void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList,
             out int[] switches, out List<IBinder> binders);
-    void onPanelRevealed();
+    void onPanelRevealed(boolean clearNotificationEffects);
     void onPanelHidden();
+    // Mark current notifications as "seen" and stop ringing, vibrating, blinking.
+    void clearNotificationEffects();
     void onNotificationClick(String key);
     void onNotificationActionClick(String key, int actionIndex);
     void onNotificationError(String pkg, String tag, int id,
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index 054ca30..8d1f73a 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -97,6 +97,7 @@
         mTitle = toolbar.getTitle();
         mSubtitle = toolbar.getSubtitle();
         mTitleSet = mTitle != null;
+        mNavIcon = mToolbar.getNavigationIcon();
         final TypedArray a = toolbar.getContext().obtainStyledAttributes(null,
                 R.styleable.ActionBar, R.attr.actionBarStyle, 0);
         mDefaultNavigationIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator);
@@ -120,7 +121,7 @@
             if (icon != null) {
                 setIcon(icon);
             }
-            if (mDefaultNavigationIcon != null) {
+            if (mNavIcon == null && mDefaultNavigationIcon != null) {
                 setNavigationIcon(mDefaultNavigationIcon);
             }
             setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0));
diff --git a/core/res/res/layout/resolve_list_item.xml b/core/res/res/layout/resolve_list_item.xml
index 2933a6a..5d52832 100644
--- a/core/res/res/layout/resolve_list_item.xml
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -33,7 +33,6 @@
                android:layout_height="24dp"
                android:layout_gravity="start|center_vertical"
                android:layout_marginStart="?attr/listPreferredItemPaddingStart"
-               android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
                android:layout_marginTop="12dp"
                android:layout_marginBottom="12dp"
                android:scaleType="fitCenter" />
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 9ae3aec..00c25e6 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -25,19 +25,56 @@
     android:maxCollapsedHeightSmall="56dp"
     android:id="@id/contentPanel">
 
-    <TextView android:id="@+id/title"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content"
-              android:layout_alwaysShow="true"
-              android:minHeight="56dp"
-              android:textAppearance="?attr/textAppearanceMedium"
-              android:gravity="start|center_vertical"
-              android:paddingStart="?attr/dialogPreferredPadding"
-              android:paddingEnd="?attr/dialogPreferredPadding"
-              android:paddingTop="8dp"
-              android:paddingBottom="8dp"
-              android:background="@color/white"
-              android:elevation="8dp" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alwaysShow="true"
+        android:elevation="8dp"
+        android:background="@color/white" >
+        <TextView android:id="@+id/title"
+                  android:layout_width="0dp"
+                  android:layout_height="wrap_content"
+                  android:layout_weight="1"
+                  android:minHeight="56dp"
+                  android:textAppearance="?attr/textAppearanceMedium"
+                  android:gravity="start|center_vertical"
+                  android:paddingStart="?attr/dialogPreferredPadding"
+                  android:paddingEnd="?attr/dialogPreferredPadding"
+                  android:paddingTop="8dp"
+                  android:paddingBottom="8dp" />
+        <LinearLayout android:id="@+id/profile_button"
+                      android:layout_width="wrap_content"
+                      android:layout_height="48dp"
+                      android:layout_marginTop="4dp"
+                      android:layout_marginEnd="4dp"
+                      android:paddingStart="8dp"
+                      android:paddingEnd="8dp"
+                      android:paddingTop="4dp"
+                      android:paddingBottom="4dp"
+                      android:focusable="true"
+                      android:visibility="gone"
+                      style="?attr/borderlessButtonStyle">
+            <ImageView android:id="@+id/icon"
+                       android:layout_width="24dp"
+                       android:layout_height="24dp"
+                       android:layout_gravity="start|center_vertical"
+                       android:layout_marginStart="4dp"
+                       android:layout_marginEnd="16dp"
+                       android:layout_marginTop="12dp"
+                       android:layout_marginBottom="12dp"
+                       android:scaleType="fitCenter" />
+            <TextView android:id="@id/text1"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:layout_gravity="start|center_vertical"
+                      android:layout_marginEnd="16dp"
+                      android:textAppearance="?attr/textAppearanceButton"
+                      android:textColor="?attr/textColorPrimary"
+                      android:minLines="1"
+                      android:maxLines="1"
+                      android:ellipsize="marquee" />
+        </LinearLayout>
+    </LinearLayout>
 
     <ListView
         android:layout_width="match_parent"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 884f41e..31361e5 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -36,8 +36,7 @@
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="64dp"
-            android:orientation="horizontal"
-            >
+            android:orientation="horizontal" >
 
             <ImageView android:id="@+id/icon"
                        android:layout_width="24dp"
@@ -46,8 +45,7 @@
                        android:layout_marginStart="16dp"
                        android:layout_marginEnd="16dp"
                        android:layout_marginTop="20dp"
-                       android:scaleType="fitCenter"
-                       />
+                       android:scaleType="fitCenter" />
             <TextView android:id="@+id/title"
                       android:layout_width="0dp"
                       android:layout_weight="1"
@@ -55,8 +53,38 @@
                       android:layout_marginStart="16dp"
                       android:textAppearance="?android:attr/textAppearanceMedium"
                       android:gravity="start|center_vertical"
-                      android:paddingEnd="16dp"
-                      />
+                      android:paddingEnd="16dp" />
+            <LinearLayout android:id="@+id/profile_button"
+                          android:layout_width="wrap_content"
+                          android:layout_height="48dp"
+                          android:layout_marginTop="4dp"
+                          android:layout_marginEnd="4dp"
+                          android:paddingStart="8dp"
+                          android:paddingEnd="8dp"
+                          android:paddingTop="4dp"
+                          android:paddingBottom="4dp"
+                          android:focusable="true"
+                          android:visibility="gone"
+                          style="?attr/borderlessButtonStyle">
+                <ImageView android:id="@+id/icon"
+                           android:layout_width="24dp"
+                           android:layout_height="24dp"
+                           android:layout_gravity="start|center_vertical"
+                           android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
+                           android:layout_marginTop="12dp"
+                           android:layout_marginBottom="12dp"
+                           android:scaleType="fitCenter" />
+                <TextView android:id="@id/text1"
+                          android:layout_width="wrap_content"
+                          android:layout_height="wrap_content"
+                          android:layout_gravity="start|center_vertical"
+                          android:layout_marginEnd="?attr/listPreferredItemPaddingEnd"
+                          android:textAppearance="?attr/textAppearanceButton"
+                          android:textColor="?attr/textColorPrimary"
+                          android:minLines="1"
+                          android:maxLines="1"
+                          android:ellipsize="marquee" />
+            </LinearLayout>
         </LinearLayout>
 
         <LinearLayout
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5296966..a9123d1 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2129,6 +2129,7 @@
   <java-symbol type="id" name="scrollIndicatorDown" />
   <java-symbol type="array" name="config_sms_convert_destination_number_support" />
   <java-symbol type="string" name="prohibit_manual_network_selection_in_gobal_mode" />
+  <java-symbol type="id" name="profile_button" />
 
   <!-- From SignalStrength -->
   <java-symbol type="integer" name="config_LTE_RSRP_threshold_type" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 36d7116..def8659 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -551,6 +551,8 @@
         <item name="windowDrawsSystemBarBackgrounds">false</item>
         <item name="windowContentOverlay">@null</item>
         <item name="colorControlActivated">?attr/colorControlHighlight</item>
+        <item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item>
+        <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
     </style>
 
 </resources>
diff --git a/docs/html/about/dashboards/index.jd b/docs/html/about/dashboards/index.jd
index 448dcda..063084d 100644
--- a/docs/html/about/dashboards/index.jd
+++ b/docs/html/about/dashboards/index.jd
@@ -57,7 +57,7 @@
 </div>
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on November 3, 2014.
+<p style="clear:both"><em>Data collected during a 7-day period ending on December 1, 2014.
 <br/>Any versions with less than 0.1% distribution are not shown.</em>
 </p>
 
@@ -88,7 +88,7 @@
 </div>
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on November 3, 2014.
+<p style="clear:both"><em>Data collected during a 7-day period ending on December 1, 2014.
 
 <br/>Any screen configurations with less than 0.1% distribution are not shown.</em></p>
 
@@ -108,7 +108,8 @@
 
 
 <img alt="" style="float:right"
-src="//chart.googleapis.com/chart?chl=GL%202.0%7CGL%203.0&chd=t%3A74.7%2C25.3&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=400x250" />
+src="//chart.googleapis.com/chart?chl=GL%202.0%7CGL%203.0&chf=bg%2Cs%2C00000000&chd=t%3A72.2%2C27.8&chco=c4df9b%2C6fad0c&cht=p&chs=400x250" />
+
 
 <p>To declare which version of OpenGL ES your application requires, you should use the {@code
 android:glEsVersion} attribute of the <a
@@ -126,17 +127,17 @@
 </tr>
 <tr>
 <td>2.0</td>
-<td>74.7%</td>
+<td>72.2%</td>
 </tr>
 <tr>
 <td>3.0</td>
-<td>25.3%</td>
+<td>27.8%</td>
 </tr>
 </table>
 
 
 
-<p style="clear:both"><em>Data collected during a 7-day period ending on November 3, 2014</em></p>
+<p style="clear:both"><em>Data collected during a 7-day period ending on December 1, 2014</em></p>
 
 
 
@@ -154,42 +155,42 @@
 var VERSION_DATA =
 [
   {
-    "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat&chd=t%3A0.6%2C9.8%2C8.5%2C50.9%2C30.2&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=500x250",
+    "chart": "//chart.googleapis.com/chart?chl=Froyo%7CGingerbread%7CIce%20Cream%20Sandwich%7CJelly%20Bean%7CKitKat&chf=bg%2Cs%2C00000000&chd=t%3A0.5%2C9.1%2C7.8%2C48.7%2C33.9&chco=c4df9b%2C6fad0c&cht=p&chs=500x250",
     "data": [
       {
         "api": 8,
         "name": "Froyo",
-        "perc": "0.6"
+        "perc": "0.5"
       },
       {
         "api": 10,
         "name": "Gingerbread",
-        "perc": "9.8"
+        "perc": "9.1"
       },
       {
         "api": 15,
         "name": "Ice Cream Sandwich",
-        "perc": "8.5"
+        "perc": "7.8"
       },
       {
         "api": 16,
         "name": "Jelly Bean",
-        "perc": "22.8"
+        "perc": "21.3"
       },
       {
         "api": 17,
         "name": "Jelly Bean",
-        "perc": "20.8"
+        "perc": "20.4"
       },
       {
         "api": 18,
         "name": "Jelly Bean",
-        "perc": "7.3"
+        "perc": "7.0"
       },
       {
         "api": 19,
         "name": "KitKat",
-        "perc": "30.2"
+        "perc": "33.9"
       }
     ]
   }
@@ -203,27 +204,28 @@
       "Large": {
         "hdpi": "0.6",
         "ldpi": "0.5",
-        "mdpi": "4.5",
-        "tvdpi": "1.9",
+        "mdpi": "4.6",
+        "tvdpi": "2.0",
         "xhdpi": "0.6"
       },
       "Normal": {
-        "hdpi": "36.6",
-        "mdpi": "9.9",
-        "xhdpi": "18.9",
-        "xxhdpi": "16.0"
+        "hdpi": "36.9",
+        "mdpi": "9.4",
+        "tvdpi": "0.2",
+        "xhdpi": "18.8",
+        "xxhdpi": "16.3"
       },
       "Small": {
-        "ldpi": "5.8"
+        "ldpi": "5.4"
       },
       "Xlarge": {
         "hdpi": "0.3",
-        "mdpi": "3.9",
-        "xhdpi": "0.5"
+        "mdpi": "3.8",
+        "xhdpi": "0.6"
       }
     },
-    "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chd=t%3A6.3%2C18.3%2C1.9%2C37.5%2C20.0%2C16.0&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=400x250",
-    "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chd=t%3A4.7%2C8.1%2C81.4%2C5.8&chf=bg%2Cs%2C00000000&chco=c4df9b%2C6fad0c&cht=p&chs=400x250"
+    "densitychart": "//chart.googleapis.com/chart?chl=ldpi%7Cmdpi%7Ctvdpi%7Chdpi%7Cxhdpi%7Cxxhdpi&chf=bg%2Cs%2C00000000&chd=t%3A5.9%2C17.8%2C2.2%2C37.8%2C20.0%2C16.3&chco=c4df9b%2C6fad0c&cht=p&chs=400x250",
+    "layoutchart": "//chart.googleapis.com/chart?chl=Xlarge%7CLarge%7CNormal%7CSmall&chf=bg%2Cs%2C00000000&chd=t%3A4.7%2C8.3%2C81.6%2C5.4&chco=c4df9b%2C6fad0c&cht=p&chs=400x250"
   }
 ];
 
diff --git a/docs/html/design/media/wear/ContextualExample.006.png b/docs/html/design/media/wear/ContextualExample.006.png
index 7c3da57a..e680afb 100644
--- a/docs/html/design/media/wear/ContextualExample.006.png
+++ b/docs/html/design/media/wear/ContextualExample.006.png
Binary files differ
diff --git a/docs/html/design/media/wear/ContextualExample.006_2x.png b/docs/html/design/media/wear/ContextualExample.006_2x.png
index 319530d..ee4087e 100644
--- a/docs/html/design/media/wear/ContextualExample.006_2x.png
+++ b/docs/html/design/media/wear/ContextualExample.006_2x.png
Binary files differ
diff --git a/docs/html/design/wear/context.jd b/docs/html/design/wear/context.jd
index 2e66532..688806f 100644
--- a/docs/html/design/wear/context.jd
+++ b/docs/html/design/wear/context.jd
@@ -131,7 +131,7 @@
 
 <div class="slide">
 <h2>Zoo</h2>
-<p>Notifies visitors when the penguins are going to be fed!
+<p>Notifies visitors when the penguins are going to be fed.
 </p>
 <img src="{@docRoot}design/media/wear/ContextualExample.014.png" alt=""
      srcset="{@docRoot}design/media/wear/ContextualExample.014.png 1x,
diff --git a/docs/html/training/wearables/apps/creating.jd b/docs/html/training/wearables/apps/creating.jd
index 018d9f7..683dd31 100644
--- a/docs/html/training/wearables/apps/creating.jd
+++ b/docs/html/training/wearables/apps/creating.jd
@@ -6,6 +6,7 @@
 <div id="tb">
 <h2>This lesson teaches you to</h2>
 <ol>
+  <li><a href="#UpdateSDK">Update Your SDK</a></li>
   <li><a href="#SetupEmulator">Set Up an Android Wear Emulator</a></li>
   <li><a href="#SetupDevice">Set Up an Android Wear Device</a></li>
   <li><a href="#CreateProject">Create a Project</a></li>
@@ -13,7 +14,7 @@
 </ol>
 <h2>Dependencies and Prerequisites</h2>
   <ul>
-    <li>Android Studio 0.8 or later and Gradle 0.12 or later</li>
+    <li>Android Studio 0.8.12 or later and Gradle 0.12 or later</li>
   </ul>
 </div>
 </div>
@@ -34,6 +35,24 @@
 both your wearable and handheld apps.
 </p>
 
+<h2 id="UpdateSDK">Update Your SDK</h2>
+
+<p>Before you begin building wearable apps, you must:</p>
+
+<ul>
+  <li><strong>Update your SDK tools to version 23.0.0 or higher</strong>
+    <br>
+    The updated SDK tools enable you to build and test wearable apps.
+  </li>
+  <li><strong>Update your SDK with Android 4.4W.2 (API 20) or higher</strong>
+    <br>
+    The updated platform version provides new APIs for wearable apps.
+  </li>
+</ul>
+
+<p>To update your SDK with these components, see
+<a href="{@docRoot}sdk/installing/adding-packages.html#GetTools"> Get the latest SDK tools</a>.</p>
+
 
 <h2 id="SetupEmulator">Set Up an Android Wear Emulator or Device</h2>
 <p>We recommend that you develop on real hardware so you can better
@@ -45,29 +64,24 @@
 <p>To set up an Android Wear virtual device:</p>
 <ol>
   <li>Click <b>Tools > Android > AVD Manager</b>.</li>
-  <li>Click <b>Create...</b>.</li>
-  <li>Fill in the following details for the AVD you want to specify and leave the rest
-  of the fields with their default values:
-    <ul>
-      <li><b>AVD Name</b> - A name for your AVD</li>
-      <li><b>Device</b> - Android Wear Round or Square device types</li>
-      <li><b>Target</b> - Android 4.4W - API Level 20</li>
-      <li><b>CPU/ABI</b> - Android Wear ARM (armeabi-v7a)</li>
-      <li><b>Keyboard</b> - Select <b>Hardware keyboard present</b></li>
-      <li><b>Skin</b> - AndroidWearRound or AndroidWearSquare depending on the selected device type</li>
-      <li><b>Snapshot</b> - Not selected</li>
-      <li><b>Use Host GPU</b> - Selected, to support custom activities for wearable notifications</li>
-    </ul>
-  </li>
-  <li>Click <b>OK</b>.</li>
+  <li>Click <b>Create Virtual Device...</b>.</li>
+  <ol>
+    <li>Click <b>Wear</b> in the Category list:</li>
+    <li>Select Android Wear Square or Android Wear Round.</li>
+    <li>Click <b>Next</b>.</li>
+    <li>Select a release name (for example, KitKat Wear).</li>
+    <li>Click <b>Next</b>.</li>
+    <li>(Optional) Change any preferences for your virtual device.</li>
+    <li>Click <b>Finish</b>.</li>
+  </ol>
 <li>Start the emulator:
 <ol>
   <li>Select the virtual device you just created.</li>
-  <li>Click <b>Start...</b>, then click <b>Launch</b>.</li>
+  <li>Click the <b>Play</b> button.</li>
   <li>Wait until the emulator initializes and shows the Android Wear home screen.</li>
 </ol>
 </li>
-<li>Pair Your handheld with the emulator:
+<li>Pair your handheld with the emulator:
 <ol>
   <li>On your handheld, install the Android Wear app from Google Play.</li>
   <li>Connect the handheld to your machine through USB.</li>
diff --git a/docs/html/training/wearables/ui/confirm.jd b/docs/html/training/wearables/ui/confirm.jd
index 36330a6..07a352f 100644
--- a/docs/html/training/wearables/ui/confirm.jd
+++ b/docs/html/training/wearables/ui/confirm.jd
@@ -116,15 +116,14 @@
 mDelayedView.start();
 </pre>
 
-
-<h2 id="show-confirmation">Show Confirmation Animations</h2>
-
-<div style="float:right;margin-left:25px;width:200px">
+<div style="float:right;margin-left:25px;width:210px;margin-top:15px">
 <img src="{@docRoot}wear/images/08_uilib.png" width="200" height="200" alt=""/>
-<p class="img-caption" style="text-align:center"><strong>Figure 2:</strong>
+<p class="img-caption" style="text-align:center;margin-left:-5px"><strong>Figure 2:</strong>
 A confirmation animation.</p>
 </div>
 
+<h2 id="show-confirmation">Show Confirmation Animations</h2>
+
 <p>To show a confirmation animation when users complete an action in your app, create an intent
 that starts <code>ConfirmationActivity</code> from one of your activities. You can specify
 one of the these animations with the <code>EXTRA_ANIMATION_TYPE</code> intent extra:</p>
diff --git a/docs/html/training/wearables/ui/layouts.jd b/docs/html/training/wearables/ui/layouts.jd
index 14b9403..130f1c4 100644
--- a/docs/html/training/wearables/ui/layouts.jd
+++ b/docs/html/training/wearables/ui/layouts.jd
@@ -166,7 +166,7 @@
 
 <h2 id="same-layout">Use a Shape-Aware Layout</h2>
 
-<div style="float:right;margin-left:25px;width:250px">
+<div style="float:right;margin-left:25px;width:260px">
 <img src="{@docRoot}wear/images/02_uilib.png" width="250" height="250" alt=""/>
 <p class="img-caption"><strong>Figure 2.</strong> Window insets on a round screen.</p>
 </div>
diff --git a/docs/html/training/wearables/ui/lists.jd b/docs/html/training/wearables/ui/lists.jd
index e8aaed4..1d6e8ed 100644
--- a/docs/html/training/wearables/ui/lists.jd
+++ b/docs/html/training/wearables/ui/lists.jd
@@ -36,9 +36,9 @@
 <li>Add a <code>WearableListView</code> element to your activity's layout definition.</li>
 <li>Create a custom layout implementation for your list items.</li>
 <li>Use this implementation to create a layout definition file for your list items.</li>
-<div style="float:right;margin-left:25px;width:215px;margin-top:-20px">
+<div style="float:right;margin-left:25px;width:220px;margin-top:-25px">
 <img src="{@docRoot}wear/images/06_uilib.png" width="200" height="200" alt=""/>
-<p class="img-caption" style="text-align:center"><strong>Figure 3:</strong>
+<p class="img-caption" style="text-align:center;margin-left:-10px"><strong>Figure 3:</strong>
 A list view on Android Wear.</p>
 </div>
 <li>Create an adapter to populate the list.</li>
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 80b4c2a..075f2c5 100755
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1560,13 +1560,7 @@
             setScissorFromClip();
         }
 
-        if (clearLayer) {
-            setStencilFromClip();
-        } else {
-            // While clearing layer, force disable stencil buffer, since
-            // it's invalid to stencil-clip *during* the layer clear
-            mCaches.stencil.disable();
-        }
+        setStencilFromClip();
     }
 
     mDescription.reset();
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index bbba2dd8..810e487 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -680,13 +680,32 @@
 
     float casterAlpha = properties().getAlpha() * properties().getOutline().getAlpha();
 
-    const SkPath* outlinePath = casterOutlinePath;
-    if (revealClipPath) {
-        // if we can't simply use the caster's path directly, create a temporary one
-        SkPath* frameAllocatedPath = handler.allocPathForFrame();
 
-        // intersect the outline with the convex reveal clip
-        Op(*casterOutlinePath, *revealClipPath, kIntersect_PathOp, frameAllocatedPath);
+    // holds temporary SkPath to store the result of intersections
+    SkPath* frameAllocatedPath = NULL;
+    const SkPath* outlinePath = casterOutlinePath;
+
+    // intersect the outline with the reveal clip, if present
+    if (revealClipPath) {
+        frameAllocatedPath = handler.allocPathForFrame();
+
+        Op(*outlinePath, *revealClipPath, kIntersect_PathOp, frameAllocatedPath);
+        outlinePath = frameAllocatedPath;
+    }
+
+    // intersect the outline with the clipBounds, if present
+    if (properties().getClippingFlags() & CLIP_TO_CLIP_BOUNDS) {
+        if (!frameAllocatedPath) {
+            frameAllocatedPath = handler.allocPathForFrame();
+        }
+
+        Rect clipBounds;
+        properties().getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds);
+        SkPath clipBoundsPath;
+        clipBoundsPath.addRect(clipBounds.left, clipBounds.top,
+                clipBounds.right, clipBounds.bottom);
+
+        Op(*outlinePath, clipBoundsPath, kIntersect_PathOp, frameAllocatedPath);
         outlinePath = frameAllocatedPath;
     }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 39528be..aa32541 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -60,6 +60,7 @@
 
 void CanvasContext::destroy() {
     stopDrawing();
+    setSurface(NULL);
     freePrefetechedLayers();
     destroyHardwareResources();
     mAnimationContext->destroy();
@@ -67,7 +68,6 @@
         delete mCanvas;
         mCanvas = 0;
     }
-    setSurface(NULL);
 }
 
 void CanvasContext::setSurface(ANativeWindow* window) {
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 20c4978..489f552 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -241,11 +241,11 @@
 
     /**
      * @hide
-     * CANDIDATE FOR PUBLIC API
      * Return the capture preset.
      * @return one of the values that can be set in {@link Builder#setCapturePreset(int)} or a
      *    negative value if none has been set.
      */
+    @SystemApi
     public int getCapturePreset() {
         return mSource;
     }
@@ -508,6 +508,7 @@
          *     {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
          * @return the same Builder instance.
          */
+        @SystemApi
         public Builder setCapturePreset(int preset) {
             switch (preset) {
                 case MediaRecorder.AudioSource.DEFAULT:
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 24817aa..543836b 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2459,6 +2459,7 @@
      *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
      * @throws IllegalArgumentException
      */
+    @SystemApi
     public int requestAudioFocus(OnAudioFocusChangeListener l,
             @NonNull AudioAttributes requestAttributes,
             int durationHint,
@@ -2853,17 +2854,17 @@
 
     /**
      * @hide
-     * CANDIDATE FOR PUBLIC API
      * Register the given {@link AudioPolicy}.
      * This call is synchronous and blocks until the registration process successfully completed
      * or failed to complete.
-     * @param policy the {@link AudioPolicy} to register.
+     * @param policy the non-null {@link AudioPolicy} to register.
      * @return {@link #ERROR} if there was an error communicating with the registration service
      *    or if the user doesn't have the required
      *    {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission,
      *    {@link #SUCCESS} otherwise.
      */
-    public int registerAudioPolicy(AudioPolicy policy) {
+    @SystemApi
+    public int registerAudioPolicy(@NonNull AudioPolicy policy) {
         if (policy == null) {
             throw new IllegalArgumentException("Illegal null AudioPolicy argument");
         }
@@ -2885,16 +2886,17 @@
 
     /**
      * @hide
-     * CANDIDATE FOR PUBLIC API
-     * @param policy the {@link AudioPolicy} to unregister.
+     * @param policy the non-null {@link AudioPolicy} to unregister.
      */
-    public void unregisterAudioPolicyAsync(AudioPolicy policy) {
+    @SystemApi
+    public void unregisterAudioPolicyAsync(@NonNull AudioPolicy policy) {
         if (policy == null) {
             throw new IllegalArgumentException("Illegal null AudioPolicy argument");
         }
         IAudioService service = getService();
         try {
             service.unregisterAudioPolicyAsync(policy.token());
+            policy.setRegistration(null);
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
         }
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 4da2705..9a3ec42 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -1124,20 +1124,11 @@
 
             // Check if volume update should be send to Hdmi system audio.
             int newIndex = mStreamStates[streamType].getIndex(device);
+            if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
+                setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
+            }
             if (mHdmiManager != null) {
                 synchronized (mHdmiManager) {
-                    if (mHdmiTvClient != null &&
-                        streamTypeAlias == AudioSystem.STREAM_MUSIC &&
-                        (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
-                        oldIndex != newIndex) {
-                        int maxIndex = getStreamMaxVolume(streamType);
-                        synchronized (mHdmiTvClient) {
-                            if (mHdmiSystemAudioSupported) {
-                                mHdmiTvClient.setSystemAudioVolume(
-                                        (oldIndex + 5) / 10, (newIndex + 5) / 10, maxIndex);
-                            }
-                        }
-                    }
                     // mHdmiCecSink true => mHdmiPlaybackClient != null
                     if (mHdmiCecSink &&
                             streamTypeAlias == AudioSystem.STREAM_MUSIC &&
@@ -1156,6 +1147,23 @@
         sendVolumeUpdate(streamType, oldIndex, index, flags);
     }
 
+    private void setSystemAudioVolume(int oldVolume, int newVolume, int maxVolume, int flags) {
+        if (mHdmiManager == null
+                || mHdmiTvClient == null
+                || oldVolume == newVolume
+                || (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) != 0) return;
+
+        // Sets the audio volume of AVR when we are in system audio mode. The new volume info
+        // is tranformed to HDMI-CEC commands and passed through CEC bus.
+        synchronized (mHdmiManager) {
+            if (!mHdmiSystemAudioSupported) return;
+            synchronized (mHdmiTvClient) {
+                mHdmiTvClient.setSystemAudioVolume(
+                        (oldVolume + 5) / 10, (newVolume + 5) / 10, maxVolume);
+            }
+        }
+    }
+
     /** @see AudioManager#adjustMasterVolume(int, int) */
     public void adjustMasterVolume(int steps, int flags, String callingPackage) {
         adjustMasterVolume(steps, flags, callingPackage, Binder.getCallingUid());
@@ -1267,21 +1275,8 @@
                 }
             }
 
-            if (mHdmiManager != null) {
-                synchronized (mHdmiManager) {
-                    if (mHdmiTvClient != null &&
-                        streamTypeAlias == AudioSystem.STREAM_MUSIC &&
-                        (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0 &&
-                        oldIndex != index) {
-                        int maxIndex = getStreamMaxVolume(streamType);
-                        synchronized (mHdmiTvClient) {
-                            if (mHdmiSystemAudioSupported) {
-                                mHdmiTvClient.setSystemAudioVolume(
-                                        (oldIndex + 5) / 10, (index + 5) / 10, maxIndex);
-                            }
-                        }
-                    }
-                }
+            if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
+                setSystemAudioVolume(oldIndex, index, getStreamMaxVolume(streamType), flags);
             }
 
             flags &= ~AudioManager.FLAG_FIXED_VOLUME;
@@ -1422,15 +1417,8 @@
             streamType = AudioSystem.STREAM_NOTIFICATION;
         }
 
-        // If Hdmi-CEC system audio mode is on, show volume bar
-        // only when TV receives volume notification from Audio Receiver.
-        if (mHdmiTvClient != null && streamType == AudioSystem.STREAM_MUSIC) {
-            synchronized (mHdmiTvClient) {
-                if (mHdmiSystemAudioSupported &&
-                        ((flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0)) {
-                    flags &= ~AudioManager.FLAG_SHOW_UI;
-                }
-            }
+        if (streamType == AudioSystem.STREAM_MUSIC) {
+            flags = updateFlagsForSystemAudio(flags);
         }
         mVolumeController.postVolumeChanged(streamType, flags);
 
@@ -1445,9 +1433,23 @@
         }
     }
 
+    // If Hdmi-CEC system audio mode is on, we show volume bar only when TV
+    // receives volume notification from Audio Receiver.
+    private int updateFlagsForSystemAudio(int flags) {
+        if (mHdmiTvClient != null) {
+            synchronized (mHdmiTvClient) {
+                if (mHdmiSystemAudioSupported &&
+                        ((flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) == 0)) {
+                    flags &= ~AudioManager.FLAG_SHOW_UI;
+                }
+            }
+        }
+        return flags;
+    }
+
     // UI update and Broadcast Intent
     private void sendMasterVolumeUpdate(int flags, int oldVolume, int newVolume) {
-        mVolumeController.postMasterVolumeChanged(flags);
+        mVolumeController.postMasterVolumeChanged(updateFlagsForSystemAudio(flags));
 
         Intent intent = new Intent(AudioManager.MASTER_VOLUME_CHANGED_ACTION);
         intent.putExtra(AudioManager.EXTRA_PREV_MASTER_VOLUME_VALUE, oldVolume);
@@ -1457,7 +1459,7 @@
 
     // UI update and Broadcast Intent
     private void sendMasterMuteUpdate(boolean muted, int flags) {
-        mVolumeController.postMasterMuteChanged(flags);
+        mVolumeController.postMasterMuteChanged(updateFlagsForSystemAudio(flags));
         broadcastMasterMuteStatus(muted);
     }
 
@@ -1517,21 +1519,29 @@
         }
 
         if (isStreamAffectedByMute(streamType)) {
-            if (mHdmiManager != null) {
-                synchronized (mHdmiManager) {
-                    if (streamType == AudioSystem.STREAM_MUSIC && mHdmiTvClient != null) {
-                        synchronized (mHdmiTvClient) {
-                            if (mHdmiSystemAudioSupported) {
-                                mHdmiTvClient.setSystemAudioMute(state);
-                            }
-                        }
-                    }
-                }
+            if (streamType == AudioSystem.STREAM_MUSIC) {
+                setSystemAudioMute(state);
             }
             mStreamStates[streamType].mute(cb, state);
         }
     }
 
+    private void setSystemAudioMute(boolean state) {
+        if (mHdmiManager == null || mHdmiTvClient == null) return;
+        synchronized (mHdmiManager) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mHdmiTvClient) {
+                    if (mHdmiSystemAudioSupported) {
+                        mHdmiTvClient.setSystemAudioMute(state);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
     /** get stream mute state. */
     public boolean isStreamMute(int streamType) {
         synchronized (VolumeStreamState.class) {
@@ -1649,6 +1659,7 @@
             return;
         }
         if (state != AudioSystem.getMasterMute()) {
+            setSystemAudioMute(state);
             AudioSystem.setMasterMute(state);
             // Post a persist master volume msg
             sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, state ? 1
@@ -1729,6 +1740,7 @@
                 // Post a persist master volume msg
                 sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME, SENDMSG_REPLACE,
                         Math.round(volume * (float)1000.0), 0, null, PERSIST_DELAY);
+                setSystemAudioVolume(oldVolume, newVolume, getMasterMaxVolume(), flags);
             }
             // Send the volume update regardless whether there was a change.
             sendMasterVolumeUpdate(flags, oldVolume, newVolume);
@@ -5526,6 +5538,8 @@
         pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
         pw.print("  mMcc="); pw.println(mMcc);
         pw.print("  mHasVibrator="); pw.println(mHasVibrator);
+
+        dumpAudioPolicies(pw);
     }
 
     private static String safeMediaVolumeStateToString(Integer state) {
@@ -5785,6 +5799,10 @@
         }
         synchronized (mAudioPolicies) {
             try {
+                if (mAudioPolicies.containsKey(cb)) {
+                    Slog.e(TAG, "Cannot re-register policy");
+                    return null;
+                }
                 AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
                 cb.linkToDeath(app, 0/*flags*/);
                 regId = app.connectMixes();
@@ -5805,6 +5823,7 @@
             if (app == null) {
                 Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
                         + Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
+                return;
             } else {
                 cb.unlinkToDeath(app, 0/*flags*/);
             }
@@ -5813,12 +5832,21 @@
         // TODO implement clearing mix attribute matching info in native audio policy
     }
 
+    private void dumpAudioPolicies(PrintWriter pw) {
+        pw.println("\nAudio policies:");
+        synchronized (mAudioPolicies) {
+            for(AudioPolicyProxy policy : mAudioPolicies.values()) {
+                pw.println(policy.toLogFriendlyString());
+            }
+        }
+    }
+
     //======================
     // Audio policy proxy
     //======================
     /**
-     * This internal class inherits from AudioPolicyConfig which contains all the mixes and
-     * their configurations.
+     * This internal class inherits from AudioPolicyConfig, each instance contains all the
+     * mixes of an AudioPolicy and their configurations.
      */
     public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
         private static final String TAG = "AudioPolicyProxy";
@@ -5826,7 +5854,7 @@
         IBinder mToken;
         AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
             super(config);
-            setRegistration(new String(config.toString() + ":ap:" + mAudioPolicyCounter++));
+            setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
             mToken = token;
         }
 
@@ -5840,7 +5868,7 @@
 
         String connectMixes() {
             updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
-            return mRegistrationId;
+            return getRegistration();
         }
 
         void disconnectMixes() {
@@ -5851,8 +5879,9 @@
             for (AudioMix mix : mMixes) {
                 // TODO implement sending the mix attribute matching info to native audio policy
                 if (DEBUG_AP) {
-                    Log.v(TAG, "AudioPolicyProxy connect mix state=" + connectionState
-                            + " addr=" + mix.getRegistration()); }
+                    Log.v(TAG, "AudioPolicyProxy mix new connection state=" + connectionState
+                            + " addr=" + mix.getRegistration());
+                }
                 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX,
                         connectionState,
                         mix.getRegistration());
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index b541454..400c082 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -26,6 +26,7 @@
 import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.nio.NioUtils;
 
 /**
  * <p>The ImageReader class allows direct application access to image data
@@ -688,6 +689,15 @@
             }
 
             private void clearBuffer() {
+                // Need null check first, as the getBuffer() may not be called before an image
+                // is closed.
+                if (mBuffer == null) {
+                    return;
+                }
+
+                if (mBuffer.isDirect()) {
+                    NioUtils.freeDirectBuffer(mBuffer);
+                }
                 mBuffer = null;
             }
 
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index bb52682..1806662 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -17,21 +17,25 @@
 package android.media.audiopolicy;
 
 import android.annotation.IntDef;
+import android.annotation.SystemApi;
 import android.media.AudioFormat;
 import android.media.AudioSystem;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * @hide
  */
+@SystemApi
 public class AudioMix {
 
     private AudioMixingRule mRule;
     private AudioFormat mFormat;
     private int mRouteFlags;
     private String mRegistrationId;
+    private int mMixType = MIX_TYPE_INVALID;
 
     /**
      * All parameters are guaranteed valid through the Builder.
@@ -41,20 +45,39 @@
         mFormat = format;
         mRouteFlags = routeFlags;
         mRegistrationId = null;
+        mMixType = rule.getTargetMixType();
     }
 
     /**
      * An audio mix behavior where the output of the mix is sent to the original destination of
      * the audio signal, i.e. an output device for an output mix, or a recording for an input mix.
      */
+    @SystemApi
     public static final int ROUTE_FLAG_RENDER    = 0x1;
     /**
      * An audio mix behavior where the output of the mix is rerouted back to the framework and
-     * is accessible for injection or capture through the {@link Audiotrack} and {@link AudioRecord}
+     * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord}
      * APIs.
      */
+    @SystemApi
     public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
 
+    /**
+     * @hide
+     * Invalid mix type, default value.
+     */
+    public static final int MIX_TYPE_INVALID = -1;
+    /**
+     * @hide
+     * Mix type indicating playback streams are mixed.
+     */
+    public static final int MIX_TYPE_PLAYERS = 0;
+    /**
+     * @hide
+     * Mix type indicating recording streams are mixed.
+     */
+    public static final int MIX_TYPE_RECORDERS = 1;
+
     int getRouteFlags() {
         return mRouteFlags;
     }
@@ -67,6 +90,11 @@
         return mRule;
     }
 
+    /** @hide */
+    public int getMixType() {
+        return mMixType;
+    }
+
     void setRegistration(String regId) {
         mRegistrationId = regId;
     }
@@ -77,6 +105,12 @@
     }
 
     /** @hide */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
+    }
+
+    /** @hide */
     @IntDef(flag = true,
             value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
     @Retention(RetentionPolicy.SOURCE)
@@ -86,6 +120,7 @@
      * Builder class for {@link AudioMix} objects
      *
      */
+    @SystemApi
     public static class Builder {
         private AudioMixingRule mRule = null;
         private AudioFormat mFormat = null;
@@ -102,6 +137,7 @@
          * @param rule a non-null {@link AudioMixingRule} instance.
          * @throws IllegalArgumentException
          */
+        @SystemApi
         public Builder(AudioMixingRule rule)
                 throws IllegalArgumentException {
             if (rule == null) {
@@ -132,6 +168,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
+        @SystemApi
         public Builder setFormat(AudioFormat format)
                 throws IllegalArgumentException {
             if (format == null) {
@@ -148,6 +185,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
+        @SystemApi
         public Builder setRouteFlags(@RouteFlags int routeFlags)
                 throws IllegalArgumentException {
             if (routeFlags == 0) {
@@ -166,6 +204,7 @@
          * @return a new {@link AudioMix} object
          * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
          */
+        @SystemApi
         public AudioMix build() throws IllegalArgumentException {
             if (mRule == null) {
                 throw new IllegalArgumentException("Illegal null AudioMixingRule");
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 2e06a80..02b03d2 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -16,10 +16,13 @@
 
 package android.media.audiopolicy;
 
+import android.annotation.SystemApi;
 import android.media.AudioAttributes;
+import android.os.Parcel;
 
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.Objects;
 
 
 /**
@@ -35,44 +38,114 @@
  *         .build();
  * </pre>
  */
+@SystemApi
 public class AudioMixingRule {
 
-    private AudioMixingRule(ArrayList<AttributeMatchCriterion> criteria) {
+    private AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria) {
         mCriteria = criteria;
+        mTargetMixType = mixType;
     }
 
     /**
-     * A rule requiring the usage information of the {@link AudioAttributes} to match
+     * A rule requiring the usage information of the {@link AudioAttributes} to match.
      */
+    @SystemApi
     public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
     /**
-     * A rule requiring the usage information of the {@link AudioAttributes} to differ
+     * A rule requiring the capture preset information of the {@link AudioAttributes} to match.
      */
-    public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = 0x1 << 1;
+    @SystemApi
+    public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;
+
+    private final static int RULE_EXCLUSION_MASK = 0x8000;
+    /**
+     * @hide
+     * A rule requiring the usage information of the {@link AudioAttributes} to differ.
+     */
+    public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE =
+            RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE;
+    /**
+     * @hide
+     * A rule requiring the capture preset information of the {@link AudioAttributes} to differ.
+     */
+    public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET =
+            RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
 
     static final class AttributeMatchCriterion {
         AudioAttributes mAttr;
         int mRule;
 
+        /** input parameters must be valid */
         AttributeMatchCriterion(AudioAttributes attributes, int rule) {
             mAttr = attributes;
             mRule = rule;
         }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mAttr, mRule);
+        }
+
+        void writeToParcel(Parcel dest) {
+            dest.writeInt(mRule);
+            if ((mRule == RULE_MATCH_ATTRIBUTE_USAGE) || (mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+                dest.writeInt(mAttr.getUsage());
+            } else {
+                // capture preset rule
+                dest.writeInt(mAttr.getCapturePreset());
+            }
+        }
     }
 
-    private ArrayList<AttributeMatchCriterion> mCriteria;
+    private final int mTargetMixType;
+    int getTargetMixType() { return mTargetMixType; }
+    private final ArrayList<AttributeMatchCriterion> mCriteria;
     ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; }
 
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTargetMixType, mCriteria);
+    }
+
+    private static boolean isValidSystemApiRule(int rule) {
+        switch(rule) {
+            case RULE_MATCH_ATTRIBUTE_USAGE:
+            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isValidIntRule(int rule) {
+        switch(rule) {
+            case RULE_MATCH_ATTRIBUTE_USAGE:
+            case RULE_EXCLUDE_ATTRIBUTE_USAGE:
+            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+            case RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isPlayerRule(int rule) {
+        return ((rule == RULE_MATCH_ATTRIBUTE_USAGE)
+                || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE));
+    }
+
     /**
      * Builder class for {@link AudioMixingRule} objects
-     *
      */
+    @SystemApi
     public static class Builder {
         private ArrayList<AttributeMatchCriterion> mCriteria;
+        private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
 
         /**
          * Constructs a new Builder with no rules.
          */
+        @SystemApi
         public Builder() {
             mCriteria = new ArrayList<AttributeMatchCriterion>();
         }
@@ -81,18 +154,80 @@
          * Add a rule for the selection of which streams are mixed together.
          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
          *     rule hasn't been set yet.
-         * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
-         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}.
+         * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
+         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
+        @SystemApi
         public Builder addRule(AudioAttributes attrToMatch, int rule)
                 throws IllegalArgumentException {
+            if (!isValidSystemApiRule(rule)) {
+                throw new IllegalArgumentException("Illegal rule value " + rule);
+            }
+            return addRuleInt(attrToMatch, rule);
+        }
+
+        /**
+         * Add a rule by exclusion for the selection of which streams are mixed together.
+         * <br>For instance the following code
+         * <br><pre>
+         * AudioAttributes mediaAttr = new AudioAttributes.Builder()
+         *         .setUsage(AudioAttributes.USAGE_MEDIA)
+         *         .build();
+         * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
+         *         .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
+         *         .build();
+         * </pre>
+         * <br>will create a rule which maps to any usage value, except USAGE_MEDIA.
+         * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
+         *     rule hasn't been set yet.
+         * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
+         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        @SystemApi
+        public Builder excludeRule(AudioAttributes attrToMatch, int rule)
+                throws IllegalArgumentException {
+            if (!isValidSystemApiRule(rule)) {
+                throw new IllegalArgumentException("Illegal rule value " + rule);
+            }
+            return addRuleInt(attrToMatch, rule | RULE_EXCLUSION_MASK);
+        }
+
+        /**
+         * Add or exclude a rule for the selection of which streams are mixed together.
+         * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
+         *     rule hasn't been set yet.
+         * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
+         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
+         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
+         *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        Builder addRuleInt(AudioAttributes attrToMatch, int rule)
+                throws IllegalArgumentException {
             if (attrToMatch == null) {
                 throw new IllegalArgumentException("Illegal null AudioAttributes argument");
             }
-            if ((rule != RULE_MATCH_ATTRIBUTE_USAGE) && (rule != RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+            if (!isValidIntRule(rule)) {
                 throw new IllegalArgumentException("Illegal rule value " + rule);
+            } else {
+                // as rules are added to the Builder, we verify they are consistent with the type
+                // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
+                if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
+                    if (isPlayerRule(rule)) {
+                        mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
+                    } else {
+                        mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
+                    }
+                } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
+                        || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
+                {
+                    throw new IllegalArgumentException("Incompatible rule for mix");
+                }
             }
             synchronized (mCriteria) {
                 Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator();
@@ -111,6 +246,19 @@
                                         + attrToMatch);
                             }
                         }
+                    } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
+                            || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
+                        // "capture preset"-base rule
+                        if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
+                            if (criterion.mRule == rule) {
+                             // rule already exists, we're done
+                                return this;
+                            } else {
+                                // criterion already exists with a another rule, it is incompatible
+                                throw new IllegalArgumentException("Contradictory rule exists for "
+                                        + attrToMatch);
+                            }
+                        }
                     }
                 }
                 // rule didn't exist, add it
@@ -119,13 +267,32 @@
             return this;
         }
 
+        Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
+            int rule = in.readInt();
+            AudioAttributes attr;
+            if ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+                int usage = in.readInt();
+                attr = new AudioAttributes.Builder()
+                        .setUsage(usage).build();
+            } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
+                    || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
+                int preset = in.readInt();
+                attr = new AudioAttributes.Builder()
+                        .setInternalCapturePreset(preset).build();
+            } else {
+                in.readInt(); // assume there was in int value to read as for now they come in pair
+                throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
+            }
+            return addRuleInt(attr, rule);
+        }
+
         /**
          * Combines all of the matching and exclusion rules that have been set and return a new
          * {@link AudioMixingRule} object.
          * @return a new {@link AudioMixingRule} object
          */
         public AudioMixingRule build() {
-            return new AudioMixingRule(mCriteria);
+            return new AudioMixingRule(mTargetMixType, mCriteria);
         }
     }
 }
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e9dc3af..44d2430 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -17,17 +17,21 @@
 package android.media.audiopolicy;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioRecord;
-import android.media.AudioSystem;
 import android.media.AudioTrack;
 import android.media.MediaRecorder;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.util.Log;
 import android.util.Slog;
 
@@ -39,21 +43,20 @@
  * @hide
  * AudioPolicy provides access to the management of audio routing and audio focus.
  */
+@SystemApi
 public class AudioPolicy {
 
     private static final String TAG = "AudioPolicy";
 
     /**
-     * The status of an audio policy that cannot be used because it is invalid.
-     */
-    public static final int POLICY_STATUS_INVALID = 0;
-    /**
      * The status of an audio policy that is valid but cannot be used because it is not registered.
      */
+    @SystemApi
     public static final int POLICY_STATUS_UNREGISTERED = 1;
     /**
      * The status of an audio policy that is valid, successfully registered and thus active.
      */
+    @SystemApi
     public static final int POLICY_STATUS_REGISTERED = 2;
 
     private int mStatus;
@@ -72,22 +75,29 @@
     /**
      * The parameter is guaranteed non-null through the Builder
      */
-    private AudioPolicy(AudioPolicyConfig config, Context context) {
+    private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper) {
         mConfig = config;
-        if (mConfig.mMixes.isEmpty()) {
-            mStatus = POLICY_STATUS_INVALID;
-        } else {
-            mStatus = POLICY_STATUS_UNREGISTERED;
-        }
+        mStatus = POLICY_STATUS_UNREGISTERED;
         mContext = context;
+        if (looper == null) {
+            looper = Looper.getMainLooper();
+        }
+        if (looper != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else {
+            mEventHandler = null;
+            Log.e(TAG, "No event handler due to looper without a thread");
+        }
     }
 
     /**
      * Builder class for {@link AudioPolicy} objects
      */
+    @SystemApi
     public static class Builder {
         private ArrayList<AudioMix> mMixes;
         private Context mContext;
+        private Looper mLooper;
 
         /**
          * Constructs a new Builder with no audio mixes.
@@ -104,7 +114,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        public Builder addMix(AudioMix mix) throws IllegalArgumentException {
+        public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
             if (mix == null) {
                 throw new IllegalArgumentException("Illegal null AudioMix argument");
             }
@@ -112,18 +122,41 @@
             return this;
         }
 
+        /**
+         * Sets the {@link Looper} on which to run the event loop.
+         * @param looper a non-null specific Looper.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
+            if (looper == null) {
+                throw new IllegalArgumentException("Illegal null Looper argument");
+            }
+            mLooper = looper;
+            return this;
+        }
+
         public AudioPolicy build() {
-            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext);
+            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper);
         }
     }
 
-    /** @hide */
     public void setRegistration(String regId) {
         mRegistrationId = regId;
         mConfig.setRegistration(regId);
+        if (regId != null) {
+            mStatus = POLICY_STATUS_REGISTERED;
+        } else {
+            mStatus = POLICY_STATUS_UNREGISTERED;
+        }
+        sendMsg(mEventHandler, MSG_POLICY_STATUS_CHANGE);
     }
 
     private boolean policyReadyToUse() {
+        if (mStatus != POLICY_STATUS_REGISTERED) {
+            Log.e(TAG, "Cannot use unregistered AudioPolicy");
+            return false;
+        }
         if (mContext == null) {
             Log.e(TAG, "Cannot use AudioPolicy without context");
             return false;
@@ -155,11 +188,17 @@
         {
             throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
         }
-        // TODO also check mix is defined for playback or recording, and matches forTrack argument
+        if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
+            throw new IllegalArgumentException(
+                    "Invalid AudioMix: not defined for being a recording source");
+        }
+        if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
+            throw new IllegalArgumentException(
+                    "Invalid AudioMix: not defined for capturing playback");
+        }
     }
 
     /**
-     * @hide
      * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
      * Audio buffers recorded through the created instance will contain the mix of the audio
      * streams that fed the given mixer.
@@ -170,6 +209,7 @@
      *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
      * @throws IllegalArgumentException
      */
+    @SystemApi
     public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
         if (!policyReadyToUse()) {
             Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
@@ -186,7 +226,7 @@
         AudioRecord ar = new AudioRecord(
                 new AudioAttributes.Builder()
                         .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
-                        .addTag(mix.getRegistration())
+                        .addTag(addressForTag(mix))
                         .build(),
                 mixFormat,
                 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
@@ -198,17 +238,17 @@
     }
 
     /**
-     * @hide
      * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
      * Audio buffers played through the created instance will be sent to the given mix
      * to be recorded through the recording APIs.
      * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
      *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
-     * @returna new {@link AudioTrack} instance whose data format is the one defined in the
+     * @return a new {@link AudioTrack} instance whose data format is the one defined in the
      *     {@link AudioMix}, or null if this policy was not successfully registered
      *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
      * @throws IllegalArgumentException
      */
+    @SystemApi
     public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
         if (!policyReadyToUse()) {
             Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
@@ -219,7 +259,7 @@
         AudioTrack at = new AudioTrack(
                 new AudioAttributes.Builder()
                         .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
-                        .addTag(mix.getRegistration())
+                        .addTag(addressForTag(mix))
                         .build(),
                 mix.getFormat(),
                 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
@@ -230,20 +270,63 @@
         return at;
     }
 
+    @SystemApi
     public int getStatus() {
         return mStatus;
     }
 
+    @SystemApi
     public static abstract class AudioPolicyStatusListener {
-        void onStatusChange() {}
-        void onMixStateUpdate(AudioMix mix) {}
+        public void onStatusChange() {}
+        public void onMixStateUpdate(AudioMix mix) {}
     }
 
-    void setStatusListener(AudioPolicyStatusListener l) {
+    @SystemApi
+    synchronized public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
         mStatusListener = l;
     }
 
-    /** @hide */
+    synchronized private void onPolicyStatusChange() {
+        if (mStatusListener == null) {
+            return;
+        }
+        mStatusListener.onStatusChange();
+    }
+
+    //==================================================
+    // Event handling
+    private final EventHandler mEventHandler;
+    private final static int MSG_POLICY_STATUS_CHANGE = 0;
+
+    private class EventHandler extends Handler {
+        public EventHandler(AudioPolicy ap, Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case MSG_POLICY_STATUS_CHANGE:
+                    onPolicyStatusChange();
+                    break;
+                default:
+                    Log.e(TAG, "Unknown event " + msg.what);
+            }
+        }
+    }
+
+    //==========================================================
+    // Utils
+    private static String addressForTag(AudioMix mix) {
+        return "addr=" + mix.getRegistration();
+    }
+
+    private static void sendMsg(Handler handler, int msg) {
+        if (handler != null) {
+            handler.sendEmptyMessage(msg);
+        }
+    }
+
     public String toLogFriendlyString() {
         String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
         textDump += "config=" + mConfig.toLogFriendlyString();
@@ -252,7 +335,6 @@
 
     /** @hide */
     @IntDef({
-        POLICY_STATUS_INVALID,
         POLICY_STATUS_REGISTERED,
         POLICY_STATUS_UNREGISTERED
     })
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index a9a4175..e2a20da 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Objects;
 
 /**
  * @hide
@@ -38,7 +39,7 @@
 
     protected ArrayList<AudioMix> mMixes;
 
-    protected String mRegistrationId = null;
+    private String mRegistrationId = null;
 
     protected AudioPolicyConfig(AudioPolicyConfig conf) {
         mMixes = conf.mMixes;
@@ -62,6 +63,11 @@
     }
 
     @Override
+    public int hashCode() {
+        return Objects.hash(mMixes);
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
@@ -80,8 +86,7 @@
             final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria();
             dest.writeInt(criteria.size());
             for (AttributeMatchCriterion criterion : criteria) {
-                dest.writeInt(criterion.mRule);
-                dest.writeInt(criterion.mAttr.getUsage());
+                criterion.writeToParcel(dest);
             }
         }
     }
@@ -106,17 +111,7 @@
             AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
             for (int j = 0 ; j < nbRules ; j++) {
                 // read the matching rules
-                int matchRule = in.readInt();
-                if ((matchRule == AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE)
-                    || (matchRule == AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)) {
-                    int usage = in.readInt();
-                    final AudioAttributes attr = new AudioAttributes.Builder()
-                            .setUsage(usage).build();
-                    ruleBuilder.addRule(attr, matchRule);
-                } else {
-                    Log.w(TAG, "Encountered unsupported rule, skipping");
-                    in.readInt();
-                }
+                ruleBuilder.addRuleFromParcel(in);
             }
             mixBuilder.setMixingRule(ruleBuilder.build());
             mMixes.add(mixBuilder.build());
@@ -140,7 +135,7 @@
 
     public String toLogFriendlyString () {
         String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
-        textDump += mMixes.size() + " AudioMix:\n";
+        textDump += mMixes.size() + " AudioMix: "+ mRegistrationId + "\n";
         for(AudioMix mix : mMixes) {
             // write mix route flags
             textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
@@ -161,6 +156,14 @@
                         textDump += "  match usage ";
                         textDump += criterion.mAttr.usageToString();
                         break;
+                    case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
+                        textDump += "  exclude capture preset ";
+                        textDump += criterion.mAttr.getCapturePreset();
+                        break;
+                    case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+                        textDump += "  match capture preset ";
+                        textDump += criterion.mAttr.getCapturePreset();
+                        break;
                     default:
                         textDump += "invalid rule!";
                 }
@@ -170,12 +173,32 @@
         return textDump;
     }
 
-    public void setRegistration(String regId) {
-        mRegistrationId = regId;
+    protected void setRegistration(String regId) {
+        final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty();
+        final boolean newRegNull = (regId == null) || regId.isEmpty();
+        if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) {
+            Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId);
+            return;
+        }
+        mRegistrationId = regId == null ? "" : regId;
         int mixIndex = 0;
         for (AudioMix mix : mMixes) {
-            mix.setRegistration(mRegistrationId + "mix:" + mixIndex++);
+            if (!mRegistrationId.isEmpty()) {
+                mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
+                        + mixIndex++);
+            } else {
+                mix.setRegistration("");
+            }
         }
     }
 
+    private static String mixTypeId(int type) {
+        if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
+        else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
+        else return "i";
+    }
+
+    protected String getRegistration() {
+        return mRegistrationId;
+    }
 }
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 6ca794e..21549c9 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -38,6 +38,7 @@
 interface ITvInputManager {
     List<TvInputInfo> getTvInputList(int userId);
     TvInputInfo getTvInputInfo(in String inputId, int userId);
+    int getTvInputState(in String inputId, int userId);
 
     List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId);
 
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index de9d54f..f55299e 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -72,6 +72,17 @@
     public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = VIDEO_UNAVAILABLE_REASON_END;
 
     /**
+     * The TV input is in unknown state.
+     * <p>
+     * State for denoting unknown TV input state. The typical use case is when a requested TV
+     * input is removed from the device or it is not registered. Used in
+     * {@code ITvInputManager.getTvInputState()}.
+     * </p>
+     * @hide
+     */
+    public static final int INPUT_STATE_UNKNOWN = -1;
+
+    /**
      * The TV input is connected.
      * <p>
      * State for {@link #getInputState} and {@link
@@ -127,10 +138,10 @@
      * <receiver android:name=".TvInputReceiver">
      *     <intent-filter>
      *         <action android:name=
-     *                 "android.media.tv.TvInputManager.ACTION_QUERY_CONTENT_RATING_SYSTEMS" />
+     *                 "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
      *     </intent-filter>
      *     <meta-data
-     *             android:name="android.media.tv.TvInputManager.META_DATA_CONTENT_RATING_SYSTEMS"
+     *             android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
      *             android:resource="@xml/tv_content_rating_systems" />
      * </receiver>}</pre></p>
      * In the above example, the <code>@xml/tv_content_rating_systems</code> resource refers to an
@@ -751,9 +762,19 @@
         try {
             if (mService != null) {
                 mService.registerCallback(mManagerCallback, mUserId);
+                List<TvInputInfo> infos = mService.getTvInputList(mUserId);
+                synchronized (mLock) {
+                    for (TvInputInfo info : infos) {
+                        String inputId = info.getId();
+                        int state = mService.getTvInputState(inputId, mUserId);
+                        if (state != INPUT_STATE_UNKNOWN) {
+                            mStateMap.put(inputId, state);
+                        }
+                    }
+                }
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "mService.registerCallback failed: " + e);
+            Log.e(TAG, "TvInputManager initialization failed: " + e);
         }
     }
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
index 90875c0..ea6281d 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -486,7 +486,9 @@
                 protected Void doInBackground(Void... params) {
                     synchronized (mLock) {
                         try {
-                            mRenderer.closeDocument();
+                            if (mRenderer != null) {
+                                mRenderer.closeDocument();
+                            }
                         } catch (RemoteException re) {
                             /* ignore */
                         }
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 352b545..f3a5c95 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -487,6 +487,10 @@
                 requestCreatePdfFileOrFinish();
             } break;
 
+            case STATE_PRINT_CANCELED: {
+                updateOptionsUi();
+            } break;
+
             default: {
                 updatePrintPreviewController(document.changed);
 
diff --git a/packages/SystemUI/res/drawable/stat_sys_no_sims.xml b/packages/SystemUI/res/drawable/stat_sys_no_sims.xml
new file mode 100644
index 0000000..8bad226
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_no_sims.xml
@@ -0,0 +1,25 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="17dp"
+        android:height="17dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+
+    <path
+        android:fillColor="#4DFFFFFF"
+        android:pathData="M19.0,5.0c0.0,-1.1 -0.9,-2.0 -2.0,-2.0l-7.0,0.0L7.7,5.3L19.0,16.7L19.0,5.0zM3.7,3.9L2.4,5.2L5.0,7.8L5.0,19.0c0.0,1.1 0.9,2.0 2.0,2.0l10.0,0.0c0.4,0.0 0.7,-0.1 1.0,-0.3l1.9,1.9l1.3,-1.3L3.7,3.9z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/mobile_signal_group.xml b/packages/SystemUI/res/layout/mobile_signal_group.xml
new file mode 100644
index 0000000..97697189
--- /dev/null
+++ b/packages/SystemUI/res/layout/mobile_signal_group.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* apps/common/assets/default/default/skins/StatusBar.xml
+**
+** Copyright 2011, 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.
+*/
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/mobile_combo"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    >
+    <ImageView
+        android:id="@+id/mobile_signal"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        />
+    <ImageView
+        android:id="@+id/mobile_type"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
index f45aa85..8fbd8f7 100644
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ b/packages/SystemUI/res/layout/signal_cluster_view.xml
@@ -50,27 +50,18 @@
         android:layout_height="4dp"
         android:visibility="gone"
         />
-    <FrameLayout
+    <LinearLayout
+        android:id="@+id/mobile_signal_group"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         >
-        <FrameLayout
-            android:id="@+id/mobile_combo"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            >
-            <ImageView
-                android:id="@+id/mobile_signal"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                />
-            <ImageView
-                android:id="@+id/mobile_type"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                />
-        </FrameLayout>
-    </FrameLayout>
+    </LinearLayout>
+    <ImageView
+        android:id="@+id/no_sims"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:src="@drawable/stat_sys_no_sims"
+        />
     <View
         android:id="@+id/wifi_airplane_spacer"
         android:layout_width="4dp"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 68a7622..44330e8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -515,6 +515,9 @@
          type icon is wide. -->
     <dimen name="wide_type_icon_start_padding">2dp</dimen>
 
+    <!-- Extra padding between multiple phone signal icons. -->
+    <dimen name="secondary_telephony_padding">2dp</dimen>
+
     <!-- Extra padding between the mobile data type icon and the strength indicator when the data
          type icon is wide for the tile in quick settings. -->
     <dimen name="wide_type_icon_start_padding_qs">3dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 6ea5c70..89a2c74 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -28,6 +28,7 @@
     void pulseWhileDozing(@NonNull PulseCallback callback, int reason);
     void stopDozing();
     boolean isPowerSaveActive();
+    boolean isNotificationLightOn();
 
     public interface Callback {
         void onNewNotifications();
@@ -40,4 +41,4 @@
         void onPulseStarted();
         void onPulseFinished();
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 1e29476..2341144 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -56,6 +56,17 @@
     private static final String NOTIFICATION_PULSE_ACTION = ACTION_BASE + ".notification_pulse";
     private static final String EXTRA_INSTANCE = "instance";
 
+    /**
+     * Earliest time we pulse due to a notification light after the service started.
+     *
+     * <p>Incoming notification light events during the blackout period are
+     * delayed to the earliest time defined by this constant.</p>
+     *
+     * <p>This delay avoids a pulse immediately after screen off, at which
+     * point the notification light is re-enabled again by NoMan.</p>
+     */
+    private static final int EARLIEST_LIGHT_PULSE_AFTER_START_MS = 10 * 1000;
+
     private final String mTag = String.format(TAG + ".%08x", hashCode());
     private final Context mContext = this;
     private final DozeParameters mDozeParameters = new DozeParameters(mContext);
@@ -77,6 +88,7 @@
     private boolean mPowerSaveActive;
     private boolean mCarMode;
     private long mNotificationPulseTime;
+    private long mEarliestPulseDueToLight;
     private int mScheduleResetsRemaining;
 
     public DozeService() {
@@ -161,8 +173,9 @@
         }
 
         mDreaming = true;
-        listenForPulseSignals(true);
         rescheduleNotificationPulse(false /*predicate*/);  // cancel any pending pulse alarms
+        mEarliestPulseDueToLight = System.currentTimeMillis() + EARLIEST_LIGHT_PULSE_AFTER_START_MS;
+        listenForPulseSignals(true);
 
         // Ask the host to get things ready to start dozing.
         // Once ready, we call startDozing() at which point the CPU may suspend
@@ -298,6 +311,12 @@
         if (listen) {
             resetNotificationResets();
             mHost.addCallback(mHostCallback);
+
+            // Continue to pulse for existing LEDs.
+            mNotificationLightOn = mHost.isNotificationLightOn();
+            if (mNotificationLightOn) {
+                updateNotificationPulseDueToLight();
+            }
         } else {
             mHost.removeCallback(mHostCallback);
         }
@@ -308,21 +327,26 @@
         mScheduleResetsRemaining = mDozeParameters.getPulseScheduleResets();
     }
 
-    private void updateNotificationPulse() {
-        if (DEBUG) Log.d(mTag, "updateNotificationPulse");
+    private void updateNotificationPulseDueToLight() {
+        long timeMs = System.currentTimeMillis();
+        timeMs = Math.max(timeMs, mEarliestPulseDueToLight);
+        updateNotificationPulse(timeMs);
+    }
+
+    private void updateNotificationPulse(long notificationTimeMs) {
+        if (DEBUG) Log.d(mTag, "updateNotificationPulse notificationTimeMs=" + notificationTimeMs);
         if (!mDozeParameters.getPulseOnNotifications()) return;
         if (mScheduleResetsRemaining <= 0) {
             if (DEBUG) Log.d(mTag, "No more schedule resets remaining");
             return;
         }
-        final long now = System.currentTimeMillis();
-        if ((now - mNotificationPulseTime) < mDozeParameters.getPulseDuration()) {
+        if ((notificationTimeMs - mNotificationPulseTime) < mDozeParameters.getPulseDuration()) {
             if (DEBUG) Log.d(mTag, "Recently updated, not resetting schedule");
             return;
         }
         mScheduleResetsRemaining--;
         if (DEBUG) Log.d(mTag, "mScheduleResetsRemaining = " + mScheduleResetsRemaining);
-        mNotificationPulseTime = now;
+        mNotificationPulseTime = notificationTimeMs;
         rescheduleNotificationPulse(true /*predicate*/);
     }
 
@@ -404,14 +428,14 @@
     private final DozeHost.Callback mHostCallback = new DozeHost.Callback() {
         @Override
         public void onNewNotifications() {
-            if (DEBUG) Log.d(mTag, "onNewNotifications");
+            if (DEBUG) Log.d(mTag, "onNewNotifications (noop)");
             // noop for now
         }
 
         @Override
         public void onBuzzBeepBlinked() {
             if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked");
-            updateNotificationPulse();
+            updateNotificationPulse(System.currentTimeMillis());
         }
 
         @Override
@@ -420,7 +444,7 @@
             if (mNotificationLightOn == on) return;
             mNotificationLightOn = on;
             if (mNotificationLightOn) {
-                updateNotificationPulse();
+                updateNotificationPulseDueToLight();
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 80ddd4a..f2ebcf6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -147,16 +147,15 @@
     }
 
     private final NetworkSignalChangedCallback mCallback = new NetworkSignalChangedCallback() {
-        private boolean mWifiEnabled;
-        private boolean mWifiConnected;
-        private boolean mAirplaneModeEnabled;
+        private final CallbackInfo mInfo = new CallbackInfo();
 
         @Override
         public void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
                 boolean activityIn, boolean activityOut,
                 String wifiSignalContentDescriptionId, String description) {
-            mWifiEnabled = enabled;
-            mWifiConnected = connected;
+            mInfo.wifiEnabled = enabled;
+            mInfo.wifiConnected = connected;
+            refreshState(mInfo);
         }
 
         @Override
@@ -164,28 +163,35 @@
                 int mobileSignalIconId,
                 String mobileSignalContentDescriptionId, int dataTypeIconId,
                 boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description, boolean noSim,
+                String dataTypeContentDescriptionId, String description,
                 boolean isDataTypeIconWide) {
-            final CallbackInfo info = new CallbackInfo();  // TODO pool?
-            info.enabled = enabled;
-            info.wifiEnabled = mWifiEnabled;
-            info.wifiConnected = mWifiConnected;
-            info.airplaneModeEnabled = mAirplaneModeEnabled;
-            info.mobileSignalIconId = mobileSignalIconId;
-            info.signalContentDescription = mobileSignalContentDescriptionId;
-            info.dataTypeIconId = dataTypeIconId;
-            info.dataContentDescription = dataTypeContentDescriptionId;
-            info.activityIn = activityIn;
-            info.activityOut = activityOut;
-            info.enabledDesc = description;
-            info.noSim = noSim;
-            info.isDataTypeIconWide = isDataTypeIconWide;
-            refreshState(info);
+            mInfo.enabled = enabled;
+            mInfo.mobileSignalIconId = mobileSignalIconId;
+            mInfo.signalContentDescription = mobileSignalContentDescriptionId;
+            mInfo.dataTypeIconId = dataTypeIconId;
+            mInfo.dataContentDescription = dataTypeContentDescriptionId;
+            mInfo.activityIn = activityIn;
+            mInfo.activityOut = activityOut;
+            mInfo.enabledDesc = description;
+            mInfo.isDataTypeIconWide = isDataTypeIconWide;
+            refreshState(mInfo);
+        }
+
+        @Override
+        public void onNoSimVisibleChanged(boolean visible) {
+            mInfo.noSim = visible;
+            if (mInfo.noSim) {
+                // Make sure signal gets cleared out when no sims.
+                mInfo.mobileSignalIconId = 0;
+                mInfo.dataTypeIconId = 0;
+            }
+            refreshState(mInfo);
         }
 
         @Override
         public void onAirplaneModeChanged(boolean enabled) {
-            mAirplaneModeEnabled = enabled;
+            mInfo.airplaneModeEnabled = enabled;
+            refreshState(mInfo);
         }
 
         public void onMobileDataEnabled(boolean enabled) {
@@ -203,7 +209,8 @@
         @Override
         public Boolean getToggleState() {
             return mDataController.isMobileDataSupported()
-                    ? mDataController.isMobileDataEnabled() : null;
+                    ? mDataController.isMobileDataEnabled()
+                    : null;
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 4fb1189..5e30622 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -217,11 +217,15 @@
                 int mobileSignalIconId,
                 String mobileSignalContentDescriptionId, int dataTypeIconId,
                 boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description, boolean noSim,
+                String dataTypeContentDescriptionId, String description,
                 boolean isDataTypeIconWide) {
             // noop
         }
 
+        public void onNoSimVisibleChanged(boolean noSims) {
+            // noop
+        }
+
         @Override
         public void onAirplaneModeChanged(boolean enabled) {
             // noop
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 4a16f8d..57ac4b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -689,6 +689,12 @@
         }
     }
 
+    protected boolean isCurrentProfile(int userId) {
+        synchronized (mCurrentProfiles) {
+            return mCurrentProfiles.get(userId) != null;
+        }
+    }
+
     @Override
     public String getCurrentMediaNotificationKey() {
         return null;
@@ -1642,7 +1648,11 @@
     protected void handleVisibleToUserChanged(boolean visibleToUser) {
         try {
             if (visibleToUser) {
-                mBarService.onPanelRevealed();
+                // Only stop blinking, vibrating, ringing when the user went into the shade
+                // manually (SHADE or SHADE_LOCKED).
+                boolean clearNotificationEffects =
+                        (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
+                mBarService.onPanelRevealed(clearNotificationEffects);
             } else {
                 mBarService.onPanelHidden();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 27da6fd..0faad21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -244,6 +244,9 @@
 
     public void notifyContentUpdated() {
         selectLayout(false /* animate */, true /* force */);
+        if (mContractedChild != null) {
+            mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+        }
     }
 
     public boolean isContentExpandable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
index 045be3e..0702d7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationCustomViewWrapper.java
@@ -27,7 +27,6 @@
 public class NotificationCustomViewWrapper extends NotificationViewWrapper {
 
     private final ViewInvertHelper mInvertHelper;
-    private boolean mDark;
 
     protected NotificationCustomViewWrapper(View view) {
         super(view);
@@ -36,13 +35,10 @@
 
     @Override
     public void setDark(boolean dark, boolean fade, long delay) {
-        if (mDark != dark) {
-            mDark = dark;
-            if (fade) {
-                mInvertHelper.fade(dark, delay);
-            } else {
-                mInvertHelper.update(dark);
-            }
+        if (fade) {
+            mInvertHelper.fade(dark, delay);
+        } else {
+            mInvertHelper.update(dark);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
index 8f63a79..953c373 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
@@ -24,19 +24,14 @@
  */
 public class NotificationMediaViewWrapper extends NotificationTemplateViewWrapper {
 
-    private boolean mDark;
-
     protected NotificationMediaViewWrapper(Context ctx, View view) {
         super(ctx, view);
     }
 
     @Override
     public void setDark(boolean dark, boolean fade, long delay) {
-        if (mDark != dark) {
-            mDark = dark;
 
-            // Only update the large icon, because the rest is already inverted.
-            setPictureGrayscale(dark, fade, delay);
-        }
+        // Only update the large icon, because the rest is already inverted.
+        setPictureGrayscale(dark, fade, delay);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
index 8dc14b0..5b6e1cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
@@ -51,8 +51,6 @@
     private final int mIconBackgroundDarkColor;
     private final Interpolator mLinearOutSlowInInterpolator;
 
-    private boolean mDark;
-
     protected NotificationTemplateViewWrapper(Context ctx, View view) {
         super(view);
         mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
@@ -95,26 +93,23 @@
 
     @Override
     public void setDark(boolean dark, boolean fade, long delay) {
-        if (mDark != dark) {
-            mDark = dark;
-            if (mInvertHelper != null) {
-                if (fade) {
-                    mInvertHelper.fade(dark, delay);
-                } else {
-                    mInvertHelper.update(dark);
-                }
+        if (mInvertHelper != null) {
+            if (fade) {
+                mInvertHelper.fade(dark, delay);
+            } else {
+                mInvertHelper.update(dark);
             }
-            if (mIcon != null) {
-                if (fade) {
-                    fadeIconColorFilter(mIcon, dark, delay);
-                    fadeIconAlpha(mIcon, dark, delay);
-                } else {
-                    updateIconColorFilter(mIcon, dark);
-                    updateIconAlpha(mIcon, dark);
-                }
-            }
-            setPictureGrayscale(dark, fade, delay);
         }
+        if (mIcon != null) {
+            if (fade) {
+                fadeIconColorFilter(mIcon, dark, delay);
+                fadeIconAlpha(mIcon, dark, delay);
+            } else {
+                updateIconColorFilter(mIcon, dark);
+                updateIconAlpha(mIcon, dark);
+            }
+        }
+        setPictureGrayscale(dark, fade, delay);
     }
 
     protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 418c57f..8e50abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -17,8 +17,10 @@
 package com.android.systemui.statusbar;
 
 import android.content.Context;
+import android.telephony.SubscriptionInfo;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
@@ -29,6 +31,9 @@
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
 import com.android.systemui.statusbar.policy.SecurityController;
 
+import java.util.ArrayList;
+import java.util.List;
+
 // Intimately tied to the design of res/layout/signal_cluster_view.xml
 public class SignalClusterView
         extends LinearLayout
@@ -41,23 +46,24 @@
     NetworkControllerImpl mNC;
     SecurityController mSC;
 
+    private boolean mNoSimsVisible = false;
     private boolean mVpnVisible = false;
     private boolean mWifiVisible = false;
     private int mWifiStrengthId = 0;
-    private boolean mMobileVisible = false;
-    private int mMobileStrengthId = 0, mMobileTypeId = 0;
     private boolean mIsAirplaneMode = false;
     private int mAirplaneIconId = 0;
     private int mAirplaneContentDescription;
-    private String mWifiDescription, mMobileDescription, mMobileTypeDescription;
-    private boolean mIsMobileTypeIconWide;
+    private String mWifiDescription;
+    private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
 
-    ViewGroup mWifiGroup, mMobileGroup;
-    ImageView mVpn, mWifi, mMobile, mMobileType, mAirplane;
+    ViewGroup mWifiGroup;
+    ImageView mVpn, mWifi, mAirplane, mNoSims;
     View mWifiAirplaneSpacer;
     View mWifiSignalSpacer;
+    LinearLayout mMobileSignalGroup;
 
     private int mWideTypeIconStartPadding;
+    private int mSecondaryTelephonyPadding;
     private int mEndPadding;
     private int mEndPaddingNothingVisible;
 
@@ -90,6 +96,8 @@
         super.onFinishInflate();
         mWideTypeIconStartPadding = getContext().getResources().getDimensionPixelSize(
                 R.dimen.wide_type_icon_start_padding);
+        mSecondaryTelephonyPadding = getContext().getResources().getDimensionPixelSize(
+                R.dimen.secondary_telephony_padding);
         mEndPadding = getContext().getResources().getDimensionPixelSize(
                 R.dimen.signal_cluster_battery_padding);
         mEndPaddingNothingVisible = getContext().getResources().getDimensionPixelSize(
@@ -103,12 +111,14 @@
         mVpn            = (ImageView) findViewById(R.id.vpn);
         mWifiGroup      = (ViewGroup) findViewById(R.id.wifi_combo);
         mWifi           = (ImageView) findViewById(R.id.wifi_signal);
-        mMobileGroup    = (ViewGroup) findViewById(R.id.mobile_combo);
-        mMobile         = (ImageView) findViewById(R.id.mobile_signal);
-        mMobileType     = (ImageView) findViewById(R.id.mobile_type);
         mAirplane       = (ImageView) findViewById(R.id.airplane);
+        mNoSims         = (ImageView) findViewById(R.id.no_sims);
         mWifiAirplaneSpacer =         findViewById(R.id.wifi_airplane_spacer);
         mWifiSignalSpacer =           findViewById(R.id.wifi_signal_spacer);
+        mMobileSignalGroup = (LinearLayout) findViewById(R.id.mobile_signal_group);
+        for (PhoneState state : mPhoneStates) {
+            mMobileSignalGroup.addView(state.mMobileGroup);
+        }
 
         apply();
     }
@@ -118,10 +128,9 @@
         mVpn            = null;
         mWifiGroup      = null;
         mWifi           = null;
-        mMobileGroup    = null;
-        mMobile         = null;
-        mMobileType     = null;
         mAirplane       = null;
+        mMobileSignalGroup.removeAllViews();
+        mMobileSignalGroup = null;
 
         super.onDetachedFromWindow();
     }
@@ -149,18 +158,56 @@
 
     @Override
     public void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
-            String contentDescription, String typeContentDescription, boolean isTypeIconWide) {
-        mMobileVisible = visible;
-        mMobileStrengthId = strengthIcon;
-        mMobileTypeId = typeIcon;
-        mMobileDescription = contentDescription;
-        mMobileTypeDescription = typeContentDescription;
-        mIsMobileTypeIconWide = isTypeIconWide;
+            String contentDescription, String typeContentDescription, boolean isTypeIconWide,
+            int subId) {
+        PhoneState state = getOrInflateState(subId);
+        state.mMobileVisible = visible;
+        state.mMobileStrengthId = strengthIcon;
+        state.mMobileTypeId = typeIcon;
+        state.mMobileDescription = contentDescription;
+        state.mMobileTypeDescription = typeContentDescription;
+        state.mIsMobileTypeIconWide = isTypeIconWide;
 
         apply();
     }
 
     @Override
+    public void setNoSims(boolean show) {
+        mNoSimsVisible = show;
+    }
+
+    @Override
+    public void setSubs(List<SubscriptionInfo> subs) {
+        // Clear out all old subIds.
+        mPhoneStates.clear();
+        if (mMobileSignalGroup != null) {
+            mMobileSignalGroup.removeAllViews();
+        }
+        final int n = subs.size();
+        for (int i = 0; i < n; i++) {
+            inflatePhoneState(subs.get(i).getSubscriptionId());
+        }
+    }
+
+    private PhoneState getOrInflateState(int subId) {
+        for (PhoneState state : mPhoneStates) {
+            if (state.mSubId == subId) {
+                return state;
+            }
+        }
+        return inflatePhoneState(subId);
+    }
+
+    private PhoneState inflatePhoneState(int subId) {
+        PhoneState state = new PhoneState(subId, mContext);
+        if (mMobileSignalGroup != null) {
+            mMobileSignalGroup.addView(state.mMobileGroup);
+        }
+        mPhoneStates.add(state);
+        return state;
+    }
+
+    @Override
     public void setIsAirplaneMode(boolean is, int airplaneIconId, int contentDescription) {
         mIsAirplaneMode = is;
         mAirplaneIconId = airplaneIconId;
@@ -175,8 +222,9 @@
         // ignore content description, so populate manually
         if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null)
             event.getText().add(mWifiGroup.getContentDescription());
-        if (mMobileVisible && mMobileGroup != null && mMobileGroup.getContentDescription() != null)
-            event.getText().add(mMobileGroup.getContentDescription());
+        for (PhoneState state : mPhoneStates) {
+            state.populateAccessibilityEvent(event);
+        }
         return super.dispatchPopulateAccessibilityEvent(event);
     }
 
@@ -188,12 +236,13 @@
             mWifi.setImageDrawable(null);
         }
 
-        if (mMobile != null) {
-            mMobile.setImageDrawable(null);
-        }
-
-        if (mMobileType != null) {
-            mMobileType.setImageDrawable(null);
+        for (PhoneState state : mPhoneStates) {
+            if (state.mMobile != null) {
+                state.mMobile.setImageDrawable(null);
+            }
+            if (state.mMobileType != null) {
+                state.mMobileType.setImageDrawable(null);
+            }
         }
 
         if(mAirplane != null) {
@@ -227,19 +276,17 @@
                     (mWifiVisible ? "VISIBLE" : "GONE"),
                     mWifiStrengthId));
 
-        if (mMobileVisible && !mIsAirplaneMode) {
-            mMobile.setImageResource(mMobileStrengthId);
-            mMobileType.setImageResource(mMobileTypeId);
-            mMobileGroup.setContentDescription(mMobileTypeDescription + " " + mMobileDescription);
-            mMobileGroup.setVisibility(View.VISIBLE);
-        } else {
-            mMobileGroup.setVisibility(View.GONE);
+        boolean anyMobileVisible = false;
+        for (PhoneState state : mPhoneStates) {
+            if (state.apply(anyMobileVisible)) {
+                anyMobileVisible = true;
+            }
         }
 
         if (mIsAirplaneMode) {
             mAirplane.setImageResource(mAirplaneIconId);
             mAirplane.setContentDescription(mAirplaneContentDescription != 0 ?
-                    mContext.getString(mAirplaneContentDescription) : "");
+                    mContext.getString(mAirplaneContentDescription) : null);
             mAirplane.setVisibility(View.VISIBLE);
         } else {
             mAirplane.setVisibility(View.GONE);
@@ -251,23 +298,73 @@
             mWifiAirplaneSpacer.setVisibility(View.GONE);
         }
 
-        if (mMobileVisible && mMobileTypeId != 0 && mWifiVisible) {
+        if ((anyMobileVisible || mNoSimsVisible) && mWifiVisible) {
             mWifiSignalSpacer.setVisibility(View.VISIBLE);
         } else {
             mWifiSignalSpacer.setVisibility(View.GONE);
         }
 
-        mMobile.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0, 0, 0, 0);
+        mNoSims.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE);
 
-        if (DEBUG) Log.d(TAG,
-                String.format("mobile: %s sig=%d typ=%d",
-                    (mMobileVisible ? "VISIBLE" : "GONE"),
-                    mMobileStrengthId, mMobileTypeId));
-
-        mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
-
-        boolean anythingVisible = mWifiVisible || mIsAirplaneMode || mMobileVisible || mVpnVisible;
+        boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode
+                || anyMobileVisible || mVpnVisible;
         setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
     }
+
+    private class PhoneState {
+        private final int mSubId;
+        private boolean mMobileVisible = false;
+        private int mMobileStrengthId = 0, mMobileTypeId = 0;
+        private boolean mIsMobileTypeIconWide;
+        private String mMobileDescription, mMobileTypeDescription;
+
+        private ViewGroup mMobileGroup;
+        private ImageView mMobile, mMobileType;
+
+        public PhoneState(int subId, Context context) {
+            ViewGroup root = (ViewGroup) LayoutInflater.from(context)
+                    .inflate(R.layout.mobile_signal_group, null);
+            setViews(root);
+            mSubId = subId;
+        }
+
+        public void setViews(ViewGroup root) {
+            mMobileGroup    = root;
+            mMobile         = (ImageView) root.findViewById(R.id.mobile_signal);
+            mMobileType     = (ImageView) root.findViewById(R.id.mobile_type);
+        }
+
+        public boolean apply(boolean isSecondaryIcon) {
+            if (mMobileVisible && !mIsAirplaneMode) {
+                mMobile.setImageResource(mMobileStrengthId);
+                mMobileType.setImageResource(mMobileTypeId);
+                mMobileGroup.setContentDescription(mMobileTypeDescription
+                        + " " + mMobileDescription);
+                mMobileGroup.setVisibility(View.VISIBLE);
+            } else {
+                mMobileGroup.setVisibility(View.GONE);
+            }
+
+            // When this isn't next to wifi, give it some extra padding between the signals.
+            mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0,
+                    0, 0, 0);
+            mMobile.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0,
+                    0, 0, 0);
+
+            if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
+                        (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
+
+            mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
+
+            return mMobileVisible;
+        }
+
+        public void populateAccessibilityEvent(AccessibilityEvent event) {
+            if (mMobileVisible && mMobileGroup != null
+                    && mMobileGroup.getContentDescription() != null) {
+                event.getText().add(mMobileGroup.getContentDescription());
+            }
+        }
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index 15f6dc2..7ec84da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -60,9 +60,13 @@
     @Override
     public void transitionTo(int mode, boolean animate) {
         mRequestedMode = mode;
-        if (mVertical && (mode == MODE_TRANSLUCENT || mode == MODE_TRANSPARENT)) {
+        if (mVertical) {
             // translucent mode not allowed when vertical
-            mode = MODE_OPAQUE;
+            if (mode == MODE_TRANSLUCENT || mode == MODE_TRANSPARENT) {
+                mode = MODE_OPAQUE;
+            } else if (mode == MODE_LIGHTS_OUT_TRANSPARENT) {
+                mode = MODE_LIGHTS_OUT;
+            }
         }
         super.transitionTo(mode, animate);
     }
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 4fb505e..7a3b5e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -851,14 +851,6 @@
                     }
                 }
             });
-
-            // set up the dynamic hide/show of the label
-            // TODO: uncomment, handle this for the Stack scroller aswell
-//                ((NotificationRowLayout) mStackScroller)
-// .setOnSizeChangedListener(new OnSizeChangedListener() {
-//                @Override
-//                public void onSizeChanged(View view, int w, int h, int oldw, int oldh) {
-//                    updateCarrierLabelVisibility(false);
         }
 
         mFlashlightController = new FlashlightController(mContext);
@@ -922,7 +914,7 @@
             filter.addAction("fake_artwork");
         }
         filter.addAction(ACTION_DEMO);
-        context.registerReceiver(mBroadcastReceiver, filter);
+        context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
 
         // listen for USER_SETUP_COMPLETE setting (per-user)
         resetUserSetupObserver();
@@ -3096,12 +3088,14 @@
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
             String action = intent.getAction();
             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
-                int flags = CommandQueue.FLAG_EXCLUDE_NONE;
-                String reason = intent.getStringExtra("reason");
-                if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
-                    flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
+                if (isCurrentProfile(getSendingUserId())) {
+                    int flags = CommandQueue.FLAG_EXCLUDE_NONE;
+                    String reason = intent.getStringExtra("reason");
+                    if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
+                        flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL;
+                    }
+                    animateCollapsePanels(flags);
                 }
-                animateCollapsePanels(flags);
             }
             else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                 mScreenOn = false;
@@ -3801,6 +3795,16 @@
      * @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.
+            }
+        }
         mState = state;
         mStatusBarWindowManager.setStatusBarState(state);
     }
@@ -4132,6 +4136,9 @@
         private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
         private final H mHandler = new H();
 
+        // Keeps the last reported state by fireNotificationLight.
+        private boolean mNotificationLightOn;
+
         @Override
         public String toString() {
             return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
@@ -4150,6 +4157,7 @@
         }
 
         public void fireNotificationLight(boolean on) {
+            mNotificationLightOn = on;
             for (Callback callback : mCallbacks) {
                 callback.onNotificationLight(on);
             }
@@ -4191,6 +4199,11 @@
             return mBatteryController != null && mBatteryController.isPowerSave();
         }
 
+        @Override
+        public boolean isNotificationLightOn() {
+            return mNotificationLightOn;
+        }
+
         private void handleStartDozing(@NonNull Runnable ready) {
             if (!mDozing) {
                 mDozing = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java
index b7c74e3..63fcbc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityContentDescriptions.java
@@ -33,5 +33,6 @@
         R.string.accessibility_wifi_three_bars,
         R.string.accessibility_wifi_signal_full
     };
+
     static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java
index 20f0a83..30da9cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileDataControllerImpl.java
@@ -33,6 +33,7 @@
 import android.net.NetworkTemplate;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.format.DateUtils;
 import android.text.format.Time;
@@ -213,7 +214,8 @@
 
     private static String getActiveSubscriberId(Context context) {
         final TelephonyManager tele = TelephonyManager.from(context);
-        final String actualSubscriberId = tele.getSubscriberId();
+        final String actualSubscriberId = tele.getSubscriberId(
+                SubscriptionManager.getDefaultDataSubId());
         return actualSubscriberId;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index b024f58..58bf246 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -32,8 +32,9 @@
         void onMobileDataSignalChanged(boolean enabled, int mobileSignalIconId,
                 String mobileSignalContentDescriptionId, int dataTypeIconId,
                 boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description, boolean noSim,
+                String dataTypeContentDescriptionId, String description,
                 boolean isDataTypeIconWide);
+        void onNoSimVisibleChanged(boolean visible);
         void onAirplaneModeChanged(boolean enabled);
         void onMobileDataEnabled(boolean enabled);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f3a04b6..5e71047 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -35,12 +35,17 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.cdma.EriInfo;
 import com.android.internal.util.AsyncChannel;
@@ -50,6 +55,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -64,9 +71,9 @@
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     // additional diagnostics, but not logspew
     static final boolean CHATTY =  Log.isLoggable(TAG + ".Chat", Log.DEBUG);
-    // Save the previous states of all SignalController state info.
+    // Save the previous SignalController.States of all SignalControllers for dumps.
     static final boolean RECORD_HISTORY = true;
-    // How many to save, must be a power of 2.
+    // If RECORD_HISTORY how many to save, must be a power of 2.
     static final int HISTORY_SIZE = 16;
 
     private static final int INET_CONDITION_THRESHOLD = 50;
@@ -75,13 +82,19 @@
     private final TelephonyManager mPhone;
     private final WifiManager mWifiManager;
     private final ConnectivityManager mConnectivityManager;
+    private final SubscriptionManager mSubscriptionManager;
     private final boolean mHasMobileDataFeature;
+    private final Config mConfig;
 
     // Subcontrollers.
     @VisibleForTesting
     final WifiSignalController mWifiSignalController;
     @VisibleForTesting
-    final MobileSignalController mMobileSignalController;
+    final Map<Integer, MobileSignalController> mMobileSignalControllers =
+            new HashMap<Integer, MobileSignalController>();
+    // When no SIMs are around at setup, and one is added later, it seems to default to the first
+    // SIM for most actions.  This may be null if there aren't any SIMs around.
+    private MobileSignalController mDefaultSignalController;
     private final AccessPointControllerImpl mAccessPoints;
     private final MobileDataControllerImpl mMobileDataController;
 
@@ -97,7 +110,11 @@
 
     // States that don't belong to a subcontroller.
     private boolean mAirplaneMode = false;
+    private boolean mHasNoSims;
     private Locale mLocale = null;
+    // This list holds our ordering.
+    private List<SubscriptionInfo> mCurrentSubscriptions
+            = new ArrayList<SubscriptionInfo>();
 
     // All the callbacks.
     private ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<EmergencyListener>();
@@ -106,6 +123,7 @@
     private ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>();
     private ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks =
             new ArrayList<NetworkSignalChangedCallback>();
+    private boolean mListening;
 
     /**
      * Construct this controller object and register for updates.
@@ -114,18 +132,21 @@
         this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
                 (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
-                Config.readConfig(context), new AccessPointControllerImpl(context),
-                new MobileDataControllerImpl(context));
+                SubscriptionManager.from(context), Config.readConfig(context),
+                new AccessPointControllerImpl(context), new MobileDataControllerImpl(context));
         registerListeners();
     }
 
     @VisibleForTesting
     NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
-            TelephonyManager telephonyManager, WifiManager wifiManager, Config config,
+            TelephonyManager telephonyManager, WifiManager wifiManager,
+            SubscriptionManager subManager, Config config,
             AccessPointControllerImpl accessPointController,
             MobileDataControllerImpl mobileDataController) {
         mContext = context;
+        mConfig = config;
 
+        mSubscriptionManager = subManager;
         mConnectivityManager = connectivityManager;
         mHasMobileDataFeature =
                 mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
@@ -149,16 +170,17 @@
         });
         mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
                 mSignalsChangedCallbacks, mSignalClusters, this);
-        mMobileSignalController = new MobileSignalController(mContext, config,
-                mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, mSignalClusters, this);
 
         // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
-        updateAirplaneMode(true);
+        updateAirplaneMode(true /* force callback */);
         mAccessPoints.setNetworkController(this);
     }
 
     private void registerListeners() {
-        mMobileSignalController.registerListener();
+        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+            mobileSignalController.registerListener();
+        }
+        mSubscriptionManager.registerOnSubscriptionsChangedListener(mSubscriptionListener);
 
         // broadcasts
         IntentFilter filter = new IntentFilter();
@@ -166,16 +188,25 @@
         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+        filter.addAction(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
         filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE);
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         mContext.registerReceiver(this, filter);
+        mListening = true;
+
+        updateMobileControllers();
     }
 
     private void unregisterListeners() {
-        mMobileSignalController.unregisterListener();
+        mListening = false;
+        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+            mobileSignalController.unregisterListener();
+        }
+        mSubscriptionManager.unregisterOnSubscriptionsChangedListener(mSubscriptionListener);
         mContext.unregisterReceiver(this);
     }
 
@@ -195,7 +226,7 @@
 
     public void addEmergencyListener(EmergencyListener listener) {
         mEmergencyListeners.add(listener);
-        refreshCarrierLabel();
+        listener.setEmergencyCallsOnly(isEmergencyOnly());
     }
 
     public void addCarrierLabel(CarrierLabelListener listener) {
@@ -204,7 +235,7 @@
     }
 
     private void notifyMobileDataEnabled(boolean enabled) {
-        int length = mSignalsChangedCallbacks.size();
+        final int length = mSignalsChangedCallbacks.size();
         for (int i = 0; i < length; i++) {
             mSignalsChangedCallbacks.get(i).onMobileDataEnabled(enabled);
         }
@@ -218,12 +249,39 @@
         return mPhone.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;
     }
 
+    private MobileSignalController getDataController() {
+        int dataSubId = SubscriptionManager.getDefaultDataSubId();
+        if (dataSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            if (DEBUG) Log.e(TAG, "No data sim selected");
+            return mDefaultSignalController;
+        }
+        if (mMobileSignalControllers.containsKey(dataSubId)) {
+            return mMobileSignalControllers.get(dataSubId);
+        }
+        Log.e(TAG, "Cannot find controller for data sub: " + dataSubId);
+        return mDefaultSignalController;
+    }
+
     public String getMobileNetworkName() {
-        return mMobileSignalController.mCurrentState.networkName;
+        MobileSignalController controller = getDataController();
+        return controller != null ? controller.getState().networkName : "";
     }
 
     public boolean isEmergencyOnly() {
-        return mMobileSignalController.isEmergencyOnly();
+        int voiceSubId = SubscriptionManager.getDefaultVoiceSubId();
+        if (voiceSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+                if (!mobileSignalController.isEmergencyOnly()) {
+                    return false;
+                }
+            }
+        }
+        if (mMobileSignalControllers.containsKey(voiceSubId)) {
+            return mMobileSignalControllers.get(voiceSubId).isEmergencyOnly();
+        }
+        Log.e(TAG, "Cannot find controller for voice sub: " + voiceSubId);
+        // Something is wrong, better assume we can't make calls...
+        return true;
     }
 
     /**
@@ -232,26 +290,35 @@
      */
     void recalculateEmergency() {
         final boolean emergencyOnly = isEmergencyOnly();
-
-        int length = mEmergencyListeners.size();
+        final int length = mEmergencyListeners.size();
         for (int i = 0; i < length; i++) {
             mEmergencyListeners.get(i).setEmergencyCallsOnly(emergencyOnly);
         }
+        // If the emergency has a chance to change, then so does the carrier
+        // label.
+        refreshCarrierLabel();
     }
 
     public void addSignalCluster(SignalCluster cluster) {
         mSignalClusters.add(cluster);
+        cluster.setSubs(mCurrentSubscriptions);
         cluster.setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
                 R.string.accessibility_airplane_mode);
+        cluster.setNoSims(mHasNoSims);
         mWifiSignalController.notifyListeners();
-        mMobileSignalController.notifyListeners();
+        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+            mobileSignalController.notifyListeners();
+        }
     }
 
     public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
         mSignalsChangedCallbacks.add(cb);
         cb.onAirplaneModeChanged(mAirplaneMode);
+        cb.onNoSimVisibleChanged(mHasNoSims);
         mWifiSignalController.notifyListeners();
-        mMobileSignalController.notifyListeners();
+        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+            mobileSignalController.notifyListeners();
+        }
     }
 
     public void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
@@ -293,9 +360,120 @@
             refreshLocale();
             updateAirplaneMode(false);
             refreshCarrierLabel();
+        } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)) {
+            // We are using different subs now, we might be able to make calls.
+            recalculateEmergency();
+        } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
+            // Notify every MobileSignalController so they can know whether they are the
+            // data sim or not.
+            for (MobileSignalController controller : mMobileSignalControllers.values()) {
+                controller.handleBroadcast(intent);
+            }
+        } else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
+            // Might have different subscriptions now.
+            updateMobileControllers();
+        } else {
+            int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                if (mMobileSignalControllers.containsKey(subId)) {
+                    mMobileSignalControllers.get(subId).handleBroadcast(intent);
+                } else {
+                    // Can't find this subscription...  We must be out of date.
+                    updateMobileControllers();
+                }
+            } else {
+                // No sub id, must be for the wifi.
+                mWifiSignalController.handleBroadcast(intent);
+            }
         }
-        mWifiSignalController.handleBroadcast(intent);
-        mMobileSignalController.handleBroadcast(intent);
+    }
+
+    private void updateMobileControllers() {
+        if (!mListening) {
+            return;
+        }
+        List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
+        // If there have been no relevant changes to any of the subscriptions, we can leave as is.
+        if (hasCorrectMobileControllers(subscriptions)) {
+            // Even if the controllers are correct, make sure we have the right no sims state.
+            // Such as on boot, don't need any controllers, because there are no sims,
+            // but we still need to update the no sim state.
+            updateNoSims();
+            return;
+        }
+        setCurrentSubscriptions(subscriptions);
+        updateNoSims();
+    }
+
+    private void updateNoSims() {
+        boolean hasNoSims = mPhone.getPhoneCount() != 0 && mMobileSignalControllers.size() == 0;
+        if (hasNoSims != mHasNoSims) {
+            mHasNoSims = hasNoSims;
+            notifyListeners();
+        }
+    }
+
+    @VisibleForTesting
+    void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {
+        Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
+            @Override
+            public int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) {
+                return lhs.getSimSlotIndex() == rhs.getSimSlotIndex()
+                        ? lhs.getSubscriptionId() - rhs.getSubscriptionId()
+                        : lhs.getSimSlotIndex() - rhs.getSimSlotIndex();
+            }
+        });
+        final int length = mSignalClusters.size();
+        for (int i = 0; i < length; i++) {
+            mSignalClusters.get(i).setSubs(subscriptions);
+        }
+        mCurrentSubscriptions = subscriptions;
+
+        HashMap<Integer, MobileSignalController> cachedControllers =
+                new HashMap<Integer, MobileSignalController>(mMobileSignalControllers);
+        mMobileSignalControllers.clear();
+        final int num = subscriptions.size();
+        for (int i = 0; i < num; i++) {
+            int subId = subscriptions.get(i).getSubscriptionId();
+            // If we have a copy of this controller already reuse it, otherwise make a new one.
+            if (cachedControllers.containsKey(subId)) {
+                mMobileSignalControllers.put(subId, cachedControllers.get(subId));
+            } else {
+                MobileSignalController controller = new MobileSignalController(mContext, mConfig,
+                        mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, mSignalClusters,
+                        this, subscriptions.get(i));
+                mMobileSignalControllers.put(subId, controller);
+                if (subscriptions.get(i).getSimSlotIndex() == 0) {
+                    mDefaultSignalController = controller;
+                }
+                if (mListening) {
+                    controller.registerListener();
+                }
+            }
+        }
+        if (mListening) {
+            for (Integer key : cachedControllers.keySet()) {
+                if (cachedControllers.get(key) == mDefaultSignalController) {
+                    mDefaultSignalController = null;
+                }
+                cachedControllers.get(key).unregisterListener();
+            }
+        }
+    }
+
+    private boolean hasCorrectMobileControllers(List<SubscriptionInfo> allSubscriptions) {
+        if (allSubscriptions == null) {
+            // If null then the system doesn't know the subscriptions yet, instead just wait
+            // to update the MobileControllers until it knows the state.
+            return true;
+        }
+        for (SubscriptionInfo info : allSubscriptions) {
+            if (!mMobileSignalControllers.containsKey(info.getSubscriptionId())) {
+                return false;
+            }
+        }
+        return true;
     }
 
     private void updateAirplaneMode(boolean force) {
@@ -303,15 +481,17 @@
                 Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
         if (airplaneMode != mAirplaneMode || force) {
             mAirplaneMode = airplaneMode;
-            mMobileSignalController.setAirplaneMode(mAirplaneMode);
-            notifyAirplaneCallbacks();
+            for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+                mobileSignalController.setAirplaneMode(mAirplaneMode);
+            }
+            notifyListeners();
             refreshCarrierLabel();
         }
     }
 
     private void refreshLocale() {
         Locale current = mContext.getResources().getConfiguration().locale;
-        if (current.equals(mLocale)) {
+        if (!current.equals(mLocale)) {
             mLocale = current;
             notifyAllListeners();
         }
@@ -319,30 +499,40 @@
 
     /**
      * Turns inet condition into a boolean indexing for a specific network.
-     * returns 0 for bad connectivity on this network.
-     * returns 1 for good connectivity on this network.
+     * @return 0 for bad connectivity on this network, 1 for good connectivity
      */
     private int inetConditionForNetwork(int networkType, boolean inetCondition) {
         return (inetCondition && mConnectedNetworkType == networkType) ? 1 : 0;
     }
 
+    /**
+     * Forces update of all callbacks on both SignalClusters and
+     * NetworkSignalChangedCallbacks.
+     */
     private void notifyAllListeners() {
-        // Something changed, trigger everything!
-        notifyAirplaneCallbacks();
-        mMobileSignalController.notifyListeners();
+        notifyListeners();
+        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+            mobileSignalController.notifyListeners();
+        }
         mWifiSignalController.notifyListeners();
     }
 
-    private void notifyAirplaneCallbacks() {
+    /**
+     * Notifies listeners of changes in state of to the NetworkController, but
+     * does not notify for any info on SignalControllers, for that call
+     * notifyAllListeners.
+     */
+    private void notifyListeners() {
         int length = mSignalClusters.size();
         for (int i = 0; i < length; i++) {
             mSignalClusters.get(i).setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
                     R.string.accessibility_airplane_mode);
+            mSignalClusters.get(i).setNoSims(mHasNoSims);
         }
-        // update QS
         int signalsChangedLength = mSignalsChangedCallbacks.size();
         for (int i = 0; i < signalsChangedLength; i++) {
             mSignalsChangedCallbacks.get(i).onAirplaneModeChanged(mAirplaneMode);
+            mSignalsChangedCallbacks.get(i).onNoSimVisibleChanged(mHasNoSims);
         }
     }
 
@@ -378,8 +568,10 @@
         }
 
         // We want to update all the icons, all at once, for any condition change
-        mMobileSignalController.setInetCondition(mInetCondition ? 1 : 0,
-                inetConditionForNetwork(mMobileSignalController.getNetworkType(), mInetCondition));
+        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+            mobileSignalController.setInetCondition(mInetCondition ? 1 : 0, inetConditionForNetwork(
+                    mobileSignalController.getNetworkType(), mInetCondition));
+        }
         mWifiSignalController.setInetCondition(
                 inetConditionForNetwork(mWifiSignalController.getNetworkType(), mInetCondition));
     }
@@ -391,8 +583,10 @@
         Context context = mContext;
 
         WifiSignalController.WifiState wifiState = mWifiSignalController.getState();
-        MobileSignalController.MobileState mobileState = mMobileSignalController.getState();
-        String label = mMobileSignalController.getLabel("", mConnected, mHasMobileDataFeature);
+        String label = "";
+        for (MobileSignalController controller : mMobileSignalControllers.values()) {
+            label = controller.getLabel(label, mConnected, mHasMobileDataFeature);
+        }
 
         // TODO Simplify this ugliness, some of the flows below shouldn't be possible anymore
         // but stay for the sake of history.
@@ -406,7 +600,7 @@
             label = context.getString(R.string.ethernet_label);
         }
 
-        if (mAirplaneMode && (!mobileState.connected && !mobileState.isEmergency)) {
+        if (mAirplaneMode && !isEmergencyOnly()) {
             // combined values from connected wifi take precedence over airplane mode
             if (wifiState.connected && mHasMobileDataFeature) {
                 // Suppress "No internet connection." from mobile if wifi connected.
@@ -417,7 +611,7 @@
                               R.string.status_bar_settings_signal_meter_disconnected);
                  }
             }
-        } else if (!mobileState.dataConnected && !wifiState.connected && !mBluetoothTethered &&
+        } else if (!isMobileDataConnected() && !wifiState.connected && !mBluetoothTethered &&
                  !ethernetConnected && !mHasMobileDataFeature) {
             // Pretty much no connection.
             label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
@@ -432,6 +626,11 @@
         }
     }
 
+    private boolean isMobileDataConnected() {
+        MobileSignalController controller = getDataController();
+        return controller != null ? controller.getState().dataConnected : false;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NetworkController state:");
         pw.println(String.format("  %s network type %d (%s)",
@@ -453,14 +652,15 @@
         pw.print("  mLocale=");
         pw.println(mLocale);
 
-        mMobileSignalController.dump(pw);
+        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
+            mobileSignalController.dump(pw);
+        }
         mWifiSignalController.dump(pw);
     }
 
     private boolean mDemoMode;
     private int mDemoInetCondition;
     private WifiSignalController.WifiState mDemoWifiState;
-    private MobileSignalController.MobileState mDemoMobileState;
 
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
@@ -470,12 +670,16 @@
             mDemoMode = true;
             mDemoInetCondition = mInetCondition ? 1 : 0;
             mDemoWifiState = mWifiSignalController.getState();
-            mDemoMobileState = mMobileSignalController.getState();
         } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
             if (DEBUG) Log.d(TAG, "Exiting demo mode");
             mDemoMode = false;
+            // Update what MobileSignalControllers, because they may change
+            // to set the number of sim slots.
+            updateMobileControllers();
+            for (MobileSignalController controller : mMobileSignalControllers.values()) {
+                controller.resetLastState();
+            }
             mWifiSignalController.resetLastState();
-            mMobileSignalController.resetLastState();
             registerListeners();
             notifyAllListeners();
             refreshCarrierLabel();
@@ -493,7 +697,9 @@
             if (fully != null) {
                 mDemoInetCondition = Boolean.parseBoolean(fully) ? 1 : 0;
                 mWifiSignalController.setInetCondition(mDemoInetCondition);
-                mMobileSignalController.setInetCondition(mDemoInetCondition, mDemoInetCondition);
+                for (MobileSignalController controller : mMobileSignalControllers.values()) {
+                    controller.setInetCondition(mDemoInetCondition, mDemoInetCondition);
+                }
             }
             String wifi = args.getString("wifi");
             if (wifi != null) {
@@ -507,12 +713,47 @@
                 mDemoWifiState.enabled = show;
                 mWifiSignalController.notifyListeners();
             }
+            String sims = args.getString("sims");
+            if (sims != null) {
+                int num = Integer.parseInt(sims);
+                List<SubscriptionInfo> subs = new ArrayList<SubscriptionInfo>();
+                if (num != mMobileSignalControllers.size()) {
+                    mMobileSignalControllers.clear();
+                    int start = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
+                    for (int i = start /* get out of normal index range */; i < start + num; i++) {
+                        SubscriptionInfo info = new SubscriptionInfo(i, "", i, "", "", 0, 0, "", 0,
+                                null, 0, 0, "");
+                        subs.add(info);
+                        mMobileSignalControllers.put(i, new MobileSignalController(mContext,
+                                mConfig, mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks,
+                                mSignalClusters, this, info));
+                    }
+                }
+                final int n = mSignalClusters.size();
+                for (int i = 0; i < n; i++) {
+                    mSignalClusters.get(i).setSubs(subs);
+                }
+            }
+            String nosim = args.getString("nosim");
+            if (nosim != null) {
+                boolean show = nosim.equals("show");
+                final int n = mSignalClusters.size();
+                for (int i = 0; i < n; i++) {
+                    mSignalClusters.get(i).setNoSims(show);
+                }
+            }
             String mobile = args.getString("mobile");
             if (mobile != null) {
                 boolean show = mobile.equals("show");
                 String datatype = args.getString("datatype");
+                String slotString = args.getString("slot");
+                int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
+                // Hack to index linearly for easy use.
+                MobileSignalController controller = mMobileSignalControllers
+                        .values().toArray(new MobileSignalController[0])[slot];
+                controller.getState().dataSim = datatype != null;
                 if (datatype != null) {
-                    mDemoMobileState.iconGroup =
+                    controller.getState().iconGroup =
                             datatype.equals("1x") ? TelephonyIcons.ONE_X :
                             datatype.equals("3g") ? TelephonyIcons.THREE_G :
                             datatype.equals("4g") ? TelephonyIcons.FOUR_G :
@@ -526,17 +767,25 @@
                 int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH;
                 String level = args.getString("level");
                 if (level != null) {
-                    mDemoMobileState.level = level.equals("null") ? -1
+                    controller.getState().level = level.equals("null") ? -1
                             : Math.min(Integer.parseInt(level), icons[0].length - 1);
-                    mDemoMobileState.connected = mDemoMobileState.level >= 0;
+                    controller.getState().connected = controller.getState().level >= 0;
                 }
-                mDemoMobileState.enabled = show;
-                mMobileSignalController.notifyListeners();
+                controller.getState().enabled = show;
+                controller.notifyListeners();
             }
             refreshCarrierLabel();
         }
     }
 
+    private final OnSubscriptionsChangedListener mSubscriptionListener =
+            new OnSubscriptionsChangedListener() {
+        public void onSubscriptionInfoChanged() {
+            updateMobileControllers();
+        };
+    };
+
+    // TODO: Move to its own file.
     static class WifiSignalController extends
             SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
         private final WifiManager mWifiManager;
@@ -571,20 +820,17 @@
         }
 
         @Override
-        public WifiState cleanState() {
+        protected WifiState cleanState() {
             return new WifiState();
         }
 
-        /**
-         * {@inheritDoc}
-         */
         @Override
         public void notifyListeners() {
             // only show wifi in the cluster if connected or if wifi-only
-            boolean wifiEnabled = mCurrentState.enabled
+            boolean wifiVisible = mCurrentState.enabled
                     && (mCurrentState.connected || !mHasMobileData);
-            String wifiDesc = wifiEnabled ? mCurrentState.ssid : null;
-            boolean ssidPresent = wifiEnabled && mCurrentState.ssid != null;
+            String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
+            boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
             String contentDescription = getStringIfExists(getContentDescription());
             int length = mSignalsChangedCallbacks.size();
             for (int i = 0; i < length; i++) {
@@ -596,10 +842,8 @@
 
             int signalClustersLength = mSignalClusters.size();
             for (int i = 0; i < signalClustersLength; i++) {
-                mSignalClusters.get(i).setWifiIndicators(
-                        // only show wifi in the cluster if connected or if wifi-only
-                        mCurrentState.enabled && (mCurrentState.connected || !mHasMobileData),
-                        getCurrentIconId(), contentDescription);
+                mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(),
+                        contentDescription);
             }
         }
 
@@ -622,7 +866,7 @@
                             ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
                             : mWifiManager.getConnectionInfo();
                     if (info != null) {
-                        mCurrentState.ssid = huntForSsid(info);
+                        mCurrentState.ssid = getSsid(info);
                     } else {
                         mCurrentState.ssid = null;
                     }
@@ -630,6 +874,7 @@
                     mCurrentState.ssid = null;
                 }
             } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+                // Default to -200 as its below WifiManager.MIN_RSSI.
                 mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
                 mCurrentState.level = WifiManager.calculateSignalLevel(
                         mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT);
@@ -638,7 +883,7 @@
             notifyListenersIfNecessary();
         }
 
-        private String huntForSsid(WifiInfo info) {
+        private String getSsid(WifiInfo info) {
             String ssid = info.getSSID();
             if (ssid != null) {
                 return ssid;
@@ -693,15 +938,15 @@
 
             @Override
             public void copyFrom(State s) {
+                super.copyFrom(s);
                 WifiState state = (WifiState) s;
                 ssid = state.ssid;
-                super.copyFrom(s);
             }
 
             @Override
             protected void toString(StringBuilder builder) {
-                builder.append("ssid=").append(ssid).append(',');
                 super.toString(builder);
+                builder.append(',').append("ssid=").append(ssid);
             }
 
             @Override
@@ -712,12 +957,17 @@
         }
     }
 
+    // TODO: Move to its own file.
     static class MobileSignalController extends SignalController<MobileSignalController.MobileState,
             MobileSignalController.MobileIconGroup> {
         private final Config mConfig;
         private final TelephonyManager mPhone;
         private final String mNetworkNameDefault;
         private final String mNetworkNameSeparator;
+        @VisibleForTesting
+        final PhoneStateListener mPhoneStateListener;
+        // Save entire info for logging, we only use the id.
+        private final SubscriptionInfo mSubscriptionInfo;
 
         // @VisibleForDemoMode
         Map<Integer, MobileIconGroup> mNetworkToIconLookup;
@@ -736,11 +986,15 @@
         // need listener lists anymore.
         public MobileSignalController(Context context, Config config, boolean hasMobileData,
                 TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks,
-                List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
-            super("MobileSignalController", context, ConnectivityManager.TYPE_MOBILE,
-                    signalCallbacks, signalClusters, networkController);
+                List<SignalCluster> signalClusters, NetworkControllerImpl networkController,
+                SubscriptionInfo info) {
+            super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
+                    ConnectivityManager.TYPE_MOBILE, signalCallbacks, signalClusters,
+                    networkController);
             mConfig = config;
             mPhone = phone;
+            mSubscriptionInfo = info;
+            mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId());
             mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
             mNetworkNameDefault = getStringIfExists(
                     com.android.internal.R.string.lockscreen_carrier_default);
@@ -750,6 +1004,8 @@
             mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault;
             mLastState.enabled = mCurrentState.enabled = hasMobileData;
             mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons;
+            // Get initial data sim state.
+            updateDataSim();
         }
 
         /**
@@ -779,15 +1035,19 @@
                         mobileLabel = mCurrentState.networkName;
                     }
                 } else {
-                    mobileLabel = mContext
-                            .getString(R.string.status_bar_settings_signal_meter_disconnected);
+                    mobileLabel = mContext.getString(
+                            R.string.status_bar_settings_signal_meter_disconnected);
                 }
 
+                if (currentLabel.length() != 0) {
+                    currentLabel = currentLabel + mNetworkNameSeparator;
+                }
                 // Now for things that should only be shown when actually using mobile data.
                 if (isMobileLabel) {
-                    return mobileLabel;
+                    return currentLabel + mobileLabel;
                 } else {
-                    return mCurrentState.dataConnected ? mobileLabel : currentLabel;
+                    return currentLabel
+                            + (mCurrentState.dataConnected ? mobileLabel : currentLabel);
                 }
             }
         }
@@ -845,7 +1105,7 @@
             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
 
-            if (!mConfig.showAtLeastThreeGees) {
+            if (!mConfig.showAtLeast3G) {
                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
                         TelephonyIcons.UNKNOWN);
                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
@@ -881,31 +1141,31 @@
             }
         }
 
-        /**
-         * {@inheritDoc}
-         */
         @Override
         public void notifyListeners() {
             MobileIconGroup icons = getIcons();
 
             String contentDescription = getStringIfExists(getContentDescription());
             String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
-            int qsTypeIcon = icons.mQsDataType[mCurrentState.inetForNetwork];
-            int length = mSignalsChangedCallbacks.size();
-            for (int i = 0; i < length; i++) {
-                mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
-                        && !mCurrentState.isEmergency && !mCurrentState.airplaneMode,
-                        getQsCurrentIconId(), contentDescription,
-                        qsTypeIcon,
-                        mCurrentState.dataConnected && mCurrentState.activityIn,
-                        mCurrentState.dataConnected && mCurrentState.activityOut,
-                        dataContentDescription,
-                        mCurrentState.isEmergency ? null : mCurrentState.networkName,
-                        mCurrentState.noSim,
-                        // Only wide if actually showing something.
-                        icons.mIsWide && qsTypeIcon != 0);
+            // Only send data sim callbacks to QS.
+            if (mCurrentState.dataSim) {
+                int qsTypeIcon = mCurrentState.dataConnected ?
+                        icons.mQsDataType[mCurrentState.inetForNetwork] : 0;
+                int length = mSignalsChangedCallbacks.size();
+                for (int i = 0; i < length; i++) {
+                    mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
+                            && !mCurrentState.isEmergency && !mCurrentState.airplaneMode,
+                            getQsCurrentIconId(), contentDescription,
+                            qsTypeIcon,
+                            mCurrentState.dataConnected && mCurrentState.activityIn,
+                            mCurrentState.dataConnected && mCurrentState.activityOut,
+                            dataContentDescription,
+                            mCurrentState.isEmergency ? null : mCurrentState.networkName,
+                            // Only wide if actually showing something.
+                            icons.mIsWide && qsTypeIcon != 0);
+                }
             }
-            boolean showDataIcon = mCurrentState.inetForNetwork != 0
+            boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0
                     || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
             int typeIcon = showDataIcon ? icons.mDataType : 0;
             int signalClustersLength = mSignalClusters.size();
@@ -917,12 +1177,13 @@
                         contentDescription,
                         dataContentDescription,
                         // Only wide if actually showing something.
-                        icons.mIsWide && typeIcon != 0);
+                        icons.mIsWide && typeIcon != 0,
+                        mSubscriptionInfo.getSubscriptionId());
             }
         }
 
         @Override
-        public MobileState cleanState() {
+        protected MobileState cleanState() {
             return new MobileState();
         }
 
@@ -969,42 +1230,32 @@
 
         public void handleBroadcast(Intent intent) {
             String action = intent.getAction();
-            if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
-                String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
-                final String lockedReason =
-                        intent.getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
-                updateSimState(stateExtra, lockedReason);
-                updateTelephony();
-            } else if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
+            if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
                 updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false),
                         intent.getStringExtra(TelephonyIntents.EXTRA_SPN),
                         intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
                         intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
                 notifyListenersIfNecessary();
+            } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
+                updateDataSim();
             }
         }
 
-        /**
-         * Determines the current sim state, based on a TelephonyIntents.ACTION_SIM_STATE_CHANGED
-         * broadcast.
-         */
-        private final void updateSimState(String stateExtra, String lockedReason) {
-            if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
-                mSimState = IccCardConstants.State.ABSENT;
-            } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
-                mSimState = IccCardConstants.State.READY;
-            } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
-                if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
-                    mSimState = IccCardConstants.State.PIN_REQUIRED;
-                } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
-                    mSimState = IccCardConstants.State.PUK_REQUIRED;
-                } else {
-                    mSimState = IccCardConstants.State.NETWORK_LOCKED;
-                }
+        private void updateDataSim() {
+            int defaultDataSub = SubscriptionManager.getDefaultDataSubId();
+            if (SubscriptionManager.isValidSubId(defaultDataSub)) {
+                mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId();
             } else {
-                mSimState = IccCardConstants.State.UNKNOWN;
+                // There doesn't seem to be a data sim selected, however if
+                // there isn't a MobileSignalController with dataSim set, then
+                // QS won't get any callbacks and will be blank.  Instead
+                // lets just assume we are the data sim (which will basically
+                // show one at random) in QS until one is selected.  The user
+                // should pick one soon after, so we shouldn't be in this state
+                // for long.
+                mCurrentState.dataSim = true;
             }
-            if (DEBUG) Log.d(TAG, "updateSimState: mSimState=" + mSimState);
+            notifyListenersIfNecessary();
         }
 
         /**
@@ -1057,16 +1308,6 @@
             }
             mCurrentState.dataConnected = mCurrentState.connected
                     && mDataState == TelephonyManager.DATA_CONNECTED;
-            if (!isCdma()) {
-                if (mSimState == IccCardConstants.State.READY ||
-                        mSimState == IccCardConstants.State.UNKNOWN) {
-                    mCurrentState.noSim = false;
-                } else {
-                    mCurrentState.noSim = true;
-                    // No sim, no data.
-                    mCurrentState.dataConnected = false;
-                }
-            }
 
             if (isRoaming()) {
                 mCurrentState.iconGroup = TelephonyIcons.ROAMING;
@@ -1075,6 +1316,11 @@
                 mCurrentState.isEmergency = isEmergencyOnly();
                 mNetworkController.recalculateEmergency();
             }
+            // Fill in the network name if we think we have it.
+            if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null
+                    && mServiceState.getOperatorAlphaShort() != null) {
+                mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
+            }
             notifyListenersIfNecessary();
         }
 
@@ -1090,17 +1336,22 @@
         @Override
         public void dump(PrintWriter pw) {
             super.dump(pw);
+            pw.println("  mSubscription=" + mSubscriptionInfo + ",");
             pw.println("  mServiceState=" + mServiceState + ",");
             pw.println("  mSignalStrength=" + mSignalStrength + ",");
             pw.println("  mDataState=" + mDataState + ",");
             pw.println("  mDataNetType=" + mDataNetType + ",");
         }
 
-        PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        class MobilePhoneStateListener extends PhoneStateListener {
+            public MobilePhoneStateListener(int subId) {
+                super(subId);
+            }
+
             @Override
             public void onSignalStrengthsChanged(SignalStrength signalStrength) {
                 if (DEBUG) {
-                    Log.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength +
+                    Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength +
                             ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
                 }
                 mSignalStrength = signalStrength;
@@ -1110,7 +1361,7 @@
             @Override
             public void onServiceStateChanged(ServiceState state) {
                 if (DEBUG) {
-                    Log.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
+                    Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
                             + " dataState=" + state.getDataRegState());
                 }
                 mServiceState = state;
@@ -1120,7 +1371,7 @@
             @Override
             public void onDataConnectionStateChanged(int state, int networkType) {
                 if (DEBUG) {
-                    Log.d(TAG, "onDataConnectionStateChanged: state=" + state
+                    Log.d(mTag, "onDataConnectionStateChanged: state=" + state
                             + " type=" + networkType);
                 }
                 mDataState = state;
@@ -1131,7 +1382,7 @@
             @Override
             public void onDataActivity(int direction) {
                 if (DEBUG) {
-                    Log.d(TAG, "onDataActivity: direction=" + direction);
+                    Log.d(mTag, "onDataActivity: direction=" + direction);
                 }
                 setActivity(direction);
             }
@@ -1158,7 +1409,7 @@
 
         static class MobileState extends SignalController.State {
             String networkName;
-            boolean noSim;
+            boolean dataSim;
             boolean dataConnected;
             boolean isEmergency;
             boolean airplaneMode;
@@ -1166,32 +1417,33 @@
 
             @Override
             public void copyFrom(State s) {
+                super.copyFrom(s);
                 MobileState state = (MobileState) s;
-                noSim = state.noSim;
+                dataSim = state.dataSim;
                 networkName = state.networkName;
                 dataConnected = state.dataConnected;
                 inetForNetwork = state.inetForNetwork;
                 isEmergency = state.isEmergency;
                 airplaneMode = state.airplaneMode;
-                super.copyFrom(s);
             }
 
             @Override
             protected void toString(StringBuilder builder) {
-                builder.append("noSim=").append(noSim).append(',');
+                super.toString(builder);
+                builder.append(',');
+                builder.append("dataSim=").append(dataSim).append(',');
                 builder.append("networkName=").append(networkName).append(',');
                 builder.append("dataConnected=").append(dataConnected).append(',');
                 builder.append("inetForNetwork=").append(inetForNetwork).append(',');
                 builder.append("isEmergency=").append(isEmergency).append(',');
-                builder.append("airplaneMode=").append(airplaneMode).append(',');
-                super.toString(builder);
+                builder.append("airplaneMode=").append(airplaneMode);
             }
 
             @Override
             public boolean equals(Object o) {
                 return super.equals(o)
                         && Objects.equals(((MobileState) o).networkName, networkName)
-                        && ((MobileState) o).noSim == noSim
+                        && ((MobileState) o).dataSim == dataSim
                         && ((MobileState) o).dataConnected == dataConnected
                         && ((MobileState) o).isEmergency == isEmergency
                         && ((MobileState) o).airplaneMode == airplaneMode
@@ -1225,7 +1477,7 @@
         public SignalController(String tag, Context context, int type,
                 List<NetworkSignalChangedCallback> signalCallbacks,
                 List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
-            mTag = TAG + "::" + tag;
+            mTag = TAG + "." + tag;
             mNetworkController = networkController;
             mNetworkType = type;
             mContext = context;
@@ -1254,11 +1506,12 @@
             notifyListenersIfNecessary();
         }
 
-        // @VisibleForDemoMode
         /**
          * Used at the end of demo mode to clear out any ugly state that it has created.
          * Since we haven't had any callbacks, then isDirty will not have been triggered,
          * so we can just take the last good state directly from there.
+         *
+         * Used for demo mode.
          */
         void resetLastState() {
             mCurrentState.copyFrom(mLastState);
@@ -1281,7 +1534,7 @@
 
         public void saveLastState() {
             if (RECORD_HISTORY) {
-                recordLast();
+                recordLastState();
             }
             // Updates the current time.
             mCurrentState.time = System.currentTimeMillis();
@@ -1315,7 +1568,7 @@
         }
 
         /**
-         * Gets the content description for the signal based on current state of connected and
+         * Gets the content description id for the signal based on current state of connected and
          * level.
          */
         public int getContentDescription() {
@@ -1326,7 +1579,7 @@
             }
         }
 
-        protected void notifyListenersIfNecessary() {
+        public void notifyListenersIfNecessary() {
             if (isDirty()) {
                 saveLastState();
                 notifyListeners();
@@ -1349,7 +1602,7 @@
          * Saves the last state of any changes, so we can log the current
          * and last value of any state data.
          */
-        protected void recordLast() {
+        protected void recordLastState() {
             mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
         }
 
@@ -1381,7 +1634,7 @@
         /**
          * Generate a blank T.
          */
-        public abstract T cleanState();
+        protected abstract T cleanState();
 
         /*
          * Holds icons for a given state. Arrays are generally indexed as inet
@@ -1490,7 +1743,10 @@
         void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
 
         void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
-                String contentDescription, String typeContentDescription, boolean isTypeIconWide);
+                String contentDescription, String typeContentDescription, boolean isTypeIconWide,
+                int subId);
+        void setSubs(List<SubscriptionInfo> subs);
+        void setNoSims(boolean show);
 
         void setIsAirplaneMode(boolean is, int airplaneIcon, int contentDescription);
     }
@@ -1505,7 +1761,7 @@
 
     @VisibleForTesting
     static class Config {
-        boolean showAtLeastThreeGees = false;
+        boolean showAtLeast3G = false;
         boolean alwaysShowCdmaRssi = false;
         boolean show4gForLte = false;
         boolean hspaDataDistinguishable;
@@ -1514,7 +1770,7 @@
             Config config = new Config();
             Resources res = context.getResources();
 
-            config.showAtLeastThreeGees = res.getBoolean(R.bool.config_showMin3G);
+            config.showAtLeast3G = res.getBoolean(R.bool.config_showMin3G);
             config.alwaysShowCdmaRssi =
                     res.getBoolean(com.android.internal.R.bool.config_alwaysUseCdmaRssi);
             config.show4gForLte = res.getBoolean(R.bool.config_show4GForLTE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 49fe1e3..eaf2f78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -11,6 +11,8 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
 import android.util.Log;
@@ -25,6 +27,8 @@
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 public class NetworkControllerBaseTest extends AndroidTestCase {
     private static final String TAG = "NetworkControllerBaseTest";
@@ -44,6 +48,7 @@
     private ServiceState mServiceState;
     protected ConnectivityManager mMockCm;
     protected WifiManager mMockWm;
+    protected SubscriptionManager mMockSm;
     protected TelephonyManager mMockTm;
     protected Config mConfig;
 
@@ -56,6 +61,7 @@
 
         mMockWm = mock(WifiManager.class);
         mMockTm = mock(TelephonyManager.class);
+        mMockSm = mock(SubscriptionManager.class);
         mMockCm = mock(ConnectivityManager.class);
         when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
 
@@ -64,14 +70,22 @@
 
         mConfig = new Config();
         mConfig.hspaDataDistinguishable = true;
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                 mConfig, mock(AccessPointControllerImpl.class),
                 mock(MobileDataControllerImpl.class));
         setupNetworkController();
     }
 
     protected void setupNetworkController() {
-        mPhoneStateListener = mNetworkController.mMobileSignalController.mPhoneStateListener;
+        // For now just pretend to be the data sim, so we can test that too.
+        final int subId = SubscriptionManager.getDefaultDataSubId();
+        SubscriptionInfo subscription = mock(SubscriptionInfo.class);
+        List<SubscriptionInfo> subs = new ArrayList<SubscriptionInfo>();
+        when(subscription.getSubscriptionId()).thenReturn(subId);
+        subs.add(subscription);
+        mNetworkController.setCurrentSubscriptions(subs);
+        mPhoneStateListener =
+                mNetworkController.mMobileSignalControllers.get(subId).mPhoneStateListener;
         mSignalCluster = mock(SignalCluster.class);
         mNetworkSignalChangedCallback = mock(NetworkSignalChangedCallback.class);
         mNetworkController.addSignalCluster(mSignalCluster);
@@ -182,13 +196,12 @@
     }
 
     protected void verifyLastQsMobileDataIndicators(boolean visible, int icon, int typeIcon,
-            boolean dataIn, boolean dataOut, boolean noSim) {
+            boolean dataIn, boolean dataOut) {
         ArgumentCaptor<Integer> iconArg = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Boolean> visibleArg = ArgumentCaptor.forClass(Boolean.class);
         ArgumentCaptor<Boolean> dataInArg = ArgumentCaptor.forClass(Boolean.class);
         ArgumentCaptor<Boolean> dataOutArg = ArgumentCaptor.forClass(Boolean.class);
-        ArgumentCaptor<Boolean> noSimArg = ArgumentCaptor.forClass(Boolean.class);
 
         Mockito.verify(mNetworkSignalChangedCallback, Mockito.atLeastOnce())
                 .onMobileDataSignalChanged(visibleArg.capture(), iconArg.capture(),
@@ -198,7 +211,6 @@
                         dataOutArg.capture(),
                         ArgumentCaptor.forClass(String.class).capture(),
                         ArgumentCaptor.forClass(String.class).capture(),
-                        noSimArg.capture(),
                         ArgumentCaptor.forClass(Boolean.class).capture());
         assertEquals("Visibility in, quick settings", visible, (boolean) visibleArg.getValue());
         assertEquals("Signal icon in, quick settings", icon, (int) iconArg.getValue());
@@ -207,7 +219,6 @@
                 (boolean) dataInArg.getValue());
         assertEquals("Data direction out, in quick settings", dataOut,
                 (boolean) dataOutArg.getValue());
-        assertEquals("Sim state in quick settings", noSim, (boolean) noSimArg.getValue());
     }
 
     protected void verifyLastMobileDataIndicators(boolean visible, int icon, int typeIcon) {
@@ -220,7 +231,8 @@
                 visibleArg.capture(), iconArg.capture(), typeIconArg.capture(),
                 ArgumentCaptor.forClass(String.class).capture(),
                 ArgumentCaptor.forClass(String.class).capture(),
-                ArgumentCaptor.forClass(Boolean.class).capture());
+                ArgumentCaptor.forClass(Boolean.class).capture(),
+                ArgumentCaptor.forClass(Integer.class).capture());
 
         assertEquals("Signal icon in status bar", icon, (int) iconArg.getValue());
         assertEquals("Data icon in status bar", typeIcon, (int) typeIconArg.getValue());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 146e76d..e327233 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -22,7 +22,7 @@
                 TelephonyIcons.ROAMING_ICON);
         verifyLastQsMobileDataIndicators(true,
                 TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH[1][DEFAULT_LEVEL],
-                TelephonyIcons.QS_DATA_R[1], false, false, false);
+                TelephonyIcons.QS_DATA_R[1], false, false);
     }
 
     public void test2gDataIcon() {
@@ -86,14 +86,14 @@
 
         verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, DEFAULT_ICON);
         verifyLastQsMobileDataIndicators(true, DEFAULT_QS_SIGNAL_STRENGTH,
-                DEFAULT_QS_ICON, in, out, false);
+                DEFAULT_QS_ICON, in, out);
 
     }
 
     private void verifyDataIndicators(int dataIcon, int qsDataIcon) {
         verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, dataIcon);
         verifyLastQsMobileDataIndicators(true, DEFAULT_QS_SIGNAL_STRENGTH, qsDataIcon, false,
-                false, false);
+                false);
     }
 
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index bb2ff7c..b5d97d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -17,7 +17,7 @@
         // Turn off mobile network support.
         Mockito.when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
         // Create a new NetworkController as this is currently handled in constructor.
-        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+        mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
                 mConfig, mock(AccessPointControllerImpl.class),
                 mock(MobileDataControllerImpl.class));
         setupNetworkController();
@@ -90,7 +90,7 @@
 
             verifyLastQsMobileDataIndicators(true,
                     TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH[1][testStrength],
-                    DEFAULT_QS_ICON, false, false, false);
+                    DEFAULT_QS_ICON, false, false);
         }
     }
 
@@ -103,7 +103,7 @@
 
             verifyLastQsMobileDataIndicators(true,
                     TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH[1][testStrength],
-                    TelephonyIcons.QS_ICON_1X, false, false, false);
+                    TelephonyIcons.QS_ICON_1X, false, false);
         }
     }
 
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 6c2681b..6b8b5b1 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -7664,7 +7664,7 @@
 
             // If this was a full-system restore, record the ancestral
             // dataset information
-            if (mIsSystemRestore) {
+            if (mIsSystemRestore && mPmAgent != null) {
                 mAncestralPackages = mPmAgent.getRestoredPackages();
                 mAncestralToken = mToken;
                 writeRestoreTokens();
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 8b3739d..5eec0b7 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3593,8 +3593,14 @@
 //            updateMtu(lp, null);
 //        }
         updateTcpBufferSizes(networkAgent);
+
+        // TODO: deprecate and remove mDefaultDns when we can do so safely.
+        // For now, use it only when the network has Internet access. http://b/18327075
+        final boolean useDefaultDns = networkAgent.networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET);
         final boolean flushDns = updateRoutes(newLp, oldLp, netId);
-        updateDnses(newLp, oldLp, netId, flushDns);
+        updateDnses(newLp, oldLp, netId, flushDns, useDefaultDns);
+
         updateClat(newLp, oldLp, networkAgent);
         if (isDefaultNetwork(networkAgent)) handleApplyDefaultProxy(newLp.getHttpProxy());
         // TODO - move this check to cover the whole function
@@ -3688,10 +3694,11 @@
         }
         return !routeDiff.added.isEmpty() || !routeDiff.removed.isEmpty();
     }
-    private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId, boolean flush) {
+    private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId,
+                             boolean flush, boolean useDefaultDns) {
         if (oldLp == null || (newLp.isIdenticalDnses(oldLp) == false)) {
             Collection<InetAddress> dnses = newLp.getDnsServers();
-            if (dnses.size() == 0 && mDefaultDns != null) {
+            if (dnses.size() == 0 && mDefaultDns != null && useDefaultDns) {
                 dnses = new ArrayList();
                 dnses.add(mDefaultDns);
                 if (DBG) {
@@ -3768,8 +3775,8 @@
             int notificationType) {
         if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE) {
             Intent intent = new Intent();
-            intent.putExtra(ConnectivityManager.EXTRA_NETWORK, nri.request);
-            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, networkAgent.network);
+            intent.putExtra(ConnectivityManager.EXTRA_NETWORK, networkAgent.network);
+            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, nri.request);
             sendIntent(nri.mPendingIntent, intent);
         }
         // else not handled
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 8417ccc..f04487e 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -51,8 +51,8 @@
 # ---------------------------
 # NotificationManagerService.java
 # ---------------------------
-# when a NotificationManager.notify is called
-2750 notification_enqueue (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3),(update|1)
+# when a NotificationManager.notify is called. status: 0=post, 1=update, 2=ignored
+2750 notification_enqueue (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3),(status|1)
 # when someone tries to cancel a notification, the notification manager sometimes
 # calls this with flags too
 2751 notification_cancel (uid|1|5),(pid|1|5),(pkg|3),(id|1|5),(tag|3),(userid|1|5),(required_flags|1),(forbidden_flags|1),(reason|1|5),(listener|3)
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java
index 8c3b020..d2dfc7b 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/services/core/java/com/android/server/NativeDaemonConnector.java
@@ -174,6 +174,8 @@
 
                 for (int i = 0; i < count; i++) {
                     if (buffer[i] == 0) {
+                        // Note - do not log this raw message since it may contain
+                        // sensitive data
                         final String rawEvent = new String(
                                 buffer, start, i - start, StandardCharsets.UTF_8);
 
@@ -181,6 +183,9 @@
                         try {
                             final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
                                     rawEvent);
+
+                            log("RCV <- {" + event + "}");
+
                             if (event.isClassUnsolicited()) {
                                 // TODO: migrate to sending NativeDaemonEvent instances
                                 if (mCallbacks.onCheckHoldWakeLock(event.getCode())
@@ -196,6 +201,7 @@
                                 mResponseQueue.add(event.getCmdNumber(), event);
                             }
                         } catch (IllegalArgumentException e) {
+                            log("Problem parsing message " + e);
                         } finally {
                             if (releaseWl) {
                                 mWakeLock.acquire();
@@ -205,8 +211,9 @@
                         start = i + 1;
                     }
                 }
+
                 if (start == 0) {
-                    final String rawEvent = new String(buffer, start, count, StandardCharsets.UTF_8);
+                    log("RCV incomplete");
                 }
 
                 // We should end at the amount we read. If not, compact then
diff --git a/services/core/java/com/android/server/NativeDaemonEvent.java b/services/core/java/com/android/server/NativeDaemonEvent.java
index 59d50bd..4e61c0b 100644
--- a/services/core/java/com/android/server/NativeDaemonEvent.java
+++ b/services/core/java/com/android/server/NativeDaemonEvent.java
@@ -33,16 +33,21 @@
     private final int mCode;
     private final String mMessage;
     private final String mRawEvent;
+    private final String mLogMessage;
     private String[] mParsed;
 
-    private NativeDaemonEvent(int cmdNumber, int code, String message, String rawEvent) {
+    private NativeDaemonEvent(int cmdNumber, int code, String message,
+                              String rawEvent, String logMessage) {
         mCmdNumber = cmdNumber;
         mCode = code;
         mMessage = message;
         mRawEvent = rawEvent;
+        mLogMessage = logMessage;
         mParsed = null;
     }
 
+    static public final String SENSITIVE_MARKER = "{{sensitive}}";
+
     public int getCmdNumber() {
         return mCmdNumber;
     }
@@ -62,7 +67,7 @@
 
     @Override
     public String toString() {
-        return mRawEvent;
+        return mLogMessage;
     }
 
     /**
@@ -151,9 +156,15 @@
             }
         }
 
+        String logMessage = rawEvent;
+        if (parsed.length > 2 && parsed[2].equals(SENSITIVE_MARKER)) {
+            skiplength += parsed[2].length() + 1;
+            logMessage = parsed[0] + " " + parsed[1] + " {}";
+        }
+
         final String message = rawEvent.substring(skiplength);
 
-        return new NativeDaemonEvent(cmdNumber, code, message, rawEvent);
+        return new NativeDaemonEvent(cmdNumber, code, message, rawEvent, logMessage);
     }
 
     /**
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 5fe0d1c..748018d 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -1710,14 +1710,18 @@
     public void setDnsServersForNetwork(int netId, String[] servers, String domains) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
-        final Command cmd = new Command("resolver", "setnetdns", netId,
-                (domains == null ? "" : domains));
-
-        for (String s : servers) {
-            InetAddress a = NetworkUtils.numericToInetAddress(s);
-            if (a.isAnyLocalAddress() == false) {
-                cmd.appendArg(a.getHostAddress());
+        Command cmd;
+        if (servers.length > 0) {
+            cmd = new Command("resolver", "setnetdns", netId,
+                    (domains == null ? "" : domains));
+            for (String s : servers) {
+                InetAddress a = NetworkUtils.numericToInetAddress(s);
+                if (a.isAnyLocalAddress() == false) {
+                    cmd.appendArg(a.getHostAddress());
+                }
             }
+        } else {
+            cmd = new Command("resolver", "clearnetdns", netId);
         }
 
         try {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index a2f4d56..e52b2bf 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -390,7 +390,7 @@
             boolean accountDeleted = false;
             Cursor cursor = db.query(TABLE_ACCOUNTS,
                     new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
-                    null, null, null, null, null);
+                    null, null, null, null, ACCOUNTS_ID);
             try {
                 accounts.accountCache.clear();
                 final HashMap<String, ArrayList<String>> accountNamesByType =
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 7c303ff..2c8e1dc 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -42,6 +42,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.telephony.Phone;
@@ -630,8 +631,11 @@
     }
 
     public void checkDunRequired() {
-        int secureSetting = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.TETHER_DUN_REQUIRED, 2);
+        int secureSetting = 2;
+        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm != null) {
+            secureSetting = tm.getTetherApnRequired();
+        }
         synchronized (mPublicSync) {
             // 2 = not set, 0 = DUN not required, 1 = DUN required
             if (secureSetting != 2) {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 683212a..61631d4 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -34,6 +34,7 @@
 abstract class DisplayDevice {
     private final DisplayAdapter mDisplayAdapter;
     private final IBinder mDisplayToken;
+    private final String mUniqueId;
 
     // The display device does not manage these properties itself, they are set by
     // the display manager service.  The display device shouldn't really be looking at these.
@@ -46,9 +47,10 @@
     // within a transaction from performTraversalInTransactionLocked.
     private Surface mCurrentSurface;
 
-    public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken) {
+    public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId) {
         mDisplayAdapter = displayAdapter;
         mDisplayToken = displayToken;
+        mUniqueId = uniqueId;
     }
 
     /**
@@ -80,6 +82,13 @@
     }
 
     /**
+     * Returns the unique id of the display device.
+     */
+    public final String getUniqueId() {
+        return mUniqueId;
+    }
+
+    /**
      * Gets information about the display device.
      *
      * The information returned should not change between calls unless the display
@@ -208,6 +217,7 @@
      */
     public void dumpLocked(PrintWriter pw) {
         pw.println("mAdapter=" + mDisplayAdapter.getName());
+        pw.println("mUniqueId=" + mUniqueId);
         pw.println("mDisplayToken=" + mDisplayToken);
         pw.println("mCurrentLayerStack=" + mCurrentLayerStack);
         pw.println("mCurrentOrientation=" + mCurrentOrientation);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index f48428a..d1e73f0 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -104,12 +104,17 @@
     public static final int TOUCH_EXTERNAL = 2;
 
     /**
-     * Gets the name of the display device, which may be derived from
-     * EDID or other sources.  The name may be displayed to the user.
+     * Gets the name of the display device, which may be derived from EDID or
+     * other sources. The name may be localized and displayed to the user.
      */
     public String name;
 
     /**
+     * Unique Id of display device.
+     */
+    public String uniqueId;
+
+    /**
      * The width of the display in its natural orientation, in pixels.
      * This value is not affected by display rotation.
      */
@@ -235,6 +240,7 @@
     public boolean equals(DisplayDeviceInfo other) {
         return other != null
                 && Objects.equal(name, other.name)
+                && Objects.equal(uniqueId, other.uniqueId)
                 && width == other.width
                 && height == other.height
                 && refreshRate == other.refreshRate
@@ -261,6 +267,7 @@
 
     public void copyFrom(DisplayDeviceInfo other) {
         name = other.name;
+        uniqueId = other.uniqueId;
         width = other.width;
         height = other.height;
         refreshRate = other.refreshRate;
@@ -285,7 +292,8 @@
     public String toString() {
         StringBuilder sb = new StringBuilder();
         sb.append("DisplayDeviceInfo{\"");
-        sb.append(name).append("\": ").append(width).append(" x ").append(height);
+        sb.append(name).append("\": uniqueId=\"").append(uniqueId).append("\", ");
+        sb.append(width).append(" x ").append(height);
         sb.append(", ").append(refreshRate).append(" fps");
         sb.append(", supportedRefreshRates ").append(Arrays.toString(supportedRefreshRates));
         sb.append(", density ").append(densityDpi);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 24cf423..5ebe64d 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -41,6 +41,8 @@
 final class LocalDisplayAdapter extends DisplayAdapter {
     private static final String TAG = "LocalDisplayAdapter";
 
+    private static final String UNIQUE_ID_PREFIX = "local:";
+
     private static final int[] BUILT_IN_DISPLAY_IDS_TO_SCAN = new int[] {
             SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN,
             SurfaceControl.BUILT_IN_DISPLAY_ID_HDMI,
@@ -140,7 +142,7 @@
 
         public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
                 SurfaceControl.PhysicalDisplayInfo[] physicalDisplayInfos, int activeDisplayInfo) {
-            super(LocalDisplayAdapter.this, displayToken);
+            super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + builtInDisplayId);
             mBuiltInDisplayId = builtInDisplayId;
             mPhys = new SurfaceControl.PhysicalDisplayInfo(
                     physicalDisplayInfos[activeDisplayInfo]);
@@ -179,6 +181,7 @@
                 mInfo.appVsyncOffsetNanos = mPhys.appVsyncOffsetNanos;
                 mInfo.presentationDeadlineNanos = mPhys.presentationDeadlineNanos;
                 mInfo.state = mState;
+                mInfo.uniqueId = getUniqueId();
 
                 // Assume that all built-in displays that have secure output (eg. HDCP) also
                 // support compositing from gralloc protected buffers.
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 00ff1cf..6c57eec 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -118,6 +118,7 @@
                 mInfo.copyFrom(mOverrideDisplayInfo);
                 mInfo.layerStack = mBaseDisplayInfo.layerStack;
                 mInfo.name = mBaseDisplayInfo.name;
+                mInfo.uniqueId = mBaseDisplayInfo.uniqueId;
                 mInfo.state = mBaseDisplayInfo.state;
             } else {
                 mInfo.copyFrom(mBaseDisplayInfo);
@@ -208,6 +209,7 @@
             mBaseDisplayInfo.type = deviceInfo.type;
             mBaseDisplayInfo.address = deviceInfo.address;
             mBaseDisplayInfo.name = deviceInfo.name;
+            mBaseDisplayInfo.uniqueId = deviceInfo.uniqueId;
             mBaseDisplayInfo.appWidth = deviceInfo.width;
             mBaseDisplayInfo.appHeight = deviceInfo.height;
             mBaseDisplayInfo.logicalWidth = deviceInfo.width;
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index f514531..5b6f35b 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -61,6 +61,9 @@
     private static final Pattern SETTING_PATTERN =
             Pattern.compile("(\\d+)x(\\d+)/(\\d+)(,[a-z]+)*");
 
+    // Unique id prefix for overlay displays.
+    private static final String UNIQUE_ID_PREFIX = "overlay:";
+
     private final Handler mUiHandler;
     private final ArrayList<OverlayDisplayHandle> mOverlays =
             new ArrayList<OverlayDisplayHandle>();
@@ -160,7 +163,7 @@
                                 + ", densityDpi=" + densityDpi + ", secure=" + secure);
 
                         mOverlays.add(new OverlayDisplayHandle(name,
-                                width, height, densityDpi, gravity, secure));
+                                width, height, densityDpi, gravity, secure, number));
                         continue;
                     }
                 } catch (NumberFormatException ex) {
@@ -203,8 +206,8 @@
         public OverlayDisplayDevice(IBinder displayToken, String name,
                 int width, int height, float refreshRate, long presentationDeadlineNanos,
                 int densityDpi, boolean secure, int state,
-                SurfaceTexture surfaceTexture) {
-            super(OverlayDisplayAdapter.this, displayToken);
+                SurfaceTexture surfaceTexture, int number) {
+            super(OverlayDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + number);
             mName = name;
             mWidth = width;
             mHeight = height;
@@ -245,6 +248,7 @@
             if (mInfo == null) {
                 mInfo = new DisplayDeviceInfo();
                 mInfo.name = mName;
+                mInfo.uniqueId = getUniqueId();
                 mInfo.width = mWidth;
                 mInfo.height = mHeight;
                 mInfo.refreshRate = mRefreshRate;
@@ -279,18 +283,20 @@
         private final int mDensityDpi;
         private final int mGravity;
         private final boolean mSecure;
+        private final int mNumber;
 
         private OverlayDisplayWindow mWindow;
         private OverlayDisplayDevice mDevice;
 
-        public OverlayDisplayHandle(String name,
-                int width, int height, int densityDpi, int gravity, boolean secure) {
+        public OverlayDisplayHandle(String name, int width,
+                int height, int densityDpi, int gravity, boolean secure, int number) {
             mName = name;
             mWidth = width;
             mHeight = height;
             mDensityDpi = densityDpi;
             mGravity = gravity;
             mSecure = secure;
+            mNumber = number;
 
             mUiHandler.post(mShowRunnable);
         }
@@ -308,7 +314,7 @@
                 IBinder displayToken = SurfaceControl.createDisplay(mName, mSecure);
                 mDevice = new OverlayDisplayDevice(displayToken, mName,
                         mWidth, mHeight, refreshRate, presentationDeadlineNanos,
-                        mDensityDpi, mSecure, state, surfaceTexture);
+                        mDensityDpi, mSecure, state, surfaceTexture, mNumber);
 
                 sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
             }
@@ -343,6 +349,7 @@
             pw.println("    mDensityDpi=" + mDensityDpi);
             pw.println("    mGravity=" + mGravity);
             pw.println("    mSecure=" + mSecure);
+            pw.println("    mNumber=" + mNumber);
 
             // Try to dump the window state.
             if (mWindow != null) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 28d5fc0..f181cd5 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -34,6 +34,7 @@
 import android.view.SurfaceControl;
 
 import java.io.PrintWriter;
+import java.util.Iterator;
 
 /**
  * A display adapter that provides virtual displays on behalf of applications.
@@ -45,6 +46,9 @@
     static final String TAG = "VirtualDisplayAdapter";
     static final boolean DEBUG = false;
 
+    // Unique id prefix for virtual displays
+    private static final String UNIQUE_ID_PREFIX = "virtual:";
+
     private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices =
             new ArrayMap<IBinder, VirtualDisplayDevice>();
     private Handler mHandler;
@@ -62,9 +66,12 @@
         boolean secure = (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
         IBinder appToken = callback.asBinder();
         IBinder displayToken = SurfaceControl.createDisplay(name, secure);
+        final String baseUniqueId =
+                UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ",";
+        final int uniqueIndex = getNextUniqueIndex(baseUniqueId);
         VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
                 ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags,
-                new Callback(callback, mHandler));
+                new Callback(callback, mHandler), baseUniqueId + uniqueIndex, uniqueIndex);
 
         mVirtualDisplayDevices.put(appToken, device);
 
@@ -112,6 +119,29 @@
         return device;
     }
 
+    /**
+     * Returns the next unique index for the uniqueIdPrefix
+     */
+    private int getNextUniqueIndex(String uniqueIdPrefix) {
+        if (mVirtualDisplayDevices.isEmpty()) {
+            return 0;
+        }
+
+        int nextUniqueIndex = 0;
+        Iterator<VirtualDisplayDevice> it = mVirtualDisplayDevices.values().iterator();
+        while (it.hasNext()) {
+            VirtualDisplayDevice device = it.next();
+            if (device.getUniqueId().startsWith(uniqueIdPrefix)
+                    && device.mUniqueIndex >= nextUniqueIndex) {
+                // Increment the next unique index to be greater than ones we have already ran
+                // across for displays that have the same unique Id prefix.
+                nextUniqueIndex = device.mUniqueIndex + 1;
+            }
+        }
+
+        return nextUniqueIndex;
+    }
+
     private void handleBinderDiedLocked(IBinder appToken) {
         VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
         if (device != null) {
@@ -150,12 +180,13 @@
         private int mDisplayState;
         private boolean mStopped;
         private int mPendingChanges;
+        private int mUniqueIndex;
 
         public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
                 int ownerUid, String ownerPackageName,
                 String name, int width, int height, int densityDpi, Surface surface, int flags,
-                Callback callback) {
-            super(VirtualDisplayAdapter.this, displayToken);
+                Callback callback, String uniqueId, int uniqueIndex) {
+            super(VirtualDisplayAdapter.this, displayToken, uniqueId);
             mAppToken = appToken;
             mOwnerUid = ownerUid;
             mOwnerPackageName = ownerPackageName;
@@ -168,6 +199,7 @@
             mCallback = callback;
             mDisplayState = Display.STATE_UNKNOWN;
             mPendingChanges |= PENDING_SURFACE_CHANGE;
+            mUniqueIndex = uniqueIndex;
         }
 
         @Override
@@ -255,6 +287,7 @@
             if (mInfo == null) {
                 mInfo = new DisplayDeviceInfo();
                 mInfo.name = mName;
+                mInfo.uniqueId = getUniqueId();
                 mInfo.width = mWidth;
                 mInfo.height = mHeight;
                 mInfo.refreshRate = 60;
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 6b010d9..c939861 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -68,6 +68,9 @@
 
     private static final String ACTION_DISCONNECT = "android.server.display.wfd.DISCONNECT";
 
+    // Unique id prefix for wifi displays
+    private static final String DISPLAY_NAME_PREFIX = "wifi:";
+
     private final WifiDisplayHandler mHandler;
     private final PersistentDataStore mPersistentDataStore;
     private final boolean mSupportsProtectedBuffers;
@@ -587,7 +590,7 @@
         public WifiDisplayDevice(IBinder displayToken, String name,
                 int width, int height, float refreshRate, int flags, String address,
                 Surface surface) {
-            super(WifiDisplayAdapter.this, displayToken);
+            super(WifiDisplayAdapter.this, displayToken, DISPLAY_NAME_PREFIX + address);
             mName = name;
             mWidth = width;
             mHeight = height;
@@ -622,6 +625,7 @@
             if (mInfo == null) {
                 mInfo = new DisplayDeviceInfo();
                 mInfo.name = mName;
+                mInfo.uniqueId = getUniqueId();
                 mInfo.width = mWidth;
                 mInfo.height = mHeight;
                 mInfo.refreshRate = mRefreshRate;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 0e8788a..1486fee 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -129,7 +129,7 @@
     }
 
     private void init(long nativePtr) {
-        mIoHandler = new Handler(mService.getServiceLooper());
+        mIoHandler = new Handler(mService.getIoLooper());
         mControlHandler = new Handler(mService.getServiceLooper());
         mNativePtr = nativePtr;
     }
@@ -324,6 +324,7 @@
     @ServiceThreadOnly
     void setOption(int flag, int value) {
         assertRunOnServiceThread();
+        HdmiLogger.debug("setOption: [flag:%d, value:%d]", flag, value);
         nativeSetOption(mNativePtr, flag, value);
     }
 
@@ -501,6 +502,19 @@
         mControlHandler.post(runnable);
     }
 
+    @ServiceThreadOnly
+    void flush(final Runnable runnable) {
+        assertRunOnServiceThread();
+        runOnIoThread(new Runnable() {
+            @Override
+            public void run() {
+                // This ensures the runnable for cleanup is performed after all the pending
+                // commands are processed by IO thread.
+                runOnServiceThread(runnable);
+            }
+        });
+    }
+
     private boolean isAcceptableAddress(int address) {
         // Can access command targeting devices available in local device or broadcast command.
         if (address == Constants.ADDR_BROADCAST) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 8a25f62..5f8b389 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -2021,30 +2021,52 @@
     void setControlEnabled(boolean enabled) {
         assertRunOnServiceThread();
 
-        if (!enabled) {
-            // Call the vendor handler before the service is disabled.
-            invokeVendorCommandListenersOnControlStateChanged(false,
-                    HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
-        }
-        int value = toInt(enabled);
-        mCecController.setOption(OPTION_CEC_ENABLE, value);
-        mMhlController.setOption(OPTION_MHL_ENABLE, value);
-
         synchronized (mLock) {
             mHdmiControlEnabled = enabled;
         }
 
         if (enabled) {
-            initializeCec(INITIATED_BY_ENABLE_CEC);
-        } else {
-            disableDevices(new PendingActionClearedCallback() {
-                @Override
-                public void onCleared(HdmiCecLocalDevice device) {
-                    assertRunOnServiceThread();
-                    clearLocalDevices();
-                }
-            });
+            enableHdmiControlService();
+            return;
         }
+        // Call the vendor handler before the service is disabled.
+        invokeVendorCommandListenersOnControlStateChanged(false,
+                HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
+        // Post the remained tasks in the service thread again to give the vendor-issued-tasks
+        // a chance to run.
+        runOnServiceThread(new Runnable() {
+            @Override
+            public void run() {
+                disableHdmiControlService();
+            }
+        });
+        return;
+    }
+
+    @ServiceThreadOnly
+    private void enableHdmiControlService() {
+        mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
+        mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
+
+        initializeCec(INITIATED_BY_ENABLE_CEC);
+    }
+
+    @ServiceThreadOnly
+    private void disableHdmiControlService() {
+        disableDevices(new PendingActionClearedCallback() {
+            @Override
+            public void onCleared(HdmiCecLocalDevice device) {
+                assertRunOnServiceThread();
+                mCecController.flush(new Runnable() {
+                    @Override
+                    public void run() {
+                        mCecController.setOption(OPTION_CEC_ENABLE, DISABLED);
+                        mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
+                        clearLocalDevices();
+                    }
+                });
+            }
+        });
     }
 
     @ServiceThreadOnly
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 24fc455..fdb443e 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -26,8 +26,9 @@
     void onNotificationError(int callingUid, int callingPid,
             String pkg, String tag, int id,
             int uid, int initialPid, String message, int userId);
-    void onPanelRevealed();
+    void onPanelRevealed(boolean clearEffects);
     void onPanelHidden();
+    void clearEffects();
     void onNotificationVisibilityChanged(
             String[] newlyVisibleKeys, String[] noLongerVisibleKeys);
     void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 70d0e6a..323b34b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -116,13 +116,14 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map.Entry;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 
 /** {@hide} */
 public class NotificationManagerService extends SystemService {
     static final String TAG = "NotificationService";
-    static final boolean DBG = false;
+    static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
     static final int MAX_PACKAGE_NOTIFICATIONS = 50;
 
@@ -139,6 +140,7 @@
     static final int SHORT_DELAY = 2000; // 2 seconds
 
     static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
+
     static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
 
     static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
@@ -166,6 +168,15 @@
     static final float MATCHES_CALL_FILTER_TIMEOUT_AFFINITY =
             ValidateNotificationPeople.STARRED_CONTACT;
 
+    /** notification_enqueue status value for a newly enqueued notification. */
+    private static final int EVENTLOG_ENQUEUE_STATUS_NEW = 0;
+
+    /** notification_enqueue status value for an existing notification. */
+    private static final int EVENTLOG_ENQUEUE_STATUS_UPDATE = 1;
+
+    /** notification_enqueue status value for an ignored notification. */
+    private static final int EVENTLOG_ENQUEUE_STATUS_IGNORED = 2;
+
     private IActivityManager mAm;
     AudioManager mAudioManager;
     StatusBarManagerInternal mStatusBar;
@@ -209,6 +220,7 @@
     final ArrayMap<String, NotificationRecord> mNotificationsByKey =
             new ArrayMap<String, NotificationRecord>();
     final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
+    final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
 
     ArrayList<String> mLights = new ArrayList<String>();
     NotificationRecord mLedNotification;
@@ -251,6 +263,7 @@
     private static final int REASON_LISTENER_CANCEL = 10;
     private static final int REASON_LISTENER_CANCEL_ALL = 11;
     private static final int REASON_GROUP_SUMMARY_CANCELED = 12;
+    private static final int REASON_GROUP_OPTIMIZATION = 13;
 
     private static class Archive {
         final int mBufferSize;
@@ -564,9 +577,23 @@
         }
 
         @Override
-        public void onPanelRevealed() {
+        public void onPanelRevealed(boolean clearEffects) {
             EventLogTags.writeNotificationPanelRevealed();
+            if (clearEffects) {
+                clearEffects();
+            }
+        }
+
+        @Override
+        public void onPanelHidden() {
+            EventLogTags.writeNotificationPanelHidden();
+        }
+
+        @Override
+        public void clearEffects() {
             synchronized (mNotificationList) {
+                if (DBG) Slog.d(TAG, "clearEffects");
+
                 // sound
                 mSoundNotification = null;
 
@@ -598,11 +625,6 @@
         }
 
         @Override
-        public void onPanelHidden() {
-            EventLogTags.writeNotificationPanelHidden();
-        }
-
-        @Override
         public void onNotificationError(int callingUid, int callingPid, String pkg, String tag, int id,
                 int uid, int initialPid, String message, int userId) {
             Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
@@ -740,8 +762,10 @@
                 // Keep track of screen on/off state, but do not turn off the notification light
                 // until user passes through the lock screen or views the notification.
                 mScreenOn = true;
+                updateNotificationPulse();
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 mScreenOn = false;
+                updateNotificationPulse();
             } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
                 mInCall = TelephonyManager.EXTRA_STATE_OFFHOOK
                         .equals(intent.getStringExtra(TelephonyManager.EXTRA_STATE));
@@ -1658,6 +1682,16 @@
 
             pw.println("\n  Condition providers:");
             mConditionProviders.dump(pw, filter);
+
+            pw.println("\n  Group summaries:");
+            for (Entry<String, NotificationRecord> entry : mSummaryByGroupKey.entrySet()) {
+                NotificationRecord r = entry.getValue();
+                pw.println("    " + entry.getKey() + " -> " + r.getKey());
+                if (mNotificationsByKey.get(r.getKey()) != r) {
+                    pw.println("!!!!!!LEAK: Record not found in mNotificationsByKey.");
+                    r.dump(pw, "      ", getContext());
+                }
+            }
         }
     }
 
@@ -1779,16 +1813,34 @@
                         // Retain ranking information from previous record
                         r.copyRankingInformation(old);
                     }
-                    mRankingHelper.extractSignals(r);
+
+                    // Handle grouped notifications and bail out early if we
+                    // can to avoid extracting signals.
+                    handleGroupedNotificationLocked(r, old, callingUid, callingPid);
+                    boolean ignoreNotification =
+                            removeUnusedGroupedNotificationLocked(r, callingUid, callingPid);
 
                     // This conditional is a dirty hack to limit the logging done on
                     //     behalf of the download manager without affecting other apps.
                     if (!pkg.equals("com.android.providers.downloads")
                             || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
+                        int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
+                        if (ignoreNotification) {
+                            enqueueStatus = EVENTLOG_ENQUEUE_STATUS_IGNORED;
+                        } else if (old != null) {
+                            enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
+                        }
                         EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
                                 pkg, id, tag, userId, notification.toString(),
-                                (old != null) ? 1 : 0);
+                                enqueueStatus);
                     }
+
+                    if (ignoreNotification) {
+                        return;
+                    }
+
+                    mRankingHelper.extractSignals(r);
+
                     // 3. Apply local rules
 
                     // blocked apps
@@ -1805,16 +1857,6 @@
                         return;
                     }
 
-                    // Clear out group children of the old notification if the update causes the
-                    // group summary to go away. This happens when the old notification was a
-                    // summary and the new one isn't, or when the old notification was a summary
-                    // and its group key changed.
-                    if (old != null && old.getNotification().isGroupSummary() &&
-                            (!notification.isGroupSummary() ||
-                                    !old.getGroupKey().equals(r.getGroupKey()))) {
-                        cancelGroupChildrenLocked(old, callingUid, callingPid, null);
-                    }
-
                     int index = indexOfNotificationLocked(n.getKey());
                     if (index < 0) {
                         mNotificationList.add(r);
@@ -1864,6 +1906,90 @@
         idOut[0] = id;
     }
 
+    /**
+     * Ensures that grouped notification receive their special treatment.
+     *
+     * <p>Cancels group children if the new notification causes a group to lose
+     * its summary.</p>
+     *
+     * <p>Updates mSummaryByGroupKey.</p>
+     */
+    private void handleGroupedNotificationLocked(NotificationRecord r, NotificationRecord old,
+            int callingUid, int callingPid) {
+        StatusBarNotification sbn = r.sbn;
+        Notification n = sbn.getNotification();
+        String group = sbn.getGroupKey();
+        boolean isSummary = n.isGroupSummary();
+
+        Notification oldN = old != null ? old.sbn.getNotification() : null;
+        String oldGroup = old != null ? old.sbn.getGroupKey() : null;
+        boolean oldIsSummary = old != null && oldN.isGroupSummary();
+
+        if (oldIsSummary) {
+            NotificationRecord removedSummary = mSummaryByGroupKey.remove(oldGroup);
+            if (removedSummary != old) {
+                String removedKey =
+                        removedSummary != null ? removedSummary.getKey() : "<null>";
+                Slog.w(TAG, "Removed summary didn't match old notification: old=" + old.getKey() +
+                        ", removed=" + removedKey);
+            }
+        }
+        if (isSummary) {
+            mSummaryByGroupKey.put(group, r);
+        }
+
+        // Clear out group children of the old notification if the update
+        // causes the group summary to go away. This happens when the old
+        // notification was a summary and the new one isn't, or when the old
+        // notification was a summary and its group key changed.
+        if (oldIsSummary && (!isSummary || !oldGroup.equals(group))) {
+            cancelGroupChildrenLocked(old, callingUid, callingPid, null,
+                    REASON_GROUP_SUMMARY_CANCELED);
+        }
+    }
+
+    /**
+     * Performs group notification optimizations if SysUI is the only active
+     * notification listener and returns whether the given notification should
+     * be ignored.
+     *
+     * <p>Returns true if the given notification is a child of a group with a
+     * summary, which means that SysUI will never show it, and hence the new
+     * notification can be safely ignored.</p>
+     *
+     * <p>For summaries, cancels all children of that group, as SysUI will
+     * never show them anymore.</p>
+     *
+     * @return true if the given notification can be ignored as an optimization
+     */
+    private boolean removeUnusedGroupedNotificationLocked(NotificationRecord r,
+            int callingUid, int callingPid) {
+        // No optimizations are possible if listeners want groups.
+        if (mListeners.notificationGroupsDesired()) {
+            return false;
+        }
+
+        StatusBarNotification sbn = r.sbn;
+        String group = sbn.getGroupKey();
+        boolean isSummary = sbn.getNotification().isGroupSummary();
+        boolean isChild = sbn.getNotification().isGroupChild();
+
+        NotificationRecord summary = mSummaryByGroupKey.get(group);
+        if (isChild && summary != null) {
+            // Child with an active summary -> ignore
+            if (DBG) {
+                Slog.d(TAG, "Ignoring group child " + sbn.getKey() + " due to existing summary "
+                        + summary.getKey());
+            }
+            return true;
+        } else if (isSummary) {
+            // Summary -> cancel children
+            cancelGroupChildrenLocked(r, callingUid, callingPid, null,
+                    REASON_GROUP_OPTIMIZATION);
+        }
+        return false;
+    }
+
     private void buzzBeepBlinkLocked(NotificationRecord record) {
         boolean buzzBeepBlinked = false;
         final Notification notification = record.sbn.getNotification();
@@ -2386,6 +2512,11 @@
         }
 
         mNotificationsByKey.remove(r.sbn.getKey());
+        String groupKey = r.getGroupKey();
+        NotificationRecord groupSummary = mSummaryByGroupKey.get(groupKey);
+        if (groupSummary != null && groupSummary.getKey().equals(r.getKey())) {
+            mSummaryByGroupKey.remove(groupKey);
+        }
 
         // Save it for users of getHistoricalNotifications()
         mArchive.record(r.sbn);
@@ -2433,7 +2564,8 @@
                         mNotificationList.remove(index);
 
                         cancelNotificationLocked(r, sendDelete, reason);
-                        cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName);
+                        cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName,
+                                REASON_GROUP_SUMMARY_CANCELED);
                         updateLightsLocked();
                     }
                 }
@@ -2512,7 +2644,7 @@
                 final int M = canceledNotifications.size();
                 for (int i = 0; i < M; i++) {
                     cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
-                            listenerName);
+                            listenerName, REASON_GROUP_SUMMARY_CANCELED);
                 }
             }
             if (canceledNotifications != null) {
@@ -2556,14 +2688,14 @@
         int M = canceledNotifications != null ? canceledNotifications.size() : 0;
         for (int i = 0; i < M; i++) {
             cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
-                    listenerName);
+                    listenerName, REASON_GROUP_SUMMARY_CANCELED);
         }
         updateLightsLocked();
     }
 
     // Warning: The caller is responsible for invoking updateLightsLocked().
     private void cancelGroupChildrenLocked(NotificationRecord r, int callingUid, int callingPid,
-            String listenerName) {
+            String listenerName, int reason) {
         Notification n = r.getNotification();
         if (!n.isGroupSummary()) {
             return;
@@ -2583,11 +2715,10 @@
             StatusBarNotification childSbn = childR.sbn;
             if (childR.getNotification().isGroupChild() &&
                     childR.getGroupKey().equals(r.getGroupKey())) {
-                EventLogTags.writeNotificationCancel(callingUid, callingPid,
-                        pkg, childSbn.getId(), childSbn.getTag(), userId, 0, 0,
-                        REASON_GROUP_SUMMARY_CANCELED, listenerName);
+                EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(),
+                        childSbn.getTag(), userId, 0, 0, reason, listenerName);
                 mNotificationList.remove(i);
-                cancelNotificationLocked(childR, false, REASON_GROUP_SUMMARY_CANCELED);
+                cancelNotificationLocked(childR, false, reason);
             }
         }
     }
@@ -2783,6 +2914,7 @@
     public class NotificationListeners extends ManagedServices {
 
         private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
+        private boolean mNotificationGroupsDesired;
 
         public NotificationListeners() {
             super(getContext(), mHandler, mNotificationList, mUserProfiles);
@@ -2810,6 +2942,7 @@
             final INotificationListener listener = (INotificationListener) info.service;
             final NotificationRankingUpdate update;
             synchronized (mNotificationList) {
+                updateNotificationGroupsDesiredLocked();
                 update = makeRankingUpdateLocked(info);
             }
             try {
@@ -2825,6 +2958,7 @@
                 updateListenerHintsLocked();
             }
             mLightTrimListeners.remove(removed);
+            updateNotificationGroupsDesiredLocked();
         }
 
         public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
@@ -3028,6 +3162,31 @@
             }
             return false;
         }
+
+        /**
+         * Returns whether any of the currently registered listeners wants to receive notification
+         * groups.
+         *
+         * <p>Currently we assume groups are desired by non-SystemUI listeners.</p>
+         */
+        public boolean notificationGroupsDesired() {
+            return mNotificationGroupsDesired;
+        }
+
+        private void updateNotificationGroupsDesiredLocked() {
+            mNotificationGroupsDesired = true;
+            // No listeners, no groups.
+            if (mServices.isEmpty()) {
+                mNotificationGroupsDesired = false;
+                return;
+            }
+            // One listener: Check whether it's SysUI.
+            if (mServices.size() == 1 &&
+                    mServices.get(0).component.getPackageName().equals("com.android.systemui")) {
+                mNotificationGroupsDesired = false;
+                return;
+            }
+        }
     }
 
     public static final class DumpFilter {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4fd9fa7..524f638 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3324,6 +3324,18 @@
             pw.print(",");
             pw.print(ps.installerPackageName != null ? ps.installerPackageName : "?");
             pw.println();
+            if (ps.pkg != null) {
+                pw.print(checkinTag); pw.print("-"); pw.print("splt,");
+                pw.print("base,");
+                pw.println(ps.pkg.baseRevisionCode);
+                if (ps.pkg.splitNames != null) {
+                    for (int i = 0; i < ps.pkg.splitNames.length; i++) {
+                        pw.print(checkinTag); pw.print("-"); pw.print("splt,");
+                        pw.print(ps.pkg.splitNames[i]); pw.print(",");
+                        pw.println(ps.pkg.splitRevisionCodes[i]);
+                    }
+                }
+            }
             for (UserInfo user : users) {
                 pw.print(checkinTag);
                 pw.print("-");
@@ -3374,6 +3386,7 @@
         pw.println();
         if (ps.pkg != null) {
             pw.print(prefix); pw.print("  versionName="); pw.println(ps.pkg.mVersionName);
+            pw.print(prefix); pw.print("  splits="); dumpSplitNames(pw, ps.pkg); pw.println();
             pw.print(prefix); pw.print("  applicationInfo=");
                 pw.println(ps.pkg.applicationInfo.toString());
             pw.print(prefix); pw.print("  flags="); printFlags(pw, ps.pkg.applicationInfo.flags,
@@ -3646,4 +3659,27 @@
         pw.println("Settings parse messages:");
         pw.print(mReadMessages.toString());
     }
+
+    private static void dumpSplitNames(PrintWriter pw, PackageParser.Package pkg) {
+        if (pkg == null) {
+            pw.print("unknown");
+        } else {
+            // [base:10, config.mdpi, config.xhdpi:12]
+            pw.print("[");
+            pw.print("base");
+            if (pkg.baseRevisionCode != 0) {
+                pw.print(":"); pw.print(pkg.baseRevisionCode);
+            }
+            if (pkg.splitNames != null) {
+                for (int i = 0; i < pkg.splitNames.length; i++) {
+                    pw.print(", ");
+                    pw.print(pkg.splitNames[i]);
+                    if (pkg.splitRevisionCodes[i] != 0) {
+                        pw.print(":"); pw.print(pkg.splitRevisionCodes[i]);
+                    }
+                }
+            }
+            pw.print("]");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 9828cd4..cf2ed07 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -26,7 +26,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.util.Slog;
-import android.view.WindowManager;
 
 import com.android.internal.statusbar.IStatusBar;
 import com.android.internal.statusbar.IStatusBarService;
@@ -495,16 +494,26 @@
     }
 
     /**
-     * The status bar service should call this each time the user brings the panel from
-     * invisible to visible in order to clear the notification light.
+     * @param clearNotificationEffects whether to consider notifications as "shown" and stop
+     *     LED, vibration, and ringing
      */
     @Override
-    public void onPanelRevealed() {
+    public void onPanelRevealed(boolean clearNotificationEffects) {
         enforceStatusBarService();
         long identity = Binder.clearCallingIdentity();
         try {
-            // tell the notification manager to turn off the lights.
-            mNotificationDelegate.onPanelRevealed();
+            mNotificationDelegate.onPanelRevealed(clearNotificationEffects);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void clearNotificationEffects() throws RemoteException {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.clearEffects();
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index dc355c4..5ab3fa1 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -19,6 +19,7 @@
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
 import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
+import static android.media.tv.TvInputManager.INPUT_STATE_UNKNOWN;
 
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
@@ -779,6 +780,22 @@
         }
 
         @Override
+        public int getTvInputState(String inputId, int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "getTvInputState");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    UserState userState = getUserStateLocked(resolvedUserId);
+                    TvInputState state = userState.inputMap.get(inputId);
+                    return state == null ? INPUT_STATE_UNKNOWN : state.state;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public List<TvContentRatingSystemInfo> getTvContentRatingSystemList(int userId) {
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, "getTvContentRatingSystemList");
@@ -816,10 +833,6 @@
                     } catch (RemoteException e) {
                         Slog.e(TAG, "client process has already died", e);
                     }
-                    for (TvInputState state : userState.inputMap.values()) {
-                        notifyInputStateChangedLocked(userState, state.info.getId(), state.state,
-                                callback);
-                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/wm/DisplaySettings.java b/services/core/java/com/android/server/wm/DisplaySettings.java
index 34d1a64..ba995f2 100644
--- a/services/core/java/com/android/server/wm/DisplaySettings.java
+++ b/services/core/java/com/android/server/wm/DisplaySettings.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.os.Environment;
 import android.util.AtomicFile;
@@ -41,7 +40,6 @@
 public class DisplaySettings {
     private static final String TAG = WindowManagerService.TAG;
 
-    private final Context mContext;
     private final AtomicFile mFile;
     private final HashMap<String, Entry> mEntries = new HashMap<String, Entry>();
 
@@ -57,15 +55,19 @@
         }
     }
 
-    public DisplaySettings(Context context) {
-        mContext = context;
+    public DisplaySettings() {
         File dataDir = Environment.getDataDirectory();
         File systemDir = new File(dataDir, "system");
         mFile = new AtomicFile(new File(systemDir, "display_settings.xml"));
     }
 
-    public void getOverscanLocked(String name, Rect outRect) {
-        Entry entry = mEntries.get(name);
+    public void getOverscanLocked(String name, String uniqueId, Rect outRect) {
+        // Try to get the entry with the unique if possible.
+        // Else, fall back on the display name.
+        Entry entry;
+        if (uniqueId == null || (entry = mEntries.get(uniqueId)) == null) {
+            entry = mEntries.get(name);
+        }
         if (entry != null) {
             outRect.left = entry.overscanLeft;
             outRect.top = entry.overscanTop;
@@ -110,7 +112,7 @@
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG
                     && type != XmlPullParser.END_DOCUMENT) {
-                ;
+                // Do nothing.
             }
 
             if (type != XmlPullParser.START_TAG) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bcfd7f0..6a55ffc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -825,7 +825,7 @@
                 com.android.internal.R.bool.config_hasPermanentDpad);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
-        mDisplaySettings = new DisplaySettings(context);
+        mDisplaySettings = new DisplaySettings();
         mDisplaySettings.readSettingsLocked();
 
         LocalServices.addService(WindowManagerPolicy.class, mPolicy);
@@ -8476,7 +8476,7 @@
             displayInfo.overscanBottom = bottom;
         }
 
-        mDisplaySettings.setOverscanLocked(displayInfo.name, left, top, right, bottom);
+        mDisplaySettings.setOverscanLocked(displayInfo.uniqueId, left, top, right, bottom);
         mDisplaySettings.writeSettingsLocked();
 
         reconfigureDisplayLocked(displayContent);
@@ -9539,6 +9539,9 @@
                                     + " interesting=" + numInteresting
                                     + " drawn=" + wtoken.numDrawnWindows);
                             wtoken.allDrawn = true;
+                            // Force an additional layout pass where WindowStateAnimator#
+                            // commitFinishDrawingLocked() will call performShowLocked().
+                            displayContent.layoutNeeded = true;
                             mH.obtainMessage(H.NOTIFY_ACTIVITY_DRAWN, wtoken.token).sendToTarget();
                         }
                     }
@@ -11458,7 +11461,7 @@
 
         DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final Rect rect = new Rect();
-        mDisplaySettings.getOverscanLocked(displayInfo.name, rect);
+        mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
         synchronized (displayContent.mDisplaySizeLock) {
             displayInfo.overscanLeft = rect.left;
             displayInfo.overscanTop = rect.top;
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 7df40f1..bbf3384 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -153,10 +153,10 @@
         public static final int CAPABILITY_SUPPORTS_VT_REMOTE = 0x00000200;
 
         /**
-         * Call is using voice over LTE.
+         * Call is using high definition audio.
          * @hide
          */
-        public static final int CAPABILITY_VoLTE = 0x00000400;
+        public static final int CAPABILITY_HIGH_DEF_AUDIO = 0x00000400;
 
         /**
          * Call is using voice over WIFI.
@@ -255,8 +255,8 @@
             if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE)) {
                 builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE");
             }
-            if (can(capabilities, CAPABILITY_VoLTE)) {
-                builder.append(" CAPABILITY_VoLTE");
+            if (can(capabilities, CAPABILITY_HIGH_DEF_AUDIO)) {
+                builder.append(" CAPABILITY_HIGH_DEF_AUDIO");
             }
             if (can(capabilities, CAPABILITY_VoWIFI)) {
                 builder.append(" CAPABILITY_VoWIFI");
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 125ada0..04274c7 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -118,10 +118,10 @@
     public static final int CAPABILITY_SUPPORTS_VT_REMOTE = 0x00000200;
 
     /**
-     * Connection is using voice over LTE.
+     * Connection is using high definition audio.
      * @hide
      */
-    public static final int CAPABILITY_VoLTE = 0x00000400;
+    public static final int CAPABILITY_HIGH_DEF_AUDIO = 0x00000400;
 
     /**
      * Connection is using voice over WIFI.
@@ -224,8 +224,8 @@
         if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE)) {
             builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE");
         }
-        if (can(capabilities, CAPABILITY_VoLTE)) {
-            builder.append(" CAPABILITY_VoLTE");
+        if (can(capabilities, CAPABILITY_HIGH_DEF_AUDIO)) {
+            builder.append(" CAPABILITY_HIGH_DEF_AUDIO");
         }
         if (can(capabilities, CAPABILITY_VoWIFI)) {
             builder.append(" CAPABILITY_VoWIFI");
@@ -1009,7 +1009,7 @@
     }
 
     /** @hide */
-    @Deprecated public final void setCapabilities(int connectionCapabilities) {
+    @SystemApi @Deprecated public final void setCallCapabilities(int connectionCapabilities) {
         setConnectionCapabilities(connectionCapabilities);
     }
 
@@ -1310,12 +1310,16 @@
     }
 
     private static class FailureSignalingConnection extends Connection {
+        private boolean mImmutable = false;
         public FailureSignalingConnection(DisconnectCause disconnectCause) {
             setDisconnected(disconnectCause);
+            mImmutable = true;
         }
 
         public void checkImmutable() {
-            throw new UnsupportedOperationException("Connection is immutable");
+            if (mImmutable) {
+                throw new UnsupportedOperationException("Connection is immutable");
+            }
         }
     }
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 1b54eec..de2a6da 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1811,9 +1811,10 @@
      *
      * @param alphaTag alpha-tagging of the dailing nubmer
      * @param number The dialing number
+     * @return true if the operation was executed correctly.
      */
-    public void setLine1NumberForDisplay(String alphaTag, String number) {
-        setLine1NumberForDisplayForSubscriber(getDefaultSubscription(), alphaTag, number);
+    public boolean setLine1NumberForDisplay(String alphaTag, String number) {
+        return setLine1NumberForDisplayForSubscriber(getDefaultSubscription(), alphaTag, number);
     }
 
     /**
@@ -1828,14 +1829,16 @@
      * @param subId the subscriber that the alphatag and dialing number belongs to.
      * @param alphaTag alpha-tagging of the dailing nubmer
      * @param number The dialing number
+     * @return true if the operation was executed correctly.
      * @hide
      */
-    public void setLine1NumberForDisplayForSubscriber(int subId, String alphaTag, String number) {
+    public boolean setLine1NumberForDisplayForSubscriber(int subId, String alphaTag, String number) {
         try {
-            getITelephony().setLine1NumberForDisplayForSubscriber(subId, alphaTag, number);
+            return getITelephony().setLine1NumberForDisplayForSubscriber(subId, alphaTag, number);
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
         }
+        return false;
     }
 
     /**
@@ -3084,6 +3087,26 @@
     }
 
     /**
+     * Check TETHER_DUN_REQUIRED and TETHER_DUN_APN settings, net.tethering.noprovisioning
+     * SystemProperty, and config_tether_apndata to decide whether DUN APN is required for
+     * tethering.
+     *
+     * @return 0: Not required. 1: required. 2: Not set.
+     * @hide
+     */
+    public int getTetherApnRequired() {
+        try {
+            return getITelephony().getTetherApnRequired();
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "hasMatchedTetherApnSetting RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "hasMatchedTetherApnSetting NPE", ex);
+        }
+        return 2;
+    }
+
+
+    /**
      * Values used to return status for hasCarrierPrivileges call.
      */
     /** @hide */
diff --git a/telephony/java/com/android/ims/internal/IImsService.aidl b/telephony/java/com/android/ims/internal/IImsService.aidl
index b9cee42..30c48d7 100644
--- a/telephony/java/com/android/ims/internal/IImsService.aidl
+++ b/telephony/java/com/android/ims/internal/IImsService.aidl
@@ -32,7 +32,7 @@
  * {@hide}
  */
 interface IImsService {
-    int open(int serviceClass, in PendingIntent incomingCallIntent,
+    int open(int phoneId, int serviceClass, in PendingIntent incomingCallIntent,
             in IImsRegistrationListener listener);
     void close(int serviceId);
     boolean isConnected(int serviceId, int serviceType, int callType);
@@ -53,18 +53,18 @@
     /**
      * Config interface to get/set IMS service/capability parameters.
      */
-    IImsConfig getConfigInterface();
+    IImsConfig getConfigInterface(int phoneId);
 
     /**
      * Used for turning on IMS when its in OFF state.
      */
-    void turnOnIms();
+    void turnOnIms(int phoneId);
 
     /**
      * Used for turning off IMS when its in ON state.
      * When IMS is OFF, device will behave as CSFB'ed.
      */
-    void turnOffIms();
+    void turnOffIms(int phoneId);
 
     /**
      * ECBM interface for Emergency Callback mode mechanism.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index adb3bc4..bf7f332 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -657,6 +657,15 @@
     int getPreferredNetworkType();
 
     /**
+     * Check TETHER_DUN_REQUIRED and TETHER_DUN_APN settings, net.tethering.noprovisioning
+     * SystemProperty, and config_tether_apndata to decide whether DUN APN is required for
+     * tethering.
+     *
+     * @return 0: Not required. 1: required. 2: Not set.
+     */
+    int getTetherApnRequired();
+
+    /**
      * Set the preferred network type.
      * Used for device configuration by some CDMA operators.
      *
@@ -740,8 +749,9 @@
      * @param subId the subscriber that the alphatag and dialing number belongs to.
      * @param alphaTag alpha-tagging of the dailing nubmer
      * @param number The dialing number
+     * @return true if the operation was executed correctly.
      */
-    void setLine1NumberForDisplayForSubscriber(int subId, String alphaTag, String number);
+    boolean setLine1NumberForDisplayForSubscriber(int subId, String alphaTag, String number);
 
     /**
      * Returns the displayed dialing number string if it was set previously via