Merge "Add carrier config variable to hide ims apn" into mnc-dr-dev
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 8c84b4d..9ef13de 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -426,7 +426,7 @@
     @RequiresPermission(GET_ACCOUNTS)
     public Account[] getAccounts() {
         try {
-            return mService.getAccounts(null);
+            return mService.getAccounts(null, mContext.getOpPackageName());
         } catch (RemoteException e) {
             // won't ever happen
             throw new RuntimeException(e);
@@ -451,7 +451,7 @@
     @RequiresPermission(GET_ACCOUNTS)
     public Account[] getAccountsAsUser(int userId) {
         try {
-            return mService.getAccountsAsUser(null, userId);
+            return mService.getAccountsAsUser(null, userId, mContext.getOpPackageName());
         } catch (RemoteException e) {
             // won't ever happen
             throw new RuntimeException(e);
@@ -468,7 +468,7 @@
      */
     public Account[] getAccountsForPackage(String packageName, int uid) {
         try {
-            return mService.getAccountsForPackage(packageName, uid);
+            return mService.getAccountsForPackage(packageName, uid, mContext.getOpPackageName());
         } catch (RemoteException re) {
             // won't ever happen
             throw new RuntimeException(re);
@@ -485,7 +485,8 @@
      */
     public Account[] getAccountsByTypeForPackage(String type, String packageName) {
         try {
-            return mService.getAccountsByTypeForPackage(type, packageName);
+            return mService.getAccountsByTypeForPackage(type, packageName,
+                    mContext.getOpPackageName());
         } catch (RemoteException re) {
             // won't ever happen
             throw new RuntimeException(re);
@@ -522,7 +523,8 @@
     /** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */
     public Account[] getAccountsByTypeAsUser(String type, UserHandle userHandle) {
         try {
-            return mService.getAccountsAsUser(type, userHandle.getIdentifier());
+            return mService.getAccountsAsUser(type, userHandle.getIdentifier(),
+                    mContext.getOpPackageName());
         } catch (RemoteException e) {
             // won't ever happen
             throw new RuntimeException(e);
@@ -610,7 +612,7 @@
         if (features == null) throw new IllegalArgumentException("features is null");
         return new Future2Task<Boolean>(handler, callback) {
             public void doWork() throws RemoteException {
-                mService.hasFeatures(mResponse, account, features);
+                mService.hasFeatures(mResponse, account, features, mContext.getOpPackageName());
             }
             public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
                 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
@@ -662,7 +664,8 @@
         if (type == null) throw new IllegalArgumentException("type is null");
         return new Future2Task<Account[]>(handler, callback) {
             public void doWork() throws RemoteException {
-                mService.getAccountsByFeatures(mResponse, type, features);
+                mService.getAccountsByFeatures(mResponse, type, features,
+                        mContext.getOpPackageName());
             }
             public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
                 if (!bundle.containsKey(KEY_ACCOUNTS)) {
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 04b3c88..4378df4 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -30,12 +30,14 @@
     String getPassword(in Account account);
     String getUserData(in Account account, String key);
     AuthenticatorDescription[] getAuthenticatorTypes(int userId);
-    Account[] getAccounts(String accountType);
-    Account[] getAccountsForPackage(String packageName, int uid);
-    Account[] getAccountsByTypeForPackage(String type, String packageName);
-    Account[] getAccountsAsUser(String accountType, int userId);
-    void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features);
-    void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features);
+    Account[] getAccounts(String accountType, String opPackageName);
+    Account[] getAccountsForPackage(String packageName, int uid, String opPackageName);
+    Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName);
+    Account[] getAccountsAsUser(String accountType, int userId, String opPackageName);
+    void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features,
+        String opPackageName);
+    void getAccountsByFeatures(in IAccountManagerResponse response, String accountType,
+        in String[] features, String opPackageName);
     boolean addAccountExplicitly(in Account account, String password, in Bundle extras);
     void removeAccount(in IAccountManagerResponse response, in Account account,
         boolean expectActivityLaunch);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 42ac67c..09c0a6e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -235,8 +235,10 @@
     public static final int OP_WRITE_EXTERNAL_STORAGE = 60;
     /** @hide Turned on the screen. */
     public static final int OP_TURN_SCREEN_ON = 61;
+    /** @hide Get device accounts. */
+    public static final int OP_GET_ACCOUNTS = 62;
     /** @hide */
-    public static final int _NUM_OP = 62;
+    public static final int _NUM_OP = 63;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -331,6 +333,9 @@
     /** Required to write/modify/update system settingss. */
     public static final String OPSTR_WRITE_SETTINGS
             = "android:write_settings";
+    /** @hide Get device accounts. */
+    public static final String OPSTR_GET_ACCOUNTS
+            = "android:get_accounts";
 
     /**
      * This maps each operation to the operation that serves as the
@@ -403,6 +408,7 @@
             OP_READ_EXTERNAL_STORAGE,
             OP_WRITE_EXTERNAL_STORAGE,
             OP_TURN_SCREEN_ON,
+            OP_GET_ACCOUNTS,
     };
 
     /**
@@ -472,6 +478,7 @@
             OPSTR_READ_EXTERNAL_STORAGE,
             OPSTR_WRITE_EXTERNAL_STORAGE,
             null,
+            OPSTR_GET_ACCOUNTS
     };
 
     /**
@@ -541,6 +548,7 @@
             "READ_EXTERNAL_STORAGE",
             "WRITE_EXTERNAL_STORAGE",
             "TURN_ON_SCREEN",
+            "GET_ACCOUNTS",
     };
 
     /**
@@ -610,6 +618,7 @@
             Manifest.permission.READ_EXTERNAL_STORAGE,
             Manifest.permission.WRITE_EXTERNAL_STORAGE,
             null, // no permission for turning the screen on
+            Manifest.permission.GET_ACCOUNTS
     };
 
     /**
@@ -680,6 +689,7 @@
             null, // READ_EXTERNAL_STORAGE
             null, // WRITE_EXTERNAL_STORAGE
             null, // TURN_ON_SCREEN
+            null, // GET_ACCOUNTS
     };
 
     /**
@@ -749,6 +759,7 @@
             false, // READ_EXTERNAL_STORAGE
             false, // WRITE_EXTERNAL_STORAGE
             false, // TURN_ON_SCREEN
+            false, // GET_ACCOUNTS
     };
 
     /**
@@ -817,6 +828,7 @@
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,  // OP_TURN_ON_SCREEN
+            AppOpsManager.MODE_ALLOWED,
     };
 
     /**
@@ -889,6 +901,7 @@
             false,
             false,
             false,
+            false
     };
 
     /**
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 4055836..9a2a241 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1243,6 +1243,8 @@
 
         /** The hardware does not support this request. */
         public static final int ERROR_HARDWARE_UNSUPPORTED = -30;
+        /** The hardware returned an error. */
+        public static final int ERROR_HARDWARE_ERROR = -31;
 
         public static final int NATT_PORT = 4500;
 
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
index 5f46c73..cab88b9 100644
--- a/core/java/android/net/NetworkFactory.java
+++ b/core/java/android/net/NetworkFactory.java
@@ -169,7 +169,8 @@
         }
     }
 
-    private void handleAddRequest(NetworkRequest request, int score) {
+    @VisibleForTesting
+    protected void handleAddRequest(NetworkRequest request, int score) {
         NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
         if (n == null) {
             if (DBG) log("got request " + request + " with score " + score);
@@ -184,7 +185,8 @@
         evalRequest(n);
     }
 
-    private void handleRemoveRequest(NetworkRequest request) {
+    @VisibleForTesting
+    protected void handleRemoveRequest(NetworkRequest request) {
         NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
         if (n != null) {
             mNetworkRequests.remove(request.requestId);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c8bb675..b8b6444 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1056,6 +1056,11 @@
     <permission android:name="android.permission.CONNECTIVITY_INTERNAL"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows a system application to access hardware packet offload capabilities.
+         @hide -->
+    <permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi
          @hide -->
     <permission android:name="android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index f4439bf..012dc9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -390,7 +390,8 @@
                 serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM);
                 try {
                     if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection,
-                            Context.BIND_AUTO_CREATE, new UserHandle(UserHandle.USER_CURRENT))) {
+                            Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                            new UserHandle(UserHandle.USER_CURRENT))) {
                         mPrewarmBound = true;
                     }
                 } catch (SecurityException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index b93586e..9321938 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -111,12 +111,6 @@
     private boolean mQsTracking;
 
     /**
-     * Handles launching the secure camera properly even when other applications may be using the
-     * camera hardware.
-     */
-    private SecureCameraLaunchManager mSecureCameraLaunchManager;
-
-    /**
      * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
      * the expansion for quick settings.
      */
@@ -262,8 +256,6 @@
         mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
         mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
         mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
-        mSecureCameraLaunchManager =
-                new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea);
         mLastOrientation = getResources().getConfiguration().orientation;
 
         // recompute internal state when qspanel height changes
@@ -368,16 +360,6 @@
         updateMaxHeadsUpTranslation();
     }
 
-    @Override
-    public void onAttachedToWindow() {
-        mSecureCameraLaunchManager.create();
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        mSecureCameraLaunchManager.destroy();
-    }
-
     private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
         if (mQsSizeChangeAnimator != null) {
             oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
@@ -1963,7 +1945,7 @@
         } else {
             EventLogTags.writeSysuiLockscreenGesture(
                     EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp);
-            mSecureCameraLaunchManager.startSecureCameraLaunch();
+            mKeyguardBottomArea.launchCamera();
         }
         mStatusBar.startLaunchTransitionTimeout();
         mBlockTouches = true;
@@ -2010,7 +1992,6 @@
         boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon
                 : rightIcon;
         if (camera) {
-            mSecureCameraLaunchManager.onSwipingStarted();
             mKeyguardBottomArea.bindCameraPrewarmService();
         }
         requestDisallowInterceptTouchEvent(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 55b1127..638cd1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -3535,6 +3535,7 @@
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         releaseGestureWakeLock();
         mNotificationPanel.onAffordanceLaunchEnded();
+        mNotificationPanel.animate().cancel();
         mNotificationPanel.setAlpha(1f);
         return staying;
     }
@@ -3928,7 +3929,7 @@
     }
 
     private void vibrateForCameraGesture() {
-        mVibrator.vibrate(1000L);
+        mVibrator.vibrate(750L);
     }
 
     public void onScreenTurnedOn() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java
deleted file mode 100644
index 45c8938..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.hardware.camera2.CameraManager;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.provider.MediaStore;
-import android.util.Log;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Handles launching the secure camera properly even when other applications may be using the camera
- * hardware.
- *
- * When other applications (e.g., Face Unlock) are using the camera, they must close the camera to
- * allow the secure camera to open it.  Since we want to minimize the delay when opening the secure
- * camera, other apps should close the camera at the first possible opportunity (i.e., as soon as
- * the user begins swiping to go to the secure camera).
- *
- * If the camera is unavailable when the user begins to swipe, the SecureCameraLaunchManager sends a
- * broadcast to tell other apps to close the camera.  When and if the user completes their swipe to
- * launch the secure camera, the SecureCameraLaunchManager delays launching the secure camera until
- * a callback indicates that the camera has become available.  If it doesn't receive that callback
- * within a specified timeout period, the secure camera is launched anyway.
- *
- * Ideally, the secure camera would handle waiting for the camera to become available.  This allows
- * some of the time necessary to close the camera to happen in parallel with starting the secure
- * camera app.  We can't rely on all third-party camera apps to handle this.  However, an app can
- * put com.android.systemui.statusbar.phone.will_wait_for_camera_available in its meta-data to
- * indicate that it will be responsible for waiting for the camera to become available.
- *
- * It is assumed that the functions in this class, including the constructor, will be called from
- * the UI thread.
- */
-public class SecureCameraLaunchManager {
-    private static final boolean DEBUG = false;
-    private static final String TAG = "SecureCameraLaunchManager";
-
-    // Action sent as a broadcast to tell other apps to stop using the camera.  Other apps that use
-    // the camera from keyguard (e.g., Face Unlock) should listen for this broadcast and close the
-    // camera as soon as possible after receiving it.
-    private static final String CLOSE_CAMERA_ACTION_NAME =
-            "com.android.systemui.statusbar.phone.CLOSE_CAMERA";
-
-    // Apps should put this field in their meta-data to indicate that they will take on the
-    // responsibility of waiting for the camera to become available.  If this field is present, the
-    // SecureCameraLaunchManager launches the secure camera even if the camera hardware has not
-    // become available.  Having the secure camera app do the waiting is the optimal approach, but
-    // without this field, the SecureCameraLaunchManager doesn't launch the secure camera until the
-    // camera hardware is available.
-    private static final String META_DATA_WILL_WAIT_FOR_CAMERA_AVAILABLE =
-            "com.android.systemui.statusbar.phone.will_wait_for_camera_available";
-
-    // If the camera hardware hasn't become available after this period of time, the
-    // SecureCameraLaunchManager launches the secure camera anyway.
-    private static final int CAMERA_AVAILABILITY_TIMEOUT_MS = 1000;
-
-    private Context mContext;
-    private Handler mHandler;
-    private LockPatternUtils mLockPatternUtils;
-    private KeyguardBottomAreaView mKeyguardBottomArea;
-
-    private CameraManager mCameraManager;
-    private CameraAvailabilityCallback mCameraAvailabilityCallback;
-    private Map<String, Boolean> mCameraAvailabilityMap;
-    private boolean mWaitingToLaunchSecureCamera;
-    private Runnable mLaunchCameraRunnable;
-
-    private class CameraAvailabilityCallback extends CameraManager.AvailabilityCallback {
-        @Override
-        public void onCameraUnavailable(String cameraId) {
-            if (DEBUG) Log.d(TAG, "onCameraUnavailble(" + cameraId + ")");
-            mCameraAvailabilityMap.put(cameraId, false);
-        }
-
-        @Override
-        public void onCameraAvailable(String cameraId) {
-            if (DEBUG) Log.d(TAG, "onCameraAvailable(" + cameraId + ")");
-            mCameraAvailabilityMap.put(cameraId, true);
-
-            // If we were waiting for the camera hardware to become available to launch the
-            // secure camera, we can launch it now if all cameras are available.  If one or more
-            // cameras are still not available, we will get this callback again for those
-            // cameras.
-            if (mWaitingToLaunchSecureCamera && areAllCamerasAvailable()) {
-                mKeyguardBottomArea.launchCamera();
-                mWaitingToLaunchSecureCamera = false;
-
-                // We no longer need to launch the camera after the timeout hits.
-                mHandler.removeCallbacks(mLaunchCameraRunnable);
-            }
-        }
-    }
-
-    public SecureCameraLaunchManager(Context context, KeyguardBottomAreaView keyguardBottomArea) {
-        mContext = context;
-        mHandler = new Handler();
-        mLockPatternUtils = new LockPatternUtils(context);
-        mKeyguardBottomArea = keyguardBottomArea;
-
-        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
-        mCameraAvailabilityCallback = new CameraAvailabilityCallback();
-
-        // An onCameraAvailable() or onCameraUnavailable() callback will be received for each camera
-        // when the availability callback is registered, thus initializing the map.
-        //
-        // Keeping track of the state of all cameras using the onCameraAvailable() and
-        // onCameraUnavailable() callbacks can get messy when dealing with hot-pluggable cameras.
-        // However, we have a timeout in place such that we will never hang waiting for cameras.
-        mCameraAvailabilityMap = new HashMap<String, Boolean>();
-
-        mWaitingToLaunchSecureCamera = false;
-        mLaunchCameraRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    if (mWaitingToLaunchSecureCamera) {
-                        Log.w(TAG, "Timeout waiting for camera availability");
-                        mKeyguardBottomArea.launchCamera();
-                        mWaitingToLaunchSecureCamera = false;
-                    }
-                }
-            };
-    }
-
-    /**
-     * Initializes the SecureCameraManager and starts listening for camera availability.
-     */
-    public void create() {
-        mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, mHandler);
-    }
-
-    /**
-     * Stops listening for camera availability and cleans up the SecureCameraManager.
-     */
-    public void destroy() {
-        mCameraManager.unregisterAvailabilityCallback(mCameraAvailabilityCallback);
-    }
-
-    /**
-     * Called when the user is starting to swipe horizontally, possibly to start the secure camera.
-     * Although this swipe ultimately may not result in the secure camera opening, we need to stop
-     * all other camera usage (e.g., Face Unlock) as soon as possible.  We send out a broadcast to
-     * notify other apps that they should close the camera immediately.  The broadcast is sent even
-     * if the camera appears to be available, because there could be an app that is about to open
-     * the camera.
-     */
-    public void onSwipingStarted() {
-        if (DEBUG) Log.d(TAG, "onSwipingStarted");
-        AsyncTask.execute(new Runnable() {
-                @Override
-                public void run() {
-                    Intent intent = new Intent();
-                    intent.setAction(CLOSE_CAMERA_ACTION_NAME);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                    mContext.sendBroadcast(intent);
-                }
-            });
-    }
-
-    /**
-     * Called when the secure camera should be started.  If the camera is available or the secure
-     * camera app has indicated that it will wait for camera availability, the secure camera app is
-     * launched immediately.  Otherwise, we wait for the camera to become available (or timeout)
-     * before launching the secure camera.
-     */
-    public void startSecureCameraLaunch() {
-        if (DEBUG) Log.d(TAG, "startSecureCameraLunch");
-        if (areAllCamerasAvailable() || targetWillWaitForCameraAvailable()) {
-            mKeyguardBottomArea.launchCamera();
-        } else {
-            mWaitingToLaunchSecureCamera = true;
-            mHandler.postDelayed(mLaunchCameraRunnable, CAMERA_AVAILABILITY_TIMEOUT_MS);
-        }
-    }
-
-    /**
-     * Returns true if all of the cameras we are tracking are currently available.
-     */
-    private boolean areAllCamerasAvailable() {
-        for (boolean cameraAvailable: mCameraAvailabilityMap.values()) {
-            if (!cameraAvailable) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Determines if the secure camera app will wait for the camera hardware to become available
-     * before trying to open the camera.  If so, we can fire off an intent to start the secure
-     * camera app before the camera is available.  Otherwise, it is our responsibility to wait for
-     * the camera hardware to become available before firing off the intent to start the secure
-     * camera.
-     *
-     * Ideally we are able to fire off the secure camera intent as early as possibly so that, if the
-     * camera is closing, it can continue to close while the secure camera app is opening.  This
-     * improves secure camera startup time.
-     */
-    private boolean targetWillWaitForCameraAvailable() {
-        // Create intent that would launch the secure camera.
-        Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
-                .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-        PackageManager packageManager = mContext.getPackageManager();
-
-        // Get the list of applications that can handle the intent.
-        final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
-                intent, PackageManager.MATCH_DEFAULT_ONLY, KeyguardUpdateMonitor.getCurrentUser());
-        if (appList.size() == 0) {
-            if (DEBUG) Log.d(TAG, "No targets found for secure camera intent");
-            return false;
-        }
-
-        // Get the application that the intent resolves to.
-        ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
-                PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
-                KeyguardUpdateMonitor.getCurrentUser());
-
-        if (resolved == null || resolved.activityInfo == null) {
-            return false;
-        }
-
-        // If we would need to launch the resolver activity, then we can't assume that the target
-        // is one that would wait for the camera.
-        if (wouldLaunchResolverActivity(resolved, appList)) {
-            if (DEBUG) Log.d(TAG, "Secure camera intent would launch resolver");
-            return false;
-        }
-
-        // If the target doesn't have meta-data we must assume it won't wait for the camera.
-        if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) {
-            if (DEBUG) Log.d(TAG, "No meta-data found for secure camera application");
-            return false;
-        }
-
-        // Check the secure camera app meta-data to see if it indicates that it will wait for the
-        // camera to become available.
-        boolean willWaitForCameraAvailability =
-                resolved.activityInfo.metaData.getBoolean(META_DATA_WILL_WAIT_FOR_CAMERA_AVAILABLE);
-
-        if (DEBUG) Log.d(TAG, "Target will wait for camera: " + willWaitForCameraAvailability);
-
-        return willWaitForCameraAvailability;
-    }
-
-    /**
-     * Determines if the activity that would be launched by the intent is the ResolverActivity.
-     */
-    private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) {
-        // If the list contains the resolved activity, then it can't be the ResolverActivity itself.
-        for (int i = 0; i < appList.size(); i++) {
-            ResolveInfo tmp = appList.get(i);
-            if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
-                    && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
-                return false;
-            }
-        }
-        return true;
-    }
-}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1f4427f..d1e1683 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -359,6 +359,9 @@
      */
     private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
 
