Merge "AudioService: add missing audio becoming noisy intent" into mnc-dev
diff --git a/api/current.txt b/api/current.txt
index 3b57f18..a400fa6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20,8 +20,8 @@
     field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS";
     field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
     field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
-    field public static final java.lang.String BIND_CARRIER_CONFIG_SERVICE = "android.permission.BIND_CARRIER_CONFIG_SERVICE";
-    field public static final java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
+    field public static final deprecated java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
+    field public static final java.lang.String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES";
     field public static final java.lang.String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE";
     field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
     field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
@@ -15730,7 +15730,7 @@
     method public void restoreKeys(byte[], byte[]);
     method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
     method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
-    method public void setOnKeysChangeListener(android.media.MediaDrm.OnKeysChangeListener, android.os.Handler);
+    method public void setOnKeyStatusChangeListener(android.media.MediaDrm.OnKeyStatusChangeListener, android.os.Handler);
     method public void setPropertyByteArray(java.lang.String, byte[]);
     method public void setPropertyString(java.lang.String, java.lang.String);
     field public static final int EVENT_KEY_EXPIRED = 3; // 0x3
@@ -15738,11 +15738,6 @@
     field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1
     field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5
     field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4
-    field public static final int KEY_STATUS_EXPIRED = 1; // 0x1
-    field public static final int KEY_STATUS_INTERNAL_ERROR = 4; // 0x4
-    field public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
-    field public static final int KEY_STATUS_PENDING = 3; // 0x3
-    field public static final int KEY_STATUS_USABLE = 0; // 0x0
     field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
     field public static final int KEY_TYPE_RELEASE = 3; // 0x3
     field public static final int KEY_TYPE_STREAMING = 1; // 0x1
@@ -15751,9 +15746,6 @@
     field public static final java.lang.String PROPERTY_DEVICE_UNIQUE_ID = "deviceUniqueId";
     field public static final java.lang.String PROPERTY_VENDOR = "vendor";
     field public static final java.lang.String PROPERTY_VERSION = "version";
-    field public static final int REQUEST_TYPE_INITIAL = 0; // 0x0
-    field public static final int REQUEST_TYPE_RELEASE = 2; // 0x2
-    field public static final int REQUEST_TYPE_RENEWAL = 1; // 0x1
   }
 
   public final class MediaDrm.CryptoSession {
@@ -15767,11 +15759,19 @@
     method public byte[] getData();
     method public java.lang.String getDefaultUrl();
     method public int getRequestType();
+    field public static final int REQUEST_TYPE_INITIAL = 0; // 0x0
+    field public static final int REQUEST_TYPE_RELEASE = 2; // 0x2
+    field public static final int REQUEST_TYPE_RENEWAL = 1; // 0x1
   }
 
   public static final class MediaDrm.KeyStatus {
     method public byte[] getKeyId();
     method public int getStatusCode();
+    field public static final int STATUS_EXPIRED = 1; // 0x1
+    field public static final int STATUS_INTERNAL_ERROR = 4; // 0x4
+    field public static final int STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
+    field public static final int STATUS_PENDING = 3; // 0x3
+    field public static final int STATUS_USABLE = 0; // 0x0
   }
 
   public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
@@ -15786,8 +15786,8 @@
     method public abstract void onExpirationUpdate(android.media.MediaDrm, byte[], long);
   }
 
-  public static abstract interface MediaDrm.OnKeysChangeListener {
-    method public abstract void onKeysChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
+  public static abstract interface MediaDrm.OnKeyStatusChangeListener {
+    method public abstract void onKeyStatusChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
   }
 
   public static final class MediaDrm.ProvisionRequest {
diff --git a/api/system-current.txt b/api/system-current.txt
index dbe0d4f..351ec8e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -28,8 +28,8 @@
     field public static final java.lang.String BATTERY_STATS = "android.permission.BATTERY_STATS";
     field public static final java.lang.String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
     field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
-    field public static final java.lang.String BIND_CARRIER_CONFIG_SERVICE = "android.permission.BIND_CARRIER_CONFIG_SERVICE";
-    field public static final java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
+    field public static final deprecated java.lang.String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
+    field public static final java.lang.String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES";
     field public static final java.lang.String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE";
     field public static final java.lang.String BIND_CONDITION_PROVIDER_SERVICE = "android.permission.BIND_CONDITION_PROVIDER_SERVICE";
     field public static final deprecated java.lang.String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE";
@@ -8367,6 +8367,7 @@
     field public static final java.lang.String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS";
     field public static final java.lang.String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE";
     field public static final java.lang.String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
+    field public static final java.lang.String ACTION_MANAGE_PERMISSIONS = "android.intent.action.MANAGE_PERMISSIONS";
     field public static final java.lang.String ACTION_MANAGE_PERMISSION_APPS = "android.intent.action.MANAGE_PERMISSION_APPS";
     field public static final java.lang.String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL";
     field public static final java.lang.String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
@@ -16967,7 +16968,7 @@
     method public void restoreKeys(byte[], byte[]);
     method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
     method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
-    method public void setOnKeysChangeListener(android.media.MediaDrm.OnKeysChangeListener, android.os.Handler);
+    method public void setOnKeyStatusChangeListener(android.media.MediaDrm.OnKeyStatusChangeListener, android.os.Handler);
     method public void setPropertyByteArray(java.lang.String, byte[]);
     method public void setPropertyString(java.lang.String, java.lang.String);
     method public void unprovisionDevice();
@@ -16976,11 +16977,6 @@
     field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1
     field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5
     field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4
-    field public static final int KEY_STATUS_EXPIRED = 1; // 0x1
-    field public static final int KEY_STATUS_INTERNAL_ERROR = 4; // 0x4
-    field public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
-    field public static final int KEY_STATUS_PENDING = 3; // 0x3
-    field public static final int KEY_STATUS_USABLE = 0; // 0x0
     field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
     field public static final int KEY_TYPE_RELEASE = 3; // 0x3
     field public static final int KEY_TYPE_STREAMING = 1; // 0x1
@@ -16989,9 +16985,6 @@
     field public static final java.lang.String PROPERTY_DEVICE_UNIQUE_ID = "deviceUniqueId";
     field public static final java.lang.String PROPERTY_VENDOR = "vendor";
     field public static final java.lang.String PROPERTY_VERSION = "version";
-    field public static final int REQUEST_TYPE_INITIAL = 0; // 0x0
-    field public static final int REQUEST_TYPE_RELEASE = 2; // 0x2
-    field public static final int REQUEST_TYPE_RENEWAL = 1; // 0x1
   }
 
   public final class MediaDrm.CryptoSession {
@@ -17005,11 +16998,19 @@
     method public byte[] getData();
     method public java.lang.String getDefaultUrl();
     method public int getRequestType();
+    field public static final int REQUEST_TYPE_INITIAL = 0; // 0x0
+    field public static final int REQUEST_TYPE_RELEASE = 2; // 0x2
+    field public static final int REQUEST_TYPE_RENEWAL = 1; // 0x1
   }
 
   public static final class MediaDrm.KeyStatus {
     method public byte[] getKeyId();
     method public int getStatusCode();
+    field public static final int STATUS_EXPIRED = 1; // 0x1
+    field public static final int STATUS_INTERNAL_ERROR = 4; // 0x4
+    field public static final int STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
+    field public static final int STATUS_PENDING = 3; // 0x3
+    field public static final int STATUS_USABLE = 0; // 0x0
   }
 
   public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
@@ -17024,8 +17025,8 @@
     method public abstract void onExpirationUpdate(android.media.MediaDrm, byte[], long);
   }
 
-  public static abstract interface MediaDrm.OnKeysChangeListener {
-    method public abstract void onKeysChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
+  public static abstract interface MediaDrm.OnKeyStatusChangeListener {
+    method public abstract void onKeyStatusChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
   }
 
   public static final class MediaDrm.ProvisionRequest {
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 4a8cf08..0dad4dc 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -71,6 +71,8 @@
             runHasAdoptable();
         } else if ("get-primary-storage-uuid".equals(op)) {
             runGetPrimaryStorageUuid();
+        } else if ("set-force-adoptable".equals(op)) {
+            runSetForceAdoptable();
         } else if ("partition".equals(op)) {
             runPartition();
         } else if ("mount".equals(op)) {
@@ -116,14 +118,19 @@
     }
 
     public void runHasAdoptable() {
-        System.out.println(SystemProperties.getBoolean(StorageManager.PROP_HAS_ADOPTABLE, false)
-                || SystemProperties.getBoolean(StorageManager.PROP_FORCE_ADOPTABLE, false));
+        System.out.println(SystemProperties.getBoolean(StorageManager.PROP_HAS_ADOPTABLE, false));
     }
 
     public void runGetPrimaryStorageUuid() throws RemoteException {
         System.out.println(mSm.getPrimaryStorageUuid());
     }
 
+    public void runSetForceAdoptable() throws RemoteException {
+        final boolean forceAdoptable = Boolean.parseBoolean(nextArg());
+        mSm.setDebugFlags(forceAdoptable ? StorageManager.DEBUG_FORCE_ADOPTABLE : 0,
+                StorageManager.DEBUG_FORCE_ADOPTABLE);
+    }
+
     public void runPartition() throws RemoteException {
         final String diskId = nextArg();
         final String type = nextArg();
@@ -177,6 +184,7 @@
         System.err.println("       sm list-volumes [public|private|emulated|all]");
         System.err.println("       sm has-adoptable");
         System.err.println("       sm get-primary-storage-uuid");
+        System.err.println("       sm set-force-adoptable [true|false]");
         System.err.println("");
         System.err.println("       sm partition DISK [public|private|mixed] [ratio]");
         System.err.println("       sm mount VOLUME");
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7d76760..2db623b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1535,6 +1535,22 @@
             "android.intent.action.MANAGE_APP_PERMISSIONS";
 
     /**
+     * Activity action: Launch UI to manage permissions.
+     * <p>
+     * Input: Nothing.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_PERMISSIONS =
+            "android.intent.action.MANAGE_PERMISSIONS";
+
+    /**
      * Intent extra: An app package name.
      * <p>
      * Type: String
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 4dfe0de..5f515eb 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -425,6 +425,24 @@
         public abstract long getMobileRadioActiveTime(int which);
         public abstract int getMobileRadioActiveCount(int which);
 
+        /**
+         * Get the total cpu time (in microseconds) this UID had processes executing in userspace.
+         */
+        public abstract long getUserCpuTimeUs(int which);
+
+        /**
+         * Get the total cpu time (in microseconds) this UID had processes executing kernel syscalls.
+         */
+        public abstract long getSystemCpuTimeUs(int which);
+
+        /**
+         * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed.
+         * @param speedStep the index of the CPU speed. This is not the actual speed of the CPU.
+         * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
+         * @see BatteryStats#getCpuSpeedSteps()
+         */
+        public abstract long getTimeAtCpuSpeed(int step, int which);
+
         public static abstract class Sensor {
             /*
              * FIXME: it's not correct to use this magic value because it
@@ -506,15 +524,6 @@
              */
             public abstract long getForegroundTime(int which);
 
-            /**
-             * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed.
-             * @param speedStep the index of the CPU speed. This is not the actual speed of the
-             * CPU.
-             * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
-             * @see BatteryStats#getCpuSpeedSteps()
-             */
-            public abstract long getTimeAtCpuSpeedStep(int speedStep, int which);
-
             public abstract int countExcessivePowers();
 
             public abstract ExcessivePower getExcessivePower(int i);
@@ -3873,6 +3882,16 @@
                 }
             }
 
+            final long userCpuTimeUs = u.getUserCpuTimeUs(which);
+            final long systemCpuTimeUs = u.getSystemCpuTimeUs(which);
+            if (userCpuTimeUs > 0 || systemCpuTimeUs > 0) {
+                sb.setLength(0);
+                sb.append(prefix);
+                sb.append("    Total cpu time: ");
+                formatTimeMs(sb, (userCpuTimeUs + systemCpuTimeUs) / 1000);
+                pw.println(sb.toString());
+            }
+
             final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
                     = u.getProcessStats();
             for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 2b058a8..e55ae99 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -1005,6 +1005,22 @@
             }
 
             @Override
+            public long benchmark(String volId) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeString(volId);
+                    mRemote.transact(Stub.TRANSACTION_benchmark, _data, _reply, 0);
+                    _reply.readException();
+                    return _reply.readLong();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
             public void partitionPublic(String diskId) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
@@ -1113,6 +1129,22 @@
             }
 
             @Override
+            public void setDebugFlags(int _flags, int _mask) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(_flags);
+                    _data.writeInt(_mask);
+                    mRemote.transact(Stub.TRANSACTION_setDebugFlags, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
+
+            @Override
             public String getPrimaryStorageUuid() throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
@@ -1257,6 +1289,9 @@
         static final int TRANSACTION_getPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 57;
         static final int TRANSACTION_setPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 58;
 
+        static final int TRANSACTION_benchmark = IBinder.FIRST_CALL_TRANSACTION + 59;
+        static final int TRANSACTION_setDebugFlags = IBinder.FIRST_CALL_TRANSACTION + 60;
+
         /**
          * Cast an IBinder object into an IMountService interface, generating a
          * proxy if needed.
@@ -1726,6 +1761,14 @@
                     reply.writeNoException();
                     return true;
                 }
+                case TRANSACTION_benchmark: {
+                    data.enforceInterface(DESCRIPTOR);
+                    String volId = data.readString();
+                    long res = benchmark(volId);
+                    reply.writeNoException();
+                    reply.writeLong(res);
+                    return true;
+                }
                 case TRANSACTION_partitionPublic: {
                     data.enforceInterface(DESCRIPTOR);
                     String diskId = data.readString();
@@ -1778,6 +1821,14 @@
                     reply.writeNoException();
                     return true;
                 }
+                case TRANSACTION_setDebugFlags: {
+                    data.enforceInterface(DESCRIPTOR);
+                    int _flags = data.readInt();
+                    int _mask = data.readInt();
+                    setDebugFlags(_flags, _mask);
+                    reply.writeNoException();
+                    return true;
+                }
                 case TRANSACTION_getPrimaryStorageUuid: {
                     data.enforceInterface(DESCRIPTOR);
                     String volumeUuid = getPrimaryStorageUuid();
@@ -2088,6 +2139,7 @@
     public void mount(String volId) throws RemoteException;
     public void unmount(String volId) throws RemoteException;
     public void format(String volId) throws RemoteException;
+    public long benchmark(String volId) throws RemoteException;
 
     public void partitionPublic(String diskId) throws RemoteException;
     public void partitionPrivate(String diskId) throws RemoteException;
@@ -2097,6 +2149,7 @@
     public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException;
     public void forgetVolume(String fsUuid) throws RemoteException;
     public void forgetAllVolumes() throws RemoteException;
+    public void setDebugFlags(int flags, int mask) throws RemoteException;
 
     public String getPrimaryStorageUuid() throws RemoteException;
     public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 8c0bbbf..8ff56f8 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -82,6 +82,9 @@
     /** {@hide} */
     public static final String UUID_PRIMARY_PHYSICAL = "primary_physical";
 
+    /** {@hide} */
+    public static final int DEBUG_FORCE_ADOPTABLE = 1 << 0;
+
     private final Context mContext;
     private final ContentResolver mResolver;
 
@@ -641,6 +644,15 @@
     }
 
     /** {@hide} */
+    public long benchmark(String volId) {
+        try {
+            return mMountService.benchmark(volId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
     public void partitionPublic(String diskId) {
         try {
             mMountService.partitionPublic(diskId);
diff --git a/core/java/android/service/carrier/CarrierConfigService.java b/core/java/android/service/carrier/CarrierConfigService.java
index bc42b6b..bf33ad5 100644
--- a/core/java/android/service/carrier/CarrierConfigService.java
+++ b/core/java/android/service/carrier/CarrierConfigService.java
@@ -23,14 +23,14 @@
  * A service that sets carrier configuration for telephony services.
  * <p>
  * To extend this class, you must declare the service in your manifest file to require the
- * {@link android.Manifest.permission#BIND_CARRIER_CONFIG_SERVICE} permission and include an intent
+ * {@link android.Manifest.permission#BIND_CARRIER_SERVICES} permission and include an intent
  * filter with the {@link #SERVICE_INTERFACE} action. For example:
  * </p>
  *
  * <pre>{@code
  * <service android:name=".MyCarrierConfigService"
  *       android:label="@string/service_name"
- *       android:permission="android.permission.BIND_CARRIER_CONFIG_SERVICE">
+ *       android:permission="android.permission.BIND_CARRIER_SERVICES">
  *  <intent-filter>
  *      <action android:name="android.service.carrier.CarrierConfigService" />
  *  </intent-filter>
diff --git a/core/java/android/service/carrier/CarrierMessagingService.java b/core/java/android/service/carrier/CarrierMessagingService.java
index d7bf10c..f5396a3 100644
--- a/core/java/android/service/carrier/CarrierMessagingService.java
+++ b/core/java/android/service/carrier/CarrierMessagingService.java
@@ -31,12 +31,12 @@
  * A service that receives calls from the system when new SMS and MMS are
  * sent or received.
  * <p>To extend this class, you must declare the service in your manifest file with
- * the {@link android.Manifest.permission#BIND_CARRIER_MESSAGING_SERVICE} permission
+ * the {@link android.Manifest.permission#BIND_CARRIER_SERVICES} permission
  * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
  * <pre>
  * &lt;service android:name=".MyMessagingService"
  *          android:label="&#64;string/service_name"
- *          android:permission="android.permission.BIND_CARRIER_MESSAGING_SERVICE">
+ *          android:permission="android.permission.BIND_CARRIER_SERVICES">
  *     &lt;intent-filter>
  *         &lt;action android:name="android.service.carrier.CarrierMessagingService" />
  *     &lt;/intent-filter>
diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java
index 2a2589a..7ea9da1 100644
--- a/core/java/android/text/BidiFormatter.java
+++ b/core/java/android/text/BidiFormatter.java
@@ -25,7 +25,7 @@
 /**
  * Utility class for formatting text for display in a potentially opposite-directionality context
  * without garbling. The directionality of the context is set at formatter creation and the
- * directionality of the text can be either estimated or passed in when known. 
+ * directionality of the text can be either estimated or passed in when known.
  *
  * <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
  * you can use the support library's {@link android.support.v4.text.BidiFormatter} class.
@@ -377,9 +377,11 @@
      *        See {@link TextDirectionHeuristics} for pre-defined heuristics.
      * @param isolate Whether to directionally isolate the string to prevent it from garbling the
      *     content around it
-     * @return Input string after applying the above processing.
+     * @return Input string after applying the above processing. {@code null} if {@code str} is
+     *     {@code null}.
      */
     public String unicodeWrap(String str, TextDirectionHeuristic heuristic, boolean isolate) {
+        if (str == null) return null;
         final boolean isRtl = heuristic.isRtl(str, 0, str.length());
         StringBuilder result = new StringBuilder();
         if (getStereoReset() && isolate) {
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 9782d72..b7d529e 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -40,7 +40,10 @@
 import dalvik.system.VMRuntime;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Arrays;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
 /**
  * Top level factory, used creating all the main WebView implementation classes.
@@ -323,15 +326,30 @@
                 long newVmSize = 0L;
 
                 for (String path : nativeLibs) {
+                    if (path == null || TextUtils.isEmpty(path)) continue;
                     if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
-                    if (path == null) continue;
                     File f = new File(path);
                     if (f.exists()) {
-                        long length = f.length();
-                        if (length > newVmSize) {
-                            newVmSize = length;
+                        newVmSize = Math.max(newVmSize, f.length());
+                        continue;
+                    }
+                    if (path.contains("!")) {
+                        String[] split = TextUtils.split(path, "!");
+                        if (split.length == 2) {
+                            try {
+                                ZipFile z = new ZipFile(split[0]);
+                                ZipEntry e = z.getEntry(split[1]);
+                                if (e != null && e.getMethod() == ZipEntry.STORED) {
+                                    newVmSize = Math.max(newVmSize, e.getSize());
+                                    continue;
+                                }
+                            }
+                            catch (IOException e) {
+                                Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e);
+                            }
                         }
                     }
+                    Log.e(LOGTAG, "error sizing load for " + path);
                 }
 
                 if (DEBUG) {
@@ -355,6 +373,27 @@
     }
 
     // throws MissingWebViewPackageException
+    private static String getLoadFromApkPath(String apkPath,
+                                             String[] abiList,
+                                             String nativeLibFileName) {
+        // Search the APK for a native library conforming to a listed ABI.
+        try {
+            ZipFile z = new ZipFile(apkPath);
+            for (String abi : abiList) {
+                final String entry = "lib/" + abi + "/" + nativeLibFileName;
+                ZipEntry e = z.getEntry(entry);
+                if (e != null && e.getMethod() == ZipEntry.STORED) {
+                    // Return a path formatted for dlopen() load from APK.
+                    return apkPath + "!" + entry;
+                }
+            }
+        } catch (IOException e) {
+            throw new MissingWebViewPackageException(e);
+        }
+        return "";
+    }
+
+    // throws MissingWebViewPackageException
     private static String[] getWebViewNativeLibraryPaths() {
         ApplicationInfo ai = getWebViewApplicationInfo();
         final String NATIVE_LIB_FILE_NAME = getWebViewLibrary(ai);
@@ -382,8 +421,29 @@
             path32 = ai.nativeLibraryDir;
             path64 = "";
         }
-        if (!TextUtils.isEmpty(path32)) path32 += "/" + NATIVE_LIB_FILE_NAME;
-        if (!TextUtils.isEmpty(path64)) path64 += "/" + NATIVE_LIB_FILE_NAME;
+
+        // Form the full paths to the extracted native libraries.
+        // If libraries were not extracted, try load from APK paths instead.
+        if (!TextUtils.isEmpty(path32)) {
+            path32 += "/" + NATIVE_LIB_FILE_NAME;
+            File f = new File(path32);
+            if (!f.exists()) {
+                path32 = getLoadFromApkPath(ai.sourceDir,
+                                            Build.SUPPORTED_32_BIT_ABIS,
+                                            NATIVE_LIB_FILE_NAME);
+            }
+        }
+        if (!TextUtils.isEmpty(path64)) {
+            path64 += "/" + NATIVE_LIB_FILE_NAME;
+            File f = new File(path64);
+            if (!f.exists()) {
+                path64 = getLoadFromApkPath(ai.sourceDir,
+                                            Build.SUPPORTED_64_BIT_ABIS,
+                                            NATIVE_LIB_FILE_NAME);
+            }
+        }
+
+        if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64);
         return new String[] { path32, path64 };
     }
 
diff --git a/core/java/android/widget/DayPickerPagerAdapter.java b/core/java/android/widget/DayPickerPagerAdapter.java
index d271af2..8fe8252 100644
--- a/core/java/android/widget/DayPickerPagerAdapter.java
+++ b/core/java/android/widget/DayPickerPagerAdapter.java
@@ -186,11 +186,12 @@
     }
 
     private int getMonthForPosition(int position) {
-        return position % MONTHS_IN_YEAR + mMinDate.get(Calendar.MONTH);
+        return (position + mMinDate.get(Calendar.MONTH)) % MONTHS_IN_YEAR;
     }
 
     private int getYearForPosition(int position) {
-        return position / MONTHS_IN_YEAR + mMinDate.get(Calendar.YEAR);
+        final int yearOffset = (position + mMinDate.get(Calendar.MONTH)) / MONTHS_IN_YEAR;
+        return yearOffset + mMinDate.get(Calendar.YEAR);
     }
 
     private int getPositionForDay(@Nullable Calendar day) {
@@ -198,8 +199,8 @@
             return -1;
         }
 
-        final int yearOffset = (day.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR));
-        final int monthOffset = (day.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH));
+        final int yearOffset = day.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR);
+        final int monthOffset = day.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH);
         final int position = yearOffset * MONTHS_IN_YEAR + monthOffset;
         return position;
     }
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index a434317..dc772fb 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -176,8 +176,6 @@
                 }
             }
         });
-
-        updateButtonVisibility(mViewPager.getCurrentItem());
     }
 
     private void updateButtonVisibility(int position) {
@@ -346,6 +344,8 @@
         // Changing the min/max date changes the selection position since we
         // don't really have stable IDs. Jumps immediately to the new position.
         setDate(mSelectedDay.getTimeInMillis(), false, false);
+
+        updateButtonVisibility(mViewPager.getCurrentItem());
     }
 
     /**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 1d6f6dc..c829783 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3426,9 +3426,13 @@
         protected void updateDrawable() {
             final int offset = getCurrentCursorOffset();
             final boolean isRtlCharAtOffset = mTextView.getLayout().isRtlCharAt(offset);
+            final Drawable oldDrawable = mDrawable;
             mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
             mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset);
             mHorizontalGravity = getHorizontalGravity(isRtlCharAtOffset);
+            if (oldDrawable != mDrawable) {
+                postInvalidate();
+            }
         }
 
         protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun);
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 80bc5fe..49230c1 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -45,33 +45,11 @@
 import android.widget.ImageView;
 
 public class PlatLogoActivity extends Activity {
-    final static int[] FLAVORS = {
-            0xFF9C27B0, 0xFFBA68C8, // grape
-            0xFFFF9800, 0xFFFFB74D, // orange
-            0xFFF06292, 0xFFF8BBD0, // bubblegum
-            0xFFAFB42B, 0xFFCDDC39, // lime
-            0xFFFFEB3B, 0xFFFFF176, // lemon
-            0xFF795548, 0xFFA1887F, // mystery flavor
-    };
     FrameLayout mLayout;
     int mTapCount;
     int mKeyCount;
     PathInterpolator mInterpolator = new PathInterpolator(0f, 0f, 0.5f, 1f);
 
-    static int newColorIndex() {
-        return 2*((int) (Math.random()*FLAVORS.length/2));
-    }
-
-    Drawable makeRipple() {
-        final int idx = newColorIndex();
-        final ShapeDrawable popbg = new ShapeDrawable(new OvalShape());
-        popbg.getPaint().setColor(FLAVORS[idx]);
-        final RippleDrawable ripple = new RippleDrawable(
-                ColorStateList.valueOf(FLAVORS[idx+1]),
-                popbg, null);
-        return ripple;
-    }
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -87,120 +65,62 @@
         final int size = (int)
                 (Math.min(Math.min(dm.widthPixels, dm.heightPixels), 600*dp) - 100*dp);
 
-        final View stick = new View(this) {
-            Paint mPaint = new Paint();
-            Path mShadow = new Path();
-
-            @Override
-            public void onAttachedToWindow() {
-                super.onAttachedToWindow();
-                setWillNotDraw(false);
-                setOutlineProvider(new ViewOutlineProvider() {
-                    @Override
-                    public void getOutline(View view, Outline outline) {
-                        outline.setRect(0, getHeight() / 2, getWidth(), getHeight());
-                    }
-                });
-            }
-            @Override
-            public void onDraw(Canvas c) {
-                final int w = c.getWidth();
-                final int h = c.getHeight() / 2;
-                c.translate(0, h);
-                final GradientDrawable g = new GradientDrawable();
-                g.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT);
-                g.setGradientCenter(w * 0.75f, 0);
-                g.setColors(new int[] { 0xFFFFFFFF, 0xFFAAAAAA });
-                g.setBounds(0, 0, w, h);
-                g.draw(c);
-                mPaint.setColor(0xFFAAAAAA);
-                mShadow.reset();
-                mShadow.moveTo(0,0);
-                mShadow.lineTo(w, 0);
-                mShadow.lineTo(w, size/2 + 1.5f*w);
-                mShadow.lineTo(0, size/2);
-                mShadow.close();
-                c.drawPath(mShadow, mPaint);
-            }
-        };
-        mLayout.addView(stick, new FrameLayout.LayoutParams((int) (32 * dp),
-                ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER_HORIZONTAL));
-        stick.setAlpha(0f);
-
-        final ImageView im = new ImageView(this);
+        final View im = new View(this);
         im.setTranslationZ(20);
-        im.setScaleX(0);
-        im.setScaleY(0);
+        im.setScaleX(0.5f);
+        im.setScaleY(0.5f);
+        im.setAlpha(0f);
+        im.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                final int pad = (int) (8*dp);
+                outline.setOval(pad, pad, view.getWidth()-pad, view.getHeight()-pad);
+            }
+        });
         final Drawable platlogo = getDrawable(com.android.internal.R.drawable.platlogo);
-        platlogo.setAlpha(0);
-        im.setImageDrawable(platlogo);
-        im.setBackground(makeRipple());
+        im.setBackground(new RippleDrawable(
+                ColorStateList.valueOf(0xFFFFFFFF),
+                platlogo,
+                null));
         im.setClickable(true);
-        final ShapeDrawable highlight = new ShapeDrawable(new OvalShape());
-        highlight.getPaint().setColor(0x10FFFFFF);
-        highlight.setBounds((int)(size*.15f), (int)(size*.15f),
-                            (int)(size*.6f), (int)(size*.6f));
-        im.getOverlay().add(highlight);
         im.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                if (mTapCount == 0) {
-                    im.animate()
-                            .translationZ(40)
-                            .scaleX(1)
-                            .scaleY(1)
-                            .setInterpolator(mInterpolator)
-                            .setDuration(700)
-                            .setStartDelay(500)
-                            .start();
+                im.setOnLongClickListener(new View.OnLongClickListener() {
+                    @Override
+                    public boolean onLongClick(View v) {
+                        if (mTapCount < 5) return false;
 
-                    final ObjectAnimator a = ObjectAnimator.ofInt(platlogo, "alpha", 0, 255);
-                    a.setInterpolator(mInterpolator);
-                    a.setStartDelay(1000);
-                    a.start();
-
-                    stick.animate()
-                            .translationZ(20)
-                            .alpha(1)
-                            .setInterpolator(mInterpolator)
-                            .setDuration(700)
-                            .setStartDelay(750)
-                            .start();
-
-                    im.setOnLongClickListener(new View.OnLongClickListener() {
-                        @Override
-                        public boolean onLongClick(View v) {
-                            if (mTapCount < 5) return false;
-
-                            final ContentResolver cr = getContentResolver();
-                            if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0)
-                                    == 0) {
-                                // For posterity: the moment this user unlocked the easter egg
+                        final ContentResolver cr = getContentResolver();
+                        if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0)
+                                == 0) {
+                            // For posterity: the moment this user unlocked the easter egg
+                            try {
                                 Settings.System.putLong(cr,
                                         Settings.System.EGG_MODE,
                                         System.currentTimeMillis());
+                            } catch (RuntimeException e) {
+                                Log.e("PlatLogoActivity", "Can't write settings", e);
                             }
-                            im.post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    try {
-                                        startActivity(new Intent(Intent.ACTION_MAIN)
-                                                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                                                        | Intent.FLAG_ACTIVITY_CLEAR_TASK
-                                                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
-                                                .addCategory("com.android.internal.category.PLATLOGO"));
-                                    } catch (ActivityNotFoundException ex) {
-                                        Log.e("PlatLogoActivity", "No more eggs.");
-                                    }
-                                    finish();
-                                }
-                            });
-                            return true;
                         }
-                    });
-                } else {
-                    im.setBackground(makeRipple());
-                }
+                        im.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                try {
+                                    startActivity(new Intent(Intent.ACTION_MAIN)
+                                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                                                    | Intent.FLAG_ACTIVITY_CLEAR_TASK
+                                                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                                            .addCategory("com.android.internal.category.PLATLOGO"));
+                                } catch (ActivityNotFoundException ex) {
+                                    Log.e("PlatLogoActivity", "No more eggs.");
+                                }
+                                finish();
+                            }
+                        });
+                        return true;
+                    }
+                });
                 mTapCount++;
             }
         });
@@ -229,7 +149,7 @@
 
         mLayout.addView(im, new FrameLayout.LayoutParams(size, size, Gravity.CENTER));
 
-        im.animate().scaleX(0.3f).scaleY(0.3f)
+        im.animate().scaleX(1f).scaleY(1f).alpha(1f)
                 .setInterpolator(mInterpolator)
                 .setDuration(500)
                 .setStartDelay(800)
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index a53d46c..fbe87c5 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -615,6 +615,7 @@
                 mStatsType);
         if (bs.sumPower() > 0) {
             aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
+            mUsageList.add(bs);
         }
     }
 
@@ -742,7 +743,6 @@
                     parcel.setDataPosition(0);
                     BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
                             .createFromParcel(parcel);
-                    stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
                     return stats;
                 } catch (IOException e) {
                     Log.w(TAG, "Unable to read statistics stream", e);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index c2212f7..eaca43b 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -105,7 +105,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 125 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 126 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -132,6 +132,9 @@
     private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
 
+    private final KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
+    private final KernelCpuSpeedReader mKernelCpuSpeedReader = new KernelCpuSpeedReader();
+
     public interface BatteryCallback {
         public void batteryNeedsCpuUpdate();
         public void batteryPowerChanged(boolean onBattery);
@@ -794,20 +797,6 @@
         }
     }
 
-    public static class SamplingCounter extends Counter {
-        SamplingCounter(TimeBase timeBase, Parcel in) {
-            super(timeBase, in);
-        }
-
-        SamplingCounter(TimeBase timeBase) {
-            super(timeBase);
-        }
-
-        public void addCountAtomic(long count) {
-            mCount.addAndGet((int)count);
-        }
-    }
-
     public static class LongSamplingCounter extends LongCounter implements TimeBaseObs {
         final TimeBase mTimeBase;
         long mCount;
@@ -2918,8 +2907,7 @@
 
     public void finishAddingCpuLocked(int perc, int remainUTime, int remainSTtime,
             int totalUTime, int totalSTime, int statUserTime, int statSystemTime,
-            int statIOWaitTime, int statIrqTime, int statSoftIrqTime, int statIdleTime,
-            long[] cpuSpeedTimes) {
+            int statIOWaitTime, int statIrqTime, int statSoftIrqTime, int statIdleTime) {
         if (DEBUG) Slog.d(TAG, "Adding cpu: tuser=" + totalUTime + " tsys=" + totalSTime
                 + " user=" + statUserTime + " sys=" + statSystemTime
                 + " io=" + statIOWaitTime + " irq=" + statIrqTime
@@ -2959,7 +2947,7 @@
                             remainSTtime -= mySTime;
                             num--;
                             Uid.Proc proc = uid.getProcessStatsLocked("*wakelock*");
-                            proc.addCpuTimeLocked(myUTime, mySTime, cpuSpeedTimes);
+                            proc.addCpuTimeLocked(myUTime, mySTime);
                         }
                     }
                 }
@@ -2970,7 +2958,7 @@
                 Uid uid = getUidStatsLocked(Process.SYSTEM_UID);
                 if (uid != null) {
                     Uid.Proc proc = uid.getProcessStatsLocked("*lost*");
-                    proc.addCpuTimeLocked(remainUTime, remainSTtime, cpuSpeedTimes);
+                    proc.addCpuTimeLocked(remainUTime, remainSTtime);
                 }
             }
         }
@@ -4398,6 +4386,10 @@
         long mCurStepUserTime;
         long mCurStepSystemTime;
 
+        LongSamplingCounter mUserCpuTime = new LongSamplingCounter(mOnBatteryTimeBase);
+        LongSamplingCounter mSystemCpuTime = new LongSamplingCounter(mOnBatteryTimeBase);
+        LongSamplingCounter[] mSpeedBins;
+
         /**
          * The statistics we have collected for this uid's wake locks.
          */
@@ -4455,6 +4447,7 @@
             mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
                     mWifiMulticastTimers, mOnBatteryTimeBase);
             mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
+            mSpeedBins = new LongSamplingCounter[getCpuSpeedSteps()];
         }
 
         @Override
@@ -4925,6 +4918,26 @@
         }
 
         @Override
+        public long getUserCpuTimeUs(int which) {
+            return mUserCpuTime.getCountLocked(which);
+        }
+
+        @Override
+        public long getSystemCpuTimeUs(int which) {
+            return mSystemCpuTime.getCountLocked(which);
+        }
+
+        @Override
+        public long getTimeAtCpuSpeed(int step, int which) {
+            if (step >= 0 && step < mSpeedBins.length) {
+                if (mSpeedBins[step] != null) {
+                    return mSpeedBins[step].getCountLocked(which);
+                }
+            }
+            return 0;
+        }
+
+        @Override
         public long getWifiControllerActivity(int type, int which) {
             if (type >= 0 && type < NUM_CONTROLLER_ACTIVITY_TYPES &&
                     mWifiControllerTime[type] != null) {
@@ -5026,6 +5039,15 @@
                 }
             }
 
+            mUserCpuTime.reset(false);
+            mSystemCpuTime.reset(false);
+            for (int i = 0; i < mSpeedBins.length; i++) {
+                LongSamplingCounter c = mSpeedBins[i];
+                if (c != null) {
+                    c.reset(false);
+                }
+            }
+
             final ArrayMap<String, Wakelock> wakeStats = mWakelockStats.getMap();
             for (int iw=wakeStats.size()-1; iw>=0; iw--) {
                 Wakelock wl = wakeStats.valueAt(iw);
@@ -5159,6 +5181,15 @@
                     }
                 }
                 mPids.clear();
+
+                mUserCpuTime.detach();
+                mSystemCpuTime.detach();
+                for (int i = 0; i < mSpeedBins.length; i++) {
+                    LongSamplingCounter c = mSpeedBins[i];
+                    if (c != null) {
+                        c.detach();
+                    }
+                }
             }
 
             return !active;
@@ -5317,6 +5348,20 @@
                     out.writeInt(0);
                 }
             }
+
+            mUserCpuTime.writeToParcel(out);
+            mSystemCpuTime.writeToParcel(out);
+
+            out.writeInt(mSpeedBins.length);
+            for (int i = 0; i < mSpeedBins.length; i++) {
+                LongSamplingCounter c = mSpeedBins[i];
+                if (c != null) {
+                    out.writeInt(1);
+                    c.writeToParcel(out);
+                } else {
+                    out.writeInt(0);
+                }
+            }
         }
 
         void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
@@ -5482,6 +5527,18 @@
                     mBluetoothControllerTime[i] = null;
                 }
             }
+
+            mUserCpuTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
+            mSystemCpuTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
+
+            int bins = in.readInt();
+            int steps = getCpuSpeedSteps();
+            mSpeedBins = new LongSamplingCounter[bins >= steps ? bins : steps];
+            for (int i = 0; i < bins; i++) {
+                if (in.readInt() != 0) {
+                    mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+                }
+            }
         }
 
         /**
@@ -5762,14 +5819,11 @@
              */
             int mProcessState = PROCESS_STATE_NONE;
 
-            SamplingCounter[] mSpeedBins;
-
             ArrayList<ExcessivePower> mExcessivePower;
 
             Proc(String name) {
                 mName = name;
                 mOnBatteryTimeBase.add(this);
-                mSpeedBins = new SamplingCounter[getCpuSpeedSteps()];
             }
 
             public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
@@ -5791,25 +5845,12 @@
                 mLoadedStarts = mLoadedNumCrashes = mLoadedNumAnrs = 0;
                 mUnpluggedUserTime = mUnpluggedSystemTime = mUnpluggedForegroundTime = 0;
                 mUnpluggedStarts = mUnpluggedNumCrashes = mUnpluggedNumAnrs = 0;
-                for (int i = 0; i < mSpeedBins.length; i++) {
-                    SamplingCounter c = mSpeedBins[i];
-                    if (c != null) {
-                        c.reset(false);
-                    }
-                }
                 mExcessivePower = null;
             }
 
             void detach() {
                 mActive = false;
                 mOnBatteryTimeBase.remove(this);
-                for (int i = 0; i < mSpeedBins.length; i++) {
-                    SamplingCounter c = mSpeedBins[i];
-                    if (c != null) {
-                        mOnBatteryTimeBase.remove(c);
-                        mSpeedBins[i] = null;
-                    }
-                }
             }
 
             public int countExcessivePowers() {
@@ -5903,18 +5944,6 @@
                 out.writeInt(mUnpluggedStarts);
                 out.writeInt(mUnpluggedNumCrashes);
                 out.writeInt(mUnpluggedNumAnrs);
-
-                out.writeInt(mSpeedBins.length);
-                for (int i = 0; i < mSpeedBins.length; i++) {
-                    SamplingCounter c = mSpeedBins[i];
-                    if (c != null) {
-                        out.writeInt(1);
-                        c.writeToParcel(out);
-                    } else {
-                        out.writeInt(0);
-                    }
-                }
-
                 writeExcessivePowerToParcelLocked(out);
             }
 
@@ -5937,39 +5966,12 @@
                 mUnpluggedStarts = in.readInt();
                 mUnpluggedNumCrashes = in.readInt();
                 mUnpluggedNumAnrs = in.readInt();
-
-                int bins = in.readInt();
-                int steps = getCpuSpeedSteps();
-                mSpeedBins = new SamplingCounter[bins >= steps ? bins : steps];
-                for (int i = 0; i < bins; i++) {
-                    if (in.readInt() != 0) {
-                        mSpeedBins[i] = new SamplingCounter(mOnBatteryTimeBase, in);
-                    }
-                }
-
                 readExcessivePowerFromParcelLocked(in);
             }
 
