Merge "Fixed a bug where parts weren't scrollable" into oc-mr1-dev
diff --git a/api/test-current.txt b/api/test-current.txt
index 62f8885..9487740 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -12093,6 +12093,18 @@
     ctor public SQLiteFullException(java.lang.String);
   }
 
+  public final class SQLiteGlobal {
+    method public static java.lang.String getDefaultJournalMode();
+    method public static int getDefaultPageSize();
+    method public static java.lang.String getDefaultSyncMode();
+    method public static int getIdleConnectionTimeout();
+    method public static int getJournalSizeLimit();
+    method public static int getWALAutoCheckpoint();
+    method public static int getWALConnectionPoolSize();
+    method public static java.lang.String getWALSyncMode();
+    method public static int releaseMemory();
+  }
+
   public class SQLiteMisuseException extends android.database.sqlite.SQLiteException {
     ctor public SQLiteMisuseException();
     ctor public SQLiteMisuseException(java.lang.String);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index cec5db9..a558d68 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -26,7 +26,6 @@
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Region;
-import android.hardware.fingerprint.FingerprintManager;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -52,8 +51,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
-import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
-
 /**
  * Accessibility services should only be used to assist users with disabilities in using
  * Android devices and apps. They run in the background and receive callbacks by the system
@@ -394,7 +391,7 @@
     public static final int SHOW_MODE_AUTO = 0;
     public static final int SHOW_MODE_HIDDEN = 1;
 
-    private int mConnectionId;
+    private int mConnectionId = AccessibilityInteractionClient.NO_ID;
 
     private AccessibilityServiceInfo mInfo;
 
@@ -1612,7 +1609,7 @@
 
         private final Callbacks mCallback;
 
-        private int mConnectionId;
+        private int mConnectionId = AccessibilityInteractionClient.NO_ID;
 
         public IAccessibilityServiceClientWrapper(Context context, Looper looper,
                 Callbacks callback) {
@@ -1707,7 +1704,8 @@
                     if (event != null) {
                         // Send the event to AccessibilityCache via AccessibilityInteractionClient
                         AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
-                        if (serviceWantsEvent) {
+                        if (serviceWantsEvent
+                                && (mConnectionId != AccessibilityInteractionClient.NO_ID)) {
                             // Send the event to AccessibilityService
                             mCallback.onAccessibilityEvent(event);
                         }
@@ -1721,7 +1719,9 @@
                 } return;
 
                 case DO_ON_INTERRUPT: {
-                    mCallback.onInterrupt();
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.onInterrupt();
+                    }
                 } return;
 
                 case DO_INIT: {
@@ -1746,8 +1746,10 @@
                 } return;
 
                 case DO_ON_GESTURE: {
-                    final int gestureId = message.arg1;
-                    mCallback.onGesture(gestureId);
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final int gestureId = message.arg1;
+                        mCallback.onGesture(gestureId);
+                    }
                 } return;
 
                 case DO_CLEAR_ACCESSIBILITY_CACHE: {
@@ -1779,37 +1781,51 @@
                 } return;
 
                 case DO_ON_MAGNIFICATION_CHANGED: {
-                    final SomeArgs args = (SomeArgs) message.obj;
-                    final Region region = (Region) args.arg1;
-                    final float scale = (float) args.arg2;
-                    final float centerX = (float) args.arg3;
-                    final float centerY = (float) args.arg4;
-                    mCallback.onMagnificationChanged(region, scale, centerX, centerY);
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final Region region = (Region) args.arg1;
+                        final float scale = (float) args.arg2;
+                        final float centerX = (float) args.arg3;
+                        final float centerY = (float) args.arg4;
+                        mCallback.onMagnificationChanged(region, scale, centerX, centerY);
+                    }
                 } return;
 
                 case DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED: {
-                    final int showMode = (int) message.arg1;
-                    mCallback.onSoftKeyboardShowModeChanged(showMode);
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final int showMode = (int) message.arg1;
+                        mCallback.onSoftKeyboardShowModeChanged(showMode);
+                    }
                 } return;
 
                 case DO_GESTURE_COMPLETE: {
-                    final boolean successfully = message.arg2 == 1;
-                    mCallback.onPerformGestureResult(message.arg1, successfully);
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final boolean successfully = message.arg2 == 1;
+                        mCallback.onPerformGestureResult(message.arg1, successfully);
+                    }
                 } return;
                 case DO_ON_FINGERPRINT_ACTIVE_CHANGED: {
-                    mCallback.onFingerprintCapturingGesturesChanged(message.arg1 == 1);
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.onFingerprintCapturingGesturesChanged(message.arg1 == 1);
+                    }
                 } return;
                 case DO_ON_FINGERPRINT_GESTURE: {
-                    mCallback.onFingerprintGesture(message.arg1);
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.onFingerprintGesture(message.arg1);
+                    }
                 } return;
 
                 case (DO_ACCESSIBILITY_BUTTON_CLICKED): {
-                    mCallback.onAccessibilityButtonClicked();
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.onAccessibilityButtonClicked();
+                    }
                 } return;
 
                 case (DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED): {
-                    final boolean available = (message.arg1 != 0);
-                    mCallback.onAccessibilityButtonAvailabilityChanged(available);
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final boolean available = (message.arg1 != 0);
+                        mCallback.onAccessibilityButtonAvailabilityChanged(available);
+                    }
                 } return;
 
                 default :
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index 7d4d70d..9b2bfc5 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -1006,9 +1006,12 @@
         final int numElements = mTransitioningViews == null ? 0 : mTransitioningViews.size();
         for (int i = 0; i < numElements; i++) {
             final View view = mTransitioningViews.get(i);
-            view.setTransitionVisibility(visiblity);
             if (invalidate) {
-                view.invalidate();
+                // Allow the view to be invalidated by the visibility change
+                view.setVisibility(visiblity);
+            } else {
+                // Don't invalidate the view with the visibility change
+                view.setTransitionVisibility(visiblity);
             }
         }
     }
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index 9dfc4b4..4ebef4f 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -125,7 +125,7 @@
 
     private Context mContext;
     private ServiceListener mServiceListener;
-    private IBluetoothA2dpSink mService;
+    private volatile IBluetoothA2dpSink mService;
     private BluetoothAdapter mAdapter;
 
     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
@@ -239,16 +239,16 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothA2dpSink service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.connect(device);
+                return service.connect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -280,16 +280,16 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothA2dpSink service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.disconnect(device);
+                return service.disconnect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -298,15 +298,16 @@
      */
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothA2dpSink service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -315,15 +316,16 @@
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothA2dpSink service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -332,16 +334,16 @@
      */
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        if (mService != null && isEnabled()
-            && isValidDevice(device)) {
+        final IBluetoothA2dpSink service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
@@ -358,16 +360,16 @@
      */
           public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
         if (VDBG) log("getAudioConfig(" + device + ")");
-        if (mService != null && isEnabled()
-            && isValidDevice(device)) {
+        final IBluetoothA2dpSink service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getAudioConfig(device);
+                return service.getAudioConfig(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return null;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return null;
     }
 
@@ -388,21 +390,21 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        if (mService != null && isEnabled()
-            && isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF &&
-                priority != BluetoothProfile.PRIORITY_ON){
+        final IBluetoothA2dpSink service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON) {
                 return false;
             }
             try {
-                return mService.setPriority(device, priority);
+                return service.setPriority(device, priority);
             } catch (RemoteException e) {
                    Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                    return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
     }
 
     /**
@@ -420,16 +422,16 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        if (mService != null && isEnabled()
-            && isValidDevice(device)) {
+        final IBluetoothA2dpSink service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getPriority(device);
+                return service.getPriority(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.PRIORITY_OFF;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.PRIORITY_OFF;
     }
 
@@ -441,16 +443,16 @@
      * @param device BluetoothDevice device
      */
     public boolean isA2dpPlaying(BluetoothDevice device) {
-        if (mService != null && isEnabled()
-            && isValidDevice(device)) {
+        final IBluetoothA2dpSink service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.isA2dpPlaying(device);
+                return service.isA2dpPlaying(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -483,7 +485,6 @@
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
             mService = IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service));
-
             if (mServiceListener != null) {
                 mServiceListener.onServiceConnected(BluetoothProfile.A2DP_SINK,
                         BluetoothA2dpSink.this);
@@ -499,15 +500,11 @@
     };
 
     private boolean isEnabled() {
-       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
-       return false;
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
     }
 
-    private boolean isValidDevice(BluetoothDevice device) {
-       if (device == null) return false;
-
-       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
-       return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
index 0261b1b..a04f110 100644
--- a/core/java/android/bluetooth/BluetoothAvrcpController.java
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -20,8 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.media.MediaMetadata;
-import android.media.session.PlaybackState;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -83,7 +81,7 @@
 
     private Context mContext;
     private ServiceListener mServiceListener;
-    private IBluetoothAvrcpController mService;
+    private volatile IBluetoothAvrcpController mService;
     private BluetoothAdapter mAdapter;
 
     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
@@ -180,15 +178,16 @@
      */
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothAvrcpController service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -197,15 +196,16 @@
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothAvrcpController service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -214,16 +214,16 @@
      */
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        if (mService != null && isEnabled()
-            && isValidDevice(device)) {
+        final IBluetoothAvrcpController service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
@@ -235,9 +235,10 @@
     public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getPlayerSettings");
         BluetoothAvrcpPlayerSettings settings = null;
-        if (mService != null && isEnabled()) {
+        final IBluetoothAvrcpController service = mService;
+        if (service != null && isEnabled()) {
             try {
-                settings = mService.getPlayerSettings(device);
+                settings = service.getPlayerSettings(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
                 return null;
@@ -252,15 +253,16 @@
      */
     public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
         if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
-        if (mService != null && isEnabled()) {
+        final IBluetoothAvrcpController service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.setPlayerApplicationSetting(plAppSetting);
+                return service.setPlayerApplicationSetting(plAppSetting);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -269,24 +271,25 @@
      * possible keycode values: next_grp, previous_grp defined above
      */
     public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
-        Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " + keyState);
-        if (mService != null && isEnabled()) {
+        Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = "
+                + keyState);
+        final IBluetoothAvrcpController service = mService;
+        if (service != null && isEnabled()) {
             try {
-                mService.sendGroupNavigationCmd(device, keyCode, keyState);
+                service.sendGroupNavigationCmd(device, keyCode, keyState);
                 return;
             } catch (RemoteException e) {
                 Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
                 return;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
     }
 
     private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
             mService = IBluetoothAvrcpController.Stub.asInterface(Binder.allowBlocking(service));
-
             if (mServiceListener != null) {
                 mServiceListener.onServiceConnected(BluetoothProfile.AVRCP_CONTROLLER,
                         BluetoothAvrcpController.this);
@@ -302,15 +305,11 @@
     };
 
     private boolean isEnabled() {
-       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
-       return false;
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
     }
 
-    private boolean isValidDevice(BluetoothDevice device) {
-       if (device == null) return false;
-
-       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
-       return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index a206b53..98cd319 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -23,10 +23,9 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Parcel;
-import android.os.Parcelable;
 import android.os.ParcelUuid;
+import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
@@ -683,7 +682,7 @@
      * getService() called.
      * TODO: Unify implementation of sService amongst BluetoothFoo API's
      */
-    private static IBluetooth sService;
+    private static volatile IBluetooth sService;
 
     private final String mAddress;
 
@@ -803,13 +802,16 @@
      */
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     public String getName() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot get Remote Device name");
             return null;
         }
         try {
-            return sService.getRemoteName(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.getRemoteName(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return null;
     }
 
@@ -822,13 +824,16 @@
      */
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     public int getType() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot get Remote Device type");
             return DEVICE_TYPE_UNKNOWN;
         }
         try {
-            return sService.getRemoteType(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.getRemoteType(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return DEVICE_TYPE_UNKNOWN;
     }
 
@@ -840,13 +845,16 @@
      * @hide
      */
     public String getAlias() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot get Remote Device Alias");
             return null;
         }
         try {
-            return sService.getRemoteAlias(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.getRemoteAlias(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return null;
     }
 
@@ -861,13 +869,16 @@
      * @hide
      */
     public boolean setAlias(String alias) {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot set Remote Device name");
             return false;
         }
         try {
-            return sService.setRemoteAlias(this, alias);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.setRemoteAlias(this, alias);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return false;
     }
 
@@ -899,13 +910,16 @@
      */
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     public int getBatteryLevel() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "Bluetooth disabled. Cannot get remote device battery level");
             return BATTERY_LEVEL_UNKNOWN;
         }
         try {
-            return sService.getBatteryLevel(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.getBatteryLevel(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return BATTERY_LEVEL_UNKNOWN;
     }
 
@@ -921,16 +935,19 @@
      */
     @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
     public boolean createBond() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot create bond to Remote Device");
             return false;
         }
         try {
-            Log.i(TAG, "createBond() for device " + getAddress() +
-                    " called by pid: " + Process.myPid() +
-                    " tid: " + Process.myTid());
-            return sService.createBond(this, TRANSPORT_AUTO);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            Log.i(TAG, "createBond() for device " + getAddress()
+                    + " called by pid: " + Process.myPid()
+                    + " tid: " + Process.myTid());
+            return service.createBond(this, TRANSPORT_AUTO);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return false;
     }
 
@@ -951,7 +968,8 @@
      * @hide
      */
     public boolean createBond(int transport) {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot create bond to Remote Device");
             return false;
         }
@@ -960,11 +978,13 @@
             throw new IllegalArgumentException(transport + " is not a valid Bluetooth transport");
         }
         try {
-            Log.i(TAG, "createBond() for device " + getAddress() +
-                    " called by pid: " + Process.myPid() +
-                    " tid: " + Process.myTid());
-            return sService.createBond(this, transport);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            Log.i(TAG, "createBond() for device " + getAddress()
+                    + " called by pid: " + Process.myPid()
+                    + " tid: " + Process.myTid());
+            return service.createBond(this, transport);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return false;
     }
 
@@ -988,17 +1008,31 @@
      * @hide
      */
     public boolean createBondOutOfBand(int transport, OobData oobData) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.w(TAG, "BT not enabled, createBondOutOfBand failed");
+            return false;
+        }
         try {
-            return sService.createBondOutOfBand(this, transport, oobData);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.createBondOutOfBand(this, transport, oobData);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return false;
     }
 
     /** @hide */
     public boolean isBondingInitiatedLocally() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.w(TAG, "BT not enabled, isBondingInitiatedLocally failed");
+            return false;
+        }
         try {
-            return sService.isBondingInitiatedLocally(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.isBondingInitiatedLocally(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return false;
     }
 
@@ -1032,16 +1066,19 @@
      * @hide
      */
     public boolean cancelBondProcess() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot cancel Remote Device bond");
             return false;
         }
         try {
-            Log.i(TAG, "cancelBondProcess() for device " + getAddress() +
-                    " called by pid: " + Process.myPid() +
-                    " tid: " + Process.myTid());
-            return sService.cancelBondProcess(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            Log.i(TAG, "cancelBondProcess() for device " + getAddress()
+                    + " called by pid: " + Process.myPid()
+                    + " tid: " + Process.myTid());
+            return service.cancelBondProcess(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return false;
     }
 
@@ -1056,16 +1093,19 @@
      * @hide
      */
     public boolean removeBond() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot remove Remote Device bond");
             return false;
         }
         try {
-            Log.i(TAG, "removeBond() for device " + getAddress() +
-                    " called by pid: " + Process.myPid() +
-                    " tid: " + Process.myTid());
-            return sService.removeBond(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            Log.i(TAG, "removeBond() for device " + getAddress()
+                    + " called by pid: " + Process.myPid()
+                    + " tid: " + Process.myTid());
+            return service.removeBond(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return false;
     }
 
@@ -1080,18 +1120,15 @@
      */
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     public int getBondState() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot get bond state");
             return BOND_NONE;
         }
         try {
-            return sService.getBondState(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
-        catch (NullPointerException npe) {
-            // Handle case where bluetooth service proxy
-            // is already null.
-            Log.e(TAG, "NullPointerException for getBondState() of device ("+
-                getAddress()+")", npe);
+            return service.getBondState(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
         }
         return BOND_NONE;
     }
@@ -1105,12 +1142,13 @@
      */
     @SystemApi
     public boolean isConnected() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             // BT is not enabled, we cannot be connected.
             return false;
         }
         try {
-            return sService.getConnectionState(this) != CONNECTION_STATE_DISCONNECTED;
+            return service.getConnectionState(this) != CONNECTION_STATE_DISCONNECTED;
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
             return false;
@@ -1127,12 +1165,13 @@
      */
     @SystemApi
     public boolean isEncrypted() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             // BT is not enabled, we cannot be connected.
             return false;
         }
         try {
-            return sService.getConnectionState(this) > CONNECTION_STATE_CONNECTED;
+            return service.getConnectionState(this) > CONNECTION_STATE_CONNECTED;
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
             return false;
@@ -1146,12 +1185,13 @@
      */
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     public BluetoothClass getBluetoothClass() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot get Bluetooth Class");
             return null;
         }
         try {
-            int classInt = sService.getRemoteClass(this);
+            int classInt = service.getRemoteClass(this);
             if (classInt == BluetoothClass.ERROR) return null;
             return new BluetoothClass(classInt);
         } catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -1170,75 +1210,82 @@
      *         or null on error
      */
     @RequiresPermission(Manifest.permission.BLUETOOTH)
-     public ParcelUuid[] getUuids() {
-         if (sService == null || isBluetoothEnabled() == false) {
+    public ParcelUuid[] getUuids() {
+        final IBluetooth service = sService;
+        if (service == null || !isBluetoothEnabled()) {
             Log.e(TAG, "BT not enabled. Cannot get remote device Uuids");
              return null;
          }
         try {
-            return sService.getRemoteUuids(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.getRemoteUuids(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return null;
     }
 
-     /**
-      * Perform a service discovery on the remote device to get the UUIDs supported.
-      *
-      * <p>This API is asynchronous and {@link #ACTION_UUID} intent is sent,
-      * with the UUIDs supported by the remote end. If there is an error
-      * in getting the SDP records or if the process takes a long time,
-      * {@link #ACTION_UUID} intent is sent with the UUIDs that is currently
-      * present in the cache. Clients should use the {@link #getUuids} to get UUIDs
-      * if service discovery is not to be performed.
-      *
-      * @return False if the sanity check fails, True if the process
-      *               of initiating an ACL connection to the remote device
-      *               was started.
-      */
-     @RequiresPermission(Manifest.permission.BLUETOOTH)
-     public boolean fetchUuidsWithSdp() {
-        IBluetooth service = sService;
-        if (service == null || isBluetoothEnabled() == false) {
+    /**
+     * Perform a service discovery on the remote device to get the UUIDs supported.
+     *
+     * <p>This API is asynchronous and {@link #ACTION_UUID} intent is sent,
+     * with the UUIDs supported by the remote end. If there is an error
+     * in getting the SDP records or if the process takes a long time,
+     * {@link #ACTION_UUID} intent is sent with the UUIDs that is currently
+     * present in the cache. Clients should use the {@link #getUuids} to get UUIDs
+     * if service discovery is not to be performed.
+     *
+     * @return False if the sanity check fails, True if the process of initiating an ACL connection
+     * to the remote device was started.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean fetchUuidsWithSdp() {
+        final IBluetooth service = sService;
+        if (service == null || !isBluetoothEnabled()) {
             Log.e(TAG, "BT not enabled. Cannot fetchUuidsWithSdp");
             return false;
         }
         try {
             return service.fetchRemoteUuids(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
-            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
     }
 
-     /**
-      * Perform a service discovery on the remote device to get the SDP records associated
-      * with the specified UUID.
-      *
-      * <p>This API is asynchronous and {@link #ACTION_SDP_RECORD} intent is sent,
-      * with the SDP records found on the remote end. If there is an error
-      * in getting the SDP records or if the process takes a long time,
-      * {@link #ACTION_SDP_RECORD} intent is sent with an status value in
-      * {@link #EXTRA_SDP_SEARCH_STATUS} different from 0.
-      * Detailed status error codes can be found by members of the Bluetooth package in
-      * the AbstractionLayer class.
-      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
-      * The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
-      * The object type will match one of the SdpXxxRecord types, depending on the UUID searched
-      * for.
-      *
-      * @return False if the sanity check fails, True if the process
-      *               of initiating an ACL connection to the remote device
-      *               was started.
-      */
-     /** @hide */
-     public boolean sdpSearch(ParcelUuid uuid) {
-         if (sService == null) {
-             Log.e(TAG, "BT not enabled. Cannot query remote device sdp records");
-             return false;
-         }
-         try {
-             return sService.sdpSearch(this,uuid);
-         } catch (RemoteException e) {Log.e(TAG, "", e);}
-         return false;
-     }
+    /**
+     * Perform a service discovery on the remote device to get the SDP records associated
+     * with the specified UUID.
+     *
+     * <p>This API is asynchronous and {@link #ACTION_SDP_RECORD} intent is sent,
+     * with the SDP records found on the remote end. If there is an error
+     * in getting the SDP records or if the process takes a long time,
+     * {@link #ACTION_SDP_RECORD} intent is sent with an status value in
+     * {@link #EXTRA_SDP_SEARCH_STATUS} different from 0.
+     * Detailed status error codes can be found by members of the Bluetooth package in
+     * the AbstractionLayer class.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
+     * The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
+     * The object type will match one of the SdpXxxRecord types, depending on the UUID searched
+     * for.
+     *
+     * @return False if the sanity check fails, True if the process
+     *               of initiating an ACL connection to the remote device
+     *               was started.
+     */
+    /** @hide */
+    public boolean sdpSearch(ParcelUuid uuid) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot query remote device sdp records");
+            return false;
+        }
+        try {
+            return service.sdpSearch(this, uuid);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
 
     /**
      * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
@@ -1248,13 +1295,16 @@
      *         false for error
      */
     public boolean setPin(byte[] pin) {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot set Remote Device pin");
             return false;
         }
         try {
-            return sService.setPin(this, true, pin.length, pin);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.setPin(this, true, pin.length, pin);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return false;
     }
 
@@ -1276,13 +1326,16 @@
      */
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setPairingConfirmation(boolean confirm) {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot set pairing confirmation");
             return false;
         }
         try {
-            return sService.setPairingConfirmation(this, confirm);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.setPairingConfirmation(this, confirm);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return false;
     }
 
@@ -1298,13 +1351,16 @@
 
     /** @hide */
     public boolean cancelPairingUserInput() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             Log.e(TAG, "BT not enabled. Cannot create pairing user input");
             return false;
         }
         try {
-            return sService.cancelBondProcess(this);
-        } catch (RemoteException e) {Log.e(TAG, "", e);}
+            return service.cancelBondProcess(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
         return false;
     }
 
@@ -1334,11 +1390,12 @@
      * @hide
      */
     public int getPhonebookAccessPermission() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             return ACCESS_UNKNOWN;
         }
         try {
-            return sService.getPhonebookAccessPermission(this);
+            return service.getPhonebookAccessPermission(this);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1354,11 +1411,12 @@
      * @hide
      */
     public boolean setPhonebookAccessPermission(int value) {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             return false;
         }
         try {
-            return sService.setPhonebookAccessPermission(this, value);
+            return service.setPhonebookAccessPermission(this, value);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1372,11 +1430,12 @@
      * @hide
      */
     public int getMessageAccessPermission() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             return ACCESS_UNKNOWN;
         }
         try {
-            return sService.getMessageAccessPermission(this);
+            return service.getMessageAccessPermission(this);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1392,11 +1451,12 @@
      * @hide
      */
     public boolean setMessageAccessPermission(int value) {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             return false;
         }
         try {
-            return sService.setMessageAccessPermission(this, value);
+            return service.setMessageAccessPermission(this, value);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1410,11 +1470,12 @@
      * @hide
      */
     public int getSimAccessPermission() {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             return ACCESS_UNKNOWN;
         }
         try {
-            return sService.getSimAccessPermission(this);
+            return service.getSimAccessPermission(this);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1430,11 +1491,12 @@
      * @hide
      */
     public boolean setSimAccessPermission(int value) {
-        if (sService == null) {
+        final IBluetooth service = sService;
+        if (service == null) {
             return false;
         }
         try {
-            return sService.setSimAccessPermission(this, value);
+            return service.setSimAccessPermission(this, value);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index c84643f..500cba3 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -298,7 +298,7 @@
 
     private Context mContext;
     private ServiceListener mServiceListener;
-    private IBluetoothHeadset mService;
+    private volatile IBluetoothHeadset mService;
     private BluetoothAdapter mAdapter;
 
     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
@@ -411,16 +411,16 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.connect(device);
+                return service.connect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -452,16 +452,16 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.disconnect(device);
+                return service.disconnect(device);
             } catch (RemoteException e) {
               Log.e(TAG, Log.getStackTraceString(new Throwable()));
               return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -470,15 +470,16 @@
      */
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -487,15 +488,16 @@
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -504,16 +506,16 @@
      */
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getConnectionState(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
@@ -534,20 +536,20 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF &&
-                priority != BluetoothProfile.PRIORITY_ON) {
-              return false;
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON) {
+                return false;
             }
             try {
-                return mService.setPriority(device, priority);
+                return service.setPriority(device, priority);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -566,16 +568,16 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getPriority(device);
+                return service.getPriority(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return PRIORITY_OFF;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return PRIORITY_OFF;
     }
 
@@ -602,15 +604,15 @@
      */
     public boolean startVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("startVoiceRecognition()");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.startVoiceRecognition(device);
+                return service.startVoiceRecognition(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -626,15 +628,15 @@
      */
     public boolean stopVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("stopVoiceRecognition()");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.stopVoiceRecognition(device);
+                return service.stopVoiceRecognition(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -649,15 +651,15 @@
      */
     public boolean isAudioConnected(BluetoothDevice device) {
         if (VDBG) log("isAudioConnected()");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-              return mService.isAudioConnected(device);
+                return service.isAudioConnected(device);
             } catch (RemoteException e) {
               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -677,15 +679,15 @@
      */
     public int getBatteryUsageHint(BluetoothDevice device) {
         if (VDBG) log("getBatteryUsageHint()");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getBatteryUsageHint(device);
+                return service.getBatteryUsageHint(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return -1;
     }
 
@@ -708,10 +710,13 @@
      */
     public boolean acceptIncomingConnect(BluetoothDevice device) {
         if (DBG) log("acceptIncomingConnect");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.acceptIncomingConnect(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.acceptIncomingConnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
@@ -725,10 +730,13 @@
      */
     public boolean rejectIncomingConnect(BluetoothDevice device) {
         if (DBG) log("rejectIncomingConnect");
-        if (mService != null) {
+        final IBluetoothHeadset service = mService;
+        if (service != null) {
             try {
-                return mService.rejectIncomingConnect(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.rejectIncomingConnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
@@ -744,10 +752,13 @@
      */
     public int getAudioState(BluetoothDevice device) {
         if (VDBG) log("getAudioState");
-        if (mService != null && !isDisabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && !isDisabled()) {
             try {
-                return mService.getAudioState(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.getAudioState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
@@ -768,10 +779,13 @@
      */
     public void setAudioRouteAllowed(boolean allowed) {
         if (VDBG) log("setAudioRouteAllowed");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                mService.setAudioRouteAllowed(allowed);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                service.setAudioRouteAllowed(allowed);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
@@ -786,10 +800,13 @@
      */
     public boolean getAudioRouteAllowed() {
         if (VDBG) log("getAudioRouteAllowed");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getAudioRouteAllowed();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.getAudioRouteAllowed();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
@@ -807,9 +824,10 @@
      */
     public void setForceScoAudio(boolean forced) {
         if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                mService.setForceScoAudio(forced);
+                service.setForceScoAudio(forced);
             } catch (RemoteException e) {
               Log.e(TAG, e.toString());
             }
@@ -830,14 +848,15 @@
      */
     public boolean isAudioOn() {
         if (VDBG) log("isAudioOn()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-              return mService.isAudioOn();
+                return service.isAudioOn();
             } catch (RemoteException e) {
               Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
 
     }
@@ -852,9 +871,10 @@
      * @hide
      */
     public boolean connectAudio() {
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.connectAudio();
+                return service.connectAudio();
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -875,9 +895,10 @@
      * @hide
      */
     public boolean disconnectAudio() {
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.disconnectAudio();
+                return service.disconnectAudio();
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -901,9 +922,10 @@
      */
     public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
         if (DBG) log("startScoUsingVirtualVoiceCall()");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.startScoUsingVirtualVoiceCall(device);
+                return service.startScoUsingVirtualVoiceCall(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -924,9 +946,10 @@
      */
     public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
         if (DBG) log("stopScoUsingVirtualVoiceCall()");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.stopScoUsingVirtualVoiceCall(device);
+                return service.stopScoUsingVirtualVoiceCall(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -946,10 +969,11 @@
      * @hide
      */
     public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
-                                  int type) {
-        if (mService != null && isEnabled()) {
+            int type) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                mService.phoneStateChanged(numActive, numHeld, callState, number, type);
+                service.phoneStateChanged(numActive, numHeld, callState, number, type);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -965,10 +989,11 @@
      * @hide
      */
     public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
-                             String number, int type) {
-        if (mService != null && isEnabled()) {
+            String number, int type) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                mService.clccResponse(index, direction, status, mode, mpty, number, type);
+                service.clccResponse(index, direction, status, mode, mpty, number, type);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1004,15 +1029,15 @@
         if (command == null) {
             throw new IllegalArgumentException("command is null");
         }
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.sendVendorSpecificResultCode(device, command, arg);
+                return service.sendVendorSpecificResultCode(device, command, arg);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
         }
         return false;
@@ -1027,9 +1052,10 @@
      * @hide
      */
     public boolean enableWBS() {
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.enableWBS();
+                return service.enableWBS();
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1049,9 +1075,10 @@
      * @hide
      */
     public boolean disableWBS() {
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.disableWBS();
+                return service.disableWBS();
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1078,16 +1105,17 @@
      * Send Headset the BIND response from AG to report change in the status of the
      * HF indicators to the headset
      *
-     * @param ind_id Assigned Number of the indicator (defined by SIG)
-     * @param ind_status
+     * @param indId Assigned Number of the indicator (defined by SIG)
+     * @param indStatus
      * possible values- false-Indicator is disabled, no value changes shall be sent for this indicator
      *                  true-Indicator is enabled, value changes may be sent for this indicator
      * @hide
      */
-    public void bindResponse(int ind_id, boolean ind_status) {
-        if (mService != null && isEnabled()) {
+    public void bindResponse(int indId, boolean indStatus) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
             try {
-                mService.bindResponse(ind_id, ind_status);
+                service.bindResponse(indId, indStatus);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1116,20 +1144,15 @@
     };
 
     private boolean isEnabled() {
-       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
-       return false;
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
     }
 
     private boolean isDisabled() {
-       if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
-       return false;
+        return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
     }
 
-    private boolean isValidDevice(BluetoothDevice device) {
-       if (device == null) return false;
-
-       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
-       return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index 544b3b95..73a16de 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -28,7 +28,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.UUID;
 
 /**
  * Public API to control Hands Free Profile (HFP role only).
@@ -77,8 +76,8 @@
      * Intent sent whenever audio state changes.
      *
      * <p>It includes two mandatory extras:
-     * {@link BluetoothProfile.EXTRA_STATE},
-     * {@link BluetoothProfile.EXTRA_PREVIOUS_STATE},
+     * {@link BluetoothProfile#EXTRA_STATE},
+     * {@link BluetoothProfile#EXTRA_PREVIOUS_STATE},
      * with possible values:
      * {@link #STATE_AUDIO_CONNECTING},
      * {@link #STATE_AUDIO_CONNECTED},
@@ -368,7 +367,7 @@
 
     private Context mContext;
     private ServiceListener mServiceListener;
-    private IBluetoothHeadsetClient mService;
+    private volatile IBluetoothHeadsetClient mService;
     private BluetoothAdapter mAdapter;
 
     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
@@ -480,16 +479,16 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.connect(device);
+                return service.connect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -504,16 +503,16 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.disconnect(device);
+                return service.disconnect(device);
             } catch (RemoteException e) {
               Log.e(TAG, Log.getStackTraceString(new Throwable()));
               return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -525,15 +524,16 @@
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -548,15 +548,16 @@
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -569,16 +570,16 @@
     @Override
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getConnectionState(" + device + ")");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
@@ -589,20 +590,20 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF &&
-                    priority != BluetoothProfile.PRIORITY_ON) {
-              return false;
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON) {
+                return false;
             }
             try {
-                return mService.setPriority(device, priority);
+                return service.setPriority(device, priority);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -611,16 +612,16 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getPriority(device);
+                return service.getPriority(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return PRIORITY_OFF;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return PRIORITY_OFF;
     }
 
@@ -639,15 +640,15 @@
      */
     public boolean startVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("startVoiceRecognition()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.startVoiceRecognition(device);
+                return service.startVoiceRecognition(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -666,15 +667,15 @@
      */
     public boolean stopVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("stopVoiceRecognition()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.stopVoiceRecognition(device);
+                return service.stopVoiceRecognition(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -686,15 +687,15 @@
      */
     public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
         if (DBG) log("getCurrentCalls()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getCurrentCalls(device);
+                return service.getCurrentCalls(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return null;
     }
 
@@ -707,15 +708,15 @@
      */
     public Bundle getCurrentAgEvents(BluetoothDevice device) {
         if (DBG) log("getCurrentCalls()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getCurrentAgEvents(device);
+                return service.getCurrentAgEvents(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return null;
     }
 
@@ -733,15 +734,15 @@
      */
     public boolean acceptCall(BluetoothDevice device, int flag) {
         if (DBG) log("acceptCall()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.acceptCall(device, flag);
+                return service.acceptCall(device, flag);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -756,15 +757,15 @@
      */
     public boolean holdCall(BluetoothDevice device) {
         if (DBG) log("holdCall()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.holdCall(device);
+                return service.holdCall(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -783,15 +784,15 @@
      */
     public boolean rejectCall(BluetoothDevice device) {
         if (DBG) log("rejectCall()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.rejectCall(device);
+                return service.rejectCall(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -802,7 +803,7 @@
      *
      * @param device    remote device
      * @param call      Handle of call obtained in {@link dial()} or obtained via
-     *                  {@link ACTION_CALL_CHANGED}. {@code call} may be null in which
+     *                  {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which
      *                  case we will hangup all active calls.
      * @return          <code>true</code> if command has been issued successfully;
      *                   <code>false</code> otherwise;
@@ -815,15 +816,15 @@
      */
     public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
         if (DBG) log("terminateCall()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.terminateCall(device, call);
+                return service.terminateCall(device, call);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -845,15 +846,15 @@
      */
     public boolean enterPrivateMode(BluetoothDevice device, int index) {
         if (DBG) log("enterPrivateMode()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.enterPrivateMode(device, index);
+                return service.enterPrivateMode(device, index);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -874,15 +875,15 @@
      */
     public boolean explicitCallTransfer(BluetoothDevice device) {
         if (DBG) log("explicitCallTransfer()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.explicitCallTransfer(device);
+                return service.explicitCallTransfer(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -900,15 +901,15 @@
      */
     public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
         if (DBG) log("dial()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.dial(device, number);
+                return service.dial(device, number);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return null;
     }
 
@@ -925,15 +926,15 @@
      */
     public boolean sendDTMF(BluetoothDevice device, byte code) {
         if (DBG) log("sendDTMF()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.sendDTMF(device, code);
+                return service.sendDTMF(device, code);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -952,15 +953,15 @@
      */
     public boolean getLastVoiceTagNumber(BluetoothDevice device) {
         if (DBG) log("getLastVoiceTagNumber()");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getLastVoiceTagNumber(device);
+                return service.getLastVoiceTagNumber(device);
             } catch (RemoteException e) {
                 Log.e(TAG,  Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -971,10 +972,13 @@
      */
     public int getAudioState(BluetoothDevice device) {
         if (VDBG) log("getAudioState");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getAudioState(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.getAudioState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
@@ -991,10 +995,13 @@
      */
     public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
         if (VDBG) log("setAudioRouteAllowed");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                mService.setAudioRouteAllowed(device, allowed);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                service.setAudioRouteAllowed(device, allowed);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
@@ -1009,10 +1016,13 @@
      */
     public boolean getAudioRouteAllowed(BluetoothDevice device) {
         if (VDBG) log("getAudioRouteAllowed");
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getAudioRouteAllowed(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.getAudioRouteAllowed(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
@@ -1032,9 +1042,10 @@
      *                   intent;
      */
     public boolean connectAudio(BluetoothDevice device) {
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.connectAudio(device);
+                return service.connectAudio(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1057,9 +1068,10 @@
      *                   intent;
      */
     public boolean disconnectAudio(BluetoothDevice device) {
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.disconnectAudio(device);
+                return service.disconnectAudio(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1078,9 +1090,10 @@
      *                  AG not connected
      */
     public Bundle getCurrentAgFeatures(BluetoothDevice device) {
-        if (mService != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getCurrentAgFeatures(device);
+                return service.getCurrentAgFeatures(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1092,7 +1105,7 @@
     }
 
 
-    private ServiceConnection mConnection = new ServiceConnection() {
+    private final ServiceConnection mConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
@@ -1114,15 +1127,11 @@
     };
 
     private boolean isEnabled() {
-       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
-       return false;
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
     }
 
-    private boolean isValidDevice(BluetoothDevice device) {
-       if (device == null) return false;
-
-       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
-       return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index 8d77888..07be63f 100644
--- a/core/java/android/bluetooth/BluetoothHealth.java
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -178,9 +178,10 @@
         BluetoothHealthAppConfiguration config =
                 new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
 
-        if (mService != null) {
+        final IBluetoothHealth service = mService;
+        if (service != null) {
             try {
-                result = mService.registerAppConfiguration(config, wrapper);
+                result = service.registerAppConfiguration(config, wrapper);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -202,9 +203,10 @@
      */
     public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
         boolean result = false;
-        if (mService != null && isEnabled() && config != null) {
+        final IBluetoothHealth service = mService;
+        if (service != null && isEnabled() && config != null) {
             try {
-                result = mService.unregisterAppConfiguration(config);
+                result = service.unregisterAppConfiguration(config);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -230,10 +232,10 @@
      */
     public boolean connectChannelToSource(BluetoothDevice device,
             BluetoothHealthAppConfiguration config) {
-        if (mService != null && isEnabled() && isValidDevice(device) &&
-                config != null) {
+        final IBluetoothHealth service = mService;
+        if (service != null && isEnabled() && isValidDevice(device) && config != null) {
             try {
-                return mService.connectChannelToSource(device, config);
+                return service.connectChannelToSource(device, config);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -259,10 +261,10 @@
      */
     public boolean connectChannelToSink(BluetoothDevice device,
             BluetoothHealthAppConfiguration config, int channelType) {
-        if (mService != null && isEnabled() && isValidDevice(device) &&
-                config != null) {
+        final IBluetoothHealth service = mService;
+        if (service != null && isEnabled() && isValidDevice(device) && config != null) {
             try {
-                return mService.connectChannelToSink(device, config, channelType);
+                return service.connectChannelToSink(device, config, channelType);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -288,10 +290,10 @@
      */
     public boolean disconnectChannel(BluetoothDevice device,
             BluetoothHealthAppConfiguration config, int channelId) {
-        if (mService != null && isEnabled() && isValidDevice(device) &&
-                config != null) {
+        final IBluetoothHealth service = mService;
+        if (service != null && isEnabled() && isValidDevice(device) && config != null) {
             try {
-                return mService.disconnectChannel(device, config, channelId);
+                return service.disconnectChannel(device, config, channelId);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -317,10 +319,10 @@
      */
     public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
             BluetoothHealthAppConfiguration config) {
-        if (mService != null && isEnabled() && isValidDevice(device) &&
-                config != null) {
+        final IBluetoothHealth service = mService;
+        if (service != null && isEnabled() && isValidDevice(device) && config != null) {
             try {
-                return mService.getMainChannelFd(device, config);
+                return service.getMainChannelFd(device, config);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -348,9 +350,10 @@
      */
     @Override
     public int getConnectionState(BluetoothDevice device) {
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHealth service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getHealthDeviceConnectionState(device);
+                return service.getHealthDeviceConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -376,15 +379,16 @@
      */
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
-        if (mService != null && isEnabled()) {
+        final IBluetoothHealth service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getConnectedHealthDevices();
+                return service.getConnectedHealthDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -408,15 +412,16 @@
      */
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        if (mService != null && isEnabled()) {
+        final IBluetoothHealth service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getHealthDevicesMatchingConnectionStates(states);
+                return service.getHealthDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -462,7 +467,7 @@
 
     private Context mContext;
     private ServiceListener mServiceListener;
-    private IBluetoothHealth mService;
+    private volatile IBluetoothHealth mService;
     BluetoothAdapter mAdapter;
 
     /**
@@ -546,11 +551,8 @@
         return false;
     }
 
-    private boolean isValidDevice(BluetoothDevice device) {
-        if (device == null) return false;
-
-        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
-        return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
     private boolean checkAppParam(String name, int role, int channelType,
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index a5a0243..07966ed 100644
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -214,7 +214,7 @@
     private Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
-    private IBluetoothInputDevice mService;
+    private volatile IBluetoothInputDevice mService;
 
     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
             new IBluetoothStateChangeCallback.Stub() {
@@ -325,15 +325,16 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.connect(device);
+                return service.connect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -365,15 +366,16 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.disconnect(device);
+                return service.disconnect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -382,15 +384,16 @@
      */
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -399,15 +402,16 @@
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -416,15 +420,16 @@
      */
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
@@ -445,19 +450,20 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF &&
-                priority != BluetoothProfile.PRIORITY_ON) {
-              return false;
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON) {
+                return false;
             }
             try {
-                return mService.setPriority(device, priority);
+                return service.setPriority(device, priority);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -476,15 +482,16 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getPriority(device);
+                return service.getPriority(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.PRIORITY_OFF;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.PRIORITY_OFF;
     }
 
@@ -507,18 +514,13 @@
     };
 
     private boolean isEnabled() {
-       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
-       return false;
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
     }
 
-    private boolean isValidDevice(BluetoothDevice device) {
-       if (device == null) return false;
-
-       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
-       return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
-
     /**
      * Initiate virtual unplug for a HID input device.
      *
@@ -531,16 +533,17 @@
      */
     public boolean virtualUnplug(BluetoothDevice device) {
         if (DBG) log("virtualUnplug(" + device + ")");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.virtualUnplug(device);
+                return service.virtualUnplug(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
 
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
 
     }
@@ -557,16 +560,17 @@
     */
     public boolean getProtocolMode(BluetoothDevice device) {
         if (VDBG) log("getProtocolMode(" + device + ")");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getProtocolMode(device);
+                return service.getProtocolMode(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
     }
 
     /**
@@ -581,15 +585,16 @@
      */
     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
         if (DBG) log("setProtocolMode(" + device + ")");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.setProtocolMode(device, protocolMode);
+                return service.setProtocolMode(device, protocolMode);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -606,17 +611,22 @@
      *               true otherwise
      * @hide
      */
-    public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) {
-        if (VDBG) log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId + "bufferSize=" + bufferSize);
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+    public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
+            int bufferSize) {
+        if (VDBG) {
+            log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId
+                    + "bufferSize=" + bufferSize);
+        }
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getReport(device, reportType, reportId, bufferSize);
+                return service.getReport(device, reportType, reportId, bufferSize);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -634,15 +644,16 @@
      */
     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
         if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.setReport(device, reportType, report);
+                return service.setReport(device, reportType, report);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -659,15 +670,16 @@
      */
     public boolean sendData(BluetoothDevice device, String report) {
         if (DBG) log("sendData(" + device + "), report=" + report);
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.sendData(device, report);
+                return service.sendData(device, report);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -683,15 +695,16 @@
      */
     public boolean getIdleTime(BluetoothDevice device) {
         if (DBG) log("getIdletime(" + device + ")");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getIdleTime(device);
+                return service.getIdleTime(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -708,15 +721,16 @@
      */
     public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
         if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothInputDevice service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.setIdleTime(device, idleTime);
+                return service.setIdleTime(device, idleTime);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
diff --git a/core/java/android/bluetooth/BluetoothInputHost.java b/core/java/android/bluetooth/BluetoothInputHost.java
index 68d105f..6a0506d 100644
--- a/core/java/android/bluetooth/BluetoothInputHost.java
+++ b/core/java/android/bluetooth/BluetoothInputHost.java
@@ -26,8 +26,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
-import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -114,7 +114,7 @@
 
     private ServiceListener mServiceListener;
 
-    private IBluetoothInputHost mService;
+    private volatile IBluetoothInputHost mService;
 
     private BluetoothAdapter mAdapter;
 
@@ -195,24 +195,18 @@
         }
     };
 
-    private ServiceConnection mConnection = new ServiceConnection() {
-
+    private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             Log.d(TAG, "onServiceConnected()");
-
             mService = IBluetoothInputHost.Stub.asInterface(service);
-
             if (mServiceListener != null) {
                 mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST,
                     BluetoothInputHost.this);
             }
         }
-
         public void onServiceDisconnected(ComponentName className) {
             Log.d(TAG, "onServiceDisconnected()");
-
             mService = null;
-
             if (mServiceListener != null) {
                 mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST);
             }
@@ -283,9 +277,10 @@
     public List<BluetoothDevice> getConnectedDevices() {
         Log.v(TAG, "getConnectedDevices()");
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
-                return mService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -302,9 +297,10 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
-                return mService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -321,9 +317,10 @@
     public int getConnectionState(BluetoothDevice device) {
         Log.v(TAG, "getConnectionState(): device=" + device);
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -363,13 +360,14 @@
             return false;
         }
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
                 BluetoothHidDeviceAppConfiguration config =
-                    new BluetoothHidDeviceAppConfiguration();
+                        new BluetoothHidDeviceAppConfiguration();
                 BluetoothHidDeviceCallbackWrapper cbw =
-                    new BluetoothHidDeviceCallbackWrapper(callback);
-                result = mService.registerApp(config, sdp, inQos, outQos, cbw);
+                        new BluetoothHidDeviceCallbackWrapper(callback);
+                result = service.registerApp(config, sdp, inQos, outQos, cbw);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -397,9 +395,10 @@
 
         boolean result = false;
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
-                result = mService.unregisterApp(config);
+                result = service.unregisterApp(config);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -421,9 +420,10 @@
     public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
         boolean result = false;
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
-                result = mService.sendReport(device, id, data);
+                result = service.sendReport(device, id, data);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -448,9 +448,10 @@
 
         boolean result = false;
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
-                result = mService.replyReport(device, type, id, data);
+                result = service.replyReport(device, type, id, data);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -473,9 +474,10 @@
 
         boolean result = false;
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
-                result = mService.reportError(device, error);
+                result = service.reportError(device, error);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -496,9 +498,10 @@
 
         boolean result = false;
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
-                result = mService.unplug(device);
+                result = service.unplug(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -520,9 +523,10 @@
 
         boolean result = false;
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
-                result = mService.connect(device);
+                result = service.connect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -543,9 +547,10 @@
 
         boolean result = false;
 
-        if (mService != null) {
+        final IBluetoothInputHost service = mService;
+        if (service != null) {
             try {
-                result = mService.disconnect(device);
+                result = service.disconnect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 2e73051..0f801fd 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -16,15 +16,18 @@
 
 package android.bluetooth;
 
-import java.util.List;
-import java.util.ArrayList;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.os.*;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * This class provides the APIs to control the Bluetooth MAP
  * Profile.
@@ -39,7 +42,7 @@
     public static final String ACTION_CONNECTION_STATE_CHANGED =
         "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
 
-    private IBluetoothMap mService;
+    private volatile IBluetoothMap mService;
     private final Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
@@ -156,10 +159,13 @@
      */
     public int getState() {
         if (VDBG) log("getState()");
-        if (mService != null) {
+        final IBluetoothMap service = mService;
+        if (service != null) {
             try {
-                return mService.getState();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.getState();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
@@ -175,10 +181,13 @@
      */
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
-        if (mService != null) {
+        final IBluetoothMap service = mService;
+        if (service != null) {
             try {
-                return mService.getClient();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.getClient();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
@@ -193,10 +202,13 @@
      */
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
-        if (mService != null) {
+        final IBluetoothMap service = mService;
+        if (service != null) {
             try {
-                return mService.isConnected(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.isConnected(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
@@ -222,16 +234,16 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothMap service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.disconnect(device);
+                return service.disconnect(device);
             } catch (RemoteException e) {
               Log.e(TAG, Log.getStackTraceString(new Throwable()));
               return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -262,15 +274,16 @@
      */
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothMap service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -281,15 +294,16 @@
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothMap service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -300,16 +314,16 @@
      */
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothMap service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
@@ -326,20 +340,20 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF &&
-                priority != BluetoothProfile.PRIORITY_ON) {
-              return false;
+        final IBluetoothMap service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON) {
+                return false;
             }
             try {
-                return mService.setPriority(device, priority);
+                return service.setPriority(device, priority);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -355,16 +369,16 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothMap service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getPriority(device);
+                return service.getPriority(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return PRIORITY_OFF;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return PRIORITY_OFF;
     }
 
@@ -395,12 +409,8 @@
         log("Bluetooth is Not enabled");
         return false;
     }
-    private boolean isValidDevice(BluetoothDevice device) {
-       if (device == null) return false;
-
-       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
-       return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
-
 }
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index ccab3cd..b8fadf4 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -59,7 +59,7 @@
     public static final String EXTRA_SENDER_CONTACT_NAME =
             "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
 
-    private IBluetoothMapClient mService;
+    private volatile IBluetoothMapClient mService;
     private final Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
@@ -176,9 +176,10 @@
      */
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
-        if (mService != null) {
+        final IBluetoothMapClient service = mService;
+        if (service != null) {
             try {
-                return mService.isConnected(device);
+                return service.isConnected(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -195,9 +196,10 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
-        if (mService != null) {
+        final IBluetoothMapClient service = mService;
+        if (service != null) {
             try {
-                return mService.connect(device);
+                return service.connect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -216,15 +218,15 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "disconnect(" + device + ")");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothMapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.disconnect(device);
+                return service.disconnect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -236,15 +238,16 @@
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) Log.d(TAG, "getConnectedDevices()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothMapClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<>();
     }
 
@@ -256,15 +259,16 @@
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothMapClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<>();
     }
 
@@ -276,16 +280,16 @@
     @Override
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothMapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
@@ -300,20 +304,20 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF &&
-                    priority != BluetoothProfile.PRIORITY_ON) {
+        final IBluetoothMapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON) {
                 return false;
             }
             try {
-                return mService.setPriority(device, priority);
+                return service.setPriority(device, priority);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -329,16 +333,16 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
-        if (mService != null && isEnabled() &&
-                isValidDevice(device)) {
+        final IBluetoothMapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getPriority(device);
+                return service.getPriority(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return PRIORITY_OFF;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return PRIORITY_OFF;
     }
 
@@ -357,9 +361,10 @@
     public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
             PendingIntent sentIntent, PendingIntent deliveredIntent) {
         if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothMapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
+                return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -376,9 +381,10 @@
      */
     public boolean getUnreadMessages(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothMapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getUnreadMessages(device);
+                return service.getUnreadMessages(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
@@ -413,12 +419,8 @@
         return false;
     }
 
-    private boolean isValidDevice(BluetoothDevice device) {
-        if (device == null) return false;
-
-        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
-        return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
-
 }
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 2a026a9..123d10b 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -121,7 +121,7 @@
     private Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
-    private IBluetoothPan mPanService;
+    private volatile IBluetoothPan mPanService;
 
     /**
      * Create a BluetoothPan proxy object for interacting with the local
@@ -235,16 +235,16 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        if (mPanService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothPan service = mPanService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mPanService.connect(device);
+                return service.connect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mPanService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -276,16 +276,16 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        if (mPanService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothPan service = mPanService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mPanService.disconnect(device);
+                return service.disconnect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mPanService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -294,15 +294,16 @@
      */
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        if (mPanService != null && isEnabled()) {
+        final IBluetoothPan service = mPanService;
+        if (service != null && isEnabled()) {
             try {
-                return mPanService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mPanService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -311,15 +312,16 @@
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        if (mPanService != null && isEnabled()) {
+        final IBluetoothPan service = mPanService;
+        if (service != null && isEnabled()) {
             try {
-                return mPanService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mPanService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -328,25 +330,25 @@
      */
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        if (mPanService != null && isEnabled()
-            && isValidDevice(device)) {
+        final IBluetoothPan service = mPanService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mPanService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
-        if (mPanService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
     public void setBluetoothTethering(boolean value) {
         if (DBG) log("setBluetoothTethering(" + value + ")");
-
-        if (mPanService != null && isEnabled()) {
+        final IBluetoothPan service = mPanService;
+        if (service != null && isEnabled()) {
             try {
-                mPanService.setBluetoothTethering(value);
+                service.setBluetoothTethering(value);
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             }
@@ -355,10 +357,10 @@
 
     public boolean isTetheringOn() {
         if (VDBG) log("isTetheringOn()");
-
-        if (mPanService != null && isEnabled()) {
+        final IBluetoothPan service = mPanService;
+        if (service != null && isEnabled()) {
             try {
-                return mPanService.isTetheringOn();
+                return service.isTetheringOn();
             } catch (RemoteException e) {
                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             }
@@ -370,7 +372,6 @@
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "BluetoothPAN Proxy object connected");
             mPanService = IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service));
-
             if (mServiceListener != null) {
                 mServiceListener.onServiceConnected(BluetoothProfile.PAN,
                                                     BluetoothPan.this);
@@ -386,15 +387,11 @@
     };
 
     private boolean isEnabled() {
-       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
-       return false;
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
     }
 
-    private boolean isValidDevice(BluetoothDevice device) {
-       if (device == null) return false;
-
-       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
-       return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index dc01fc7..f16160e4 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -20,8 +20,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.os.RemoteException;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Log;
 
 /**
@@ -67,7 +67,7 @@
     public static final String PBAP_STATE_CHANGED_ACTION =
         "android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED";
 
-    private IBluetoothPbap mService;
+    private volatile IBluetoothPbap mService;
     private final Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
@@ -212,10 +212,13 @@
      */
     public int getState() {
         if (VDBG) log("getState()");
-        if (mService != null) {
+        final IBluetoothPbap service = mService;
+        if (service != null) {
             try {
-                return mService.getState();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.getState();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
@@ -231,10 +234,13 @@
      */
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
-        if (mService != null) {
+        final IBluetoothPbap service = mService;
+        if (service != null) {
             try {
-                return mService.getClient();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.getClient();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
@@ -249,10 +255,13 @@
      */
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
-        if (mService != null) {
+        final IBluetoothPbap service = mService;
+        if (service != null) {
             try {
-                return mService.isConnected(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.isConnected(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
@@ -267,9 +276,10 @@
      */
     public boolean disconnect() {
         if (DBG) log("disconnect()");
-        if (mService != null) {
+        final IBluetoothPbap service = mService;
+        if (service != null) {
             try {
-                mService.disconnect();
+                service.disconnect();
                 return true;
             } catch (RemoteException e) {Log.e(TAG, e.toString());}
         } else {
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index 9f00e1a..28b551e 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -16,17 +16,18 @@
 
 package android.bluetooth;
 
-import java.util.List;
-import java.util.ArrayList;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.os.RemoteException;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * This class provides the APIs to control the Bluetooth PBAP Client Profile.
  *@hide
@@ -40,7 +41,7 @@
     public static final String ACTION_CONNECTION_STATE_CHANGED =
         "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
 
-    private IBluetoothPbapClient mService;
+    private volatile IBluetoothPbapClient mService;
     private final Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
@@ -171,15 +172,16 @@
         if (DBG) {
             log("connect(" + device + ") for PBAP Client.");
         }
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothPbapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.connect(device);
+                return service.connect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
         }
         return false;
@@ -196,16 +198,17 @@
         if (DBG) {
             log("disconnect(" + device + ")" + new Exception() );
         }
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothPbapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                mService.disconnect(device);
+                service.disconnect(device);
                 return true;
             } catch (RemoteException e) {
               Log.e(TAG, Log.getStackTraceString(new Throwable()));
               return false;
             }
         }
-        if (mService == null) {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
         }
         return false;
@@ -222,15 +225,16 @@
         if (DBG) {
             log("getConnectedDevices()");
         }
-        if (mService != null && isEnabled()) {
+        final IBluetoothPbapClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
         }
         return new ArrayList<BluetoothDevice>();
@@ -246,15 +250,16 @@
         if (DBG) {
             log("getDevicesMatchingStates()");
         }
-        if (mService != null && isEnabled()) {
+        final IBluetoothPbapClient service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
         }
         return new ArrayList<BluetoothDevice>();
@@ -270,15 +275,16 @@
         if (DBG) {
             log("getConnectionState(" + device + ")");
         }
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothPbapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
-        if (mService == null) {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
         }
         return BluetoothProfile.STATE_DISCONNECTED;
@@ -318,14 +324,8 @@
         return false;
     }
 
-    private boolean isValidDevice(BluetoothDevice device) {
-       if (device == null) {
-           return false;
-       }
-       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
-           return true;
-       }
-       return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
     /**
@@ -336,27 +336,27 @@
      * {@link #PRIORITY_OFF},
      *
      * @param device Paired bluetooth device
-     * @param priority
+     * @param priority Priority of this profile
      * @return true if priority is set, false on error
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) {
             log("setPriority(" + device + ", " + priority + ")");
         }
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF &&
-                priority != BluetoothProfile.PRIORITY_ON) {
-              return false;
+        final IBluetoothPbapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON) {
+                return false;
             }
             try {
-                return mService.setPriority(device, priority);
+                return service.setPriority(device, priority);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
         }
         return false;
@@ -376,15 +376,16 @@
         if (VDBG) {
             log("getPriority(" + device + ")");
         }
-        if (mService != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothPbapClient service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getPriority(device);
+                return service.getPriority(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return PRIORITY_OFF;
             }
         }
-        if (mService == null) {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
         }
         return PRIORITY_OFF;
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
index 89c1bf8..f9ddb2e 100644
--- a/core/java/android/bluetooth/BluetoothSap.java
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -16,19 +16,18 @@
 
 package android.bluetooth;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.os.RemoteException;
 import android.os.Binder;
 import android.os.IBinder;
-import android.os.ServiceManager;
+import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * This class provides the APIs to control the Bluetooth SIM
  * Access Profile (SAP).
@@ -67,7 +66,7 @@
     public static final String ACTION_CONNECTION_STATE_CHANGED =
         "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED";
 
-    private IBluetoothSap mService;
+    private volatile IBluetoothSap mService;
     private final Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
@@ -196,10 +195,13 @@
      */
     public int getState() {
         if (VDBG) log("getState()");
-        if (mService != null) {
+        final IBluetoothSap service = mService;
+        if (service != null) {
             try {
-                return mService.getState();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.getState();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
@@ -216,10 +218,13 @@
      */
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
-        if (mService != null) {
+        final IBluetoothSap service = mService;
+        if (service != null) {
             try {
-                return mService.getClient();
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.getClient();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
@@ -235,10 +240,13 @@
      */
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
-        if (mService != null) {
+        final IBluetoothSap service = mService;
+        if (service != null) {
             try {
-                return mService.isConnected(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+                return service.isConnected(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
         } else {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
@@ -266,16 +274,16 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothSap service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.disconnect(device);
+                return service.disconnect(device);
             } catch (RemoteException e) {
               Log.e(TAG, Log.getStackTraceString(new Throwable()));
               return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -287,15 +295,16 @@
      */
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothSap service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getConnectedDevices();
+                return service.getConnectedDevices();
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -307,15 +316,16 @@
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
-        if (mService != null && isEnabled()) {
+        final IBluetoothSap service = mService;
+        if (service != null && isEnabled()) {
             try {
-                return mService.getDevicesMatchingConnectionStates(states);
+                return service.getDevicesMatchingConnectionStates(states);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return new ArrayList<BluetoothDevice>();
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return new ArrayList<BluetoothDevice>();
     }
 
@@ -327,16 +337,16 @@
      */
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothSap service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return BluetoothProfile.STATE_DISCONNECTED;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
@@ -352,20 +362,20 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF &&
-                priority != BluetoothProfile.PRIORITY_ON) {
-              return false;
+        final IBluetoothSap service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON) {
+                return false;
             }
             try {
-                return mService.setPriority(device, priority);
+                return service.setPriority(device, priority);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return false;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return false;
     }
 
@@ -378,20 +388,20 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        if (mService != null && isEnabled() &&
-            isValidDevice(device)) {
+        final IBluetoothSap service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
             try {
-                return mService.getPriority(device);
+                return service.getPriority(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 return PRIORITY_OFF;
             }
         }
-        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
         return PRIORITY_OFF;
     }
 
-    private ServiceConnection mConnection = new ServiceConnection() {
+    private final ServiceConnection mConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) log("Proxy object connected");
             mService = IBluetoothSap.Stub.asInterface(Binder.allowBlocking(service));
@@ -421,13 +431,8 @@
         return false;
     }
 
-    private boolean isValidDevice(BluetoothDevice device) {
-       if (device == null)
-           return false;
-
-       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress()))
-           return true;
-       return false;
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
     }
 
 }
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 9e9c8fe..7106a84 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -137,6 +137,11 @@
      * the PendingIntent. Use this method of scanning if your process is not always running and it
      * should be started when scan results are available.
      * <p>
+     * An app must hold
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
+     * in order to get results.
+     * <p>
      * When the PendingIntent is delivered, the Intent passed to the receiver or activity
      * will contain one or more of the extras {@link #EXTRA_CALLBACK_TYPE},
      * {@link #EXTRA_ERROR_CODE} and {@link #EXTRA_LIST_SCAN_RESULT} to indicate the result of
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index ad7a5ab..664bcbca 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -718,6 +718,10 @@
      * Cycles do not exist because they are illegal and screened for during installation.
      *
      * May be null if no splits are installed, or if no dependencies exist between them.
+     *
+     * NOTE: Any change to the way split dependencies are stored must update the logic that
+     *       creates the class loader context for dexopt (DexoptUtils#getClassLoaderContexts).
+     *
      * @hide
      */
     public SparseArray<int[]> splitDependencies;
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index 571656a..94d5555 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -16,6 +16,7 @@
 
 package android.database.sqlite;
 
+import android.annotation.TestApi;
 import android.content.res.Resources;
 import android.os.StatFs;
 import android.os.SystemProperties;
@@ -34,6 +35,7 @@
  *
  * @hide
  */
+@TestApi
 public final class SQLiteGlobal {
     private static final String TAG = "SQLiteGlobal";
 
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 0d5c5e3..bfeb14d 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1769,7 +1769,7 @@
         }
 
         @Override
-        public void onRepeatingRequestError(long lastFrameNumber) {
+        public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
             if (DEBUG) {
                 Log.d(TAG, "Repeating request error received. Last frame number is " +
                         lastFrameNumber);
@@ -1782,7 +1782,10 @@
                 }
 
                 checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
-                mRepeatingRequestId = REQUEST_ID_NONE;
+                // Check if there is already a new repeating request
+                if (mRepeatingRequestId == repeatingRequestId) {
+                    mRepeatingRequestId = REQUEST_ID_NONE;
+                }
             }
         }
 
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
index 135d92b..89ecd5f 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceState.java
@@ -77,7 +77,7 @@
         void onCaptureStarted(RequestHolder holder, long timestamp);
         void onCaptureResult(CameraMetadataNative result, RequestHolder holder);
         void onRequestQueueEmpty();
-        void onRepeatingRequestError(long lastFrameNumber);
+        void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId);
     }
 
     /**
@@ -208,12 +208,14 @@
      * <p>Repeating request has been stopped due to an error such as abandoned output surfaces.</p>
      *
      * @param lastFrameNumber Frame number of the last repeating request before it is stopped.
+     * @param repeatingRequestId The ID of the repeating request being stopped
      */
-    public synchronized void setRepeatingRequestError(final long lastFrameNumber) {
+    public synchronized void setRepeatingRequestError(final long lastFrameNumber,
+            final int repeatingRequestId) {
         mCurrentHandler.post(new Runnable() {
             @Override
             public void run() {
-                mCurrentListener.onRepeatingRequestError(lastFrameNumber);
+                mCurrentListener.onRepeatingRequestError(lastFrameNumber, repeatingRequestId);
             }
         });
     }
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index d8df9a0..49d4096 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -264,10 +264,10 @@
         }
 
         @Override
-        public void onRepeatingRequestError(long lastFrameNumber) {
+        public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
+            Object[] objArray = new Object[] { lastFrameNumber, repeatingRequestId };
             Message msg = getHandler().obtainMessage(REPEATING_REQUEST_ERROR,
-                    /*arg1*/ (int) (lastFrameNumber & 0xFFFFFFFFL),
-                    /*arg2*/ (int) ( (lastFrameNumber >> 32) & 0xFFFFFFFFL));
+                    /*obj*/ objArray);
             getHandler().sendMessage(msg);
         }
 
@@ -329,9 +329,10 @@
                             break;
                         }
                         case REPEATING_REQUEST_ERROR: {
-                            long lastFrameNumber = msg.arg2 & 0xFFFFFFFFL;
-                            lastFrameNumber = (lastFrameNumber << 32) | (msg.arg1 & 0xFFFFFFFFL);
-                            mCallbacks.onRepeatingRequestError(lastFrameNumber);
+                            Object[] objArray = (Object[]) msg.obj;
+                            long lastFrameNumber = (Long) objArray[0];
+                            int repeatingRequestId = (Integer) objArray[1];
+                            mCallbacks.onRepeatingRequestError(lastFrameNumber, repeatingRequestId);
                             break;
                         }
                         case REQUEST_QUEUE_EMPTY: {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 621ea84..cb59fd1 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -263,7 +263,8 @@
         }
 
         @Override
-        public void onRepeatingRequestError(final long lastFrameNumber) {
+        public void onRepeatingRequestError(final long lastFrameNumber,
+                final int repeatingRequestId) {
             mResultHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -271,7 +272,8 @@
                         Log.d(TAG, "doing onRepeatingRequestError callback.");
                     }
                     try {
-                        mDeviceCallbacks.onRepeatingRequestError(lastFrameNumber);
+                        mDeviceCallbacks.onRepeatingRequestError(lastFrameNumber,
+                                repeatingRequestId);
                     } catch (RemoteException e) {
                         throw new IllegalStateException(
                                 "Received remote exception during onRepeatingRequestError " +
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 565a43e..aaf07e6 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -939,7 +939,8 @@
                             Log.d(TAG, "Stopped repeating request. Last frame number is " +
                                     lastFrameNumber);
                         }
-                        mDeviceState.setRepeatingRequestError(lastFrameNumber);
+                        mDeviceState.setRepeatingRequestError(lastFrameNumber,
+                                burstHolder.getRequestId());
                     }
 
                     if (DEBUG) {
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 1bb0fbb..f527f77 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -18,14 +18,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.ProxyInfo;
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.TextUtils;
 
-import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
+import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -504,11 +503,22 @@
     }
 
     /**
+     * Make sure this LinkProperties instance contains routes that cover the local subnet
+     * of its link addresses. Add any route that is missing.
+     * @hide
+     */
+    public void ensureDirectlyConnectedRoutes() {
+        for (LinkAddress addr: mLinkAddresses) {
+            addRoute(new RouteInfo(addr, null, mIfaceName));
+        }
+    }
+
+    /**
      * Returns all the routes on this link and all the links stacked above it.
      * @hide
      */
     public List<RouteInfo> getAllRoutes() {
-        List<RouteInfo> routes = new ArrayList();
+        List<RouteInfo> routes = new ArrayList<>();
         routes.addAll(mRoutes);
         for (LinkProperties stacked: mStackedLinks.values()) {
             routes.addAll(stacked.getAllRoutes());
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 1dcaef4..3e08dcf 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -51,6 +51,7 @@
  *       Settings screen).
  * </ol>
  *
+ * <a name="BasicUsage"></a>
  * <h3>Basic usage</h3>
  *
  * <p>The basic autofill process is defined by the workflow below:
@@ -122,12 +123,14 @@
  * each {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} received - if it
  * doesn't, the request will eventually time out and be discarded by the Android System.
  *
+ * <a name="SavingUserData"></a>
  * <h3>Saving user data</h3>
  *
  * <p>If the service is also interested on saving the data filled by the user, it must set a
  * {@link SaveInfo} object in the {@link FillResponse}. See {@link SaveInfo} for more details and
  * examples.
  *
+ * <a name="UserAuthentication"></a>
  * <h3>User authentication</h3>
  *
  * <p>The service can provide an extra degree of security by requiring the user to authenticate
@@ -164,6 +167,7 @@
  * credentials in "vaults": the first response would contain fake datasets with the vault names,
  * and the subsequent response would contain the app credentials stored in that vault.
  *
+ * <a name="DataPartioning"></a>
  * <h3>Data partitioning</h3>
  *
  * <p>The autofillable views in a screen should be grouped in logical groups called "partitions".
@@ -243,6 +247,7 @@
  * <p>When the service returns multiple {@link FillResponse}, the last one overrides the previous;
  * that's why the {@link SaveInfo} in the 2nd request above has the info for both partitions.
  *
+ * <a name="PackageVerification"></a>
  * <h3>Package verification</h3>
  *
  * <p>When autofilling app-specific data (like username and password), the service must verify
@@ -270,9 +275,16 @@
  *   }
  *   return hash.toString();
  * }
- *
  * </pre>
  *
+ * <p>If the service did not store the signing certificates data the first time the data was saved
+ * &mdash; for example, because the data was created by a previous version of the app that did not
+ * use the Autofill Framework &mdash; the service should warn the user that the authenticity of the
+ * app cannot be confirmed (see an example on how to show such warning in the
+ * <a href="#WebSecurityDisclaimer">Web security</a> section below), and if the user agrees,
+ * then the service could save the data from the signing ceriticates for future use.
+ *
+ * <a name="IgnoringViews"></a>
  * <h3>Ignoring views</h3>
  *
  * <p>If the service find views that cannot be autofilled (for example, a text field representing
@@ -281,6 +293,7 @@
  * a new {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} when these views are
  * focused.
  *
+ * <a name="WebSecurity"></a>
  * <h3>Web security</h3>
  *
  * <p>When handling autofill requests that represent web pages (typically
@@ -313,6 +326,7 @@
  * }
  * </pre>
  *
+ * <a name="WebSecurityDisclaimer"></a>
  * <p>If the association between the web domain and app package cannot be verified through the steps
  * above, but the service thinks that it is appropriate to fill persisted credentials that are
  * stored for the web domain, the service should warn the user about the potential data
diff --git a/core/java/android/service/autofill/CharSequenceTransformation.java b/core/java/android/service/autofill/CharSequenceTransformation.java
index 8ab856e..2413e97 100644
--- a/core/java/android/service/autofill/CharSequenceTransformation.java
+++ b/core/java/android/service/autofill/CharSequenceTransformation.java
@@ -86,7 +86,7 @@
             }
             try {
                 final Matcher matcher = field.first.matcher(value);
-                if (!matcher.matches()) {
+                if (!matcher.find()) {
                     if (sDebug) Log.d(TAG, "match for " + field.first + " failed on id " + id);
                     return;
                 }
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index f7dc1c5..768e743 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -22,8 +22,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.view.autofill.AutofillId;
-import android.widget.RemoteViews;
+import android.view.autofill.AutofillManager;
 
 import com.android.internal.util.Preconditions;
 
@@ -81,7 +80,7 @@
     /**
      * Returns the client state set in the previous {@link FillResponse}.
      *
-     * <p><b>NOTE: </b>the state is associated with the app that was autofilled in the previous
+     * <p><b>Note: </b>the state is associated with the app that was autofilled in the previous
      * {@link AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)}
      * , which is not necessary the same app being autofilled now.
      */
@@ -148,6 +147,14 @@
     public static final class Event {
         /**
          * A dataset was selected. The dataset selected can be read from {@link #getDatasetId()}.
+         *
+         * <p><b>Note: </b>on Android {@link android.os.Build.VERSION_CODES#O}, this event was also
+         * incorrectly reported after a
+         * {@link Dataset.Builder#setAuthentication(IntentSender) dataset authentication} was
+         * selected and the service returned a dataset in the
+         * {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT} of the activity launched from that
+         * {@link IntentSender}. This behavior was fixed on Android
+         * {@link android.os.Build.VERSION_CODES#O_MR1}.
          */
         public static final int TYPE_DATASET_SELECTED = 0;
 
@@ -158,8 +165,8 @@
         public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1;
 
         /**
-         * A {@link FillResponse.Builder#setAuthentication(AutofillId[], IntentSender, RemoteViews)
-         * fill response authentication} was selected.
+         * A {@link FillResponse.Builder#setAuthentication(android.view.autofill.AutofillId[],
+         * IntentSender, android.widget.RemoteViews) fill response authentication} was selected.
          */
         public static final int TYPE_AUTHENTICATION_SELECTED = 2;
 
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index 1c32fe0..3a84224 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Bundle;
-import android.os.CancellationSignal;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.View;
@@ -38,7 +37,7 @@
  * interesting for saving and what are the possible ways to fill the inputs on
  * the screen if applicable.
  *
- * @see AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)
+ * @see AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)
  */
 public final class FillRequest implements Parcelable {
 
@@ -122,9 +121,14 @@
         return mContexts;
     }
 
+    @Override
+    public String toString() {
+        return "FillRequest: [id=" + mId + ", flags=" + mFlags + ", ctxts= " + mContexts + "]";
+    }
+
     /**
      * Gets the extra client state returned from the last {@link
-     * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)
+     * AutofillService#onFillRequest(FillRequest, android.os.CancellationSignal, FillCallback)
      * fill request}, so the service can use it for state management.
      *
      * <p>Once a {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 3b09c67..6d8a959 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -148,10 +148,12 @@
          * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen
          * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE
          * client state}. Once you complete your authentication flow you should set the
-         * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and provide the fully
-         * populated {@link FillResponse response} by setting it to the
-         * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra.
-         * For example, if you provided an empty {@link FillResponse resppnse} because the
+         * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and set the
+         * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra
+         * with the fully populated {@link FillResponse response} (or {@code null} if the screen
+         * cannot be autofilled).
+         *
+         * <p>For example, if you provided an empty {@link FillResponse response} because the
          * user's data was locked and marked that the response needs an authentication then
          * in the response returned if authentication succeeds you need to provide all
          * available data sets some of which may need to be further authenticated, for
diff --git a/core/java/android/service/oemlock/IOemLockService.aidl b/core/java/android/service/oemlock/IOemLockService.aidl
index 682e7ee..d5e10d6 100644
--- a/core/java/android/service/oemlock/IOemLockService.aidl
+++ b/core/java/android/service/oemlock/IOemLockService.aidl
@@ -28,7 +28,6 @@
     void setOemUnlockAllowedByUser(boolean allowed);
     boolean isOemUnlockAllowedByUser();
 
-    boolean canUserAllowOemUnlock();
     boolean isOemUnlockAllowed();
     boolean isDeviceOemUnlocked();
 }
diff --git a/core/java/android/service/oemlock/OemLockManager.java b/core/java/android/service/oemlock/OemLockManager.java
index 3a56d9f..f0d6603 100644
--- a/core/java/android/service/oemlock/OemLockManager.java
+++ b/core/java/android/service/oemlock/OemLockManager.java
@@ -118,24 +118,6 @@
     }
 
     /**
-     * Returns whether all parties other than the user allow OEM unlock meaning the user can
-     * directly control whether or not the device can be OEM unlocked.
-     *
-     * If this is true, {@link #isOemUnlockAllowedByUser} is the same as {@link #isOemUnlockAllowed}
-     *
-     * @return Whether the user can directly control whether the device can be OEM unlocked.
-     *
-     * @hide
-     */
-    public boolean canUserAllowOemUnlock() {
-        try {
-            return mService.canUserAllowOemUnlock();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * @return Whether the bootloader is able to OEM unlock the device.
      *
      * @hide
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 829b2b7..a2147b7 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -804,8 +804,11 @@
     public static final int KEYCODE_SYSTEM_NAVIGATION_LEFT = 282;
     /** Key code constant: Consumed by the system for navigation right */
     public static final int KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;
+    /** Key code constant: Show all apps
+     * @hide */
+    public static final int KEYCODE_ALL_APPS = 284;
 
-    private static final int LAST_KEYCODE = KEYCODE_SYSTEM_NAVIGATION_RIGHT;
+    private static final int LAST_KEYCODE = KEYCODE_ALL_APPS;
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 1d206ab..cac27af 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
 import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
 import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
@@ -871,6 +872,31 @@
         return callbacks;
     }
 
+    /**
+     * This method still exists only for compatibility reasons because some applications have relied
+     * on this method via reflection. See Issue 36345857 for details.
+     *
+     * @deprecated No platform code is using this method anymore.
+     * @hide
+     */
+    @Deprecated
+    public void setWindowType(int type) {
+        if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
+            throw new UnsupportedOperationException(
+                    "SurfaceView#setWindowType() has never been a public API.");
+        }
+
+        if (type == TYPE_APPLICATION_PANEL) {
+            Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
+                    + "just to make the SurfaceView to be placed on top of its window, you must "
+                    + "call setZOrderOnTop(true) instead.", new Throwable());
+            setZOrderOnTop(true);
+            return;
+        }
+        Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
+                + "type=" + type, new Throwable());
+    }
+
     private void runOnUiThread(Runnable runnable) {
         Handler handler = getHandler();
         if (handler != null && handler.getLooper() != Looper.myLooper()) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 73fc437..d030c93 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7618,6 +7618,10 @@
      *   <li>Call
      *    {@link android.view.autofill.AutofillManager#notifyValueChanged(View, int, AutofillValue)}
      *       when the value of a virtual child changed.
+     *   <li>Call
+     *    {@link
+     *    android.view.autofill.AutofillManager#notifyViewVisibilityChanged(View, int, boolean)}
+     *       when the visibility of a virtual child changed.
      *   <li>Call {@link AutofillManager#commit()} when the autofill context of the view structure
      *       changed and the current context should be committed (for example, when the user tapped
      *       a {@code SUBMIT} button in an HTML page).
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b2e2e88..cd2d2fd 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10875,6 +10875,7 @@
     @Override
     public boolean performLongClick() {
         boolean handled = false;
+        boolean performedHapticFeedback = false;
 
         if (mEditor != null) {
             mEditor.mIsBeingLongClicked = true;
@@ -10882,6 +10883,7 @@
 
         if (super.performLongClick()) {
             handled = true;
+            performedHapticFeedback = true;
         }
 
         if (mEditor != null) {
@@ -10890,7 +10892,9 @@
         }
 
         if (handled) {
-            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            if (!performedHapticFeedback) {
+              performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            }
             if (mEditor != null) mEditor.mDiscardNextActionUp = true;
         } else {
             MetricsLogger.action(
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 36ab394..b22ce5e 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -53,8 +53,7 @@
 import android.widget.ImageView;
 
 public class PlatLogoActivity extends Activity {
-    public static final boolean REVEAL_THE_NAME = false;
-    public static final boolean FINISH = false;
+    public static final boolean FINISH = true;
 
     FrameLayout mLayout;
     int mTapCount;
@@ -85,15 +84,18 @@
         im.setAlpha(0f);
 
         im.setBackground(new RippleDrawable(
-                ColorStateList.valueOf(0xFFFFFFFF),
+                ColorStateList.valueOf(0xFF776677),
                 getDrawable(com.android.internal.R.drawable.platlogo),
                 null));
-//        im.setOutlineProvider(new ViewOutlineProvider() {
-//            @Override
-//            public void getOutline(View view, Outline outline) {
-//                outline.setOval(0, 0, view.getWidth(), view.getHeight());
-//            }
-//        });
+        im.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                final int w = view.getWidth();
+                final int h = view.getHeight();
+                outline.setOval((int)(w*.125), (int)(h*.125), (int)(w*.96), (int)(h*.96));
+            }
+        });
+        im.setElevation(12f*dp);
         im.setClickable(true);
         im.setOnClickListener(new View.OnClickListener() {
             @Override
@@ -103,18 +105,6 @@
                     public boolean onLongClick(View v) {
                         if (mTapCount < 5) return false;
 
-                        if (REVEAL_THE_NAME) {
-                            final Drawable overlay = getDrawable(
-                                com.android.internal.R.drawable.platlogo_m);
-                            overlay.setBounds(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
-                            im.getOverlay().clear();
-                            im.getOverlay().add(overlay);
-                            overlay.setAlpha(0);
-                            ObjectAnimator.ofInt(overlay, "alpha", 0, 255)
-                                .setDuration(500)
-                                .start();
-                        }
-
                         final ContentResolver cr = getContentResolver();
                         if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0)
                                 == 0) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index dafa68e..4e06577 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1064,7 +1064,7 @@
         WindowManager.LayoutParams attrs = mWindow.getAttributes();
         int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
 
-        if (!mWindow.mIsFloating && ActivityManager.isHighEndGfx()) {
+        if (!mWindow.mIsFloating) {
             boolean disallowAnimate = !isLaidOut();
             disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
                     & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
@@ -1317,11 +1317,12 @@
                 v.setTag(new Pair<>(verticalBar, seascape));
             } else {
                 final LayerDrawable d = (LayerDrawable) v.getBackground();
-                final InsetDrawable inset = ((InsetDrawable) d.getDrawable(0));
-                ((ColorDrawable) inset.getDrawable()).setColor(dividerColor);
-                ((ColorDrawable) d.getDrawable(1)).setColor(color);
+                final InsetDrawable inset = ((InsetDrawable) d.getDrawable(1));
+                ((ColorDrawable) inset.getDrawable()).setColor(color);
+                ((ColorDrawable) d.getDrawable(0)).setColor(dividerColor);
             }
         } else {
+            v.setTag(null);
             v.setBackgroundColor(color);
         }
     }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 2de9537..b13560c 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2441,7 +2441,7 @@
 
         // Non-floating windows on high end devices must put up decor beneath the system bars and
         // therefore must know about visibility changes of those.
-        if (!mIsFloating && ActivityManager.isHighEndGfx()) {
+        if (!mIsFloating) {
             if (!targetPreL && a.getBoolean(
                     R.styleable.Window_windowDrawsSystemBarBackgrounds,
                     false)) {
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index b702898..39f82a5 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -31,7 +31,6 @@
  */
 @RemoteViews.RemoteView
 public class NotificationExpandButton extends ImageView {
-    private View mLabeledBy;
 
     public NotificationExpandButton(Context context) {
         super(context);
@@ -69,12 +68,5 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         info.setClassName(Button.class.getName());
-        if (mLabeledBy != null) {
-            info.setLabeledBy(mLabeledBy);
-        }
-    }
-
-    public void setLabeledBy(View labeledBy) {
-        mLabeledBy = labeledBy;
     }
 }
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 592576b..e53162c 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -25,6 +25,7 @@
 import android.hardware.input.InputManager.InputDeviceListener;
 import android.os.SystemProperties;
 import android.util.Log;
+import android.util.Slog;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -630,6 +631,12 @@
                     >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP
 
             final int id = event.getPointerId(index);
+            if (id >= NP) {
+                Slog.wtf(TAG, "Got pointer ID out of bounds: id=" + id + " arraysize="
+                        + NP + " pointerindex=" + index
+                        + " action=0x" + Integer.toHexString(action));
+                return;
+            }
             final PointerState ps = mPointers.get(id);
             ps.mCurDown = false;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 89bbec2..031c3f5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -309,6 +309,10 @@
     <protected-broadcast android:name="com.android.server.usb.ACTION_OPEN_IN_APPS" />
     <protected-broadcast android:name="com.android.server.am.DELETE_DUMPHEAP" />
     <protected-broadcast android:name="com.android.server.net.action.SNOOZE_WARNING" />
+    <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
+    <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
+    <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
+    <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_NETWORK_AFTER_FAILURE" />
     <protected-broadcast android:name="android.net.wifi.WIFI_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index 182ba24..a6dee8a 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
 Copyright (C) 2017 The Android Open Source Project
 
@@ -14,27 +15,35 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="480dp"
-        android:height="480dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-    <path
-        android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0"
-        android:fillAlpha="0.066"
-        android:fillColor="#000000"/>
-    <path
-        android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0"
-        android:fillColor="#FFC107"/>
-    <path
-        android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z"
-        android:fillColor="#FE9F00"/>
-    <path
-        android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0"
-        android:fillColor="#FED44F"/>
-    <path
-        android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z"
-        android:fillColor="#FFC107"/>
-    <path
-        android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0"
-        android:fillColor="#FFFFFF"/>
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+    <group>
+        <path
+            android:fillColor="#2C292A"
+            android:fillType="evenOdd"
+            android:pathData="M6,26a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/>
+        <path
+            android:fillColor="#FAFAFA"
+            android:fillType="evenOdd"
+            android:pathData="M4,24a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/>
+        <path
+            android:fillColor="#2C292A"
+            android:fillType="evenOdd"
+            android:pathData="M2,22a20,20 0 0,1 40,0a20,20 0 0,1 -40,0z"/>
+        <path
+            android:fillColor="#00000000"
+            android:strokeColor="#453F41"
+            android:strokeWidth="1"
+            android:fillType="evenOdd"
+            android:pathData="M26.5 29.5v3c0 1.13-.87 2-2 2s-2-.87-2-2v-3h-1v3c0 1.13-.87 2-2 2s-2-.87-2-2v-3H17a1.5 1.5 0 0 1-1.5-1.5V17.5h13V28a1.5 1.5 0 0 1-1.5 1.5h-.5zM13.5 17.5c1.13 0 2 .87 2 2v7c0 1.13-.87 2-2 2s-2-.87-2-2v-7c0-1.13.87-2 2-2zM30.5 17.5c1.13 0 2 .87 2 2v7c0 1.13-.87 2-2 2s-2-.87-2-2v-7c0-1.13.87-2 2-2zM26.3 12.11A6.46 6.46 0 0 1 28.5 17v.5h-13V17a6.46 6.46 0 0 1 2.2-4.89l-.9-.9a.98.98 0 0 1 0-1.41.98.98 0 0 1 1.4 0l1.26 1.25A6.33 6.33 0 0 1 22 10.5c.87 0 1.73.2 2.54.55L25.8 9.8a.98.98 0 0 1 1.4 0 .98.98 0 0 1 0 1.4l-.9.91z"/>
+        <path
+            android:fillColor="#453F41"
+            android:fillType="evenOdd"
+            android:pathData="M20.16 14.5a.66.66 0 1 1-1.31 0c0-.36.29-.65.65-.65.36 0 .65.29.65.65zM25.16 14.5c0 .36-.3.66-.66.66a.65.65 0 1 1 .66-.66z"/>
+        <path
+            android:fillColor="#453F41"
+            android:pathData="M22 40.5c0.36 0 0.73-0.01 1.09-0.03l-0.18-3A15.77 15.77 0 0 1 22 37.5v3zm2.17-0.13a18.48 18.48 0 0 0 1.08-0.15l-0.53-2.96c-0.3 0.05-0.6 0.1-0.9 0.13l0.35 2.98zM26.32 40a18.37 18.37 0 0 0 1.05-0.28l-0.87-2.87a15.37 15.37 0 0 1-0.88 0.23l0.7 2.92zm2.1-0.64l-1.03-2.81a15.39 15.39 0 0 0 0.84-0.34l1.2 2.74a18.39 18.39 0 0 1-1 0.41zm1.99-0.87l-1.37-2.67a15.46 15.46 0 0 0 0.8-0.44l1.52 2.59a18.46 18.46 0 0 1-0.95 0.52zm1.89-1.11l-1.67-2.5a15.55 15.55 0 0 0 0.74-0.52l1.81 2.39a18.55 18.55 0 0 1-0.88 0.63zm1.75-1.33l-1.95-2.28a15.6 15.6 0 0 0 0.67-0.61l2.09 2.15a18.6 18.6 0 0 1-0.8 0.74zm1.6-1.55l-2.22-2.02a15.6 15.6 0 0 0 0.6-0.7l2.33 1.9a18.6 18.6 0 0 1-0.72 0.82zM37 32.82l-2.43-1.76a15.53 15.53 0 0 0 0.5-0.75l2.54 1.6c-0.2 0.31-0.4 0.61-0.61 0.9zm1.15-1.8l-2.62-1.47a15.45 15.45 0 0 0 0.42-0.8l2.7 1.3a18.45 18.45 0 0 1-0.5 0.97zm0.95-1.98l-2.77-1.14a15.38 15.38 0 0 0 0.32-0.86l2.84 0.98a18.38 18.38 0 0 1-0.39 1.02zm0.72-2.09c0.1-0.34 0.18-0.7 0.26-1.05l-2.93-0.63a15.38 15.38 0 0 1-0.22 0.88l2.89 0.8zm0.46-2.15a18.52 18.52 0 0 0 0.13-1.08l-2.99-0.28a15.52 15.52 0 0 1-0.1 0.9l2.96 0.46zm0.2-2.2a18.81 18.81 0 0 0 0-1.1l-3 0.08a16 16 0 0 1 0 0.92l3 0.1zm-0.06-2.2a18.54 18.54 0 0 0-0.12-1.07l-2.97 0.43c0.04 0.3 0.08 0.6 0.1 0.9l3-0.25zm-0.31-2.15a18.39 18.39 0 0 0-0.25-1.06l-2.9 0.78a15.39 15.39 0 0 1 0.21 0.89l2.94-0.6zm-0.57-2.12l-2.85 0.95a15.37 15.37 0 0 0-0.31-0.85l2.78-1.12a18.37 18.37 0 0 1 0.38 1.02zm-0.83-2.06l-2.71 1.29a15.44 15.44 0 0 0-0.42-0.81l2.63-1.45a18.44 18.44 0 0 1 0.5 0.97zm-1.03-1.88l-2.54 1.6a15.53 15.53 0 0 0-0.5-0.76l2.44-1.74 0.6 0.9zm-1.28-1.79l-2.33 1.88a15.6 15.6 0 0 0-0.6-0.69l2.23-2.02a18.6 18.6 0 0 1 0.7 0.83zm-1.48-1.63l-2.1 2.14a15.6 15.6 0 0 0-0.67-0.62l1.97-2.26a18.6 18.6 0 0 1 0.8 0.74zM33.24 7.3l-1.82 2.38a15.55 15.55 0 0 0-0.74-0.53l1.68-2.49c0.3 0.2 0.6 0.42 0.88 0.64zm-1.71-1.17L29.98 8.7a15.47 15.47 0 0 0-0.8-0.45l1.4-2.66a18.47 18.47 0 0 1 0.95 0.54zm-1.95-1.02l-1.23 2.74A15.4 15.4 0 0 0 27.5 7.5l1.06-2.8a18.4 18.4 0 0 1 1.01 0.4zm-2.06-0.78l-0.9 2.86a15.37 15.37 0 0 0-0.87-0.24l0.72-2.92a18.37 18.37 0 0 1 1.05 0.3zM25.38 3.8a18.47 18.47 0 0 0-1.08-0.17l-0.37 2.98c0.3 0.04 0.6 0.08 0.9 0.14l0.55-2.95zm-2.2-0.27A18.75 18.75 0 0 0 22.1 3.5l-0.02 3L23 6.53l0.19-3zM21 3.53a18.6 18.6 0 0 0-1.08 0.09l0.33 2.98a15.6 15.6 0 0 1 0.91-0.08l-0.16-3zm-2.16 0.24A18.4 18.4 0 0 0 17.76 4l0.68 2.92a15.4 15.4 0 0 1 0.9-0.18l-0.51-2.96zm-2.14 0.5l0.86 2.88a15.37 15.37 0 0 0-0.86 0.28l-1.03-2.81a18.37 18.37 0 0 1 1.03-0.35zm-2.07 0.76l1.2 2.75a15.42 15.42 0 0 0-0.83 0.4L13.63 5.5a18.42 18.42 0 0 1 0.99-0.47zM12.7 6l1.5 2.6a15.5 15.5 0 0 0-0.76 0.48l-1.66-2.5A18.5 18.5 0 0 1 12.7 6zm-1.83 1.22l1.8 2.4a15.58 15.58 0 0 0-0.7 0.57L10.01 7.9a18.58 18.58 0 0 1 0.85-0.68zM9.19 8.66l2.07 2.16a15.6 15.6 0 0 0-0.63 0.65l-2.2-2.04a18.6 18.6 0 0 1 0.76-0.77zm-1.51 1.63l2.32 1.9a15.57 15.57 0 0 0-0.56 0.72l-2.42-1.76a18.57 18.57 0 0 1 0.66-0.86zm-1.23 1.69l2.52 1.62a15.5 15.5 0 0 0-0.47 0.78l-2.61-1.47a18.5 18.5 0 0 1 0.56-0.93zm-1.08 1.9l2.7 1.32a15.41 15.41 0 0 0-0.38 0.83l-2.77-1.15a18.41 18.41 0 0 1 0.45-1zm-0.85 2.04l2.84 0.98a15.37 15.37 0 0 0-0.28 0.87L4.2 16.96c0.1-0.35 0.2-0.7 0.32-1.04zm-0.6 2.12a18.43 18.43 0 0 0-0.2 1.07l2.97 0.47c0.05-0.3 0.1-0.6 0.17-0.9l-2.93-0.64zm-0.34 2.18a18.65 18.65 0 0 0-0.07 1.09l3 0.11 0.06-0.91-2.99-0.29zm-0.08 2.2a18.7 18.7 0 0 0 0.06 1.1l3-0.25a15.7 15.7 0 0 1-0.06-0.91l-3 0.07zm0.18 2.18a18.44 18.44 0 0 0 0.18 1.07l2.95-0.6a15.44 15.44 0 0 1-0.16-0.9L3.68 24.6zm0.43 2.14l2.9-0.77a15.37 15.37 0 0 0 0.26 0.88l-2.85 0.94a18.37 18.37 0 0 1-0.3-1.05zm0.7 2.1l2.78-1.11a15.4 15.4 0 0 0 0.36 0.83l-2.71 1.27a18.4 18.4 0 0 1-0.44-1zm0.9 1.95l2.65-1.43a15.48 15.48 0 0 0 0.45 0.8l-2.55 1.57a18.48 18.48 0 0 1-0.54-0.94zm1.17 1.87l2.45-1.73a15.56 15.56 0 0 0 0.54 0.73l-2.34 1.87a18.56 18.56 0 0 1-0.65-0.87zm1.37 1.72l2.23-2a15.6 15.6 0 0 0 0.63 0.65l-2.1 2.14a18.6 18.6 0 0 1-0.76-0.79zm1.58 1.56l1.98-2.26c0.22 0.2 0.46 0.39 0.7 0.58l-1.84 2.37a18.59 18.59 0 0 1-0.84-0.7zm1.66 1.28l1.7-2.46a15.52 15.52 0 0 0 0.77 0.5l-1.56 2.56a18.52 18.52 0 0 1-0.91-0.6zm1.87 1.14l1.4-2.65a15.43 15.43 0 0 0 0.82 0.4l-1.24 2.73a18.43 18.43 0 0 1-0.98-0.48zm2 0.91l1.08-2.8a15.37 15.37 0 0 0 0.86 0.3l-0.9 2.86a18.37 18.37 0 0 1-1.04-0.36zm2.1 0.67a18.4 18.4 0 0 0 1.07 0.23l0.56-2.94a15.4 15.4 0 0 1-0.9-0.2l-0.72 2.91zm2.18 0.41a18.57 18.57 0 0 0 1.08 0.1l0.2-2.99a15.57 15.57 0 0 1-0.9-0.09l-0.38 2.98zm2.2 0.15H22v-3h-0.13l-0.03 3z"/>
+    </group>
 </vector>
diff --git a/core/res/res/drawable-nodpi/platlogo_m.xml b/core/res/res/drawable-nodpi/platlogo_m.xml
index d9a558d..aacf674 100644
--- a/core/res/res/drawable-nodpi/platlogo_m.xml
+++ b/core/res/res/drawable-nodpi/platlogo_m.xml
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2016 The Android Open Source Project
+Copyright (C) 2017 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.
@@ -14,59 +14,27 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="48dp"
-        android:height="48dp"
+        android:width="480dp"
+        android:height="480dp"
         android:viewportWidth="48.0"
         android:viewportHeight="48.0">
+    <!--<path
+        android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0"
+        android:fillAlpha="0.066"
+        android:fillColor="#000000"/>-->
     <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M33.8,38l-25.5,-25.5l-1.7000003,0.6999998l25.499998,25.5z"/>
+        android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0"
+        android:fillColor="#FFC107"/>
     <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M40.8,34.8l-25.4,-25.5l-1.6999998,0.6999998l25.5,25.5z"/>
+        android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z"
+        android:fillColor="#FE9F00"/>
     <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M11.1,13.1l-0.3,-0.4l1.1,-1.3l0,0l-1.6,0.8l-0.4,-0.4l2.6,-1.2l0.4,0.4l-1.1,1.3l0,0l1.6,-0.8l0.3,0.4L11.1,13.1z"/>
+        android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0"
+        android:fillColor="#FED44F"/>
     <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M13,14.2l-0.5,-0.5l-0.6,0.2l-0.4,-0.4l3.1,-0.7l0.4,0.4l-2.1,1.7l-0.4,-0.4L13,14.2z M13,13.6l0.3,0.3l0.8,-0.6   l0,0L13,13.6z"/>
+        android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z"
+        android:fillColor="#FFC107"/>
     <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M16.3,14.6l-1.6,1.2l0,0l2.2,-0.6l0.5,0.5l-2.6,1.2l-0.3,-0.4l0.7,-0.3l1,-0.4l0,0l-2.1,0.5L13.9,16l1.4,-1.1l0,0   l-0.9,0.5l-0.7,0.3l-0.3,-0.4l2.6,-1.2L16.3,14.6z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M17.4,17.8l-0.6,-0.6l-0.7,0.3l0.7,0.7l-0.4,0.2l-1,-1l2.6,-1.2l1,1l-0.4,0.2l-0.7,-0.7L17.2,17l0.6,0.6L17.4,17.8   z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M18.8,18.7L18.8,18.7l1.3,-0.2l0.4,0.4l-2.1,0.3l-0.9,0.4l-0.3,-0.4l1,-0.4l1.2,-1.2l0.4,0.4L18.8,18.7z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M22.2,20.5l-1.6,1.2l0,0l2.2,-0.6l0.5,0.5l-2.6,1.2l-0.3,-0.4l0.7,-0.3l1,-0.4l0,0L20,22.1l-0.2,-0.2l1.4,-1.1l0,0   l-0.9,0.5l-0.7,0.3l-0.3,-0.4l2.6,-1.2L22.2,20.5z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M22,23.6c0,0,0.1,0.1,0.2,0.1c0.1,0,0.2,0,0.3,-0.1l0.3,0.3c-0.2,0.1,-0.4,0.1,-0.6,0.1c-0.2,0,-0.4,-0.1,-0.5,-0.2   c-0.2,-0.2,-0.2,-0.3,-0.1,-0.5c0.1,-0.2,0.2,-0.3,0.5,-0.4l0.2,-0.1c0.3,-0.1,0.5,-0.2,0.8,-0.2c0.2,0,0.5,0.1,0.6,0.3   c0.1,0.1,0.2,0.3,0.1,0.4c0,0.1,-0.2,0.3,-0.4,0.4L23,23.4c0.1,0,0.2,-0.1,0.2,-0.2c0,-0.1,0,-0.1,0,-0.2C23.1,23,23,22.9,22.9,23   c-0.1,0,-0.2,0.1,-0.4,0.1l-0.2,0.1c-0.2,0.1,-0.3,0.1,-0.3,0.2C21.9,23.5,21.9,23.5,22,23.6z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M23.8,25.9l-0.3,-0.4l1.1,-1.3l0,0L22.9,25l-0.4,-0.4l2.6,-1.2l0.4,0.4l-1.1,1.3l0,0l1.6,-0.8l0.3,0.4L23.8,25.9z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M25.7,27l-0.5,-0.5l-0.6,0.2l-0.4,-0.4l3.1,-0.7l0.4,0.4l-2.1,1.7l-0.4,-0.4L25.7,27z M25.7,26.4l0.3,0.3l0.8,-0.6   l0,0L25.7,26.4z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M29,27.4l-1.6,1.2l0,0l2.2,-0.6l0.5,0.5l-2.6,1.2l-0.3,-0.4l0.8,-0.3l1,-0.4l0,0L26.8,29l-0.2,-0.2l1.4,-1.1l0,0   L27,28.1l-0.8,0.3l-0.3,-0.4l2.6,-1.2L29,27.4z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M30,30.6L29.5,30l-0.7,0.3l0.7,0.7L29,31.2l-1,-1l2.6,-1.2l1,1l-0.4,0.2l-0.7,-0.7l-0.6,0.3l0.6,0.6L30,30.6z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M31.5,32.1l-0.6,-0.6L29.8,32l-0.4,-0.4l2.6,-1.2l1,1l-0.4,0.2L32,31l-0.7,0.3l0.6,0.6L31.5,32.1z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M32,33.5L31.6,33L31,33.2l-0.4,-0.4l3.1,-0.7l0.4,0.4l-2.1,1.7l-0.4,-0.4L32,33.5z M32.1,32.9l0.3,0.3l0.8,-0.6   l0,0L32.1,32.9z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M34.3,35.3c-0.3,0.1,-0.5,0.2,-0.8,0.1c-0.2,0,-0.5,-0.1,-0.6,-0.3c-0.2,-0.2,-0.2,-0.4,-0.2,-0.5   c0.1,-0.2,0.2,-0.3,0.6,-0.5l0.8,-0.4c0.3,-0.1,0.6,-0.2,0.9,-0.2c0.3,0,0.5,0.1,0.7,0.3c0.2,0.2,0.3,0.4,0.2,0.5c0,0.2,-0.2,0.3,-0.5,0.5   l-0.3,-0.4c0.2,-0.1,0.3,-0.1,0.3,-0.2c0,-0.1,0,-0.1,-0.1,-0.2C35,34,34.9,34,34.8,34c-0.1,0,-0.3,0,-0.5,0.1l-0.8,0.4   c-0.2,0.1,-0.3,0.2,-0.4,0.2c0,0.1,0,0.1,0,0.2c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.4,-0.1L34.3,35.3z"/>
-    <path
-        android:fillColor="#FFFFFFFF"
-        android:pathData="M36,36.6L35.4,36l-0.7,0.3l0.7,0.7l-0.4,0.2l-1,-1l2.6,-1.2l1,1l-0.4,0.2l-0.7,-0.7l-0.6,0.3l0.6,0.6L36,36.6z"/>
+        android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0"
+        android:fillColor="#FFFFFF"/>
 </vector>
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 89e42e6..2e2b395 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
 Copyright (C) 2017 The Android Open Source Project
 
@@ -14,19 +15,23 @@
     limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M12.0,12.0m-10.0,0.0a10.0,10.0,0,1,1,20.0,0.0a10.0,10.0,0,1,1,-20.0,0.0"
-        android:fillAlpha="0.25"/>
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 Z M12,18.5 C15.5898509,18.5 18.5,15.5898509 18.5,12 C18.5,8.41014913 15.5898509,5.5 12,5.5 C8.41014913,5.5 5.5,8.41014913 5.5,12 C5.5,15.5898509 8.41014913,18.5 12,18.5 Z"/>
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M12,18.5 C8.41014913,18.5 5.5,15.5898509 5.5,12 C5.5,8.41014913 8.41014913,5.5 12,5.5 C15.5898509,5.5 18.5,8.41014913 18.5,12 C18.5,15.5898509 15.5898509,18.5 12,18.5 Z M12,15 C13.6568542,15 15,13.6568542 15,12 C15,10.3431458 13.6568542,9 12,9 C10.3431458,9 9,10.3431458 9,12 C9,13.6568542 10.3431458,15 12,15 Z"
-        android:fillAlpha="0.25"/>
-</vector>
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <group>
+        <path
+            android:fillColor="#FFFFFF"
+            android:fillAlpha=".33"
+            android:fillType="evenOdd"
+            android:pathData="M5.71 18.29A8.99 8.99 0 0 0 22 13c0-3-1.46-5.65-3.71-7.29A8.99 8.99 0 0 0 2 11c0 3 1.46 5.65 3.71 7.29z"/>
+        <path
+            android:fillColor="#FFFFFF"
+            android:fillType="evenOdd"
+            android:pathData="M7.25 19.18A8.5 8.5 0 0 0 19.19 7.24 9 9 0 0 1 7.24 19.19z"/>
+        <path
+            android:fillColor="#FFFFFF"
+            android:fillAlpha=".33"
+            android:pathData="M10.5 3a0.5 0.5 0 1 1 1 0v2.05a0.5 0.5 0 1 1-1 0V3zm3.1 0.42a0.5 0.5 0 0 1 0.93 0.39l-0.8 1.88A0.5 0.5 0 1 1 12.8 5.3l0.8-1.88zm2.7 1.57a0.5 0.5 0 1 1 0.71 0.7l-1.45 1.46a0.5 0.5 0 0 1-0.7-0.71l1.44-1.45zm1.9 2.5a0.5 0.5 0 0 1 0.38 0.92l-1.9 0.77a0.5 0.5 0 0 1-0.37-0.93l1.9-0.77zM19 10.5a0.5 0.5 0 1 1 0 1h-2.05a0.5 0.5 0 0 1 0-1H19zm-0.42 3.1a0.5 0.5 0 0 1-0.39 0.93l-1.88-0.8a0.5 0.5 0 1 1 0.39-0.92l1.88 0.8zm-1.57 2.7a0.5 0.5 0 1 1-0.7 0.71l-1.46-1.45a0.5 0.5 0 0 1 0.71-0.7l1.45 1.44zm-2.5 1.9a0.5 0.5 0 1 1-0.92 0.38l-0.77-1.9a0.5 0.5 0 0 1 0.93-0.37l0.77 1.9zM11.5 19a0.5 0.5 0 1 1-1 0v-2.05a0.5 0.5 0 0 1 1 0V19zm-3.1-0.42a0.5 0.5 0 0 1-0.93-0.39l0.8-1.88A0.5 0.5 0 0 1 9.2 16.7l-0.8 1.88zm-2.7-1.57a0.5 0.5 0 1 1-0.71-0.7l1.45-1.46a0.5 0.5 0 0 1 0.7 0.71L5.7 17.01zm-1.9-2.48a0.5 0.5 0 0 1-0.38-0.92l1.88-0.8a0.5 0.5 0 0 1 0.4 0.92l-1.9 0.8zM3 11.5a0.5 0.5 0 1 1 0-1h2.05a0.5 0.5 0 1 1 0 1H3zm0.42-3.1A0.5 0.5 0 0 1 3.8 7.46l1.88 0.8A0.5 0.5 0 1 1 5.3 9.2L3.42 8.4zm1.57-2.7a0.5 0.5 0 1 1 0.7-0.71l1.46 1.45a0.5 0.5 0 0 1-0.71 0.7L4.99 5.7zm2.5-1.9A0.5 0.5 0 0 1 8.4 3.41l0.77 1.9a0.5 0.5 0 0 1-0.93 0.37L7.48 3.8z"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 15d593b..105ef44 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1891,6 +1891,7 @@
         <enum name="KEYCODE_SYSTEM_NAVIGATION_DOWN" value="281" />
         <enum name="KEYCODE_SYSTEM_NAVIGATION_LEFT" value="282" />
         <enum name="KEYCODE_SYSTEM_NAVIGATION_RIGHT" value="283" />
+        <enum name="KEYCODE_ALL_APPS" value="284" />
     </attr>
 
     <!-- ***************************************************************** -->
@@ -2096,16 +2097,15 @@
              -->
         <attr name="windowSplashscreenContent" format="reference" />
 
-        <!-- @hide
-             If set, the navigation bar will be drawn such that it is compatible with a light
-             navigation bar background.
+        <!-- @hide If set, the navigation bar will be drawn such that it is
+             compatible with a light navigation bar background.
              <p>For this to take effect, the window must be drawing the system bar backgrounds with
              {@link android.R.attr#windowDrawsSystemBarBackgrounds} and the navigation bar must not
              have been requested to be translucent with
              {@link android.R.attr#windowTranslucentNavigation}.
              Corresponds to setting {@link android.view.View#SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR} on
              the decor view. -->
-        <attr name="windowLightNavigationBar" format="boolean" />
+         <attr name="windowLightNavigationBar" format="boolean" />
     </declare-styleable>
 
     <!-- The set of attributes that describe a AlertDialog's theme. -->
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
index c707240..a8d5164 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
@@ -275,6 +275,21 @@
     }
 
     @Test
+    public void testStepLoop() {
+        openTuner();
+
+        for (int i = 0; i < 10; i++) {
+            Log.d(TAG, "step loop iteration " + (i + 1));
+
+            int ret = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, true);
+            assertEquals(RadioManager.STATUS_OK, ret);
+            verify(mCallback, timeout(kTuneCallbackTimeoutMs)).onProgramInfoChanged(any());
+
+            resetCallback();
+        }
+    }
+
+    @Test
     public void testTuneAndGetPI() {
         openTuner();
 
diff --git a/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java b/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java
index c9db034..6156692 100644
--- a/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java
+++ b/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java
@@ -23,6 +23,7 @@
         TestSuite suite =
           new TestSuite(NewDatabasePerformanceTestSuite.class.getName());
 
+        suite.addTestSuite(NewDatabasePerformanceTests.CreateTable100.class);
         suite.addTestSuite(NewDatabasePerformanceTests.Insert100.class);
         suite.addTestSuite(NewDatabasePerformanceTests.InsertIndexed100.class);
         suite.addTestSuite(NewDatabasePerformanceTests.Select100.class);
diff --git a/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java b/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java
index f8d8e5e..bcd9060 100644
--- a/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java
+++ b/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java
@@ -34,8 +34,9 @@
 public class NewDatabasePerformanceTests {
     private static final String TAG = "NewDatabasePerformanceTests";
 
-    // Edit this to change the test run times.  The original is 100.
-    private static final int SIZE_MULTIPLIER = 100;
+    private static final int DATASET_SIZE = 100; // Size of dataset to use for testing
+    private static final int FAST_OP_MULTIPLIER = 25;
+    private static final int FAST_OP_COUNT = FAST_OP_MULTIPLIER * DATASET_SIZE;
 
     public static class PerformanceBase extends TestCase
     implements PerformanceTestCase {
@@ -50,16 +51,21 @@
             if (mDatabaseFile.exists()) {
                 mDatabaseFile.delete();
             }
-            mDatabase =
-                    SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(),
-                            null);
+            mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
             assertTrue(mDatabase != null);
             mDatabase.setVersion(CURRENT_DATABASE_VERSION);
+            mDatabase.beginTransactionNonExclusive();
+            prepareForTest();
+            mDatabase.setTransactionSuccessful();
+            mDatabase.endTransaction();
             mSetupFinishedTime = System.currentTimeMillis();
             Log.i(TAG, "Setup for " + getClass().getSimpleName() + " took "
                     + (mSetupFinishedTime - setupStarted) + " ms");
         }
 
+        protected void prepareForTest() {
+        }
+
         public void tearDown() {
             long duration = System.currentTimeMillis() - mSetupFinishedTime;
             Log.i(TAG, "Test " + getClass().getSimpleName() + " took " + duration + " ms");
@@ -76,7 +82,7 @@
             return 0;
         }
 
-        public String numberName(int number) {
+        String numberName(int number) {
             String result = "";
 
             if (number >= 1000) {
@@ -106,36 +112,50 @@
 
             return result;
         }
+
+        void checkCursor(Cursor c) {
+            c.getColumnCount();
+            c.close();
+        }
+    }
+
+    /**
+     * Test CREATE SIZE tables with 1 row.
+     */
+    public static class CreateTable100 extends PerformanceBase {
+        public void testRun() {
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                String t = "t" + i;
+                mDatabase.execSQL("CREATE TABLE " + t + "(a INTEGER, b INTEGER, c VARCHAR(100))");
+                mDatabase.execSQL("INSERT INTO " + t + " VALUES(" + i + "," + i + ",'"
+                        + numberName(i) + "')");
+            }
+        }
     }
 
     /**
      * Test 100 inserts.
      */
-
     public static class Insert100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-
-        private String[] statements = new String[SIZE];
+        private String[] mStatements = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
-                statements[i] =
+                mStatements[i] =
                         "INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                                 + numberName(r) + "')";
             }
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
         }
 
         public void testRun() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.execSQL(statements[i]);
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                mDatabase.execSQL(mStatements[i]);
             }
         }
     }
@@ -145,30 +165,25 @@
      */
 
     public static class InsertIndexed100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-
-        private String[] statements = new String[SIZE];
+        private String[] mStatements = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
-                statements[i] =
-                        "INSERT INTO t1 VALUES(" + i + "," + r + ",'"
-                                + numberName(r) + "')";
+                mStatements[i] = "INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+                        + numberName(r) + "')";
             }
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
             mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
         }
 
         public void testRun() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.execSQL(statements[i]);
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                mDatabase.execSQL(mStatements[i]);
             }
         }
     }
@@ -176,41 +191,35 @@
     /**
      * 100 SELECTs without an index
      */
-
     public static class Select100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"count(*)", "avg(b)"};
 
-        private String[] where = new String[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
             mDatabase
             .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int lower = i * 100;
                 int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
+                mWhere[i] = "b >= " + lower + " AND b < " + upper;
             }
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase
-                            .query("t1", COLUMNS, where[i], null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase
+                        .query("t1", COLUMNS, mWhere[i % DATASET_SIZE], null, null, null, null));
             }
         }
     }
@@ -219,37 +228,32 @@
      * 100 SELECTs on a string comparison
      */
     public static class SelectStringComparison100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"count(*)", "avg(b)"};
 
-        private String[] where = new String[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
             mDatabase
             .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
-                where[i] = "c LIKE '" + numberName(i) + "'";
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                mWhere[i] = "c LIKE '" + numberName(i) + "'";
             }
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase
-                            .query("t1", COLUMNS, where[i], null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase
+                        .query("t1", COLUMNS, mWhere[i % DATASET_SIZE], null, null, null, null));
             }
         }
     }
@@ -258,40 +262,35 @@
      * 100 SELECTs with an index
      */
     public static class SelectIndex100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
+        private static final int TABLE_SIZE = 100;
         private static final String[] COLUMNS = {"count(*)", "avg(b)"};
 
-        private String[] where = new String[SIZE];
+        private String[] mWhere = new String[TABLE_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
             mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < TABLE_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < TABLE_SIZE; i++) {
                 int lower = i * 100;
                 int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
+                mWhere[i] = "b >= " + lower + " AND b < " + upper;
             }
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase
-                            .query("t1", COLUMNS, where[i], null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase
+                        .query("t1", COLUMNS, mWhere[i % TABLE_SIZE], null, null, null, null));
             }
         }
     }
@@ -300,27 +299,22 @@
      *  INNER JOIN without an index
      */
     public static class InnerJoin100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"t1.a"};
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase
-            .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
@@ -328,9 +322,9 @@
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
-                        null, null, null, null);
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
+                        null, null, null, null));
             }
         }
     }
@@ -340,29 +334,24 @@
      */
 
     public static class InnerJoinOneSide100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"t1.a"};
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase
-            .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
 
             mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
@@ -370,9 +359,9 @@
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
-                        null, null, null, null);
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
+                        null, null, null, null));
             }
         }
     }
@@ -381,29 +370,24 @@
      *  INNER JOIN without an index on one side
      */
     public static class InnerJoinNoIndex100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"t1.a"};
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase
-            .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
 
             mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
@@ -411,9 +395,10 @@
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                mDatabase.query("t1 INNER JOIN t2 ON t1.c = t2.c", COLUMNS, null,
-                        null, null, null, null);
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase
+                        .query("t1 INNER JOIN t2 ON t1.c = t2.c", COLUMNS, null, null, null, null,
+                                null));
             }
         }
     }
@@ -422,49 +407,44 @@
      *  100 SELECTs with subqueries. Subquery is using an index
      */
     public static class SelectSubQIndex100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"t1.a"};
 
-        private String[] where = new String[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
-            mDatabase
-            .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
 
             mDatabase.execSQL("CREATE INDEX i2b ON t2(b)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int lower = i * 100;
                 int upper = (i + 10) * 100;
-                where[i] =
+                mWhere[i] =
                         "t1.b IN (SELECT t2.b FROM t2 WHERE t2.b >= " + lower
                         + " AND t2.b < " + upper + ")";
             }
         }
 
         public void testRun() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase
-                .query("t1", COLUMNS, where[i], null, null, null, null);
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase
+                        .query("t1", COLUMNS, mWhere[i % DATASET_SIZE], null, null, null, null));
             }
         }
     }
@@ -473,38 +453,32 @@
      *  100 SELECTs on string comparison with Index
      */
     public static class SelectIndexStringComparison100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"count(*)", "avg(b)"};
 
-        private String[] where = new String[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
             mDatabase.execSQL("CREATE INDEX i3c ON t1(c)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
-                where[i] = "c LIKE '" + numberName(i) + "'";
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                mWhere[i] = "c LIKE '" + numberName(i) + "'";
             }
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase
-                            .query("t1", COLUMNS, where[i], null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase
+                        .query("t1", COLUMNS, mWhere[i % DATASET_SIZE], null, null, null, null));
             }
         }
     }
@@ -513,19 +487,15 @@
      *  100 SELECTs on integer
      */
     public static class SelectInteger100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"b"};
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
@@ -534,10 +504,8 @@
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase.query("t1", COLUMNS, null, null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase.query("t1", COLUMNS, null, null, null, null, null));
             }
         }
     }
@@ -546,19 +514,16 @@
      *  100 SELECTs on String
      */
     public static class SelectString100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"c"};
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
             mDatabase
             .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
@@ -566,10 +531,8 @@
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase.query("t1", COLUMNS, null, null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                mDatabase.query("t1", COLUMNS, null, null, null, null, null);
             }
         }
     }
@@ -578,20 +541,17 @@
      *  100 SELECTs on integer with index
      */
     public static class SelectIntegerIndex100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"b"};
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
             mDatabase
             .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
             mDatabase.execSQL("CREATE INDEX i1b on t1(b)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
@@ -600,10 +560,8 @@
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase.query("t1", COLUMNS, null, null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                mDatabase.query("t1", COLUMNS, null, null, null, null, null);
             }
         }
     }
@@ -612,20 +570,16 @@
      *  100 SELECTs on String with index
      */
     public static class SelectIndexString100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"c"};
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
             mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
@@ -633,10 +587,8 @@
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase.query("t1", COLUMNS, null, null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase.query("t1", COLUMNS, null, null, null, null, null));
             }
         }
 
@@ -646,40 +598,33 @@
      *  100 SELECTs on String with starts with
      */
     public static class SelectStringStartsWith100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"c"};
-        private String[] where = new String[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
             mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
-                where[i] = "c LIKE '" + numberName(r).substring(0, 1) + "*'";
+                mWhere[i] = "c LIKE '" + numberName(r).substring(0, 1) + "*'";
 
             }
 
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase
-                            .query("t1", COLUMNS, where[i], null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                mDatabase.query("t1", COLUMNS, mWhere[i % DATASET_SIZE], null, null, null, null);
             }
         }
     }
@@ -688,18 +633,15 @@
      *  100 Deletes on an indexed table
      */
     public static class DeleteIndexed100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
             mDatabase.execSQL("CREATE INDEX i3c ON t1(c)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
@@ -708,7 +650,7 @@
         }
 
         public void testRun() {
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 mDatabase.delete("t1", null, null);
             }
         }
@@ -718,17 +660,13 @@
      *  100 Deletes
      */
     public static class Delete100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
@@ -737,7 +675,7 @@
         }
 
         public void testRun() {
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 mDatabase.delete("t1", null, null);
             }
         }
@@ -747,33 +685,30 @@
      *  100 DELETE's without an index with where clause
      */
     public static class DeleteWhere100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private String[] where = new String[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int lower = i * 100;
                 int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
+                mWhere[i] = "b >= " + lower + " AND b < " + upper;
             }
         }
 
         public void testRun() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.delete("t1", where[i], null);
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                mDatabase.delete("t1", mWhere[i], null);
             }
         }
     }
@@ -782,34 +717,31 @@
      * 100 DELETE's with an index with where clause
      */
     public static class DeleteIndexWhere100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private String[] where = new String[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
             mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int lower = i * 100;
                 int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
+                mWhere[i] = "b >= " + lower + " AND b < " + upper;
             }
         }
 
         public void testRun() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.delete("t1", where[i], null);
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                mDatabase.delete("t1", mWhere[i], null);
             }
         }
     }
@@ -818,30 +750,26 @@
      * 100 update's with an index with where clause
      */
     public static class UpdateIndexWhere100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private String[] where = new String[SIZE];
-        ContentValues[] mValues = new ContentValues[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
+        ContentValues[] mValues = new ContentValues[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
             mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
-
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int lower = i * 100;
                 int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
+                mWhere[i] = "b >= " + lower + " AND b < " + upper;
                 ContentValues b = new ContentValues(1);
                 b.put("b", upper);
                 mValues[i] = b;
@@ -850,8 +778,8 @@
         }
 
         public void testRun() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.update("t1", mValues[i], where[i], null);
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                mDatabase.update("t1", mValues[i], mWhere[i], null);
             }
         }
     }
@@ -860,29 +788,26 @@
      * 100 update's without an index with where clause
      */
     public static class UpdateWhere100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private String[] where = new String[SIZE];
-        ContentValues[] mValues = new ContentValues[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
+        ContentValues[] mValues = new ContentValues[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
 
                 int lower = i * 100;
                 int upper = (i + 10) * 100;
-                where[i] = "b >= " + lower + " AND b < " + upper;
+                mWhere[i] = "b >= " + lower + " AND b < " + upper;
                 ContentValues b = new ContentValues(1);
                 b.put("b", upper);
                 mValues[i] = b;
@@ -890,8 +815,8 @@
         }
 
         public void testRun() {
-            for (int i = 0; i < SIZE; i++) {
-                mDatabase.update("t1", mValues[i], where[i], null);
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                mDatabase.update("t1", mValues[i], mWhere[i], null);
             }
         }
     }
@@ -900,36 +825,31 @@
      * 100 selects for a String - contains 'e'
      */
     public static class SelectStringContains100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"t3.a"};
-        private String[] where = new String[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t3(a VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t3(a VARCHAR(100))");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t3 VALUES('"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
-                where[i] = "a LIKE '*e*'";
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                mWhere[i] = "a LIKE '*e*'";
 
             }
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase
+                        .query("t3", COLUMNS, mWhere[i % DATASET_SIZE], null, null, null, null));
             }
         }
     }
@@ -938,37 +858,32 @@
      * 100 selects for a String - contains 'e'-indexed table
      */
     public static class SelectStringIndexedContains100 extends PerformanceBase {
-        private static final int SIZE = SIZE_MULTIPLIER;
-        private static final int REPEAT_COUNT = 10;
         private static final String[] COLUMNS = {"t3.a"};
-        private String[] where = new String[SIZE];
+        private String[] mWhere = new String[DATASET_SIZE];
 
         @Override
-        public void setUp() {
-            super.setUp();
+        protected void prepareForTest() {
             Random random = new Random(42);
 
-            mDatabase
-            .execSQL("CREATE TABLE t3(a VARCHAR(100))");
+            mDatabase.execSQL("CREATE TABLE t3(a VARCHAR(100))");
             mDatabase.execSQL("CREATE INDEX i3a ON t3(a)");
 
-            for (int i = 0; i < SIZE; i++) {
+            for (int i = 0; i < DATASET_SIZE; i++) {
                 int r = random.nextInt(100000);
                 mDatabase.execSQL("INSERT INTO t3 VALUES('"
                         + numberName(r) + "')");
             }
 
-            for (int i = 0; i < SIZE; i++) {
-                where[i] = "a LIKE '*e*'";
+            for (int i = 0; i < DATASET_SIZE; i++) {
+                mWhere[i] = "a LIKE '*e*'";
 
             }
         }
 
         public void testRun() {
-            for (int iter = 0; iter < REPEAT_COUNT; iter++) {
-                for (int i = 0; i < SIZE; i++) {
-                    mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
-                }
+            for (int i = 0; i < FAST_OP_COUNT; i++) {
+                checkCursor(mDatabase
+                        .query("t3", COLUMNS, mWhere[i % DATASET_SIZE], null, null, null, null));
             }
         }
     }
diff --git a/core/tests/coretests/src/android/database/run_newdb_perf_test.sh b/core/tests/coretests/src/android/database/run_newdb_perf_test.sh
index 10a62e6..c5b2c97 100755
--- a/core/tests/coretests/src/android/database/run_newdb_perf_test.sh
+++ b/core/tests/coretests/src/android/database/run_newdb_perf_test.sh
@@ -17,8 +17,11 @@
 adb install -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
 adb logcat -c
 
-echo "Running benchmark 5 times"
-for i in {1..5}
+# by default run 5 times
+RUN_N=${1:-5}
+echo "Running benchmark $RUN_N times"
+
+for (( i=0; i<$RUN_N; i++ ))
 do
     adb  shell am instrument -e class 'android.database.NewDatabasePerformanceTestSuite' -w 'com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner'
 done
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index d5f6321..9686dd9 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -24,10 +24,15 @@
 import android.system.OsConstants;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.test.suitebuilder.annotation.Suppress;
+import android.util.ArraySet;
+
 import junit.framework.TestCase;
 
 import java.net.InetAddress;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
 
 
 public class LinkPropertiesTest extends TestCase {
@@ -678,4 +683,76 @@
         stacked.addRoute(new RouteInfo((IpPrefix) null, stackedAddress));
         assertTrue(v6lp.isReachable(DNS1));
     }
+
+    @SmallTest
+    public void testLinkPropertiesEnsureDirectlyConnectedRoutes() {
+        // IPv4 case: no route added initially
+        LinkProperties rmnet0 = new LinkProperties();
+        rmnet0.setInterfaceName("rmnet0");
+        rmnet0.addLinkAddress(new LinkAddress("10.0.0.2/8"));
+        RouteInfo directRoute0 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
+                rmnet0.getInterfaceName());
+
+        // Since no routes is added explicitly, getAllRoutes() should return empty.
+        assertTrue(rmnet0.getAllRoutes().isEmpty());
+        rmnet0.ensureDirectlyConnectedRoutes();
+        // ensureDirectlyConnectedRoutes() should have added the missing local route.
+        assertEqualRoutes(Collections.singletonList(directRoute0), rmnet0.getAllRoutes());
+
+        // IPv4 case: both direct and default routes added initially
+        LinkProperties rmnet1 = new LinkProperties();
+        rmnet1.setInterfaceName("rmnet1");
+        rmnet1.addLinkAddress(new LinkAddress("10.0.0.3/8"));
+        RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null,
+                NetworkUtils.numericToInetAddress("10.0.0.1"), rmnet1.getInterfaceName());
+        RouteInfo directRoute1 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
+                rmnet1.getInterfaceName());
+        rmnet1.addRoute(defaultRoute1);
+        rmnet1.addRoute(directRoute1);
+
+        // Check added routes
+        assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes());
+        // ensureDirectlyConnectedRoutes() shouldn't change the routes since direct connected
+        // route is already part of the configuration.
+        rmnet1.ensureDirectlyConnectedRoutes();
+        assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes());
+
+        // IPv6 case: only default routes added initially
+        LinkProperties rmnet2 = new LinkProperties();
+        rmnet2.setInterfaceName("rmnet2");
+        rmnet2.addLinkAddress(new LinkAddress("fe80::cafe/64"));
+        rmnet2.addLinkAddress(new LinkAddress("2001:db8::2/64"));
+        RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null,
+                NetworkUtils.numericToInetAddress("2001:db8::1"), rmnet2.getInterfaceName());
+        RouteInfo directRoute2 = new RouteInfo(new IpPrefix("2001:db8::/64"), null,
+                rmnet2.getInterfaceName());
+        RouteInfo linkLocalRoute2 = new RouteInfo(new IpPrefix("fe80::/64"), null,
+                rmnet2.getInterfaceName());
+        rmnet2.addRoute(defaultRoute2);
+
+        assertEqualRoutes(Arrays.asList(defaultRoute2), rmnet2.getAllRoutes());
+        rmnet2.ensureDirectlyConnectedRoutes();
+        assertEqualRoutes(Arrays.asList(defaultRoute2, directRoute2, linkLocalRoute2),
+                rmnet2.getAllRoutes());
+
+        // Corner case: no interface name
+        LinkProperties rmnet3 = new LinkProperties();
+        rmnet3.addLinkAddress(new LinkAddress("192.168.0.2/24"));
+        RouteInfo directRoute3 = new RouteInfo(new IpPrefix("192.168.0.0/24"), null,
+                rmnet3.getInterfaceName());
+
+        assertTrue(rmnet3.getAllRoutes().isEmpty());
+        rmnet3.ensureDirectlyConnectedRoutes();
+        assertEqualRoutes(Collections.singletonList(directRoute3), rmnet3.getAllRoutes());
+
+    }
+
+    private void assertEqualRoutes(Collection<RouteInfo> expected, Collection<RouteInfo> actual) {
+        Set<RouteInfo> expectedSet = new ArraySet<>(expected);
+        Set<RouteInfo> actualSet = new ArraySet<>(actual);
+        // Duplicated entries in actual routes are considered failures
+        assertEquals(actual.size(), actualSet.size());
+
+        assertEquals(expectedSet, actualSet);
+    }
 }
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index ef2950b..0e6b31e 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -94,11 +94,10 @@
 
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
 
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
 # Make sure to run droiddoc first to generate the stub source files.
-$(full_classes_compiled_jar) : $(legacy_test_api_gen_stamp)
-$(full_classes_jack) : $(legacy_test_api_gen_stamp)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Archive a copy of the classes.jar in SDK build.
 $(call dist-for-goals,sdk win_sdk,$(full_classes_jar):legacy.test.stubs.jar)
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 2d008c7..022198b 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -137,7 +137,7 @@
     mLooper->start(
             false,      // runOnCallingThread
             true,       // canCallJava
-            PRIORITY_FOREGROUND);
+            ANDROID_PRIORITY_VIDEO);
 
     if (nameIsType) {
         mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index fcb861c..cf5882f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -271,7 +271,7 @@
          * @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
          */
         @Override
-        public void onRepeatingRequestError(long lastFrameNumber) {
+        public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
             // TODO Auto-generated method stub
         }
     }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 476f016..e628b68 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -161,7 +161,7 @@
          * @see android.hardware.camera2.ICameraDeviceCallbacks#onRepeatingRequestError()
          */
         @Override
-        public void onRepeatingRequestError(long lastFrameNumber) {
+        public void onRepeatingRequestError(long lastFrameNumber, int repeatingRequestId) {
             // TODO Auto-generated method stub
         }
     }
diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml
index 14861c26..172490d 100644
--- a/packages/EasterEgg/AndroidManifest.xml
+++ b/packages/EasterEgg/AndroidManifest.xml
@@ -84,5 +84,16 @@
                 <action android:name="android.service.quicksettings.action.QS_TILE" />
             </intent-filter>
         </service>
+
+        <!-- FileProvider for sending pictures -->
+        <provider
+                android:name="android.support.v4.content.FileProvider"
+                android:authorities="com.android.egg.fileprovider"
+                android:grantUriPermissions="true"
+                android:exported="false">
+            <meta-data
+                    android:name="android.support.FILE_PROVIDER_PATHS"
+                    android:resource="@xml/filepaths" />
+        </provider>
     </application>
 </manifest>
diff --git a/packages/EasterEgg/res/drawable/food_cookie.xml b/packages/EasterEgg/res/drawable/food_cookie.xml
new file mode 100644
index 0000000..74dd134
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/food_cookie.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2017 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="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <group>
+        <path
+            android:fillColor="#55FFFFFF"
+            android:fillType="evenOdd"
+            android:pathData="M5.71 18.29A8.99 8.99 0 0 0 22 13c0-3-1.46-5.65-3.71-7.29A8.99 8.99 0 0 0 2 11c0 3 1.46 5.65 3.71 7.29z"/>
+        <path
+            android:fillColor="#FFFFFFFF"
+            android:fillType="evenOdd"
+            android:pathData="M7.25 19.18A8.5 8.5 0 0 0 19.19 7.24 9 9 0 0 1 7.24 19.19z"/>
+        <path
+            android:fillColor="#55FFFFFF"
+            android:pathData="M10.5 3a0.5 0.5 0 1 1 1 0v2.05a0.5 0.5 0 1 1-1 0V3zm3.1 0.42a0.5 0.5 0 0 1 0.93 0.39l-0.8 1.88A0.5 0.5 0 1 1 12.8 5.3l0.8-1.88zm2.7 1.57a0.5 0.5 0 1 1 0.71 0.7l-1.45 1.46a0.5 0.5 0 0 1-0.7-0.71l1.44-1.45zm1.9 2.5a0.5 0.5 0 0 1 0.38 0.92l-1.9 0.77a0.5 0.5 0 0 1-0.37-0.93l1.9-0.77zM19 10.5a0.5 0.5 0 1 1 0 1h-2.05a0.5 0.5 0 0 1 0-1H19zm-0.42 3.1a0.5 0.5 0 0 1-0.39 0.93l-1.88-0.8a0.5 0.5 0 1 1 0.39-0.92l1.88 0.8zm-1.57 2.7a0.5 0.5 0 1 1-0.7 0.71l-1.46-1.45a0.5 0.5 0 0 1 0.71-0.7l1.45 1.44zm-2.5 1.9a0.5 0.5 0 1 1-0.92 0.38l-0.77-1.9a0.5 0.5 0 0 1 0.93-0.37l0.77 1.9zM11.5 19a0.5 0.5 0 1 1-1 0v-2.05a0.5 0.5 0 0 1 1 0V19zm-3.1-0.42a0.5 0.5 0 0 1-0.93-0.39l0.8-1.88A0.5 0.5 0 0 1 9.2 16.7l-0.8 1.88zm-2.7-1.57a0.5 0.5 0 1 1-0.71-0.7l1.45-1.46a0.5 0.5 0 0 1 0.7 0.71L5.7 17.01zm-1.9-2.48a0.5 0.5 0 0 1-0.38-0.92l1.88-0.8a0.5 0.5 0 0 1 0.4 0.92l-1.9 0.8zM3 11.5a0.5 0.5 0 1 1 0-1h2.05a0.5 0.5 0 1 1 0 1H3zm0.42-3.1A0.5 0.5 0 0 1 3.8 7.46l1.88 0.8A0.5 0.5 0 1 1 5.3 9.2L3.42 8.4zm1.57-2.7a0.5 0.5 0 1 1 0.7-0.71l1.46 1.45a0.5 0.5 0 0 1-0.71 0.7L4.99 5.7zm2.5-1.9A0.5 0.5 0 0 1 8.4 3.41l0.77 1.9a0.5 0.5 0 0 1-0.93 0.37L7.48 3.8z"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/packages/EasterEgg/res/values/strings.xml b/packages/EasterEgg/res/values/strings.xml
index 8478a43..61e3834 100644
--- a/packages/EasterEgg/res/values/strings.xml
+++ b/packages/EasterEgg/res/values/strings.xml
@@ -17,6 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
     <string name="app_name" translatable="false">Android Easter Egg</string>
     <string name="notification_name" translatable="false">Android Neko</string>
+    <string name="notification_channel_name" translatable="false">New cats</string>
     <string name="default_tile_name" translatable="false">\????</string>
     <string name="notification_title" translatable="false">A cat is here.</string>
     <string name="default_cat_name" translatable="false">Cat #%s</string>
@@ -34,7 +35,7 @@
         <item>@drawable/food_bits</item>
         <item>@drawable/food_sysuituna</item>
         <item>@drawable/food_chicken</item>
-        <item>@drawable/food_donut</item>
+        <item>@drawable/food_cookie</item>
     </array>
     <integer-array name="food_intervals">
         <item>0</item>
diff --git a/packages/EasterEgg/res/xml/filepaths.xml b/packages/EasterEgg/res/xml/filepaths.xml
new file mode 100644
index 0000000..2130025
--- /dev/null
+++ b/packages/EasterEgg/res/xml/filepaths.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2017 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.
+-->
+<paths>
+    <external-path name="cats" path="Pictures/Cats" />
+</paths>
\ No newline at end of file
diff --git a/packages/EasterEgg/src/com/android/egg/neko/Cat.java b/packages/EasterEgg/src/com/android/egg/neko/Cat.java
index a4df372..dd1bd07 100644
--- a/packages/EasterEgg/src/com/android/egg/neko/Cat.java
+++ b/packages/EasterEgg/src/com/android/egg/neko/Cat.java
@@ -31,6 +31,8 @@
 import com.android.egg.R;
 import com.android.internal.logging.MetricsLogger;
 
+import static com.android.egg.neko.NekoLand.CHAN_ID;
+
 public class Cat extends Drawable {
     public static final long[] PURR = {0, 40, 20, 40, 20, 40, 20, 40, 20, 40, 20, 40};
 
@@ -218,6 +220,7 @@
                 .setContentText(getName())
                 .setContentIntent(PendingIntent.getActivity(context, 0, intent, 0))
                 .setAutoCancel(true)
+                .setChannel(CHAN_ID)
                 .setVibrate(PURR)
                 .addExtras(extras);
     }
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoLand.java b/packages/EasterEgg/src/com/android/egg/neko/NekoLand.java
index 689e381..d2e37d8 100644
--- a/packages/EasterEgg/src/com/android/egg/neko/NekoLand.java
+++ b/packages/EasterEgg/src/com/android/egg/neko/NekoLand.java
@@ -31,6 +31,7 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.provider.MediaStore.Images;
+import android.support.v4.content.FileProvider;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
@@ -56,6 +57,8 @@
 import java.util.List;
 
 public class NekoLand extends Activity implements PrefsListener {
+    public static String CHAN_ID = "EGG";
+
     public static boolean DEBUG = false;
     public static boolean DEBUG_NOTIFICATIONS = false;
 
@@ -289,10 +292,13 @@
                         new String[] {png.toString()},
                         new String[] {"image/png"},
                         null);
-                Uri uri = Uri.fromFile(png);
+                Log.v("Neko", "cat file: " + png);
+                Uri uri = FileProvider.getUriForFile(this, "com.android.egg.fileprovider", png);
+                Log.v("Neko", "cat uri: " + uri);
                 Intent intent = new Intent(Intent.ACTION_SEND);
                 intent.putExtra(Intent.EXTRA_STREAM, uri);
                 intent.putExtra(Intent.EXTRA_SUBJECT, cat.getName());
+                intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                 intent.setType("image/png");
                 startActivity(Intent.createChooser(intent, null));
                 cat.logShare(this);
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoService.java b/packages/EasterEgg/src/com/android/egg/neko/NekoService.java
index 808ec36..42506e6 100644
--- a/packages/EasterEgg/src/com/android/egg/neko/NekoService.java
+++ b/packages/EasterEgg/src/com/android/egg/neko/NekoService.java
@@ -15,15 +15,15 @@
 package com.android.egg.neko;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
+import android.net.Uri;
 import android.os.Bundle;
 
 import java.util.List;
@@ -33,6 +33,9 @@
 
 import java.util.Random;
 
+import static com.android.egg.neko.Cat.PURR;
+import static com.android.egg.neko.NekoLand.CHAN_ID;
+
 public class NekoService extends JobService {
 
     private static final String TAG = "NekoService";
@@ -40,6 +43,7 @@
     public static int JOB_ID = 42;
 
     public static int CAT_NOTIFICATION = 1;
+    public static int DEBUG_NOTIFICATION = 1234;
 
     public static float CAT_CAPTURE_PROB = 1.0f; // generous
 
@@ -50,6 +54,18 @@
 
     public static float INTERVAL_JITTER_FRAC = 0.25f;
 
+    private static void setupNotificationChannels(Context context) {
+        NotificationManager noman = context.getSystemService(NotificationManager.class);
+        NotificationChannel eggChan = new NotificationChannel(CHAN_ID,
+                context.getString(R.string.notification_channel_name),
+                NotificationManager.IMPORTANCE_DEFAULT);
+        eggChan.setSound(Uri.EMPTY, Notification.AUDIO_ATTRIBUTES_DEFAULT); // cats are quiet
+        eggChan.setVibrationPattern(PURR); // not totally quiet though
+        eggChan.setBlockableSystem(true); // unlike a real cat, you can push this one off your lap
+        eggChan.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); // cats sit in the window
+        noman.createNotificationChannel(eggChan);
+    }
+
     @Override
     public boolean onStartJob(JobParameters params) {
         Log.v(TAG, "Starting job: " + String.valueOf(params));
@@ -64,8 +80,9 @@
             final Notification.Builder builder
                     = cat.buildNotification(this)
                         .setContentTitle("DEBUG")
+                        .setChannel(NekoLand.CHAN_ID)
                         .setContentText("Ran job: " + params);
-            noman.notify(1, builder.build());
+            noman.notify(DEBUG_NOTIFICATION, builder.build());
         }
 
         final PrefState prefs = new PrefState(this);
@@ -111,6 +128,8 @@
     }
 
     public static void registerJob(Context context, long intervalMinutes) {
+        setupNotificationChannels(context);
+
         JobScheduler jss = context.getSystemService(JobScheduler.class);
         jss.cancel(JOB_ID);
         long interval = intervalMinutes * MINUTES;
@@ -126,12 +145,13 @@
 
         if (NekoLand.DEBUG_NOTIFICATIONS) {
             NotificationManager noman = context.getSystemService(NotificationManager.class);
-            noman.notify(500, new Notification.Builder(context)
+            noman.notify(DEBUG_NOTIFICATION, new Notification.Builder(context)
                     .setSmallIcon(R.drawable.stat_icon)
                     .setContentTitle(String.format("Job scheduled in %d min", (interval / MINUTES)))
                     .setContentText(String.valueOf(jobInfo))
                     .setPriority(Notification.PRIORITY_MIN)
                     .setCategory(Notification.CATEGORY_SERVICE)
+                    .setChannel(NekoLand.CHAN_ID)
                     .setShowWhen(true)
                     .build());
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index bf1bf34..40c2b1f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -339,26 +339,35 @@
         synchronized (mEntriesMap) {
             AppEntry entry = mEntriesMap.get(userId).get(packageName);
             if (entry != null && (entry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
-                mBackgroundHandler.post(() -> {
-                    try {
-                        final StorageStats stats = mStats.queryStatsForPackage(
-                                entry.info.storageUuid, packageName, UserHandle.of(userId));
-                        final PackageStats legacy = new PackageStats(packageName, userId);
-                        legacy.codeSize = stats.getCodeBytes();
-                        legacy.dataSize = stats.getDataBytes();
-                        legacy.cacheSize = stats.getCacheBytes();
-                        try {
-                            mBackgroundHandler.mStatsObserver.onGetStatsCompleted(legacy, true);
-                        } catch (RemoteException ignored) {
-                        }
-                    } catch (NameNotFoundException | IOException e) {
-                        Log.w(TAG, "Failed to query stats: " + e);
-                        try {
-                            mBackgroundHandler.mStatsObserver.onGetStatsCompleted(null, false);
-                        } catch (RemoteException ignored) {
-                        }
-                    }
-                });
+                mBackgroundHandler.post(
+                        () -> {
+                            try {
+                                final StorageStats stats =
+                                        mStats.queryStatsForPackage(
+                                                entry.info.storageUuid,
+                                                packageName,
+                                                UserHandle.of(userId));
+                                final long cacheQuota =
+                                        mStats.getCacheQuotaBytes(
+                                                entry.info.storageUuid.toString(), entry.info.uid);
+                                final PackageStats legacy = new PackageStats(packageName, userId);
+                                legacy.codeSize = stats.getCodeBytes();
+                                legacy.dataSize = stats.getDataBytes();
+                                legacy.cacheSize = Math.min(stats.getCacheBytes(), cacheQuota);
+                                try {
+                                    mBackgroundHandler.mStatsObserver.onGetStatsCompleted(
+                                            legacy, true);
+                                } catch (RemoteException ignored) {
+                                }
+                            } catch (NameNotFoundException | IOException e) {
+                                Log.w(TAG, "Failed to query stats: " + e);
+                                try {
+                                    mBackgroundHandler.mStatsObserver.onGetStatsCompleted(
+                                            null, false);
+                                } catch (RemoteException ignored) {
+                                }
+                            }
+                        });
             }
             if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock");
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 9620a91..3c095ae 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -442,15 +442,6 @@
                     if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
                         int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
                         remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
-                        if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
-                            String uriString = metaData.getString(
-                                    META_DATA_PREFERENCE_SUMMARY_URI);
-                            String overrideSummary = getTextFromUri(context, uriString, providerMap,
-                                    META_DATA_PREFERENCE_SUMMARY);
-                            if (overrideSummary != null) {
-                                remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
-                            }
-                        }
                     }
                 }
             } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
@@ -555,6 +546,30 @@
         }
     }
 
+    public static void updateTileUsingSummaryUri(Context context, Tile tile) {
+        if (tile == null || tile.metaData == null ||
+            !tile.metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
+            return;
+        }
+
+        final Map<String, IContentProvider> providerMap = new HashMap<>();
+
+        final String uriString = tile.metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI);
+        final Bundle bundle = getBundleFromUri(context, uriString, providerMap);
+        final String overrideSummary = getString(bundle, META_DATA_PREFERENCE_SUMMARY);
+        final String overrideTitle = getString(bundle, META_DATA_PREFERENCE_TITLE);
+        if (overrideSummary != null) {
+            tile.remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
+        }
+        if (overrideTitle != null) {
+            tile.remoteViews.setTextViewText(android.R.id.title, overrideTitle);
+        }
+    }
+
+    private static String getString(Bundle bundle, String key) {
+        return bundle == null ? null : bundle.getString(key);
+    }
+
     private static IContentProvider getProviderFromUri(Context context, Uri uri,
             Map<String, IContentProvider> providerMap) {
         if (uri == null) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index e9ca753..7e7fdb2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -22,10 +22,12 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.RuntimeEnvironment.application;
@@ -64,9 +66,6 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.internal.ShadowExtractor;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -75,8 +74,7 @@
 
 @RunWith(RobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH,
-        sdk = TestConfig.SDK_VERSION,
-        shadows = {TileUtilsTest.TileUtilsShadowRemoteViews.class})
+        sdk = TestConfig.SDK_VERSION)
 public class TileUtilsTest {
 
     @Mock
@@ -421,24 +419,12 @@
     }
 
     @Test
-    public void getTilesForIntent_summaryUriSpecified_shouldOverrideRemoteViewSummary()
+    public void updateTileUsingSummaryUri_summaryUriSpecified_shouldOverrideRemoteViewSummary()
             throws RemoteException {
-        Intent intent = new Intent();
-        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
-        List<Tile> outTiles = new ArrayList<>();
-        List<ResolveInfo> info = new ArrayList<>();
-        ResolveInfo resolveInfo = newInfo(true, null /* category */, null,
-                null, URI_GET_SUMMARY);
-        resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
-                R.layout.user_preference);
-        info.add(resolveInfo);
-
-        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
-                .thenReturn(info);
-
         // Mock the content provider interaction.
         Bundle bundle = new Bundle();
-        bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, "new summary text");
+        String expectedSummary = "new summary text";
+        bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, expectedSummary);
         when(mIContentProvider.call(anyString(),
                 eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY),
                 any())).thenReturn(bundle);
@@ -447,57 +433,12 @@
         when(mContentResolver.acquireUnstableProvider(any(Uri.class)))
                 .thenReturn(mIContentProvider);
 
-        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
-                null /* defaultCategory */, outTiles, false /* usePriority */,
-                false /* checkCategory */, true /* forceTintExternalIcon */);
-
-        assertThat(outTiles.size()).isEqualTo(1);
-        Tile tile = outTiles.get(0);
-        assertThat(tile.remoteViews).isNotNull();
-        assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
-        // Make sure the summary TextView got a new text string.
-        TileUtilsShadowRemoteViews shadowRemoteViews =
-                (TileUtilsShadowRemoteViews) ShadowExtractor.extract(tile.remoteViews);
-        assertThat(shadowRemoteViews.overrideViewId).isEqualTo(android.R.id.summary);
-        assertThat(shadowRemoteViews.overrideText).isEqualTo("new summary text");
-    }
-
-    @Test
-    public void getTilesForIntent_providerUnavailable_shouldNotOverrideRemoteViewSummary()
-            throws RemoteException {
-        Intent intent = new Intent();
-        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
-        List<Tile> outTiles = new ArrayList<>();
-        List<ResolveInfo> info = new ArrayList<>();
-        ResolveInfo resolveInfo = newInfo(true, null /* category */, null,
-                null, URI_GET_SUMMARY);
-        resolveInfo.activityInfo.metaData.putInt("com.android.settings.custom_view",
-                R.layout.user_preference);
-        info.add(resolveInfo);
-
-        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
-                .thenReturn(info);
-
-        // Mock the content provider interaction.
-        Bundle bundle = new Bundle();
-        bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY, "new summary text");
-        when(mIContentProvider.call(anyString(),
-                eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY),
-                any())).thenReturn(bundle);
-
-        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
-                null /* defaultCategory */, outTiles, false /* usePriority */,
-                false /* checkCategory */, true /* forceTintExternalIcon */);
-
-        assertThat(outTiles.size()).isEqualTo(1);
-        Tile tile = outTiles.get(0);
-        assertThat(tile.remoteViews).isNotNull();
-        assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
-        // Make sure the summary TextView didn't get any text view updates.
-        TileUtilsShadowRemoteViews shadowRemoteViews =
-                (TileUtilsShadowRemoteViews) ShadowExtractor.extract(tile.remoteViews);
-        assertThat(shadowRemoteViews.overrideViewId).isNull();
-        assertThat(shadowRemoteViews.overrideText).isNull();
+        Tile tile = new Tile();
+        tile.metaData = new Bundle();
+        tile.metaData.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, URI_GET_SUMMARY);
+        tile.remoteViews = mock(RemoteViews.class);
+        TileUtils.updateTileUsingSummaryUri(mContext, tile);
+        verify(tile.remoteViews, times(1)).setTextViewText(anyInt(), eq(expectedSummary));
     }
 
     public static ResolveInfo newInfo(boolean systemApp, String category) {
@@ -562,16 +503,4 @@
         }
     }
 
-    @Implements(RemoteViews.class)
-    public static class TileUtilsShadowRemoteViews {
-
-        private Integer overrideViewId;
-        private CharSequence overrideText;
-
-        @Implementation
-        public void setTextViewText(int viewId, CharSequence text) {
-            overrideViewId = viewId;
-            overrideText = text;
-        }
-    }
 }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c997160..070634b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -318,15 +318,20 @@
             android:exported="false">
         </activity>
 
+        <!-- Springboard for launching the share activity -->
+        <receiver android:name=".screenshot.GlobalScreenshot$ShareReceiver"
+            android:process=":screenshot"
+            android:exported="false" />
+
         <!-- Callback for dismissing screenshot notification after a share target is picked -->
         <receiver android:name=".screenshot.GlobalScreenshot$TargetChosenReceiver"
-                  android:process=":screenshot"
-                  android:exported="false" />
+            android:process=":screenshot"
+            android:exported="false" />
 
         <!-- Callback for deleting screenshot notification -->
         <receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver"
-                  android:process=":screenshot"
-                  android:exported="false" />
+            android:process=":screenshot"
+            android:exported="false" />
 
         <!-- started from UsbDeviceSettingsManager -->
         <activity android:name=".usb.UsbConfirmActivity"
diff --git a/packages/SystemUI/res/color/qs_detail_progress_track.xml b/packages/SystemUI/res/color/qs_detail_progress_track.xml
index c56382e..d86119f 100644
--- a/packages/SystemUI/res/color/qs_detail_progress_track.xml
+++ b/packages/SystemUI/res/color/qs_detail_progress_track.xml
@@ -16,5 +16,5 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- I really don't want to define this, but the View that uses this asset uses both the
          light and dark accent colors. -->
-    <item android:alpha="0.6" android:drawable="@*android:color/accent_device_default_light" />
+    <item android:alpha="0.6" android:drawable="@*android:color/accent_device_default_dark" />
 </selector>
diff --git a/packages/SystemUI/res/layout/car_fullscreen_user_pod.xml b/packages/SystemUI/res/layout/car_fullscreen_user_pod.xml
index dde2db2..2f16516 100644
--- a/packages/SystemUI/res/layout/car_fullscreen_user_pod.xml
+++ b/packages/SystemUI/res/layout/car_fullscreen_user_pod.xml
@@ -30,7 +30,8 @@
     <TextView android:id="@+id/user_name"
         android:layout_width="@dimen/car_fullscreen_user_pod_width"
         android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/car_fullscreen_user_pod_margin_above_text"
+        android:layout_marginTop="@dimen/car_fullscreen_user_pod_margin_name_top"
+        android:layout_marginBottom="@dimen/car_fullscreen_user_pod_margin_name_bottom"
         android:textSize="@dimen/car_fullscreen_user_pod_text_size"
         android:textColor="@color/qs_user_detail_name"
         android:ellipsize="end"
diff --git a/packages/SystemUI/res/layout/car_qs_footer.xml b/packages/SystemUI/res/layout/car_qs_footer.xml
index 96f34fc..044090b 100644
--- a/packages/SystemUI/res/layout/car_qs_footer.xml
+++ b/packages/SystemUI/res/layout/car_qs_footer.xml
@@ -18,23 +18,24 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/qs_footer"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/qs_footer_height"
+    android:layout_height="@dimen/car_qs_footer_height"
     android:baselineAligned="false"
     android:clickable="false"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:paddingBottom="16dp"
-    android:paddingTop="16dp"
-    android:paddingEnd="32dp"
-    android:paddingStart="32dp"
+    android:paddingBottom="@dimen/car_qs_footer_padding_bottom"
+    android:paddingTop="@dimen/car_qs_footer_padding_top"
+    android:paddingEnd="@dimen/car_qs_footer_padding_end"
+    android:paddingStart="@dimen/car_qs_footer_padding_start"
     android:gravity="center_vertical">
 
     <com.android.systemui.statusbar.phone.MultiUserSwitch
         android:id="@+id/multi_user_switch"
         android:layout_alignParentStart="true"
         android:layout_centerVertical="true"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
+        android:layout_width="@dimen/car_qs_footer_icon_width"
+        android:layout_height="@dimen/car_qs_footer_icon_height"
+        android:layout_marginRight="@dimen/car_qs_footer_user_switch_margin_right"
         android:background="@drawable/ripple_drawable"
         android:focusable="true">
 
@@ -43,15 +44,24 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_gravity="center"
-            android:scaleType="centerInside"/>
+            android:scaleType="fitCenter"/>
     </com.android.systemui.statusbar.phone.MultiUserSwitch>
 
+    <TextView android:id="@+id/user_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="@dimen/car_qs_footer_user_name_text_size"
+        android:textColor="@color/car_qs_footer_user_name_color"
+        android:gravity="start|center_vertical"
+        android:layout_centerVertical="true"
+        android:layout_toEndOf="@id/multi_user_switch" />
+
     <com.android.systemui.statusbar.phone.SettingsButton
         android:id="@+id/settings_button"
         android:layout_alignParentEnd="true"
         android:layout_centerVertical="true"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
+        android:layout_width="@dimen/car_qs_footer_icon_width"
+        android:layout_height="@dimen/car_qs_footer_icon_height"
         android:background="@drawable/ripple_drawable"
         android:contentDescription="@string/accessibility_quick_settings_settings"
         android:scaleType="centerCrop"
diff --git a/packages/SystemUI/res/values-h600dp/dimens_car.xml b/packages/SystemUI/res/values-h600dp/dimens_car.xml
new file mode 100644
index 0000000..c3e62c8
--- /dev/null
+++ b/packages/SystemUI/res/values-h600dp/dimens_car.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2017, 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.
+*/
+-->
+<resources>
+    <dimen name="car_body2_size">32sp</dimen> <!-- B2 -->
+</resources>
diff --git a/packages/SystemUI/res/values-ldrtl/config.xml b/packages/SystemUI/res/values-ldrtl/config.xml
index 40604c1..884c95f 100644
--- a/packages/SystemUI/res/values-ldrtl/config.xml
+++ b/packages/SystemUI/res/values-ldrtl/config.xml
@@ -17,5 +17,5 @@
 <resources>
     <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
          when the PIP menu is shown with settings. -->
-    <string translatable="false" name="pip_settings_bounds">"778 54 1258 324"</string>
+    <string translatable="false" name="pip_settings_bounds">"778 756 1258 1026"</string>
 </resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 745d6015..a923f0b 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -34,6 +34,7 @@
         <attr name="recentItemLayout" format="reference" />
         <!-- Style for the "Clear all" button. -->
         <attr name="clearAllStyle" format="reference" />
+        <attr name="clearAllBackgroundColor" format="reference" />
     </declare-styleable>
     <declare-styleable name="DeadZone">
         <attr name="minSize" format="dimension" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index f72f379..f244d88 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -60,6 +60,11 @@
     <!-- The background color for the freeform workspace. -->
     <color name="recents_freeform_workspace_bg_color">#33FFFFFF</color>
 
+    <!-- The background color for clear all button on light backgrounds if not transparent. -->
+    <color name="recents_clear_all_button_bg_light_color">#CCFFFFFF</color>
+    <!-- The background color for clear all button on dark backgrounds if not transparent. -->
+    <color name="recents_clear_all_button_bg_dark_color">#CC000000</color>
+
     <color name="keyguard_affordance">#ffffffff</color>
 
     <!-- The color of the legacy notification background -->
diff --git a/packages/SystemUI/res/values/colors_car.xml b/packages/SystemUI/res/values/colors_car.xml
index 1b8c2fa..710b3e0 100644
--- a/packages/SystemUI/res/values/colors_car.xml
+++ b/packages/SystemUI/res/values/colors_car.xml
@@ -20,8 +20,12 @@
     <color name="car_qs_background_primary">#263238</color> <!-- Blue Gray 900 -->
     <color name="car_user_switcher_progress_bgcolor">#00000000</color> <!-- Transparent -->
     <color name="car_user_switcher_progress_fgcolor">#80CBC4</color> <!-- Teal 200 -->
-    <color name="car_user_switcher_no_user_image_bgcolor">#FAFAFA</color> <!-- Grey 50 -->
-    <color name="car_user_switcher_no_user_image_fgcolor">#212121</color> <!-- Grey 900 -->
-    <color name="car_start_driving_background">#FAFAFA</color> <!-- Grey 50 -->
-    <color name="car_start_driving_text">#212121</color> <!-- Grey 900 -->
+    <color name="car_user_switcher_no_user_image_bgcolor">@color/car_grey_50</color>
+    <color name="car_user_switcher_no_user_image_fgcolor">@color/car_grey_900</color>
+    <color name="car_start_driving_background">@color/car_grey_50</color>
+    <color name="car_start_driving_text">@color/car_grey_900</color>
+    <color name="car_qs_footer_user_name_color">@color/car_grey_50</color>
+
+    <color name="car_grey_50">#FAFAFA</color>
+    <color name="car_grey_900">#212121</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens_car.xml b/packages/SystemUI/res/values/dimens_car.xml
index 5f56c4e..8853587 100644
--- a/packages/SystemUI/res/values/dimens_car.xml
+++ b/packages/SystemUI/res/values/dimens_car.xml
@@ -18,7 +18,8 @@
 <resources>
     <dimen name="car_margin">148dp</dimen>
 
-    <dimen name="car_fullscreen_user_pod_margin_above_text">24dp</dimen>
+    <dimen name="car_fullscreen_user_pod_margin_name_top">24dp</dimen>
+    <dimen name="car_fullscreen_user_pod_margin_name_bottom">64dp</dimen>
     <dimen name="car_fullscreen_user_pod_margin_between">24dp</dimen>
     <dimen name="car_fullscreen_user_pod_icon_text_size">96dp</dimen>
     <dimen name="car_fullscreen_user_pod_image_avatar_width">192dp</dimen>
@@ -37,5 +38,17 @@
     <dimen name="car_start_driving_corner_radius">16dp</dimen>
     <dimen name="car_start_driving_padding_side">30dp</dimen>
     <dimen name="car_start_driving_height">80dp</dimen>
-    <dimen name="car_start_driving_text_size">32sp</dimen> <!-- B2 -->
+    <dimen name="car_start_driving_text_size">@dimen/car_body2_size</dimen>
+
+    <dimen name="car_qs_footer_height">112dp</dimen>
+    <dimen name="car_qs_footer_padding_bottom">16dp</dimen>
+    <dimen name="car_qs_footer_padding_top">16dp</dimen>
+    <dimen name="car_qs_footer_padding_end">46dp</dimen>
+    <dimen name="car_qs_footer_padding_start">46dp</dimen>
+    <dimen name="car_qs_footer_icon_width">56dp</dimen>
+    <dimen name="car_qs_footer_icon_height">56dp</dimen>
+    <dimen name="car_qs_footer_user_switch_margin_right">46dp</dimen>
+    <dimen name="car_qs_footer_user_name_text_size">@dimen/car_body2_size</dimen>
+
+    <dimen name="car_body2_size">26sp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index b27dedd..2148c80 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -59,6 +59,7 @@
     <item type="id" name="transformation_start_y_tag"/>
     <item type="id" name="transformation_start_scale_x_tag"/>
     <item type="id" name="transformation_start_scale_y_tag"/>
+    <item type="id" name="continuous_clipping_tag"/>
 
     <!-- Whether the icon is from a notification for which targetSdk < L -->
     <item type="id" name="icon_is_pre_L"/>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2199fff..90c5977 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -34,11 +34,13 @@
         <item name="android:windowShowWallpaper">true</item>
         <item name="android:windowDisablePreview">true</item>
         <item name="clearAllStyle">@style/ClearAllButtonDefaultMargins</item>
+        <item name="clearAllBackgroundColor">@color/recents_clear_all_button_bg_dark_color</item>
         <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
         <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
     </style>
 
     <style name="RecentsTheme.Wallpaper.Light">
+        <item name="clearAllBackgroundColor">@color/recents_clear_all_button_bg_light_color</item>
         <item name="wallpaperTextColor">@*android:color/primary_text_material_light</item>
         <item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_light</item>
     </style>
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 97a5962..a9a915b 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -42,6 +42,7 @@
 import com.android.systemui.power.PowerUI;
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -296,12 +297,13 @@
 
         mProviders.put(UiOffloadThread.class, UiOffloadThread::new);
 
-
         mProviders.put(PowerUI.WarningsUI.class, () -> new PowerNotificationWarnings(mContext));
 
         mProviders.put(IconLogger.class, () -> new IconLoggerImpl(mContext,
                 getDependency(BG_LOOPER), getDependency(MetricsLogger.class)));
 
+        mProviders.put(LightBarController.class, () -> new LightBarController(mContext));
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
index d42b87b..142aab2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
@@ -22,6 +22,7 @@
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
+import android.widget.TextView;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -44,6 +45,7 @@
     private UserInfoController mUserInfoController;
 
     private MultiUserSwitch mMultiUserSwitch;
+    private TextView mUserName;
     private ImageView mMultiUserAvatar;
     private UserGridView mUserGridView;
 
@@ -56,6 +58,7 @@
         super.onFinishInflate();
         mMultiUserSwitch = findViewById(R.id.multi_user_switch);
         mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
+        mUserName = findViewById(R.id.user_name);
 
         mUserInfoController = Dependency.get(UserInfoController.class);
 
@@ -89,6 +92,7 @@
     @Override
     public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
         mMultiUserAvatar.setImageDrawable(picture);
+        mUserName.setText(name);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 6c95a80..5a3081c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -18,6 +18,7 @@
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
+import android.app.AlertDialog;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
@@ -35,6 +36,8 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
 import android.widget.LinearLayout;
 import android.widget.Toolbar;
 import android.widget.Toolbar.OnMenuItemClickListener;
@@ -48,7 +51,9 @@
 import com.android.systemui.qs.QSContainerImpl;
 import com.android.systemui.qs.QSDetailClipper;
 import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
 
@@ -67,6 +72,7 @@
     private static final String EXTRA_QS_CUSTOMIZING = "qs_customizing";
 
     private final QSDetailClipper mClipper;
+    private final LightBarController mLightBarController;
 
     private boolean isShown;
     private QSTileHost mHost;
@@ -80,6 +86,7 @@
     private int mX;
     private int mY;
     private boolean mOpening;
+    private boolean mIsShowingNavBackdrop;
 
     public QSCustomizer(Context context, AttributeSet attrs) {
         super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
@@ -114,6 +121,7 @@
         DefaultItemAnimator animator = new DefaultItemAnimator();
         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
         mRecyclerView.setItemAnimator(animator);
+        mLightBarController = Dependency.get(LightBarController.class);
         updateNavBackDrop(getResources().getConfiguration());
     }
 
@@ -125,11 +133,16 @@
 
     private void updateNavBackDrop(Configuration newConfig) {
         View navBackdrop = findViewById(R.id.nav_bar_background);
+        mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600
+                || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
         if (navBackdrop != null) {
-            boolean shouldShow = newConfig.smallestScreenWidthDp >= 600
-                    || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
-            navBackdrop.setVisibility(shouldShow ? View.VISIBLE : View.GONE);
+            navBackdrop.setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE);
         }
+        updateNavColors();
+    }
+
+    private void updateNavColors() {
+        mLightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
     }
 
     public void setHost(QSTileHost host) {
@@ -161,6 +174,7 @@
             announceForAccessibility(mContext.getString(
                     R.string.accessibility_desc_quick_settings_edit));
             Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
+            updateNavColors();
         }
     }
 
@@ -176,6 +190,7 @@
             mNotifQsContainer.setCustomizerAnimating(false);
             mNotifQsContainer.setCustomizerShowing(true);
             Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
+            updateNavColors();
         }
     }
 
@@ -201,6 +216,7 @@
             announceForAccessibility(mContext.getString(
                     R.string.accessibility_desc_quick_settings));
             Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
+            updateNavColors();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 017365f..176112b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -31,6 +31,7 @@
 import android.service.quicksettings.IQSTileService;
 import android.service.quicksettings.Tile;
 import android.service.quicksettings.TileService;
+import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
@@ -51,6 +52,8 @@
 public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
     public static final String PREFIX = "custom(";
 
+    private static final long CUSTOM_STALE_TIMEOUT = DateUtils.HOUR_IN_MILLIS;
+
     private static final boolean DEBUG = false;
 
     // We don't want to thrash binding and unbinding if the user opens and closes the panel a lot.
@@ -83,6 +86,11 @@
         mUser = ActivityManager.getCurrentUser();
     }
 
+    @Override
+    protected long getStaleTimeout() {
+        return CUSTOM_STALE_TIMEOUT + DateUtils.MINUTE_IN_MILLIS * mHost.indexOf(getTileSpec());
+    }
+
     private void setTileIcon() {
         try {
             PackageManager pm = mContext.getPackageManager();
@@ -186,7 +194,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (mListening == listening) return;
         mListening = listening;
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 8c04daf..6a5530f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -32,10 +32,12 @@
 import android.os.Looper;
 import android.os.Message;
 import android.service.quicksettings.Tile;
+import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.Utils;
@@ -45,7 +47,6 @@
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.qs.PagedTileLayout;
 import com.android.systemui.qs.PagedTileLayout.TilePage;
 import com.android.systemui.qs.QSHost;
 
@@ -62,14 +63,18 @@
     protected final String TAG = "Tile." + getClass().getSimpleName();
     protected static final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG);
 
+    private static final long DEFAULT_STALE_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;
+
     protected final QSHost mHost;
     protected final Context mContext;
-    protected final H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
+    // @NonFinalForTesting
+    protected H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
     protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
     private final ArraySet<Object> mListeners = new ArraySet<>();
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+    private final Object mStaleListener = new Object();
     protected TState mState = newTileState();
     private TState mTmpState = newTileState();
     private boolean mAnnounceNextStateChange;
@@ -95,6 +100,7 @@
     protected QSTileImpl(QSHost host) {
         mHost = host;
         mContext = host.getContext();
+        handleStale(); // Tile was just created, must be stale.
     }
 
     /**
@@ -106,6 +112,7 @@
             if (mListeners.add(listener) && mListeners.size() == 1) {
                 if (DEBUG) Log.d(TAG, "setListening " + true);
                 mHandler.obtainMessage(H.SET_LISTENING, 1, 0).sendToTarget();
+                refreshState(); // Ensure we get at least one refresh after listening.
             }
         } else {
             if (mListeners.remove(listener) && mListeners.size() == 0) {
@@ -115,6 +122,15 @@
         }
     }
 
+    protected long getStaleTimeout() {
+        return DEFAULT_STALE_TIMEOUT;
+    }
+
+    @VisibleForTesting
+    protected void handleStale() {
+        setListening(mStaleListener, true);
+    }
+
     public String getTileSpec() {
         return mTileSpec;
     }
@@ -273,6 +289,9 @@
         if (changed) {
             handleStateChanged();
         }
+        mHandler.removeMessages(H.STALE);
+        mHandler.sendEmptyMessageDelayed(H.STALE, getStaleTimeout());
+        setListening(mStaleListener, false);
     }
 
     private void handleStateChanged() {
@@ -326,11 +345,11 @@
         handleRefreshState(null);
     }
 
-    protected abstract void setListening(boolean listening);
+    protected abstract void handleSetListening(boolean listening);
 
     protected void handleDestroy() {
         if (mListeners.size() != 0) {
-            setListening(false);
+            handleSetListening(false);
         }
         mCallbacks.clear();
     }
@@ -380,8 +399,10 @@
         private static final int REMOVE_CALLBACKS = 12;
         private static final int REMOVE_CALLBACK = 13;
         private static final int SET_LISTENING = 14;
+        private static final int STALE = 15;
 
-        private H(Looper looper) {
+        @VisibleForTesting
+        protected H(Looper looper) {
             super(looper);
         }
 
@@ -436,8 +457,11 @@
                     name = "handleClearState";
                     handleClearState();
                 } else if (msg.what == SET_LISTENING) {
-                    name = "setListening";
-                    setListening(msg.arg1 != 0);
+                    name = "handleSetListening";
+                    handleSetListening(msg.arg1 != 0);
+                } else if (msg.what == STALE) {
+                    name = "handleStale";
+                    handleStale();
                 } else {
                     throw new IllegalArgumentException("Unknown msg: " + msg.what);
                 }
@@ -515,7 +539,7 @@
         }
     }
 
-    protected class AnimationIcon extends ResourceIcon {
+    protected static class AnimationIcon extends ResourceIcon {
         private final int mAnimatedResId;
 
         public AnimationIcon(int resId, int staticResId) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 2e7012e..bef1aff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -110,7 +110,7 @@
         }
     }
 
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (mListening == listening) return;
         mListening = listening;
         if (listening) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 3f419a8..95504ed 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -55,7 +55,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (listening) {
             mBatteryController.addCallback(this);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index bc6233d..774f0b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -76,7 +76,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (listening) {
             mController.addCallback(mCallback);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 2fc9fc7..fb396b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -91,9 +91,9 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (mController == null) return;
-        if (DEBUG) Log.d(TAG, "setListening " + listening);
+        if (DEBUG) Log.d(TAG, "handleSetListening " + listening);
         if (listening) {
             mController.addCallback(mCallback);
             mKeyguard.addCallback(mCallback);
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 f900da0..0bb7479 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -92,7 +92,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (listening) {
             mController.addCallback(mSignalCallback);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 40fe484..b93f1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -63,7 +63,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         mSetting.setListening(listening);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 8b62beb..a102696 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -45,7 +45,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (listening) {
             mDataSaverController.addCallback(this);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 5938749..9e265e22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -232,7 +232,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (mListening == listening) return;
         mListening = listening;
         if (mListening) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index e6ac908..f2ead1c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -54,7 +54,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (listening) {
             mFlashlightController.addCallback(this);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index c17573d..910b6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -74,7 +74,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (mListening == listening) return;
         mListening = listening;
         if (listening) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
index 00cfbfa..4f4004c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java
@@ -76,7 +76,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 5e66334..c35f591 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -55,7 +55,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (listening) {
             mController.addCallback(mCallback);
             mKeyguard.addCallback(mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index 6500740..b3ff4e5b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -51,7 +51,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         mListening = listening;
         if (mListening) {
             mContext.registerReceiver(mNfcReceiver,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 2a12769..4c20361 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -95,7 +95,7 @@
     }
 
     @Override
-    protected void setListening(boolean listening) {
+    protected void handleSetListening(boolean listening) {
         mIsListening = listening;
         if (listening) {
             mController.setListener(this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index fb937bd..1e00894 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -62,7 +62,7 @@
         return new BooleanState();
     }
 
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (mController == null) return;
         if (listening) {
             mController.addCallback(mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
index d6043f4..bde1c98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserTile.java
@@ -69,7 +69,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (listening) {
             mUserInfoController.addCallback(this);
         } else {
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 136cf21..33b1512 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -75,7 +75,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (listening) {
             mController.addCallback(mSignalCallback);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 38821f6..5f7d6fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -48,7 +48,7 @@
     }
 
     @Override
-    public void setListening(boolean listening) {
+    public void handleSetListening(boolean listening) {
         if (listening) {
             mProfileController.addCallback(this);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index f844866..f545556 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -65,6 +65,7 @@
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
+import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
@@ -119,6 +120,7 @@
     private boolean mFinishedOnStartup;
     private boolean mIgnoreAltTabRelease;
     private boolean mIsVisible;
+    private boolean mRecentsStartRequested;
     private Configuration mLastConfig;
 
     // Top level views
@@ -416,6 +418,7 @@
             launchState.launchedFromHome = false;
             onEnterAnimationComplete();
         }
+        mRecentsStartRequested = false;
     }
 
     @Override
@@ -449,7 +452,6 @@
      * Reloads the stack views upon launching Recents.
      */
     private void reloadStackView() {
-
         // If the Recents component has preloaded a load plan, then use that to prevent
         // reconstructing the task stack
         RecentsTaskLoader loader = Recents.getTaskLoader();
@@ -572,7 +574,9 @@
         MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
         Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(false);
 
-        if (!isChangingConfigurations()) {
+        // When recents starts again before onStop, do not reset launch flags so entrance animation
+        // can run
+        if (!isChangingConfigurations() && !mRecentsStartRequested) {
             // Workaround for b/22542869, if the RecentsActivity is started again, but without going
             // through SystemUI, we need to reset the config launch flags to ensure that we do not
             // wait on the system to send a signal that was never queued.
@@ -718,6 +722,10 @@
         MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE);
     }
 
+    public final void onBusEvent(RecentsActivityStartingEvent event) {
+        mRecentsStartRequested = true;
+    }
+
     public final void onBusEvent(UserInteractionEvent event) {
         // Stop the fast-toggle dozer
         mIterateTrigger.stopDozing();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 71f06cb..c44cd72 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -205,6 +205,10 @@
                         mStackButtonShadowDistance.x, mStackButtonShadowDistance.y,
                         mStackButtonShadowColor);
             }
+            if (Recents.getConfiguration().isLowRamDevice) {
+                int bgColor = Utils.getColorAttr(mContext, R.attr.clearAllBackgroundColor);
+                mStackActionButton.setBackgroundColor(bgColor);
+            }
         }
 
         // Let's also require dark status and nav bars if the text is dark
@@ -955,8 +959,8 @@
         int left, top;
         if (Recents.getConfiguration().isLowRamDevice) {
             Rect windowRect = Recents.getSystemServices().getWindowRect();
-            left = (windowRect.width() - mSystemInsets.left - mSystemInsets.right
-                    - mStackActionButton.getMeasuredWidth()) / 2;
+            int spaceLeft = windowRect.width() - mSystemInsets.left - mSystemInsets.right;
+            left = (spaceLeft - mStackActionButton.getMeasuredWidth()) / 2 + mSystemInsets.left;
             top = windowRect.height() - (mStackActionButton.getMeasuredHeight()
                     + mSystemInsets.bottom + mStackActionButton.getPaddingBottom() / 2);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index a633a3e..8899e30 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -2396,10 +2396,14 @@
     }
 
     private void updateStackActionButtonVisibility() {
+        if (Recents.getConfiguration().isLowRamDevice) {
+            return;
+        }
+
         // Always show the button in grid layout.
         if (useGridLayout() ||
                 (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
-                        mStack.getTaskCount() > 0 && !Recents.getConfiguration().isLowRamDevice)) {
+                        mStack.getTaskCount() > 0)) {
             EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
         } else {
             EventBus.getDefault().send(new HideStackActionButtonEvent());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
index 52fa7b5..17e6b9e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
@@ -126,7 +126,6 @@
             return transformOut;
         }
         boolean visible = true;
-        int x = mPaddingLeftRight;
         int y;
         if (taskCount > 1) {
             y = getTaskTopFromIndex(taskIndex) - percentageToScroll(stackScroll);
@@ -255,7 +254,7 @@
         transformOut.dimAlpha = 0f;
         transformOut.viewOutlineAlpha = 1f;
         transformOut.rect.set(getTaskRect());
-        transformOut.rect.offset(mPaddingLeftRight, y);
+        transformOut.rect.offset(mPaddingLeftRight + mSystemInsets.left, y);
         Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
         transformOut.visible = visible;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index a35310f..991c3c8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -16,11 +16,16 @@
 
 package com.android.systemui.screenshot;
 
+import static com.android.systemui.screenshot.GlobalScreenshot.SHARING_INTENT;
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.admin.DevicePolicyManager;
 import android.app.Notification;
 import android.app.Notification.BigPictureStyle;
@@ -48,6 +53,7 @@
 import android.os.Environment;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.MediaStore;
 import android.util.DisplayMetrics;
@@ -277,14 +283,13 @@
             sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
             sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
 
-            // Create a share action for the notification
-            PendingIntent chooseAction = PendingIntent.getBroadcast(context, 0,
-                    new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
-                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
-            Intent chooserIntent = Intent.createChooser(sharingIntent, null,
-                    chooseAction.getIntentSender())
-                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
-            PendingIntent shareAction = PendingIntent.getActivity(context, 0, chooserIntent,
+            // Create a share action for the notification. Note, we proxy the call to ShareReceiver
+            // because RemoteViews currently forces an activity options on the PendingIntent being
+            // launched, and since we don't want to trigger the share sheet in this case, we will
+            // start the chooser activitiy directly in ShareReceiver.
+            PendingIntent shareAction = PendingIntent.getBroadcast(context, 0,
+                    new Intent(context, GlobalScreenshot.ShareReceiver.class)
+                            .putExtra(SHARING_INTENT, sharingIntent),
                     PendingIntent.FLAG_CANCEL_CURRENT);
             Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
                     R.drawable.ic_screenshot_share,
@@ -292,7 +297,7 @@
             mNotificationBuilder.addAction(shareActionBuilder.build());
 
             // Create a delete action for the notification
-            PendingIntent deleteAction = PendingIntent.getBroadcast(context,  0,
+            PendingIntent deleteAction = PendingIntent.getBroadcast(context, 0,
                     new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
                             .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()),
                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
@@ -403,6 +408,7 @@
 
 class GlobalScreenshot {
     static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
+    static final String SHARING_INTENT = "android:screenshot_sharing_intent";
 
     private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
     private static final int SCREENSHOT_DROP_IN_DURATION = 430;
@@ -897,6 +903,30 @@
     }
 
     /**
+     * Receiver to proxy the share intent.
+     */
+    public static class ShareReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            try {
+                ActivityManager.getService().closeSystemDialogs(SYSTEM_DIALOG_REASON_SCREENSHOT);
+            } catch (RemoteException e) {
+            }
+
+            Intent sharingIntent = intent.getParcelableExtra(SHARING_INTENT);
+            PendingIntent chooseAction = PendingIntent.getBroadcast(context, 0,
+                    new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
+                    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+            Intent chooserIntent = Intent.createChooser(sharingIntent, null,
+                    chooseAction.getIntentSender())
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+            ActivityOptions opts = ActivityOptions.makeBasic();
+            opts.setDisallowEnterPictureInPictureWhileLaunching(true);
+            context.startActivityAsUser(chooserIntent, opts.toBundle(), UserHandle.CURRENT);
+        }
+    }
+
+    /**
      * Removes the notification for a screenshot after a share target is chosen.
      */
     public static class TargetChosenReceiver extends BroadcastReceiver {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 1ffb3b5..3b23a0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -126,7 +126,7 @@
                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                             | PackageManager.MATCH_DIRECT_BOOT_AWARE);
             if (info != null) {
-                mAppUid = info.uid;
+                mAppUid = sbn.getUid();
                 mAppName = String.valueOf(pm.getApplicationLabel(info));
                 pkgicon = pm.getApplicationIcon(info);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 5eefe9a..184b95d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -22,10 +22,12 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import com.android.systemui.Interpolators;
@@ -53,6 +55,7 @@
             SystemProperties.getBoolean("debug.icon_opening_animations", true);
     private static final boolean ICON_ANMATIONS_WHILE_SCROLLING
             = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
+    private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
     private ViewInvertHelper mViewInvertHelper;
     private boolean mDark;
     private NotificationIconContainer mShelfIcons;
@@ -305,6 +308,16 @@
         mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
         mShelfIcons.calculateIconTranslations();
         mShelfIcons.applyIconStates();
+        for (int i = 0; i < mHostLayout.getChildCount(); i++) {
+            View child = mHostLayout.getChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)
+                    || child.getVisibility() == GONE) {
+                continue;
+            }
+            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            updateIconClipAmount(row);
+            updateContinuousClipping(row);
+        }
         boolean hideBackground = numViewsInShelf < 1.0f;
         setHideBackground(hideBackground || backgroundForceHidden);
         if (mNotGoneIndex == -1) {
@@ -312,6 +325,43 @@
         }
     }
 
+    private void updateIconClipAmount(ExpandableNotificationRow row) {
+        float maxTop = row.getTranslationY();
+        StatusBarIconView icon = row.getEntry().expandedIcon;
+        float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY();
+        if (shelfIconPosition < maxTop) {
+            int top = (int) (maxTop - shelfIconPosition);
+            Rect clipRect = new Rect(0, top, icon.getWidth(), Math.max(top, icon.getHeight()));
+            icon.setClipBounds(clipRect);
+        } else {
+            icon.setClipBounds(null);
+        }
+    }
+
+    private void updateContinuousClipping(final ExpandableNotificationRow row) {
+        StatusBarIconView icon = row.getEntry().expandedIcon;
+        boolean needsContinuousClipping = ViewState.isAnimatingY(icon);
+        boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null;
+        if (needsContinuousClipping && !isContinuousClipping) {
+            ViewTreeObserver.OnPreDrawListener predrawListener =
+                    new ViewTreeObserver.OnPreDrawListener() {
+                        @Override
+                        public boolean onPreDraw() {
+                            boolean animatingY = ViewState.isAnimatingY(icon);
+                            if (!animatingY || !icon.isAttachedToWindow()) {
+                                icon.getViewTreeObserver().removeOnPreDrawListener(this);
+                                icon.setTag(TAG_CONTINUOUS_CLIPPING, null);
+                                return true;
+                            }
+                            updateIconClipAmount(row);
+                            return true;
+                        }
+                    };
+            icon.getViewTreeObserver().addOnPreDrawListener(predrawListener);
+            icon.setTag(TAG_CONTINUOUS_CLIPPING, predrawListener);
+        }
+    }
+
     private void updateNotificationClipHeight(ExpandableNotificationRow row,
             float notificationClipEnd) {
         float viewEnd = row.getTranslationY() + row.getActualHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index fcc982ea..b95b8a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -114,7 +114,6 @@
         mIcon = mView.findViewById(com.android.internal.R.id.icon);
         mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
         mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
-        mExpandButton.setLabeledBy(mRow);
         mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
         mColor = resolveColor(mExpandButton);
         mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index f379a46..1f44abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -41,8 +41,6 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_COLORS = false;
 
-    public static final boolean HIGH_END = ActivityManager.isHighEndGfx();
-
     public static final int MODE_OPAQUE = 0;
     public static final int MODE_SEMI_TRANSPARENT = 1;
     public static final int MODE_TRANSLUCENT = 2;
@@ -66,9 +64,7 @@
         mTag = "BarTransitions." + view.getClass().getSimpleName();
         mView = view;
         mBarBackground = new BarBackgroundDrawable(mView.getContext(), gradientResourceId);
-        if (HIGH_END) {
-            mView.setBackground(mBarBackground);
-        }
+        mView.setBackground(mBarBackground);
     }
 
     public int getMode() {
@@ -89,7 +85,7 @@
 
     public boolean isAlwaysOpaque() {
         // Low-end devices do not support translucent modes, fallback to opaque
-        return !HIGH_END || mAlwaysOpaque;
+        return mAlwaysOpaque;
     }
 
     public void transitionTo(int mode, boolean animate) {
@@ -109,9 +105,7 @@
     }
 
     protected void onTransition(int oldMode, int newMode, boolean animate) {
-        if (HIGH_END) {
-            applyModeBackground(oldMode, newMode, animate);
-        }
+        applyModeBackground(oldMode, newMode, animate);
     }
 
     protected void applyModeBackground(int oldMode, int newMode, boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 8c923cb..2c3f452 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -161,10 +161,6 @@
                 showNotificationIconArea(animate);
             }
         }
-        if (!BarTransitions.HIGH_END) {
-            int mask = DISABLE_NOTIFICATION_ICONS | DISABLE_SYSTEM_INFO;
-            getView().setVisibility((mDisabled1 & mask) == mask ? View.GONE : View.VISIBLE);
-        }
     }
 
     protected int adjustDisableFlags(int state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 917a56f..533771a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -72,6 +72,7 @@
 
     private final Rect mLastFullscreenBounds = new Rect();
     private final Rect mLastDockedBounds = new Rect();
+    private boolean mQsCustomizing;
 
     public LightBarController(Context ctx) {
         mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
@@ -129,7 +130,8 @@
             mHasLightNavigationBar = isLight(vis, navigationBarMode,
                     View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
             mNavigationLight = mHasLightNavigationBar
-                    && (mScrimAlphaBelowThreshold || !mInvertLightNavBarWithScrim);
+                    && (mScrimAlphaBelowThreshold || !mInvertLightNavBarWithScrim)
+                    && !mQsCustomizing;
             if (mNavigationLight != last) {
                 updateNavigation();
             }
@@ -146,6 +148,12 @@
                 mLastNavigationBarMode);
     }
 
+    public void setQsCustomizing(boolean customizing) {
+        if (mQsCustomizing == customizing) return;
+        mQsCustomizing = customizing;
+        reevaluate();
+    }
+
     public void setScrimAlpha(float alpha) {
         mScrimAlpha = alpha;
         boolean belowThresholdBefore = mScrimAlphaBelowThreshold;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index d3a6280..b0ac6ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -127,11 +127,6 @@
     }
 
     public void setIconsDark(boolean dark, boolean animate) {
-        if (!BarTransitions.HIGH_END) {
-            setIconTintInternal(0.0f);
-            mNextDarkIntensity = 0.0f;
-            return;
-        }
         if (!animate) {
             setIconTintInternal(dark ? 1.0f : 0.0f);
             mNextDarkIntensity = dark ? 1.0f : 0.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
index 004a604..1452e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
@@ -87,7 +87,8 @@
             if (mTouchingChild != null) {
                 event.offsetLocation(mTouchingChild.getWidth() / 2 - event.getX(),
                         mTouchingChild.getHeight() / 2 - event.getY());
-                return mTouchingChild.dispatchTouchEvent(event);
+                return mTouchingChild.getVisibility() == VISIBLE
+                        && mTouchingChild.dispatchTouchEvent(event);
             }
         }
         return super.onTouchEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 7aebfdc..c48ca8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -300,6 +300,7 @@
     // Should match the values in PhoneWindowManager
     public static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
     public static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+    static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot";
 
     private static final String BANNER_ACTION_CANCEL =
             "com.android.systemui.statusbar.banner_action_cancel";
@@ -1124,7 +1125,7 @@
             }
         });
 
-        mLightBarController = new LightBarController(context);
+        mLightBarController = Dependency.get(LightBarController.class);
         if (mNavigationBar != null) {
             mNavigationBar.setLightBarController(mLightBarController);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
index 1411a54..f9c2130 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -159,6 +159,11 @@
         public void onScreenTurnedOff() {
             update(false /* updateAlways */);
         }
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean showing) {
+            update(false /* updateAlways */);
+        }
     };
 
     public boolean isTrustManaged() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 0500a54..004ff29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -21,13 +21,19 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static java.lang.Thread.sleep;
+
 import android.content.Intent;
 import android.metrics.LogMaker;
 import android.support.test.filters.SmallTest;
@@ -66,10 +72,10 @@
         mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
         mHost = mock(QSTileHost.class);
         when(mHost.indexOf(spec)).thenReturn(POSITION);
-        mTestableLooper.runWithLooper(() -> {
-            mTile = new TileImpl(mHost);
-            mTile.setTileSpec(spec);
-        });
+
+        mTile = spy(new TileImpl(mHost));
+        mTile.mHandler = mTile.new H(mTestableLooper.getLooper());
+        mTile.setTileSpec(spec);
     }
 
     @Test
@@ -94,12 +100,38 @@
     public void testPopulate() {
         LogMaker maker = mock(LogMaker.class);
         when(maker.setSubtype(anyInt())).thenReturn(maker);
+        when(maker.addTaggedData(anyInt(), any())).thenReturn(maker);
         mTile.getState().value = true;
         mTile.populate(maker);
         verify(maker).addTaggedData(eq(FIELD_QS_VALUE), eq(1));
         verify(maker).addTaggedData(eq(FIELD_QS_POSITION), eq(POSITION));
     }
 
+    @Test
+    public void testStaleTimeout() throws InterruptedException {
+        when(mTile.getStaleTimeout()).thenReturn(5l);
+        clearInvocations(mTile);
+
+        mTile.handleRefreshState(null);
+        mTestableLooper.processAllMessages();
+        verify(mTile, never()).handleStale();
+
+        sleep(10);
+        mTestableLooper.processAllMessages();
+        verify(mTile).handleStale();
+    }
+
+    @Test
+    public void testStaleListening() {
+        mTile.handleStale();
+        mTestableLooper.processAllMessages();
+        verify(mTile).handleSetListening(eq(true));
+
+        mTile.handleRefreshState(null);
+        mTestableLooper.processAllMessages();
+        verify(mTile).handleSetListening(eq(false));
+    }
+
     private class TileLogMatcher implements ArgumentMatcher<LogMaker> {
 
         private final int mCategory;
@@ -164,7 +196,7 @@
         }
 
         @Override
-        protected void setListening(boolean listening) {
+        protected void handleSetListening(boolean listening) {
 
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index b6827ea..88fa659 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -121,7 +121,7 @@
         mDefaultNotificationChannel = new NotificationChannel(
                 NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME,
                 NotificationManager.IMPORTANCE_LOW);
-        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, 0, 0,
+        mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
                 new Notification(), UserHandle.CURRENT, null, 0);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
index 12a4399..f8fd53d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationInflaterTest.java
@@ -185,7 +185,9 @@
         countDownLatch.await();
     }
 
+    /* Cancelling requires us to be on the UI thread otherwise we might have a race */
     @Test
+    @UiThreadTest
     public void testSupersedesExistingTask() throws Exception {
         mNotificationInflater.inflateNotificationViews();
         mNotificationInflater.setIsLowPriority(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
index ed1491d3..500d620 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
@@ -71,6 +71,24 @@
     }
 
     @Test
+    public void testInvisibleViews() {
+        View left = mockViewAt(0, 0, 10, 10);
+        View right = mockViewAt(20, 0, 10, 10);
+        when(left.getVisibility()).thenReturn(View.INVISIBLE);
+
+        mNearestTouchFrame.addView(left);
+        mNearestTouchFrame.addView(right);
+        mNearestTouchFrame.onMeasure(0, 0);
+
+        MotionEvent ev = MotionEvent.obtain(0, 0, 0,
+                12 /* x */, 5 /* y */, 0);
+        mNearestTouchFrame.onTouchEvent(ev);
+        verify(left, never()).onTouchEvent(eq(ev));
+        verify(right, never()).onTouchEvent(eq(ev));
+        ev.recycle();
+    }
+
+    @Test
     public void testHorizontalSelection_Left() {
         View left = mockViewAt(0, 0, 10, 10);
         View right = mockViewAt(20, 0, 10, 10);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
index a915cb2..b22a646 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
@@ -27,7 +27,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.Log;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
@@ -145,7 +144,6 @@
     @Test
     @RunWithLooper
     public void testSortOrder() {
-        Log.d("TestTest", "Config " + mContext.getResources().getConfiguration().uiMode);
         Object def = new Object();
         Object uiMode = new Object();
         Object tuner = new Object();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java
index 66a8561..ec994a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/IconLoggerImplTest.java
@@ -32,6 +32,7 @@
 import static java.lang.Thread.sleep;
 
 import android.metrics.LogMaker;
+import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.MessageHandler;
@@ -48,6 +49,7 @@
 
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
+@SmallTest
 public class IconLoggerImplTest extends SysuiTestCase {
 
     private MetricsLogger mMetricsLogger;
@@ -172,4 +174,4 @@
             return true;
         }));
     }
-}
\ No newline at end of file
+}
diff --git a/packages/overlays/SysuiDarkThemeOverlay/res/color/qs_detail_progress_track.xml b/packages/overlays/SysuiDarkThemeOverlay/res/color/qs_detail_progress_track.xml
new file mode 100644
index 0000000..c56382e
--- /dev/null
+++ b/packages/overlays/SysuiDarkThemeOverlay/res/color/qs_detail_progress_track.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- I really don't want to define this, but the View that uses this asset uses both the
+         light and dark accent colors. -->
+    <item android:alpha="0.6" android:drawable="@*android:color/accent_device_default_light" />
+</selector>
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 9fbec70..8156025 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4300,6 +4300,17 @@
     // OS: O MR
     FIELD_NUM_STATUS_ICONS = 1095;
 
+    // ACTION: Logged when user tries to pair a Bluetooth device without name from Settings app
+    // CATEGORY: SETTINGS
+    // OS: O MR
+    ACTION_SETTINGS_BLUETOOTH_PAIR_DEVICES_WITHOUT_NAMES = 1096;
+
+    // FIELD - Whether smart suggestion ranking was enabled or not
+    // Type: int encoded boolean
+    // CATEGORY: SETTINGS
+    // OS: O MR
+    FIELD_SETTINGS_SMART_SUGGESTIONS_ENABLED = 1097;
+
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
   }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 20ccee2..512e128 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -533,9 +533,9 @@
     /**
      * Updates the last fill selection when an dataset authentication was selected.
      */
-    void setDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId) {
+    void logDatasetAuthenticationSelected(@Nullable String selectedDataset, int sessionId) {
         synchronized (mLock) {
-            if (isValidEventLocked("setDatasetAuthenticationSelected()", sessionId)) {
+            if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset));
             }
@@ -545,9 +545,9 @@
     /**
      * Updates the last fill selection when an save Ui is shown.
      */
-    void setSaveShown(int sessionId) {
+    void logSaveShown(int sessionId) {
         synchronized (mLock) {
-            if (isValidEventLocked("setSaveShown()", sessionId)) {
+            if (isValidEventLocked("logSaveShown()", sessionId)) {
                 mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null));
             }
         }
@@ -556,7 +556,7 @@
     /**
      * Updates the last fill response when a dataset was selected.
      */
-    void setDatasetSelected(@Nullable String selectedDataset, int sessionId) {
+    void logDatasetSelected(@Nullable String selectedDataset, int sessionId) {
         synchronized (mLock) {
             if (isValidEventLocked("setDatasetSelected()", sessionId)) {
                 mEventHistory.addEvent(new Event(Event.TYPE_DATASET_SELECTED, selectedDataset));
@@ -624,7 +624,7 @@
 
     void destroySessionsLocked() {
         if (mSessions.size() == 0) {
-            mUi.destroyAll(AutofillManager.NO_SESSION, null, null);
+            mUi.destroyAll(null, null);
             return;
         }
         while (mSessions.size() > 0) {
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index dd98053..f315148 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -387,8 +387,10 @@
                 @Override
                 public void executeMessage(Message message) {
                     if (mDestroyed) {
-                        Slog.w(LOG_TAG, "Not handling " + message + " as service for "
-                                + mComponentName + " is already destroyed");
+                        if (sVerbose) {
+                            Slog.v(LOG_TAG, "Not handling " + message + " as service for "
+                                    + mComponentName + " is already destroyed");
+                        }
                         return;
                     }
                     switch (message.what) {
@@ -574,6 +576,13 @@
 
         @Override
         public void run() {
+            synchronized (mLock) {
+                if (isCancelledLocked()) {
+                    // TODO(b/653742740): we should probably return here, but for now we're justing
+                    // logging to confirm this is the problem if it happens again.
+                    Slog.e(LOG_TAG, "run() called after canceled: " + mRequest);
+                }
+            }
             final RemoteFillService remoteService = getService();
             if (remoteService != null) {
                 try {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 95db603..e0e379f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -268,7 +268,7 @@
             value = state.getCurrentValue();
             if (value == null) {
                 if (sDebug) Slog.d(TAG, "getValue(): no current value for " + id);
-                value = getValueFromContexts(id);
+                value = getValueFromContextsLocked(id);
             }
         }
         if (value != null) {
@@ -276,7 +276,7 @@
                 return value.getTextValue().toString();
             }
             if (value.isList()) {
-                final CharSequence[] options = getAutofillOptionsFromContexts(id);
+                final CharSequence[] options = getAutofillOptionsFromContextsLocked(id);
                 if (options != null) {
                     final int index = value.getListValue();
                     final CharSequence option = options[index];
@@ -339,21 +339,21 @@
      * Cancels the last request sent to the {@link #mRemoteFillService}.
      */
     private void cancelCurrentRequestLocked() {
-        int canceledRequest = mRemoteFillService.cancelCurrentRequest();
+        final int canceledRequest = mRemoteFillService.cancelCurrentRequest();
 
         // Remove the FillContext as there will never be a response for the service
         if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) {
-            int numContexts = mContexts.size();
+            final int numContexts = mContexts.size();
 
             // It is most likely the last context, hence search backwards
             for (int i = numContexts - 1; i >= 0; i--) {
                 if (mContexts.get(i).getRequestId() == canceledRequest) {
+                    if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest);
                     mContexts.remove(i);
                     break;
                 }
             }
         }
-
     }
 
     /**
@@ -456,14 +456,7 @@
             }
         }
         if (response == null) {
-            if (sVerbose) Slog.v(TAG, "canceling session " + id + " when server returned null");
-            if ((requestFlags & FLAG_MANUAL_REQUEST) != 0) {
-                getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
-            }
-            mService.resetLastResponse();
-            // Nothing to be done, but need to notify client.
-            notifyUnavailableToClient();
-            removeSelf();
+            processNullResponseLocked(requestFlags);
             return;
         }
 
@@ -586,17 +579,13 @@
     public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) {
         final Intent fillInIntent;
         synchronized (mLock) {
-            synchronized (mLock) {
-                if (mDestroyed) {
-                    Slog.w(TAG, "Call to Session#authenticate() rejected - session: "
-                            + id + " destroyed");
-                    return;
-                }
+            if (mDestroyed) {
+                Slog.w(TAG, "Call to Session#authenticate() rejected - session: "
+                        + id + " destroyed");
+                return;
             }
-            fillInIntent = createAuthFillInIntent(
-                    getFillContextByRequestIdLocked(requestId).getStructure(), extras);
+            fillInIntent = createAuthFillInIntentLocked(requestId, extras);
         }
-
         mService.setAuthenticationSelected(id);
 
         final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex);
@@ -620,7 +609,7 @@
                 return;
             }
         }
-        mHandlerCaller.getHandler().post(() -> autoFill(requestId, datasetIndex, dataset));
+        mHandlerCaller.getHandler().post(() -> autoFill(requestId, datasetIndex, dataset, true));
     }
 
     // AutoFillUiCallback
@@ -748,16 +737,24 @@
         }
 
         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
+        if (sDebug) Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result);
         if (result instanceof FillResponse) {
             final FillResponse response = (FillResponse) result;
             mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
             replaceResponseLocked(authenticatedResponse, response);
         } else if (result instanceof Dataset) {
+            // TODO: add proper metric
             if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
                 final Dataset dataset = (Dataset) result;
                 authenticatedResponse.getDatasets().set(datasetIdx, dataset);
-                autoFill(requestId, datasetIdx, dataset);
+                autoFill(requestId, datasetIdx, dataset, false);
             }
+        } else {
+            if (result != null) {
+                Slog.w(TAG, "service returned invalid auth type: " + result);
+            }
+            // TODO: add proper metric (on else)
+            processNullResponseLocked(0);
         }
     }
 
@@ -845,7 +842,7 @@
 
                 AutofillValue value = viewState.getCurrentValue();
                 if (value == null || value.isEmpty()) {
-                    final AutofillValue initialValue = getValueFromContexts(id);
+                    final AutofillValue initialValue = getValueFromContextsLocked(id);
                     if (initialValue != null) {
                         if (sDebug) {
                             Slog.d(TAG, "Value of required field " + id + " didn't change; "
@@ -899,7 +896,7 @@
                         }
                     } else {
                         // Update current values cache based on initial value
-                        final AutofillValue initialValue = getValueFromContexts(id);
+                        final AutofillValue initialValue = getValueFromContextsLocked(id);
                         if (sDebug) {
                             Slog.d(TAG, "no current value for " + id + "; initial value is "
                                     + initialValue);
@@ -970,11 +967,11 @@
                 }
 
                 if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!");
-                mService.setSaveShown(id);
+                mService.logSaveShown(id);
                 final IAutoFillManagerClient client = getClient();
-                mPendingSaveUi = new PendingUi(mActivityToken);
+                mPendingSaveUi = new PendingUi(mActivityToken, id, client);
                 getUiForShowing().showSaveUi(mService.getServiceLabel(), saveInfo,
-                        valueFinder, mPackageName, this, mPendingSaveUi, id, client);
+                        valueFinder, mPackageName, this, mPendingSaveUi);
                 if (client != null) {
                     try {
                         client.setSaveUiState(id, true);
@@ -1006,7 +1003,7 @@
      * Gets the latest non-empty value for the given id in the autofill contexts.
      */
     @Nullable
-    private AutofillValue getValueFromContexts(AutofillId id) {
+    private AutofillValue getValueFromContextsLocked(AutofillId id) {
         final int numContexts = mContexts.size();
         for (int i = numContexts - 1; i >= 0; i--) {
             final FillContext context = mContexts.get(i);
@@ -1028,7 +1025,7 @@
      * Gets the latest autofill options for the given id in the autofill contexts.
      */
     @Nullable
-    private CharSequence[] getAutofillOptionsFromContexts(AutofillId id) {
+    private CharSequence[] getAutofillOptionsFromContextsLocked(AutofillId id) {
         final int numContexts = mContexts.size();
 
         for (int i = numContexts - 1; i >= 0; i--) {
@@ -1053,6 +1050,11 @@
 
         if (sVerbose) Slog.v(TAG, "callSaveLocked(): mViewStates=" + mViewStates);
 
+        if (mContexts == null) {
+            Slog.w(TAG, "callSaveLocked(): no contexts");
+            return;
+        }
+
         final int numContexts = mContexts.size();
 
         for (int contextNum = 0; contextNum < numContexts; contextNum++) {
@@ -1409,6 +1411,17 @@
         processResponseLocked(newResponse, 0);
     }
 
+    private void processNullResponseLocked(int flags) {
+        if (sVerbose) Slog.v(TAG, "canceling session " + id + " when server returned null");
+        if ((flags & FLAG_MANUAL_REQUEST) != 0) {
+            getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this);
+        }
+        mService.resetLastResponse();
+        // Nothing to be done, but need to notify client.
+        notifyUnavailableToClient();
+        removeSelf();
+    }
+
     private void processResponseLocked(@NonNull FillResponse newResponse, int flags) {
         // Make sure we are hiding the UI which will be shown
         // only if handling the current response requires it.
@@ -1523,7 +1536,7 @@
         return viewState;
     }
 
-    void autoFill(int requestId, int datasetIndex, Dataset dataset) {
+    void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent) {
         synchronized (mLock) {
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#autoFill() rejected - session: "
@@ -1532,17 +1545,18 @@
             }
             // Autofill it directly...
             if (dataset.getAuthentication() == null) {
-                mService.setDatasetSelected(dataset.getId(), id);
+                if (generateEvent) {
+                    mService.logDatasetSelected(dataset.getId(), id);
+                }
 
                 autoFillApp(dataset);
                 return;
             }
 
             // ...or handle authentication.
-            mService.setDatasetAuthenticationSelected(dataset.getId(), id);
+            mService.logDatasetAuthenticationSelected(dataset.getId(), id);
             setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
-            final Intent fillInIntent = createAuthFillInIntent(
-                    getFillContextByRequestIdLocked(requestId).getStructure(), mClientState);
+            final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
 
             final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
                     datasetIndex);
@@ -1556,9 +1570,16 @@
         }
     }
 
-    private Intent createAuthFillInIntent(AssistStructure structure, Bundle extras) {
+    private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
         final Intent fillInIntent = new Intent();
-        fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, structure);
+
+        final FillContext context = getFillContextByRequestIdLocked(requestId);
+        if (context == null) {
+            // TODO(b/653742740): this will crash system_server. We need to handle it, but we're
+            // keeping it crashing for now so we can diagnose when it happens again
+            Slog.wtf(TAG, "no FillContext for requestId" + requestId + "; mContexts= " + mContexts);
+        }
+        fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
         fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
         return fillInIntent;
     }
@@ -1696,7 +1717,7 @@
         if (mDestroyed) {
             return null;
         }
-        mUi.destroyAll(id, getClient(), this);
+        mUi.destroyAll(mPendingSaveUi, this);
         mUi.clearCallback(this);
         mDestroyed = true;
         mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
@@ -1712,7 +1733,7 @@
 
         mPendingSaveUi = null;
         removeSelfLocked();
-        mUi.destroyAll(id, getClient(), this);
+        mUi.destroyAll(mPendingSaveUi, this);
     }
 
     /**
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 7febf83..a6f6713 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -35,7 +35,6 @@
 import android.util.Slog;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
-import android.view.autofill.IAutoFillManagerClient;
 import android.view.autofill.IAutofillWindowPresenter;
 import android.widget.Toast;
 
@@ -247,8 +246,7 @@
      */
     public void showSaveUi(@NonNull CharSequence providerLabel, @NonNull SaveInfo info,
             @NonNull ValueFinder valueFinder, @NonNull String packageName,
-            @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingUi,
-            int sessionId, @Nullable IAutoFillManagerClient client) {
+            @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi) {
         if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info);
         int numIds = 0;
         numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
@@ -263,8 +261,8 @@
                 return;
             }
             hideAllUiThread(callback);
-            mSaveUi = new SaveUi(mContext, pendingUi, providerLabel, info, valueFinder,
-                    mOverlayControl, client, new SaveUi.OnSaveListener() {
+            mSaveUi = new SaveUi(mContext, pendingSaveUi, providerLabel, info, valueFinder,
+                    mOverlayControl, new SaveUi.OnSaveListener() {
                 @Override
                 public void onSave() {
                     log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
@@ -272,7 +270,7 @@
                     if (mCallback != null) {
                         mCallback.save();
                     }
-                    destroySaveUiUiThread(sessionId, client);
+                    destroySaveUiUiThread(pendingSaveUi);
                 }
 
                 @Override
@@ -290,7 +288,7 @@
                     if (mCallback != null) {
                         mCallback.cancelSave();
                     }
-                    destroySaveUiUiThread(sessionId, client);
+                    destroySaveUiUiThread(pendingSaveUi);
                 }
 
                 @Override
@@ -331,9 +329,9 @@
     /**
      * Destroy all UI affordances.
      */
-    public void destroyAll(int sessionId, @Nullable IAutoFillManagerClient client,
+    public void destroyAll(@Nullable PendingUi pendingSaveUi,
             @Nullable AutoFillUiCallback callback) {
-        mHandler.post(() -> destroyAllUiThread(sessionId, client, callback));
+        mHandler.post(() -> destroyAllUiThread(pendingSaveUi, callback));
     }
 
     public void dump(PrintWriter pw) {
@@ -363,18 +361,20 @@
     }
 
     @android.annotation.UiThread
-    private void hideSaveUiUiThread(@Nullable AutoFillUiCallback callback) {
+    @Nullable
+    private PendingUi hideSaveUiUiThread(@Nullable AutoFillUiCallback callback) {
         if (sVerbose) {
             Slog.v(TAG, "hideSaveUiUiThread(): mSaveUi=" + mSaveUi + ", callback=" + callback
                     + ", mCallback=" + mCallback);
         }
         if (mSaveUi != null && (callback == null || callback == mCallback)) {
-            mSaveUi.hide();
+            return mSaveUi.hide();
         }
+        return null;
     }
 
     @android.annotation.UiThread
-    private void destroySaveUiUiThread(int sessionId, @Nullable IAutoFillManagerClient client) {
+    private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi) {
         if (mSaveUi == null) {
             // Calling destroySaveUiUiThread() twice is normal - it usually happens when the
             // first call is made after the SaveUI is hidden and the second when the session is
@@ -383,13 +383,13 @@
             return;
         }
 
-        if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): id=" + sessionId);
+        if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): " + pendingSaveUi);
         mSaveUi.destroy();
         mSaveUi = null;
-        if (client != null) {
+        if (pendingSaveUi != null) {
             try {
                 if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): notifying client");
-                client.setSaveUiState(sessionId, false);
+                pendingSaveUi.client.setSaveUiState(pendingSaveUi.id, false);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error notifying client to set save UI state to hidden: " + e);
             }
@@ -397,15 +397,22 @@
     }
 
     @android.annotation.UiThread
-    private void destroyAllUiThread(int sessionId, @Nullable IAutoFillManagerClient client,
+    private void destroyAllUiThread(@Nullable PendingUi pendingSaveUi,
             @Nullable AutoFillUiCallback callback) {
         hideFillUiUiThread(callback);
-        destroySaveUiUiThread(sessionId, client);
+        destroySaveUiUiThread(pendingSaveUi);
     }
 
     @android.annotation.UiThread
     private void hideAllUiThread(@Nullable AutoFillUiCallback callback) {
         hideFillUiUiThread(callback);
-        hideSaveUiUiThread(callback);
+        final PendingUi pendingSaveUi = hideSaveUiUiThread(callback);
+        if (pendingSaveUi != null && pendingSaveUi.getState() == PendingUi.STATE_FINISHED) {
+            if (sDebug) {
+                Slog.d(TAG, "hideAllUiThread(): "
+                        + "destroying Save UI because pending restoration is finished");
+            }
+            destroySaveUiUiThread(pendingSaveUi);
+        }
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 24f3b33..371e74d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -229,6 +229,13 @@
     public void setFilterText(@Nullable String filterText) {
         throwIfDestroyed();
         if (mAdapter == null) {
+            // ViewState doesn't not support filtering - typically when it's for an authenticated
+            // FillResponse.
+            if (TextUtils.isEmpty(filterText)) {
+                mCallback.requestShowFillUi(mContentWidth, mContentHeight, mWindowPresenter);
+            } else {
+                mCallback.requestHideFillUi();
+            }
             return;
         }
 
@@ -510,8 +517,9 @@
                         final ViewItem item = mAllItems.get(i);
                         final String value = item.getValue();
                         // No value, i.e. null, matches any filter
-                        if (value == null
-                                || value.toLowerCase().startsWith(constraintLowerCase)) {
+                        if ((value == null && item.mDataset.getAuthentication() == null)
+                                || (value != null
+                                        && value.toLowerCase().startsWith(constraintLowerCase))) {
                             filteredItems.add(item);
                         }
                     }
@@ -525,9 +533,11 @@
                     final boolean resultCountChanged;
                     final int oldItemCount = mFilteredItems.size();
                     mFilteredItems.clear();
-                    @SuppressWarnings("unchecked")
-                    final List<ViewItem> items = (List<ViewItem>) results.values;
-                    mFilteredItems.addAll(items);
+                    if (results.count > 0) {
+                        @SuppressWarnings("unchecked")
+                        final List<ViewItem> items = (List<ViewItem>) results.values;
+                        mFilteredItems.addAll(items);
+                    }
                     resultCountChanged = (oldItemCount != mFilteredItems.size());
                     if (resultCountChanged) {
                         announceSearchResultIfNeeded();
diff --git a/services/autofill/java/com/android/server/autofill/ui/PendingUi.java b/services/autofill/java/com/android/server/autofill/ui/PendingUi.java
index 87263ed..0851d3b 100644
--- a/services/autofill/java/com/android/server/autofill/ui/PendingUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/PendingUi.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.os.IBinder;
 import android.util.DebugUtils;
+import android.view.autofill.IAutoFillManagerClient;
 
 /**
  * Helper class used to handle a pending Autofill affordance such as the Save UI.
@@ -34,15 +35,19 @@
 
     private final IBinder mToken;
     private int mState;
+    public final int id;
+    public final IAutoFillManagerClient client;
 
     /**
      * Default constructor.
      *
      * @param token token used to identify this pending UI.
      */
-    public PendingUi(@NonNull IBinder token) {
+    public PendingUi(@NonNull IBinder token, int id, @NonNull IAutoFillManagerClient client) {
         mToken = token;
         mState = STATE_CREATED;
+        this.id = id;
+        this.client = client;
     }
 
     /**
@@ -76,7 +81,7 @@
 
     @Override
     public String toString() {
-        return "PendingUi: [token=" + mToken + ", state="
+        return "PendingUi: [token=" + mToken + ", id=" + id + ", state="
                 + DebugUtils.flagsToString(PendingUi.class, "STATE_", mState) + "]";
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 67c1b8c..160c84c 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -123,7 +123,7 @@
     SaveUi(@NonNull Context context, @NonNull PendingUi pendingUi,
            @NonNull CharSequence providerLabel, @NonNull SaveInfo info,
            @NonNull ValueFinder valueFinder, @NonNull OverlayControl overlayControl,
-           @NonNull IAutoFillManagerClient client, @NonNull OnSaveListener listener) {
+           @NonNull OnSaveListener listener) {
         mPendingUi= pendingUi;
         mListener = new OneTimeListener(listener);
         mOverlayControl = overlayControl;
@@ -206,7 +206,7 @@
                             final IBinder token = mPendingUi.getToken();
                             intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
                             try {
-                                client.startIntentSender(pendingIntent.getIntentSender(),
+                                pendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
                                         intent);
                                 mPendingUi.setState(PendingUi.STATE_PENDING);
                                 if (sDebug) {
@@ -318,13 +318,14 @@
         mOverlayControl.hideOverlays();
    }
 
-    void hide() {
+    PendingUi hide() {
         if (sVerbose) Slog.v(TAG, "Hiding save dialog.");
         try {
             mDialog.hide();
         } finally {
             mOverlayControl.showOverlays();
         }
+        return mPendingUi;
     }
 
     void destroy() {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 04b2112..f1ea853 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4343,11 +4343,13 @@
             int currentScore, NetworkMisc networkMisc) {
         enforceConnectivityInternalPermission();
 
+        LinkProperties lp = new LinkProperties(linkProperties);
+        lp.ensureDirectlyConnectedRoutes();
         // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network
         // satisfies mDefaultRequest.
         final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
-                new Network(reserveNetId()), new NetworkInfo(networkInfo), new LinkProperties(
-                linkProperties), new NetworkCapabilities(networkCapabilities), currentScore,
+                new Network(reserveNetId()), new NetworkInfo(networkInfo), lp,
+                new NetworkCapabilities(networkCapabilities), currentScore,
                 mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this);
         synchronized (this) {
             nai.networkMonitor.systemReady = mSystemReady;
@@ -4657,6 +4659,8 @@
         synchronized (nai) {
             nai.linkProperties = newLp;
         }
+        // msg.obj is already a defensive copy.
+        nai.linkProperties.ensureDirectlyConnectedRoutes();
         if (nai.everConnected) {
             updateLinkProperties(nai, oldLp);
         }
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 16f4d04..05c7504 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -403,7 +403,9 @@
     private final BroadcastReceiver mInteractivityReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            updateInteractivityLocked();
+            synchronized (DeviceIdleController.this) {
+                updateInteractivityLocked();
+            }
         }
     };
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index dad3125..85fa7a1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13231,7 +13231,6 @@
                 return;
             }
         }
-
         // We are now ready to launch the assist activity.
         IResultReceiver sendReceiver = null;
         Bundle sendBundle = null;
@@ -13261,17 +13260,24 @@
             return;
         }
 
-        long ident = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
-            pae.intent.replaceExtras(pae.extras);
-            pae.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_SINGLE_TOP
-                    | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            closeSystemDialogs("assist");
-            try {
-                mContext.startActivityAsUser(pae.intent, new UserHandle(pae.userHandle));
-            } catch (ActivityNotFoundException e) {
-                Slog.w(TAG, "No activity to handle assist action.", e);
+            if (TextUtils.equals(pae.intent.getAction(),
+                    android.service.voice.VoiceInteractionService.SERVICE_INTERFACE)) {
+                pae.intent.putExtras(pae.extras);
+                mContext.startServiceAsUser(pae.intent, new UserHandle(pae.userHandle));
+            } else {
+                pae.intent.replaceExtras(pae.extras);
+                pae.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_SINGLE_TOP
+                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                closeSystemDialogs("assist");
+
+                try {
+                    mContext.startActivityAsUser(pae.intent, new UserHandle(pae.userHandle));
+                } catch (ActivityNotFoundException e) {
+                    Slog.w(TAG, "No activity to handle assist action.", e);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index d62d935..791b2c0 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3007,13 +3007,6 @@
             // Ensure the task/activity being brought forward is not the assistant
             return false;
         }
-        final boolean isFullscreen = toFrontTask != null
-                ? toFrontTask.containsOnlyFullscreenActivities()
-                : toFrontActivity.fullscreen;
-        if (!isFullscreen) {
-            // Ensure the task/activity being brought forward is fullscreen
-            return false;
-        }
         return true;
     }
 
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 1bbb068..eadc8a6 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1112,19 +1112,6 @@
         return intent != null ? intent : affinityIntent;
     }
 
-    /**
-     * @return Whether there are only fullscreen activities in this task.
-     */
-    boolean containsOnlyFullscreenActivities() {
-        for (int i = 0; i < mActivities.size(); i++) {
-            final ActivityRecord r = mActivities.get(i);
-            if (!r.finishing && !r.fullscreen) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     /** Returns the first non-finishing activity from the root. */
     ActivityRecord getRootActivity() {
         for (int i = 0; i < mActivities.size(); i++) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 788867f..5eafe5f 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -30,6 +30,10 @@
 import android.net.LinkProperties;
 import android.net.NetworkStats;
 import android.net.RouteInfo;
+import android.net.netlink.ConntrackMessage;
+import android.net.netlink.NetlinkConstants;
+import android.net.netlink.NetlinkSocket;
+import android.net.util.IpUtils;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.Looper;
@@ -37,10 +41,12 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.OsConstants;
 import android.text.TextUtils;
-import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
 
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
 
 import java.net.Inet4Address;
 import java.net.Inet6Address;
@@ -63,6 +69,7 @@
  */
 public class OffloadController {
     private static final String TAG = OffloadController.class.getSimpleName();
+    private static final boolean DBG = false;
     private static final String ANYIP = "0.0.0.0";
     private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
 
@@ -96,6 +103,9 @@
     // includes upstream interfaces that have a quota set.
     private HashMap<String, Long> mInterfaceQuotas = new HashMap<>();
 
+    private int mNatUpdateCallbacksReceived;
+    private int mNatUpdateNetlinkErrors;
+
     public OffloadController(Handler h, OffloadHardwareInterface hwi,
             ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
         mHandler = h;
@@ -115,12 +125,12 @@
         }
     }
 
-    public void start() {
-        if (started()) return;
+    public boolean start() {
+        if (started()) return true;
 
         if (isOffloadDisabled()) {
             mLog.i("tethering offload disabled");
-            return;
+            return false;
         }
 
         if (!mConfigInitialized) {
@@ -128,11 +138,14 @@
             if (!mConfigInitialized) {
                 mLog.i("tethering offload config not supported");
                 stop();
-                return;
+                return false;
             }
         }
 
         mControlInitialized = mHwInterface.initOffloadControl(
+                // OffloadHardwareInterface guarantees that these callback
+                // methods are called on the handler passed to it, which is the
+                // same as mHandler, as coordinated by the setup in Tethering.
                 new OffloadHardwareInterface.ControlCallback() {
                     @Override
                     public void onStarted() {
@@ -203,15 +216,20 @@
                                                    String srcAddr, int srcPort,
                                                    String dstAddr, int dstPort) {
                         if (!started()) return;
-                        mLog.log(String.format("NAT timeout update: %s (%s,%s) -> (%s,%s)",
-                                proto, srcAddr, srcPort, dstAddr, dstPort));
+                        updateNatTimeout(proto, srcAddr, srcPort, dstAddr, dstPort);
                     }
                 });
-        if (!mControlInitialized) {
+
+        final boolean isStarted = started();
+        if (!isStarted) {
             mLog.i("tethering offload control not supported");
             stop();
+        } else {
+            mLog.log("tethering offload started");
+            mNatUpdateCallbacksReceived = 0;
+            mNatUpdateNetlinkErrors = 0;
         }
-        mLog.log("tethering offload started");
+        return isStarted;
     }
 
     public void stop() {
@@ -227,6 +245,10 @@
         if (wasStarted) mLog.log("tethering offload stopped");
     }
 
+    private boolean started() {
+        return mConfigInitialized && mControlInitialized;
+    }
+
     private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
         @Override
         public NetworkStats getTetherStats(int how) {
@@ -402,10 +424,6 @@
                 mContentResolver, TETHER_OFFLOAD_DISABLED, defaultDisposition) != 0);
     }
 
-    private boolean started() {
-        return mConfigInitialized && mControlInitialized;
-    }
-
     private boolean pushUpstreamParameters(String prevUpstream) {
         final String iface = currentUpstreamInterface();
 
@@ -516,10 +534,113 @@
             pw.println("Offload disabled");
             return;
         }
-        pw.println("Offload HALs " + (started() ? "started" : "not started"));
+        final boolean isStarted = started();
+        pw.println("Offload HALs " + (isStarted ? "started" : "not started"));
         LinkProperties lp = mUpstreamLinkProperties;
         String upstream = (lp != null) ? lp.getInterfaceName() : null;
         pw.println("Current upstream: " + upstream);
         pw.println("Exempt prefixes: " + mLastLocalPrefixStrs);
+        pw.println("NAT timeout update callbacks received during the "
+                + (isStarted ? "current" : "last")
+                + " offload session: "
+                + mNatUpdateCallbacksReceived);
+        pw.println("NAT timeout update netlink errors during the "
+                + (isStarted ? "current" : "last")
+                + " offload session: "
+                + mNatUpdateNetlinkErrors);
+    }
+
+    private void updateNatTimeout(
+            int proto, String srcAddr, int srcPort, String dstAddr, int dstPort) {
+        final String protoName = protoNameFor(proto);
+        if (protoName == null) {
+            mLog.e("Unknown NAT update callback protocol: " + proto);
+            return;
+        }
+
+        final Inet4Address src = parseIPv4Address(srcAddr);
+        if (src == null) {
+            mLog.e("Failed to parse IPv4 address: " + srcAddr);
+            return;
+        }
+
+        if (!IpUtils.isValidUdpOrTcpPort(srcPort)) {
+            mLog.e("Invalid src port: " + srcPort);
+            return;
+        }
+
+        final Inet4Address dst = parseIPv4Address(dstAddr);
+        if (dst == null) {
+            mLog.e("Failed to parse IPv4 address: " + dstAddr);
+            return;
+        }
+
+        if (!IpUtils.isValidUdpOrTcpPort(dstPort)) {
+            mLog.e("Invalid dst port: " + dstPort);
+            return;
+        }
+
+        mNatUpdateCallbacksReceived++;
+        if (DBG) {
+            mLog.log(String.format("NAT timeout update: %s (%s, %s) -> (%s, %s)",
+                     protoName, srcAddr, srcPort, dstAddr, dstPort));
+        }
+
+        final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto);
+        final byte[] msg = ConntrackMessage.newIPv4TimeoutUpdateRequest(
+                proto, src, srcPort, dst, dstPort, timeoutSec);
+
+        try {
+            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
+        } catch (ErrnoException e) {
+            mNatUpdateNetlinkErrors++;
+            mLog.e("Error updating NAT conntrack entry: " + e
+                    + ", msg: " + NetlinkConstants.hexify(msg));
+            mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived);
+            mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors);
+        }
+    }
+
+    private static Inet4Address parseIPv4Address(String addrString) {
+        try {
+            final InetAddress ip = InetAddress.parseNumericAddress(addrString);
+            // TODO: Consider other sanitization steps here, including perhaps:
+            //           not eql to 0.0.0.0
+            //           not within 169.254.0.0/16
+            //           not within ::ffff:0.0.0.0/96
+            //           not within ::/96
+            // et cetera.
+            if (ip instanceof Inet4Address) {
+                return (Inet4Address) ip;
+            }
+        } catch (IllegalArgumentException iae) {}
+        return null;
+    }
+
+    private static String protoNameFor(int proto) {
+        // OsConstants values are not constant expressions; no switch statement.
+        if (proto == OsConstants.IPPROTO_UDP) {
+            return "UDP";
+        } else if (proto == OsConstants.IPPROTO_TCP) {
+            return "TCP";
+        }
+        return null;
+    }
+
+    private static int connectionTimeoutUpdateSecondsFor(int proto) {
+        // TODO: Replace this with more thoughtful work, perhaps reading from
+        // and maybe writing to any required
+        //
+        //     /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_*
+        //     /proc/sys/net/netfilter/nf_conntrack_udp_timeout{,_stream}
+        //
+        // entries.  TBD.
+        if (proto == OsConstants.IPPROTO_TCP) {
+            // Cf. /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
+            return 432000;
+        } else {
+            // Cf. /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream
+            return 180;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 865a989..553fd8c 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -21,10 +21,12 @@
 import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
 import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
 import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
+import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
 import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.net.util.SharedLog;
+import android.system.OsConstants;
 
 import java.util.ArrayList;
 
@@ -327,13 +329,24 @@
         public void updateTimeout(NatTimeoutUpdate params) {
             handler.post(() -> {
                     controlCb.onNatTimeoutUpdate(
-                        params.proto,
+                        networkProtocolToOsConstant(params.proto),
                         params.src.addr, uint16(params.src.port),
                         params.dst.addr, uint16(params.dst.port));
             });
         }
     }
 
+    private static int networkProtocolToOsConstant(int proto) {
+        switch (proto) {
+            case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
+            case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
+            default:
+                // The caller checks this value and will log an error. Just make
+                // sure it won't collide with valid OsContants.IPPROTO_* values.
+                return -Math.abs(proto);
+        }
+    }
+
     private static class CbResults {
         boolean success;
         String errMsg;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 44e571a..e578485 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1461,8 +1461,18 @@
         if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
             // cancel
             cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
-                    UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
+                    UserHandle.getUserId(uid), REASON_CHANNEL_BANNED,
                     null);
+            if (isUidSystemOrPhone(uid)) {
+                int[] profileIds = mUserProfiles.getCurrentProfileIds();
+                int N = profileIds.length;
+                for (int i = 0; i < N; i++) {
+                    int profileId = profileIds[i];
+                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
+                            profileId, REASON_CHANNEL_BANNED,
+                            null);
+                }
+            }
         }
         mRankingHelper.updateNotificationChannel(pkg, uid, channel);
 
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 3386fe83..f193a19 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -1174,7 +1174,7 @@
             changed |= oldValue != newValue;
         }
         if (changed) {
-            mRankingHandler.requestSort();
+            updateConfig();
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1a0b878..3194c52 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -465,6 +465,8 @@
                             + "from " + currRule.getName() + " to " + defaultRule.name);
                     // update default rule (if locale changed, name of rule will change)
                     AutomaticZenRule defaultAutoRule = createAutomaticZenRule(defaultRule);
+                    // ensure enabled state is carried over from current rule
+                    defaultAutoRule.setEnabled(currRule.isEnabled());
                     updateAutomaticZenRule(ruleId, defaultAutoRule,
                             "locale changed");
                 }
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 5e19b13..40c6639 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -149,8 +149,12 @@
 
             final long token = Binder.clearCallingIdentity();
             try {
-                if (!canUserAllowOemUnlock()) {
-                    throw new SecurityException("User cannot allow OEM unlock");
+                if (!isOemUnlockAllowedByAdmin()) {
+                    throw new SecurityException("Admin does not allow OEM unlock");
+                }
+
+                if (!mOemLock.isOemUnlockAllowedByCarrier()) {
+                    throw new SecurityException("Carrier does not allow OEM unlock");
                 }
 
                 mOemLock.setOemUnlockAllowedByDevice(allowedByUser);
@@ -172,18 +176,6 @@
         }
 
         @Override
-        public boolean canUserAllowOemUnlock() {
-            enforceOemUnlockReadPermission();
-
-            final long token = Binder.clearCallingIdentity();
-            try {
-                return isOemUnlockAllowedByAdmin() && mOemLock.isOemUnlockAllowedByCarrier();
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-
-        @Override
         public boolean isOemUnlockAllowed() {
             enforceOemUnlockReadPermission();
 
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 698d387..0f580d8 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -147,8 +147,13 @@
         // Get the class loader context dependencies.
         // For each code path in the package, this array contains the class loader context that
         // needs to be passed to dexopt in order to ensure correct optimizations.
+        boolean[] pathsWithCode = new boolean[paths.size()];
+        pathsWithCode[0] = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
+        for (int i = 1; i < paths.size(); i++) {
+            pathsWithCode[i] = (pkg.splitFlags[i - 1] & ApplicationInfo.FLAG_HAS_CODE) != 0;
+        }
         String[] classLoaderContexts = DexoptUtils.getClassLoaderContexts(
-                pkg.applicationInfo, sharedLibraries);
+                pkg.applicationInfo, sharedLibraries, pathsWithCode);
 
         // Sanity check that we do not call dexopt with inconsistent data.
         if (paths.size() != classLoaderContexts.length) {
@@ -164,10 +169,15 @@
         int result = DEX_OPT_SKIPPED;
         for (int i = 0; i < paths.size(); i++) {
             // Skip paths that have no code.
-            if ((i == 0 && (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) ||
-                    (i != 0 && (pkg.splitFlags[i - 1] & ApplicationInfo.FLAG_HAS_CODE) == 0)) {
+            if (!pathsWithCode[i]) {
                 continue;
             }
+            if (classLoaderContexts[i] == null) {
+                throw new IllegalStateException("Inconsistent information in the "
+                        + "package structure. A split is marked to contain code "
+                        + "but has no dependency listed. Index=" + i + " path=" + paths.get(i));
+            }
+
             // Append shared libraries with split dependencies for this split.
             String path = paths.get(i);
             if (options.getSplitName() != null) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index c3b93b4..1fa37b9 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -319,9 +319,15 @@
                 if (type == START_TAG) {
                     final String tag = in.getName();
                     if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
-                        final PackageInstallerSession session = PackageInstallerSession.
-                                readFromXml(in, mInternalCallback, mContext, mPm,
-                                        mInstallThread.getLooper(), mSessionsDir);
+                        final PackageInstallerSession session;
+                        try {
+                            session = PackageInstallerSession.readFromXml(in, mInternalCallback,
+                                    mContext, mPm, mInstallThread.getLooper(), mSessionsDir);
+                        } catch (Exception e) {
+                            Slog.e(TAG, "Could not read session", e);
+                            continue;
+                        }
+
                         final long age = System.currentTimeMillis() - session.createdMillis;
 
                         final boolean valid;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index fddbd67..810c399 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -351,7 +351,17 @@
         }
 
         mPrepared = prepared;
-        mSealed = sealed;
+
+        if (sealed) {
+            synchronized (mLock) {
+                try {
+                    sealAndValidateLocked();
+                } catch (PackageManagerException | IOException e) {
+                    destroyInternal();
+                    throw new IllegalArgumentException(e);
+                }
+            }
+        }
 
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -667,11 +677,6 @@
     public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
         Preconditions.checkNotNull(statusReceiver);
 
-        // Cache package manager data without the lock held
-        final PackageInfo installedPkgInfo = mPm.getPackageInfo(
-                params.appPackageName, PackageManager.GET_SIGNATURES
-                        | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
-
         final boolean wasSealed;
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
@@ -696,7 +701,9 @@
             wasSealed = mSealed;
             if (!mSealed) {
                 try {
-                    sealAndValidateLocked(installedPkgInfo);
+                    sealAndValidateLocked();
+                } catch (IOException e) {
+                    throw new IllegalArgumentException(e);
                 } catch (PackageManagerException e) {
                     // Do now throw an exception here to stay compatible with O and older
                     destroyInternal();
@@ -730,18 +737,33 @@
      *
      * <p>The session will be sealed after calling this method even if it failed.
      *
-     * @param pkgInfo The package info for {@link #params}.packagename
+     * @throws PackageManagerException if the session was sealed but something went wrong. If the
+     *                                 session was sealed this is the only possible exception.
      */
-    private void sealAndValidateLocked(@Nullable PackageInfo pkgInfo)
-            throws PackageManagerException {
+    private void sealAndValidateLocked() throws PackageManagerException, IOException {
         assertNoWriteFileTransfersOpenLocked();
+        assertPreparedAndNotDestroyedLocked("sealing of session");
+
+        final PackageInfo pkgInfo = mPm.getPackageInfo(
+                params.appPackageName, PackageManager.GET_SIGNATURES
+                        | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+
+        resolveStageDirLocked();
 
         mSealed = true;
 
         // Verify that stage looks sane with respect to existing application.
         // This currently only ensures packageName, versionCode, and certificate
         // consistency.
-        validateInstallLocked(pkgInfo);
+        try {
+            validateInstallLocked(pkgInfo);
+        } catch (PackageManagerException e) {
+            throw e;
+        } catch (Throwable e) {
+            // Convert all exceptions into package manager exceptions as only those are handled
+            // in the code above
+            throw new PackageManagerException(e);
+        }
 
         // Read transfers from the original owner stay open, but as the session's data
         // cannot be modified anymore, there is no leak of information.
@@ -762,11 +784,6 @@
                     + "the " + Manifest.permission.INSTALL_PACKAGES + " permission");
         }
 
-        // Cache package manager data without the lock held
-        final PackageInfo installedPkgInfo = mPm.getPackageInfo(
-                params.appPackageName, PackageManager.GET_SIGNATURES
-                        | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
-
         // Only install flags that can be verified by the app the session is transferred to are
         // allowed. The parameters can be read via PackageInstaller.SessionInfo.
         if (!params.areHiddenOptionsSet()) {
@@ -778,8 +795,14 @@
             assertPreparedAndNotSealedLocked("transfer");
 
             try {
-                sealAndValidateLocked(installedPkgInfo);
+                sealAndValidateLocked();
+            } catch (IOException e) {
+                throw new IllegalStateException(e);
             } catch (PackageManagerException e) {
+                // Session is sealed but could not be verified, we need to destroy it
+                destroyInternal();
+                dispatchSessionFinished(e.error, ExceptionUtils.getCompleteMessage(e), null);
+
                 throw new IllegalArgumentException("Package is not valid", e);
             }
 
@@ -1539,6 +1562,10 @@
      */
     void write(@NonNull XmlSerializer out, @NonNull File sessionsDir) throws IOException {
         synchronized (mLock) {
+            if (mDestroyed) {
+                return;
+            }
+
             out.startTag(null, TAG_SESSION);
 
             writeIntAttribute(out, ATTR_SESSION_ID, sessionId);
diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java
index 0e3f173..0793b09 100644
--- a/services/core/java/com/android/server/pm/PackageManagerException.java
+++ b/services/core/java/com/android/server/pm/PackageManagerException.java
@@ -40,6 +40,11 @@
         this.error = error;
     }
 
+    public PackageManagerException(Throwable e) {
+        super(e);
+        this.error = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+    }
+
     public static PackageManagerException from(PackageParserException e)
             throws PackageManagerException {
         throw new PackageManagerException(e.error, e.getMessage(), e.getCause());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 20fb350..f594b38 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2711,8 +2711,14 @@
                         // Actual deletion of code and data will be handled by later
                         // reconciliation step
                     } else {
-                        final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
-                        if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
+                        // we still have a disabled system package, but, it still might have
+                        // been removed. check the code path still exists and check there's
+                        // still a package. the latter can happen if an OTA keeps the same
+                        // code path, but, changes the package name.
+                        final PackageSetting disabledPs =
+                                mSettings.getDisabledSystemPkgLPr(ps.name);
+                        if (disabledPs.codePath == null || !disabledPs.codePath.exists()
+                                || disabledPs.pkg == null) {
                             possiblyDeletedUpdatedSystemApps.add(ps.name);
                         }
                     }
@@ -8512,7 +8518,7 @@
                         continue;
                     }
                     if (filterAppAccessLPr(ps, callingUid, userId)) {
-                        return null;
+                        continue;
                     }
                     final PackageInfo pi = generatePackageInfo(ps, flags, userId);
                     if (pi != null) {
@@ -8527,7 +8533,7 @@
                         continue;
                     }
                     if (filterAppAccessLPr(ps, callingUid, userId)) {
-                        return null;
+                        continue;
                     }
                     final PackageInfo pi = generatePackageInfo((PackageSetting)
                             p.mExtras, flags, userId);
@@ -8639,7 +8645,7 @@
                             continue;
                         }
                         if (filterAppAccessLPr(ps, callingUid, userId)) {
-                            return null;
+                            continue;
                         }
                         ai = PackageParser.generateApplicationInfo(ps.pkg, effectiveFlags,
                                 ps.readUserState(userId), userId);
@@ -8665,7 +8671,7 @@
                             continue;
                         }
                         if (filterAppAccessLPr(ps, callingUid, userId)) {
-                            return null;
+                            continue;
                         }
                         ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
                                 ps.readUserState(userId), userId);
@@ -11398,6 +11404,10 @@
                                     + " but expected at " + known.codePathString
                                     + "; ignoring.");
                         }
+                    } else {
+                        throw new PackageManagerException(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+                                "Application package " + pkg.packageName
+                                + " not found; ignoring.");
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e6c6622..48d6cdc 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -128,6 +128,7 @@
  *
  * Method naming convention:
  * <ul>
+ * <li> Methods suffixed with "LAr" should be called within the {@link #mAppRestrictionsLock} lock.
  * <li> Methods suffixed with "LP" should be called within the {@link #mPackagesLock} lock.
  * <li> Methods suffixed with "LR" should be called within the {@link #mRestrictionsLock} lock.
  * <li> Methods suffixed with "LU" should be called within the {@link #mUsersLock} lock.
@@ -232,6 +233,8 @@
     // Short-term lock for internal state, when interaction/sync with PM is not required
     private final Object mUsersLock = LockGuard.installNewLock(LockGuard.INDEX_USER);
     private final Object mRestrictionsLock = new Object();
+    // Used for serializing access to app restriction files
+    private final Object mAppRestrictionsLock = new Object();
 
     private final Handler mHandler;
 
@@ -2328,13 +2331,11 @@
     /**
      * Removes the app restrictions file for a specific package and user id, if it exists.
      */
-    private void cleanAppRestrictionsForPackage(String pkg, int userId) {
-        synchronized (mPackagesLock) {
-            File dir = Environment.getUserSystemDirectory(userId);
-            File resFile = new File(dir, packageToRestrictionsFileName(pkg));
-            if (resFile.exists()) {
-                resFile.delete();
-            }
+    private static void cleanAppRestrictionsForPackageLAr(String pkg, int userId) {
+        File dir = Environment.getUserSystemDirectory(userId);
+        File resFile = new File(dir, packageToRestrictionsFileName(pkg));
+        if (resFile.exists()) {
+            resFile.delete();
         }
     }
 
@@ -2853,9 +2854,9 @@
                 || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
             checkSystemOrRoot("get application restrictions for other user/app " + packageName);
         }
-        synchronized (mPackagesLock) {
+        synchronized (mAppRestrictionsLock) {
             // Read the restrictions from XML
-            return readApplicationRestrictionsLP(packageName, userId);
+            return readApplicationRestrictionsLAr(packageName, userId);
         }
     }
 
@@ -2866,12 +2867,12 @@
         if (restrictions != null) {
             restrictions.setDefusable(true);
         }
-        synchronized (mPackagesLock) {
+        synchronized (mAppRestrictionsLock) {
             if (restrictions == null || restrictions.isEmpty()) {
-                cleanAppRestrictionsForPackage(packageName, userId);
+                cleanAppRestrictionsForPackageLAr(packageName, userId);
             } else {
                 // Write the restrictions to XML
-                writeApplicationRestrictionsLP(packageName, restrictions, userId);
+                writeApplicationRestrictionsLAr(packageName, restrictions, userId);
             }
         }
 
@@ -2894,15 +2895,17 @@
         }
     }
 
-    private Bundle readApplicationRestrictionsLP(String packageName, int userId) {
+    @GuardedBy("mAppRestrictionsLock")
+    private static Bundle readApplicationRestrictionsLAr(String packageName, int userId) {
         AtomicFile restrictionsFile =
                 new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
                         packageToRestrictionsFileName(packageName)));
-        return readApplicationRestrictionsLP(restrictionsFile);
+        return readApplicationRestrictionsLAr(restrictionsFile);
     }
 
     @VisibleForTesting
-    static Bundle readApplicationRestrictionsLP(AtomicFile restrictionsFile) {
+    @GuardedBy("mAppRestrictionsLock")
+    static Bundle readApplicationRestrictionsLAr(AtomicFile restrictionsFile) {
         final Bundle restrictions = new Bundle();
         final ArrayList<String> values = new ArrayList<>();
         if (!restrictionsFile.getBaseFile().exists()) {
@@ -2985,16 +2988,18 @@
         return childBundle;
     }
 
-    private void writeApplicationRestrictionsLP(String packageName,
+    @GuardedBy("mAppRestrictionsLock")
+    private static void writeApplicationRestrictionsLAr(String packageName,
             Bundle restrictions, int userId) {
         AtomicFile restrictionsFile = new AtomicFile(
                 new File(Environment.getUserSystemDirectory(userId),
                         packageToRestrictionsFileName(packageName)));
-        writeApplicationRestrictionsLP(restrictions, restrictionsFile);
+        writeApplicationRestrictionsLAr(restrictions, restrictionsFile);
     }
 
     @VisibleForTesting
-    static void writeApplicationRestrictionsLP(Bundle restrictions, AtomicFile restrictionsFile) {
+    @GuardedBy("mAppRestrictionsLock")
+    static void writeApplicationRestrictionsLAr(Bundle restrictions, AtomicFile restrictionsFile) {
         FileOutputStream fos = null;
         try {
             fos = restrictionsFile.startWrite();
@@ -3238,7 +3243,7 @@
         return -1;
     }
 
-    private String packageToRestrictionsFileName(String packageName) {
+    private static String packageToRestrictionsFileName(String packageName) {
         return RESTRICTIONS_FILE_PREFIX + packageName + XML_SUFFIX;
     }
 
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index 0196212..ef2ee4a 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -35,7 +35,9 @@
     /**
      * Creates the class loader context dependencies for each of the application code paths.
      * The returned array contains the class loader contexts that needs to be passed to dexopt in
-     * order to ensure correct optimizations.
+     * order to ensure correct optimizations. "Code" paths with no actual code, as specified by
+     * {@param pathsWithCode}, are ignored and will have null as their context in the returned array
+     * (configuration splits are an example of paths without code).
      *
      * A class loader context describes how the class loader chain should be built by dex2oat
      * in order to ensure that classes are resolved during compilation as they would be resolved
@@ -60,7 +62,8 @@
      * {@link android.app.LoadedApk#makePaths(
      * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}.
      */
-    public static String[] getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries) {
+    public static String[] getClassLoaderContexts(ApplicationInfo info,
+            String[] sharedLibraries, boolean[] pathsWithCode) {
         // The base class loader context contains only the shared library.
         String sharedLibrariesClassPath = encodeClasspath(sharedLibraries);
         String baseApkContextClassLoader = encodeClassLoader(
@@ -86,7 +89,7 @@
         // Index 0 is the class loaded context for the base apk.
         // Index `i` is the class loader context encoding for split `i`.
         String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length];
-        classLoaderContexts[0] = baseApkContextClassLoader;
+        classLoaderContexts[0] = pathsWithCode[0] ? baseApkContextClassLoader : null;
 
         if (!info.requestsIsolatedSplitLoading() || info.splitDependencies == null) {
             // If the app didn't request for the splits to be loaded in isolation or if it does not
@@ -94,7 +97,15 @@
             // apk class loader (in the order of their definition).
             String classpath = sharedLibrariesAndBaseClassPath;
             for (int i = 1; i < classLoaderContexts.length; i++) {
-                classLoaderContexts[i] = encodeClassLoader(classpath, info.classLoaderName);
+                classLoaderContexts[i] = pathsWithCode[i]
+                        ? encodeClassLoader(classpath, info.classLoaderName) : null;
+                // Note that the splits with no code are not removed from the classpath computation.
+                // i.e. split_n might get the split_n-1 in its classpath dependency even
+                // if split_n-1 has no code.
+                // The splits with no code do not matter for the runtime which ignores
+                // apks without code when doing the classpath checks. As such we could actually
+                // filter them but we don't do it in order to keep consistency with how the apps
+                // are loaded.
                 classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]);
             }
         } else {
@@ -116,9 +127,17 @@
             String splitDependencyOnBase = encodeClassLoader(
                     sharedLibrariesAndBaseClassPath, info.classLoaderName);
             SparseArray<int[]> splitDependencies = info.splitDependencies;
+
+            // Note that not all splits have dependencies (e.g. configuration splits)
+            // The splits without dependencies will have classLoaderContexts[config_split_index]
+            // set to null after this step.
             for (int i = 1; i < splitDependencies.size(); i++) {
-                getParentDependencies(splitDependencies.keyAt(i), splitClassLoaderEncodingCache,
-                        splitDependencies, classLoaderContexts, splitDependencyOnBase);
+                int splitIndex = splitDependencies.keyAt(i);
+                if (pathsWithCode[splitIndex]) {
+                    // Compute the class loader context only for the splits with code.
+                    getParentDependencies(splitIndex, splitClassLoaderEncodingCache,
+                            splitDependencies, classLoaderContexts, splitDependencyOnBase);
+                }
             }
 
             // At this point classLoaderContexts contains only the parent dependencies.
@@ -126,8 +145,17 @@
             // come first in the context.
             for (int i = 1; i < classLoaderContexts.length; i++) {
                 String splitClassLoader = encodeClassLoader("", info.splitClassLoaderNames[i - 1]);
-                classLoaderContexts[i] = encodeClassLoaderChain(
-                        splitClassLoader, classLoaderContexts[i]);
+                if (pathsWithCode[i]) {
+                    // If classLoaderContexts[i] is null it means that the split does not have
+                    // any dependency. In this case its context equals its declared class loader.
+                    classLoaderContexts[i] = classLoaderContexts[i] == null
+                            ? splitClassLoader
+                            : encodeClassLoaderChain(splitClassLoader, classLoaderContexts[i]);
+                } else {
+                    // This is a split without code, it has no dependency and it is not compiled.
+                    // Its context will be null.
+                    classLoaderContexts[i] = null;
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 27159fa..bc531c3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -335,6 +335,7 @@
     static public final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
     static public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
     static public final String SYSTEM_DIALOG_REASON_ASSIST = "assist";
+    static public final String SYSTEM_DIALOG_REASON_SCREENSHOT = "screenshot";
 
     /**
      * These are the system UI flags that, when changing, can cause the layout
@@ -829,6 +830,7 @@
     private static final int MSG_ACCESSIBILITY_TV = 23;
     private static final int MSG_DISPATCH_BACK_KEY_TO_AUTOFILL = 24;
     private static final int MSG_SYSTEM_KEY_PRESS = 25;
+    private static final int MSG_HANDLE_ALL_APPS = 26;
 
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -921,6 +923,9 @@
                 case MSG_SYSTEM_KEY_PRESS:
                     sendSystemKeyToStatusBar(msg.arg1);
                     break;
+                case MSG_HANDLE_ALL_APPS:
+                    launchAllAppsAction();
+                    break;
             }
         }
     }
@@ -1801,6 +1806,17 @@
 
     private void launchAllAppsAction() {
         Intent intent = new Intent(Intent.ACTION_ALL_APPS);
+        if (mHasFeatureLeanback) {
+            final PackageManager pm = mContext.getPackageManager();
+            Intent intentLauncher = new Intent(Intent.ACTION_MAIN);
+            intentLauncher.addCategory(Intent.CATEGORY_HOME);
+            ResolveInfo resolveInfo = pm.resolveActivityAsUser(intentLauncher,
+                    PackageManager.MATCH_SYSTEM_ONLY,
+                    mCurrentUserId);
+            if (resolveInfo != null) {
+                intent.setPackage(resolveInfo.activityInfo.packageName);
+            }
+        }
         startActivityAsUser(intent, UserHandle.CURRENT);
     }
 
@@ -3620,6 +3636,14 @@
             return -1;
         } else if (mHasFeatureLeanback && interceptAccessibilityGestureTv(keyCode, down)) {
             return -1;
+        } else if (keyCode == KeyEvent.KEYCODE_ALL_APPS) {
+            if (!down) {
+                mHandler.removeMessages(MSG_HANDLE_ALL_APPS);
+                Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS);
+                msg.setAsynchronous(true);
+                msg.sendToTarget();
+            }
+            return -1;
         }
 
         // Toggle Caps Lock on META-ALT.
diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java
index 8969771..c3fa823 100644
--- a/services/core/java/com/android/server/search/SearchManagerService.java
+++ b/services/core/java/com/android/server/search/SearchManagerService.java
@@ -17,7 +17,6 @@
 package com.android.server.search;
 
 import android.app.ActivityManager;
-import android.app.AppGlobals;
 import android.app.IActivityManager;
 import android.app.ISearchManager;
 import android.app.SearchManager;
@@ -26,7 +25,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.ContentObserver;
@@ -37,6 +35,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.service.voice.VoiceInteractionService;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -272,24 +271,25 @@
         }
     }
 
+    // Check and return VIS component
     private ComponentName getLegacyAssistComponent(int userHandle) {
         try {
             userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
-                    Binder.getCallingUid(), userHandle, true, false, "getLegacyAssistComponent", null);
-            IPackageManager pm = AppGlobals.getPackageManager();
-            Intent assistIntent = new Intent(Intent.ACTION_ASSIST);
-            ResolveInfo info =
-                    pm.resolveIntent(assistIntent,
-                            assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                            PackageManager.MATCH_DEFAULT_ONLY, userHandle);
-            if (info != null) {
+                    Binder.getCallingUid(), userHandle, true, false, "getLegacyAssistComponent",
+                    null);
+            PackageManager pm = mContext.getPackageManager();
+            Intent intentAssistProbe = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
+            List<ResolveInfo> infoListVis = pm.queryIntentServicesAsUser(intentAssistProbe,
+                    PackageManager.MATCH_SYSTEM_ONLY, userHandle);
+            if (infoListVis == null || infoListVis.isEmpty()) {
+                return null;
+            } else {
+                ResolveInfo rInfo = infoListVis.get(0);
                 return new ComponentName(
-                        info.activityInfo.applicationInfo.packageName,
-                        info.activityInfo.name);
+                        rInfo.serviceInfo.applicationInfo.packageName,
+                        rInfo.serviceInfo.name);
+
             }
-        } catch (RemoteException re) {
-            // Local call
-            Log.e(TAG, "RemoteException in getLegacyAssistComponent: " + re);
         } catch (Exception e) {
             Log.e(TAG, "Exception in getLegacyAssistComponent: " + e);
         }
@@ -304,9 +304,15 @@
         }
         long ident = Binder.clearCallingIdentity();
         try {
-            Intent intent = new Intent(Intent.ACTION_ASSIST);
+            Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
             intent.setComponent(comp);
+
             IActivityManager am = ActivityManager.getService();
+            if (args != null) {
+                args.putInt(Intent.EXTRA_KEY_EVENT, android.view.KeyEvent.KEYCODE_ASSIST);
+            }
+            intent.putExtras(args);
+
             return am.launchAssistIntent(intent, ActivityManager.ASSIST_CONTEXT_BASIC, hint,
                     userHandle, args);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index dd887e0..64ac7ac 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1657,7 +1657,14 @@
                     if (mWin.mAppToken != null) {
                         mWin.mAppToken.setCanTurnScreenOn(false);
                     }
-                    mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN;
+
+                    // We do not add {@code SET_TURN_ON_SCREEN} when the screen is already
+                    // interactive as the value may persist until the next animation, which could
+                    // potentially occurring while turning off the screen. This would lead to the
+                    // screen incorrectly turning back on.
+                    if (!mService.mPowerManager.isInteractive()) {
+                        mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN;
+                    }
                 }
             }
             if (hasSurface()) {
diff --git a/services/core/jni/BroadcastRadio/NativeCallbackThread.cpp b/services/core/jni/BroadcastRadio/NativeCallbackThread.cpp
index 85ec9e0..81d46f3 100644
--- a/services/core/jni/BroadcastRadio/NativeCallbackThread.cpp
+++ b/services/core/jni/BroadcastRadio/NativeCallbackThread.cpp
@@ -48,15 +48,19 @@
         return;
     }
 
-    while (!mExiting) {
-        ALOGV("Waiting for task...");
+    while (true) {
         Task task;
         {
             unique_lock<mutex> lk(mQueueMutex);
-            mQueueCond.wait(lk);
-            if (mExiting) break;
 
-            if (mQueue.empty()) continue;
+            if (mExiting) break;
+            if (mQueue.empty()) {
+                ALOGV("Waiting for task...");
+                mQueueCond.wait(lk);
+                if (mExiting) break;
+                if (mQueue.empty()) continue;
+            }
+
             task = mQueue.front();
             mQueue.pop();
         }
@@ -74,6 +78,7 @@
     ALOGE_IF(res != JNI_OK, "Couldn't detach thread");
 
     ALOGV("Native callback thread %p finished", this);
+    ALOGD_IF(!mQueue.empty(), "Skipped execution of %zu tasks", mQueue.size());
 }
 
 void NativeCallbackThread::enqueue(const Task &task) {
@@ -84,6 +89,7 @@
         return;
     }
 
+    ALOGV("Adding task to the queue...");
     mQueue.push(task);
     mQueueCond.notify_one();
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0b967b3..38d7d25 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -99,6 +99,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.StringParceledListSlice;
@@ -152,6 +153,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -4106,6 +4108,16 @@
 
     private boolean isActivePasswordSufficientForUserLocked(
             DevicePolicyData policy, int userHandle, boolean parent) {
+        final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent);
+        if (requiredPasswordQuality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+            // A special case is when there is no required password quality, then we just return
+            // true since any password would be sufficient. This is for the scenario when a work
+            // profile is first created so there is no information about the current password but
+            // it should be considered sufficient as there is no password requirement either.
+            // This is useful since it short-circuits the password checkpoint for FDE device below.
+            return true;
+        }
+
         if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()
                 && !policy.mPasswordStateHasBeenSetSinceBoot) {
             // Before user enters their password for the first time after a reboot, return the
@@ -4116,7 +4128,6 @@
             return policy.mPasswordValidAtLastCheckpoint;
         }
 
-        final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent);
         if (policy.mActivePasswordMetrics.quality < requiredPasswordQuality) {
             return false;
         }
@@ -9581,6 +9592,10 @@
                         < android.os.Build.VERSION_CODES.M) {
                     return false;
                 }
+                if (!isRuntimePermission(permission)) {
+                    EventLog.writeEvent(0x534e4554, "62623498", user.getIdentifier(), "");
+                    return false;
+                }
                 final PackageManager packageManager = mInjector.getPackageManager();
                 switch (grantState) {
                     case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED: {
@@ -9607,6 +9622,8 @@
                 return true;
             } catch (SecurityException se) {
                 return false;
+            } catch (NameNotFoundException e) {
+                return false;
             } finally {
                 mInjector.binderRestoreCallingIdentity(ident);
             }
@@ -9656,6 +9673,13 @@
         }
     }
 
+    public boolean isRuntimePermission(String permissionName) throws NameNotFoundException {
+        final PackageManager packageManager = mInjector.getPackageManager();
+        PermissionInfo permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
+        return (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+                == PermissionInfo.PROTECTION_DANGEROUS;
+    }
+
     @Override
     public boolean isProvisioningAllowed(String action, String packageName) {
         Preconditions.checkNotNull(packageName);
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 97c9d82..88bd78c 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -183,44 +183,14 @@
         final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
                 1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
 
-        int errno = -OsConstants.EPROTO;
-        try (NetlinkSocket nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE)) {
-            final long IO_TIMEOUT = 300L;
-            nlSocket.connectToKernel();
-            nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
-            final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
-            // recvMessage() guaranteed to not return null if it did not throw.
-            final NetlinkMessage response = NetlinkMessage.parse(bytes);
-            if (response != null && response instanceof NetlinkErrorMessage &&
-                    (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
-                errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
-                if (errno != 0) {
-                    // TODO: consider ignoring EINVAL (-22), which appears to be
-                    // normal when probing a neighbor for which the kernel does
-                    // not already have / no longer has a link layer address.
-                    Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + response.toString());
-                }
-            } else {
-                String errmsg;
-                if (response == null) {
-                    bytes.position(0);
-                    errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
-                } else {
-                    errmsg = response.toString();
-                }
-                Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + errmsg);
-            }
+        try {
+            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
         } catch (ErrnoException e) {
-            Log.e(TAG, "Error " + msgSnippet, e);
-            errno = -e.errno;
-        } catch (InterruptedIOException e) {
-            Log.e(TAG, "Error " + msgSnippet, e);
-            errno = -OsConstants.ETIMEDOUT;
-        } catch (SocketException e) {
-            Log.e(TAG, "Error " + msgSnippet, e);
-            errno = -OsConstants.EIO;
+            Log.e(TAG, "Error " + msgSnippet + ": " + e);
+            return -e.errno;
         }
-        return errno;
+
+        return 0;
     }
 
     public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback) {
diff --git a/services/net/java/android/net/netlink/ConntrackMessage.java b/services/net/java/android/net/netlink/ConntrackMessage.java
new file mode 100644
index 0000000..605c46b
--- /dev/null
+++ b/services/net/java/android/net/netlink/ConntrackMessage.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netlink;
+
+import static android.net.netlink.NetlinkConstants.alignedLengthOf;
+import static android.net.netlink.StructNlAttr.makeNestedType;
+import static android.net.netlink.StructNlAttr.NLA_HEADERLEN;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static android.net.util.NetworkConstants.IPV4_ADDR_LEN;
+import static java.nio.ByteOrder.BIG_ENDIAN;
+
+import android.system.OsConstants;
+import android.util.Log;
+import libcore.io.SizeOf;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+/**
+ * A NetlinkMessage subclass for netlink conntrack messages.
+ *
+ * see also: &lt;linux_src&gt;/include/uapi/linux/netfilter/nfnetlink_conntrack.h
+ *
+ * @hide
+ */
+public class ConntrackMessage extends NetlinkMessage {
+    public static final int STRUCT_SIZE = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
+
+    public static final short NFNL_SUBSYS_CTNETLINK = 1;
+    public static final short IPCTNL_MSG_CT_NEW = 0;
+
+    // enum ctattr_type
+    public static final short CTA_TUPLE_ORIG  = 1;
+    public static final short CTA_TUPLE_REPLY = 2;
+    public static final short CTA_TIMEOUT     = 7;
+
+    // enum ctattr_tuple
+    public static final short CTA_TUPLE_IP    = 1;
+    public static final short CTA_TUPLE_PROTO = 2;
+
+    // enum ctattr_ip
+    public static final short CTA_IP_V4_SRC = 1;
+    public static final short CTA_IP_V4_DST = 2;
+
+    // enum ctattr_l4proto
+    public static final short CTA_PROTO_NUM      = 1;
+    public static final short CTA_PROTO_SRC_PORT = 2;
+    public static final short CTA_PROTO_DST_PORT = 3;
+
+    public static byte[] newIPv4TimeoutUpdateRequest(
+            int proto, Inet4Address src, int sport, Inet4Address dst, int dport, int timeoutSec) {
+        // *** STYLE WARNING ***
+        //
+        // Code below this point uses extra block indentation to highlight the
+        // packing of nested tuple netlink attribute types.
+        final StructNlAttr ctaTupleOrig = new StructNlAttr(CTA_TUPLE_ORIG,
+                new StructNlAttr(CTA_TUPLE_IP,
+                        new StructNlAttr(CTA_IP_V4_SRC, src),
+                        new StructNlAttr(CTA_IP_V4_DST, dst)),
+                new StructNlAttr(CTA_TUPLE_PROTO,
+                        new StructNlAttr(CTA_PROTO_NUM, (byte) proto),
+                        new StructNlAttr(CTA_PROTO_SRC_PORT, (short) sport, BIG_ENDIAN),
+                        new StructNlAttr(CTA_PROTO_DST_PORT, (short) dport, BIG_ENDIAN)));
+
+        final StructNlAttr ctaTimeout = new StructNlAttr(CTA_TIMEOUT, timeoutSec, BIG_ENDIAN);
+
+        final int payloadLength = ctaTupleOrig.getAlignedLength() + ctaTimeout.getAlignedLength();
+        final byte[] bytes = new byte[STRUCT_SIZE + payloadLength];
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        final ConntrackMessage ctmsg = new ConntrackMessage();
+        ctmsg.mHeader.nlmsg_len = bytes.length;
+        ctmsg.mHeader.nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW;
+        ctmsg.mHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
+        ctmsg.mHeader.nlmsg_seq = 1;
+        ctmsg.pack(byteBuffer);
+
+        ctaTupleOrig.pack(byteBuffer);
+        ctaTimeout.pack(byteBuffer);
+
+        return bytes;
+    }
+
+    protected StructNfGenMsg mNfGenMsg;
+
+    private ConntrackMessage() {
+        super(new StructNlMsgHdr());
+        mNfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET);
+    }
+
+    public void pack(ByteBuffer byteBuffer) {
+        mHeader.pack(byteBuffer);
+        mNfGenMsg.pack(byteBuffer);
+    }
+}
diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java
index 657d48c..a9e0cd9 100644
--- a/services/net/java/android/net/netlink/NetlinkSocket.java
+++ b/services/net/java/android/net/netlink/NetlinkSocket.java
@@ -51,6 +51,47 @@
     private long mLastRecvTimeoutMs;
     private long mLastSendTimeoutMs;
 
+    public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
+        final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
+
+        try (NetlinkSocket nlSocket = new NetlinkSocket(nlProto)) {
+            final long IO_TIMEOUT = 300L;
+            nlSocket.connectToKernel();
+            nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
+            final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
+            // recvMessage() guaranteed to not return null if it did not throw.
+            final NetlinkMessage response = NetlinkMessage.parse(bytes);
+            if (response != null && response instanceof NetlinkErrorMessage &&
+                    (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
+                final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
+                if (errno != 0) {
+                    // TODO: consider ignoring EINVAL (-22), which appears to be
+                    // normal when probing a neighbor for which the kernel does
+                    // not already have / no longer has a link layer address.
+                    Log.e(TAG, errPrefix + ", errmsg=" + response.toString());
+                    // Note: convert kernel errnos (negative) into userspace errnos (positive).
+                    throw new ErrnoException(response.toString(), Math.abs(errno));
+                }
+            } else {
+                final String errmsg;
+                if (response == null) {
+                    bytes.position(0);
+                    errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
+                } else {
+                    errmsg = response.toString();
+                }
+                Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
+                throw new ErrnoException(errmsg, OsConstants.EPROTO);
+            }
+        } catch (InterruptedIOException e) {
+            Log.e(TAG, errPrefix, e);
+            throw new ErrnoException(errPrefix, OsConstants.ETIMEDOUT, e);
+        } catch (SocketException e) {
+            Log.e(TAG, errPrefix, e);
+            throw new ErrnoException(errPrefix, OsConstants.EIO, e);
+        }
+    }
+
     public NetlinkSocket(int nlProto) throws ErrnoException {
         mDescriptor = Os.socket(
                 OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto);
diff --git a/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java b/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java
index 02df131..e784fbb 100644
--- a/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java
+++ b/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java
@@ -36,7 +36,7 @@
 
 
 /**
- * A NetlinkMessage subclass for netlink error messages.
+ * A NetlinkMessage subclass for rtnetlink neighbor messages.
  *
  * see also: &lt;linux_src&gt;/include/uapi/linux/neighbour.h
  *
diff --git a/services/net/java/android/net/netlink/StructNfGenMsg.java b/services/net/java/android/net/netlink/StructNfGenMsg.java
new file mode 100644
index 0000000..99695e2
--- /dev/null
+++ b/services/net/java/android/net/netlink/StructNfGenMsg.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netlink;
+
+import libcore.io.SizeOf;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nfgenmsg
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/netfilter/nfnetlink.h
+ *
+ * @hide
+ */
+public class StructNfGenMsg {
+    public static final int STRUCT_SIZE = 2 + SizeOf.SHORT;
+
+    public static final int NFNETLINK_V0 = 0;
+
+    final public byte nfgen_family;
+    final public byte version;
+    final public short res_id;  // N.B.: this is big endian in the kernel
+
+    public StructNfGenMsg(byte family) {
+        nfgen_family = family;
+        version = (byte) NFNETLINK_V0;
+        res_id = (short) 0;
+    }
+
+    public void pack(ByteBuffer byteBuffer) {
+        byteBuffer.put(nfgen_family);
+        byteBuffer.put(version);
+        byteBuffer.putShort(res_id);
+    }
+}
diff --git a/services/net/java/android/net/netlink/StructNlAttr.java b/services/net/java/android/net/netlink/StructNlAttr.java
index 597a6aa..811bdbb 100644
--- a/services/net/java/android/net/netlink/StructNlAttr.java
+++ b/services/net/java/android/net/netlink/StructNlAttr.java
@@ -34,7 +34,12 @@
  */
 public class StructNlAttr {
     // Already aligned.
-    public static final int NLA_HEADERLEN         = 4;
+    public static final int NLA_HEADERLEN  = 4;
+    public static final int NLA_F_NESTED   = (1 << 15);
+
+    public static short makeNestedType(short type) {
+        return (short) (type | NLA_F_NESTED);
+    }
 
     // Return a (length, type) object only, without consuming any bytes in
     // |byteBuffer| and without copying or interpreting any value bytes.
@@ -46,10 +51,17 @@
         }
         final int baseOffset = byteBuffer.position();
 
-        final StructNlAttr struct = new StructNlAttr();
-        struct.nla_len = byteBuffer.getShort();
-        struct.nla_type = byteBuffer.getShort();
-        struct.mByteOrder = byteBuffer.order();
+        // Assume the byte order of the buffer is the expected byte order of the value.
+        final StructNlAttr struct = new StructNlAttr(byteBuffer.order());
+        // The byte order of nla_len and nla_type is always native.
+        final ByteOrder originalOrder = byteBuffer.order();
+        byteBuffer.order(ByteOrder.nativeOrder());
+        try {
+            struct.nla_len = byteBuffer.getShort();
+            struct.nla_type = byteBuffer.getShort();
+        } finally {
+            byteBuffer.order(originalOrder);
+        }
 
         byteBuffer.position(baseOffset);
         if (struct.nla_len < NLA_HEADERLEN) {
@@ -78,13 +90,65 @@
         return struct;
     }
 
-    public short nla_len;
+    public short nla_len = (short) NLA_HEADERLEN;
     public short nla_type;
     public byte[] nla_value;
-    public ByteOrder mByteOrder;
 
-    public StructNlAttr() {
-        mByteOrder = ByteOrder.nativeOrder();
+    // The byte order used to read/write the value member. Netlink length and
+    // type members are always read/written in native order.
+    private ByteOrder mByteOrder = ByteOrder.nativeOrder();
+
+    public StructNlAttr() {}
+
+    public StructNlAttr(ByteOrder byteOrder) {
+        mByteOrder = byteOrder;
+    }
+
+    public StructNlAttr(short type, byte value) {
+        nla_type = type;
+        setValue(new byte[1]);
+        nla_value[0] = value;
+    }
+
+    public StructNlAttr(short type, short value) {
+        this(type, value, ByteOrder.nativeOrder());
+    }
+
+    public StructNlAttr(short type, short value, ByteOrder order) {
+        this(order);
+        nla_type = type;
+        setValue(new byte[SizeOf.SHORT]);
+        getValueAsByteBuffer().putShort(value);
+    }
+
+    public StructNlAttr(short type, int value) {
+        this(type, value, ByteOrder.nativeOrder());
+    }
+
+    public StructNlAttr(short type, int value, ByteOrder order) {
+        this(order);
+        nla_type = type;
+        setValue(new byte[SizeOf.INT]);
+        getValueAsByteBuffer().putInt(value);
+    }
+
+    public StructNlAttr(short type, InetAddress ip) {
+        nla_type = type;
+        setValue(ip.getAddress());
+    }
+
+    public StructNlAttr(short type, StructNlAttr... nested) {
+        this();
+        nla_type = makeNestedType(type);
+
+        int payloadLength = 0;
+        for (StructNlAttr nla : nested) payloadLength += nla.getAlignedLength();
+        setValue(new byte[payloadLength]);
+
+        final ByteBuffer buf = getValueAsByteBuffer();
+        for (StructNlAttr nla : nested) {
+            nla.pack(buf);
+        }
     }
 
     public int getAlignedLength() {
@@ -117,13 +181,25 @@
     }
 
     public void pack(ByteBuffer byteBuffer) {
+        final ByteOrder originalOrder = byteBuffer.order();
         final int originalPosition = byteBuffer.position();
-        byteBuffer.putShort(nla_len);
-        byteBuffer.putShort(nla_type);
-        byteBuffer.put(nla_value);
+
+        byteBuffer.order(ByteOrder.nativeOrder());
+        try {
+            byteBuffer.putShort(nla_len);
+            byteBuffer.putShort(nla_type);
+            if (nla_value != null) byteBuffer.put(nla_value);
+        } finally {
+            byteBuffer.order(originalOrder);
+        }
         byteBuffer.position(originalPosition + getAlignedLength());
     }
 
+    private void setValue(byte[] value) {
+        nla_value = value;
+        nla_len = (short) (NLA_HEADERLEN + ((nla_value != null) ? nla_value.length : 0));
+    }
+
     @Override
     public String toString() {
         return "StructNlAttr{ "
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 9f77297..d136614 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -55,11 +55,11 @@
     public void testWriteReadApplicationRestrictions() throws IOException {
         AtomicFile atomicFile = new AtomicFile(restrictionsFile);
         Bundle bundle = createBundle();
-        UserManagerService.writeApplicationRestrictionsLP(bundle, atomicFile);
+        UserManagerService.writeApplicationRestrictionsLAr(bundle, atomicFile);
         assertTrue(atomicFile.getBaseFile().exists());
         String s = FileUtils.readTextFile(restrictionsFile, 10000, "");
         System.out.println("restrictionsFile: " + s);
-        bundle = UserManagerService.readApplicationRestrictionsLP(atomicFile);
+        bundle = UserManagerService.readApplicationRestrictionsLAr(atomicFile);
         System.out.println("readApplicationRestrictionsLocked bundle: " + bundle);
         assertBundle(bundle);
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
index 2c56a82..c8c8eb1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
@@ -46,22 +46,35 @@
     private static final String DELEGATE_LAST_CLASS_LOADER_NAME =
             DelegateLastClassLoader.class.getName();
 
-    private ApplicationInfo createMockApplicationInfo(String baseClassLoader, boolean addSplits,
+    private static class TestData {
+        ApplicationInfo info;
+        boolean[] pathsWithCode;
+    }
+
+    private TestData createMockApplicationInfo(String baseClassLoader, boolean addSplits,
             boolean addSplitDependencies) {
         ApplicationInfo ai = new ApplicationInfo();
         String codeDir = "/data/app/mock.android.com";
         ai.setBaseCodePath(codeDir + "/base.dex");
         ai.classLoaderName = baseClassLoader;
         ai.privateFlags = ai.privateFlags | ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
+        boolean[] pathsWithCode;
+        if (!addSplits) {
+            pathsWithCode = new boolean[] {true};
+        } else {
+            pathsWithCode = new boolean[9];
+            Arrays.fill(pathsWithCode, true);
+            pathsWithCode[7] = false;  // config split
 
-        if (addSplits) {
             ai.setSplitCodePaths(new String[]{
                     codeDir + "/base-1.dex",
                     codeDir + "/base-2.dex",
                     codeDir + "/base-3.dex",
                     codeDir + "/base-4.dex",
                     codeDir + "/base-5.dex",
-                    codeDir + "/base-6.dex"});
+                    codeDir + "/base-6.dex",
+                    codeDir + "/config-split-7.dex",
+                    codeDir + "/feature-no-deps.dex"});
 
             ai.splitClassLoaderNames = new String[]{
                     DELEGATE_LAST_CLASS_LOADER_NAME,
@@ -69,7 +82,9 @@
                     PATH_CLASS_LOADER_NAME,
                     DEX_CLASS_LOADER_NAME,
                     PATH_CLASS_LOADER_NAME,
-                    null};  // A null class loader name should default to PathClassLoader.
+                    null,   // A null class loader name should default to PathClassLoader.
+                    null,   // The config split gets a null class loader.
+                    null};  // The feature split with no dependency and no specified class loader.
             if (addSplitDependencies) {
                 ai.splitDependencies = new SparseArray<>(ai.splitClassLoaderNames.length + 1);
                 ai.splitDependencies.put(0, new int[] {-1}); // base has no dependency
@@ -79,18 +94,24 @@
                 ai.splitDependencies.put(4, new int[] {0}); // split 4 depends on base
                 ai.splitDependencies.put(5, new int[] {0}); // split 5 depends on base
                 ai.splitDependencies.put(6, new int[] {5}); // split 6 depends on 5
+                // Do not add the config split to the dependency list.
+                // Do not add the feature split with no dependency to the dependency list.
             }
         }
-        return ai;
+        TestData data = new TestData();
+        data.info = ai;
+        data.pathsWithCode = pathsWithCode;
+        return data;
     }
 
     @Test
     public void testSplitChain() {
-        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
+        TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
         String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
-        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, sharedLibrary, data.pathsWithCode);
 
-        assertEquals(7, contexts.length);
+        assertEquals(9, contexts.length);
         assertEquals("PCL[a.dex:b.dex]", contexts[0]);
         assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]",
                 contexts[1]);
@@ -99,15 +120,18 @@
         assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]);
         assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]);
         assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]);
+        assertEquals(null, contexts[7]);  // config split
+        assertEquals("PCL[]", contexts[8]);  // feature split with no dependency
     }
 
     @Test
     public void testSplitChainNoSplitDependencies() {
-        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false);
+        TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false);
         String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
-        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, sharedLibrary, data.pathsWithCode);
 
-        assertEquals(7, contexts.length);
+        assertEquals(9, contexts.length);
         assertEquals("PCL[a.dex:b.dex]", contexts[0]);
         assertEquals("PCL[a.dex:b.dex:base.dex]", contexts[1]);
         assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex]", contexts[2]);
@@ -119,15 +143,21 @@
         assertEquals(
                 "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]",
                 contexts[6]);
+        assertEquals(null, contexts[7]);  // config split
+        assertEquals(
+                "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex:base-6.dex:config-split-7.dex]",
+                contexts[8]);  // feature split with no dependency
     }
 
     @Test
     public void testSplitChainNoIsolationNoSharedLibrary() {
-        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
-        ai.privateFlags = ai.privateFlags & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING);
-        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+        TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
+        data.info.privateFlags = data.info.privateFlags
+                & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, null, data.pathsWithCode);
 
-        assertEquals(7, contexts.length);
+        assertEquals(9, contexts.length);
         assertEquals("PCL[]", contexts[0]);
         assertEquals("PCL[base.dex]", contexts[1]);
         assertEquals("PCL[base.dex:base-1.dex]", contexts[2]);
@@ -137,14 +167,20 @@
         assertEquals(
                 "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]",
                 contexts[6]);
+        assertEquals(null, contexts[7]);  // config split
+        assertEquals(
+                "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex:base-6.dex:config-split-7.dex]",
+                contexts[8]);  // feature split with no dependency
     }
+
     @Test
     public void testSplitChainNoSharedLibraries() {
-        ApplicationInfo ai = createMockApplicationInfo(
+        TestData data = createMockApplicationInfo(
                 DELEGATE_LAST_CLASS_LOADER_NAME, true, true);
-        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, null, data.pathsWithCode);
 
-        assertEquals(7, contexts.length);
+        assertEquals(9, contexts.length);
         assertEquals("DLC[]", contexts[0]);
         assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];DLC[base.dex]", contexts[1]);
         assertEquals("DLC[];PCL[base-4.dex];DLC[base.dex]", contexts[2]);
@@ -152,16 +188,19 @@
         assertEquals("PCL[];DLC[base.dex]", contexts[4]);
         assertEquals("PCL[];DLC[base.dex]", contexts[5]);
         assertEquals("PCL[];PCL[base-5.dex];DLC[base.dex]", contexts[6]);
+        assertEquals(null, contexts[7]);  // config split
+        assertEquals("PCL[]", contexts[8]);  // feature split with no dependency
     }
 
     @Test
     public void testSplitChainWithNullPrimaryClassLoader() {
         // A null classLoaderName should mean PathClassLoader.
-        ApplicationInfo ai = createMockApplicationInfo(null, true, true);
+        TestData data = createMockApplicationInfo(null, true, true);
         String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
-        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, sharedLibrary, data.pathsWithCode);
 
-        assertEquals(7, contexts.length);
+        assertEquals(9, contexts.length);
         assertEquals("PCL[a.dex:b.dex]", contexts[0]);
         assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]);
         assertEquals("DLC[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]);
@@ -169,13 +208,16 @@
         assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]);
         assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]);
         assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]);
+        assertEquals(null, contexts[7]);  // config split
+        assertEquals("PCL[]", contexts[8]);  // feature split with no dependency
     }
 
     @Test
     public void tesNoSplits() {
-        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false);
+        TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false);
         String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
-        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, sharedLibrary, data.pathsWithCode);
 
         assertEquals(1, contexts.length);
         assertEquals("PCL[a.dex:b.dex]", contexts[0]);
@@ -183,9 +225,10 @@
 
     @Test
     public void tesNoSplitsNullClassLoaderName() {
-        ApplicationInfo ai = createMockApplicationInfo(null, false, false);
+        TestData data = createMockApplicationInfo(null, false, false);
         String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
-        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, sharedLibrary, data.pathsWithCode);
 
         assertEquals(1, contexts.length);
         assertEquals("PCL[a.dex:b.dex]", contexts[0]);
@@ -193,10 +236,11 @@
 
     @Test
     public void tesNoSplitDelegateLast() {
-        ApplicationInfo ai = createMockApplicationInfo(
+        TestData data = createMockApplicationInfo(
                 DELEGATE_LAST_CLASS_LOADER_NAME, false, false);
         String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
-        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, sharedLibrary, data.pathsWithCode);
 
         assertEquals(1, contexts.length);
         assertEquals("DLC[a.dex:b.dex]", contexts[0]);
@@ -204,8 +248,9 @@
 
     @Test
     public void tesNoSplitsNoSharedLibraries() {
-        ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false);
-        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+        TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, null, data.pathsWithCode);
 
         assertEquals(1, contexts.length);
         assertEquals("PCL[]", contexts[0]);
@@ -213,15 +258,55 @@
 
     @Test
     public void tesNoSplitDelegateLastNoSharedLibraries() {
-        ApplicationInfo ai = createMockApplicationInfo(
+        TestData data = createMockApplicationInfo(
                 DELEGATE_LAST_CLASS_LOADER_NAME, false, false);
-        String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, null, data.pathsWithCode);
 
         assertEquals(1, contexts.length);
         assertEquals("DLC[]", contexts[0]);
     }
 
     @Test
+    public void testContextWithNoCode() {
+        TestData data = createMockApplicationInfo(null, true, false);
+        Arrays.fill(data.pathsWithCode, false);
+
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, sharedLibrary, data.pathsWithCode);
+
+        assertEquals(9, contexts.length);
+        assertEquals(null, contexts[0]);
+        assertEquals(null, contexts[1]);
+        assertEquals(null, contexts[2]);
+        assertEquals(null, contexts[3]);
+        assertEquals(null, contexts[4]);
+        assertEquals(null, contexts[5]);
+        assertEquals(null, contexts[6]);
+        assertEquals(null, contexts[7]);
+    }
+
+    @Test
+    public void testContextBaseNoCode() {
+        TestData data = createMockApplicationInfo(null, true, true);
+        data.pathsWithCode[0] = false;
+        String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+        String[] contexts = DexoptUtils.getClassLoaderContexts(
+                data.info, sharedLibrary, data.pathsWithCode);
+
+        assertEquals(9, contexts.length);
+        assertEquals(null, contexts[0]);
+        assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]);
+        assertEquals("DLC[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]);
+        assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]);
+        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]);
+        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]);
+        assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]);
+        assertEquals(null, contexts[7]);
+    }
+
+    @Test
     public void testProcessContextForDexLoad() {
         List<String> classLoaders = Arrays.asList(
                 DELEGATE_LAST_CLASS_LOADER_NAME,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 63d492a..c36b3ac 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1762,7 +1762,11 @@
 
         // Carrier Signalling Receivers
         sDefaults.putString(KEY_CARRIER_SETUP_APP_STRING, "");
-        sDefaults.putStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY, null);
+        sDefaults.putStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY,
+                new String[]{
+                        "com.android.carrierdefaultapp/.CarrierDefaultBroadcastReceiver:"
+                                + "com.android.internal.telephony.CARRIER_SIGNAL_RESET"
+                });
         sDefaults.putStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, null);
 
 
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
index 4c3f7e7..1e8cf18 100644
--- a/telephony/java/android/telephony/MbmsDownloadManager.java
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -18,19 +18,19 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
-import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.telephony.mbms.DownloadProgressListener;
 import android.telephony.mbms.FileInfo;
 import android.telephony.mbms.DownloadRequest;
-import android.telephony.mbms.IDownloadProgressListener;
-import android.telephony.mbms.IMbmsDownloadManagerCallback;
 import android.telephony.mbms.MbmsDownloadManagerCallback;
 import android.telephony.mbms.MbmsDownloadReceiver;
 import android.telephony.mbms.MbmsException;
@@ -44,6 +44,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -52,147 +53,38 @@
 public class MbmsDownloadManager {
     private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName();
 
+    /** @hide */
+    // TODO: systemapi
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     public static final String MBMS_DOWNLOAD_SERVICE_ACTION =
             "android.telephony.action.EmbmsDownload";
-    /**
-     * The MBMS middleware should send this when a download of single file has completed or
-     * failed. Mandatory extras are
-     * {@link #EXTRA_RESULT}
-     * {@link #EXTRA_FILE_INFO}
-     * {@link #EXTRA_REQUEST}
-     * {@link #EXTRA_TEMP_LIST}
-     * {@link #EXTRA_FINAL_URI}
-     *
-     * TODO: future systemapi
-     */
-    public static final String ACTION_DOWNLOAD_RESULT_INTERNAL =
-            "android.telephony.mbms.action.DOWNLOAD_RESULT_INTERNAL";
-
-    /**
-     * The MBMS middleware should send this when it wishes to request {@code content://} URIs to
-     * serve as temp files for downloads or when it wishes to resume paused downloads. Mandatory
-     * extras are
-     * {@link #EXTRA_REQUEST}
-     *
-     * Optional extras are
-     * {@link #EXTRA_FD_COUNT} (0 if not present)
-     * {@link #EXTRA_PAUSED_LIST} (empty if not present)
-     *
-     * TODO: future systemapi
-     */
-    public static final String ACTION_FILE_DESCRIPTOR_REQUEST =
-            "android.telephony.mbms.action.FILE_DESCRIPTOR_REQUEST";
-
-    /**
-     * The MBMS middleware should send this when it wishes to clean up temp  files in the app's
-     * filesystem. Mandatory extras are:
-     * {@link #EXTRA_TEMP_FILES_IN_USE}
-     *
-     * TODO: future systemapi
-     */
-    public static final String ACTION_CLEANUP =
-            "android.telephony.mbms.action.CLEANUP";
 
     /**
      * Integer extra indicating the result code of the download. One of
      * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, or {@link #RESULT_CANCELLED}.
-     * TODO: Not systemapi.
      */
     public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT";
 
     /**
      * Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result
      * is for. Must not be null.
-     * TODO: Not systemapi.
      */
     public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO";
 
     /**
-     * Extra containing the {@link DownloadRequest} for which the download result or file
-     * descriptor request is for. Must not be null.
-     * TODO: future systemapi (here and and all extras) except the three for the app intent
-     */
-    public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
-
-    /**
-     * Extra containing a {@link List} of {@link Uri}s that were used as temp files for this
-     * completed file. These {@link Uri}s should have scheme {@code file://}, and the temp
-     * files will be deleted upon receipt of the intent.
-     * May be null.
-     */
-    public static final String EXTRA_TEMP_LIST = "android.telephony.mbms.extra.TEMP_LIST";
-
-    /**
-     * Extra containing a single {@link Uri} indicating the path to the temp file in which the
-     * decoded downloaded file resides. Must not be null.
-     */
-    public static final String EXTRA_FINAL_URI = "android.telephony.mbms.extra.FINAL_URI";
-
-    /**
-     * Extra containing an integer indicating the number of temp files requested.
-     */
-    public static final String EXTRA_FD_COUNT = "android.telephony.mbms.extra.FD_COUNT";
-
-    /**
-     * Extra containing a list of {@link Uri}s that the middleware is requesting access to via
-     * {@link #ACTION_FILE_DESCRIPTOR_REQUEST} in order to resume downloading. These {@link Uri}s
-     * should have scheme {@code file://}.
-     */
-    public static final String EXTRA_PAUSED_LIST = "android.telephony.mbms.extra.PAUSED_LIST";
-
-    /**
-     * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
-     * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These are temp files that are meant
-     * to be used for new file downloads.
-     */
-    public static final String EXTRA_FREE_URI_LIST = "android.telephony.mbms.extra.FREE_URI_LIST";
-
-    /**
-     * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
-     * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These
-     * {@link android.telephony.mbms.UriPathPair}s contain {@code content://} URIs that provide
-     * access to previously paused downloads.
-     */
-    public static final String EXTRA_PAUSED_URI_LIST =
-            "android.telephony.mbms.extra.PAUSED_URI_LIST";
-
-    /**
-     * Extra containing a string that points to the middleware's knowledge of where the temp file
-     * root for the app is. The path should be a canonical path as returned by
-     * {@link File#getCanonicalPath()}
-     */
-    public static final String EXTRA_TEMP_FILE_ROOT =
-            "android.telephony.mbms.extra.TEMP_FILE_ROOT";
-
-    /**
-     * Extra containing a list of {@link Uri}s indicating temp files which the middleware is
-     * still using.
-     */
-    public static final String EXTRA_TEMP_FILES_IN_USE =
-            "android.telephony.mbms.extra.TEMP_FILES_IN_USE";
-
-    /**
-     * Extra containing an instance of {@link android.telephony.mbms.ServiceInfo}, used by
-     * file-descriptor requests and cleanup requests to specify which service they want to
-     * request temp files or clean up temp files for, respectively.
-     */
-    public static final String EXTRA_SERVICE_INFO =
-            "android.telephony.mbms.extra.SERVICE_INFO";
-
-    /**
      * Extra containing a single {@link Uri} indicating the location of the successfully
      * downloaded file. Set on the intent provided via
      * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}.
      * Will always be set to a non-null value if {@link #EXTRA_RESULT} is set to
      * {@link #RESULT_SUCCESSFUL}.
-     * TODO: Not systemapi.
      */
     public static final String EXTRA_COMPLETED_FILE_URI =
             "android.telephony.mbms.extra.COMPLETED_FILE_URI";
 
     public static final int RESULT_SUCCESSFUL = 1;
-    public static final int RESULT_CANCELLED  = 2;
-    public static final int RESULT_EXPIRED    = 3;
+    public static final int RESULT_CANCELLED = 2;
+    public static final int RESULT_EXPIRED = 3;
+    public static final int RESULT_IO_ERROR = 4;
     // TODO - more results!
 
     /** @hide */
@@ -207,8 +99,16 @@
     public static final int STATUS_PENDING_REPAIR = 3;
     public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4;
 
+    private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
+
     private final Context mContext;
     private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
+    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
+        }
+    };
 
     private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
     private final MbmsDownloadManagerCallback mCallback;
@@ -236,10 +136,21 @@
      *
      * Note that this call will bind a remote service and that may take a bit. The instance of
      * {@link MbmsDownloadManager} that is returned will not be ready for use until
-     * {@link IMbmsDownloadManagerCallback#middlewareReady()} is called on the provided callback.
+     * {@link MbmsDownloadManagerCallback#middlewareReady()} is called on the provided callback.
      * If you attempt to use the manager before it is ready, a {@link MbmsException} will be thrown.
      *
-     * This also may throw an {@link IllegalArgumentException} or a {@link MbmsException}.
+     * This also may throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
+     *
+     * You may only have one instance of {@link MbmsDownloadManager} per UID. If you call this
+     * method while there is an active instance of {@link MbmsDownloadManager} in your process
+     * (in other words, one that has not had {@link #dispose()} called on it), this method will
+     * throw an {@link MbmsException}. If you call this method in a different process
+     * running under the same UID, an error will be indicated via
+     * {@link MbmsDownloadManagerCallback#error(int, String)}.
+     *
+     * Note that initialization may fail asynchronously. If you wish to try again after you
+     * receive such an asynchronous error, you must call dispose() on the instance of
+     * {@link MbmsDownloadManager} that you received before calling this method again.
      *
      * @param context The instance of {@link Context} to use
      * @param listener A callback to get asynchronous error messages and file service updates.
@@ -249,8 +160,16 @@
     public static MbmsDownloadManager create(Context context,
             MbmsDownloadManagerCallback listener, int subscriptionId)
             throws MbmsException {
+        if (!sIsInitialized.compareAndSet(false, true)) {
+            throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
+        }
         MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, subscriptionId);
-        mdm.bindAndInitialize();
+        try {
+            mdm.bindAndInitialize();
+        } catch (MbmsException e) {
+            sIsInitialized.set(false);
+            throw e;
+        }
         return mdm;
     }
 
@@ -266,16 +185,27 @@
                             result = downloadService.initialize(mSubscriptionId, mCallback);
                         } catch (RemoteException e) {
                             Log.e(LOG_TAG, "Service died before initialization");
+                            sIsInitialized.set(false);
                             return;
                         } catch (RuntimeException e) {
                             Log.e(LOG_TAG, "Runtime exception during initialization");
-                            mCallback.error(
+                            sendErrorToApp(
                                     MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
                                     e.toString());
+                            sIsInitialized.set(false);
                             return;
                         }
                         if (result != MbmsException.SUCCESS) {
-                            mCallback.error(result, "Error returned during initialization");
+                            sendErrorToApp(result, "Error returned during initialization");
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        try {
+                            downloadService.asBinder().linkToDeath(mDeathRecipient, 0);
+                        } catch (RemoteException e) {
+                            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
+                                    "Middleware lost during initialization");
+                            sIsInitialized.set(false);
                             return;
                         }
                         mService.set(downloadService);
@@ -283,6 +213,7 @@
 
                     @Override
                     public void onServiceDisconnected(ComponentName name) {
+                        sIsInitialized.set(false);
                         mService.set(null);
                     }
                 });
@@ -292,7 +223,7 @@
      * An inspection API to retrieve the list of available
      * {@link android.telephony.mbms.FileServiceInfo}s currently being advertised.
      * The results are returned asynchronously via a call to
-     * {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)}
+     * {@link MbmsDownloadManagerCallback#fileServicesUpdated(List)}
      *
      * The serviceClasses argument lets the app filter on types of programming and is opaque data
      * negotiated beforehand between the app and the carrier.
@@ -306,7 +237,7 @@
      * {@link MbmsException.StreamingErrors#ERROR_UNABLE_TO_START_SERVICE}
      *
      * @param classList A list of service classes which the app wishes to receive
-     *                  {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)} callbacks
+     *                  {@link MbmsDownloadManagerCallback#fileServicesUpdated(List)} callbacks
      *                  about. Subsequent calls to this method will replace this list of service
      *                  classes (i.e. the middleware will no longer send updates for services
      *                  matching classes only in the old list).
@@ -336,14 +267,15 @@
      * local instance of {@link android.content.SharedPreferences} and by the middleware.
      *
      * If this method is not called at least once before calling
-     * {@link #download(DownloadRequest, IDownloadCallback)}, the framework
+     * {@link #download(DownloadRequest, DownloadProgressListener)}, the framework
      * will default to a directory formed by the concatenation of the app's files directory and
      * {@link android.telephony.mbms.MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
      *
      * Before calling this method, the app must cancel all of its pending
      * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
      * an {@link MbmsException} will be thrown with code
-     * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
+     * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
+     * provided directory is the same as what has been previously configured.
      *
      * The {@link File} supplied as a root temp file directory must already exist. If not, an
      * {@link IllegalArgumentException} will be thrown.
@@ -384,6 +316,26 @@
     }
 
     /**
+     * Retrieves the currently configured temp file root directory. Returns the file that was
+     * configured via {@link #setTempFileRootDirectory(File)} or the default directory
+     * {@link #download(DownloadRequest, DownloadProgressListener)} was called without ever setting
+     * the temp file root. If neither method has been called since the last time the app's shared
+     * preferences were reset, returns null.
+     *
+     * @return A {@link File} pointing to the configured temp file directory, or null if not yet
+     *         configured.
+     */
+    public @Nullable File getTempFileRootDirectory() {
+        SharedPreferences prefs = mContext.getSharedPreferences(
+                MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
+        String path = prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null);
+        if (path != null) {
+            return new File(path);
+        }
+        return null;
+    }
+
+    /**
      * Requests a download of a file that is available via multicast.
      *
      * downloadListener is an optional callback object which can be used to get progress reports
@@ -404,7 +356,7 @@
      * @param progressListener Optional listener that will be provided progress updates
      *                         if the app is running.
      */
-    public void download(DownloadRequest request, IDownloadProgressListener progressListener)
+    public void download(DownloadRequest request, DownloadProgressListener progressListener)
             throws MbmsException {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
@@ -434,7 +386,7 @@
     /**
      * Returns a list of pending {@link DownloadRequest}s that originated from this application.
      * A pending request is one that was issued via
-     * {@link #download(DownloadRequest, IDownloadCallback)} but not cancelled through
+     * {@link #download(DownloadRequest, DownloadProgressListener)} but not cancelled through
      * {@link #cancelDownload(DownloadRequest)}.
      * @return A list, possibly empty, of {@link DownloadRequest}s
      */
@@ -550,43 +502,15 @@
                 return;
             }
             downloadService.dispose(mSubscriptionId);
-            mService.set(null);
         } catch (RemoteException e) {
             // Ignore
             Log.i(LOG_TAG, "Remote exception while disposing of service");
+        } finally {
+            mService.set(null);
+            sIsInitialized.set(false);
         }
     }
 
-    /**
-     * Retrieves the {@link ComponentName} for the {@link android.content.BroadcastReceiver} that
-     * the various intents from the middleware should be targeted towards.
-     * @param uid The uid of the frontend app.
-     * @return The component name of the receiver that the middleware should send its intents to,
-     * or null if the app didn't declare it in the manifest.
-     *
-     * @hide
-     * future systemapi
-     */
-    public static ComponentName getAppReceiverFromUid(Context context, int uid) {
-        String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
-        if (packageNames == null) {
-            return null;
-        }
-
-        for (String packageName : packageNames) {
-            ComponentName candidate = new ComponentName(packageName,
-                    MbmsDownloadReceiver.class.getCanonicalName());
-            Intent queryIntent = new Intent();
-            queryIntent.setComponent(candidate);
-            List<ResolveInfo> receivers =
-                    context.getPackageManager().queryBroadcastReceivers(queryIntent, 0);
-            if (receivers != null && receivers.size() > 0) {
-                return candidate;
-            }
-        }
-        return null;
-    }
-
     private void writeDownloadRequestToken(DownloadRequest request) {
         File token = getDownloadRequestTokenPath(request);
         if (!token.getParentFile().exists()) {
@@ -651,4 +575,12 @@
             }
         }
     }
+
+    private void sendErrorToApp(int errorCode, String message) {
+        try {
+            mCallback.error(errorCode, message);
+        } catch (RemoteException e) {
+            // Ignore, should not happen locally.
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java
index 911f83f..7a6631a 100644
--- a/telephony/java/android/telephony/MbmsStreamingManager.java
+++ b/telephony/java/android/telephony/MbmsStreamingManager.java
@@ -16,11 +16,17 @@
 
 package android.telephony;
 
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ServiceConnection;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
+import android.telephony.mbms.InternalStreamingManagerCallback;
+import android.telephony.mbms.InternalStreamingServiceCallback;
 import android.telephony.mbms.MbmsException;
 import android.telephony.mbms.MbmsStreamingManagerCallback;
 import android.telephony.mbms.MbmsUtils;
@@ -31,6 +37,7 @@
 import android.util.Log;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -41,21 +48,42 @@
  */
 public class MbmsStreamingManager {
     private static final String LOG_TAG = "MbmsStreamingManager";
+
+    /**
+     * Service action which must be handled by the middleware implementing the MBMS streaming
+     * interface.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
     public static final String MBMS_STREAMING_SERVICE_ACTION =
             "android.telephony.action.EmbmsStreaming";
 
+    private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
+
     private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
-    private MbmsStreamingManagerCallback mCallbackToApp;
+    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            sIsInitialized.set(false);
+            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
+        }
+    };
+
+    private InternalStreamingManagerCallback mInternalCallback;
 
     private final Context mContext;
     private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
 
     /** @hide */
     private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback callback,
-                    int subscriptionId) {
+                    int subscriptionId, Handler handler) {
         mContext = context;
-        mCallbackToApp = callback;
         mSubscriptionId = subscriptionId;
+        if (handler == null) {
+            handler = new Handler(Looper.getMainLooper());
+        }
+        mInternalCallback = new InternalStreamingManagerCallback(callback, handler);
     }
 
     /**
@@ -65,27 +93,62 @@
      * main thread. This may throw an {@link MbmsException}, indicating errors that may happen
      * during the initialization or binding process.
      *
+     *
+     * You may only have one instance of {@link MbmsStreamingManager} per UID. If you call this
+     * method while there is an active instance of {@link MbmsStreamingManager} in your process
+     * (in other words, one that has not had {@link #dispose()} called on it), this method will
+     * throw an {@link MbmsException}. If you call this method in a different process
+     * running under the same UID, an error will be indicated via
+     * {@link MbmsStreamingManagerCallback#onError(int, String)}.
+     *
+     * Note that initialization may fail asynchronously. If you wish to try again after you
+     * receive such an asynchronous error, you must call dispose() on the instance of
+     * {@link MbmsStreamingManager} that you received before calling this method again.
+     *
      * @param context The {@link Context} to use.
      * @param callback A callback object on which you wish to receive results of asynchronous
      *                 operations.
      * @param subscriptionId The subscription ID to use.
+     * @param handler The handler you wish to receive callbacks on. If null, callbacks will be
+     *                processed on the main looper (in other words, the looper returned from
+     *                {@link Looper#getMainLooper()}).
      */
     public static MbmsStreamingManager create(Context context,
-            MbmsStreamingManagerCallback callback, int subscriptionId)
+            MbmsStreamingManagerCallback callback, int subscriptionId, Handler handler)
             throws MbmsException {
-        MbmsStreamingManager manager = new MbmsStreamingManager(context, callback, subscriptionId);
-        manager.bindAndInitialize();
+        if (!sIsInitialized.compareAndSet(false, true)) {
+            throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
+        }
+        MbmsStreamingManager manager = new MbmsStreamingManager(context, callback,
+                subscriptionId, handler);
+        try {
+            manager.bindAndInitialize();
+        } catch (MbmsException e) {
+            sIsInitialized.set(false);
+            throw e;
+        }
         return manager;
     }
 
     /**
      * Create a new MbmsStreamingManager using the system default data subscription ID.
-     * See {@link #create(Context, MbmsStreamingManagerCallback, int)}.
+     * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
+     */
+    public static MbmsStreamingManager create(Context context,
+            MbmsStreamingManagerCallback callback, Handler handler)
+            throws MbmsException {
+        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
+    }
+
+    /**
+     * Create a new MbmsStreamingManager using the system default data subscription ID and
+     * default {@link Handler}.
+     * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
      */
     public static MbmsStreamingManager create(Context context,
             MbmsStreamingManagerCallback callback)
             throws MbmsException {
-        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId());
+        return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null);
     }
 
     /**
@@ -95,17 +158,19 @@
      * May throw an {@link IllegalStateException}
      */
     public void dispose() {
-        IMbmsStreamingService streamingService = mService.get();
-        if (streamingService == null) {
-            // Ignore and return, assume already disposed.
-            return;
-        }
         try {
+            IMbmsStreamingService streamingService = mService.get();
+            if (streamingService == null) {
+                // Ignore and return, assume already disposed.
+                return;
+            }
             streamingService.dispose(mSubscriptionId);
         } catch (RemoteException e) {
             // Ignore for now
+        } finally {
+            mService.set(null);
+            sIsInitialized.set(false);
         }
-        mService.set(null);
     }
 
     /**
@@ -139,16 +204,17 @@
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService.set(null);
+            sIsInitialized.set(false);
             throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
         }
     }
 
     /**
-     * Starts streaming a requested service, reporting status to the indicated listener.
+     * Starts streaming a requested service, reporting status to the indicated callback.
      * Returns an object used to control that stream. The stream may not be ready for consumption
      * immediately upon return from this method -- wait until the streaming state has been
      * reported via
-     * {@link android.telephony.mbms.StreamingServiceCallback#streamStateUpdated(int, int)}
+     * {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)}
      *
      * May throw an
      * {@link MbmsException} containing any of the error codes in
@@ -158,34 +224,44 @@
      *
      * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
      *
-     * Asynchronous errors through the listener include any of the errors in
+     * Asynchronous errors through the callback include any of the errors in
      * {@link android.telephony.mbms.MbmsException.GeneralErrors} or
      * {@link android.telephony.mbms.MbmsException.StreamingErrors}.
      *
      * @param serviceInfo The information about the service to stream.
-     * @param listener A listener that'll be called when something about the stream changes.
+     * @param callback A callback that'll be called when something about the stream changes.
+     * @param handler A handler that calls to {@code callback} should be called on. If null,
+     *                defaults to the handler provided via
+     *                {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
      * @return An instance of {@link StreamingService} through which the stream can be controlled.
      */
     public StreamingService startStreaming(StreamingServiceInfo serviceInfo,
-            StreamingServiceCallback listener) throws MbmsException {
+            StreamingServiceCallback callback, Handler handler) throws MbmsException {
         IMbmsStreamingService streamingService = mService.get();
         if (streamingService == null) {
             throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
         }
 
+        InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback(
+                callback, handler == null ? mInternalCallback.getHandler() : handler);
+
+        StreamingService serviceForApp = new StreamingService(
+                mSubscriptionId, streamingService, serviceInfo, serviceCallback);
+
         try {
             int returnCode = streamingService.startStreaming(
-                    mSubscriptionId, serviceInfo.getServiceId(), listener);
+                    mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
             if (returnCode != MbmsException.SUCCESS) {
                 throw new MbmsException(returnCode);
             }
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Remote process died");
             mService.set(null);
+            sIsInitialized.set(false);
             throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
         }
 
-        return new StreamingService(mSubscriptionId, streamingService, serviceInfo, listener);
+        return serviceForApp;
     }
 
     private void bindAndInitialize() throws MbmsException {
@@ -197,19 +273,34 @@
                                 IMbmsStreamingService.Stub.asInterface(service);
                         int result;
                         try {
-                            result = streamingService.initialize(mCallbackToApp, mSubscriptionId);
+                            result = streamingService.initialize(mInternalCallback,
+                                    mSubscriptionId);
                         } catch (RemoteException e) {
                             Log.e(LOG_TAG, "Service died before initialization");
+                            sendErrorToApp(
+                                    MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+                                    e.toString());
+                            sIsInitialized.set(false);
                             return;
                         } catch (RuntimeException e) {
                             Log.e(LOG_TAG, "Runtime exception during initialization");
-                            mCallbackToApp.error(
+                            sendErrorToApp(
                                     MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
                                     e.toString());
+                            sIsInitialized.set(false);
                             return;
                         }
                         if (result != MbmsException.SUCCESS) {
-                            mCallbackToApp.error(result, "Error returned during initialization");
+                            sendErrorToApp(result, "Error returned during initialization");
+                            sIsInitialized.set(false);
+                            return;
+                        }
+                        try {
+                            streamingService.asBinder().linkToDeath(mDeathRecipient, 0);
+                        } catch (RemoteException e) {
+                            sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
+                                    "Middleware lost during initialization");
+                            sIsInitialized.set(false);
                             return;
                         }
                         mService.set(streamingService);
@@ -217,8 +308,17 @@
 
                     @Override
                     public void onServiceDisconnected(ComponentName name) {
+                        sIsInitialized.set(false);
                         mService.set(null);
                     }
                 });
     }
+
+    private void sendErrorToApp(int errorCode, String message) {
+        try {
+            mInternalCallback.error(errorCode, message);
+        } catch (RemoteException e) {
+            // Ignore, should not happen locally.
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 8705446..d5ff1ad 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -2540,11 +2540,11 @@
     }
 
     // Split a phone number like "+20(123)-456#" using spaces, ignoring anything that is not
-    // a digit, to produce a result like "20 123 456".
+    // a digit or the characters * and #, to produce a result like "20 123 456#".
     private static String splitAtNonNumerics(CharSequence number) {
         StringBuilder sb = new StringBuilder(number.length());
         for (int i = 0; i < number.length(); i++) {
-            sb.append(PhoneNumberUtils.isISODigit(number.charAt(i))
+            sb.append(PhoneNumberUtils.is12Key(number.charAt(i))
                     ? number.charAt(i)
                     : " ");
         }
diff --git a/telephony/java/android/telephony/mbms/DownloadProgressListener.java b/telephony/java/android/telephony/mbms/DownloadProgressListener.java
index d6bd5dc..d91e9ad 100644
--- a/telephony/java/android/telephony/mbms/DownloadProgressListener.java
+++ b/telephony/java/android/telephony/mbms/DownloadProgressListener.java
@@ -16,6 +16,8 @@
 
 package android.telephony.mbms;
 
+import android.os.RemoteException;
+
 /**
  * A optional listener class used by download clients to track progress.
  * @hide
@@ -38,8 +40,9 @@
      * @param currentDecodedSize is the number of bytes that have been decoded.
      * @param fullDecodedSize is the total number of bytes that make up the final decoded content.
      */
+    @Override
     public void progress(DownloadRequest request, FileInfo fileInfo,
             int currentDownloadSize, int fullDownloadSize,
-            int currentDecodedSize, int fullDecodedSize) {
+            int currentDecodedSize, int fullDecodedSize) throws RemoteException {
     }
 }
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 01e0bbd..eae9011 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -25,6 +25,7 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
@@ -77,12 +78,18 @@
         private String appIntent;
         private int version = CURRENT_VERSION;
 
+        /**
+         * Sets the service from which the download request to be built will download from.
+         * @param serviceInfo
+         * @return
+         */
         public Builder setServiceInfo(FileServiceInfo serviceInfo) {
             fileServiceId = serviceInfo.getServiceId();
             return this;
         }
 
         /**
+         * Set the service ID for the download request. For use by the middleware only.
          * @hide
          * TODO: systemapi
          */
@@ -91,11 +98,23 @@
             return this;
         }
 
+        /**
+         * Sets the source URI for the download request to be built.
+         * @param source
+         * @return
+         */
         public Builder setSource(Uri source) {
             this.source = source;
             return this;
         }
 
+        /**
+         * Sets the destination URI for the download request to be built. The middleware should
+         * not set this directly.
+         * @param dest A URI obtained from {@link Uri#fromFile(File)}, denoting the requested
+         *             final destination of the download.
+         * @return
+         */
         public Builder setDest(Uri dest) {
             if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) {
                 throw new IllegalArgumentException("Destination uri must not exceed length " +
@@ -105,11 +124,25 @@
             return this;
         }
 
-        public Builder setSubscriptionId(int sub) {
-            this.subscriptionId = sub;
+        /**
+         * Set the subscription ID on which the file(s) should be downloaded.
+         * @param subscriptionId
+         * @return
+         */
+        public Builder setSubscriptionId(int subscriptionId) {
+            this.subscriptionId = subscriptionId;
             return this;
         }
 
+        /**
+         * Set the {@link Intent} that should be sent when the download completes or fails. This
+         * should be an intent with a explicit {@link android.content.ComponentName} targeted to a
+         * {@link android.content.BroadcastReceiver} in the app's package.
+         *
+         * The middleware should not use this method.
+         * @param intent
+         * @return
+         */
         public Builder setAppIntent(Intent intent) {
             this.appIntent = intent.toUri(0);
             if (this.appIntent.length() > MAX_APP_INTENT_SIZE) {
@@ -120,7 +153,12 @@
         }
 
         /**
-         * For use by middleware only
+         * For use by the middleware to set the byte array of opaque data. The opaque data
+         * includes information about the download request that is used by the client app and the
+         * manager code, but is irrelevant to the middleware.
+         * @param data A byte array, the contents of which should have been originally obtained
+         *             from {@link DownloadRequest#getOpaqueData()}.
+         * @return
          * TODO: systemapi
          * @hide
          */
@@ -201,22 +239,40 @@
         out.writeInt(version);
     }
 
+    /**
+     * @return The ID of the file service to download from.
+     */
     public String getFileServiceId() {
         return fileServiceId;
     }
 
+    /**
+     * @return The source URI to download from
+     */
     public Uri getSourceUri() {
         return sourceUri;
     }
 
+    /**
+     * For use by the client app only.
+     * @return The URI of the final destination of the download.
+     */
     public Uri getDestinationUri() {
         return destinationUri;
     }
 
+    /**
+     * @return The subscription ID on which to perform MBMS operations.
+     */
     public int getSubscriptionId() {
         return subscriptionId;
     }
 
+    /**
+     * For internal use -- returns the intent to send to the app after download completion or
+     * failure.
+     * @hide
+     */
     public Intent getIntentForApp() {
         try {
             return Intent.parseUri(serializedResultIntentForApp, 0);
@@ -226,6 +282,10 @@
     }
 
     /**
+     * For use by the middleware only. The byte array returned from this method should be
+     * persisted and sent back to the app upon download completion or failure by passing it into
+     * {@link Builder#setOpaqueData(byte[])}.
+     * @return A byte array of opaque data to persist.
      * @hide
      * TODO: systemapi
      */
diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java
index 1b87393..f97131d 100644
--- a/telephony/java/android/telephony/mbms/FileInfo.java
+++ b/telephony/java/android/telephony/mbms/FileInfo.java
@@ -38,16 +38,6 @@
      */
     private final String mimeType;
 
-    /**
-     * The size of the file in bytes.
-     */
-    private final long size;
-
-    /**
-     * The MD5 hash of the file.
-     */
-    private final byte md5Hash[];
-
     public static final Parcelable.Creator<FileInfo> CREATOR =
             new Parcelable.Creator<FileInfo>() {
         @Override
@@ -61,29 +51,24 @@
         }
     };
 
-    public FileInfo(Uri uri, String mimeType, long size, byte[] md5Hash) {
+    /**
+     * @hide
+     * TODO: systemapi
+     */
+    public FileInfo(Uri uri, String mimeType) {
         this.uri = uri;
         this.mimeType = mimeType;
-        this.size = size;
-        this.md5Hash = md5Hash;
     }
 
     private FileInfo(Parcel in) {
         uri = in.readParcelable(null);
         mimeType = in.readString();
-        size = in.readLong();
-        int arraySize = in.readInt();
-        md5Hash = new byte[arraySize];
-        in.readByteArray(md5Hash);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(uri, flags);
         dest.writeString(mimeType);
-        dest.writeLong(size);
-        dest.writeInt(md5Hash.length);
-        dest.writeByteArray(md5Hash);
     }
 
     @Override
@@ -98,12 +83,4 @@
     public String getMimeType() {
         return mimeType;
     }
-
-    public long getSize() {
-        return size;
-    }
-
-    public byte[] getMd5Hash() {
-        return md5Hash;
-    }
 }
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index 6646dc8..8afe4d3 100644
--- a/telephony/java/android/telephony/mbms/FileServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -32,6 +32,7 @@
 public class FileServiceInfo extends ServiceInfo implements Parcelable {
     private final List<FileInfo> files;
 
+    /** @hide TODO: systemapi */
     public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
             List<Locale> newLocales, String newServiceId, Date start, Date end,
             List<FileInfo> newFiles) {
diff --git a/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java
new file mode 100644
index 0000000..b52df8c
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.mbms;
+
+import android.os.Handler;
+import android.os.RemoteException;
+import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.StreamingServiceInfo;
+
+import java.util.List;
+
+/** @hide */
+public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
+    private final Handler mHandler;
+    private final MbmsStreamingManagerCallback mAppCallback;
+
+    public InternalStreamingManagerCallback(MbmsStreamingManagerCallback appCallback,
+            Handler handler) {
+        mAppCallback = appCallback;
+        mHandler = handler;
+    }
+
+    @Override
+    public void error(int errorCode, String message) throws RemoteException {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mAppCallback.onError(errorCode, message);
+            }
+        });
+    }
+
+    @Override
+    public void streamingServicesUpdated(List<StreamingServiceInfo> services)
+            throws RemoteException {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mAppCallback.onStreamingServicesUpdated(services);
+            }
+        });
+    }
+
+    @Override
+    public void middlewareReady() throws RemoteException {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mAppCallback.onMiddlewareReady();
+            }
+        });
+    }
+
+    public Handler getHandler() {
+        return mHandler;
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
new file mode 100644
index 0000000..bb337b2
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.mbms;
+
+import android.os.Handler;
+import android.os.RemoteException;
+
+/** @hide */
+public class InternalStreamingServiceCallback extends IStreamingServiceCallback.Stub {
+    private final StreamingServiceCallback mAppCallback;
+    private final Handler mHandler;
+
+    public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, Handler handler) {
+        mAppCallback = appCallback;
+        mHandler = handler;
+    }
+
+    @Override
+    public void error(int errorCode, String message) throws RemoteException {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mAppCallback.onError(errorCode, message);
+            }
+        });
+    }
+
+    @Override
+    public void streamStateUpdated(int state, int reason) throws RemoteException {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mAppCallback.onStreamStateUpdated(state, reason);
+            }
+        });
+    }
+
+    @Override
+    public void mediaDescriptionUpdated() throws RemoteException {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mAppCallback.onMediaDescriptionUpdated();
+            }
+        });
+    }
+
+    @Override
+    public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mAppCallback.onBroadcastSignalStrengthUpdated(signalStrength);
+            }
+        });
+    }
+
+    @Override
+    public void streamMethodUpdated(int methodType) throws RemoteException {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mAppCallback.onStreamMethodUpdated(methodType);
+            }
+        });
+    }
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
index ba25f66..17291d0 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
@@ -16,6 +16,9 @@
 
 package android.telephony.mbms;
 
+import android.os.RemoteException;
+import android.telephony.MbmsDownloadManager;
+
 import java.util.List;
 
 /**
@@ -24,12 +27,8 @@
  */
 public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.Stub {
 
-    public final static int ERROR_CARRIER_NOT_SUPPORTED      = 1;
-    public final static int ERROR_UNABLE_TO_INITIALIZE       = 2;
-    public final static int ERROR_UNABLE_TO_ALLOCATE_MEMORY  = 3;
-
-
-    public void error(int errorCode, String message) {
+    @Override
+    public void error(int errorCode, String message) throws RemoteException {
         // default implementation empty
     }
 
@@ -45,7 +44,8 @@
      * @param services a List of FileServiceInfos
      *
      */
-    public void fileServicesUpdated(List<FileServiceInfo> services) {
+    @Override
+    public void fileServicesUpdated(List<FileServiceInfo> services) throws RemoteException {
         // default implementation empty
     }
 
@@ -58,7 +58,7 @@
      * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
      */
     @Override
-    public void middlewareReady() {
+    public void middlewareReady() throws RemoteException {
         // default implementation empty
     }
 }
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index 339ff39..ba7d120 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -25,6 +25,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.telephony.MbmsDownloadManager;
+import android.telephony.mbms.vendor.VendorIntents;
 import android.util.Log;
 
 import java.io.File;
@@ -56,9 +57,9 @@
     /**
      * Indicates that the intent sent had an invalid action. This will be the result if
      * {@link Intent#getAction()} returns anything other than
-     * {@link MbmsDownloadManager#ACTION_DOWNLOAD_RESULT_INTERNAL},
-     * {@link MbmsDownloadManager#ACTION_FILE_DESCRIPTOR_REQUEST}, or
-     * {@link MbmsDownloadManager#ACTION_CLEANUP}.
+     * {@link VendorIntents#ACTION_DOWNLOAD_RESULT_INTERNAL},
+     * {@link VendorIntents#ACTION_FILE_DESCRIPTOR_REQUEST}, or
+     * {@link VendorIntents#ACTION_CLEANUP}.
      * This is a fatal result code and no result extras should be expected.
      */
     public static final int RESULT_INVALID_ACTION = 1;
@@ -70,7 +71,7 @@
     public static final int RESULT_MALFORMED_INTENT = 2;
 
     /**
-     * Indicates that the supplied value for {@link MbmsDownloadManager#EXTRA_TEMP_FILE_ROOT}
+     * Indicates that the supplied value for {@link VendorIntents#EXTRA_TEMP_FILE_ROOT}
      * does not match what the app has stored.
      * This is a fatal result code and no result extras should be expected.
      */
@@ -104,18 +105,18 @@
             setResultCode(RESULT_MALFORMED_INTENT);
             return;
         }
-        if (!Objects.equals(intent.getStringExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT),
+        if (!Objects.equals(intent.getStringExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT),
                 MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) {
             setResultCode(RESULT_BAD_TEMP_FILE_ROOT);
             return;
         }
 
-        if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
+        if (VendorIntents.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
             moveDownloadedFile(context, intent);
             cleanupPostMove(context, intent);
-        } else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
+        } else if (VendorIntents.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
             generateTempFiles(context, intent);
-        } else if (MbmsDownloadManager.ACTION_CLEANUP.equals(intent.getAction())) {
+        } else if (VendorIntents.ACTION_CLEANUP.equals(intent.getAction())) {
             cleanupTempFiles(context, intent);
         } else {
             setResultCode(RESULT_INVALID_ACTION);
@@ -123,16 +124,16 @@
     }
 
     private boolean verifyIntentContents(Context context, Intent intent) {
-        if (MbmsDownloadManager.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
+        if (VendorIntents.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
             if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) {
                 Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_REQUEST)) {
+            if (!intent.hasExtra(VendorIntents.EXTRA_REQUEST)) {
                 Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) {
+            if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) {
                 Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
                 return false;
             }
@@ -141,12 +142,12 @@
                         "Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_FINAL_URI)) {
+            if (!intent.hasExtra(VendorIntents.EXTRA_FINAL_URI)) {
                 Log.w(LOG_TAG, "Download result did not include the path to the final " +
                         "temp file. Ignoring.");
                 return false;
             }
-            DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
+            DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST);
             String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
             File expectedTokenFile = new File(
                     MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()),
@@ -156,27 +157,27 @@
                         "Expected " + expectedTokenFile);
                 return false;
             }
-        } else if (MbmsDownloadManager.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO)) {
+        } else if (VendorIntents.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) {
+            if (!intent.hasExtra(VendorIntents.EXTRA_SERVICE_INFO)) {
                 Log.w(LOG_TAG, "Temp file request did not include the associated service info." +
                         " Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) {
+            if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) {
                 Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
                 return false;
             }
-        } else if (MbmsDownloadManager.ACTION_CLEANUP.equals(intent.getAction())) {
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO)) {
+        } else if (VendorIntents.ACTION_CLEANUP.equals(intent.getAction())) {
+            if (!intent.hasExtra(VendorIntents.EXTRA_SERVICE_INFO)) {
                 Log.w(LOG_TAG, "Cleanup request did not include the associated service info." +
                         " Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILE_ROOT)) {
+            if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) {
                 Log.w(LOG_TAG, "Cleanup request did not include the temp file root. Ignoring.");
                 return false;
             }
-            if (!intent.hasExtra(MbmsDownloadManager.EXTRA_TEMP_FILES_IN_USE)) {
+            if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILES_IN_USE)) {
                 Log.w(LOG_TAG, "Cleanup request did not include the list of temp files in use. " +
                         "Ignoring.");
                 return false;
@@ -186,7 +187,7 @@
     }
 
     private void moveDownloadedFile(Context context, Intent intent) {
-        DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
+        DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST);
         Intent intentForApp = request.getIntentForApp();
 
         int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT,
@@ -200,7 +201,7 @@
         }
 
         Uri destinationUri = request.getDestinationUri();
-        Uri finalTempFile = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FINAL_URI);
+        Uri finalTempFile = intent.getParcelableExtra(VendorIntents.EXTRA_FINAL_URI);
         if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) {
             Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
             setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
@@ -225,13 +226,13 @@
     }
 
     private void cleanupPostMove(Context context, Intent intent) {
-        DownloadRequest request = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_REQUEST);
+        DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST);
         if (request == null) {
             Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
             return;
         }
 
-        List<Uri> tempFiles = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_TEMP_LIST);
+        List<Uri> tempFiles = intent.getParcelableExtra(VendorIntents.EXTRA_TEMP_LIST);
         if (tempFiles == null) {
             return;
         }
@@ -246,15 +247,15 @@
 
     private void generateTempFiles(Context context, Intent intent) {
         FileServiceInfo serviceInfo =
-                intent.getParcelableExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO);
+                intent.getParcelableExtra(VendorIntents.EXTRA_SERVICE_INFO);
         if (serviceInfo == null) {
             Log.w(LOG_TAG, "Temp file request did not include the associated service info. " +
                     "Ignoring.");
             setResultCode(RESULT_MALFORMED_INTENT);
             return;
         }
-        int fdCount = intent.getIntExtra(MbmsDownloadManager.EXTRA_FD_COUNT, 0);
-        List<Uri> pausedList = intent.getParcelableExtra(MbmsDownloadManager.EXTRA_PAUSED_LIST);
+        int fdCount = intent.getIntExtra(VendorIntents.EXTRA_FD_COUNT, 0);
+        List<Uri> pausedList = intent.getParcelableExtra(VendorIntents.EXTRA_PAUSED_LIST);
 
         if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
             Log.i(LOG_TAG, "No temp files actually requested. Ending.");
@@ -269,8 +270,8 @@
                 generateUrisForPausedFiles(context, serviceInfo, pausedList);
 
         Bundle result = new Bundle();
-        result.putParcelableArrayList(MbmsDownloadManager.EXTRA_FREE_URI_LIST, freshTempFiles);
-        result.putParcelableArrayList(MbmsDownloadManager.EXTRA_PAUSED_URI_LIST, pausedFiles);
+        result.putParcelableArrayList(VendorIntents.EXTRA_FREE_URI_LIST, freshTempFiles);
+        result.putParcelableArrayList(VendorIntents.EXTRA_PAUSED_URI_LIST, pausedFiles);
         setResultCode(RESULT_OK);
         setResultExtras(result);
     }
@@ -353,11 +354,11 @@
 
     private void cleanupTempFiles(Context context, Intent intent) {
         FileServiceInfo serviceInfo =
-                intent.getParcelableExtra(MbmsDownloadManager.EXTRA_SERVICE_INFO);
+                intent.getParcelableExtra(VendorIntents.EXTRA_SERVICE_INFO);
         File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context,
                 serviceInfo.getServiceId());
         final List<Uri> filesInUse =
-                intent.getParcelableArrayListExtra(MbmsDownloadManager.EXTRA_TEMP_FILES_IN_USE);
+                intent.getParcelableArrayListExtra(VendorIntents.EXTRA_TEMP_FILES_IN_USE);
         File[] filesToDelete = tempFileDir.listFiles(new FileFilter() {
             @Override
             public boolean accept(File file) {
diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java
index 8888119..7cf8792 100644
--- a/telephony/java/android/telephony/mbms/MbmsException.java
+++ b/telephony/java/android/telephony/mbms/MbmsException.java
@@ -31,7 +31,7 @@
 
     /**
      * Indicates that the app attempted to perform an operation on an instance of
-     * {@link android.telephony.MbmsDownloadManager} or
+     * TODO: link android.telephony.MbmsDownloadManager or
      * {@link android.telephony.MbmsStreamingManager} without being bound to the middleware.
      */
     public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2;
@@ -44,10 +44,11 @@
      * middleware. They are applicable to both streaming and file-download use-cases.
      */
     public static class InitializationErrors {
+        private InitializationErrors() {}
         /**
          * Indicates that the app tried to create more than one instance each of
          * {@link android.telephony.MbmsStreamingManager} or
-         * {@link android.telephony.MbmsDownloadManager}.
+         * TODO: link android.telephony.MbmsDownloadManager
          */
         public static final int ERROR_DUPLICATE_INITIALIZE = 101;
         /** Indicates that the app is not authorized to access media via MBMS.*/
@@ -61,10 +62,11 @@
      * streaming and file-download.
      */
     public static class GeneralErrors {
+        private GeneralErrors() {}
         /**
          * Indicates that the app attempted to perform an operation before receiving notification
-         * that the middleware is ready via {@link MbmsStreamingManagerCallback#middlewareReady()}
-         * or {@link MbmsDownloadManagerCallback#middlewareReady()}.
+         * that the middleware is ready via {@link MbmsStreamingManagerCallback#onMiddlewareReady()}
+         * or TODO: link MbmsDownloadManagerCallback#middlewareReady
          */
         public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201;
         /**
@@ -97,6 +99,7 @@
      * Indicates the errors that are applicable only to the streaming use-case
      */
     public static class StreamingErrors {
+        private StreamingErrors() {}
         /** Indicates that the middleware cannot start a stream due to too many ongoing streams */
         public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301;
 
@@ -105,7 +108,8 @@
 
         /**
          * Indicates that the app called
-         * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo, StreamingServiceCallback)}
+         * {@link android.telephony.MbmsStreamingManager#startStreaming(
+         * StreamingServiceInfo, StreamingServiceCallback, android.os.Handler)}
          * more than once for the same {@link StreamingServiceInfo}.
          */
         public static final int ERROR_DUPLICATE_START_STREAM = 303;
@@ -113,6 +117,8 @@
 
     /**
      * Indicates the errors that are applicable only to the file-download use-case
+     * TODO: unhide
+     * @hide
      */
     public static class DownloadErrors {
         /**
@@ -127,9 +133,7 @@
 
     private final int mErrorCode;
 
-    /** @hide
-     * TODO: future systemapi
-     */
+    /** @hide */
     public MbmsException(int errorCode) {
         super();
         mErrorCode = errorCode;
diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
index 2e91be9..831050e 100644
--- a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
@@ -16,20 +16,26 @@
 
 package android.telephony.mbms;
 
+import android.content.Context;
+import android.os.RemoteException;
+import android.telephony.MbmsStreamingManager;
+
 import java.util.List;
 
 /**
- * A Parcelable class with Cell-Broadcast service information.
+ * A callback class that is used to receive information from the middleware on MBMS streaming
+ * services. An instance of this object should be passed into
+ * {@link android.telephony.MbmsStreamingManager#create(Context, MbmsStreamingManagerCallback)}.
  * @hide
  */
-public class MbmsStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
-
-    public final static int ERROR_CARRIER_NOT_SUPPORTED      = 1;
-    public final static int ERROR_UNABLE_TO_INITIALIZE       = 2;
-    public final static int ERROR_UNABLE_TO_ALLOCATE_MEMORY  = 3;
-
-
-    public void error(int errorCode, String message) {
+public class MbmsStreamingManagerCallback {
+    /**
+     * Called by the middleware when it has detected an error condition. The possible error codes
+     * are listed in {@link MbmsException}.
+     * @param errorCode The error code.
+     * @param message A human-readable message generated by the middleware for debugging purposes.
+     */
+    public void onError(int errorCode, String message) {
         // default implementation empty
     }
 
@@ -45,7 +51,7 @@
      * @param services a List of StreamingServiceInfos
      *
      */
-    public void streamingServicesUpdated(List<StreamingServiceInfo> services) {
+    public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
         // default implementation empty
     }
 
@@ -57,8 +63,7 @@
      * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
      * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
      */
-    @Override
-    public void middlewareReady() {
+    public void onMiddlewareReady() {
         // default implementation empty
     }
 }
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index f9ad44c..423ae01 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -30,43 +30,22 @@
 import java.util.Set;
 
 /**
- * A Parcelable class with Cell-Broadcast service information.
+ * Describes a cell-broadcast service. This class should not be instantiated directly -- use
+ * {@link StreamingServiceInfo} or FileServiceInfo TODO: add link once that's unhidden
  * @hide
  */
-public class ServiceInfo implements Parcelable {
+public class ServiceInfo {
     // arbitrary limit on the number of locale -> name pairs we support
     final static int MAP_LIMIT = 1000;
-    /**
-     * User displayable names listed by language.  Unmodifiable.
-     */
-    final Map<Locale, String> names;
 
-    /**
-     * The class name for this service - used to catagorize and filter
-     */
-    final String className;
+    private final Map<Locale, String> names;
+    private final String className;
+    private final List<Locale> locales;
+    private final String serviceId;
+    private final Date sessionStartTime;
+    private final Date sessionEndTime;
 
-    /**
-     * The languages available for this service content
-     */
-    final List<Locale> locales;
-
-    /**
-     * The carrier's identifier for the service.
-     */
-    final String serviceId;
-
-    /**
-     * The start time indicating when this service will be available.
-     */
-    final Date sessionStartTime;
-
-    /**
-     * The end time indicating when this sesion stops being available.
-     */
-    final Date sessionEndTime;
-
-
+    /** @hide */
     public ServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales,
             String newServiceId, Date start, Date end) {
         if (newNames == null || newNames.isEmpty() || TextUtils.isEmpty(newClassName)
@@ -89,20 +68,8 @@
         sessionEndTime = (Date)end.clone();
     }
 
-    public static final Parcelable.Creator<FileServiceInfo> CREATOR =
-            new Parcelable.Creator<FileServiceInfo>() {
-        @Override
-        public FileServiceInfo createFromParcel(Parcel source) {
-            return new FileServiceInfo(source);
-        }
-
-        @Override
-        public FileServiceInfo[] newArray(int size) {
-            return new FileServiceInfo[size];
-        }
-    };
-
-    ServiceInfo(Parcel in) {
+    /** @hide */
+    protected ServiceInfo(Parcel in) {
         int mapCount = in.readInt();
         if (mapCount > MAP_LIMIT || mapCount < 0) {
             throw new RuntimeException("bad map length" + mapCount);
@@ -128,7 +95,7 @@
         sessionEndTime = (java.util.Date) in.readSerializable();
     }
 
-    @Override
+    /** @hide */
     public void writeToParcel(Parcel dest, int flags) {
         Set<Locale> keySet = names.keySet();
         dest.writeInt(keySet.size());
@@ -147,31 +114,44 @@
         dest.writeSerializable(sessionEndTime);
     }
 
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
+    /**
+     * User displayable names listed by language. Do not modify the map returned from this method.
+     */
     public Map<Locale, String> getNames() {
         return names;
     }
 
+    /**
+     * The class name for this service - used to categorize and filter
+     */
     public String getClassName() {
         return className;
     }
 
+    /**
+     * The languages available for this service content
+     */
     public List<Locale> getLocales() {
         return locales;
     }
 
+    /**
+     * The carrier's identifier for the service.
+     */
     public String getServiceId() {
         return serviceId;
     }
 
+    /**
+     * The start time indicating when this service will be available.
+     */
     public Date getSessionStartTime() {
         return sessionStartTime;
     }
 
+    /**
+     * The end time indicating when this session stops being available.
+     */
     public Date getSessionEndTime() {
         return sessionEndTime;
     }
diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java
index c49f8a9..5c4b786 100644
--- a/telephony/java/android/telephony/mbms/StreamingService.java
+++ b/telephony/java/android/telephony/mbms/StreamingService.java
@@ -26,13 +26,17 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
+ * Class used to represent a single MBMS stream. After a stream has been started with
+ * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+ * StreamingServiceCallback, android.os.Handler)},
+ * this class is used to hold information about the stream and control it.
  * @hide
  */
 public class StreamingService {
     private static final String LOG_TAG = "MbmsStreamingService";
 
     /**
-     * The state of a stream, reported via {@link StreamingServiceCallback#streamStateUpdated}
+     * The state of a stream, reported via {@link StreamingServiceCallback#onStreamStateUpdated}
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
@@ -44,7 +48,7 @@
 
     /**
      * The reason for a stream state change, reported via
-     * {@link StreamingServiceCallback#streamStateUpdated}
+     * {@link StreamingServiceCallback#onStreamStateUpdated}
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
@@ -60,7 +64,8 @@
 
     /**
      * State changed due to a call to {@link #stopStreaming()} or
-     * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo, StreamingServiceCallback)}
+     * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+     * StreamingServiceCallback, android.os.Handler)}
      */
     public static final int REASON_BY_USER_REQUEST = 1;
 
@@ -87,27 +92,28 @@
     /**
      * State changed due to the device leaving the where this stream is being broadcast.
      */
-    public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 5;
+    public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 6;
 
     /**
      * The method of transmission currently used for a stream,
-     * reported via {@link StreamingServiceCallback#streamMethodUpdated}
+     * reported via {@link StreamingServiceCallback#onStreamMethodUpdated}
      */
     public final static int BROADCAST_METHOD = 1;
     public final static int UNICAST_METHOD   = 2;
 
     private final int mSubscriptionId;
     private final StreamingServiceInfo mServiceInfo;
-    private final IStreamingServiceCallback mCallback;
+    private final InternalStreamingServiceCallback mCallback;
 
     private IMbmsStreamingService mService;
+
     /**
      * @hide
      */
     public StreamingService(int subscriptionId,
             IMbmsStreamingService service,
             StreamingServiceInfo streamingServiceInfo,
-            IStreamingServiceCallback callback) {
+            InternalStreamingServiceCallback callback) {
         mSubscriptionId = subscriptionId;
         mService = service;
         mServiceInfo = streamingServiceInfo;
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
index cab9c23..eeef8bc 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
@@ -17,10 +17,11 @@
 package android.telephony.mbms;
 
 /**
- * A Callback class for use when the application is actively streaming content.
+ * A callback class for use when the application is actively streaming content. The middleware
+ * will provide updates on the status of the stream via this callback.
  * @hide
  */
-public class StreamingServiceCallback extends IStreamingServiceCallback.Stub {
+public class StreamingServiceCallback {
 
     /**
      * Indicates broadcast signal strength is not available for this service.
@@ -31,8 +32,13 @@
      */
     public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1;
 
-    @Override
-    public void error(int errorCode, String message) {
+    /**
+     * Called by the middleware when it has detected an error condition in this stream. The
+     * possible error codes are listed in {@link MbmsException}.
+     * @param errorCode The error code.
+     * @param message A human-readable message generated by the middleware for debugging purposes.
+     */
+    public void onError(int errorCode, String message) {
         // default implementation empty
     }
 
@@ -42,8 +48,7 @@
      * See {@link StreamingService#STATE_STOPPED}, {@link StreamingService#STATE_STARTED}
      * and {@link StreamingService#STATE_STALLED}.
      */
-    @Override
-    public void streamStateUpdated(@StreamingService.StreamingState int state,
+    public void onStreamStateUpdated(@StreamingService.StreamingState int state,
             @StreamingService.StreamingStateChangeReason int reason) {
         // default implementation empty
     }
@@ -58,8 +63,7 @@
      * This may be called when a looping stream hits the end or
      * when parameters have changed to account for time drift.
      */
-    @Override
-    public void mediaDescriptionUpdated() {
+    public void onMediaDescriptionUpdated() {
         // default implementation empty
     }
 
@@ -73,8 +77,7 @@
      * {@link #SIGNAL_STRENGTH_UNAVAILABLE} if broadcast is not available
      * for this service due to timing, geography or popularity.
      */
-    @Override
-    public void broadcastSignalStrengthUpdated(int signalStrength) {
+    public void onBroadcastSignalStrengthUpdated(int signalStrength) {
         // default implementation empty
     }
 
@@ -94,8 +97,7 @@
      * See {@link StreamingService#BROADCAST_METHOD} and
      * {@link StreamingService#UNICAST_METHOD}
      */
-    @Override
-    public void streamMethodUpdated(int methodType) {
+    public void onStreamMethodUpdated(int methodType) {
         // default implementation empty
     }
 }
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
index 77ce3bb..8e7917a 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
@@ -16,6 +16,7 @@
 
 package android.telephony.mbms;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -25,15 +26,25 @@
 import java.util.Map;
 
 /**
- * A Parcelable class Cell-Broadcast media stream information.
- * This may not have any more info than ServiceInfo, but kept for completeness.
+ * Describes a single MBMS streaming service.
  * @hide
  */
-public class StreamingServiceInfo extends ServiceInfo implements Parcelable {
+public final class StreamingServiceInfo extends ServiceInfo implements Parcelable {
 
-    public StreamingServiceInfo(Map<Locale, String> newNames, String newClassName,
-            List<Locale> newLocales, String newServiceId, Date start, Date end) {
-        super(newNames, newClassName, newLocales, newServiceId, start, end);
+    /**
+     * @param names User displayable names listed by language.
+     * @param className The class name for this service - used by frontend apps to categorize and
+     *                  filter.
+     * @param locales The languages available for this service content.
+     * @param serviceId The carrier's identifier for the service.
+     * @param start The start time indicating when this service will be available.
+     * @param end The end time indicating when this session stops being available.
+     * @hide
+     */
+    @SystemApi
+    public StreamingServiceInfo(Map<Locale, String> names, String className,
+            List<Locale> locales, String serviceId, Date start, Date end) {
+        super(names, className, locales, serviceId, start, end);
     }
 
     public static final Parcelable.Creator<StreamingServiceInfo> CREATOR =
@@ -49,7 +60,7 @@
         }
     };
 
-    StreamingServiceInfo(Parcel in) {
+    private StreamingServiceInfo(Parcel in) {
         super(in);
     }
 
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index edd5858..71713d0 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -18,10 +18,13 @@
 
 import android.annotation.NonNull;
 import android.os.RemoteException;
+import android.telephony.mbms.DownloadProgressListener;
 import android.telephony.mbms.DownloadRequest;
 import android.telephony.mbms.FileInfo;
+import android.telephony.mbms.FileServiceInfo;
 import android.telephony.mbms.IDownloadProgressListener;
 import android.telephony.mbms.IMbmsDownloadManagerCallback;
+import android.telephony.mbms.MbmsDownloadManagerCallback;
 import android.telephony.mbms.MbmsException;
 
 import java.util.List;
@@ -44,16 +47,40 @@
      * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
      * {@link IMbmsDownloadManagerCallback#error(int, String)}.
      *
-     * @param listener The callback to use to communicate with the app.
+     * @param callback The callback to use to communicate with the app.
      * @param subscriptionId The subscription ID to use.
      */
-    @Override
-    public int initialize(int subscriptionId,
-            IMbmsDownloadManagerCallback listener) throws RemoteException {
+    public int initialize(int subscriptionId, MbmsDownloadManagerCallback callback)
+            throws RemoteException {
         return 0;
     }
 
     /**
+     * Actual AIDL implementation -- hides the callback AIDL from the API.
+     * @hide
+     */
+    @Override
+    public final int initialize(int subscriptionId,
+            final IMbmsDownloadManagerCallback callback) throws RemoteException {
+        return initialize(subscriptionId, new MbmsDownloadManagerCallback() {
+            @Override
+            public void error(int errorCode, String message) throws RemoteException {
+                callback.error(errorCode, message);
+            }
+
+            @Override
+            public void fileServicesUpdated(List<FileServiceInfo> services) throws RemoteException {
+                callback.fileServicesUpdated(services);
+            }
+
+            @Override
+            public void middlewareReady() throws RemoteException {
+                callback.middlewareReady();
+            }
+        });
+    }
+
+    /**
      * Registers serviceClasses of interest with the appName/subId key.
      * Starts async fetching data on streaming services of matching classes to be reported
      * later via {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)}
@@ -107,19 +134,36 @@
      * @param downloadRequest An object describing the set of files to be downloaded.
      * @param listener A listener through which the middleware can provide progress updates to
      *                 the app while both are still running.
-     * @return TODO: enumerate possible return values
+     * @return Any error from {@link android.telephony.mbms.MbmsException.GeneralErrors}
+     *         or {@link MbmsException#SUCCESS}
+     */
+    public int download(DownloadRequest downloadRequest, DownloadProgressListener listener) {
+        return 0;
+    }
+
+    /**
+     * Actual AIDL implementation -- hides the callback AIDL from the API.
+     * @hide
      */
     @Override
-    public int download(DownloadRequest downloadRequest, IDownloadProgressListener listener)
+    public final int download(DownloadRequest downloadRequest, IDownloadProgressListener listener)
             throws RemoteException {
-        return 0;
+        return download(downloadRequest, new DownloadProgressListener() {
+            @Override
+            public void progress(DownloadRequest request, FileInfo fileInfo, int
+                    currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
+                    fullDecodedSize) throws RemoteException {
+                listener.progress(request, fileInfo, currentDownloadSize, fullDownloadSize,
+                        currentDecodedSize, fullDecodedSize);
+            }
+        });
     }
 
 
     /**
      * Returns a list of pending {@link DownloadRequest}s that originated from the calling
      * application, identified by its uid. A pending request is one that was issued via
-     * {@link #download(DownloadRequest, IDownloadCallback)} but not cancelled through
+     * {@link #download(DownloadRequest, IDownloadProgressListener)} but not cancelled through
      * {@link #cancelDownload(DownloadRequest)}.
      * The middleware must return a non-null result synchronously or throw an exception
      * inheriting from {@link RuntimeException}.
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index 585d5b9..8f2786f 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -17,23 +17,29 @@
 package android.telephony.mbms.vendor;
 
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.telephony.mbms.IMbmsStreamingManagerCallback;
 import android.telephony.mbms.IStreamingServiceCallback;
 import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.StreamingService;
+import android.telephony.mbms.StreamingServiceCallback;
+import android.telephony.mbms.StreamingServiceInfo;
 
 import java.util.List;
 
 /**
  * @hide
- * TODO: future systemapi
  */
+//@SystemApi
 public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
     /**
      * Initialize streaming service for this app and subId, registering the listener.
      *
-     * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which
+     * May throw an {@link IllegalArgumentException} or a {@link SecurityException}, which
      * will be intercepted and passed to the app as
      * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
      *
@@ -41,16 +47,61 @@
      * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
      * {@link IMbmsStreamingManagerCallback#error(int, String)}.
      *
-     * @param listener The callback to use to communicate with the app.
+     * @param callback The callback to use to communicate with the app.
      * @param subscriptionId The subscription ID to use.
      */
-    @Override
-    public int initialize(IMbmsStreamingManagerCallback listener, int subscriptionId)
+    public int initialize(MbmsStreamingManagerCallback callback, int subscriptionId)
             throws RemoteException {
         return 0;
     }
 
     /**
+     * Actual AIDL implementation that hides the callback AIDL from the middleware.
+     * @hide
+     */
+    @Override
+    public final int initialize(final IMbmsStreamingManagerCallback callback,
+            final int subscriptionId) throws RemoteException {
+        final int uid = Binder.getCallingUid();
+        callback.asBinder().linkToDeath(new DeathRecipient() {
+            @Override
+            public void binderDied() {
+                onAppCallbackDied(uid, subscriptionId);
+            }
+        }, 0);
+
+        return initialize(new MbmsStreamingManagerCallback() {
+            @Override
+            public void onError(int errorCode, String message) {
+                try {
+                    callback.error(errorCode, message);
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }
+
+            @Override
+            public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
+                try {
+                    callback.streamingServicesUpdated(services);
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }
+
+            @Override
+            public void onMiddlewareReady() {
+                try {
+                    callback.middlewareReady();
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }
+        }, subscriptionId);
+    }
+
+
+    /**
      * Registers serviceClasses of interest with the appName/subId key.
      * Starts async fetching data on streaming services of matching classes to be reported
      * later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)}
@@ -82,13 +133,77 @@
      *
      * @param subscriptionId The subscription id to use.
      * @param serviceId The ID of the streaming service that the app has requested.
-     * @param listener The listener object on which the app wishes to receive updates.
+     * @param callback The callback object on which the app wishes to receive updates.
      * @return Any error in {@link android.telephony.mbms.MbmsException.GeneralErrors}
      */
+    public int startStreaming(int subscriptionId, String serviceId,
+            StreamingServiceCallback callback) throws RemoteException {
+        return 0;
+    }
+
+    /**
+     * Actual AIDL implementation of startStreaming that hides the callback AIDL from the
+     * middleware.
+     * @hide
+     */
     @Override
     public int startStreaming(int subscriptionId, String serviceId,
-            IStreamingServiceCallback listener) throws RemoteException {
-        return 0;
+            IStreamingServiceCallback callback) throws RemoteException {
+        final int uid = Binder.getCallingUid();
+        callback.asBinder().linkToDeath(new DeathRecipient() {
+            @Override
+            public void binderDied() {
+                onAppCallbackDied(uid, subscriptionId);
+            }
+        }, 0);
+
+        return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
+            @Override
+            public void onError(int errorCode, String message) {
+                try {
+                    callback.error(errorCode, message);
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }
+
+            @Override
+            public void onStreamStateUpdated(@StreamingService.StreamingState int state,
+                    @StreamingService.StreamingStateChangeReason int reason) {
+                try {
+                    callback.streamStateUpdated(state, reason);
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }
+
+            @Override
+            public void onMediaDescriptionUpdated() {
+                try {
+                    callback.mediaDescriptionUpdated();
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }
+
+            @Override
+            public void onBroadcastSignalStrengthUpdated(int signalStrength) {
+                try {
+                    callback.broadcastSignalStrengthUpdated(signalStrength);
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }
+
+            @Override
+            public void onStreamMethodUpdated(int methodType) {
+                try {
+                    callback.streamMethodUpdated(methodType);
+                } catch (RemoteException e) {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }
+        });
     }
 
     /**
@@ -153,4 +268,12 @@
     @Override
     public void dispose(int subscriptionId) throws RemoteException {
     }
+
+    /**
+     * Indicates that the app identified by the given UID and subscription ID has died.
+     * @param uid the UID of the app, as returned by {@link Binder#getCallingUid()}.
+     * @param subscriptionId The subscription ID the app is using.
+     */
+    public void onAppCallbackDied(int uid, int subscriptionId) {
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/vendor/VendorIntents.java b/telephony/java/android/telephony/mbms/vendor/VendorIntents.java
new file mode 100644
index 0000000..367c995
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/VendorIntents.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.mbms.vendor;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.MbmsDownloadReceiver;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * @hide
+ * TODO: future systemapi
+ */
+public class VendorIntents {
+
+    /**
+     * The MBMS middleware should send this when a download of single file has completed or
+     * failed. Mandatory extras are
+     * {@link android.telephony.MbmsDownloadManager#EXTRA_RESULT}
+     * {@link android.telephony.MbmsDownloadManager#EXTRA_FILE_INFO}
+     * {@link #EXTRA_REQUEST}
+     * {@link #EXTRA_TEMP_LIST}
+     * {@link #EXTRA_FINAL_URI}
+     */
+    public static final String ACTION_DOWNLOAD_RESULT_INTERNAL =
+            "android.telephony.mbms.action.DOWNLOAD_RESULT_INTERNAL";
+
+    /**
+     * The MBMS middleware should send this when it wishes to request {@code content://} URIs to
+     * serve as temp files for downloads or when it wishes to resume paused downloads. Mandatory
+     * extras are
+     * {@link #EXTRA_REQUEST}
+     *
+     * Optional extras are
+     * {@link #EXTRA_FD_COUNT} (0 if not present)
+     * {@link #EXTRA_PAUSED_LIST} (empty if not present)
+     */
+    public static final String ACTION_FILE_DESCRIPTOR_REQUEST =
+            "android.telephony.mbms.action.FILE_DESCRIPTOR_REQUEST";
+
+    /**
+     * The MBMS middleware should send this when it wishes to clean up temp  files in the app's
+     * filesystem. Mandatory extras are:
+     * {@link #EXTRA_TEMP_FILES_IN_USE}
+     */
+    public static final String ACTION_CLEANUP =
+            "android.telephony.mbms.action.CLEANUP";
+
+    /**
+     * Extra containing a {@link List} of {@link Uri}s that were used as temp files for this
+     * completed file. These {@link Uri}s should have scheme {@code file://}, and the temp
+     * files will be deleted upon receipt of the intent.
+     * May be null.
+     */
+    public static final String EXTRA_TEMP_LIST = "android.telephony.mbms.extra.TEMP_LIST";
+
+    /**
+     * Extra containing an integer indicating the number of temp files requested.
+     */
+    public static final String EXTRA_FD_COUNT = "android.telephony.mbms.extra.FD_COUNT";
+
+    /**
+     * Extra containing a list of {@link Uri}s that the middleware is requesting access to via
+     * {@link #ACTION_FILE_DESCRIPTOR_REQUEST} in order to resume downloading. These {@link Uri}s
+     * should have scheme {@code file://}.
+     */
+    public static final String EXTRA_PAUSED_LIST = "android.telephony.mbms.extra.PAUSED_LIST";
+
+    /**
+     * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
+     * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These are temp files that are meant
+     * to be used for new file downloads.
+     */
+    public static final String EXTRA_FREE_URI_LIST = "android.telephony.mbms.extra.FREE_URI_LIST";
+
+    /**
+     * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
+     * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These
+     * {@link android.telephony.mbms.UriPathPair}s contain {@code content://} URIs that provide
+     * access to previously paused downloads.
+     */
+    public static final String EXTRA_PAUSED_URI_LIST =
+            "android.telephony.mbms.extra.PAUSED_URI_LIST";
+
+    /**
+     * Extra containing a string that points to the middleware's knowledge of where the temp file
+     * root for the app is. The path should be a canonical path as returned by
+     * {@link File#getCanonicalPath()}
+     */
+    public static final String EXTRA_TEMP_FILE_ROOT =
+            "android.telephony.mbms.extra.TEMP_FILE_ROOT";
+
+    /**
+     * Extra containing a list of {@link Uri}s indicating temp files which the middleware is
+     * still using.
+     */
+    public static final String EXTRA_TEMP_FILES_IN_USE =
+            "android.telephony.mbms.extra.TEMP_FILES_IN_USE";
+
+    /**
+     * Extra containing the {@link DownloadRequest} for which the download result or file
+     * descriptor request is for. Must not be null.
+     */
+    public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
+
+    /**
+     * Extra containing a single {@link Uri} indicating the path to the temp file in which the
+     * decoded downloaded file resides. Must not be null.
+     */
+    public static final String EXTRA_FINAL_URI = "android.telephony.mbms.extra.FINAL_URI";
+
+    /**
+     * Extra containing an instance of {@link android.telephony.mbms.ServiceInfo}, used by
+     * file-descriptor requests and cleanup requests to specify which service they want to
+     * request temp files or clean up temp files for, respectively.
+     */
+    public static final String EXTRA_SERVICE_INFO =
+            "android.telephony.mbms.extra.SERVICE_INFO";
+
+    /**
+     * Retrieves the {@link ComponentName} for the {@link android.content.BroadcastReceiver} that
+     * the various intents from the middleware should be targeted towards.
+     * @param uid The uid of the frontend app.
+     * @return The component name of the receiver that the middleware should send its intents to,
+     * or null if the app didn't declare it in the manifest.
+     */
+    public static ComponentName getAppReceiverFromUid(Context context, int uid) {
+        String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
+        if (packageNames == null) {
+            return null;
+        }
+
+        for (String packageName : packageNames) {
+            ComponentName candidate = new ComponentName(packageName,
+                    MbmsDownloadReceiver.class.getCanonicalName());
+            Intent queryIntent = new Intent();
+            queryIntent.setComponent(candidate);
+            List<ResolveInfo> receivers =
+                    context.getPackageManager().queryBroadcastReceivers(queryIntent, 0);
+            if (receivers != null && receivers.size() > 0) {
+                return candidate;
+            }
+        }
+        return null;
+    }
+}
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 3c3718a..060a518 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -107,11 +107,10 @@
 
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
 
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
 # Make sure to run droiddoc first to generate the stub source files.
-$(full_classes_compiled_jar) : $(android_test_runner_api_gen_stamp)
-$(full_classes_jack) : $(android_test_runner_api_gen_stamp)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_runner_api_gen_stamp)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Archive a copy of the classes.jar in SDK build.
 $(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.runner.stubs.jar)
@@ -202,11 +201,10 @@
 
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
 
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
 # Make sure to run droiddoc first to generate the stub source files.
-$(full_classes_compiled_jar) : $(android_test_mock_gen_stamp)
-$(full_classes_jack) : $(android_test_mock_gen_stamp)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_gen_stamp)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Archive a copy of the classes.jar in SDK build.
 $(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.mock.stubs.jar)
diff --git a/tests/net/java/android/net/netlink/ConntrackMessageTest.java b/tests/net/java/android/net/netlink/ConntrackMessageTest.java
new file mode 100644
index 0000000..3aab942
--- /dev/null
+++ b/tests/net/java/android/net/netlink/ConntrackMessageTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netlink;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.system.OsConstants;
+import libcore.util.HexEncoding;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConntrackMessageTest {
+    private static final boolean USING_LE = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN);
+
+    // Example 1: TCP (192.168.43.209, 44333) -> (23.211.13.26, 443)
+    public static final String CT_V4UPDATE_TCP_HEX =
+            // struct nlmsghdr
+            "50000000" +      // length = 80
+            "0001" +          // type = (1 << 8) | 0
+            "0501" +          // flags
+            "01000000" +      // seqno = 1
+            "00000000" +      // pid = 0
+            // struct nfgenmsg
+            "02" +            // nfgen_family  = AF_INET
+            "00" +            // version = NFNETLINK_V0
+            "0000" +          // res_id
+            // struct nlattr
+            "3400" +          // nla_len = 52
+            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
+                // struct nlattr
+                "1400" +      // nla_len = 20
+                "0180" +      // nla_type = nested CTA_TUPLE_IP
+                    "0800 0100 C0A82BD1" +  // nla_type=CTA_IP_V4_SRC, ip=192.168.43.209
+                    "0800 0200 17D30D1A" +  // nla_type=CTA_IP_V4_DST, ip=23.211.13.26
+                // struct nlattr
+                "1C00" +      // nla_len = 28
+                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
+                    "0500 0100 06 000000" +  // nla_type=CTA_PROTO_NUM, proto=6
+                    "0600 0200 AD2D 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=44333 (big endian)
+                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
+            // struct nlattr
+            "0800" +          // nla_len = 8
+            "0700" +          // nla_type = CTA_TIMEOUT
+            "00069780";       // nla_value = 432000 (big endian)
+    public static final byte[] CT_V4UPDATE_TCP_BYTES =
+            HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+    // Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443)
+    public static final String CT_V4UPDATE_UDP_HEX =
+            // struct nlmsghdr
+            "50000000" +      // length = 80
+            "0001" +          // type = (1 << 8) | 0
+            "0501" +          // flags
+            "01000000" +      // seqno = 1
+            "00000000" +      // pid = 0
+            // struct nfgenmsg
+            "02" +            // nfgen_family  = AF_INET
+            "00" +            // version = NFNETLINK_V0
+            "0000" +          // res_id
+            // struct nlattr
+            "3400" +          // nla_len = 52
+            "0180" +          // nla_type = nested CTA_TUPLE_ORIG
+                // struct nlattr
+                "1400" +      // nla_len = 20
+                "0180" +      // nla_type = nested CTA_TUPLE_IP
+                    "0800 0100 6460A792" +  // nla_type=CTA_IP_V4_SRC, ip=100.96.167.146
+                    "0800 0200 D83AC50A" +  // nla_type=CTA_IP_V4_DST, ip=216.58.197.10
+                // struct nlattr
+                "1C00" +      // nla_len = 28
+                "0280" +      // nla_type = nested CTA_TUPLE_PROTO
+                    "0500 0100 11 000000" +  // nla_type=CTA_PROTO_NUM, proto=17
+                    "0600 0200 90CD 0000" +  // nla_type=CTA_PROTO_SRC_PORT, port=37069 (big endian)
+                    "0600 0300 01BB 0000" +  // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
+            // struct nlattr
+            "0800" +          // nla_len = 8
+            "0700" +          // nla_type = CTA_TIMEOUT
+            "000000B4";       // nla_value = 180 (big endian)
+    public static final byte[] CT_V4UPDATE_UDP_BYTES =
+            HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+    @Test
+    public void testConntrackIPv4TcpTimeoutUpdate() throws Exception {
+        assumeTrue(USING_LE);
+
+        final byte[] tcp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
+                OsConstants.IPPROTO_TCP,
+                (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333,
+                (Inet4Address) InetAddress.getByName("23.211.13.26"), 443,
+                432000);
+        assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp);
+    }
+
+    @Test
+    public void testConntrackIPv4UdpTimeoutUpdate() throws Exception {
+        assumeTrue(USING_LE);
+
+        final byte[] udp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
+                OsConstants.IPPROTO_UDP,
+                (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069,
+                (Inet4Address) InetAddress.getByName("216.58.197.10"), 443,
+                180);
+        assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp);
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index f6481cf..8816d43 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -64,6 +64,7 @@
 import android.net.NetworkMisc;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
+import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.net.StringNetworkSpecifier;
 import android.net.metrics.IpConnectivityLog;
@@ -88,6 +89,7 @@
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.LogPrinter;
 
@@ -109,7 +111,10 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -304,6 +309,10 @@
         private String mRedirectUrl;
 
         MockNetworkAgent(int transport) {
+            this(transport, new LinkProperties());
+        }
+
+        MockNetworkAgent(int transport, LinkProperties linkProperties) {
             final int type = transportToLegacyType(transport);
             final String typeName = ConnectivityManager.getNetworkTypeName(type);
             mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
@@ -329,7 +338,7 @@
             mHandlerThread.start();
             mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
                     "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
-                    new LinkProperties(), mScore, new NetworkMisc()) {
+                    linkProperties, mScore, new NetworkMisc()) {
                 @Override
                 public void unwanted() { mDisconnected.open(); }
 
@@ -3338,6 +3347,68 @@
         assertException(() -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); }, unsupported);
     }
 
+    @SmallTest
+    public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() {
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(networkRequest, networkCallback);
+
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName("wlan0");
+        LinkAddress myIpv4Address = new LinkAddress("192.168.12.3/24");
+        RouteInfo myIpv4DefaultRoute = new RouteInfo((IpPrefix) null,
+                NetworkUtils.numericToInetAddress("192.168.12.1"), lp.getInterfaceName());
+        lp.addLinkAddress(myIpv4Address);
+        lp.addRoute(myIpv4DefaultRoute);
+
+        // Verify direct routes are added when network agent is first registered in
+        // ConnectivityService.
+        MockNetworkAgent networkAgent = new MockNetworkAgent(TRANSPORT_WIFI, lp);
+        networkAgent.connect(true);
+        networkCallback.expectCallback(CallbackState.AVAILABLE, networkAgent);
+        networkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, networkAgent);
+        CallbackInfo cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES,
+                networkAgent);
+        networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent);
+        networkCallback.assertNoCallback();
+        checkDirectlyConnectedRoutes(cbi.arg, Arrays.asList(myIpv4Address),
+                Arrays.asList(myIpv4DefaultRoute));
+        checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()),
+                Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute));
+
+        // Verify direct routes are added during subsequent link properties updates.
+        LinkProperties newLp = new LinkProperties(lp);
+        LinkAddress myIpv6Address1 = new LinkAddress("fe80::cafe/64");
+        LinkAddress myIpv6Address2 = new LinkAddress("2001:db8::2/64");
+        newLp.addLinkAddress(myIpv6Address1);
+        newLp.addLinkAddress(myIpv6Address2);
+        networkAgent.sendLinkProperties(newLp);
+        cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, networkAgent);
+        networkCallback.assertNoCallback();
+        checkDirectlyConnectedRoutes(cbi.arg,
+                Arrays.asList(myIpv4Address, myIpv6Address1, myIpv6Address2),
+                Arrays.asList(myIpv4DefaultRoute));
+        mCm.unregisterNetworkCallback(networkCallback);
+    }
+
+    private void checkDirectlyConnectedRoutes(Object callbackObj,
+            Collection<LinkAddress> linkAddresses, Collection<RouteInfo> otherRoutes) {
+        assertTrue(callbackObj instanceof LinkProperties);
+        LinkProperties lp = (LinkProperties) callbackObj;
+
+        Set<RouteInfo> expectedRoutes = new ArraySet<>();
+        expectedRoutes.addAll(otherRoutes);
+        for (LinkAddress address : linkAddresses) {
+            RouteInfo localRoute = new RouteInfo(address, null, lp.getInterfaceName());
+            // Duplicates in linkAddresses are considered failures
+            assertTrue(expectedRoutes.add(localRoute));
+        }
+        List<RouteInfo> observedRoutes = lp.getRoutes();
+        assertEquals(expectedRoutes.size(), observedRoutes.size());
+        assertTrue(observedRoutes.containsAll(expectedRoutes));
+    }
+
     private static <T> void assertEmpty(T[] ts) {
         int length = ts.length;
         assertEquals("expected empty array, but length was " + length, 0, length);