+    /** Handler thread used for both of the handlers below. */
+    @VisibleForTesting
+    protected final HandlerThread mHandlerThread;
     /** Handler used for internal events. */
     final private InternalHandler mHandler;
     /** Handler used for incoming {@link NetworkStateTracker} events. */
@@ -614,6 +617,11 @@
     }
     private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
 
+    @VisibleForTesting
+    protected HandlerThread createHandlerThread() {
+        return new HandlerThread("ConnectivityServiceThread");
+    }
+
     public ConnectivityService(Context context, INetworkManagementService netManager,
             INetworkStatsService statsService, INetworkPolicyManager policyManager) {
         if (DBG) log("ConnectivityService starting up");
@@ -627,10 +635,10 @@
         mDefaultMobileDataRequest = createInternetRequestForTransport(
                 NetworkCapabilities.TRANSPORT_CELLULAR);
 
-        HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
-        handlerThread.start();
-        mHandler = new InternalHandler(handlerThread.getLooper());
-        mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper());
+        mHandlerThread = createHandlerThread();
+        mHandlerThread.start();
+        mHandler = new InternalHandler(mHandlerThread.getLooper());
+        mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
 
         // setup our unique device name
         if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) {
@@ -1458,7 +1466,7 @@
     }
 
     private void enforceKeepalivePermission() {
-        mContext.enforceCallingPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
+        mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
     }
 
     public void sendConnectedBroadcast(NetworkInfo info) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 8b0e6f2..d590d7a 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -32,6 +32,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -122,6 +123,7 @@
     private final Context mContext;
 
     private final PackageManager mPackageManager;