-            public BatteryStatsImpl getBatteryStats() {
-                return BatteryStatsImpl.this;
-            }
-
-            public void addCpuTimeLocked(int utime, int stime, long[] speedStepBins) {
+            public void addCpuTimeLocked(int utime, int stime) {
                 mUserTime += utime;
-                mCurStepUserTime += utime;
                 mSystemTime += stime;
-                mCurStepSystemTime += stime;
-
-                for (int i = 0; i < mSpeedBins.length && i < speedStepBins.length; i++) {
-                    long amt = speedStepBins[i];
-                    if (amt != 0) {
-                        SamplingCounter c = mSpeedBins[i];
-                        if (c == null) {
-                            mSpeedBins[i] = c = new SamplingCounter(mOnBatteryTimeBase);
-                        }
-                        c.addCountAtomic(speedStepBins[i]);
-                    }
-                }
             }
 
             public void addForegroundTimeLocked(long ttime) {
@@ -6058,16 +6060,6 @@
                 }
                 return val;
             }
-
-            @Override
-            public long getTimeAtCpuSpeedStep(int speedStep, int which) {
-                if (speedStep < mSpeedBins.length) {
-                    SamplingCounter c = mSpeedBins[speedStep];
-                    return c != null ? c.getCountLocked(which) : 0;
-                } else {
-                    return 0;
-                }
-            }
         }
 
         /**
@@ -7729,7 +7721,7 @@
      * @param info The energy information from the bluetooth controller.
      */
     public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) {
-        if (info != null && mOnBatteryInternal && false) {
+        if (info != null && mOnBatteryInternal) {
             mHasBluetoothEnergyReporting = true;
             mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
                     info.getControllerRxTimeMillis());
@@ -7785,6 +7777,32 @@
         }
     }
 
+    /**
+     * Read and distribute CPU usage across apps.
+     */
+    public void updateCpuTimeLocked(boolean firstTime) {
+        final int cpuSpeedSteps = getCpuSpeedSteps();
+        final long[] cpuSpeeds = mKernelCpuSpeedReader.readDelta();
+        KernelUidCpuTimeReader.Callback callback = null;
+        if (mOnBatteryInternal && !firstTime) {
+            callback = new KernelUidCpuTimeReader.Callback() {
+                @Override
+                public void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs) {
+                    final Uid u = getUidStatsLocked(mapUid(uid));
+                    u.mUserCpuTime.addCountLocked(userTimeUs);
+                    u.mSystemCpuTime.addCountLocked(systemTimeUs);
+                    for (int i = 0; i < cpuSpeedSteps; i++) {
+                        if (u.mSpeedBins[i] == null) {
+                            u.mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+                        }
+                        u.mSpeedBins[i].addCountLocked(cpuSpeeds[i]);
+                    }
+                }
+            };
+        }
+        mKernelUidCpuTimeReader.readDelta(callback);
+    }
+
     boolean setChargingLocked(boolean charging) {
         if (mCharging != charging) {
             mCharging = charging;
@@ -8407,6 +8425,7 @@
      * Remove the statistics object for a particular uid.
      */
     public void removeUidStatsLocked(int uid) {
+        mKernelUidCpuTimeReader.removeUid(uid);
         mUidStats.remove(uid);
     }
 
@@ -8440,58 +8459,6 @@
         return u.getServiceStatsLocked(pkg, name);
     }
 
-    /**
-     * Massage data to distribute any reasonable work down to more specific
-     * owners.  Must only be called on a dead BatteryStats object!
-     */
-    public void distributeWorkLocked(int which) {
-        // Aggregate all CPU time associated with WIFI.
-        Uid wifiUid = mUidStats.get(Process.WIFI_UID);
-        if (wifiUid != null) {
-            long uSecTime = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which);
-            for (int ip=wifiUid.mProcessStats.size()-1; ip>=0; ip--) {
-                Uid.Proc proc = wifiUid.mProcessStats.valueAt(ip);
-                long totalRunningTime = getGlobalWifiRunningTime(uSecTime, which);
-                for (int i=0; i<mUidStats.size(); i++) {
-                    Uid uid = mUidStats.valueAt(i);
-                    if (uid.mUid != Process.WIFI_UID) {
-                        long uidRunningTime = uid.getWifiRunningTime(uSecTime, which);
-                        if (uidRunningTime > 0) {
-                            Uid.Proc uidProc = uid.getProcessStatsLocked("*wifi*");
-                            long time = proc.getUserTime(which);
-                            time = (time*uidRunningTime)/totalRunningTime;
-                            uidProc.mUserTime += time;
-                            proc.mUserTime -= time;
-                            time = proc.getSystemTime(which);
-                            time = (time*uidRunningTime)/totalRunningTime;
-                            uidProc.mSystemTime += time;
-                            proc.mSystemTime -= time;
-                            time = proc.getForegroundTime(which);
-                            time = (time*uidRunningTime)/totalRunningTime;
-                            uidProc.mForegroundTime += time;
-                            proc.mForegroundTime -= time;
-                            for (int sb=0; sb<proc.mSpeedBins.length; sb++) {
-                                SamplingCounter sc = proc.mSpeedBins[sb];
-                                if (sc != null) {
-                                    time = sc.getCountLocked(which);
-                                    time = (time*uidRunningTime)/totalRunningTime;
-                                    SamplingCounter uidSc = uidProc.mSpeedBins[sb];
-                                    if (uidSc == null) {
-                                        uidSc = new SamplingCounter(mOnBatteryTimeBase);
-                                        uidProc.mSpeedBins[sb] = uidSc;
-                                    }
-                                    uidSc.mCount.addAndGet((int)time);
-                                    sc.mCount.addAndGet((int)-time);
-                                }
-                            }
-                            totalRunningTime -= uidRunningTime;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
     public void shutdownLocked() {
         recordShutdownLocked(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
         writeSyncLocked();
@@ -8950,6 +8917,23 @@
                 u.mMobileRadioActiveCount.readSummaryFromParcelLocked(in);
             }
 
+            u.mUserCpuTime.readSummaryFromParcelLocked(in);
+            u.mSystemCpuTime.readSummaryFromParcelLocked(in);
+
+            int NSB = in.readInt();
+            if (NSB > 100) {
+                Slog.w(TAG, "File corrupt: too many speed bins " + NSB);
+                return;
+            }
+
+            u.mSpeedBins = new LongSamplingCounter[NSB];
+            for (int i=0; i<NSB; i++) {
+                if (in.readInt() != 0) {
+                    u.mSpeedBins[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+                    u.mSpeedBins[i].readSummaryFromParcelLocked(in);
+                }
+            }
+
             int NW = in.readInt();
             if (NW > 100) {
                 Slog.w(TAG, "File corrupt: too many wake locks " + NW);
@@ -9007,18 +8991,6 @@
                 p.mStarts = p.mLoadedStarts = in.readInt();
                 p.mNumCrashes = p.mLoadedNumCrashes = in.readInt();
                 p.mNumAnrs = p.mLoadedNumAnrs = in.readInt();
-                int NSB = in.readInt();
-                if (NSB > 100) {
-                    Slog.w(TAG, "File corrupt: too many speed bins " + NSB);
-                    return;
-                }
-                p.mSpeedBins = new SamplingCounter[NSB];
-                for (int i=0; i<NSB; i++) {
-                    if (in.readInt() != 0) {
-                        p.mSpeedBins[i] = new SamplingCounter(mOnBatteryTimeBase);
-                        p.mSpeedBins[i].readSummaryFromParcelLocked(in);
-                    }
-                }
                 if (!p.readExcessivePowerFromParcelLocked(in)) {
                     return;
                 }
@@ -9278,6 +9250,20 @@
                 u.mMobileRadioActiveCount.writeSummaryFromParcelLocked(out);
             }
 
+            u.mUserCpuTime.writeSummaryFromParcelLocked(out);
+            u.mSystemCpuTime.writeSummaryFromParcelLocked(out);
+
+            out.writeInt(u.mSpeedBins.length);
+            for (int i = 0; i < u.mSpeedBins.length; i++) {
+                LongSamplingCounter speedBin = u.mSpeedBins[i];
+                if (speedBin != null) {
+                    out.writeInt(1);
+                    speedBin.writeSummaryFromParcelLocked(out);
+                } else {
+                    out.writeInt(0);
+                }
+            }
+
             final ArrayMap<String, Uid.Wakelock> wakeStats = u.mWakelockStats.getMap();
             int NW = wakeStats.size();
             out.writeInt(NW);
@@ -9344,16 +9330,6 @@
                 out.writeInt(ps.mStarts);
                 out.writeInt(ps.mNumCrashes);
                 out.writeInt(ps.mNumAnrs);
-                final int N = ps.mSpeedBins.length;
-                out.writeInt(N);
-                for (int i=0; i<N; i++) {
-                    if (ps.mSpeedBins[i] != null) {
-                        out.writeInt(1);
-                        ps.mSpeedBins[i].writeSummaryFromParcelLocked(out);
-                    } else {
-                        out.writeInt(0);
-                    }
-                }
                 ps.writeExcessivePowerToParcelLocked(out);
             }
 
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 6c3f958..a3ef612 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -44,55 +44,57 @@
                              long rawUptimeUs, int statsType) {
         final int speedSteps = mSpeedStepTimes.length;
 
+        long totalTimeAtSpeeds = 0;
+        for (int step = 0; step < speedSteps; step++) {
+            mSpeedStepTimes[step] = u.getTimeAtCpuSpeed(step, statsType);
+            totalTimeAtSpeeds += mSpeedStepTimes[step];
+        }
+        totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1);
+
+        app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;
+        if (DEBUG && app.cpuTimeMs != 0) {
+            Log.d(TAG, "UID " + u.getUid() + ": CPU time " + app.cpuTimeMs + " ms");
+        }
+
+        double cpuPowerMaMs = 0;
+        for (int step = 0; step < speedSteps; step++) {
+            final double ratio = (double) mSpeedStepTimes[step] / totalTimeAtSpeeds;
+            final double cpuSpeedStepPower = ratio * app.cpuTimeMs * mPowerCpuNormal[step];
+            if (DEBUG && ratio != 0) {
+                Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
+                        + step + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power="
+                        + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
+            }
+            cpuPowerMaMs += cpuSpeedStepPower;
+        }
+
+        if (DEBUG && cpuPowerMaMs != 0) {
+            Log.d(TAG, "UID " + u.getUid() + ": cpu total power="
+                    + BatteryStatsHelper.makemAh(cpuPowerMaMs / (60 * 60 * 1000)));
+        }
+
         // Keep track of the package with highest drain.
         double highestDrain = 0;
 
+        app.cpuFgTimeMs = 0;
         final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
         final int processStatsCount = processStats.size();
         for (int i = 0; i < processStatsCount; i++) {
             final BatteryStats.Uid.Proc ps = processStats.valueAt(i);
             final String processName = processStats.keyAt(i);
-
             app.cpuFgTimeMs += ps.getForegroundTime(statsType);
-            final long totalCpuTime = ps.getUserTime(statsType) + ps.getSystemTime(statsType);
-            app.cpuTimeMs += totalCpuTime;
 
-            // Calculate the total CPU time spent at the various speed steps.
-            long totalTimeAtSpeeds = 0;
-            for (int step = 0; step < speedSteps; step++) {
-                mSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, statsType);
-                totalTimeAtSpeeds += mSpeedStepTimes[step];
-            }
-            totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1);
-
-            // Then compute the ratio of time spent at each speed and figure out
-            // the total power consumption.
-            double cpuPower = 0;
-            for (int step = 0; step < speedSteps; step++) {
-                final double ratio = (double) mSpeedStepTimes[step] / totalTimeAtSpeeds;
-                final double cpuSpeedStepPower = ratio * totalCpuTime * mPowerCpuNormal[step];
-                if (DEBUG && ratio != 0) {
-                    Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
-                            + step + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power="
-                            + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
-                }
-                cpuPower += cpuSpeedStepPower;
-            }
-
-            if (DEBUG && cpuPower != 0) {
-                Log.d(TAG, String.format("process %s, cpu power=%s",
-                        processName, BatteryStatsHelper.makemAh(cpuPower / (60 * 60 * 1000))));
-            }
-            app.cpuPowerMah += cpuPower;
+            final long costValue = ps.getUserTime(statsType) + ps.getSystemTime(statsType)
+                    + ps.getForegroundTime(statsType);
 
             // Each App can have multiple packages and with multiple running processes.
             // Keep track of the package who's process has the highest drain.
             if (app.packageWithHighestDrain == null ||
                     app.packageWithHighestDrain.startsWith("*")) {
-                highestDrain = cpuPower;
+                highestDrain = costValue;
                 app.packageWithHighestDrain = processName;
-            } else if (highestDrain < cpuPower && !processName.startsWith("*")) {
-                highestDrain = cpuPower;
+            } else if (highestDrain < costValue && !processName.startsWith("*")) {
+                highestDrain = costValue;
                 app.packageWithHighestDrain = processName;
             }
         }
@@ -108,6 +110,6 @@
         }
 
         // Convert the CPU power to mAh
-        app.cpuPowerMah /= (60 * 60 * 1000);
+        app.cpuPowerMah = cpuPowerMaMs / (60 * 60 * 1000);
     }
 }
diff --git a/core/java/com/android/internal/os/KernelCpuSpeedReader.java b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
new file mode 100644
index 0000000..c30df28
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Reads CPU time spent at various frequencies and provides a delta from the last call to
+ * {@link #readDelta}. Each line in the proc file has the format:
+ *
+ * freq time
+ *
+ * where time is measured in 1/100 seconds.
+ */
+public class KernelCpuSpeedReader {
+    private static final String TAG = "KernelCpuSpeedReader";
+    private static final String sProcFile =
+            "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state";
+    private static final int MAX_SPEEDS = 60;
+
+    private long[] mLastSpeedTimes = new long[MAX_SPEEDS];
+    private long[] mDeltaSpeedTimes = new long[MAX_SPEEDS];
+
+    /**
+     * The returned array is modified in subsequent calls to {@link #readDelta}.
+     * @return The time (in milliseconds) spent at different cpu speeds since the last call to
+     * {@link #readDelta}.
+     */
+    public long[] readDelta() {
+        try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
+            TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
+            String line;
+            int speedIndex = 0;
+            while ((line = reader.readLine()) != null) {
+                splitter.setString(line);
+                Long.parseLong(splitter.next());
+
+                // The proc file reports time in 1/100 sec, so convert to milliseconds.
+                long time = Long.parseLong(splitter.next()) * 10;
+                mDeltaSpeedTimes[speedIndex] = time - mLastSpeedTimes[speedIndex];
+                mLastSpeedTimes[speedIndex] = time;
+                speedIndex++;
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read cpu-freq", e);
+            Arrays.fill(mDeltaSpeedTimes, 0);
+        }
+        return mDeltaSpeedTimes;
+    }
+}
diff --git a/core/java/com/android/internal/os/KernelUidCpuTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
new file mode 100644
index 0000000..b236378
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelUidCpuTimeReader.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseLongArray;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * Reads /proc/uid_cputime/show_uid_stat which has the line format:
+ *
+ * uid: user_time_micro_seconds system_time_micro_seconds
+ *
+ * This provides the time a UID's processes spent executing in user-space and kernel-space.
+ * The file contains a monotonically increasing count of time for a single boot. This class
+ * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
+ * delta.
+ */
+public class KernelUidCpuTimeReader {
+    private static final String TAG = "KernelUidCpuTimeReader";
+    private static final String sProcFile = "/proc/uid_cputime/show_uid_stat";
+    private static final String sRemoveUidProcFile = "/proc/uid_cputime/remove_uid_range";
+
+    /**
+     * Callback interface for processing each line of the proc file.
+     */
+    public interface Callback {
+        void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs);
+    }
+
+    private SparseLongArray mLastUserTimeUs = new SparseLongArray();
+    private SparseLongArray mLastSystemTimeUs = new SparseLongArray();
+
+    /**
+     * Reads the proc file, calling into the callback with a delta of time for each UID.
+     * @param callback The callback to invoke for each line of the proc file. If null,
+     *                 the data is consumed and subsequent calls to readDelta will provide
+     *                 a fresh delta.
+     */
+    public void readDelta(@Nullable Callback callback) {
+        try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
+            TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
+            String line;
+            while ((line = reader.readLine()) != null) {
+                splitter.setString(line);
+                final String uidStr = splitter.next();
+                final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10);
+                final long userTimeUs = Long.parseLong(splitter.next(), 10);
+                final long systemTimeUs = Long.parseLong(splitter.next(), 10);
+
+                if (callback != null) {
+                    long userTimeDeltaUs = userTimeUs;
+                    long systemTimeDeltaUs = systemTimeUs;
+                    int index = mLastUserTimeUs.indexOfKey(uid);
+                    if (index >= 0) {
+                        userTimeDeltaUs -= mLastUserTimeUs.valueAt(index);
+                        systemTimeDeltaUs -= mLastSystemTimeUs.valueAt(index);
+
+                        if (userTimeDeltaUs < 0 || systemTimeDeltaUs < 0) {
+                            // The UID must have been removed from accounting, then added back.
+                            userTimeDeltaUs = userTimeUs;
+                            systemTimeDeltaUs = systemTimeUs;
+                        }
+                    }
+
+                    if (userTimeDeltaUs != 0 || systemTimeDeltaUs != 0) {
+                        callback.onUidCpuTime(uid, userTimeDeltaUs, systemTimeDeltaUs);
+                    }
+                }
+                mLastUserTimeUs.put(uid, userTimeUs);
+                mLastSystemTimeUs.put(uid, systemTimeUs);
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read uid_cputime", e);
+        }
+    }
+
+    /**
+     * Removes the UID from the kernel module and from internal accounting data.
+     * @param uid The UID to remove.
+     */
+    public void removeUid(int uid) {
+        int index = mLastUserTimeUs.indexOfKey(uid);
+        if (index >= 0) {
+            mLastUserTimeUs.removeAt(index);
+            mLastSystemTimeUs.removeAt(index);
+        }
+
+        try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
+            writer.write(Integer.toString(uid) + "-" + Integer.toString(uid));
+            writer.flush();
+        } catch (IOException e) {
+            Slog.e(TAG, "failed to remove uid from uid_cputime module", e);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 2983047..8393e2a 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -170,21 +170,6 @@
 
     private byte[] mBuffer = new byte[4096];
 
-    /**
-     * The time in microseconds that the CPU has been running at each speed.
-     */
-    private long[] mCpuSpeedTimes;
-
-    /**
-     * The relative time in microseconds that the CPU has been running at each speed.
-     */
-    private long[] mRelCpuSpeedTimes;
-
-    /**
-     * The different speeds that the CPU can be running at.
-     */
-    private long[] mCpuSpeeds;
-
     public static class Stats {
         public final int pid;
         public final int uid;
@@ -590,70 +575,6 @@
         }
     }
 
-    /**
-     * Returns the delta time (in clock ticks, or 1/100 sec) spent at each CPU
-     * speed, since the last call to this method. If this is the first call, it
-     * will return 1 for each value.
-     */
-    public long[] getLastCpuSpeedTimes() {
-        if (mCpuSpeedTimes == null) {
-            mCpuSpeedTimes = getCpuSpeedTimes(null);
-            mRelCpuSpeedTimes = new long[mCpuSpeedTimes.length];
-            for (int i = 0; i < mCpuSpeedTimes.length; i++) {
-                mRelCpuSpeedTimes[i] = 1; // Initialize
-            }
-        } else {
-            getCpuSpeedTimes(mRelCpuSpeedTimes);
-            for (int i = 0; i < mCpuSpeedTimes.length; i++) {
-                long temp = mRelCpuSpeedTimes[i];
-                mRelCpuSpeedTimes[i] -= mCpuSpeedTimes[i];
-                mCpuSpeedTimes[i] = temp;
-            }
-        }
-        return mRelCpuSpeedTimes;
-    }
-
-    private long[] getCpuSpeedTimes(long[] out) {
-        long[] tempTimes = out;
-        long[] tempSpeeds = mCpuSpeeds;
-        final int MAX_SPEEDS = 60;
-        if (out == null) {
-            tempTimes = new long[MAX_SPEEDS]; // Hopefully no more than that
-            tempSpeeds = new long[MAX_SPEEDS];
-        }
-        int speed = 0;
-        String file = readFile("/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state", '\0');
-        // Note: file may be null on kernels without cpufreq (i.e. the emulator's)
-        if (file != null) {
-            StringTokenizer st = new StringTokenizer(file, "\n ");
-            while (st.hasMoreElements()) {
-                String token = st.nextToken();
-                try {
-                    long val = Long.parseLong(token);
-                    tempSpeeds[speed] = val;
-                    token = st.nextToken();
-                    val = Long.parseLong(token);
-                    tempTimes[speed] = val;
-                    speed++;
-                    if (speed == MAX_SPEEDS) break; // No more
-                    if (localLOGV && out == null) {
-                        Slog.v(TAG, "First time : Speed/Time = " + tempSpeeds[speed - 1]
-                              + "\t" + tempTimes[speed - 1]);
-                    }
-                } catch (NumberFormatException nfe) {
-                    Slog.i(TAG, "Unable to parse time_in_state");
-                }
-            }
-        }
-        if (out == null) {
-            out = new long[speed];
-            mCpuSpeeds = new long[speed];
-            System.arraycopy(tempSpeeds, 0, mCpuSpeeds, 0, speed);
-            System.arraycopy(tempTimes, 0, out, 0, speed);
-        }
-        return out;
-    }
-
     final public int getLastUserTime() {
         return mRelUserTime;
     }
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index f98fbfc..fdc3547 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -79,7 +79,6 @@
     private final FloatingToolbarPopup mPopup;
 
     private final Rect mContentRect = new Rect();
-    private final Point mCoordinates = new Point();
 
     private Menu mMenu;
     private List<CharSequence> mShowingTitles = new ArrayList<CharSequence>();
@@ -87,7 +86,6 @@
 
     private int mSuggestedWidth;
     private boolean mWidthChanged = true;
-    private int mOverflowDirection;
 
     /**
      * Initializes a floating toolbar.
@@ -157,11 +155,9 @@
             mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
             mShowingTitles = getMenuItemTitles(menuItems);
         }
-        refreshCoordinates();
-        mPopup.setOverflowDirection(mOverflowDirection);
-        mPopup.updateCoordinates(mCoordinates.x, mCoordinates.y);
+        mPopup.updateCoordinates(mContentRect);
         if (!mPopup.isShowing()) {
-            mPopup.show(mCoordinates.x, mCoordinates.y);
+            mPopup.show(mContentRect);
         }
         mWidthChanged = false;
         return this;
@@ -209,25 +205,6 @@
     }
 
     /**
-     * Refreshes {@link #mCoordinates} with values based on {@link #mContentRect}.
-     */
-    private void refreshCoordinates() {
-        int x = mContentRect.centerX() - mPopup.getWidth() / 2;
-        int y;
-        if (mContentRect.top > mPopup.getHeight()) {
-            y = mContentRect.top - mPopup.getHeight();
-            mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_UP;
-        } else if (mContentRect.top > mPopup.getToolbarHeightWithVerticalMargin()) {
-            y = mContentRect.top - mPopup.getToolbarHeightWithVerticalMargin();
-            mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
-        } else {
-            y = mContentRect.bottom;
-            mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
-        }
-        mCoordinates.set(x, y);
-    }
-
-    /**
      * Returns true if this floating toolbar is currently showing the specified menu items.
      */
     private boolean isCurrentlyShowing(List<MenuItem> menuItems) {
@@ -345,6 +322,8 @@
             }
         };
 
+        private final Point mCoords = new Point();
+
         private final Region mTouchableRegion = new Region();
         private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
                 new ViewTreeObserver.OnComputeInternalInsetsListener() {
@@ -378,6 +357,7 @@
             mShowAnimation = createGrowFadeInFromBottom(mContentContainer);
             mDismissAnimation = createShrinkFadeOutFromBottomAnimation(
                     mContentContainer,
+                    0,
                     new AnimatorListenerAdapter() {
                         @Override
                         public void onAnimationEnd(Animator animation) {
@@ -387,6 +367,7 @@
                     });
             mHideAnimation = createShrinkFadeOutFromBottomAnimation(
                     mContentContainer,
+                    150,
                     new AnimatorListenerAdapter() {
                         @Override
                         public void onAnimationEnd(Animator animation) {
@@ -404,6 +385,8 @@
          */
         public void layoutMenuItems(List<MenuItem> menuItems,
                 MenuItem.OnMenuItemClickListener menuItemClickListener, int suggestedWidth) {
+            Preconditions.checkNotNull(menuItems);
+
             mContentContainer.removeAllViews();
             if (mMainPanel == null) {
                 mMainPanel = new FloatingToolbarMainPanel(mParent.getContext(), mOpenOverflow);
@@ -426,7 +409,9 @@
          * Shows this popup at the specified coordinates.
          * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
          */
-        public void show(int x, int y) {
+        public void show(Rect contentRect) {
+            Preconditions.checkNotNull(contentRect);
+
             if (isShowing()) {
                 return;
             }
@@ -435,6 +420,7 @@
             mDismissed = false;
             cancelDismissAndHideAnimations();
             cancelOverflowAnimations();
+
             // Make sure a panel is set as the content.
             if (mContentContainer.getChildCount() == 0) {
                 setMainPanelAsContent();
@@ -442,8 +428,10 @@
                 // The "show" animation will make this visible.
                 mContentContainer.setAlpha(0);
             }
+            updateOverflowHeight(contentRect.top - (mMarginVertical * 2));
+            refreshCoordinatesAndOverflowDirection(contentRect);
             preparePopupContent();
-            mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, x, y);
+            mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, mCoords.x, mCoords.y);
             setTouchableSurfaceInsetsComputer();
             runShowAnimation();
         }
@@ -496,27 +484,17 @@
          * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
          * This is a no-op if this popup is not showing.
          */
-        public void updateCoordinates(int x, int y) {
+        public void updateCoordinates(Rect contentRect) {
+            Preconditions.checkNotNull(contentRect);
+
             if (!isShowing() || !mPopupWindow.isShowing()) {
                 return;
             }
 
             cancelOverflowAnimations();
+            refreshCoordinatesAndOverflowDirection(contentRect);
             preparePopupContent();
-            mPopupWindow.update(x, y, getWidth(), getHeight());
-        }
-
-        /**
-         * Sets the direction in which the overflow will open. i.e. up or down.
-         *
-         * @param overflowDirection Either {@link #OVERFLOW_DIRECTION_UP}
-         *   or {@link #OVERFLOW_DIRECTION_DOWN}.
-         */
-        public void setOverflowDirection(int overflowDirection) {
-            mOverflowDirection = overflowDirection;
-            if (mOverflowPanel != null) {
-                mOverflowPanel.setOverflowDirection(mOverflowDirection);
-            }
+            mPopupWindow.update(mCoords.x, mCoords.y, getWidth(), getHeight());
         }
 
         /**
@@ -540,7 +518,26 @@
             return mContentContainer.getContext();
         }
 
-        int getToolbarHeightWithVerticalMargin() {
+        private void refreshCoordinatesAndOverflowDirection(Rect contentRect) {
+            int x = contentRect.centerX() - getWidth() / 2;
+            int y;
+            if (contentRect.top > getHeight()) {
+                y = contentRect.top - getHeight();
+                mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_UP;
+            } else if (contentRect.top > getToolbarHeightWithVerticalMargin()) {
+                y = contentRect.top - getToolbarHeightWithVerticalMargin();
+                mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
+            } else {
+                y = contentRect.bottom;
+                mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
+            }
+            mCoords.set(x, y);
+            if (mOverflowPanel != null) {
+                mOverflowPanel.setOverflowDirection(mOverflowDirection);
+            }
+        }
+
+        private int getToolbarHeightWithVerticalMargin() {
             return getEstimatedToolbarHeight(mParent.getContext()) + mMarginVertical * 2;
         }
 
@@ -693,16 +690,24 @@
             }
 
             // Reset position.
-            if (mMainPanel != null
-                    && mContentContainer.getChildAt(0) == mMainPanel.getView()) {
+            if (isMainPanelContent()) {
                 positionMainPanel();
             }
-            if (mOverflowPanel != null
-                    && mContentContainer.getChildAt(0) == mOverflowPanel.getView()) {
+            if (isOverflowPanelContent()) {
                 positionOverflowPanel();
             }
         }
 
+        private boolean isMainPanelContent() {
+            return mMainPanel != null
+                    && mContentContainer.getChildAt(0) == mMainPanel.getView();
+        }
+
+        private boolean isOverflowPanelContent() {
+            return mOverflowPanel != null
+                    && mContentContainer.getChildAt(0) == mOverflowPanel.getView();
+        }
+
         /**
          * Sets the current content to be the main view panel.
          */
@@ -765,6 +770,25 @@
             setContentAreaAsTouchableSurface();
         }
 
+        private void updateOverflowHeight(int height) {
+            if (mOverflowPanel != null) {
+                mOverflowPanel.setSuggestedHeight(height);
+
+                // Re-measure the popup and it's contents.
+                boolean mainPanelContent = isMainPanelContent();
+                boolean overflowPanelContent = isOverflowPanelContent();
+                mContentContainer.removeAllViews();  // required to update popup size.
+                updatePopupSize();
+                // Reset the appropriate content.
+                if (mainPanelContent) {
+                    setMainPanelAsContent();
+                }
+                if (overflowPanelContent) {
+                    setOverflowPanelAsContent();
+                }
+            }
+        }
+
         private void updatePopupSize() {
             int width = 0;
             int height = 0;
@@ -864,6 +888,8 @@
          * @return The menu items that are not included in this main panel.
          */
         public List<MenuItem> layoutMenuItems(List<MenuItem> menuItems, int suggestedWidth) {
+            Preconditions.checkNotNull(menuItems);
+
             final int toolbarWidth = getAdjustedToolbarWidth(mContext, suggestedWidth)
                     // Reserve space for the "open overflow" button.
                     - getEstimatedOpenOverflowButtonWidth(mContext);
@@ -972,6 +998,7 @@
 
         private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
         private int mOverflowWidth = 0;
+        private int mSuggestedHeight;
 
         /**
          * Initializes a floating toolbar popup overflow view panel.
@@ -981,6 +1008,7 @@
          */
         public FloatingToolbarOverflowPanel(Context context, Runnable closeOverflow) {
             mCloseOverflow = Preconditions.checkNotNull(closeOverflow);
+            mSuggestedHeight = getScreenHeight(context);
 
             mContentView = new LinearLayout(context);
             mContentView.setOrientation(LinearLayout.VERTICAL);
@@ -1043,6 +1071,11 @@
             mContentView.addView(mBackButtonContainer, index);
         }
 
+        public void setSuggestedHeight(int height) {
+            mSuggestedHeight = height;
+            setListViewHeight();
+        }
+
         /**
          * Returns the content view of the overflow.
          */
@@ -1074,9 +1107,17 @@
             int itemHeight = getEstimatedToolbarHeight(mContentView.getContext());
             int height = mListView.getAdapter().getCount() * itemHeight;
             int maxHeight = mContentView.getContext().getResources().
+                    getDimensionPixelSize(R.dimen.floating_toolbar_maximum_overflow_height);
+            int minHeight = mContentView.getContext().getResources().
                     getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
+            int availableHeight = mSuggestedHeight - (mSuggestedHeight % itemHeight)
+                    - itemHeight;  // reserve space for the back button.
             ViewGroup.LayoutParams params = mListView.getLayoutParams();
-            params.height = Math.min(height, maxHeight);
+            if (availableHeight >= minHeight) {
+                params.height = Math.min(Math.min(availableHeight, maxHeight), height);
+            } else {
+                params.height = Math.min(maxHeight, height);
+            }
             mListView.setLayoutParams(params);
         }
 
@@ -1224,15 +1265,16 @@
      * Creates a "shrink and fade out from bottom" animation for the specified view.
      *
      * @param view  The view to animate
+     * @param startDelay  The start delay of the animation
      * @param listener  The animation listener
      */
     private static AnimatorSet createShrinkFadeOutFromBottomAnimation(
-            View view, Animator.AnimatorListener listener) {
+            View view, int startDelay, Animator.AnimatorListener listener) {
         AnimatorSet shrinkFadeOutFromBottomAnimation =  new AnimatorSet();
         shrinkFadeOutFromBottomAnimation.playTogether(
                 ObjectAnimator.ofFloat(view, View.SCALE_Y, 1, 0.5f).setDuration(125),
                 ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(75));
-        shrinkFadeOutFromBottomAnimation.setStartDelay(150);
+        shrinkFadeOutFromBottomAnimation.setStartDelay(startDelay);
         shrinkFadeOutFromBottomAnimation.addListener(listener);
         return shrinkFadeOutFromBottomAnimation;
     }
@@ -1271,16 +1313,4 @@
     private static int getScreenHeight(Context context) {
         return context.getResources().getDisplayMetrics().heightPixels;
     }
-
-    /**
-     * Returns value, restricted to the range min->max (inclusive).
-     * If maximum is less than minimum, the result is undefined.
-     *
-     * @param value  The value to clamp.
-     * @param minimum  The minimum value in the range.
-     * @param maximum  The maximum value in the range. Must not be less than minimum.
-     */
-    private static int clamp(int value, int minimum, int maximum) {
-        return Math.max(minimum, Math.min(value, maximum));
-    }
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 3d23986..e5ef60c 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1097,8 +1097,11 @@
             Log.w(TAG, "Only device owner may call setCredentialRequiredForDecrypt()");
             return;
         }
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, required ? 1 : 0);
+
+        if (isDeviceEncryptionEnabled()){
+            Settings.Global.putInt(mContext.getContentResolver(),
+               Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, required ? 1 : 0);
+        }
     }
 
     private boolean isDoNotAskCredentialsOnBootSet() {
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 01e835b..be727f1 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -661,13 +661,20 @@
             }
         }
 
+        final int oldCollapsibleHeight = mCollapsibleHeight;
         mCollapsibleHeight = Math.max(0,
                 heightUsed - alwaysShowHeight - getMaxCollapsedHeight());
         mUncollapsibleHeight = heightUsed - mCollapsibleHeight;
 
         if (isLaidOut()) {
             final boolean isCollapsedOld = mCollapseOffset != 0;
-            mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
+            if (oldCollapsibleHeight < mCollapsibleHeight
+                    && mCollapseOffset == oldCollapsibleHeight) {
+                // Stay closed even at the new height.
+                mCollapseOffset = mCollapsibleHeight;
+            } else {
+                mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
+            }
             final boolean isCollapsedNew = mCollapseOffset != 0;
             if (isCollapsedOld != isCollapsedNew) {
                 notifyViewAccessibilityStateChangedIfNeeded(
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 348b0ec..e69f64e 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -102,8 +102,8 @@
             canvas->translate(bounds.fLeft, bounds.fTop);
             canvas->scale(scale, scale);
 
-            bounds.fRight = SkScalarDiv(bounds.fRight-bounds.fLeft, scale);
-            bounds.fBottom = SkScalarDiv(bounds.fBottom-bounds.fTop, scale);
+            bounds.fRight = (bounds.fRight-bounds.fLeft) / scale;
+            bounds.fBottom = (bounds.fBottom-bounds.fTop) / scale;
             bounds.fLeft = bounds.fTop = 0;
 
             ALOGV("Drawing scaled 9-patch: (%g,%g)-(%g,%g) srcDensity=%d destDensity=%d",
diff --git a/core/jni/android/graphics/NinePatchImpl.cpp b/core/jni/android/graphics/NinePatchImpl.cpp
index 26ce967..323f832 100644
--- a/core/jni/android/graphics/NinePatchImpl.cpp
+++ b/core/jni/android/graphics/NinePatchImpl.cpp
@@ -94,8 +94,7 @@
     SkScalar spaceRemaining = boundsLimit - startingPoint;
     SkScalar stretchySpaceRemaining =
                 spaceRemaining - SkIntToScalar(numFixedPixelsRemaining);
-    return SkScalarMulDiv(srcSpace, stretchySpaceRemaining,
-                          numStrechyPixelsRemaining);
+    return srcSpace * stretchySpaceRemaining / numStrechyPixelsRemaining;
 }
 
 void NinePatch_Draw(SkCanvas* canvas, const SkRect& bounds,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4c034b3..608d718 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -678,6 +678,7 @@
 
     <!-- Allows an app to use fingerprint hardware. -->
     <permission android:name="android.permission.USE_FINGERPRINT"
+        android:permissionGroup="android.permission-group.SENSORS"
         android:label="@string/permlab_useFingerprint"
         android:description="@string/permdesc_useFingerprint"
         android:protectionLevel="dangerous" />
@@ -2404,8 +2405,7 @@
     <permission android:name="android.permission.REMOVE_DRM_CERTIFICATES"
         android:protectionLevel="signature|system" />
 
-    <!-- Must be required by a {@link android.service.carrier.CarrierMessagingService}.
-         Any service that filters for this intent must be a carrier privileged app. -->
+    <!-- @deprecated Use {@link android.Manifest.permission#BIND_CARRIER_SERVICES} instead -->
     <permission android:name="android.permission.BIND_CARRIER_MESSAGING_SERVICE"
         android:protectionLevel="signature|system" />
 
@@ -2427,13 +2427,12 @@
                 android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
                 android:protectionLevel="signature" />
 
-    <!-- The system process that pulls carrier configuration from carrier apps will
-         have this permission. Carrier apps that provide
-         {@link android.service.carrier.CarrierConfigService} should require this
-         permission for clients binding to their service. -->
-    <permission android:name="android.permission.BIND_CARRIER_CONFIG_SERVICE"
-        android:label="@string/permlab_bindCarrierConfigService"
-        android:description="@string/permdesc_bindCarrierConfigService"
+    <!-- The system process that is allowed to bind to services in carrier apps will
+         have this permission. Carrier apps should use this permission to protect
+         their services that only the system is allowed to bind to. -->
+    <permission android:name="android.permission.BIND_CARRIER_SERVICES"
+        android:label="@string/permlab_bindCarrierServices"
+        android:description="@string/permdesc_bindCarrierServices"
         android:protectionLevel="signature|system" />
 
     <!-- Allows an application to query whether DO_NOT_ASK_CREDENTIALS_ON_BOOT
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index 65cb046..f5d945a 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -14,35 +14,30 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="560.0dp"
-        android:height="560.0dp"
-        android:viewportWidth="560.0"
-        android:viewportHeight="560.0">
+        android:width="480dp"
+        android:height="480dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
     <path
-        android:pathData="M264.079987,240.736l0.0,9.82c7.31,-7.15 17.139999,-11.56 28.07,-11.56c22.639999,0.0 40.799999,18.48 40.799999,41.12c0.0,22.48 -18.16,40.880 -40.799999,40.880c-10.93,0.0 -20.280,-4.09 -27.59,-10.93L264.559998,339.0l-11.32,0.0l0.0,-98.269997L264.079987,240.731zM265.809998,264.869995c-0.47,0.79 -1.26,2.04 -1.26,4.79l0.0,21.07c0.0,1.97 0.47,3.07 1.1,4.17c5.19,8.88 14.78,14.94 25.63,14.94c16.43,0.0 29.950,-13.44 29.950,-29.870c0.0,-16.280 -13.52,-29.799999 -29.950,-29.799999C280.51,250.169998 271.0,256.059998 265.809998,264.869995z"
+        android:pathData="M24.0,2.0C11.8,2.0 2.0,11.8 2.0,24.0c0.0,6.1 2.5,11.6 6.4,15.6L39.6,8.4C35.6,4.5 30.1,2.0 24.0,2.0z"
+        android:fillColor="#F57C00"/>
+    <path
+        android:pathData="M39.6,8.4L8.4,39.6c4.0,4.0 9.5,6.4 15.6,6.4c12.2,0.0 22.0,-9.8 22.0,-22.0C46.0,17.9 43.5,12.4 39.6,8.4z"
+        android:fillColor="#FF9800"/>
+    <path
+        android:pathData="M45.9,25.9L34.0,14.0L14.0,34.0l11.9,11.9C36.5,45.0 45.0,36.5 45.9,25.9z"
+        android:fillAlpha="0.33"
+        android:fillColor="#F57C00"/>
+    <path
+        android:pathData="M24.0,24.0c0.0,0.0 0.0,2.2 0.0,5.0s0.0,5.0 0.0,5.0l10.0,-10.0L34.0,14.0L24.0,24.0z"
         android:fillColor="#FFFFFF"/>
     <path
-        android:pathData="M445.731,240.736l0.0,9.82c7.31,-7.15 17.139999,-11.56 28.07,-11.56c22.639999,0.0 40.799999,18.48 40.799999,41.12c0.0,22.48 -18.16,40.880 -40.799999,40.880c-10.93,0.0 -20.280,-4.09 -27.59,-10.93L446.210052,339.0l-11.32,0.0l0.0,-98.269997L445.731,240.731zM447.459991,264.869995c-0.47,0.79 -1.26,2.04 -1.26,4.79l0.0,21.07c0.0,1.97 0.47,3.07 1.1,4.17c5.19,8.88 14.78,14.94 25.63,14.94c16.43,0.0 29.950,-13.44 29.950,-29.870c0.0,-16.280 -13.52,-29.799999 -29.950,-29.799999C462.160004,250.169998 452.649994,256.059998 447.459991,264.869995z"
-        android:fillColor="#FFFFFF"/>
+        android:pathData="M24.0,24.0L14.0,14.0l0.0,10.0l10.0,10.0c0.0,0.0 0.0,-2.2 0.0,-5.0S24.0,24.0 24.0,24.0z"
+        android:fillColor="#EEEEEE"/>
     <path
-        android:pathData="M169.490005,279.880005c0.0,22.639999 -18.32,41.12 -40.810,41.12c-22.639999,0.0 -41.040,-18.48 -41.040,-41.12c0.0,-22.48 18.389999,-40.880 41.040,-40.880C151.169998,239.0 169.490005,257.399994 169.490005,279.880005zM158.089996,280.040009c0.0,-16.43 -13.13,-29.870 -29.41,-29.870c-16.51,0.0 -29.4,13.44 -29.4,29.870c0.0,16.280 12.89,29.799999 29.4,29.799999C144.960007,309.8387 158.089996,296.309998 158.089996,280.040009z"
-        android:fillColor="#FFFFFF"/>
+        android:pathData="M14.0,34.0l10.0,0.0 -10.0,-10.0z"
+        android:fillColor="#DDDDDD"/>
     <path
-        android:pathData="M423.790009,279.880005c0.0,22.639999 -18.32,41.12 -40.810,41.12c-22.639999,0.0 -41.040,-18.48 -41.040,-41.12c0.0,-22.48 18.389999,-40.880 41.040,-40.880C405.470,239.0 423.790009,257.399994 423.790009,279.880005zM412.395,280.040009c0.0,-16.43 -13.13,-29.870 -29.41,-29.870c-16.51,0.0 -29.4,13.44 -29.4,29.870c0.0,16.280 12.89,29.799999 29.4,29.799999C399.26,309.8387 412.395,296.309998 412.395,280.040009z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M229.02,221.0l11.17,0.0l0.0,11.48l-11.17,0.0z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M229.02,240.65l11.17,0.0l0.0,78.62l-11.17,0.0z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M65.4,221.0l11.17,0.0l0.0,98.27l-11.17,0.0z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M180.58,221.0l11.17,0.0l0.0,98.27l-11.17,0.0z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M204.8,221.0l11.17,0.0l0.0,98.27l-11.17,0.0z"
-        android:fillColor="#FFFFFF"/>
+        android:pathData="M34.0,34.0l0.0,-10.0 -10.0,10.0z"
+        android:fillColor="#DDDDDD"/>
 </vector>
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index d89d1f9..8f5109df 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -19,9 +19,6 @@
         android:viewportWidth="24.0"
         android:viewportHeight="24.0">
     <path
-        android:pathData="M19.000000,10.000000c0.000000,3.866000 -3.134000,7.000000 -7.000000,7.000000s-7.000000,-3.134000 -7.000000,-7.000000c0.000000,-2.318000 1.131000,-4.367000 2.867000,-5.641000L5.778000,2.270000l0.824000,-0.825000l2.290000,2.290000C9.830000,3.269000 10.882000,3.000000 12.000000,3.000000c1.118000,0.000000 2.170000,0.269000 3.107000,0.734000l2.290000,-2.290000l0.824000,0.825000l-2.089000,2.090000C17.868000,5.633000 19.000000,7.682000 19.000000,10.000000zM10.000000,8.000000c0.000000,-0.552000 -0.447000,-1.000000 -1.000000,-1.000000S8.000000,7.448000 8.000000,8.000000s0.447000,1.000000 1.000000,1.000000S10.000000,8.552000 10.000000,8.000000zM16.000000,8.000000c0.000000,-0.552000 -0.447000,-1.000000 -1.000000,-1.000000s-1.000000,0.448000 -1.000000,1.000000s0.447000,1.000000 1.000000,1.000000S16.000000,8.552000 16.000000,8.000000z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M11,18l2,0l0,5l-2,0z"
-        android:fillColor="#FFFFFF"/>
+        android:fillColor="#FF000000"
+        android:pathData="M12.0,12.0l-10.0,-10.0 0.0,10.0 0.0,10.0 10.0,0.0 10.0,0.0 0.0,-10.0 0.0,-10.0z"/>
 </vector>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 84747f1..813591b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -392,7 +392,8 @@
      <dimen name="floating_toolbar_text_size">14sp</dimen>
      <dimen name="floating_toolbar_menu_button_minimum_width">48dp</dimen>
      <dimen name="floating_toolbar_preferred_width">328dp</dimen>
-     <dimen name="floating_toolbar_minimum_overflow_height">144dp</dimen>
+     <dimen name="floating_toolbar_minimum_overflow_height">96dp</dimen>
+     <dimen name="floating_toolbar_maximum_overflow_height">192dp</dimen>
      <dimen name="floating_toolbar_horizontal_margin">16dp</dimen>
      <dimen name="floating_toolbar_vertical_margin">8dp</dimen>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1c0122d..0e6b2df 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1408,9 +1408,9 @@
     <string name="permdesc_bindCarrierMessagingService">Allows the holder to bind to the top-level interface of a carrier messaging service. Should never be needed for normal apps.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permlab_bindCarrierConfigService">bind to a carrier config service</string>
+    <string name="permlab_bindCarrierServices">bind to carrier services</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
-    <string name="permdesc_bindCarrierConfigService">Allows the holder to bind to a carrier config service. Should never be needed for normal apps.</string>
+    <string name="permdesc_bindCarrierServices">Allows the holder to bind to carrier services. Should never be needed for normal apps.</string>
 
     <!-- Policy administration -->
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0d306c6..64e3964 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2256,6 +2256,7 @@
   <java-symbol type="dimen" name="floating_toolbar_menu_button_minimum_width" />
   <java-symbol type="dimen" name="floating_toolbar_preferred_width" />
   <java-symbol type="dimen" name="floating_toolbar_minimum_overflow_height" />
+  <java-symbol type="dimen" name="floating_toolbar_maximum_overflow_height" />
   <java-symbol type="dimen" name="floating_toolbar_horizontal_margin" />
   <java-symbol type="dimen" name="floating_toolbar_vertical_margin" />
 
diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java
index 0da4275..bd8eae0 100644
--- a/graphics/java/android/graphics/drawable/DrawableWrapper.java
+++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java
@@ -432,7 +432,8 @@
 
         @Override
         public int getChangingConfigurations() {
-            return mChangingConfigurations | mDrawableState.getChangingConfigurations();
+            return mChangingConfigurations
+                    | (mDrawableState != null ? mDrawableState.getChangingConfigurations() : 0);
         }
 
         public boolean canConstantState() {
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index ba7bd74..f893fdd 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -242,7 +242,7 @@
     /**
      * The audio channel mask used for calling native AudioTrack
      */
-    private int mChannels = AudioFormat.CHANNEL_OUT_MONO;
+    private int mChannelMask = AudioFormat.CHANNEL_OUT_MONO;
 
     /**
      * The type of the audio stream to play. See
@@ -485,7 +485,7 @@
         session[0] = sessionId;
         // native initialization
         int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
-                mSampleRate, mChannels, mChannelIndexMask, mAudioFormat,
+                mSampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
                 mNativeBufferSizeInBytes, mDataLoadMode, session);
         if (initResult != SUCCESS) {
             loge("Error code "+initResult+" when initializing AudioTrack.");
@@ -699,7 +699,7 @@
     // This is where constructor IllegalArgumentException-s are thrown
     // postconditions:
     //    mChannelCount is valid
-    //    mChannels is valid
+    //    mChannelMask is valid
     //    mAudioFormat is valid
     //    mSampleRate is valid
     //    mDataLoadMode is valid
@@ -722,12 +722,12 @@
         case AudioFormat.CHANNEL_OUT_MONO:
         case AudioFormat.CHANNEL_CONFIGURATION_MONO:
             mChannelCount = 1;
-            mChannels = AudioFormat.CHANNEL_OUT_MONO;
+            mChannelMask = AudioFormat.CHANNEL_OUT_MONO;
             break;
         case AudioFormat.CHANNEL_OUT_STEREO:
         case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
             mChannelCount = 2;
-            mChannels = AudioFormat.CHANNEL_OUT_STEREO;
+            mChannelMask = AudioFormat.CHANNEL_OUT_STEREO;
             break;
         default:
             if (channelConfig == AudioFormat.CHANNEL_INVALID && channelIndexMask != 0) {
@@ -738,7 +738,7 @@
                 // input channel configuration features unsupported channels
                 throw new IllegalArgumentException("Unsupported channel configuration.");
             }
-            mChannels = channelConfig;
+            mChannelMask = channelConfig;
             mChannelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
         }
         // check the channel index configuration (if present)
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index acff301..52ba9ec 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -110,10 +110,10 @@
     private static final String PERMISSION = android.Manifest.permission.ACCESS_DRM_CERTIFICATES;
 
     private EventHandler mEventHandler;
-    private EventHandler mOnKeysChangeEventHandler;
+    private EventHandler mOnKeyStatusChangeEventHandler;
     private EventHandler mOnExpirationUpdateEventHandler;
     private OnEventListener mOnEventListener;
-    private OnKeysChangeListener mOnKeysChangeListener;
+    private OnKeyStatusChangeListener mOnKeyStatusChangeListener;
     private OnExpirationUpdateListener mOnExpirationUpdateListener;
 
     private long mNativeContext;
@@ -297,8 +297,8 @@
      * @param handler the handler on which the listener should be invoked, or
      *     null if the listener should be invoked on the calling thread's looper.
      */
-    public void setOnKeysChangeListener(
-            @Nullable OnKeysChangeListener listener, @Nullable Handler handler) {
+    public void setOnKeyStatusChangeListener(
+            @Nullable OnKeyStatusChangeListener listener, @Nullable Handler handler) {
         if (listener != null) {
             Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
             if (looper != null) {
@@ -307,14 +307,14 @@
                 }
             }
         }
-        mOnKeysChangeListener = listener;
+        mOnKeyStatusChangeListener = listener;
     }
 
     /**
      * Interface definition for a callback to be invoked when the keys in a drm
      * session change states.
      */
-    public interface OnKeysChangeListener
+    public interface OnKeyStatusChangeListener
     {
         /**
          * Called when the keys in a session change status, such as when the license
@@ -328,64 +328,64 @@
          *     which may trigger an attempt to resume playback on the media stream
          *     if it is currently blocked waiting for a key.
          */
-        void onKeysChange(
+        void onKeyStatusChange(
                 @NonNull MediaDrm md, @NonNull byte[] sessionId,
                 @NonNull List<KeyStatus> keyInformation,
                 boolean hasNewUsableKey);
     }
 
     /**
-     * The key is currently usable to decrypt media data
-     */
-    public static final int KEY_STATUS_USABLE = 0;
-
-    /**
-     * The key is no longer usable to decrypt media data because its
-     * expiration time has passed.
-     */
-    public static final int KEY_STATUS_EXPIRED = 1;
-
-    /**
-     * The key is not currently usable to decrypt media data because its
-     * output requirements cannot currently be met.
-     */
-    public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2;
-
-    /**
-     * The status of the key is not yet known and is being determined.
-     * The status will be updated with the actual status when it has
-     * been determined.
-     */
-    public static final int KEY_STATUS_PENDING = 3;
-
-    /**
-     * The key is not currently usable to decrypt media data because of an
-     * internal error in processing unrelated to input parameters.  This error
-     * is not actionable by an app.
-     */
-    public static final int KEY_STATUS_INTERNAL_ERROR = 4;
-
-    /** @hide */
-    @IntDef({
-        KEY_STATUS_USABLE,
-        KEY_STATUS_EXPIRED,
-        KEY_STATUS_OUTPUT_NOT_ALLOWED,
-        KEY_STATUS_PENDING,
-        KEY_STATUS_INTERNAL_ERROR,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface KeyStatusCode {}
-
-    /**
      * Defines the status of a key.
      * A KeyStatus for each key in a session is provided to the
-     * {@link OnKeysChangeListener#onKeysChange}
+     * {@link OnKeyStatusChangeListener#onKeyStatusChange}
      * listener.
      */
     public static final class KeyStatus {
         private final byte[] mKeyId;
         private final int mStatusCode;
 
+        /**
+         * The key is currently usable to decrypt media data
+         */
+        public static final int STATUS_USABLE = 0;
+
+        /**
+         * The key is no longer usable to decrypt media data because its
+         * expiration time has passed.
+         */
+        public static final int STATUS_EXPIRED = 1;
+
+        /**
+         * The key is not currently usable to decrypt media data because its
+         * output requirements cannot currently be met.
+         */
+        public static final int STATUS_OUTPUT_NOT_ALLOWED = 2;
+
+        /**
+         * The status of the key is not yet known and is being determined.
+         * The status will be updated with the actual status when it has
+         * been determined.
+         */
+        public static final int STATUS_PENDING = 3;
+
+        /**
+         * The key is not currently usable to decrypt media data because of an
+         * internal error in processing unrelated to input parameters.  This error
+         * is not actionable by an app.
+         */
+        public static final int STATUS_INTERNAL_ERROR = 4;
+
+        /** @hide */
+        @IntDef({
+            STATUS_USABLE,
+            STATUS_EXPIRED,
+            STATUS_OUTPUT_NOT_ALLOWED,
+            STATUS_PENDING,
+            STATUS_INTERNAL_ERROR,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface KeyStatusCode {}
+
         KeyStatus(@NonNull byte[] keyId, @KeyStatusCode int statusCode) {
             mKeyId = keyId;
             mStatusCode = statusCode;
@@ -393,6 +393,9 @@
 
         /**
          * Returns the status code for the key
+         * @return one of {@link #STATUS_USABLE}, {@link #STATUS_EXPIRED},
+         * {@link #STATUS_OUTPUT_NOT_ALLOWED}, {@link #STATUS_PENDING}
+         * or {@link #STATUS_INTERNAL_ERROR}.
          */
         @KeyStatusCode
         public int getStatusCode() { return mStatusCode; }
@@ -484,7 +487,7 @@
 
     private static final int DRM_EVENT = 200;
     private static final int EXPIRATION_UPDATE = 201;
-    private static final int KEYS_CHANGE = 202;
+    private static final int KEY_STATUS_CHANGE = 202;
 
     private class EventHandler extends Handler
     {
@@ -522,8 +525,8 @@
                 }
                 return;
 
-            case KEYS_CHANGE:
-                if (mOnKeysChangeListener != null) {
+            case KEY_STATUS_CHANGE:
+                if (mOnKeyStatusChangeListener != null) {
                     if (msg.obj != null && msg.obj instanceof Parcel) {
                         Parcel parcel = (Parcel)msg.obj;
                         byte[] sessionId = parcel.createByteArray();
@@ -531,9 +534,9 @@
                             List<KeyStatus> keyStatusList = keyStatusListFromParcel(parcel);
                             boolean hasNewUsableKey = (parcel.readInt() != 0);
 
-                            Log.i(TAG, "Drm keys change");
-                            mOnKeysChangeListener.onKeysChange(mMediaDrm, sessionId, keyStatusList,
-                                    hasNewUsableKey);
+                            Log.i(TAG, "Drm key status changed");
+                            mOnKeyStatusChangeListener.onKeyStatusChange(mMediaDrm, sessionId,
+                                    keyStatusList, hasNewUsableKey);
                         }
                     }
                 }
@@ -641,30 +644,6 @@
     public @interface KeyType {}
 
     /**
-     * Key request type is initial license request
-     */
-    public static final int REQUEST_TYPE_INITIAL = 0;
-
-    /**
-     * Key request type is license renewal
-     */
-    public static final int REQUEST_TYPE_RENEWAL = 1;
-
-    /**
-     * Key request type is license release
-     */
-    public static final int REQUEST_TYPE_RELEASE = 2;
-
-    /** @hide */
-    @IntDef({
-        REQUEST_TYPE_INITIAL,
-        REQUEST_TYPE_RENEWAL,
-        REQUEST_TYPE_RELEASE,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RequestType {}
-
-    /**
      * Contains the opaque data an app uses to request keys from a license server
      */
     public static final class KeyRequest {
@@ -672,6 +651,30 @@
         private String mDefaultUrl;
         private int mRequestType;
 
+        /**
+         * Key request type is initial license request
+         */
+        public static final int REQUEST_TYPE_INITIAL = 0;
+
+        /**
+         * Key request type is license renewal
+         */
+        public static final int REQUEST_TYPE_RENEWAL = 1;
+
+        /**
+         * Key request type is license release
+         */
+        public static final int REQUEST_TYPE_RELEASE = 2;
+
+        /** @hide */
+        @IntDef({
+            REQUEST_TYPE_INITIAL,
+            REQUEST_TYPE_RENEWAL,
+            REQUEST_TYPE_RELEASE,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface RequestType {}
+
         KeyRequest() {}
 
         /**
@@ -707,6 +710,8 @@
 
         /**
          * Get the type of the request
+         * @return one of {@link #REQUEST_TYPE_INITIAL},
+         * {@link #REQUEST_TYPE_RENEWAL} or {@link #REQUEST_TYPE_RELEASE}
          */
         @RequestType
         public int getRequestType() { return mRequestType; }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index e3a6a83..f148606 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -40,6 +40,7 @@
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.util.Log;
+import android.util.Pair;
 import android.view.Surface;
 import android.view.SurfaceHolder;
 import android.widget.VideoView;
@@ -70,6 +71,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.InetSocketAddress;
+import java.util.BitSet;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Scanner;
 import java.util.Set;
@@ -639,9 +642,7 @@
         }
 
         mTimeProvider = new TimeProvider(this);
-        mOutOfBandSubtitleTracks = new Vector<SubtitleTrack>();
         mOpenSubtitleSources = new Vector<InputStream>();
-        mInbandSubtitleTracks = new SubtitleTrack[0];
         IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
         mAppOps = IAppOpsService.Stub.asInterface(b);
 
@@ -1693,8 +1694,6 @@
             }
             mOpenSubtitleSources.clear();
         }
-        mOutOfBandSubtitleTracks.clear();
-        mInbandSubtitleTracks = new SubtitleTrack[0];
         if (mSubtitleController != null) {
             mSubtitleController.reset();
         }
@@ -1709,6 +1708,11 @@
         if (mEventHandler != null) {
             mEventHandler.removeCallbacksAndMessages(null);
         }
+
+        synchronized (mIndexTrackPairs) {
+            mIndexTrackPairs.clear();
+            mInbandTrackIndices.clear();
+        };
     }
 
     private native void _reset();
@@ -2050,6 +2054,16 @@
 
     };
 
+    // We would like domain specific classes with more informative names than the `first` and `second`
+    // in generic Pair, but we would also like to avoid creating new/trivial classes. As a compromise
+    // we document the meanings of `first` and `second` here:
+    //
+    // Pair.first - inband track index; non-null iff representing an inband track.
+    // Pair.second - a SubtitleTrack registered with mSubtitleController; non-null iff representing
+    //               an inband subtitle track or any out-of-band track (subtitle or timedtext).
+    private Vector<Pair<Integer, SubtitleTrack>> mIndexTrackPairs = new Vector<>();
+    private BitSet mInbandTrackIndices = new BitSet();
+
     /**
      * Returns an array of track information.
      *
@@ -2061,17 +2075,20 @@
     public TrackInfo[] getTrackInfo() throws IllegalStateException {
         TrackInfo trackInfo[] = getInbandTrackInfo();
         // add out-of-band tracks
-        TrackInfo allTrackInfo[] = new TrackInfo[trackInfo.length + mOutOfBandSubtitleTracks.size()];
-        System.arraycopy(trackInfo, 0, allTrackInfo, 0, trackInfo.length);
-        int i = trackInfo.length;
-        for (SubtitleTrack track: mOutOfBandSubtitleTracks) {
-            int type = track.isTimedText()
-                    ? TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT
-                    : TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
-            allTrackInfo[i] = new TrackInfo(type, track.getFormat());
-            ++i;
+        synchronized (mIndexTrackPairs) {
+            TrackInfo allTrackInfo[] = new TrackInfo[mIndexTrackPairs.size()];
+            for (int i = 0; i < allTrackInfo.length; i++) {
+                Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
+                if (p.first != null) {
+                    // inband track
+                    allTrackInfo[i] = trackInfo[p.first];
+                } else {
+                    SubtitleTrack track = p.second;
+                    allTrackInfo[i] = new TrackInfo(track.getTrackType(), track.getFormat());
+                }
+            }
+            return allTrackInfo;
         }
-        return allTrackInfo;
     }
 
     private TrackInfo[] getInbandTrackInfo() throws IllegalStateException {
@@ -2167,22 +2184,21 @@
         }
     }
 
-    private final Object mInbandSubtitleLock = new Object();
-    private SubtitleTrack[] mInbandSubtitleTracks;
     private int mSelectedSubtitleTrackIndex = -1;
-    private Vector<SubtitleTrack> mOutOfBandSubtitleTracks;
     private Vector<InputStream> mOpenSubtitleSources;
 
     private OnSubtitleDataListener mSubtitleDataListener = new OnSubtitleDataListener() {
         @Override
         public void onSubtitleData(MediaPlayer mp, SubtitleData data) {
             int index = data.getTrackIndex();
-            if (index >= mInbandSubtitleTracks.length) {
-                return;
-            }
-            SubtitleTrack track = mInbandSubtitleTracks[index];
-            if (track != null) {
-                track.onData(data);
+            synchronized (mIndexTrackPairs) {
+                for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) {
+                    if (p.first != null && p.first == index && p.second != null) {
+                        // inband subtitle track that owns data
+                        SubtitleTrack track = p.second;
+                        track.onData(data);
+                    }
+                }
             }
         }
     };
@@ -2201,18 +2217,24 @@
         if (track == null) {
             return;
         }
-        for (int i = 0; i < mInbandSubtitleTracks.length; i++) {
-            if (mInbandSubtitleTracks[i] == track) {
-                Log.v(TAG, "Selecting subtitle track " + i);
-                mSelectedSubtitleTrackIndex = i;
-                try {
-                    selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true);
-                } catch (IllegalStateException e) {
+
+        synchronized (mIndexTrackPairs) {
+            for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) {
+                if (p.first != null && p.second == track) {
+                    // inband subtitle track that is selected
+                    mSelectedSubtitleTrackIndex = p.first;
+                    break;
                 }
-                setOnSubtitleDataListener(mSubtitleDataListener);
-                break;
             }
         }
+
+        if (mSelectedSubtitleTrackIndex >= 0) {
+            try {
+                selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true);
+            } catch (IllegalStateException e) {
+            }
+            setOnSubtitleDataListener(mSubtitleDataListener);
+        }
         // no need to select out-of-band tracks
     }
 
@@ -2252,7 +2274,9 @@
                     mOpenSubtitleSources.remove(fIs);
                 }
                 scanner.close();
-                mOutOfBandSubtitleTracks.add(track);
+                synchronized (mIndexTrackPairs) {
+                    mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track));
+                }
                 track.onData(contents.getBytes(), true /* eos */, ~0 /* runID: keep forever */);
                 return MEDIA_INFO_EXTERNAL_METADATA_UPDATE;
             }
@@ -2270,27 +2294,37 @@
 
     private void scanInternalSubtitleTracks() {
         if (mSubtitleController == null) {
-            Log.e(TAG, "Should have subtitle controller already set");
-            return;
+            Log.w(TAG, "setSubtitleAnchor in MediaPlayer");
+            setSubtitleAnchor();
         }
 
+        populateInbandTracks();
+
+        if (mSubtitleController != null) {
+            mSubtitleController.selectDefaultTrack();
+        }
+    }
+
+    private void populateInbandTracks() {
         TrackInfo[] tracks = getInbandTrackInfo();
-        synchronized (mInbandSubtitleLock) {
-            SubtitleTrack[] inbandTracks = new SubtitleTrack[tracks.length];
-            for (int i=0; i < tracks.length; i++) {
+        synchronized (mIndexTrackPairs) {
+            for (int i = 0; i < tracks.length; i++) {
+                if (mInbandTrackIndices.get(i)) {
+                    continue;
+                } else {
+                    mInbandTrackIndices.set(i);
+                }
+
+                // newly appeared inband track
                 if (tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-                    if (i < mInbandSubtitleTracks.length) {
-                        inbandTracks[i] = mInbandSubtitleTracks[i];
-                    } else {
-                        SubtitleTrack track = mSubtitleController.addTrack(
-                                tracks[i].getFormat());
-                        inbandTracks[i] = track;
-                    }
+                    SubtitleTrack track = mSubtitleController.addTrack(
+                            tracks[i].getFormat());
+                    mIndexTrackPairs.add(Pair.create(i, track));
+                } else {
+                    mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(i, null));
                 }
             }
-            mInbandSubtitleTracks = inbandTracks;
         }
-        mSubtitleController.selectDefaultTrack();
     }
 
     /* TODO: Limit the total number of external timed text source to a reasonable number.
@@ -2438,7 +2472,9 @@
             mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler));
         }
         final SubtitleTrack track = mSubtitleController.addTrack(fFormat);
-        mOutOfBandSubtitleTracks.add(track);
+        synchronized (mIndexTrackPairs) {
+            mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track));
+        }
 
         final FileDescriptor fd3 = fd2;
         final long offset2 = offset;
@@ -2510,12 +2546,18 @@
      * @see #deselectTrack(int)
      */
     public int getSelectedTrack(int trackType) throws IllegalStateException {
-        if (trackType == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE && mSubtitleController != null) {
+        if (mSubtitleController != null
+                && (trackType == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
+                || trackType == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT)) {
             SubtitleTrack subtitleTrack = mSubtitleController.getSelectedTrack();
             if (subtitleTrack != null) {
-                int index = mOutOfBandSubtitleTracks.indexOf(subtitleTrack);
-                if (index >= 0) {
-                    return mInbandSubtitleTracks.length + index;
+                synchronized (mIndexTrackPairs) {
+                    for (int i = 0; i < mIndexTrackPairs.size(); i++) {
+                        Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
+                        if (p.second == subtitleTrack && subtitleTrack.getTrackType() == trackType) {
+                            return i;
+                        }
+                    }
                 }
             }
         }
@@ -2527,8 +2569,16 @@
             request.writeInt(INVOKE_ID_GET_SELECTED_TRACK);
             request.writeInt(trackType);
             invoke(request, reply);
-            int selectedTrack = reply.readInt();
-            return selectedTrack;
+            int inbandTrackIndex = reply.readInt();
+            synchronized (mIndexTrackPairs) {
+                for (int i = 0; i < mIndexTrackPairs.size(); i++) {
+                    Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
+                    if (p.first != null && p.first == inbandTrackIndex) {
+                        return i;
+                    }
+                }
+            }
+            return -1;
         } finally {
             request.recycle();
             reply.recycle();
@@ -2588,36 +2638,30 @@
     private void selectOrDeselectTrack(int index, boolean select)
             throws IllegalStateException {
         // handle subtitle track through subtitle controller
-        SubtitleTrack track = null;
-        synchronized (mInbandSubtitleLock) {
-            if (mInbandSubtitleTracks.length == 0) {
-                TrackInfo[] tracks = getInbandTrackInfo();
-                mInbandSubtitleTracks = new SubtitleTrack[tracks.length];
-                for (int i=0; i < tracks.length; i++) {
-                    if (tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-                        mInbandSubtitleTracks[i] = mSubtitleController.addTrack(tracks[i].getFormat());
-                    }
-                }
-            }
+        populateInbandTracks();
+
+        Pair<Integer,SubtitleTrack> p = null;
+        try {
+            p = mIndexTrackPairs.get(index);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // ignore bad index
+            return;
         }
 
-        if (index < mInbandSubtitleTracks.length) {
-            track = mInbandSubtitleTracks[index];
-        } else if (index < mInbandSubtitleTracks.length + mOutOfBandSubtitleTracks.size()) {
-            track = mOutOfBandSubtitleTracks.get(index - mInbandSubtitleTracks.length);
+        SubtitleTrack track = p.second;
+        if (track == null) {
+            // inband (de)select
+            selectOrDeselectInbandTrack(p.first, select);
+            return;
         }
 
-        if (mSubtitleController != null && track != null) {
-            if (select) {
-                if (track.isTimedText()) {
-                    int ttIndex = getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT);
-                    if (ttIndex >= 0 && ttIndex < mInbandSubtitleTracks.length) {
-                        // deselect inband counterpart
-                        selectOrDeselectInbandTrack(ttIndex, false);
-                    }
-                }
-                mSubtitleController.selectTrack(track);
-            } else if (mSubtitleController.getSelectedTrack() == track) {
+        if (mSubtitleController == null) {
+            return;
+        }
+
+        if (!select) {
+            // out-of-band deselect
+            if (mSubtitleController.getSelectedTrack() == track) {
                 mSubtitleController.selectTrack(null);
             } else {
                 Log.w(TAG, "trying to deselect track that was not selected");
@@ -2625,7 +2669,20 @@
             return;
         }
 
-        selectOrDeselectInbandTrack(index, select);
+        // out-of-band select
+        if (track.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
+            int ttIndex = getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT);
+            synchronized (mIndexTrackPairs) {
+                if (ttIndex >= 0 && ttIndex < mIndexTrackPairs.size()) {
+                    Pair<Integer,SubtitleTrack> p2 = mIndexTrackPairs.get(ttIndex);
+                    if (p2.first != null && p2.second == null) {
+                        // deselect inband counterpart
+                        selectOrDeselectInbandTrack(p2.first, false);
+                    }
+                }
+            }
+        }
+        mSubtitleController.selectTrack(track);
     }
 
     private void selectOrDeselectInbandTrack(int index, boolean select)
diff --git a/media/java/android/media/SubtitleController.java b/media/java/android/media/SubtitleController.java
index f82dbe0..fd72b39 100644
--- a/media/java/android/media/SubtitleController.java
+++ b/media/java/android/media/SubtitleController.java
@@ -20,6 +20,7 @@
 import java.util.Vector;
 
 import android.content.Context;
+import android.media.MediaPlayer.TrackInfo;
 import android.media.SubtitleTrack.RenderingWidget;
 import android.os.Handler;
 import android.os.Looper;
@@ -275,7 +276,8 @@
                      mSelectedTrack.getFormat().getInteger(
                             MediaFormat.KEY_IS_FORCED_SUBTITLE, 0) != 0)) {
                     show();
-                } else if (mSelectedTrack != null && !mSelectedTrack.isTimedText()) {
+                } else if (mSelectedTrack != null
+                        && mSelectedTrack.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
                     hide();
                 }
                 mVisibilityIsExplicit = false;
diff --git a/media/java/android/media/SubtitleTrack.java b/media/java/android/media/SubtitleTrack.java
index c760810..6c8e323 100644
--- a/media/java/android/media/SubtitleTrack.java
+++ b/media/java/android/media/SubtitleTrack.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.graphics.Canvas;
+import android.media.MediaPlayer.TrackInfo;
 import android.os.Handler;
 import android.util.Log;
 import android.util.LongSparseArray;
@@ -609,8 +610,10 @@
     }
 
     /** @hide whether this is a text track who fires events instead getting rendered */
-    public boolean isTimedText() {
-        return getRenderingWidget() == null;
+    public int getTrackType() {
+        return getRenderingWidget() == null
+                ? TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT
+                : TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE;
     }
 
 
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index f8146a7..d456dc10 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -99,7 +99,7 @@
 struct EventWhat {
     jint kWhatDrmEvent;
     jint kWhatExpirationUpdate;
-    jint kWhatKeysChange;
+    jint kWhatKeyStatusChange;
 } gEventWhat;
 
 struct KeyTypes {
@@ -221,7 +221,7 @@
             jwhat = gEventWhat.kWhatExpirationUpdate;
             break;
          case DrmPlugin::kDrmPluginEventKeysChange:
-            jwhat = gEventWhat.kWhatKeysChange;
+            jwhat = gEventWhat.kWhatKeyStatusChange;
             break;
         default:
             ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType);
@@ -609,8 +609,8 @@
     gEventWhat.kWhatDrmEvent = env->GetStaticIntField(clazz, field);
     GET_STATIC_FIELD_ID(field, clazz, "EXPIRATION_UPDATE", "I");
     gEventWhat.kWhatExpirationUpdate = env->GetStaticIntField(clazz, field);
-    GET_STATIC_FIELD_ID(field, clazz, "KEYS_CHANGE", "I");
-    gEventWhat.kWhatKeysChange = env->GetStaticIntField(clazz, field);
+    GET_STATIC_FIELD_ID(field, clazz, "KEY_STATUS_CHANGE", "I");
+    gEventWhat.kWhatKeyStatusChange = env->GetStaticIntField(clazz, field);
 
     GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I");
     gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field);
@@ -619,13 +619,6 @@
     GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_RELEASE", "I");
     gKeyTypes.kKeyTypeRelease = env->GetStaticIntField(clazz, field);
 
-    GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_INITIAL", "I");
-    gKeyRequestTypes.kKeyRequestTypeInitial = env->GetStaticIntField(clazz, field);
-    GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RENEWAL", "I");
-    gKeyRequestTypes.kKeyRequestTypeRenewal = env->GetStaticIntField(clazz, field);
-    GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RELEASE", "I");
-    gKeyRequestTypes.kKeyRequestTypeRelease = env->GetStaticIntField(clazz, field);
-
     GET_STATIC_FIELD_ID(field, clazz, "CERTIFICATE_TYPE_NONE", "I");
     gCertificateTypes.kCertificateTypeNone = env->GetStaticIntField(clazz, field);
     GET_STATIC_FIELD_ID(field, clazz, "CERTIFICATE_TYPE_X509", "I");
@@ -636,6 +629,13 @@
     GET_FIELD_ID(gFields.keyRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;");
     GET_FIELD_ID(gFields.keyRequest.requestType, clazz, "mRequestType", "I");
 
+    GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_INITIAL", "I");
+    gKeyRequestTypes.kKeyRequestTypeInitial = env->GetStaticIntField(clazz, field);
+    GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RENEWAL", "I");
+    gKeyRequestTypes.kKeyRequestTypeRenewal = env->GetStaticIntField(clazz, field);
+    GET_STATIC_FIELD_ID(field, clazz, "REQUEST_TYPE_RELEASE", "I");
+    gKeyRequestTypes.kKeyRequestTypeRelease = env->GetStaticIntField(clazz, field);
+
     FIND_CLASS(clazz, "android/media/MediaDrm$ProvisionRequest");
     GET_FIELD_ID(gFields.provisionRequest.data, clazz, "mData", "[B");
     GET_FIELD_ID(gFields.provisionRequest.defaultUrl, clazz, "mDefaultUrl", "Ljava/lang/String;");
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 6c1bd97..8038cdf 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -229,7 +229,7 @@
 {
     ALOGV("unload: sampleID=%d", sampleID);
     Mutex::Autolock lock(&mLock);
-    return mSamples.removeItem(sampleID);
+    return mSamples.removeItem(sampleID) >= 0; // removeItem() returns index or BAD_VALUE
 }
 
 int SoundPool::play(int sampleID, float leftVolume, float rightVolume,
diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java
index cc20d39..883c8c6 100644
--- a/obex/javax/obex/ClientOperation.java
+++ b/obex/javax/obex/ClientOperation.java
@@ -784,12 +784,12 @@
                     mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
                 }
 
-                while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
+                while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE && !mOperationDone) {
                     if (!sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL)) {
                         break;
                     }
                 }
-                while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
+                while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE && !mOperationDone) {
                     mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL, null,
                             mReplyHeader, mPrivateInput, false);
                     // Regardless of the SRM state, wait for the response.
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index dda9358..fd0ba73 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -95,6 +95,8 @@
     <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
     <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" />
+    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
+    <uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS" />
 
     <application android:label="@string/app_label">
         <provider
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9d6d937..e47c7a0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -184,16 +184,17 @@
 
         <activity android:name=".tuner.TunerActivity"
                   android:enabled="false"
-                  android:icon="@*android:drawable/stat_sys_adb"
+                  android:icon="@drawable/tuner"
                   android:theme="@android:style/Theme.Material.Settings"
                   android:label="@string/system_ui_tuner"
+                  android:process=":tuner"
                   android:exported="true">
             <intent-filter>
                 <action android:name="com.android.settings.action.EXTRA_SETTINGS" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
             <meta-data android:name="com.android.settings.category"
-                    android:value="com.android.settings.category.device" />
+                    android:value="com.android.settings.category.system" />
         </activity>
 
         <!-- Alternate Recents -->
@@ -319,6 +320,19 @@
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".egg.ShruggyActivity"
+                  android:theme="@android:style/Theme.NoDisplay"
+                  android:exported="true"
+                  android:launchMode="singleInstance"
+                  android:screenOrientation="locked"
+                  android:process=":sweetsweetdesserts"
+                  android:excludeFromRecents="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="com.android.internal.category.PLATLOGO" />
             </intent-filter>
         </activity>
diff --git a/packages/SystemUI/docs/demo_mode.md b/packages/SystemUI/docs/demo_mode.md
index 18ae4cb..258c76b 100644
--- a/packages/SystemUI/docs/demo_mode.md
+++ b/packages/SystemUI/docs/demo_mode.md
@@ -38,6 +38,8 @@
                      |                            | ```datatype``` | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide
                      |                            | ```level```    | Sets mobile signal strength level (null or 0-4)
                      | ```carriernetworkchange``` |                | Sets mobile signal icon to carrier network change UX when disconnected (```show``` to show icon, any other value to hide)
+                     | ```sims```                 |                | Sets the number of sims (1-8)
+                     | ```nosim```                |                | ```show``` to show icon, any other value to hide
 ```bars```           |                            |                | Control the visual style of the bars (opaque, translucent, etc)
                      | ```mode```                 |                | Sets the bars visual style (opaque, translucent, semi-transparent)
 ```status```         |                            |                | Control the system status icons
diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml
index dc1e633..7b8975a 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon.xml
@@ -14,47 +14,30 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="48dp"
-        android:height="48dp"
-        android:viewportWidth="560.0"
-        android:viewportHeight="560.0">
+        android:width="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
     <path
-        android:pathData="M280.000000,288.000000m-260.000000,0.000000a260.000000,260.000000 0.000000,1.000000 1.000000,520.000000 0.000000a260.000000,260.000000 0.000000,1.000000 1.000000,-520.000000 0.000000"
-        android:fillColor="#14000000"/>
+        android:pathData="M24.0,2.0C11.8,2.0 2.0,11.8 2.0,24.0c0.0,6.1 2.5,11.6 6.4,15.6L39.6,8.4C35.6,4.5 30.1,2.0 24.0,2.0z"
+        android:fillColor="#F57C00"/>
     <path
-        android:pathData="M280.000000,285.000000m-255.000000,0.000000a255.000000,255.000000 0.000000,1.000000 1.000000,510.000000 0.000000a255.000000,255.000000 0.000000,1.000000 1.000000,-510.000000 0.000000"
-        android:fillColor="#26000000"/>
+        android:pathData="M39.6,8.4L8.4,39.6c4.0,4.0 9.5,6.4 15.6,6.4c12.2,0.0 22.0,-9.8 22.0,-22.0C46.0,17.9 43.5,12.4 39.6,8.4z"
+        android:fillColor="#FF9800"/>
     <path
-        android:pathData="M280.000000,282.000000m-252.000000,0.000000a252.000000,252.000000 0.000000,1.000000 1.000000,504.000000 0.000000a252.000000,252.000000 0.000000,1.000000 1.000000,-504.000000 0.000000"
-        android:fillColor="#26000000"/>
+        android:pathData="M45.9,25.9L34.0,14.0L14.0,34.0l11.9,11.9C36.5,45.0 45.0,36.5 45.9,25.9z"
+        android:fillAlpha="0.33"
+        android:fillColor="#F57C00"/>
     <path
-        android:pathData="M280.000000,280.000000m-250.000000,0.000000a250.000000,250.000000 0.000000,1.000000 1.000000,500.000000 0.000000a250.000000,250.000000 0.000000,1.000000 1.000000,-500.000000 0.000000"
-        android:fillColor="#9C27B0"/>
-    <path
-        android:pathData="M265.786011,244.938004l0.000000,8.768000c6.527000,-6.384000 15.303000,-10.321000 25.063000,-10.321000c20.214001,0.000000 36.429001,16.500000 36.429001,36.714001c0.000000,20.072001 -16.215000,36.500000 -36.429001,36.500000c-9.759000,0.000000 -18.107000,-3.651000 -24.634001,-9.759000l0.000000,25.839001l-10.107000,0.000000l0.000000,-87.740997L265.786011,244.937988L265.786011,244.938004zM267.330994,266.490997c-0.420000,0.706000 -1.125000,1.821000 -1.125000,4.277000l0.000000,18.813000c0.000000,1.759000 0.420000,2.740000 0.982000,3.723000c4.634000,7.929000 13.197000,13.339000 22.882999,13.339000c14.671000,0.000000 26.742001,-11.999000 26.742001,-26.669001c0.000000,-14.536000 -12.071000,-26.607000 -26.742001,-26.607000C280.454987,253.365997 271.963989,258.625000 267.330994,266.490997z"
+        android:pathData="M24.0,24.0c0.0,0.0 0.0,2.2 0.0,5.0s0.0,5.0 0.0,5.0l10.0,-10.0L34.0,14.0L24.0,24.0z"
         android:fillColor="#FFFFFF"/>
     <path
-        android:pathData="M427.973999,244.938004l0.000000,8.768000c6.526000,-6.384000 15.304000,-10.321000 25.062000,-10.321000c20.215000,0.000000 36.429001,16.500000 36.429001,36.714001c0.000000,20.072001 -16.214001,36.500000 -36.429001,36.500000c-9.758000,0.000000 -18.106001,-3.651000 -24.634001,-9.759000l0.000000,25.839001l-10.107000,0.000000l0.000000,-87.740997L427.973999,244.937988L427.973999,244.938004zM429.518005,266.490997c-0.419000,0.706000 -1.125000,1.821000 -1.125000,4.277000l0.000000,18.813000c0.000000,1.759000 0.420000,2.740000 0.982000,3.723000c4.634000,7.929000 13.196000,13.339000 22.884001,13.339000c14.670000,0.000000 26.740999,-11.999000 26.740999,-26.669001c0.000000,-14.536000 -12.071000,-26.607000 -26.740999,-26.607000C442.643005,253.365997 434.152008,258.625000 429.518005,266.490997z"
-        android:fillColor="#FFFFFF"/>
+        android:pathData="M24.0,24.0L14.0,14.0l0.0,10.0l10.0,10.0c0.0,0.0 0.0,-2.2 0.0,-5.0S24.0,24.0 24.0,24.0z"
+        android:fillColor="#EEEEEE"/>
     <path
-        android:pathData="M181.330994,279.893005c0.000000,20.214001 -16.357000,36.715000 -36.438000,36.715000c-20.214001,0.000000 -36.643002,-16.500999 -36.643002,-36.715000c0.000000,-20.070999 16.419001,-36.500000 36.643002,-36.500000C164.973007,243.393005 181.330994,259.821014 181.330994,279.893005zM171.151993,280.036011c0.000000,-14.669000 -11.723000,-26.669001 -26.259001,-26.669001c-14.741000,0.000000 -26.250000,12.000000 -26.250000,26.669001c0.000000,14.536000 11.509000,26.607000 26.250000,26.607000C159.429001,306.634003 171.151993,294.562012 171.151993,280.036011z"
-        android:fillColor="#FFFFFF"/>
+        android:pathData="M14.0,34.0l10.0,0.0 -10.0,-10.0z"
+        android:fillColor="#DDDDDD"/>
     <path
-        android:pathData="M408.384003,279.893005c0.000000,20.214001 -16.357000,36.715000 -36.437000,36.715000c-20.215000,0.000000 -36.644001,-16.500999 -36.644001,-36.715000c0.000000,-20.070999 16.420000,-36.500000 36.644001,-36.500000C392.026001,243.393005 408.384003,259.821014 408.384003,279.893005zM398.204987,280.036011c0.000000,-14.669000 -11.723000,-26.669001 -26.257999,-26.669001c-14.742000,0.000000 -26.250999,12.000000 -26.250999,26.669001c0.000000,14.536000 11.509000,26.607000 26.250999,26.607000C386.481995,306.634003 398.204987,294.562012 398.204987,280.036011z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M234.481995,227.320999l9.973000,0.000000l0.000000,10.250000l-9.973000,0.000000z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M234.481995,244.865997l9.973000,0.000000l0.000000,70.195999l-9.973000,0.000000z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M88.392998,227.320999l9.973000,0.000000l0.000000,87.740997l-9.973000,0.000000z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M191.231995,227.320999l9.973000,0.000000l0.000000,87.740997l-9.973000,0.000000z"
-        android:fillColor="#FFFFFF"/>
-    <path
-        android:pathData="M212.856995,227.320999l9.974000,0.000000l0.000000,87.740997l-9.974000,0.000000z"
-        android:fillColor="#FFFFFF"/>
+        android:pathData="M34.0,34.0l0.0,-10.0 -10.0,10.0z"
+        android:fillColor="#DDDDDD"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable-nodpi/tuner.xml b/packages/SystemUI/res/drawable-nodpi/tuner.xml
new file mode 100644
index 0000000..e27423f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/tuner.xml
@@ -0,0 +1,27 @@
+<!--
+   Copyright (C) 2015 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="48.0dp"
+        android:height="48.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M29.9,24.8c0.0,-0.3 0.1,-0.5 0.1,-0.8s0.0,-0.5 -0.1,-0.8l1.7,-1.3c0.2,-0.1 0.2,-0.3 0.1,-0.5l-1.6,-2.8c-0.1,-0.2 -0.3,-0.2 -0.5,-0.2l-2.0,0.8c-0.4,-0.3 -0.9,-0.6 -1.4,-0.8L26.0,16.3c0.0,-0.2 -0.2,-0.3 -0.4,-0.3l-3.2,0.0c-0.2,0.0 -0.4,0.1 -0.4,0.3l-0.3,2.1c-0.5,0.2 -0.9,0.5 -1.4,0.8l-2.0,-0.8c-0.2,-0.1 -0.4,0.0 -0.5,0.2l-1.6,2.8c-0.1,0.2 -0.1,0.4 0.1,0.5l1.7,1.3c0.0,0.3 -0.1,0.5 -0.1,0.8s0.0,0.5 0.1,0.8l-1.7,1.3c-0.2,0.1 -0.2,0.3 -0.1,0.5l1.6,2.8c0.1,0.2 0.3,0.2 0.5,0.2l2.0,-0.8c0.4,0.3 0.9,0.6 1.4,0.8l0.3,2.1c0.0,0.2 0.2,0.3 0.4,0.3l3.2,0.0c0.2,0.0 0.4,-0.1 0.4,-0.3l0.3,-2.1c0.5,-0.2 0.9,-0.5 1.4,-0.8l2.0,0.8c0.2,0.1 0.4,0.0 0.5,-0.2l1.6,-2.8c0.1,-0.2 0.1,-0.4 -0.1,-0.5L29.9,24.8zM24.0,26.8c-1.5,0.0 -2.8,-1.3 -2.8,-2.8s1.3,-2.8 2.8,-2.8s2.8,1.3 2.8,2.8S25.5,26.8 24.0,26.8z"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M18.0,38.0c-0.6,0.0 -1.0,-0.4 -1.0,-1.0s0.4,-1.0 1.0,-1.0s1.0,0.4 1.0,1.0S18.6,38.0 18.0,38.0zM24.0,38.0c-0.6,0.0 -1.0,-0.4 -1.0,-1.0s0.4,-1.0 1.0,-1.0s1.0,0.4 1.0,1.0S24.6,38.0 24.0,38.0zM30.0,38.0c-0.6,0.0 -1.0,-0.4 -1.0,-1.0s0.4,-1.0 1.0,-1.0s1.0,0.4 1.0,1.0S30.6,38.0 30.0,38.0zM42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,28.0c0.0,2.2 1.8,4.0 4.0,4.0l36.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM42.0,34.0L6.0,34.0L6.0,14.0l36.0,0.0L42.0,34.0zM9.0,12.0L7.0,12.0l0.0,-2.0l2.0,0.0L9.0,12.0zM13.0,12.0l-2.0,0.0l0.0,-2.0l2.0,0.0L13.0,12.0zM17.0,12.0l-2.0,0.0l0.0,-2.0l2.0,0.0L17.0,12.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml
index 2160ca3..731d4c1 100644
--- a/packages/SystemUI/res/layout/zen_mode_panel.xml
+++ b/packages/SystemUI/res/layout/zen_mode_panel.xml
@@ -61,6 +61,7 @@
             android:layout_height="wrap_content"
             android:layout_marginTop="12dp"
             android:layout_marginStart="24dp"
+            android:textDirection="locale"
             android:lineSpacingMultiplier="1.20029"
             android:layout_toStartOf="@id/zen_introduction_confirm"
             android:textAppearance="@style/TextAppearance.QS.Introduction" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index aac0311..f1bbb0d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1023,7 +1023,7 @@
     <string name="volume_stream_vibrate_dnd" translatable="false">%s vibrate — Priority only</string>
 
     <!-- Name of special SystemUI debug settings -->
-    <string name="system_ui_tuner">SystemUI Tuner</string>
+    <string name="system_ui_tuner">System UI tuner</string>
 
     <!-- Name of quick settings -->
     <string name="quick_settings">Quick Settings</string>
@@ -1033,4 +1033,9 @@
 
     <!-- Name of a quick settings tile controlled by broadcast -->
     <string name="broadcast_tile">Broadcast Tile</string>
+
+    <!-- For preview release. DO NOT TRANSLATE -->
+    <string name="regrettable_lack_of_easter_egg">
+        ¯\\_(ツ)_/¯
+    </string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/egg/ShruggyActivity.java b/packages/SystemUI/src/com/android/systemui/egg/ShruggyActivity.java
new file mode 100644
index 0000000..7459957
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/egg/ShruggyActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.egg;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+import com.android.systemui.R;
+
+public class ShruggyActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Toast.makeText(this, getString(R.string.regrettable_lack_of_easter_egg),
+                Toast.LENGTH_SHORT).show();
+        Log.v("SystemUI", "Hey, it's just a preview; what did you expect?");
+        finish();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 3b217df..72bb136 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -24,23 +24,21 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
 
-import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.qs.QSTile.State;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.Listenable;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.HotspotController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 
 import java.util.Collection;
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 1721335..dcf0438 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -30,9 +30,11 @@
 import com.android.systemui.qs.QSTileView;
 import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataController;
 import com.android.systemui.statusbar.policy.NetworkController.MobileDataController.DataUsageInfo;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
 
 /** Quick settings tile: Cellular **/
 public class CellularTile extends QSTile<QSTile.SignalState> {
@@ -63,9 +65,9 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mController.addNetworkSignalChangedCallback(mCallback);
+            mController.addSignalCallback(mSignalCallback);
         } else {
-            mController.removeNetworkSignalChangedCallback(mCallback);
+            mController.removeSignalCallback(mSignalCallback);
         }
     }
 
@@ -138,7 +140,6 @@
     private static final class CallbackInfo {
         boolean enabled;
         boolean wifiEnabled;
-        boolean wifiConnected;
         boolean airplaneModeEnabled;
         int mobileSignalIconId;
         String signalContentDescription;
@@ -151,40 +152,39 @@
         boolean isDataTypeIconWide;
     }
 
-    private final NetworkSignalChangedCallback mCallback = new NetworkSignalChangedCallback() {
+    private final SignalCallback mSignalCallback = new SignalCallbackAdapter() {
         private final CallbackInfo mInfo = new CallbackInfo();
-
         @Override
-        public void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
-                boolean activityIn, boolean activityOut,
-                String wifiSignalContentDescriptionId, String description) {
+        public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+                boolean activityIn, boolean activityOut, String description) {
             mInfo.wifiEnabled = enabled;
-            mInfo.wifiConnected = connected;
             refreshState(mInfo);
         }
 
         @Override
-        public void onMobileDataSignalChanged(boolean enabled,
-                int mobileSignalIconId,
-                String mobileSignalContentDescriptionId, int dataTypeIconId,
-                boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description,
-                boolean isDataTypeIconWide) {
-            mInfo.enabled = enabled;
-            mInfo.mobileSignalIconId = mobileSignalIconId;
-            mInfo.signalContentDescription = mobileSignalContentDescriptionId;
-            mInfo.dataTypeIconId = dataTypeIconId;
-            mInfo.dataContentDescription = dataTypeContentDescriptionId;
+        public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon,
+                int darkStatusIcon, int statusType, int qsType, boolean activityIn,
+                boolean activityOut, String typeContentDescription, String description,
+                boolean isWide, int subId) {
+            if (qsIcon == null) {
+                // Not data sim, don't display.
+                return;
+            }
+            mInfo.enabled = qsIcon.visible;
+            mInfo.mobileSignalIconId = qsIcon.icon;
+            mInfo.signalContentDescription = qsIcon.contentDescription;
+            mInfo.dataTypeIconId = qsType;
+            mInfo.dataContentDescription = typeContentDescription;
             mInfo.activityIn = activityIn;
             mInfo.activityOut = activityOut;
             mInfo.enabledDesc = description;
-            mInfo.isDataTypeIconWide = isDataTypeIconWide;
+            mInfo.isDataTypeIconWide = qsType != 0 && isWide;
             refreshState(mInfo);
         }
 
         @Override
-        public void onNoSimVisibleChanged(boolean visible) {
-            mInfo.noSim = visible;
+        public void setNoSims(boolean show) {
+            mInfo.noSim = show;
             if (mInfo.noSim) {
                 // Make sure signal gets cleared out when no sims.
                 mInfo.mobileSignalIconId = 0;
@@ -199,12 +199,13 @@
         }
 
         @Override
-        public void onAirplaneModeChanged(boolean enabled) {
-            mInfo.airplaneModeEnabled = enabled;
+        public void setIsAirplaneMode(IconState icon) {
+            mInfo.airplaneModeEnabled = icon.visible;
             refreshState(mInfo);
         }
 
-        public void onMobileDataEnabled(boolean enabled) {
+        @Override
+        public void setMobileDataEnabled(boolean enabled) {
             mDetailAdapter.setMobileDataEnabled(enabled);
         }
     };
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 c3f9e33..9504ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.qs.tiles;
 
-import java.util.List;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -36,7 +34,11 @@
 import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
+
+import java.util.List;
 
 /** Quick settings tile: Wifi **/
 public class WifiTile extends QSTile<QSTile.SignalState> {
@@ -67,9 +69,9 @@
     @Override
     public void setListening(boolean listening) {
         if (listening) {
-            mController.addNetworkSignalChangedCallback(mCallback);
+            mController.addSignalCallback(mSignalCallback);
         } else {
-            mController.removeNetworkSignalChangedCallback(mCallback);
+            mController.removeSignalCallback(mSignalCallback);
         }
     }
 
@@ -211,46 +213,21 @@
         }
     }
 
-    private final NetworkSignalChangedCallback mCallback = new NetworkSignalChangedCallback() {
+    private final SignalCallback mSignalCallback = new SignalCallbackAdapter() {
         @Override
-        public void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
-                boolean activityIn, boolean activityOut,
-                String wifiSignalContentDescriptionId, String description) {
+        public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+                boolean activityIn, boolean activityOut, String description) {
             if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + enabled);
             final CallbackInfo info = new CallbackInfo();
             info.enabled = enabled;
-            info.connected = connected;
-            info.wifiSignalIconId = wifiSignalIconId;
+            info.connected = qsIcon.visible;
+            info.wifiSignalIconId = qsIcon.icon;
             info.enabledDesc = description;
             info.activityIn = activityIn;
             info.activityOut = activityOut;
-            info.wifiSignalContentDescription = wifiSignalContentDescriptionId;
+            info.wifiSignalContentDescription = qsIcon.contentDescription;
             refreshState(info);
         }
-
-        @Override
-        public void onMobileDataSignalChanged(boolean enabled,
-                int mobileSignalIconId,
-                String mobileSignalContentDescriptionId, int dataTypeIconId,
-                boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description,
-                boolean isDataTypeIconWide) {
-            // noop
-        }
-
-        public void onNoSimVisibleChanged(boolean noSims) {
-            // noop
-        }
-
-        @Override
-        public void onAirplaneModeChanged(boolean enabled) {
-            // noop
-        }
-
-        @Override
-        public void onMobileDataEnabled(boolean enabled) {
-            // noop
-        }
     };
 
     private final class WifiDetailAdapter implements DetailAdapter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index f6629dd..14e491b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -33,6 +33,7 @@
 import android.widget.LinearLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
 import com.android.systemui.statusbar.policy.SecurityController;
 
@@ -42,7 +43,7 @@
 // Intimately tied to the design of res/layout/signal_cluster_view.xml
 public class SignalClusterView
         extends LinearLayout
-        implements NetworkControllerImpl.SignalCluster,
+        implements NetworkControllerImpl.SignalCallback,
         SecurityController.SecurityControllerCallback {
 
     static final String TAG = "SignalClusterView";
@@ -59,7 +60,7 @@
     private int mWifiStrengthId = 0;
     private boolean mIsAirplaneMode = false;
     private int mAirplaneIconId = 0;
-    private int mAirplaneContentDescription;
+    private String mAirplaneContentDescription;
     private String mWifiDescription;
     private String mEthernetDescription;
     private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
@@ -166,35 +167,36 @@
     }
 
     @Override
-    public void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription) {
-        mWifiVisible = visible;
-        mWifiStrengthId = strengthIcon;
-        mWifiDescription = contentDescription;
+    public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+            boolean activityIn, boolean activityOut, String description) {
+        mWifiVisible = statusIcon.visible;
+        mWifiStrengthId = statusIcon.icon;
+        mWifiDescription = statusIcon.contentDescription;
 
         apply();
     }
 
     @Override
-    public void setMobileDataIndicators(boolean visible, int strengthIcon, int darkStrengthIcon,
-            int typeIcon, String contentDescription, String typeContentDescription,
-            boolean isTypeIconWide, int subId) {
+    public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int darkStatusIcon,
+            int statusType, int qsType, boolean activityIn, boolean activityOut,
+            String typeContentDescription, String description, boolean isWide, int subId) {
         PhoneState state = getOrInflateState(subId);
-        state.mMobileVisible = visible;
-        state.mMobileStrengthId = strengthIcon;
-        state.mMobileDarkStrengthId = darkStrengthIcon;
-        state.mMobileTypeId = typeIcon;
-        state.mMobileDescription = contentDescription;
+        state.mMobileVisible = statusIcon.visible;
+        state.mMobileStrengthId = statusIcon.icon;
+        state.mMobileDarkStrengthId = darkStatusIcon;
+        state.mMobileTypeId = statusType;
+        state.mMobileDescription = statusIcon.contentDescription;
         state.mMobileTypeDescription = typeContentDescription;
-        state.mIsMobileTypeIconWide = isTypeIconWide;
+        state.mIsMobileTypeIconWide = statusType != 0 && isWide;
 
         apply();
     }
 
     @Override
-    public void setEthernetIndicators(boolean visible, int icon, String contentDescription) {
-        mEthernetVisible = visible;
-        mEthernetIconId = icon;
-        mEthernetDescription = contentDescription;
+    public void setEthernetIndicators(IconState state) {
+        mEthernetVisible = state.visible;
+        mEthernetIconId = state.icon;
+        mEthernetDescription = state.contentDescription;
 
         apply();
     }
@@ -239,15 +241,20 @@
     }
 
     @Override
-    public void setIsAirplaneMode(boolean is, int airplaneIconId, int contentDescription) {
-        mIsAirplaneMode = is;
-        mAirplaneIconId = airplaneIconId;
-        mAirplaneContentDescription = contentDescription;
+    public void setIsAirplaneMode(IconState icon) {
+        mIsAirplaneMode = icon.visible;
+        mAirplaneIconId = icon.icon;
+        mAirplaneContentDescription = icon.contentDescription;
 
         apply();
     }
 
     @Override
+    public void setMobileDataEnabled(boolean enabled) {
+        // Don't care.
+    }
+
+    @Override
     public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
         // Standard group layout onPopulateAccessibilityEvent() implementations
         // ignore content description, so populate manually
@@ -343,8 +350,7 @@
 
         if (mIsAirplaneMode) {
             mAirplane.setImageResource(mAirplaneIconId);
-            mAirplane.setContentDescription(mAirplaneContentDescription != 0 ?
-                    mContext.getString(mAirplaneContentDescription) : null);
+            mAirplane.setContentDescription(mAirplaneContentDescription);
             mAirplane.setVisibility(View.VISIBLE);
         } else {
             mAirplane.setVisibility(View.GONE);
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 887b8f4..2a9df19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -779,9 +779,9 @@
                 (SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
         final SignalClusterView signalClusterQs =
                 (SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
-        mNetworkController.addSignalCluster(signalCluster);
-        mNetworkController.addSignalCluster(signalClusterKeyguard);
-        mNetworkController.addSignalCluster(signalClusterQs);
+        mNetworkController.addSignalCallback(signalCluster);
+        mNetworkController.addSignalCallback(signalClusterKeyguard);
+        mNetworkController.addSignalCallback(signalClusterQs);
         signalCluster.setSecurityController(mSecurityController);
         signalCluster.setNetworkController(mNetworkController);
         signalClusterKeyguard.setSecurityController(mSecurityController);
@@ -3087,6 +3087,16 @@
         }
         mContext.unregisterReceiver(mBroadcastReceiver);
         mAssistManager.destroy();
+
+        final SignalClusterView signalCluster =
+                (SignalClusterView) mStatusBarView.findViewById(R.id.signal_cluster);
+        final SignalClusterView signalClusterKeyguard =
+                (SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
+        final SignalClusterView signalClusterQs =
+                (SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
+        mNetworkController.addSignalCallback(signalCluster);
+        mNetworkController.addSignalCallback(signalClusterKeyguard);
+        mNetworkController.addSignalCallback(signalClusterQs);
     }
 
     private boolean mDemoModeAllowed;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
new file mode 100644
index 0000000..7f52191
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.SubscriptionInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.EmergencyListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Implements network listeners and forwards the calls along onto other listeners but on
+ * the current or specified Looper.
+ */
+public class CallbackHandler extends Handler implements EmergencyListener, SignalCallback {
+    private static final int MSG_EMERGENCE_CHANGED           = 0;
+    private static final int MSG_SUBS_CHANGED                = 1;
+    private static final int MSG_NO_SIM_VISIBLE_CHANGED      = 2;
+    private static final int MSG_ETHERNET_CHANGED            = 3;
+    private static final int MSG_AIRPLANE_MODE_CHANGED       = 4;
+    private static final int MSG_MOBILE_DATA_ENABLED_CHANGED = 5;
+    private static final int MSG_ADD_REMOVE_EMERGENCY        = 6;
+    private static final int MSG_ADD_REMOVE_SIGNAL           = 7;
+
+    // All the callbacks.
+    private final ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<>();
+    private final ArrayList<SignalCallback> mSignalCallbacks = new ArrayList<>();
+
+    public CallbackHandler() {
+        super();
+    }
+
+    @VisibleForTesting
+    CallbackHandler(Looper looper) {
+        super(looper);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_EMERGENCE_CHANGED:
+                for (EmergencyListener listener : mEmergencyListeners) {
+                    listener.setEmergencyCallsOnly(msg.arg1 != 0);
+                }
+                break;
+            case MSG_SUBS_CHANGED:
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setSubs((List<SubscriptionInfo>) msg.obj);
+                }
+                break;
+            case MSG_NO_SIM_VISIBLE_CHANGED:
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setNoSims(msg.arg1 != 0);
+                }
+                break;
+            case MSG_ETHERNET_CHANGED:
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setEthernetIndicators((IconState) msg.obj);
+                }
+                break;
+            case MSG_AIRPLANE_MODE_CHANGED:
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setIsAirplaneMode((IconState) msg.obj);
+                }
+                break;
+            case MSG_MOBILE_DATA_ENABLED_CHANGED:
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setMobileDataEnabled(msg.arg1 != 0);
+                }
+                break;
+            case MSG_ADD_REMOVE_EMERGENCY:
+                if (msg.arg1 != 0) {
+                    mEmergencyListeners.add((EmergencyListener) msg.obj);
+                } else {
+                    mEmergencyListeners.remove((EmergencyListener) msg.obj);
+                }
+                break;
+            case MSG_ADD_REMOVE_SIGNAL:
+                if (msg.arg1 != 0) {
+                    mSignalCallbacks.add((SignalCallback) msg.obj);
+                } else {
+                    mSignalCallbacks.remove((SignalCallback) msg.obj);
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void setWifiIndicators(final boolean enabled, final IconState statusIcon,
+            final IconState qsIcon, final boolean activityIn, final boolean activityOut,
+            final String description) {
+        post(new Runnable() {
+            @Override
+            public void run() {
+                for (SignalCallback callback : mSignalCallbacks) {
+                    callback.setWifiIndicators(enabled, statusIcon, qsIcon, activityIn, activityOut,
+                            description);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void setMobileDataIndicators(final IconState statusIcon, final IconState qsIcon,
+            final int darkStatusIcon, final int statusType, final int qsType,
+            final boolean activityIn, final boolean activityOut,
+            final String typeContentDescription, final String description, final boolean isWide,
+            final int subId) {
+        post(new Runnable() {
+            @Override
+            public void run() {
+                for (SignalCallback signalCluster : mSignalCallbacks) {
+                    signalCluster.setMobileDataIndicators(statusIcon, qsIcon, darkStatusIcon,
+                            statusType, qsType, activityIn, activityOut, typeContentDescription,
+                            description, isWide, subId);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void setSubs(List<SubscriptionInfo> subs) {
+        obtainMessage(MSG_SUBS_CHANGED, subs).sendToTarget();
+    }
+
+    @Override
+    public void setNoSims(boolean show) {
+        obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, 0).sendToTarget();
+    }
+
+    @Override
+    public void setMobileDataEnabled(boolean enabled) {
+        obtainMessage(MSG_MOBILE_DATA_ENABLED_CHANGED, enabled ? 1 : 0, 0).sendToTarget();
+    }
+
+    @Override
+    public void setEmergencyCallsOnly(boolean emergencyOnly) {
+        obtainMessage(MSG_EMERGENCE_CHANGED, emergencyOnly ? 1 : 0, 0).sendToTarget();
+    }
+
+    @Override
+    public void setEthernetIndicators(IconState icon) {
+        obtainMessage(MSG_ETHERNET_CHANGED, icon).sendToTarget();;
+    }
+
+    @Override
+    public void setIsAirplaneMode(IconState icon) {
+        obtainMessage(MSG_AIRPLANE_MODE_CHANGED, icon).sendToTarget();;
+    }
+
+    public void setListening(EmergencyListener listener, boolean listening) {
+        obtainMessage(MSG_ADD_REMOVE_EMERGENCY, listening ? 1 : 0, 0, listener).sendToTarget();
+    }
+
+    public void setListening(SignalCallback listener, boolean listening) {
+        obtainMessage(MSG_ADD_REMOVE_SIGNAL, listening ? 1 : 0, 0, listener).sendToTarget();
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
index 9c044c4..bd36462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
@@ -18,22 +18,18 @@
 import android.content.Context;
 import android.net.NetworkCapabilities;
 
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 
-import java.util.List;
-import java.util.Objects;
+import java.util.BitSet;
 
 
 public class EthernetSignalController extends
         SignalController<SignalController.State, SignalController.IconGroup> {
 
     public EthernetSignalController(Context context,
-            List<NetworkSignalChangedCallback> signalCallbacks,
-            List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
+            CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
         super("EthernetSignalController", context, NetworkCapabilities.TRANSPORT_ETHERNET,
-                signalCallbacks, signalClusters, networkController);
+                callbackHandler, networkController);
         mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
                 "Ethernet Icons",
                 EthernetIcons.ETHERNET_ICONS,
@@ -44,25 +40,23 @@
     }
 
     @Override
+    public void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
+        mCurrentState.connected = connectedTransports.get(mTransportType);
+        super.updateConnectivity(connectedTransports, validatedTransports);
+    }
+
+    @Override
     public void notifyListeners() {
         boolean ethernetVisible = mCurrentState.connected;
         String contentDescription = getStringIfExists(getContentDescription());
 
         // TODO: wire up data transfer using WifiSignalPoller.
-        int signalClustersLength = mSignalClusters.size();
-        for (int i = 0; i < signalClustersLength; i++) {
-            mSignalClusters.get(i).setEthernetIndicators(ethernetVisible, getCurrentIconId(),
-                    contentDescription);
-        }
+        mCallbackHandler.setEthernetIndicators(new IconState(ethernetVisible, getCurrentIconId(),
+                contentDescription));
     }
 
     @Override
     public SignalController.State cleanState() {
         return new SignalController.State();
     }
-
-    public void setConnected(boolean connected) {
-        mCurrentState.connected = connected;
-        notifyListenersIfNecessary();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 22bf47c..0d59953 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.NetworkCapabilities;
+import android.os.Looper;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -31,12 +32,11 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.cdma.EriInfo;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
 
 import java.io.PrintWriter;
-import java.util.List;
+import java.util.BitSet;
 import java.util.Objects;
 
 
@@ -66,17 +66,17 @@
     // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
     // need listener lists anymore.
     public MobileSignalController(Context context, Config config, boolean hasMobileData,
-            TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks,
-            List<SignalCluster> signalClusters, NetworkControllerImpl networkController,
-            SubscriptionInfo info) {
+            TelephonyManager phone, CallbackHandler callbackHandler,
+            NetworkControllerImpl networkController, SubscriptionInfo info, Looper receiverLooper) {
         super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
-                NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters,
+                NetworkCapabilities.TRANSPORT_CELLULAR, callbackHandler,
                 networkController);
         mNetworkToIconLookup = new SparseArray<>();
         mConfig = config;
         mPhone = phone;
         mSubscriptionInfo = info;
-        mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId());
+        mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId(),
+                receiverLooper);
         mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
         mNetworkNameDefault = getStringIfExists(
                 com.android.internal.R.string.lockscreen_carrier_default);
@@ -106,13 +106,13 @@
         notifyListenersIfNecessary();
     }
 
-    public void setInetCondition(int inetCondition, int inetConditionForNetwork) {
-        // For mobile data, use general inet condition for phone signal indexing,
-        // and network specific for data indexing (I think this might be a bug, but
-        // keeping for now).
-        // TODO: Update with explanation of why.
-        mCurrentState.inetForNetwork = inetConditionForNetwork;
-        setInetCondition(inetCondition);
+    @Override
+    public void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
+        boolean isValidated = validatedTransports.get(mTransportType);
+        mCurrentState.isDefault = connectedTransports.get(mTransportType);
+        // Only show this as not having connectivity if we are default.
+        mCurrentState.inetCondition = (isValidated || !mCurrentState.isDefault) ? 1 : 0;
+        notifyListenersIfNecessary();
     }
 
     public void setCarrierNetworkChangeMode(boolean carrierNetworkChangeMode) {
@@ -196,44 +196,33 @@
         String contentDescription = getStringIfExists(getContentDescription());
         String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
 
-        boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0
+        // Show icon in QS when we are connected or need to show roaming.
+        boolean showDataIcon = mCurrentState.dataConnected
                 || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
+        IconState statusIcon = new IconState(mCurrentState.enabled && !mCurrentState.airplaneMode,
+                getCurrentIconId(), contentDescription);
 
+        int qsTypeIcon = 0;
+        IconState qsIcon = null;
+        String description = null;
         // Only send data sim callbacks to QS.
         if (mCurrentState.dataSim) {
-            int qsTypeIcon = showDataIcon ? icons.mQsDataType[mCurrentState.inetForNetwork] : 0;
-            int length = mSignalsChangedCallbacks.size();
-            for (int i = 0; i < length; i++) {
-                mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
-                        && !mCurrentState.isEmergency,
-                        getQsCurrentIconId(), contentDescription,
-                        qsTypeIcon,
-                        mCurrentState.dataConnected
-                            && !mCurrentState.carrierNetworkChangeMode
-                            && mCurrentState.activityIn,
-                        mCurrentState.dataConnected
-                            && !mCurrentState.carrierNetworkChangeMode
-                            && mCurrentState.activityOut,
-                        dataContentDescription,
-                        mCurrentState.isEmergency ? null : mCurrentState.networkName,
-                        // Only wide if actually showing something.
-                        icons.mIsWide && qsTypeIcon != 0);
-            }
+            qsTypeIcon = showDataIcon ? icons.mQsDataType : 0;
+            qsIcon = new IconState(mCurrentState.enabled
+                    && !mCurrentState.isEmergency, getQsCurrentIconId(), contentDescription);
+            description = mCurrentState.isEmergency ? null : mCurrentState.networkName;
         }
+        boolean activityIn = mCurrentState.dataConnected
+                        && !mCurrentState.carrierNetworkChangeMode
+                        && mCurrentState.activityIn;
+        boolean activityOut = mCurrentState.dataConnected
+                        && !mCurrentState.carrierNetworkChangeMode
+                        && mCurrentState.activityOut;
+        showDataIcon &= mCurrentState.isDefault;
         int typeIcon = showDataIcon ? icons.mDataType : 0;
-        int signalClustersLength = mSignalClusters.size();
-        for (int i = 0; i < signalClustersLength; i++) {
-            mSignalClusters.get(i).setMobileDataIndicators(
-                    mCurrentState.enabled && !mCurrentState.airplaneMode,
-                    getCurrentIconId(),
-                    getCurrentDarkIconId(),
-                    typeIcon,
-                    contentDescription,
-                    dataContentDescription,
-                    // Only wide if actually showing something.
-                    icons.mIsWide && typeIcon != 0,
-                    mSubscriptionInfo.getSubscriptionId());
-        }
+        mCallbackHandler.setMobileDataIndicators(statusIcon, qsIcon, getCurrentDarkIconId(),
+                typeIcon, qsTypeIcon, activityIn, activityOut, dataContentDescription, description,
+                icons.mIsWide, mSubscriptionInfo.getSubscriptionId());
     }
 
     private int getCurrentDarkIconId() {
@@ -425,8 +414,8 @@
     }
 
     class MobilePhoneStateListener extends PhoneStateListener {
-        public MobilePhoneStateListener(int subId) {
-            super(subId);
+        public MobilePhoneStateListener(int subId, Looper looper) {
+            super(subId, looper);
         }
 
         @Override
@@ -483,12 +472,12 @@
         final int mDataContentDescription; // mContentDescriptionDataType
         final int mDataType;
         final boolean mIsWide;
-        final int[] mQsDataType;
+        final int mQsDataType;
 
         public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
                 int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
                 int discContentDesc, int dataContentDesc, int dataType, boolean isWide,
-                int[] qsDataType) {
+                int qsDataType) {
             this(name, sbIcons, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState,
                     sbDiscState, sbDiscState, qsDiscState, discContentDesc, dataContentDesc,
                     dataType, isWide, qsDataType);
@@ -497,7 +486,7 @@
         public MobileIconGroup(String name, int[][] sbIcons, int[][] sbDarkIcons, int[][] qsIcons,
                 int[] contentDesc, int sbNullState, int qsNullState, int sbDiscState,
                 int sbDarkDiscState, int qsDiscState, int discContentDesc, int dataContentDesc,
-                int dataType, boolean isWide, int[] qsDataType) {
+                int dataType, boolean isWide, int qsDataType) {
             super(name, sbIcons, sbDarkIcons, qsIcons, contentDesc, sbNullState, qsNullState,
                     sbDiscState, sbDarkDiscState, qsDiscState, discContentDesc);
             mDataContentDescription = dataContentDesc;
@@ -515,7 +504,7 @@
         boolean isEmergency;
         boolean airplaneMode;
         boolean carrierNetworkChangeMode;
-        int inetForNetwork;
+        boolean isDefault;
 
         @Override
         public void copyFrom(State s) {
@@ -525,7 +514,7 @@
             networkName = state.networkName;
             networkNameData = state.networkNameData;
             dataConnected = state.dataConnected;
-            inetForNetwork = state.inetForNetwork;
+            isDefault = state.isDefault;
             isEmergency = state.isEmergency;
             airplaneMode = state.airplaneMode;
             carrierNetworkChangeMode = state.carrierNetworkChangeMode;
@@ -539,7 +528,7 @@
             builder.append("networkName=").append(networkName).append(',');
             builder.append("networkNameData=").append(networkNameData).append(',');
             builder.append("dataConnected=").append(dataConnected).append(',');
-            builder.append("inetForNetwork=").append(inetForNetwork).append(',');
+            builder.append("isDefault=").append(isDefault).append(',');
             builder.append("isEmergency=").append(isEmergency).append(',');
             builder.append("airplaneMode=").append(airplaneMode).append(',');
             builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode);
@@ -555,7 +544,7 @@
                     && ((MobileState) o).isEmergency == isEmergency
                     && ((MobileState) o).airplaneMode == airplaneMode
                     && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
-                    && ((MobileState) o).inetForNetwork == inetForNetwork;
+                    && ((MobileState) o).isDefault == isDefault;
         }
     }
 }
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 9212837..070ca63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.content.Context;
 import android.content.Intent;
+import android.telephony.SubscriptionInfo;
 
 import com.android.settingslib.wifi.AccessPoint;
 
@@ -25,25 +27,45 @@
 public interface NetworkController {
 
     boolean hasMobileDataFeature();
-    void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb);
-    void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb);
+    void addSignalCallback(SignalCallback cb);
+    void removeSignalCallback(SignalCallback cb);
     void setWifiEnabled(boolean enabled);
     void onUserSwitched(int newUserId);
     AccessPointController getAccessPointController();
     MobileDataController getMobileDataController();
 
-    public interface NetworkSignalChangedCallback {
-        void onWifiSignalChanged(boolean enabled, boolean connected, int wifiSignalIconId,
-                boolean activityIn, boolean activityOut,
-                String wifiSignalContentDescriptionId, String description);
-        void onMobileDataSignalChanged(boolean enabled, int mobileSignalIconId,
-                String mobileSignalContentDescriptionId, int dataTypeIconId,
-                boolean activityIn, boolean activityOut,
-                String dataTypeContentDescriptionId, String description,
-                boolean isDataTypeIconWide);
-        void onNoSimVisibleChanged(boolean visible);
-        void onAirplaneModeChanged(boolean enabled);
-        void onMobileDataEnabled(boolean enabled);
+    public interface SignalCallback {
+        void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+                boolean activityIn, boolean activityOut, String description);
+
+        void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int darkStatusIcon,
+                int statusType, int qsType, boolean activityIn, boolean activityOut,
+                String typeContentDescription, String description, boolean isWide, int subId);
+        void setSubs(List<SubscriptionInfo> subs);
+        void setNoSims(boolean show);
+
+        void setEthernetIndicators(IconState icon);
+
+        void setIsAirplaneMode(IconState icon);
+
+        void setMobileDataEnabled(boolean enabled);
+    }
+
+    public static class IconState {
+        public final boolean visible;
+        public final int icon;
+        public final String contentDescription;
+
+        public IconState(boolean visible, int icon, String contentDescription) {
+            this.visible = visible;
+            this.icon = icon;
+            this.contentDescription = contentDescription;
+        }
+
+        public IconState(boolean visible, int icon, int contentDescription,
+                Context context) {
+            this(visible, icon, context.getString(contentDescription));
+        }
     }
 
     /**
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 92e0365..e8957f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -28,6 +28,7 @@
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
@@ -36,6 +37,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.MathUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.PhoneConstants;
@@ -61,7 +63,7 @@
     static final String TAG = "NetworkController";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     // additional diagnostics, but not logspew
-    static final boolean CHATTY =  Log.isLoggable(TAG + ".Chat", Log.DEBUG);
+    static final boolean CHATTY =  Log.isLoggable(TAG + "Chat", Log.DEBUG);
 
     private final Context mContext;
     private final TelephonyManager mPhone;
@@ -99,20 +101,19 @@
     private boolean mHasNoSims;
     private Locale mLocale = null;
     // This list holds our ordering.
-    private List<SubscriptionInfo> mCurrentSubscriptions
-            = new ArrayList<SubscriptionInfo>();
+    private List<SubscriptionInfo> mCurrentSubscriptions = new ArrayList<>();
 
-    // All the callbacks.
-    private ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<EmergencyListener>();
-    private ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>();
-    private ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks =
-            new ArrayList<NetworkSignalChangedCallback>();
     @VisibleForTesting
     boolean mListening;
 
     // The current user ID.
     private int mCurrentUserId;
 
+    // Handler that all broadcasts are received on.
+    private final Handler mReceiverHandler;
+    // Handler that all callbacks are made on.
+    private final CallbackHandler mCallbackHandler;
+
     /**
      * Construct this controller object and register for updates.
      */
@@ -120,20 +121,24 @@
         this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
                 (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
-                SubscriptionManager.from(context), Config.readConfig(context),
+                SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
+                new CallbackHandler(),
                 new AccessPointControllerImpl(context, bgLooper),
                 new MobileDataControllerImpl(context));
-        registerListeners();
+        mReceiverHandler.post(mRegisterListeners);
     }
 
     @VisibleForTesting
     NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
             TelephonyManager telephonyManager, WifiManager wifiManager,
-            SubscriptionManager subManager, Config config,
+            SubscriptionManager subManager, Config config, Looper bgLooper,
+            CallbackHandler callbackHandler,
             AccessPointControllerImpl accessPointController,
             MobileDataControllerImpl mobileDataController) {
         mContext = context;
         mConfig = config;
+        mReceiverHandler = new Handler(bgLooper);
+        mCallbackHandler = callbackHandler;
 
         mSubscriptionManager = subManager;
         mConnectivityManager = connectivityManager;
@@ -141,7 +146,7 @@
                 mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
 
         // telephony
-        mPhone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        mPhone = telephonyManager;
 
         // wifi
         mWifiManager = wifiManager;
@@ -154,14 +159,13 @@
         mMobileDataController.setCallback(new MobileDataControllerImpl.Callback() {
             @Override
             public void onMobileDataEnabled(boolean enabled) {
-                notifyMobileDataEnabled(enabled);
+                mCallbackHandler.setMobileDataEnabled(enabled);
             }
         });
         mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
-                mSignalsChangedCallbacks, mSignalClusters, this);
+                mCallbackHandler, this);
 
-        mEthernetSignalController = new EthernetSignalController(mContext, mSignalsChangedCallbacks,
-                mSignalClusters, this);
+        mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this);
 
         // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
         updateAirplaneMode(true /* force callback */);
@@ -186,7 +190,7 @@
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        mContext.registerReceiver(this, filter);
+        mContext.registerReceiver(this, filter, null, mReceiverHandler);
         mListening = true;
 
         updateMobileControllers();
@@ -216,15 +220,8 @@
     }
 
     public void addEmergencyListener(EmergencyListener listener) {
-        mEmergencyListeners.add(listener);
-        listener.setEmergencyCallsOnly(isEmergencyOnly());
-    }
-
-    private void notifyMobileDataEnabled(boolean enabled) {
-        final int length = mSignalsChangedCallbacks.size();
-        for (int i = 0; i < length; i++) {
-            mSignalsChangedCallbacks.get(i).onMobileDataEnabled(enabled);
-        }
+        mCallbackHandler.setListening(listener, true);
+        mCallbackHandler.setEmergencyCallsOnly(isEmergencyOnly());
     }
 
     public boolean hasMobileDataFeature() {
@@ -276,19 +273,15 @@
      * so we should recheck and send out the state to listeners.
      */
     void recalculateEmergency() {
-        final boolean emergencyOnly = isEmergencyOnly();
-        final int length = mEmergencyListeners.size();
-        for (int i = 0; i < length; i++) {
-            mEmergencyListeners.get(i).setEmergencyCallsOnly(emergencyOnly);
-        }
+        mCallbackHandler.setEmergencyCallsOnly(isEmergencyOnly());
     }
 
-    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);
+    public void addSignalCallback(SignalCallback cb) {
+        mCallbackHandler.setListening(cb, true);
+        mCallbackHandler.setSubs(mCurrentSubscriptions);
+        mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
+                TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
+        mCallbackHandler.setNoSims(mHasNoSims);
         mWifiSignalController.notifyListeners();
         mEthernetSignalController.notifyListeners();
         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
@@ -296,19 +289,9 @@
         }
     }
 
-    public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
-        mSignalsChangedCallbacks.add(cb);
-        cb.onAirplaneModeChanged(mAirplaneMode);
-        cb.onNoSimVisibleChanged(mHasNoSims);
-        mWifiSignalController.notifyListeners();
-        mEthernetSignalController.notifyListeners();
-        for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
-            mobileSignalController.notifyListeners();
-        }
-    }
-
-    public void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
-        mSignalsChangedCallbacks.remove(cb);
+    @Override
+    public void removeSignalCallback(SignalCallback cb) {
+        mCallbackHandler.setListening(cb, false);
     }
 
     @Override
@@ -427,10 +410,7 @@
                         : lhs.getSimSlotIndex() - rhs.getSimSlotIndex();
             }
         });
-        final int length = mSignalClusters.size();
-        for (int i = 0; i < length; i++) {
-            mSignalClusters.get(i).setSubs(subscriptions);
-        }
+        mCallbackHandler.setSubs(subscriptions);
         mCurrentSubscriptions = subscriptions;
 
         HashMap<Integer, MobileSignalController> cachedControllers =
@@ -444,8 +424,8 @@
                 mMobileSignalControllers.put(subId, cachedControllers.remove(subId));
             } else {
                 MobileSignalController controller = new MobileSignalController(mContext, mConfig,
-                        mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, mSignalClusters,
-                        this, subscriptions.get(i));
+                        mHasMobileDataFeature, mPhone, mCallbackHandler,
+                        this, subscriptions.get(i), mReceiverHandler.getLooper());
                 mMobileSignalControllers.put(subId, controller);
                 if (subscriptions.get(i).getSimSlotIndex() == 0) {
                     mDefaultSignalController = controller;
@@ -521,17 +501,9 @@
      * 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);
-        }
-        int signalsChangedLength = mSignalsChangedCallbacks.size();
-        for (int i = 0; i < signalsChangedLength; i++) {
-            mSignalsChangedCallbacks.get(i).onAirplaneModeChanged(mAirplaneMode);
-            mSignalsChangedCallbacks.get(i).onNoSimVisibleChanged(mHasNoSims);
-        }
+        mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
+                TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
+        mCallbackHandler.setNoSims(mHasNoSims);
     }
 
     /**
@@ -566,17 +538,10 @@
     private void pushConnectivityToSignals() {
         // We want to update all the icons, all at once, for any condition change
         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
-            mobileSignalController.setInetCondition(
-                    mInetCondition ? 1 : 0,
-                    mValidatedTransports.get(mobileSignalController.getTransportType()) ? 1 : 0);
+            mobileSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
         }
-        mWifiSignalController.setInetCondition(
-                mValidatedTransports.get(mWifiSignalController.getTransportType()) ? 1 : 0);
-
-        mEthernetSignalController.setConnected(
-                mConnectedTransports.get(mEthernetSignalController.getTransportType()));
-        mEthernetSignalController.setInetCondition(
-                mValidatedTransports.get(mEthernetSignalController.getTransportType()) ? 1 : 0);
+        mWifiSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
+        mEthernetSignalController.updateConnectivity(mConnectedTransports, mValidatedTransports);
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -609,7 +574,7 @@
     }
 
     private boolean mDemoMode;
-    private int mDemoInetCondition;
+    private boolean mDemoInetCondition;
     private WifiSignalController.WifiState mDemoWifiState;
 
     @Override
@@ -618,7 +583,7 @@
             if (DEBUG) Log.d(TAG, "Entering demo mode");
             unregisterListeners();
             mDemoMode = true;
-            mDemoInetCondition = mInetCondition ? 1 : 0;
+            mDemoInetCondition = mInetCondition;
             mDemoWifiState = mWifiSignalController.getState();
         } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
             if (DEBUG) Log.d(TAG, "Exiting demo mode");
@@ -630,24 +595,30 @@
                 controller.resetLastState();
             }
             mWifiSignalController.resetLastState();
-            registerListeners();
+            mReceiverHandler.post(mRegisterListeners);
             notifyAllListeners();
         } else if (mDemoMode && command.equals(COMMAND_NETWORK)) {
             String airplane = args.getString("airplane");
             if (airplane != null) {
                 boolean show = airplane.equals("show");
-                int length = mSignalClusters.size();
-                for (int i = 0; i < length; i++) {
-                    mSignalClusters.get(i).setIsAirplaneMode(show, TelephonyIcons.FLIGHT_MODE_ICON,
-                            R.string.accessibility_airplane_mode);
-                }
+                mCallbackHandler.setIsAirplaneMode(new IconState(show,
+                        TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode,
+                        mContext));
             }
             String fully = args.getString("fully");
             if (fully != null) {
-                mDemoInetCondition = Boolean.parseBoolean(fully) ? 1 : 0;
-                mWifiSignalController.setInetCondition(mDemoInetCondition);
+                mDemoInetCondition = Boolean.parseBoolean(fully);
+                BitSet connected = new BitSet();
+
+                if (mDemoInetCondition) {
+                    connected.set(mWifiSignalController.mTransportType);
+                }
+                mWifiSignalController.updateConnectivity(connected, connected);
                 for (MobileSignalController controller : mMobileSignalControllers.values()) {
-                    controller.setInetCondition(mDemoInetCondition, mDemoInetCondition);
+                    if (mDemoInetCondition) {
+                        connected.set(controller.mTransportType);
+                    }
+                    controller.updateConnectivity(connected, connected);
                 }
             }
             String wifi = args.getString("wifi");
@@ -664,32 +635,21 @@
             }
             String sims = args.getString("sims");
             if (sims != null) {
-                int num = Integer.parseInt(sims);
-                List<SubscriptionInfo> subs = new ArrayList<SubscriptionInfo>();
+                int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
+                List<SubscriptionInfo> subs = new ArrayList<>();
                 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));
+                        subs.add(addSignalController(i, i));
                     }
                 }
-                final int n = mSignalClusters.size();
-                for (int i = 0; i < n; i++) {
-                    mSignalClusters.get(i).setSubs(subs);
-                }
+                mCallbackHandler.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);
-                }
+                mCallbackHandler.setNoSims(show);
             }
             String mobile = args.getString("mobile");
             if (mobile != null) {
@@ -697,6 +657,16 @@
                 String datatype = args.getString("datatype");
                 String slotString = args.getString("slot");
                 int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
+                slot = MathUtils.constrain(slot, 0, 8);
+                // Ensure we have enough sim slots
+                List<SubscriptionInfo> subs = new ArrayList<>();
+                while (mMobileSignalControllers.size() <= slot) {
+                    int nextSlot = mMobileSignalControllers.size();
+                    subs.add(addSignalController(nextSlot, nextSlot));
+                }
+                if (!subs.isEmpty()) {
+                    mCallbackHandler.setSubs(subs);
+                }
                 // Hack to index linearly for easy use.
                 MobileSignalController controller = mMobileSignalControllers
                         .values().toArray(new MobileSignalController[0])[slot];
@@ -733,6 +703,15 @@
         }
     }
 
+    private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
+        SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
+                null, 0, 0, "");
+        mMobileSignalControllers.put(id, new MobileSignalController(mContext,
+                mConfig, mHasMobileDataFeature, mPhone, mCallbackHandler, this, info,
+                mReceiverHandler.getLooper()));
+        return info;
+    }
+
     private final OnSubscriptionsChangedListener mSubscriptionListener =
             new OnSubscriptionsChangedListener() {
         @Override
@@ -741,28 +720,21 @@
         };
     };
 
-    public interface SignalCluster {
-        void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
-
-        void setMobileDataIndicators(boolean visible, int strengthIcon, int darkStrengthIcon,
-                int typeIcon, String contentDescription, String typeContentDescription,
-                boolean isTypeIconWide, int subId);
-        void setSubs(List<SubscriptionInfo> subs);
-        void setNoSims(boolean show);
-
-        void setEthernetIndicators(boolean visible, int icon, String contentDescription);
-
-        void setIsAirplaneMode(boolean is, int airplaneIcon, int contentDescription);
-    }
+    /**
+     * Used to register listeners from the BG Looper, this way the PhoneStateListeners that
+     * get created will also run on the BG Looper.
+     */
+    private final Runnable mRegisterListeners = new Runnable() {
+        @Override
+        public void run() {
+            registerListeners();
+        }
+    };
 
     public interface EmergencyListener {
         void setEmergencyCallsOnly(boolean emergencyOnly);
     }
 
-    public interface CarrierLabelListener {
-        void setCarrierLabel(String label);
-    }
-
     @VisibleForTesting
     static class Config {
         boolean showAtLeast3G = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
new file mode 100644
index 0000000..83a7d3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy;
+
+import android.telephony.SubscriptionInfo;
+
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+
+import java.util.List;
+
+
+/**
+ * Provides empty implementations of SignalCallback for those that only want some of
+ * the callbacks.
+ */
+public class SignalCallbackAdapter implements SignalCallback {
+
+    @Override
+    public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
+            boolean activityIn, boolean activityOut, String description) {
+    }
+
+    @Override
+    public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon,
+            int darkStatusIcon, int statusType, int qsType, boolean activityIn,
+            boolean activityOut, String typeContentDescription, String description,
+            boolean isWide, int subId) {
+    }
+
+    @Override
+    public void setSubs(List<SubscriptionInfo> subs) {
+    }
+
+    @Override
+    public void setNoSims(boolean show) {
+    }
+
+    @Override
+    public void setEthernetIndicators(IconState icon) {
+    }
+
+    @Override
+    public void setIsAirplaneMode(IconState icon) {
+    }
+
+    @Override
+    public void setMobileDataEnabled(boolean enabled) {
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index f3322a1..e6ca646 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -21,11 +21,8 @@
 import android.text.format.DateFormat;
 import android.util.Log;
 
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
-
 import java.io.PrintWriter;
-import java.util.List;
+import java.util.BitSet;
 
 
 /**
@@ -49,24 +46,22 @@
     // The owner of the SignalController (i.e. NetworkController will maintain the following
     // lists and call notifyListeners whenever the list has changed to ensure everyone
     // is aware of current state.
-    protected final List<NetworkSignalChangedCallback> mSignalsChangedCallbacks;
-    protected final List<SignalCluster> mSignalClusters;
     protected final NetworkControllerImpl mNetworkController;
 
+    protected final CallbackHandler mCallbackHandler;
+
     // Save the previous HISTORY_SIZE states for logging.
     private final State[] mHistory;
     // Where to copy the next state into.
     private int mHistoryIndex;
 
-    public SignalController(String tag, Context context, int type,
-            List<NetworkSignalChangedCallback> signalCallbacks,
-            List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
+    public SignalController(String tag, Context context, int type, CallbackHandler callbackHandler,
+            NetworkControllerImpl networkController) {
         mTag = TAG + "." + tag;
         mNetworkController = networkController;
         mTransportType = type;
         mContext = context;
-        mSignalsChangedCallbacks = signalCallbacks;
-        mSignalClusters = signalClusters;
+        mCallbackHandler = callbackHandler;
         mCurrentState = cleanState();
         mLastState = cleanState();
         if (RECORD_HISTORY) {
@@ -81,12 +76,8 @@
         return mCurrentState;
     }
 
-    public int getTransportType() {
-        return mTransportType;
-    }
-
-    public void setInetCondition(int inetCondition) {
-        mCurrentState.inetCondition = inetCondition;
+    public void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
+        mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0;
         notifyListenersIfNecessary();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index 053feb12..fa4d464 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -104,10 +104,7 @@
           R.drawable.ic_qs_signal_carrier_network_change_animation }
     };
 
-    static final int[] QS_DATA_R = {
-        R.drawable.ic_qs_signal_r,
-        R.drawable.ic_qs_signal_r
-    };
+    static final int QS_DATA_R = R.drawable.ic_qs_signal_r;
 
     //***** Data connection icons
 
@@ -123,10 +120,7 @@
               R.drawable.stat_sys_data_fully_connected_g }
         };
 
-    static final int[] QS_DATA_G = {
-        R.drawable.ic_qs_signal_g,
-        R.drawable.ic_qs_signal_g
-    };
+    static final int QS_DATA_G = R.drawable.ic_qs_signal_g;
 
     static final int[][] DATA_3G = {
             { R.drawable.stat_sys_data_fully_connected_3g,
@@ -139,10 +133,7 @@
               R.drawable.stat_sys_data_fully_connected_3g }
         };
 
-    static final int[] QS_DATA_3G = {
-        R.drawable.ic_qs_signal_3g,
-        R.drawable.ic_qs_signal_3g
-    };
+    static final int QS_DATA_3G = R.drawable.ic_qs_signal_3g;
 
     static final int[][] DATA_E = {
             { R.drawable.stat_sys_data_fully_connected_e,
@@ -155,10 +146,7 @@
               R.drawable.stat_sys_data_fully_connected_e }
         };
 
-    static final int[] QS_DATA_E = {
-        R.drawable.ic_qs_signal_e,
-        R.drawable.ic_qs_signal_e
-    };
+    static final int QS_DATA_E = R.drawable.ic_qs_signal_e;
 
     //3.5G
     static final int[][] DATA_H = {
@@ -172,10 +160,7 @@
               R.drawable.stat_sys_data_fully_connected_h }
     };
 
-    static final int[] QS_DATA_H = {
-                R.drawable.ic_qs_signal_h,
-                R.drawable.ic_qs_signal_h
-    };
+    static final int QS_DATA_H = R.drawable.ic_qs_signal_h;
 
     //CDMA
     // Use 3G icons for EVDO data and 1x icons for 1XRTT data
@@ -190,10 +175,7 @@
               R.drawable.stat_sys_data_fully_connected_1x }
             };
 
-    static final int[] QS_DATA_1X = {
-        R.drawable.ic_qs_signal_1x,
-        R.drawable.ic_qs_signal_1x
-    };
+    static final int QS_DATA_1X = R.drawable.ic_qs_signal_1x;
 
     // LTE and eHRPD
     static final int[][] DATA_4G = {
@@ -207,10 +189,7 @@
               R.drawable.stat_sys_data_fully_connected_4g }
         };
 
-    static final int[] QS_DATA_4G = {
-        R.drawable.ic_qs_signal_4g,
-        R.drawable.ic_qs_signal_4g
-    };
+    static final int QS_DATA_4G = R.drawable.ic_qs_signal_4g;
 
     // LTE branded "LTE"
     static final int[][] DATA_LTE = {
@@ -224,10 +203,7 @@
                     R.drawable.stat_sys_data_fully_connected_lte }
     };
 
-    static final int[] QS_DATA_LTE = {
-        R.drawable.ic_qs_signal_lte,
-        R.drawable.ic_qs_signal_lte
-    };
+    static final int QS_DATA_LTE = R.drawable.ic_qs_signal_lte;
 
     static final int FLIGHT_MODE_ICON = R.drawable.stat_sys_airplane_mode;
     static final int ROAMING_ICON = R.drawable.stat_sys_data_fully_connected_roam;
@@ -264,7 +240,7 @@
             R.string.accessibility_carrier_network_change_mode,
             0,
             false,
-            null
+            0
             );
 
     static final MobileIconGroup THREE_G = new MobileIconGroup(
@@ -291,7 +267,7 @@
             TelephonyIcons.TELEPHONY_NO_NETWORK,
             TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
             AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
-            0, 0, false, new int[2]
+            0, 0, false, 0
             );
 
     static final MobileIconGroup E = new MobileIconGroup(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index a97ca50..9b1e72a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -29,8 +29,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.AsyncChannel;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 
 import java.util.List;
 import java.util.Objects;
@@ -43,10 +42,9 @@
     private final boolean mHasMobileData;
 
     public WifiSignalController(Context context, boolean hasMobileData,
-            List<NetworkSignalChangedCallback> signalCallbacks,
-            List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
+            CallbackHandler callbackHandler, NetworkControllerImpl networkController) {
         super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
-                signalCallbacks, signalClusters, networkController);
+                callbackHandler, networkController);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mHasMobileData = hasMobileData;
         Handler handler = new WifiHandler();
@@ -82,19 +80,13 @@
         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++) {
-            mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled,
-                    mCurrentState.connected, getQsCurrentIconId(),
-                    ssidPresent && mCurrentState.activityIn,
-                    ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc);
-        }
 
-        int signalClustersLength = mSignalClusters.size();
-        for (int i = 0; i < signalClustersLength; i++) {
-            mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(),
-                    contentDescription);
-        }
+        IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
+        IconState qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconId(),
+                contentDescription);
+        mCallbackHandler.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
+                ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
+                wifiDesc);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 8dfa9b0..ec24d75 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.Configuration;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -175,7 +176,14 @@
         });
 
         mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
+    }
 
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mZenButtons != null) {
+            mZenButtons.updateLocale();
+        }
     }
 
     private void confirmZenIntroduction() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
new file mode 100644
index 0000000..c14d06f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy;
+
+import android.os.HandlerThread;
+import android.telephony.SubscriptionInfo;
+import android.test.AndroidTestCase;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.statusbar.policy.NetworkControllerImpl.EmergencyListener;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CallbackHandlerTest extends AndroidTestCase {
+
+    private CallbackHandler mHandler;
+    private HandlerThread mHandlerThread;
+
+    @Mock
+    private EmergencyListener mEmengencyListener;
+    @Mock
+    private SignalCallback mSignalCallback;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mHandlerThread = new HandlerThread("TestThread");
+        mHandlerThread.start();
+        mHandler = new CallbackHandler(mHandlerThread.getLooper());
+
+        MockitoAnnotations.initMocks(this);
+        mHandler.setListening(mEmengencyListener, true);
+        mHandler.setListening(mSignalCallback, true);
+    }
+
+    public void testEmergencyListener() {
+        mHandler.setEmergencyCallsOnly(true);
+        waitForCallbacks();
+
+        ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
+        Mockito.verify(mEmengencyListener).setEmergencyCallsOnly(captor.capture());
+        assertTrue(captor.getValue());
+    }
+
+    public void testSignalCallback_setWifiIndicators() {
+        boolean enabled = true;
+        IconState status = new IconState(true, 0, "");
+        IconState qs = new IconState(true, 1, "");
+        boolean in = true;
+        boolean out = true;
+        String description = "Test";
+        mHandler.setWifiIndicators(enabled, status, qs, in, out, description);
+        waitForCallbacks();
+
+        ArgumentCaptor<Boolean> enableArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<IconState> statusArg = ArgumentCaptor.forClass(IconState.class);
+        ArgumentCaptor<IconState> qsArg = ArgumentCaptor.forClass(IconState.class);
+        ArgumentCaptor<Boolean> inArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Boolean> outArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class);
+        Mockito.verify(mSignalCallback).setWifiIndicators(enableArg.capture(),
+                statusArg.capture(), qsArg.capture(), inArg.capture(), outArg.capture(),
+                descArg.capture());
+        assertEquals(enabled, (boolean) enableArg.getValue());
+        assertEquals(status, statusArg.getValue());
+        assertEquals(qs, qsArg.getValue());
+        assertEquals(in, (boolean) inArg.getValue());
+        assertEquals(out, (boolean) outArg.getValue());
+        assertEquals(description, descArg.getValue());
+    }
+
+    public void testSignalCallback_setMobileDataIndicators() {
+        IconState status = new IconState(true, 0, "");
+        IconState qs = new IconState(true, 1, "");
+        int dark = 2;
+        boolean in = true;
+        boolean out = true;
+        String typeDescription = "Test 1";
+        String description = "Test 2";
+        int type = R.drawable.stat_sys_data_fully_connected_1x;
+        int qsType = R.drawable.ic_qs_signal_1x;
+        boolean wide = true;
+        int subId = 5;
+        mHandler.setMobileDataIndicators(status, qs, dark, type, qsType, in, out, typeDescription,
+                description, wide, subId);
+        waitForCallbacks();
+
+        ArgumentCaptor<IconState> statusArg = ArgumentCaptor.forClass(IconState.class);
+        ArgumentCaptor<IconState> qsArg = ArgumentCaptor.forClass(IconState.class);
+        ArgumentCaptor<Integer> darkStrengthArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> qsTypeIconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Boolean> inArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Boolean> outArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<String> typeContentArg = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class);
+        ArgumentCaptor<Boolean> wideArg = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> subIdArg = ArgumentCaptor.forClass(Integer.class);
+        Mockito.verify(mSignalCallback).setMobileDataIndicators(statusArg.capture(), qsArg.capture(),
+                darkStrengthArg.capture(), typeIconArg.capture(), qsTypeIconArg.capture(),
+                inArg.capture(), outArg.capture(), typeContentArg.capture(), descArg.capture(),
+                wideArg.capture(), subIdArg.capture());
+        assertEquals(status, statusArg.getValue());
+        assertEquals(qs, qsArg.getValue());
+        assertEquals(dark, (int) darkStrengthArg.getValue());
+        assertEquals(type, (int) typeIconArg.getValue());
+        assertEquals(qsType, (int) qsTypeIconArg.getValue());
+        assertEquals(in, (boolean) inArg.getValue());
+        assertEquals(out, (boolean) outArg.getValue());
+        assertEquals(typeDescription, typeContentArg.getValue());
+        assertEquals(description, descArg.getValue());
+        assertEquals(wide, (boolean) wideArg.getValue());
+        assertEquals(subId, (int) subIdArg.getValue());
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testSignalCallback_setSubs() {
+        List<SubscriptionInfo> subs = new ArrayList<>();
+        mHandler.setSubs(subs);
+        waitForCallbacks();
+
+        ArgumentCaptor<ArrayList> subsArg = ArgumentCaptor.forClass(ArrayList.class);
+        Mockito.verify(mSignalCallback).setSubs(subsArg.capture());
+        assertTrue(subs == subsArg.getValue());
+    }
+
+    public void testSignalCallback_setNoSims() {
+        boolean noSims = true;
+        mHandler.setNoSims(noSims);
+        waitForCallbacks();
+
+        ArgumentCaptor<Boolean> noSimsArg = ArgumentCaptor.forClass(Boolean.class);
+        Mockito.verify(mSignalCallback).setNoSims(noSimsArg.capture());
+        assertEquals(noSims, (boolean) noSimsArg.getValue());
+    }
+
+    public void testSignalCallback_setEthernetIndicators() {
+        IconState state = new IconState(true, R.drawable.stat_sys_ethernet, "Test Description");
+        mHandler.setEthernetIndicators(state);
+        waitForCallbacks();
+
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+        Mockito.verify(mSignalCallback).setEthernetIndicators(iconArg.capture());
+        assertEquals(state, iconArg.getValue());
+    }
+
+    public void testSignalCallback_setIsAirplaneMode() {
+        IconState state = new IconState(true, R.drawable.stat_sys_airplane_mode, "Test Description");
+        mHandler.setIsAirplaneMode(state);
+        waitForCallbacks();
+
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
+        Mockito.verify(mSignalCallback).setIsAirplaneMode(iconArg.capture());
+        assertEquals(state, iconArg.getValue());
+    }
+
+    private void waitForCallbacks() {
+        mHandlerThread.quitSafely();
+        try {
+            mHandlerThread.join();
+        } catch (InterruptedException e) {
+        }
+    }
+
+}
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 29f461e..2d6bb68 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
@@ -23,6 +23,7 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.wifi.WifiManager;
+import android.os.Looper;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -31,12 +32,10 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
-import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.cdma.EriInfo;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
-import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
@@ -59,8 +58,6 @@
     protected NetworkControllerImpl mNetworkController;
     protected MobileSignalController mMobileSignalController;
     protected PhoneStateListener mPhoneStateListener;
-    protected SignalCluster mSignalCluster;
-    protected NetworkSignalChangedCallback mNetworkSignalChangedCallback;
     private SignalStrength mSignalStrength;
     private ServiceState mServiceState;
     protected ConnectivityManager mMockCm;
@@ -68,6 +65,7 @@
     protected SubscriptionManager mMockSm;
     protected TelephonyManager mMockTm;
     protected Config mConfig;
+    protected CallbackHandler mCallbackHandler;
 
     protected int mSubId;
 
@@ -91,33 +89,36 @@
 
         mConfig = new Config();
         mConfig.hspaDataDistinguishable = true;
+        mCallbackHandler = mock(CallbackHandler.class);
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                mConfig, mock(AccessPointControllerImpl.class),
-                mock(MobileDataControllerImpl.class));
+                mConfig, Looper.getMainLooper(), mCallbackHandler,
+                mock(AccessPointControllerImpl.class), mock(MobileDataControllerImpl.class));
         setupNetworkController();
     }
 
     protected void setupNetworkController() {
         // For now just pretend to be the data sim, so we can test that too.
-        mSubId = SubscriptionManager.getDefaultDataSubId();
+        mSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
         SubscriptionInfo subscription = mock(SubscriptionInfo.class);
         List<SubscriptionInfo> subs = new ArrayList<SubscriptionInfo>();
         when(subscription.getSubscriptionId()).thenReturn(mSubId);
         subs.add(subscription);
         mNetworkController.setCurrentSubscriptions(subs);
         mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId);
+        mMobileSignalController.getState().dataSim = true;
         mPhoneStateListener = mMobileSignalController.mPhoneStateListener;
-        mSignalCluster = mock(SignalCluster.class);
-        mNetworkSignalChangedCallback = mock(NetworkSignalChangedCallback.class);
-        mNetworkController.addSignalCluster(mSignalCluster);
-        mNetworkController.addNetworkSignalChangedCallback(mNetworkSignalChangedCallback);
+
+        // Trigger blank callbacks to always get the current state (some tests don't trigger
+        // changes from default state).
+        mNetworkController.addSignalCallback(null);
     }
 
     protected NetworkControllerImpl setUpNoMobileData() {
       when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
       NetworkControllerImpl networkControllerNoMobile
               = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                        mConfig, mock(AccessPointControllerImpl.class),
+                        mConfig, Looper.getMainLooper(), mCallbackHandler,
+                        mock(AccessPointControllerImpl.class),
                         mock(MobileDataControllerImpl.class));
 
       setupNetworkController();
@@ -144,10 +145,10 @@
         setLevel(DEFAULT_LEVEL);
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
                 TelephonyManager.NETWORK_TYPE_UMTS);
-        setConnectivity(100, ConnectivityManager.TYPE_MOBILE, true);
+        setConnectivity(NetworkCapabilities.TRANSPORT_CELLULAR, true, true);
     }
 
-    public void setConnectivity(int inetCondition, int networkType, boolean isConnected) {
+    public void setConnectivity(int networkType, boolean inetCondition, boolean isConnected) {
         Intent i = new Intent(ConnectivityManager.INET_CONDITION_ACTION);
         // TODO: Separate out into several NetworkCapabilities.
         if (isConnected) {
@@ -155,7 +156,7 @@
         } else {
             mNetCapabilities.removeTransportType(networkType);
         }
-        if (inetCondition != 0) {
+        if (inetCondition) {
             mNetCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
         } else {
             mNetCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
@@ -242,34 +243,30 @@
     protected void verifyHasNoSims(boolean hasNoSimsVisible) {
         ArgumentCaptor<Boolean> hasNoSimsArg = ArgumentCaptor.forClass(Boolean.class);
 
-        Mockito.verify(mSignalCluster, Mockito.atLeastOnce()).setNoSims(hasNoSimsArg.capture());
-        assertEquals("No sims in status bar", hasNoSimsVisible, (boolean) hasNoSimsArg.getValue());
-
-        Mockito.verify(mNetworkSignalChangedCallback, Mockito.atLeastOnce())
-                .onNoSimVisibleChanged(hasNoSimsArg.capture());
-        assertEquals("No sims in quick settings", hasNoSimsVisible,
-                (boolean) hasNoSimsArg.getValue());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(hasNoSimsArg.capture());
+        assertEquals("No sims", hasNoSimsVisible, (boolean) hasNoSimsArg.getValue());
     }
 
     protected void verifyLastQsMobileDataIndicators(boolean visible, int icon, int typeIcon,
             boolean dataIn, boolean dataOut) {
-        ArgumentCaptor<Integer> iconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.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);
 
-        Mockito.verify(mNetworkSignalChangedCallback, Mockito.atLeastOnce())
-                .onMobileDataSignalChanged(visibleArg.capture(), iconArg.capture(),
-                        ArgumentCaptor.forClass(String.class).capture(),
-                        typeIconArg.capture(),
-                        dataInArg.capture(),
-                        dataOutArg.capture(),
-                        ArgumentCaptor.forClass(String.class).capture(),
-                        ArgumentCaptor.forClass(String.class).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());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
+                    ArgumentCaptor.forClass(IconState.class).capture(),
+                    iconArg.capture(),
+                    ArgumentCaptor.forClass(Integer.class).capture(),
+                    ArgumentCaptor.forClass(Integer.class).capture(),
+                    typeIconArg.capture(), dataInArg.capture(), dataOutArg.capture(),
+                    ArgumentCaptor.forClass(String.class).capture(),
+                    ArgumentCaptor.forClass(String.class).capture(),
+                    ArgumentCaptor.forClass(Boolean.class).capture(),
+                    ArgumentCaptor.forClass(Integer.class).capture());
+        IconState iconState = iconArg.getValue();
+        assertEquals("Visibility in, quick settings", visible, iconState.visible);
+        assertEquals("Signal icon in, quick settings", icon, iconState.icon);
         assertEquals("Data icon in, quick settings", typeIcon, (int) typeIconArg.getValue());
         assertEquals("Data direction in, in quick settings", dataIn,
                 (boolean) dataInArg.getValue());
@@ -283,29 +280,32 @@
 
     protected void verifyLastMobileDataIndicators(boolean visible, int strengthIcon,
             int darkStrengthIcon, int typeIcon) {
-        ArgumentCaptor<Integer> strengthIconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
         ArgumentCaptor<Integer> darkStrengthIconArg = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<Boolean> visibleArg = ArgumentCaptor.forClass(Boolean.class);
 
         // TODO: Verify all fields.
-        Mockito.verify(mSignalCluster, Mockito.atLeastOnce()).setMobileDataIndicators(
-                visibleArg.capture(), strengthIconArg.capture(), darkStrengthIconArg.capture(),
-                typeIconArg.capture(),
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
+                iconArg.capture(),
+                ArgumentCaptor.forClass(IconState.class).capture(),
+                darkStrengthIconArg.capture(), typeIconArg.capture(),
+                ArgumentCaptor.forClass(Integer.class).capture(),
+                ArgumentCaptor.forClass(Boolean.class).capture(),
+                ArgumentCaptor.forClass(Boolean.class).capture(),
                 ArgumentCaptor.forClass(String.class).capture(),
                 ArgumentCaptor.forClass(String.class).capture(),
                 ArgumentCaptor.forClass(Boolean.class).capture(),
                 ArgumentCaptor.forClass(Integer.class).capture());
+        IconState iconState = iconArg.getValue();
 
-        assertEquals("Signal strength icon in status bar", strengthIcon,
-                (int) strengthIconArg.getValue());
+        assertEquals("Signal strength icon in status bar", strengthIcon, iconState.icon);
         assertEquals("Signal strength icon (dark mode) in status bar", darkStrengthIcon,
                 (int) darkStrengthIconArg.getValue());
         assertEquals("Data icon in status bar", typeIcon, (int) typeIconArg.getValue());
-        assertEquals("Visibility in status bar", visible, (boolean) visibleArg.getValue());
+        assertEquals("Visibility in status bar", visible, iconState.visible);
     }
 
    protected void assertNetworkNameEquals(String expected) {
-       assertEquals("Network name", expected, mNetworkController.getMobileDataNetworkName());
+       assertEquals("Network name", expected, mMobileSignalController.getState().networkName);
    }
 }
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 3f9312d..15752e1de 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
@@ -1,16 +1,17 @@
 package com.android.systemui.statusbar.policy;
 
-import org.mockito.Mockito;
-
+import android.os.Looper;
 import android.telephony.TelephonyManager;
 
+import org.mockito.Mockito;
+
 public class NetworkControllerDataTest extends NetworkControllerBaseTest {
 
     public void test3gDataIcon() {
         setupDefaultSignal();
 
         verifyDataIndicators(TelephonyIcons.DATA_3G[1][0 /* No direction */],
-                TelephonyIcons.QS_DATA_3G[1]);
+                TelephonyIcons.QS_DATA_3G);
     }
 
     public void testRoamingDataIcon() {
@@ -22,7 +23,7 @@
                 TelephonyIcons.ROAMING_ICON);
         verifyLastQsMobileDataIndicators(true,
                 TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH[1][DEFAULT_LEVEL],
-                TelephonyIcons.QS_DATA_R[1], false, false);
+                TelephonyIcons.QS_DATA_R, false, false);
     }
 
     public void test2gDataIcon() {
@@ -31,7 +32,7 @@
                 TelephonyManager.NETWORK_TYPE_GSM);
 
         verifyDataIndicators(TelephonyIcons.DATA_G[1][0 /* No direction */],
-                TelephonyIcons.QS_DATA_G[1]);
+                TelephonyIcons.QS_DATA_G);
     }
 
     public void testCdmaDataIcon() {
@@ -40,7 +41,7 @@
                 TelephonyManager.NETWORK_TYPE_CDMA);
 
         verifyDataIndicators(TelephonyIcons.DATA_1X[1][0 /* No direction */],
-                TelephonyIcons.QS_DATA_1X[1]);
+                TelephonyIcons.QS_DATA_1X);
     }
 
     public void testEdgeDataIcon() {
@@ -49,7 +50,7 @@
                 TelephonyManager.NETWORK_TYPE_EDGE);
 
         verifyDataIndicators(TelephonyIcons.DATA_E[1][0 /* No direction */],
-                TelephonyIcons.QS_DATA_E[1]);
+                TelephonyIcons.QS_DATA_E);
     }
 
     public void testLteDataIcon() {
@@ -58,7 +59,7 @@
                 TelephonyManager.NETWORK_TYPE_LTE);
 
         verifyDataIndicators(TelephonyIcons.DATA_LTE[1][0 /* No direction */],
-                TelephonyIcons.QS_DATA_LTE[1]);
+                TelephonyIcons.QS_DATA_LTE);
     }
 
     public void testHspaDataIcon() {
@@ -67,14 +68,15 @@
                 TelephonyManager.NETWORK_TYPE_HSPA);
 
         verifyDataIndicators(TelephonyIcons.DATA_H[1][0 /* No direction */],
-                TelephonyIcons.QS_DATA_H[1]);
+                TelephonyIcons.QS_DATA_H);
     }
 
     public void test4gDataIcon() {
         // Switch to showing 4g icon and re-initialize the NetworkController.
         mConfig.show4gForLte = true;
         mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
-                mConfig, Mockito.mock(AccessPointControllerImpl.class),
+                mConfig, Looper.getMainLooper(), mCallbackHandler,
+                Mockito.mock(AccessPointControllerImpl.class),
                 Mockito.mock(MobileDataControllerImpl.class));
         setupNetworkController();
 