+    private final AppOpsManager mAppOpsManager;
     private UserManager mUserManager;
 
     private final MessageHandler mMessageHandler;
@@ -266,6 +268,7 @@
             IAccountAuthenticatorCache authenticatorCache) {
         mContext = context;
         mPackageManager = packageManager;
+        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
 
         mMessageHandler = new MessageHandler(FgThread.get().getLooper());
 
@@ -510,7 +513,7 @@
         // Check if there's a shared account that needs to be created as an account
         Account[] sharedAccounts = getSharedAccountsAsUser(userId);
         if (sharedAccounts == null || sharedAccounts.length == 0) return;
-        Account[] accounts = getAccountsAsUser(null, userId);
+        Account[] accounts = getAccountsAsUser(null, userId, mContext.getOpPackageName());
         for (Account sa : sharedAccounts) {
             if (ArrayUtils.contains(accounts, sa)) continue;
             // Account doesn't exist. Copy it now.
@@ -868,7 +871,8 @@
                     // Confirm that the owner's account still exists before this step.
                     UserAccounts owner = getUserAccounts(UserHandle.USER_OWNER);
                     synchronized (owner.cacheLock) {
-                        for (Account acc : getAccounts(UserHandle.USER_OWNER)) {
+                        for (Account acc : getAccounts(UserHandle.USER_OWNER,
+                                mContext.getOpPackageName())) {
                             if (acc.equals(account)) {
                                 mAuthenticator.addAccountFromCredentials(
                                         this, account, accountCredentials);
@@ -988,7 +992,7 @@
 
     @Override
     public void hasFeatures(IAccountManagerResponse response,
-            Account account, String[] features) {
+            Account account, String[] features, String opPackageName) {
         int callingUid = Binder.getCallingUid();
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "hasFeatures: " + account
@@ -1001,7 +1005,8 @@
         if (account == null) throw new IllegalArgumentException("account is null");
         if (features == null) throw new IllegalArgumentException("features is null");
         int userId = UserHandle.getCallingUserId();
-        checkReadAccountsPermitted(callingUid, account.type, userId);
+        checkReadAccountsPermitted(callingUid, account.type, userId,
+                opPackageName);
 
         long identityToken = clearCallingIdentity();
         try {
@@ -2507,9 +2512,10 @@
      * Returns the accounts visible to the client within the context of a specific user
      * @hide
      */
-    public Account[] getAccounts(int userId) {
+    public Account[] getAccounts(int userId, String opPackageName) {
         int callingUid = Binder.getCallingUid();
-        List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId);
+        List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId,
+                opPackageName);
         if (visibleAccountTypes.isEmpty()) {
             return new Account[0];
         }
@@ -2571,15 +2577,16 @@
     }
 
     @Override
-    public Account[] getAccountsAsUser(String type, int userId) {
-        return getAccountsAsUser(type, userId, null, -1);
+    public Account[] getAccountsAsUser(String type, int userId, String opPackageName) {
+        return getAccountsAsUser(type, userId, null, -1, opPackageName);
     }
 
     private Account[] getAccountsAsUser(
             String type,
             int userId,
             String callingPackage,
-            int packageUid) {
+            int packageUid,
+            String opPackageName) {
         int callingUid = Binder.getCallingUid();
         // Only allow the system process to read accounts of other users
         if (userId != UserHandle.getCallingUserId()
@@ -2602,7 +2609,8 @@
             callingUid = packageUid;
         }
 
-        List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId);
+        List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId,
+                opPackageName);
         if (visibleAccountTypes.isEmpty()
                 || (type != null && !visibleAccountTypes.contains(type))) {
             return new Account[0];
@@ -2741,22 +2749,24 @@
     }
 
     @Override
-    public Account[] getAccounts(String type) {
-        return getAccountsAsUser(type, UserHandle.getCallingUserId());
+    public Account[] getAccounts(String type, String opPackageName) {
+        return getAccountsAsUser(type, UserHandle.getCallingUserId(), opPackageName);
     }
 
     @Override
-    public Account[] getAccountsForPackage(String packageName, int uid) {
+    public Account[] getAccountsForPackage(String packageName, int uid, String opPackageName) {
         int callingUid = Binder.getCallingUid();
         if (!UserHandle.isSameApp(callingUid, Process.myUid())) {
             throw new SecurityException("getAccountsForPackage() called from unauthorized uid "
                     + callingUid + " with uid=" + uid);
         }
-        return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid);
+        return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid,
+                opPackageName);
     }
 
     @Override
-    public Account[] getAccountsByTypeForPackage(String type, String packageName) {
+    public Account[] getAccountsByTypeForPackage(String type, String packageName,
+            String opPackageName) {
         int packageUid = -1;
         try {
             packageUid = AppGlobals.getPackageManager().getPackageUid(
@@ -2765,14 +2775,16 @@
             Slog.e(TAG, "Couldn't determine the packageUid for " + packageName + re);
             return new Account[0];
         }
-        return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, packageUid);
+        return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName,
+                packageUid, opPackageName);
     }
 
     @Override
     public void getAccountsByFeatures(
             IAccountManagerResponse response,
             String type,
-            String[] features) {
+            String[] features,
+            String opPackageName) {
         int callingUid = Binder.getCallingUid();
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "getAccounts: accountType " + type
@@ -2785,7 +2797,8 @@
         if (type == null) throw new IllegalArgumentException("accountType is null");
         int userId = UserHandle.getCallingUserId();
 
-        List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId);
+        List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId,
+                opPackageName);
         if (!visibleAccountTypes.contains(type)) {
             Bundle result = new Bundle();
             // Need to return just the accounts that are from matching signatures.
@@ -3685,31 +3698,22 @@
         }
     }
 
-    private boolean isPermitted(int callingUid, String... permissions) {
+    private boolean isPermitted(String opPackageName, int callingUid, String... permissions) {
         for (String perm : permissions) {
             if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
                     Log.v(TAG, "  caller uid " + callingUid + " has " + perm);
                 }
-                return true;
+                final int opCode = AppOpsManager.permissionToOpCode(perm);
+                if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp(
+                        opCode, callingUid, opPackageName) == AppOpsManager.MODE_ALLOWED) {
+                    return true;
+                }
             }
         }
         return false;
     }
 
-    /** Succeeds if any of the specified permissions are granted. */
-    private void checkBinderPermission(String... permissions) {
-        final int callingUid = Binder.getCallingUid();
-        if (isPermitted(callingUid, permissions)) {
-            String msg = String.format(
-                    "caller uid %s  lacks any of %s",
-                    callingUid,
-                    TextUtils.join(",", permissions));
-            Log.w(TAG, "  " + msg);
-            throw new SecurityException(msg);
-        }
-    }
-
     private int handleIncomingUser(int userId) {
         try {
             return ActivityManagerNative.getDefault().handleIncomingUser(
@@ -3763,11 +3767,13 @@
         return fromAuthenticator || hasExplicitGrants || isPrivileged;
     }
 
-    private boolean isAccountVisibleToCaller(String accountType, int callingUid, int userId) {
+    private boolean isAccountVisibleToCaller(String accountType, int callingUid, int userId,
+            String opPackageName) {
         if (accountType == null) {
             return false;
         } else {
-            return getTypesVisibleToCaller(callingUid, userId).contains(accountType);
+            return getTypesVisibleToCaller(callingUid, userId,
+                    opPackageName).contains(accountType);
         }
     }
 
@@ -3779,9 +3785,10 @@
         }
     }
 
-    private List<String> getTypesVisibleToCaller(int callingUid, int userId) {
+    private List<String> getTypesVisibleToCaller(int callingUid, int userId,
+            String opPackageName) {
         boolean isPermitted =
-                isPermitted(callingUid, Manifest.permission.GET_ACCOUNTS,
+                isPermitted(opPackageName, callingUid, Manifest.permission.GET_ACCOUNTS,
                         Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
         Log.i(TAG, String.format("getTypesVisibleToCaller: isPermitted? %s", isPermitted));
         return getTypesForCaller(callingUid, userId, isPermitted);
@@ -3877,8 +3884,9 @@
     private void checkReadAccountsPermitted(
             int callingUid,
             String accountType,
-            int userId) {
-        if (!isAccountVisibleToCaller(accountType, callingUid, userId)) {
+            int userId,
+            String opPackageName) {
+        if (!isAccountVisibleToCaller(accountType, callingUid, userId, opPackageName)) {
             String msg = String.format(
                     "caller uid %s cannot access %s accounts",
                     callingUid,
diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
index 64b9399..2ccfdd1 100644
--- a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
+++ b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
@@ -71,6 +71,7 @@
         // Check we have two IP addresses of the same family.
         if (srcAddress == null || dstAddress == null ||
                 !srcAddress.getClass().getName().equals(dstAddress.getClass().getName())) {
+            throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
         }
 
         // Set the protocol.
@@ -102,7 +103,7 @@
             InetAddress srcAddress, int srcPort,
             InetAddress dstAddress, int dstPort) throws InvalidPacketException {
 
-        if (!(srcAddress instanceof Inet4Address)) {
+        if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
             throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
         }
 
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index c78f347..90c9ddf 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -62,8 +62,7 @@
     private static final String TAG = "KeepaliveTracker";
     private static final boolean DBG = true;
 
-    // TODO: Change this to a system-only permission.
-    public static final String PERMISSION = android.Manifest.permission.CHANGE_NETWORK_STATE;
+    public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
 
     /** Keeps track of keepalive requests. */
     private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
@@ -208,6 +207,8 @@
                 Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name());
                 mNai.asyncChannel.sendMessage(CMD_STOP_PACKET_KEEPALIVE, mSlot);
             }
+            // TODO: at the moment we unconditionally return failure here. In cases where the
+            // NetworkAgent is alive, should we ask it to reply, so it can return failure?
             notifyMessenger(mSlot, reason);
             unlinkDeathRecipient();
         }
@@ -233,17 +234,14 @@
             mKeepalives.put(nai, networkKeepalives);
         }
 
-        // Find the lowest-numbered free slot.
+        // Find the lowest-numbered free slot. Slot numbers start from 1, because that's what two
+        // separate chipset implementations independently came up with.
         int slot;
-        for (slot = 0; slot < networkKeepalives.size(); slot++) {
+        for (slot = 1; slot <= networkKeepalives.size(); slot++) {
             if (networkKeepalives.get(slot) == null) {
                 return slot;
             }
         }
-        // No free slot, pick one at the end.
-
-        // HACK for broadcom hardware that does not support slot 0!
-        if (slot == 0) slot = 1;
         return slot;
     }
 
@@ -267,14 +265,15 @@
     }
 
     public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) {
+        String networkName = (nai == null) ? "(null)" : nai.name();
         HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
         if (networkKeepalives == null) {
-            Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + nai.name());
+            Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + networkName);
             return;
         }
         KeepaliveInfo ki = networkKeepalives.get(slot);
         if (ki == null) {
-            Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + nai.name());
+            Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + networkName);
             return;
         }
         ki.stop(reason);
@@ -332,6 +331,11 @@
 
     public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger,
             IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) {
+        if (nai == null) {
+            notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK);
+            return;
+        }
+
         InetAddress srcAddress, dstAddress;
         try {
             srcAddress = NetworkUtils.numericToInetAddress(srcAddrString);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index c998c2c..334bc18 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -340,7 +340,8 @@
         for (UserInfo user : mUserManager.getUsers(true)) {
             // Skip any partially created/removed users
             if (user.partial) continue;
-            Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id);
+            Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(
+                    user.id, mContext.getOpPackageName());
             mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
         }
     }
@@ -1232,7 +1233,8 @@
         }
 
         // Schedule sync for any accounts under started user
-        final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId);
+        final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId,
+                mContext.getOpPackageName());
         for (Account account : accounts) {
             scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null,
                     0 /* no delay */, 0 /* No flex */,
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 919293a..8bb20a6 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -39,6 +39,7 @@
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index b4c76b7..97e16da 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -20,35 +20,9 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivityManager.getNetworkTypeName;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_IA;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
+import static android.net.NetworkCapabilities.*;
+
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
 
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -58,8 +32,12 @@
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivityManager.PacketKeepalive;
+import android.net.ConnectivityManager.PacketKeepaliveCallback;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkAgent;
@@ -74,8 +52,11 @@
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.MessageQueue.IdleHandler;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
@@ -84,10 +65,10 @@
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkMonitor;
 
-import org.mockito.ArgumentCaptor;
-
 import java.net.InetAddress;
-import java.util.concurrent.Future;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -99,24 +80,7 @@
 public class ConnectivityServiceTest extends AndroidTestCase {
     private static final String TAG = "ConnectivityServiceTest";
 
-    private static final String MOBILE_IFACE = "rmnet3";
-    private static final String WIFI_IFACE = "wlan6";
-
-    private static final RouteInfo MOBILE_ROUTE_V4 = RouteInfo.makeHostRoute(parse("10.0.0.33"),
-                                                                             MOBILE_IFACE);
-    private static final RouteInfo MOBILE_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::33"),
-                                                                             MOBILE_IFACE);
-
-    private static final RouteInfo WIFI_ROUTE_V4 = RouteInfo.makeHostRoute(parse("192.168.0.66"),
-                                                                           parse("192.168.0.1"),
-                                                                           WIFI_IFACE);
-    private static final RouteInfo WIFI_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::66"),
-                                                                           parse("fd00::"),
-                                                                           WIFI_IFACE);
-
-    private INetworkManagementService mNetManager;
-    private INetworkStatsService mStatsService;
-    private INetworkPolicyManager mPolicyService;
+    private static final int TIMEOUT_MS = 500;
 
     private BroadcastInterceptingContext mServiceContext;
     private WrappedConnectivityService mService;