@@ -83,7 +85,7 @@
                 TelephonyManager.NETWORK_TYPE_LTE);
 
         verifyDataIndicators(TelephonyIcons.DATA_4G[1][0 /* No direction */],
-                TelephonyIcons.QS_DATA_4G[1]);
+                TelephonyIcons.QS_DATA_4G);
     }
 
     public void test4gDataIconConfigChange() {
@@ -99,7 +101,7 @@
         mNetworkController.handleConfigurationChanged();
 
         verifyDataIndicators(TelephonyIcons.DATA_4G[1][0 /* No direction */],
-                TelephonyIcons.QS_DATA_4G[1]);
+                TelephonyIcons.QS_DATA_4G);
     }
 
     public void testDataActivity() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
index 82ced9f..5d63d8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerEthernetTest.java
@@ -2,6 +2,8 @@
 
 import android.net.NetworkCapabilities;
 
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
@@ -24,17 +26,16 @@
     }
 
     protected void setEthernetState(boolean connected, boolean validated) {
-        setConnectivity(validated ? 100 : 0, NetworkCapabilities.TRANSPORT_ETHERNET, connected);
+        setConnectivity(NetworkCapabilities.TRANSPORT_ETHERNET, validated, connected);
     }
 
     protected void verifyLastEthernetIcon(boolean visible, int icon) {
-        ArgumentCaptor<Boolean> visibleArg = ArgumentCaptor.forClass(Boolean.class);
-        ArgumentCaptor<Integer> iconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
 
-        Mockito.verify(mSignalCluster, Mockito.atLeastOnce()).setEthernetIndicators(
-                visibleArg.capture(), iconArg.capture(),
-                ArgumentCaptor.forClass(String.class).capture());
-        assertEquals("Ethernet visible, in status bar", visible, (boolean) visibleArg.getValue());
-        assertEquals("Ethernet icon, in status bar", icon, (int) iconArg.getValue());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setEthernetIndicators(
+                iconArg.capture());
+        IconState iconState = iconArg.getValue();
+        assertEquals("Ethernet visible, in status bar", visible, iconState.visible);
+        assertEquals("Ethernet icon, in status bar", icon, iconState.icon);
     }
 }
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 389ad6f..874fdf9 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
@@ -19,12 +19,13 @@
 
 import android.content.Intent;
 import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.os.Looper;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
 
-import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.systemui.R;
@@ -41,8 +42,8 @@
         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, mMockSm,
-                mConfig, mock(AccessPointControllerImpl.class),
-                mock(MobileDataControllerImpl.class));
+                mConfig, Looper.getMainLooper(), mCallbackHandler,
+                mock(AccessPointControllerImpl.class), mock(MobileDataControllerImpl.class));
         setupNetworkController();
 
         verifyLastMobileDataIndicators(false, 0, 0);
@@ -61,8 +62,8 @@
         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, mMockSm,
-                mConfig, mock(AccessPointControllerImpl.class),
-                mock(MobileDataControllerImpl.class));
+                mConfig, Looper.getMainLooper(), mCallbackHandler,
+                mock(AccessPointControllerImpl.class), mock(MobileDataControllerImpl.class));
         setupNetworkController();
 
         // No Subscriptions.
@@ -79,13 +80,12 @@
             setLevel(testStrength);
 
             verifyLastMobileDataIndicators(true,
-                    TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[1][testStrength],
-                    DEFAULT_ICON);
+                    TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[1][testStrength], DEFAULT_ICON);
 
             // Verify low inet number indexing.
-            setConnectivity(0, ConnectivityManager.TYPE_MOBILE, true);
+            setConnectivity(NetworkCapabilities.TRANSPORT_CELLULAR, false, true);
             verifyLastMobileDataIndicators(true,
-                    TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[0][testStrength], 0);
+                    TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[0][testStrength], DEFAULT_ICON);
         }
     }
 