@@ -148,14 +112,93 @@
         }
     }
 
+    /**
+     * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
+     * will return immediately if the handler is already idle.
+     */
+    private class IdleableHandlerThread extends HandlerThread {
+        private IdleHandler mIdleHandler;
+
+        public IdleableHandlerThread(String name) {
+            super(name);
+        }
+
+        public void waitForIdle(int timeoutMs) {
+            final ConditionVariable cv = new ConditionVariable();
+            final MessageQueue queue = getLooper().getQueue();
+
+            synchronized (queue) {
+                if (queue.isIdle()) {
+                    return;
+                }
+
+                assertNull("BUG: only one idle handler allowed", mIdleHandler);
+                mIdleHandler = new IdleHandler() {
+                    public boolean queueIdle() {
+                        cv.open();
+                        mIdleHandler = null;
+                        return false;  // Remove the handler.
+                    }
+                };
+                queue.addIdleHandler(mIdleHandler);
+            }
+
+            if (!cv.block(timeoutMs)) {
+                fail("HandlerThread " + getName() +
+                        " did not become idle after " + timeoutMs + " ms");
+                queue.removeIdleHandler(mIdleHandler);
+            }
+        }
+    }
+
+    // Tests that IdleableHandlerThread works as expected.
+    public void testIdleableHandlerThread() {
+        final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
+
+        // Tests that waitForIdle returns immediately if the service is already idle.
+        for (int i = 0; i < attempts; i++) {
+            mService.waitForIdle();
+        }
+
+        // Bring up a network that we can use to send messages to ConnectivityService.
+        ConditionVariable cv = waitForConnectivityBroadcasts(1);
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        waitFor(cv);
+        Network n = mWiFiNetworkAgent.getNetwork();
+        assertNotNull(n);
+
+        // Tests that calling waitForIdle waits for messages to be processed.
+        for (int i = 0; i < attempts; i++) {
+            mWiFiNetworkAgent.setSignalStrength(i);
+            mService.waitForIdle();
+            assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength());
+        }
+
+        // Ensure that not calling waitForIdle causes a race condition.
+        for (int i = 0; i < attempts; i++) {
+            mWiFiNetworkAgent.setSignalStrength(i);
+            if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) {
+                // We hit a race condition, as expected. Pass the test.
+                return;
+            }
+        }
+
+        // No race? There is a bug in this test.
+        fail("expected race condition at least once in " + attempts + " attempts");
+    }
+
     private class MockNetworkAgent {
         private final WrappedNetworkMonitor mWrappedNetworkMonitor;
         private final NetworkInfo mNetworkInfo;
         private final NetworkCapabilities mNetworkCapabilities;
-        private final Thread mThread;
+        private final IdleableHandlerThread mHandlerThread;
         private final ConditionVariable mDisconnected = new ConditionVariable();
         private int mScore;
         private NetworkAgent mNetworkAgent;
+        private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
+        private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE;
+        private Integer mExpectedKeepaliveSlot = null;
 
         MockNetworkAgent(int transport) {
             final int type = transportToLegacyType(transport);
@@ -173,26 +216,42 @@
                 default:
                     throw new UnsupportedOperationException("unimplemented network type");
             }
-            final ConditionVariable initComplete = new ConditionVariable();
-            final ConditionVariable networkMonitorAvailable = mService.getNetworkMonitorCreatedCV();
-            mThread = new Thread() {
-                public void run() {
-                    Looper.prepare();
-                    mNetworkAgent = new NetworkAgent(Looper.myLooper(), mServiceContext,
-                            "Mock" + typeName, mNetworkInfo, mNetworkCapabilities,
-                            new LinkProperties(), mScore, new NetworkMisc()) {
-                        public void unwanted() { mDisconnected.open(); }
-                    };
-                    initComplete.open();
-                    Looper.loop();
+            mHandlerThread = new IdleableHandlerThread("Mock-" + typeName);
+            mHandlerThread.start();
+            mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
+                    "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
+                    new LinkProperties(), mScore, new NetworkMisc()) {
+                @Override
+                public void unwanted() { mDisconnected.open(); }
+
+                @Override
+                public void startPacketKeepalive(Message msg) {
+                    int slot = msg.arg1;
+                    if (mExpectedKeepaliveSlot != null) {
+                        assertEquals((int) mExpectedKeepaliveSlot, slot);
+                    }
+                    onPacketKeepaliveEvent(slot, mStartKeepaliveError);
+                }
+
+                @Override
+                public void stopPacketKeepalive(Message msg) {
+                    onPacketKeepaliveEvent(msg.arg1, mStopKeepaliveError);
                 }
             };
-            mThread.start();
-            waitFor(initComplete);
-            waitFor(networkMonitorAvailable);
+            // Waits for the NetworkAgent to be registered, which includes the creation of the
+            // NetworkMonitor.
+            mService.waitForIdle();
             mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
         }
 
+        public void waitForIdle(int timeoutMs) {
+            mHandlerThread.waitForIdle(timeoutMs);
+        }
+
+        public void waitForIdle() {
+            waitForIdle(TIMEOUT_MS);
+        }
+
         public void adjustScore(int change) {
             mScore += change;
             mNetworkAgent.sendNetworkScore(mScore);
@@ -203,6 +262,11 @@
             mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
         }
 
+        public void setSignalStrength(int signalStrength) {
+            mNetworkCapabilities.setSignalStrength(signalStrength);
+            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+        }
+
         public void connectWithoutInternet() {
             mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
             mNetworkAgent.sendNetworkInfo(mNetworkInfo);
@@ -272,15 +336,46 @@
         public WrappedNetworkMonitor getWrappedNetworkMonitor() {
             return mWrappedNetworkMonitor;
         }
+
+        public void sendLinkProperties(LinkProperties lp) {
+            mNetworkAgent.sendLinkProperties(lp);
+        }
+
+        public void setStartKeepaliveError(int error) {
+            mStartKeepaliveError = error;
+        }
+
+        public void setStopKeepaliveError(int error) {
+            mStopKeepaliveError = error;
+        }
+
+        public void setExpectedKeepaliveSlot(Integer slot) {
+            mExpectedKeepaliveSlot = slot;
+        }
     }
 
+    /**
+     * A NetworkFactory that allows tests to wait until any in-flight NetworkRequest add or remove
+     * operations have been processed. Before ConnectivityService can add or remove any requests,
+     * the factory must be told to expect those operations by calling expectAddRequests or
+     * expectRemoveRequests.
+     */
     private static class MockNetworkFactory extends NetworkFactory {
         private final ConditionVariable mNetworkStartedCV = new ConditionVariable();
         private final ConditionVariable mNetworkStoppedCV = new ConditionVariable();
-        private final ConditionVariable mNetworkRequestedCV = new ConditionVariable();
-        private final ConditionVariable mNetworkReleasedCV = new ConditionVariable();
         private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
 
+        // Used to expect that requests be removed or added on a separate thread, without sleeping.
+        // Callers can call either expectAddRequests() or expectRemoveRequests() exactly once, then
+        // cause some other thread to add or remove requests, then call waitForRequests(). We can
+        // either expect requests to be added or removed, but not both, because CountDownLatch can
+        // only count in one direction.
+        private CountDownLatch mExpectations;
+
+        // Whether we are currently expecting requests to be added or removed. Valid only if
+        // mExpectations is non-null.
+        private boolean mExpectingAdditions;
+
         public MockNetworkFactory(Looper looper, Context context, String logTag,
                 NetworkCapabilities filter) {
             super(looper, context, logTag, filter);
@@ -314,28 +409,75 @@
             return mNetworkStoppedCV;
         }
 
-        protected void needNetworkFor(NetworkRequest networkRequest, int score) {
-            super.needNetworkFor(networkRequest, score);
-            mNetworkRequestedCV.open();
+        @Override
+        protected void handleAddRequest(NetworkRequest request, int score) {
+            // If we're expecting anything, we must be expecting additions.
+            if (mExpectations != null && !mExpectingAdditions) {
+                fail("Can't add requests while expecting requests to be removed");
+            }
+
+            // Add the request.
+            super.handleAddRequest(request, score);
+
+            // Reduce the number of request additions we're waiting for.
+            if (mExpectingAdditions) {
+                assertTrue("Added more requests than expected", mExpectations.getCount() > 0);
+                mExpectations.countDown();
+            }
         }
 
-        protected void releaseNetworkFor(NetworkRequest networkRequest) {
-            super.releaseNetworkFor(networkRequest);
-            mNetworkReleasedCV.open();
+        @Override
+        protected void handleRemoveRequest(NetworkRequest request) {
+            // If we're expecting anything, we must be expecting removals.
+            if (mExpectations != null && mExpectingAdditions) {
+                fail("Can't remove requests while expecting requests to be added");
+            }
+
+            // Remove the request.
+            super.handleRemoveRequest(request);
+
+            // Reduce the number of request removals we're waiting for.
+            if (!mExpectingAdditions) {
+                assertTrue("Removed more requests than expected", mExpectations.getCount() > 0);
+                mExpectations.countDown();
+            }
         }
 
-        public ConditionVariable getNetworkRequestedCV() {
-            mNetworkRequestedCV.close();
-            return mNetworkRequestedCV;
+        private void assertNoExpectations() {
+            if (mExpectations != null) {
+                fail("Can't add expectation, " + mExpectations.getCount() + " already pending");
+            }
         }
 
-        public ConditionVariable getNetworkReleasedCV() {
-            mNetworkReleasedCV.close();
-            return mNetworkReleasedCV;
+        // Expects that count requests will be added.
+        public void expectAddRequests(final int count) {
+            assertNoExpectations();
+            mExpectingAdditions = true;
+            mExpectations = new CountDownLatch(count);
         }
 
-        public void waitForNetworkRequests(final int count) {
-            waitFor(new Criteria() { public boolean get() { return count == getRequestCount(); } });
+        // Expects that count requests will be removed.
+        public void expectRemoveRequests(final int count) {
+            assertNoExpectations();
+            mExpectingAdditions = false;
+            mExpectations = new CountDownLatch(count);
+        }
+
+        // Waits for the expected request additions or removals to happen within a timeout.
+        public void waitForRequests() throws InterruptedException {
+            assertNotNull("Nothing to wait for", mExpectations);
+            mExpectations.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            final long count = mExpectations.getCount();
+            final String msg = count + " requests still not " +
+                    (mExpectingAdditions ? "added" : "removed") +
+                    " after " + TIMEOUT_MS + " ms";
+            assertEquals(msg, 0, count);
+            mExpectations = null;
+        }
+
+        public void waitForNetworkRequests(final int count) throws InterruptedException {
+            waitForRequests();
+            assertEquals(count, getMyRequestCount());
         }
     }
 
@@ -356,7 +498,6 @@
     }
 
     private class WrappedConnectivityService extends ConnectivityService {
-        private final ConditionVariable mNetworkMonitorCreated = new ConditionVariable();
         private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
 
         public WrappedConnectivityService(Context context, INetworkManagementService netManager,
@@ -365,6 +506,11 @@
         }
 
         @Override
+        protected HandlerThread createHandlerThread() {
+            return new IdleableHandlerThread("WrappedConnectivityService");
+        }
+
+        @Override
         protected int getDefaultTcpRwnd() {
             // Prevent wrapped ConnectivityService from trying to write to SystemProperties.
             return 0;
@@ -397,7 +543,6 @@
             final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(context, handler, nai,
                     defaultRequest);
             mLastCreatedNetworkMonitor = monitor;
-            mNetworkMonitorCreated.open();
             return monitor;
         }
 
@@ -405,10 +550,14 @@
             return mLastCreatedNetworkMonitor;
         }
 
-        public ConditionVariable getNetworkMonitorCreatedCV() {
-            mNetworkMonitorCreated.close();
-            return mNetworkMonitorCreated;
+        public void waitForIdle(int timeoutMs) {
+            ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs);
         }
+
+        public void waitForIdle() {
+            waitForIdle(TIMEOUT_MS);
+        }
+
     }
 
     private interface Criteria {
@@ -431,23 +580,11 @@
     }
 
     /**
-     * Wait up to 500ms for {@code conditonVariable} to open.
-     * Fails if 500ms goes by before {@code conditionVariable} opens.
+     * Wait up to TIMEOUT_MS for {@code conditionVariable} to open.
+     * Fails if TIMEOUT_MS goes by before {@code conditionVariable} opens.
      */
     static private void waitFor(ConditionVariable conditionVariable) {
-        assertTrue(conditionVariable.block(500));
-    }
-
-    /**
-     * This should only be used to verify that nothing happens, in other words that no unexpected
-     * changes occur.  It should never be used to wait for a specific positive signal to occur.
-     */
-    private void shortSleep() {
-        // TODO: Instead of sleeping, instead wait for all message loops to idle.
-        try {
-            Thread.sleep(500);
-        } catch (InterruptedException e) {
-        }
+        assertTrue(conditionVariable.block(TIMEOUT_MS));
     }
 
     @Override
@@ -455,13 +592,11 @@
         super.setUp();
 
         mServiceContext = new MockContext(getContext());
+        mService = new WrappedConnectivityService(mServiceContext,
+                mock(INetworkManagementService.class),
+                mock(INetworkStatsService.class),
+                mock(INetworkPolicyManager.class));
 
-        mNetManager = mock(INetworkManagementService.class);
-        mStatsService = mock(INetworkStatsService.class);
-        mPolicyService = mock(INetworkPolicyManager.class);
-
-        mService = new WrappedConnectivityService(
-                mServiceContext, mNetManager, mStatsService, mPolicyService);
         mService.systemReady();
         mCm = new ConnectivityManager(getContext(), mService);
     }
@@ -583,11 +718,11 @@
         // Test bringing up unvalidated cellular
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
-        shortSleep();
+        mService.waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test cellular disconnect.
         mCellNetworkAgent.disconnect();
-        shortSleep();
+        mService.waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up validated cellular
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -797,6 +932,11 @@
         LOST
     }
 
+    /**
+     * Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks
+     * this class receives, by calling expectCallback() exactly once each time a callback is
+     * received. assertNoCallback may be called at any time.
+     */
     private class TestNetworkCallback extends NetworkCallback {
         private final ConditionVariable mConditionVariable = new ConditionVariable();
         private CallbackState mLastCallback = CallbackState.NONE;
@@ -819,14 +959,15 @@
             mConditionVariable.open();
         }
 
-        ConditionVariable getConditionVariable() {
+        void expectCallback(CallbackState state) {
+            waitFor(mConditionVariable);
+            assertEquals(state, mLastCallback);
             mLastCallback = CallbackState.NONE;
             mConditionVariable.close();
-            return mConditionVariable;
         }
 
-        CallbackState getLastCallback() {
-            return mLastCallback;
+        void assertNoCallback() {
+            assertEquals(CallbackState.NONE, mLastCallback);
         }
     }
 
@@ -842,98 +983,68 @@
         mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
 
         // Test unvalidated networks
-        ConditionVariable cellCv = cellNetworkCallback.getConditionVariable();
-        ConditionVariable wifiCv = wifiNetworkCallback.getConditionVariable();
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
-        waitFor(cellCv);
-        assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback());
-        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE);
+        wifiNetworkCallback.assertNoCallback();
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         waitFor(cv);
 
-        cellCv = cellNetworkCallback.getConditionVariable();
-        wifiCv = wifiNetworkCallback.getConditionVariable();
         // This should not trigger spurious onAvailable() callbacks, b/21762680.
         mCellNetworkAgent.adjustScore(-1);
-        shortSleep();
-        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
-        assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+        mService.waitForIdle();
+        wifiNetworkCallback.assertNoCallback();
+        cellNetworkCallback.assertNoCallback();
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
-        cellCv = cellNetworkCallback.getConditionVariable();
-        wifiCv = wifiNetworkCallback.getConditionVariable();
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        waitFor(wifiCv);
-        assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback());
-        assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+        wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE);
+        cellNetworkCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         waitFor(cv);
 