@@ -154,18 +154,12 @@
         }
     }
 
-    public void testNoRoamingWithoutSignal() {
+    public void testNoBangWithWifi() {
         setupDefaultSignal();
-        setCdma();
-        setCdmaRoaming(true);
-        setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
-        setDataRegState(ServiceState.STATE_OUT_OF_SERVICE);
+        setConnectivity(mMobileSignalController.mTransportType, false, false);
+        setConnectivity(NetworkCapabilities.TRANSPORT_WIFI, true, true);
 
-        // This exposes the bug in b/18034542, and should be switched to the commented out
-        // verification below (and pass), once the bug is fixed.
-        verifyLastMobileDataIndicators(true, R.drawable.stat_sys_signal_null,
-                TelephonyIcons.ROAMING_ICON);
-        //verifyLastMobileDataIndicators(true, R.drawable.stat_sys_signal_null, 0 /* No Icon */);
+        verifyLastMobileDataIndicators(true, TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[1][2], 0);
     }
 
     // Some tests of actual NetworkController code, just internals not display stuff
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 2e0e9a3..cecf2fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -1,11 +1,13 @@
 package com.android.systemui.statusbar.policy;
 
 import android.content.Intent;
-import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
@@ -25,9 +27,9 @@
         for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
             setWifiLevel(testLevel);
 
-            setConnectivity(100, ConnectivityManager.TYPE_WIFI, true);
+            setConnectivity(NetworkCapabilities.TRANSPORT_WIFI, true, true);
             verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
-            setConnectivity(0, ConnectivityManager.TYPE_WIFI, true);
+            setConnectivity(NetworkCapabilities.TRANSPORT_WIFI, false, true);
             verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
         }
     }