-        cellCv = cellNetworkCallback.getConditionVariable();
-        wifiCv = wifiNetworkCallback.getConditionVariable();
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent.disconnect();
-        waitFor(wifiCv);
-        assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback());
-        assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+        wifiNetworkCallback.expectCallback(CallbackState.LOST);
+        cellNetworkCallback.assertNoCallback();
         waitFor(cv);
 
-        cellCv = cellNetworkCallback.getConditionVariable();
-        wifiCv = wifiNetworkCallback.getConditionVariable();
         cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent.disconnect();
-        waitFor(cellCv);
-        assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback());
-        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+        cellNetworkCallback.expectCallback(CallbackState.LOST);
+        wifiNetworkCallback.assertNoCallback();
         waitFor(cv);
 
         // Test validated networks
-
-        cellCv = cellNetworkCallback.getConditionVariable();
-        wifiCv = wifiNetworkCallback.getConditionVariable();
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        waitFor(cellCv);
-        assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback());
-        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE);
+        wifiNetworkCallback.assertNoCallback();
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
-        cellCv = cellNetworkCallback.getConditionVariable();
-        wifiCv = wifiNetworkCallback.getConditionVariable();
         // This should not trigger spurious onAvailable() callbacks, b/21762680.
         mCellNetworkAgent.adjustScore(-1);