@@ -45,10 +47,10 @@
         for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
             setWifiLevel(testLevel);
 
-            setConnectivity(100, ConnectivityManager.TYPE_WIFI, true);
+            setConnectivity(NetworkCapabilities.TRANSPORT_WIFI, true, true);
             verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[1][testLevel],
                     testSsid);
-            setConnectivity(0, ConnectivityManager.TYPE_WIFI, true);
+            setConnectivity(NetworkCapabilities.TRANSPORT_WIFI, false, true);
             verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[0][testLevel],
                     testSsid);
         }
@@ -61,7 +63,7 @@
         setWifiEnabled(true);
         setWifiState(true, testSsid);
         setWifiLevel(testLevel);
-        setConnectivity(100, ConnectivityManager.TYPE_WIFI, true);
+        setConnectivity(NetworkCapabilities.TRANSPORT_WIFI, true, true);
         verifyLastQsWifiIcon(true, true,
                 WifiIcons.QS_WIFI_SIGNAL_STRENGTH[1][testLevel], testSsid);
 
@@ -82,13 +84,13 @@
         setWifiEnabled(true);
         setWifiState(true, testSsid);
         setWifiLevel(testLevel);
-        setConnectivity(100, ConnectivityManager.TYPE_WIFI, true);
+        setConnectivity(NetworkCapabilities.TRANSPORT_WIFI, true, true);
         verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
 
         setupDefaultSignal();
         setGsmRoaming(true);
         // Still be on wifi though.