-        shortSleep();
-        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
-        assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+        mService.waitForIdle();
+        wifiNetworkCallback.assertNoCallback();
+        cellNetworkCallback.assertNoCallback();
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
-        cellCv = cellNetworkCallback.getConditionVariable();
-        wifiCv = wifiNetworkCallback.getConditionVariable();
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        waitFor(wifiCv);
-        assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback());
-        waitFor(cellCv);
-        assertEquals(CallbackState.LOSING, cellNetworkCallback.getLastCallback());
+        wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE);
+        cellNetworkCallback.expectCallback(CallbackState.LOSING);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
-        cellCv = cellNetworkCallback.getConditionVariable();
-        wifiCv = wifiNetworkCallback.getConditionVariable();
         mWiFiNetworkAgent.disconnect();
-        waitFor(wifiCv);
-        assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback());
-        assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+        wifiNetworkCallback.expectCallback(CallbackState.LOST);
+        cellNetworkCallback.assertNoCallback();
 
-        cellCv = cellNetworkCallback.getConditionVariable();
-        wifiCv = wifiNetworkCallback.getConditionVariable();
         mCellNetworkAgent.disconnect();
-        waitFor(cellCv);
-        assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback());
-        assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+        cellNetworkCallback.expectCallback(CallbackState.LOST);
+        wifiNetworkCallback.assertNoCallback();
     }
 
     private void tryNetworkFactoryRequests(int capability) throws Exception {
@@ -957,18 +1068,21 @@
                 mServiceContext, "testFactory", filter);
         testFactory.setScoreFilter(40);
         ConditionVariable cv = testFactory.getNetworkStartedCV();
+        testFactory.expectAddRequests(1);
         testFactory.register();
+        testFactory.waitForNetworkRequests(1);
         int expectedRequestCount = 1;
         NetworkCallback networkCallback = null;
         // For non-INTERNET capabilities we cannot rely on the default request being present, so
         // add one.
         if (capability != NET_CAPABILITY_INTERNET) {
-            testFactory.waitForNetworkRequests(1);
             assertFalse(testFactory.getMyStartRequested());
             NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build();
             networkCallback = new NetworkCallback();
+            testFactory.expectAddRequests(1);
             mCm.requestNetwork(request, networkCallback);
             expectedRequestCount++;
+            testFactory.waitForNetworkRequests(expectedRequestCount);
         }
         waitFor(cv);
         assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
@@ -981,13 +1095,20 @@
         // unvalidated penalty.
         testAgent.adjustScore(40);
         cv = testFactory.getNetworkStoppedCV();
+
+        // When testAgent connects, ConnectivityService will re-send us all current requests with
+        // the new score. There are expectedRequestCount such requests, and we must wait for all of
+        // them.
+        testFactory.expectAddRequests(expectedRequestCount);
         testAgent.connect(false);
         testAgent.addCapability(capability);
         waitFor(cv);
-        assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+        testFactory.waitForNetworkRequests(expectedRequestCount);
         assertFalse(testFactory.getMyStartRequested());
 
         // Bring in a bunch of requests.
+        testFactory.expectAddRequests(10);
+        assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
         ConnectivityManager.NetworkCallback[] networkCallbacks =
                 new ConnectivityManager.NetworkCallback[10];
         for (int i = 0; i< networkCallbacks.length; i++) {
@@ -1000,6 +1121,7 @@
         assertFalse(testFactory.getMyStartRequested());
 
         // Remove the requests.
+        testFactory.expectRemoveRequests(10);
         for (int i = 0; i < networkCallbacks.length; i++) {
             mCm.unregisterNetworkCallback(networkCallbacks[i]);
         }
@@ -1088,10 +1210,8 @@
         // Test bringing up unvalidated cellular with MMS
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
-        cv = networkCallback.getConditionVariable();
         mCellNetworkAgent.connectWithoutInternet();
-        waitFor(cv);
-        assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback());
+        networkCallback.expectCallback(CallbackState.AVAILABLE);
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test releasing NetworkRequest disconnects cellular with MMS
         cv = mCellNetworkAgent.getDisconnectedCV();
@@ -1114,12 +1234,10 @@
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.requestNetwork(builder.build(), networkCallback);
         // Test bringing up MMS cellular network
-        cv = networkCallback.getConditionVariable();
         MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mmsNetworkAgent.connectWithoutInternet();
-        waitFor(cv);
-        assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback());
+        networkCallback.expectCallback(CallbackState.AVAILABLE);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
         cv = mmsNetworkAgent.getDisconnectedCV();
@@ -1139,133 +1257,245 @@
         final NetworkRequest validatedRequest = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_VALIDATED).build();
         mCm.registerNetworkCallback(validatedRequest, validatedCallback);
-        ConditionVariable validatedCv = validatedCallback.getConditionVariable();
 
         // Bring up a network with a captive portal.
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
-        ConditionVariable cv = captivePortalCallback.getConditionVariable();
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connectWithCaptivePortal();
-        waitFor(cv);
-        assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback());
+        captivePortalCallback.expectCallback(CallbackState.AVAILABLE);
 
         // Take down network.
         // Expect onLost callback.
-        cv = captivePortalCallback.getConditionVariable();
         mWiFiNetworkAgent.disconnect();
-        waitFor(cv);
-        assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback());
+        captivePortalCallback.expectCallback(CallbackState.LOST);
 
         // Bring up a network with a captive portal.
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
-        cv = captivePortalCallback.getConditionVariable();
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connectWithCaptivePortal();
-        waitFor(cv);
-        assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback());
+        captivePortalCallback.expectCallback(CallbackState.AVAILABLE);
 
         // Make captive portal disappear then revalidate.
         // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
-        cv = captivePortalCallback.getConditionVariable();
         mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        waitFor(cv);
-        assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback());
+        captivePortalCallback.expectCallback(CallbackState.LOST);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        waitFor(validatedCv);
-        assertEquals(CallbackState.AVAILABLE, validatedCallback.getLastCallback());
+        validatedCallback.expectCallback(CallbackState.AVAILABLE);
 
         // Break network connectivity.
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
-        validatedCv = validatedCallback.getConditionVariable();
         mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500;
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
-        waitFor(validatedCv);
-        assertEquals(CallbackState.LOST, validatedCallback.getLastCallback());
+        validatedCallback.expectCallback(CallbackState.LOST);
     }
 
-//    @Override
-//    public void tearDown() throws Exception {
-//        super.tearDown();
-//    }
-//
-//    public void testMobileConnectedAddedRoutes() throws Exception {
-//        Future<?> nextConnBroadcast;
-//
-//        // bring up mobile network
-//        mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null);
-//        mMobile.link.setInterfaceName(MOBILE_IFACE);
-//        mMobile.link.addRoute(MOBILE_ROUTE_V4);
-//        mMobile.link.addRoute(MOBILE_ROUTE_V6);
-//        mMobile.doReturnDefaults();
-//
-//        cv = waitForConnectivityBroadcasts(1);
-//        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
-//        waitFor(cv);
-//
-//        // verify that both routes were added
-//        int mobileNetId = mMobile.tracker.getNetwork().netId;
-//        verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4));
-//        verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6));
-//    }
-//
-//    public void testMobileWifiHandoff() throws Exception {
-//        Future<?> nextConnBroadcast;
-//
-//        // bring up mobile network
-//        mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null);
-//        mMobile.link.setInterfaceName(MOBILE_IFACE);
-//        mMobile.link.addRoute(MOBILE_ROUTE_V4);
-//        mMobile.link.addRoute(MOBILE_ROUTE_V6);
-//        mMobile.doReturnDefaults();
-//
-//        cv = waitForConnectivityBroadcasts(1);
-//        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
-//        waitFor(cv);
-//
-//        reset(mNetManager);
-//
-//        // now bring up wifi network
-//        mWifi.info.setDetailedState(DetailedState.CONNECTED, null, null);
-//        mWifi.link.setInterfaceName(WIFI_IFACE);
-//        mWifi.link.addRoute(WIFI_ROUTE_V4);
-//        mWifi.link.addRoute(WIFI_ROUTE_V6);
-//        mWifi.doReturnDefaults();
-//
-//        // expect that mobile will be torn down
-//        doReturn(true).when(mMobile.tracker).teardown();
-//
-//        cv = waitForConnectivityBroadcasts(1);
-//        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget();
-//        waitFor(cv);
-//
-//        // verify that wifi routes added, and teardown requested
-//        int wifiNetId = mWifi.tracker.getNetwork().netId;
-//        verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V4));
-//        verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V6));
-//        verify(mMobile.tracker).teardown();
-//
-//        int mobileNetId = mMobile.tracker.getNetwork().netId;
-//
-//        reset(mNetManager, mMobile.tracker);
-//
-//        // tear down mobile network, as requested
-//        mMobile.info.setDetailedState(DetailedState.DISCONNECTED, null, null);
-//        mMobile.link.clear();
-//        mMobile.doReturnDefaults();
-//
-//        cv = waitForConnectivityBroadcasts(1);
-//        mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
-//        waitFor(cv);
-//
-//        verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4));
-//        verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6));
-//
-//    }
+    private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
-    private static InetAddress parse(String addr) {
-        return InetAddress.parseNumericAddress(addr);
+        public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+
+        private class CallbackValue {
+            public CallbackType callbackType;
+            public int error;
+
+            public CallbackValue(CallbackType type) {
+                this.callbackType = type;
+                this.error = PacketKeepalive.SUCCESS;
+                assertTrue("onError callback must have error", type != CallbackType.ON_ERROR);
+            }
+
+            public CallbackValue(CallbackType type, int error) {
+                this.callbackType = type;
+                this.error = error;
+                assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR);
+            }
+
+            @Override
+            public boolean equals(Object o) {
+                return o instanceof CallbackValue &&
+                        this.callbackType == ((CallbackValue) o).callbackType &&
+                        this.error == ((CallbackValue) o).error;
+            }
+
+            @Override
+            public String toString() {
+                return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error);
+            }
+        }
+
+        private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+        @Override
+        public void onStarted() {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED));
+        }
+
+        @Override
+        public void onStopped() {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED));
+        }
+
+        @Override
+        public void onError(int error) {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
+        }
+
+        private void expectCallback(CallbackValue callbackValue) {
+            try {
+                assertEquals(
+                        callbackValue,
+                        mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {
+                fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
+            }
+        }
+
+        public void expectStarted() {
+            expectCallback(new CallbackValue(CallbackType.ON_STARTED));
+        }
+
+        public void expectStopped() {
+            expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
+        }
+
+        public void expectError(int error) {
+            expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
+        }
     }
 
+    private Network connectKeepaliveNetwork(LinkProperties lp) {
+        // Ensure the network is disconnected before we do anything.
+        if (mWiFiNetworkAgent != null) {
+            assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()));
+        }
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        ConditionVariable cv = waitForConnectivityBroadcasts(1);
+        mWiFiNetworkAgent.connect(true);
+        waitFor(cv);
+        verifyActiveNetwork(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.sendLinkProperties(lp);
+        mService.waitForIdle();
+        return mWiFiNetworkAgent.getNetwork();
+    }
+
+    public void testPacketKeepalives() throws Exception {
+        InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
+        InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
+        InetAddress myIPv6 = InetAddress.getByName("2001:db8::1");
+        InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8");
+        InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888");
+
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName("wlan12");
+        lp.addLinkAddress(new LinkAddress(myIPv6, 64));
+        lp.addLinkAddress(new LinkAddress(myIPv4, 25));
+        lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234")));
+        lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+
+        Network notMyNet = new Network(61234);
+        Network myNet = connectKeepaliveNetwork(lp);
+
+        TestKeepaliveCallback callback = new TestKeepaliveCallback();
+        PacketKeepalive ka;
+
+        // Attempt to start keepalives with invalid parameters and check for errors.
+        ka = mCm.startNattKeepalive(notMyNet, 25, callback, myIPv4, 1234, dstIPv4);
+        callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
+
+        ka = mCm.startNattKeepalive(myNet, 19, callback, notMyIPv4, 1234, dstIPv4);
+        callback.expectError(PacketKeepalive.ERROR_INVALID_INTERVAL);
+
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 1234, dstIPv6);
+        callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv6, 1234, dstIPv4);
+        callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv6, 1234, dstIPv6);
+        callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS);  // NAT-T is IPv4-only.
+
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 123456, dstIPv4);
+        callback.expectError(PacketKeepalive.ERROR_INVALID_PORT);
+
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 123456, dstIPv4);
+        callback.expectError(PacketKeepalive.ERROR_INVALID_PORT);
+
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4);
+        callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4);
+        callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+
+        // Check that a started keepalive can be stopped.
+        mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS);
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4);
+        callback.expectStarted();
+        mWiFiNetworkAgent.setStopKeepaliveError(PacketKeepalive.SUCCESS);
+        ka.stop();
+        callback.expectStopped();
+
+        // Check that deleting the IP address stops the keepalive.
+        LinkProperties bogusLp = new LinkProperties(lp);
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4);
+        callback.expectStarted();
+        bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25));
+        bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25));
+        mWiFiNetworkAgent.sendLinkProperties(bogusLp);
+        callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS);
+        mWiFiNetworkAgent.sendLinkProperties(lp);
+
+        // Check that a started keepalive is stopped correctly when the network disconnects.
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4);
+        callback.expectStarted();
+        mWiFiNetworkAgent.disconnect();
+        callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
+
+        // ... and that stopping it after that has no adverse effects.
+        assertNull(mCm.getNetworkCapabilities(myNet));
+        ka.stop();
+
+        // Reconnect.
+        myNet = connectKeepaliveNetwork(lp);
+        mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS);
+
+        // Check things work as expected when the keepalive is stopped and the network disconnects.
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4);
+        callback.expectStarted();
+        ka.stop();
+        mWiFiNetworkAgent.disconnect();
+        mService.waitForIdle();
+        callback.expectStopped();
+
+        // Reconnect.
+        myNet = connectKeepaliveNetwork(lp);
+        mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS);
+
+        // Check that keepalive slots start from 1 and increment. The first one gets slot 1.
+        mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
+        ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4);
+        callback.expectStarted();
+
+        // The second one gets slot 2.
+        mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
+        TestKeepaliveCallback callback2 = new TestKeepaliveCallback();
+        PacketKeepalive ka2 = mCm.startNattKeepalive(myNet, 25, callback2, myIPv4, 6789, dstIPv4);
+        callback2.expectStarted();
+
+        // Now stop the first one and create a third. This also gets slot 1.
+        ka.stop();
+        callback.expectStopped();
+
+        mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
+        TestKeepaliveCallback callback3 = new TestKeepaliveCallback();
+        PacketKeepalive ka3 = mCm.startNattKeepalive(myNet, 25, callback3, myIPv4, 9876, dstIPv4);
+        callback3.expectStarted();
+
+        ka2.stop();
+        callback2.expectStopped();
+
+        ka3.stop();
+        callback3.expectStopped();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 0f20dde..f9aa124 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -82,7 +82,7 @@
         mAms.addAccountExplicitly(a31, "p31", null);
         mAms.addAccountExplicitly(a32, "p32", null);
 
-        Account[] accounts = mAms.getAccounts(null);
+        Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
         Arrays.sort(accounts, new AccountSorter());
         assertEquals(6, accounts.length);
         assertEquals(a11, accounts[0]);
@@ -92,7 +92,7 @@
         assertEquals(a22, accounts[4]);
         assertEquals(a32, accounts[5]);
 
-        accounts = mAms.getAccounts("type1" );
+        accounts = mAms.getAccounts("type1", mContext.getOpPackageName());
         Arrays.sort(accounts, new AccountSorter());
         assertEquals(3, accounts.length);
         assertEquals(a11, accounts[0]);
@@ -101,7 +101,7 @@
 
         mAms.removeAccountInternal(a21);
 
-        accounts = mAms.getAccounts("type1" );
+        accounts = mAms.getAccounts("type1", mContext.getOpPackageName());
         Arrays.sort(accounts, new AccountSorter());
         assertEquals(2, accounts.length);
         assertEquals(a11, accounts[0]);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5ed41c6..135a97a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -267,6 +267,16 @@
             "carrier_instant_lettering_available_bool";
 
     /**
+     * When IMS instant lettering is available for a carrier (see
+     * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), determines the list of characters
+     * which may not be contained in messages.  Should be specified as a regular expression suitable
+     * for use with {@link String#matches(String)}.
+     * @hide
+     */
+    public static final String KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING =
+            "carrier_instant_lettering_invalid_chars_string";
+
+    /**
      * If Voice Radio Technology is RIL_RADIO_TECHNOLOGY_LTE:14 or RIL_RADIO_TECHNOLOGY_UNKNOWN:0
      * this is the value that should be used instead. A configuration value of
      * RIL_RADIO_TECHNOLOGY_UNKNOWN:0 means there is no replacement value and that the default
@@ -438,6 +448,7 @@
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false);
+        sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING, "");
         sDefaults.putBoolean(KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL, false);
         sDefaults.putBoolean(KEY_DTMF_TYPE_ENABLED_BOOL, false);
         sDefaults.putBoolean(KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL, true);