-        setConnectivity(100, ConnectivityManager.TYPE_WIFI, true);
+        setConnectivity(NetworkCapabilities.TRANSPORT_WIFI, true, true);
         verifyLastMobileDataIndicators(true,
                 TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH_ROAMING[1][DEFAULT_LEVEL],
                 TelephonyIcons.ROAMING_ICON);
@@ -133,46 +135,39 @@
         ArgumentCaptor<Boolean> inArg = ArgumentCaptor.forClass(Boolean.class);
         ArgumentCaptor<Boolean> outArg = ArgumentCaptor.forClass(Boolean.class);
 
-        Mockito.verify(mNetworkSignalChangedCallback, Mockito.atLeastOnce()).onWifiSignalChanged(
-                ArgumentCaptor.forClass(Boolean.class).capture(),
-                ArgumentCaptor.forClass(Boolean.class).capture(),
-                ArgumentCaptor.forClass(Integer.class).capture(),
-                inArg.capture(), outArg.capture(),
-                ArgumentCaptor.forClass(String.class).capture(),
-                ArgumentCaptor.forClass(String.class).capture());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
+                Mockito.anyBoolean(), Mockito.any(IconState.class), Mockito.any(IconState.class),
+                inArg.capture(), outArg.capture(), Mockito.anyString());
         assertEquals("WiFi data in, in quick settings", in, (boolean) inArg.getValue());
         assertEquals("WiFi data out, in quick settings", out, (boolean) outArg.getValue());
     }
 
     protected void verifyLastQsWifiIcon(boolean enabled, boolean connected, int icon,
             String description) {
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
         ArgumentCaptor<Boolean> enabledArg = ArgumentCaptor.forClass(Boolean.class);
-        ArgumentCaptor<Boolean> connectedArg = ArgumentCaptor.forClass(Boolean.class);
-        ArgumentCaptor<Integer> iconArg = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<String> descArg = ArgumentCaptor.forClass(String.class);
 
-        Mockito.verify(mNetworkSignalChangedCallback, Mockito.atLeastOnce()).onWifiSignalChanged(
-                enabledArg.capture(), connectedArg.capture(), iconArg.capture(),
-                ArgumentCaptor.forClass(Boolean.class).capture(),
-                ArgumentCaptor.forClass(Boolean.class).capture(),
-                ArgumentCaptor.forClass(String.class).capture(),
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
+                enabledArg.capture(), Mockito.any(IconState.class),
+                iconArg.capture(), Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
                 descArg.capture());
+        IconState iconState = iconArg.getValue();
         assertEquals("WiFi enabled, in quick settings", enabled, (boolean) enabledArg.getValue());
-        assertEquals("WiFi connected, in quick settings", connected,
-                (boolean) connectedArg.getValue());
-        assertEquals("WiFi signal, in quick settings", icon, (int) iconArg.getValue());
-        assertEquals("WiFI desc (ssid), in quick settings", description,
-                (String) descArg.getValue());
+        assertEquals("WiFi connected, in quick settings", connected, iconState.visible);
+        assertEquals("WiFi signal, in quick settings", icon, iconState.icon);
+        assertEquals("WiFI desc (ssid), in quick settings", description, descArg.getValue());
     }
 
     protected void verifyLastWifiIcon(boolean visible, int icon) {
-        ArgumentCaptor<Boolean> visibleArg = ArgumentCaptor.forClass(Boolean.class);
-        ArgumentCaptor<Integer> iconArg = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<IconState> iconArg = ArgumentCaptor.forClass(IconState.class);
 
-        Mockito.verify(mSignalCluster, Mockito.atLeastOnce()).setWifiIndicators(
-                visibleArg.capture(), iconArg.capture(),
-                ArgumentCaptor.forClass(String.class).capture());
-        assertEquals("WiFi visible, in status bar", visible, (boolean) visibleArg.getValue());
-        assertEquals("WiFi signal, in status bar", icon, (int) iconArg.getValue());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setWifiIndicators(
+                Mockito.anyBoolean(), iconArg.capture(), Mockito.any(IconState.class),
+                Mockito.anyBoolean(), Mockito.anyBoolean(), Mockito.anyString());
+        IconState iconState = iconArg.getValue();
+        assertEquals("WiFi visible, in status bar", visible, iconState.visible);
+        assertEquals("WiFi signal, in status bar", icon, iconState.icon);
     }
 }
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index c32de41..e00cf5b 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -16,8 +16,10 @@
 
 package com.android.server;
 
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -37,6 +39,7 @@
 import android.mtp.MtpStorage;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.DropBoxManager;
 import android.os.Environment;
 import android.os.Environment.UserEnvironment;
 import android.os.FileUtils;
@@ -174,6 +177,7 @@
     private static final boolean WATCHDOG_ENABLE = false;
 
     private static final String TAG = "MountService";
+    private static final String TAG_STORAGE_BENCHMARK = "storage_benchmark";
 
     private static final String VOLD_TAG = "VoldConnector";
 
@@ -233,6 +237,7 @@
         public static final int VOLUME_DESTROYED = 659;
 
         public static final int MOVE_STATUS = 660;
+        public static final int BENCHMARK_RESULT = 661;
 
         /*
          * 700 series - fstrim
@@ -247,6 +252,7 @@
     private static final String TAG_VOLUMES = "volumes";
     private static final String ATTR_VERSION = "version";
     private static final String ATTR_PRIMARY_STORAGE_UUID = "primaryStorageUuid";
+    private static final String ATTR_FORCE_ADOPTABLE = "forceAdoptable";
     private static final String TAG_VOLUME = "volume";
     private static final String ATTR_TYPE = "type";
     private static final String ATTR_FS_UUID = "fsUuid";
@@ -276,6 +282,8 @@
     private ArrayMap<String, VolumeRecord> mRecords = new ArrayMap<>();
     @GuardedBy("mLock")
     private String mPrimaryStorageUuid;
+    @GuardedBy("mLock")
+    private boolean mForceAdoptable;
 
     /** Map from disk ID to latches */
     @GuardedBy("mLock")
@@ -810,7 +818,8 @@
                 if (cooked.length != 3) break;
                 final String id = cooked[1];
                 int flags = Integer.parseInt(cooked[2]);
-                if (SystemProperties.getBoolean(StorageManager.PROP_FORCE_ADOPTABLE, false)) {
+                if (SystemProperties.getBoolean(StorageManager.PROP_FORCE_ADOPTABLE, false)
+                        || mForceAdoptable) {
                     flags |= DiskInfo.FLAG_ADOPTABLE;
                 }
                 mDisks.put(id, new DiskInfo(id, flags));
@@ -927,6 +936,12 @@
                 break;
             }
 
+            case VoldResponseCode.BENCHMARK_RESULT: {
+                final DropBoxManager dropBox = mContext.getSystemService(DropBoxManager.class);
+                dropBox.addText(TAG_STORAGE_BENCHMARK, raw);
+                break;
+            }
+
             case VoldResponseCode.FstrimCompleted: {
                 EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime());
                 break;
@@ -1199,6 +1214,7 @@
     private void readSettingsLocked() {
         mRecords.clear();
         mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
+        mForceAdoptable = false;
 
         FileInputStream fis = null;
         try {
@@ -1220,6 +1236,7 @@
                             mPrimaryStorageUuid = readStringAttribute(in,
                                     ATTR_PRIMARY_STORAGE_UUID);
                         }
+                        mForceAdoptable = readBooleanAttribute(in, ATTR_FORCE_ADOPTABLE, false);
 
                     } else if (TAG_VOLUME.equals(tag)) {
                         final VolumeRecord rec = readVolumeRecord(in);
@@ -1249,6 +1266,7 @@
             out.startTag(null, TAG_VOLUMES);
             writeIntAttribute(out, ATTR_VERSION, VERSION_FIX_PRIMARY);
             writeStringAttribute(out, ATTR_PRIMARY_STORAGE_UUID, mPrimaryStorageUuid);
+            writeBooleanAttribute(out, ATTR_FORCE_ADOPTABLE, mForceAdoptable);
             final int size = mRecords.size();
             for (int i = 0; i < size; i++) {
                 final VolumeRecord rec = mRecords.valueAt(i);
@@ -1406,6 +1424,19 @@
     }
 
     @Override
+    public long benchmark(String volId) {
+        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+        waitForReady();
+
+        try {
+            final NativeDaemonEvent res = mConnector.execute("volume", "benchmark", volId);
+            return Long.parseLong(res.getMessage());
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
     public void partitionPublic(String diskId) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
         waitForReady();
@@ -1520,6 +1551,21 @@
     }
 
     @Override
+    public void setDebugFlags(int flags, int mask) {
+        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        waitForReady();
+
+        synchronized (mLock) {
+            if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
+                mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
+            }
+
+            writeSettingsLocked();
+            resetIfReadyAndConnected();
+        }
+    }
+
+    @Override
     public String getPrimaryStorageUuid() {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
@@ -3014,6 +3060,7 @@
 
             pw.println();
             pw.println("Primary storage UUID: " + mPrimaryStorageUuid);
+            pw.println("Force adoptable: " + mForceAdoptable);
         }
 
         synchronized (mObbMounts) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2158395..a8ab667 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2441,7 +2441,6 @@
                 }
             }
 
-            long[] cpuSpeedTimes = mProcessCpuTracker.getLastCpuSpeedTimes();
             final BatteryStatsImpl bstats = mBatteryStatsService.getActiveStatistics();
             synchronized(bstats) {
                 synchronized(mPidsSelfLocked) {
@@ -2472,7 +2471,7 @@
                                                 pr.info.uid, pr.processName);
                                     }
                                     ps.addCpuTimeLocked(st.rel_utime - otherUTime,
-                                            st.rel_stime - otherSTime, cpuSpeedTimes);
+                                            st.rel_stime - otherSTime);
                                     pr.curCpuTime += st.rel_utime + st.rel_stime;
                                 } else {
                                     BatteryStatsImpl.Uid.Proc ps = st.batteryStats;
@@ -2481,7 +2480,7 @@
                                                 bstats.mapUid(st.uid), st.name);
                                     }
                                     ps.addCpuTimeLocked(st.rel_utime - otherUTime,
-                                            st.rel_stime - otherSTime, cpuSpeedTimes);
+                                            st.rel_stime - otherSTime);
                                 }
                             }
                             final int userTime = mProcessCpuTracker.getLastUserTime();
@@ -2492,7 +2491,7 @@
                             final int idleTime = mProcessCpuTracker.getLastIdleTime();
                             bstats.finishAddingCpuLocked(perc, remainUTime,
                                     remainSTime, totalUTime, totalSTime, userTime, systemTime,
-                                    iowaitTime, irqTime, softIrqTime, idleTime, cpuSpeedTimes);
+                                    iowaitTime, irqTime, softIrqTime, idleTime);
                         }
                     }
                 }
@@ -5299,7 +5298,7 @@
     private final boolean killPackageProcessesLocked(String packageName, int appId,
             int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
             boolean doit, boolean evenPersistent, String reason) {
-        ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
+        ArrayList<ProcessRecord> procs = new ArrayList<>();
 
         // Remove all processes this package may have touched: all with the
         // same UID (except for the system or root user), and all whose name
@@ -5446,6 +5445,13 @@
         for (int i = providers.size() - 1; i >= 0; i--) {
             removeDyingProviderLocked(null, providers.get(i), true);
         }
+
+        // Clean-up disabled broadcast receivers.
+        for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
+            mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
+                    packageName, disabledClasses, userId, true);
+        }
+
     }
 
     private final boolean forceStopPackageLocked(String packageName, int appId,
@@ -5542,6 +5548,13 @@
         // Remove transient permissions granted from/to this package/user
         removeUriPermissionsForPackageLocked(packageName, userId, false);
 
+        if (doit) {
+            for (i = mBroadcastQueues.length - 1; i >= 0; i--) {
+                didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
+                        packageName, null, userId, doit);
+            }
+        }
+
         if (packageName == null || uninstalling) {
             // Remove pending intents.  For now we only do this when force
             // stopping users, because we have some problems when doing this
@@ -15089,7 +15102,7 @@
             ContentProviderRecord cpr = app.pubProviders.valueAt(i);
             final boolean always = app.bad || !allowRestart;
             boolean inLaunching = removeDyingProviderLocked(app, cpr, always);
-            if ((inLaunching || always) && !cpr.connections.isEmpty()) {
+            if ((inLaunching || always) && cpr.hasConnectionOrHandle()) {
                 // We left the provider in the launching list, need to
                 // restart it.
                 restart = true;
@@ -15233,7 +15246,7 @@
         for (int i = mLaunchingProviders.size() - 1; i >= 0; i--) {
             ContentProviderRecord cpr = mLaunchingProviders.get(i);
             if (cpr.launchingApp == app) {
-                if (!alwaysBad && !app.bad && !cpr.connections.isEmpty()) {
+                if (!alwaysBad && !app.bad && cpr.hasConnectionOrHandle()) {
                     restart = true;
                 } else {
                     removeDyingProviderLocked(app, cpr, true);
@@ -16130,10 +16143,7 @@
                             final int uid = intentExtras != null
                                     ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
                             if (uid >= 0) {
-                                BatteryStatsImpl bs = mBatteryStatsService.getActiveStatistics();
-                                synchronized (bs) {
-                                    bs.removeUidStatsLocked(uid);
-                                }
+                                mBatteryStatsService.removeUid(uid);
                                 mAppOpsService.uidRemoved(uid);
                             }
                             break;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 58665d7..a61223a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -64,6 +64,7 @@
         implements PowerManagerInternal.LowPowerModeListener {
     static final String TAG = "BatteryStatsService";
 
+    private boolean mFirstExternalStatsUpdate = true;
     static IBatteryStats sService;
     final BatteryStatsImpl mStats;
     final BatteryStatsHandler mHandler;
@@ -177,6 +178,15 @@
 
     // These are for direct use by the activity manager...
 
+    /**
+     * Remove a UID from the BatteryStats and BatteryStats' external dependencies.
+     */
+    void removeUid(int uid) {
+        synchronized (mStats) {
+            mStats.removeUidStatsLocked(uid);
+        }
+    }
+
     void addIsolatedUid(int isolatedUid, int appUid) {
         synchronized (mStats) {
             mStats.addIsolatedUidLocked(isolatedUid, appUid);
@@ -1150,11 +1160,18 @@
                     mStats.addHistoryEventLocked(elapsedRealtime, uptime,
                             BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS, reason, 0);
                 }
+                mStats.updateCpuTimeLocked(mFirstExternalStatsUpdate);
                 mStats.updateKernelWakelocksLocked();
                 mStats.updateMobileRadioStateLocked(SystemClock.elapsedRealtime());
                 mStats.updateWifiStateLocked(wifiEnergyInfo);
                 mStats.updateBluetoothStateLocked(bluetoothEnergyInfo);
             }
+
+            if (mFirstExternalStatsUpdate) {
+                // We have read the stats for the first time, which means we have a baseline
+                // from which to calculate delta.
+                mFirstExternalStatsUpdate = false;
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e89ef57..745cb7e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -21,6 +21,7 @@
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.Set;
 
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -209,7 +210,7 @@
     }
 
     public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) {
-        for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {
+        for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
             if (r.intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
                 if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
                         "***** DROPPING PARALLEL ["
@@ -222,7 +223,7 @@
     }
 
     public final boolean replaceOrderedBroadcastLocked(BroadcastRecord r) {
-        for (int i=mOrderedBroadcasts.size()-1; i>0; i--) {
+        for (int i = mOrderedBroadcasts.size() - 1; i > 0; i--) {
             if (r.intent.filterEquals(mOrderedBroadcasts.get(i).intent)) {
                 if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
                         "***** DROPPING ORDERED ["
@@ -1097,6 +1098,28 @@
         mSummaryHistoryNext = ringAdvance(mSummaryHistoryNext, 1, MAX_BROADCAST_SUMMARY_HISTORY);
     }
 
+    boolean cleanupDisabledPackageReceiversLocked(
+            String packageName, Set<String> filterByClasses, int userId, boolean doit) {
+        boolean didSomething = false;
+        for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
+            didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
+                    packageName, filterByClasses, userId, doit);
+            if (!doit && didSomething) {
+                return true;
+            }
+        }
+
+        for (int i = mOrderedBroadcasts.size() - 1; i >= 0; i--) {
+            didSomething |= mOrderedBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
+                    packageName, filterByClasses, userId, doit);
+            if (!doit && didSomething) {
+                return true;
+            }
+        }
+
+        return didSomething;
+    }
+
     final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
         if (r.nextReceiver > 0) {
             Object curReceiver = r.receivers.get(r.nextReceiver-1);
@@ -1130,7 +1153,7 @@
         if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0
                 || mPendingBroadcast != null) {
             boolean printed = false;
-            for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {
+            for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
                 BroadcastRecord br = mParallelBroadcasts.get(i);
                 if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
                     continue;
@@ -1148,7 +1171,7 @@
             }
             printed = false;
             needSep = true;
-            for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) {
+            for (int i = mOrderedBroadcasts.size() - 1; i >= 0; i--) {
                 BroadcastRecord br = mOrderedBroadcasts.get(i);
                 if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
                     continue;
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 9a4d7a0..c050d03f 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -26,12 +26,14 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.PrintWriterPrinter;
 import android.util.TimeUtils;
 
 import java.io.PrintWriter;
 import java.util.Date;
 import java.util.List;
+import java.util.Set;
 
 /**
  * An active intent broadcast.
@@ -164,7 +166,7 @@
         final int N = receivers != null ? receivers.size() : 0;
         String p2 = prefix + "  ";
         PrintWriterPrinter printer = new PrintWriterPrinter(pw);
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             Object o = receivers.get(i);
             pw.print(prefix); pw.print("Receiver #"); pw.print(i);
                     pw.print(": "); pw.println(o);
@@ -205,6 +207,36 @@
         state = IDLE;
     }
 
+    boolean cleanupDisabledPackageReceiversLocked(
+            String packageName, Set<String> filterByClasses, int userId, boolean doit) {
+        if ((userId != UserHandle.USER_ALL && this.userId != userId) || receivers == null) {
+            return false;
+        }
+
+        boolean didSomething = false;
+        Object o;
+        for (int i = receivers.size() - 1; i >= 0; i--) {
+            o = receivers.get(i);
+            if (!(o instanceof ResolveInfo)) {
+                continue;
+            }
+            ActivityInfo info = ((ResolveInfo)o).activityInfo;
+
+            final boolean sameComponent = packageName == null
+                    || (info.applicationInfo.packageName.equals(packageName)
+                    && (filterByClasses == null || filterByClasses.contains(info.name)));
+            if (sameComponent) {
+                if (!doit) {
+                    return true;
+                }
+                didSomething = true;
+                receivers.remove(i);
+            }
+        }
+
+        return didSomething;
+    }
+
     public String toString() {
         return "BroadcastRecord{"
             + Integer.toHexString(System.identityHashCode(this))
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index a37249d..dceadf4 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -139,6 +139,10 @@
         return (externalProcessTokenToHandle != null || externalProcessNoHandleCount > 0);
     }
 
+    public boolean hasConnectionOrHandle() {
+        return !connections.isEmpty() || hasExternalProcessHandles();
+    }
+
     void dump(PrintWriter pw, String prefix, boolean full) {
         if (full) {
             pw.print(prefix); pw.print("package=");
diff --git a/services/core/java/com/android/server/am/ProviderMap.java b/services/core/java/com/android/server/am/ProviderMap.java
index a1dc3e3..ed8b1dd 100644
--- a/services/core/java/com/android/server/am/ProviderMap.java
+++ b/services/core/java/com/android/server/am/ProviderMap.java
@@ -210,8 +210,11 @@
     boolean collectPackageProvidersLocked(String packageName, Set<String> filterByClasses,
             boolean doit, boolean evenPersistent, int userId,
             ArrayList<ContentProviderRecord> result) {
-        boolean didSomething = collectPackageProvidersLocked(packageName, filterByClasses,
-                doit, evenPersistent, mSingletonByClass, result);
+        boolean didSomething = false;
+        if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_OWNER) {
+            didSomething = collectPackageProvidersLocked(packageName, filterByClasses,
+                    doit, evenPersistent, mSingletonByClass, result);
+        }
         if (!doit && didSomething) {
             return true;
         }
diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java
index 1fb22be..259ff1d 100644
--- a/services/core/java/com/android/server/location/FlpHardwareProvider.java
+++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java
@@ -48,7 +48,7 @@
     // Capabilities provided by FlpCallbacks
     private boolean mHaveBatchingCapabilities;
     private int mBatchingCapabilities;
-    private int mVersion;
+    private int mVersion = 1;
 
     private static FlpHardwareProvider sSingletonInstance = null;
 
@@ -154,7 +154,9 @@
 
     private void setVersion(int version) {
         mVersion = version;
-        getGeofenceHardwareSink().setVersion(version);
+        if (mGeofenceHardwareSink != null) {
+            mGeofenceHardwareSink.setVersion(version);
+        }
     }
 
     private void maybeSendCapabilities() {
@@ -480,6 +482,7 @@
     private GeofenceHardwareImpl getGeofenceHardwareSink() {
         if (mGeofenceHardwareSink == null) {
             mGeofenceHardwareSink = GeofenceHardwareImpl.getInstance(mContext);
+            mGeofenceHardwareSink.setVersion(mVersion);
         }
 
         return mGeofenceHardwareSink;
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index b505f7e..7024ec8 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -17,9 +17,12 @@
 package com.android.server.pm;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageParser;
+import android.os.PowerManager;
 import android.os.UserHandle;
+import android.os.WorkSource;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
@@ -50,8 +53,14 @@
     private final PackageManagerService mPackageManagerService;
     private ArraySet<PackageParser.Package> mDeferredDexOpt;
 
+    private final PowerManager.WakeLock mDexoptWakeLock;
+    private volatile boolean mSystemReady;
+
     PackageDexOptimizer(PackageManagerService packageManagerService) {
         this.mPackageManagerService = packageManagerService;
+        PowerManager powerManager = (PowerManager)packageManagerService.mContext.getSystemService(
+                Context.POWER_SERVICE);
+        mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*dexopt*");
     }
 
     /**
@@ -71,7 +80,18 @@
             done = null;
         }
         synchronized (mPackageManagerService.mInstallLock) {
-            return performDexOptLI(pkg, instructionSets, forceDex, defer, done);
+            final boolean useLock = mSystemReady;
+            if (useLock) {
+                mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
+                mDexoptWakeLock.acquire();
+            }
+            try {
+                return performDexOptLI(pkg, instructionSets, forceDex, defer, done);
+            } finally {
+                if (useLock) {
+                    mDexoptWakeLock.release();
+                }
+            }
         }
     }
 
@@ -242,4 +262,8 @@
         }
         mDeferredDexOpt.add(pkg);
     }
+
+    void systemReady() {
+        mSystemReady = true;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8e0cdf1..509289b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -13486,6 +13486,7 @@
         storage.registerListener(mStorageListener);
 
         mInstallerService.systemReady();
+        mPackageDexOptimizer.systemReady();
     }
 
     @Override
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 025ede5..91639c5 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -189,16 +189,16 @@
 }
 
 struct CompileItem {
-    Source source;
     ResourceName name;
     ConfigDescription config;
+    Source source;
     std::string extension;
 };
 
 struct LinkItem {
-    Source source;
     ResourceName name;
     ConfigDescription config;
+    Source source;
     std::string originalPath;
     ZipFile* apk;
     std::u16string originalPackage;
@@ -236,7 +236,8 @@
     return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
 }
 
-bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
+template <typename T>
+bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) {
     StringPool& pool = table->getValueStringPool();
     StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
                                        StringPool::Context{ 0, item.config });
@@ -334,9 +335,40 @@
     return true;
 }
 
-bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
-             const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk,
-             std::queue<LinkItem>* outQueue) {
+/**
+ * Determines if a layout should be auto generated based on SDK level. We do not
+ * generate a layout if there is already a layout defined whose SDK version is greater than
+ * the one we want to generate.
+ */
+bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table,
+                                     const ResourceName& name, const ConfigDescription& config,
+                                     int sdkVersionToGenerate) {
+    assert(sdkVersionToGenerate > config.sdkVersion);
+    const ResourceTableType* type;
+    const ResourceEntry* entry;
+    std::tie(type, entry) = table->findResource(name);
+    assert(type && entry);
+
+    auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
+            [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool {
+        return lhs.config < config;
+    });
+
+    assert(iter != entry->values.end());
+    ++iter;
+
+    if (iter == entry->values.end()) {
+        return true;
+    }
+
+    ConfigDescription newConfig = config;
+    newConfig.sdkVersion = sdkVersionToGenerate;
+    return newConfig < iter->config;
+}
+
+bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+             const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
+             const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue) {
     std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
     if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
         return false;
@@ -376,9 +408,21 @@
     if (minStrippedSdk.value() > 0) {
         // Something was stripped, so let's generate a new file
         // with the version of the smallest SDK version stripped.
-        LinkItem newWork = item;
-        newWork.config.sdkVersion = minStrippedSdk.value();
-        outQueue->push(newWork);
+        // We can only generate a versioned layout if there doesn't exist a layout
+        // with sdk version greater than the current one but less than the one we
+        // want to generate.
+        if (shouldGenerateVersionedResource(table, item.name, item.config,
+                    minStrippedSdk.value())) {
+            LinkItem newWork = item;
+            newWork.config.sdkVersion = minStrippedSdk.value();
+            outQueue->push(newWork);
+
+            if (!addFileReference(table, newWork)) {
+                Logger::error(options.output) << "failed to add auto-versioned resource '"
+                                              << newWork.name << "'." << std::endl;
+                return false;
+            }
+        }
     }
 
     if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
@@ -604,7 +648,7 @@
                     newSource.path += "/";
                     newSource.path += pathUtf8;
                     outLinkQueue->push(LinkItem{
-                            newSource, name, value.config, pathUtf8, apk.get(),
+                            name, value.config, newSource, pathUtf8, apk.get(),
                             table->getPackage() });
                     // Now rewrite the file path.
                     if (mangle) {
@@ -743,8 +787,8 @@
             void* uncompressedData = item.apk->uncompress(entry);
             assert(uncompressedData);
 
-            if (!linkXml(options, resolver, item, uncompressedData, entry->getUncompressedLen(),
-                    &outApk, &linkQueue)) {
+            if (!linkXml(options, outTable, resolver, item, uncompressedData,
+                        entry->getUncompressedLen(), &outApk, &linkQueue)) {
                 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
                                               << std::endl;
                 return false;
@@ -862,9 +906,9 @@
             }
 
             compileQueue.push(CompileItem{
-                    source,
                     ResourceName{ table->getPackage(), *type, pathData.name },
                     pathData.config,
+                    source,
                     pathData.extension
             });
         }
diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile
index 6b5fafa..ce5201b 100644
--- a/tools/aapt2/data/Makefile
+++ b/tools/aapt2/data/Makefile
@@ -49,7 +49,7 @@
 # returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
 define make-collect-rule
 $(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
-	$(AAPT) compile --package $(LOCAL_PACKAGE) --binding $(LOCAL_GEN) -o $$@ $$^
+	$(AAPT) compile --package $(LOCAL_PACKAGE) -o $$@ $$^
 endef
 
 # Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml