Expose a simple tethering API which includes provision checks.

This CL exposes startTethering and stopTethering functions which also
encapsulate all provisioning check logic. Right now, only silent checks
are implemented, but UI checks will come in a follow-up CL. GTS tests
and Settings changes are under the same topic ID.

BUG: 26247383
Change-Id: I65f61d899594cb3f9035d8496366af17a57a090f
diff --git a/api/system-current.txt b/api/system-current.txt
index 55f08c8..6b6320d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -24401,6 +24401,7 @@
     method public boolean isActiveNetworkMetered();
     method public boolean isDefaultNetworkActive();
     method public static deprecated boolean isNetworkTypeValid(int);
+    method public boolean isTetheringSupported();
     method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
     method public void registerNetworkCallback(android.net.NetworkRequest, android.app.PendingIntent);
     method public void releaseNetworkRequest(android.app.PendingIntent);
@@ -24413,7 +24414,10 @@
     method public deprecated boolean requestRouteToHost(int, int);
     method public deprecated void setNetworkPreference(int);
     method public static deprecated boolean setProcessDefaultNetwork(android.net.Network);
+    method public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
+    method public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
     method public deprecated int startUsingNetworkFeature(int, java.lang.String);
+    method public void stopTethering(int);
     method public deprecated int stopUsingNetworkFeature(int, java.lang.String);
     method public void unregisterNetworkCallback(android.net.ConnectivityManager.NetworkCallback);
     method public void unregisterNetworkCallback(android.app.PendingIntent);
@@ -24435,6 +24439,9 @@
     field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
     field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
     field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
+    field public static final int TETHERING_BLUETOOTH = 2; // 0x2
+    field public static final int TETHERING_USB = 1; // 0x1
+    field public static final int TETHERING_WIFI = 0; // 0x0
     field public static final int TYPE_BLUETOOTH = 7; // 0x7
     field public static final int TYPE_DUMMY = 8; // 0x8
     field public static final int TYPE_ETHERNET = 9; // 0x9
@@ -24461,6 +24468,12 @@
     method public abstract void onNetworkActive();
   }
 
+  public static abstract class ConnectivityManager.OnStartTetheringCallback {
+    ctor public ConnectivityManager.OnStartTetheringCallback();
+    method public void onTetheringFailed();
+    method public void onTetheringStarted();
+  }
+
   public class Credentials {
     ctor public Credentials(int, int, int);
     method public int getGid();
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 08c0c09..7cb086f 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -16,6 +16,7 @@
 package android.net;
 
 import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -27,6 +28,7 @@
 import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -37,6 +39,7 @@
 import android.os.Messenger;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
@@ -338,6 +341,71 @@
     public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED";
 
     /**
+     * Invalid tethering type.
+     * @see #startTethering(int, OnStartTetheringCallback, boolean)
+     * @hide
+     */
+    public static final int TETHERING_INVALID   = -1;
+
+    /**
+     * Wifi tethering type.
+     * @see #startTethering(int, OnStartTetheringCallback, boolean)
+     * @hide
+     */
+    @SystemApi
+    public static final int TETHERING_WIFI      = 0;
+
+    /**
+     * USB tethering type.
+     * @see #startTethering(int, OnStartTetheringCallback, boolean)
+     * @hide
+     */
+    @SystemApi
+    public static final int TETHERING_USB       = 1;
+
+    /**
+     * Bluetooth tethering type.
+     * @see #startTethering(int, OnStartTetheringCallback, boolean)
+     * @hide
+     */
+    @SystemApi
+    public static final int TETHERING_BLUETOOTH = 2;
+
+    /**
+     * Extra used for communicating with the TetherService. Includes the type of tethering to
+     * enable if any.
+     * @hide
+     */
+    public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+
+    /**
+     * Extra used for communicating with the TetherService. Includes the type of tethering for
+     * which to cancel provisioning.
+     * @hide
+     */
+    public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+
+    /**
+     * Extra used for communicating with the TetherService. True to schedule a recheck of tether
+     * provisioning.
+     * @hide
+     */
+    public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+
+    /**
+     * Tells the TetherService to run a provision check now.
+     * @hide
+     */
+    public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+
+    /**
+     * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
+     * which will receive provisioning results. Can be left empty.
+     * @hide
+     */
+    public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+
+    /**
      * The absence of a connection type.
      * @hide
      */
@@ -1789,6 +1857,11 @@
      * or the ability to modify system settings as determined by
      * {@link android.provider.Settings.System#canWrite}.</p>
      *
+     * <p>WARNING: New clients should not use this function. The only usages should be in PanService
+     * and WifiStateMachine which need direct access. All other clients should use
+     * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning
+     * logic.</p>
+     *
      * @param iface the interface name to tether.
      * @return error a {@code TETHER_ERROR} value indicating success or failure type
      *
@@ -1810,6 +1883,11 @@
      * or the ability to modify system settings as determined by
      * {@link android.provider.Settings.System#canWrite}.</p>
      *
+     * <p>WARNING: New clients should not use this function. The only usages should be in PanService
+     * and WifiStateMachine which need direct access. All other clients should use
+     * {@link #startTethering} and {@link #stopTethering} which encapsulate proper provisioning
+     * logic.</p>
+     *
      * @param iface the interface name to untether.
      * @return error a {@code TETHER_ERROR} value indicating success or failure type
      *
@@ -1834,6 +1912,7 @@
      *
      * {@hide}
      */
+    @SystemApi
     public boolean isTetheringSupported() {
         try {
             return mService.isTetheringSupported();
@@ -1843,6 +1922,94 @@
     }
 
     /**
+     * Callback for use with {@link #startTethering} to find out whether tethering succeeded.
+     * @hide
+     */
+    @SystemApi
+    public static abstract class OnStartTetheringCallback {
+        /**
+         * Called when tethering has been successfully started.
+         */
+        public void onTetheringStarted() {};
+
+        /**
+         * Called when starting tethering failed.
+         */
+        public void onTetheringFailed() {};
+    }
+
+    /**
+     * Convenient overload for
+     * {@link #startTethering(int, boolean, OnStartTetheringCallback, Handler)} which passes a null
+     * handler to run on the current thread's {@link Looper}.
+     * @hide
+     */
+    @SystemApi
+    public void startTethering(int type, boolean showProvisioningUi,
+            final OnStartTetheringCallback callback) {
+        startTethering(type, showProvisioningUi, callback, null);
+    }
+
+    /**
+     * Runs tether provisioning for the given type if needed and then starts tethering if
+     * the check succeeds. If no carrier provisioning is required for tethering, tethering is
+     * enabled immediately. If provisioning fails, tethering will not be enabled. It also
+     * schedules tether provisioning re-checks if appropriate.
+     *
+     * @param type The type of tethering to start. Must be one of
+     *         {@link ConnectivityManager.TETHERING_WIFI},
+     *         {@link ConnectivityManager.TETHERING_USB}, or
+     *         {@link ConnectivityManager.TETHERING_BLUETOOTH}.
+     * @param showProvisioningUi a boolean indicating to show the provisioning app UI if there
+     *         is one. This should be true the first time this function is called and also any time
+     *         the user can see this UI. It gives users information from their carrier about the
+     *         check failing and how they can sign up for tethering if possible.
+     * @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller
+     *         of the result of trying to tether.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @hide
+     */
+    @SystemApi
+    public void startTethering(int type, boolean showProvisioningUi,
+            final OnStartTetheringCallback callback, Handler handler) {
+        ResultReceiver wrappedCallback = new ResultReceiver(handler) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                if (resultCode == TETHER_ERROR_NO_ERROR) {
+                    callback.onTetheringStarted();
+                } else {
+                    callback.onTetheringFailed();
+                }
+            }
+        };
+        try {
+            mService.startTethering(type, wrappedCallback, showProvisioningUi);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception trying to start tethering.", e);
+            wrappedCallback.send(TETHER_ERROR_SERVICE_UNAVAIL, null);
+        }
+    }
+
+    /**
+     * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if
+     * applicable.
+     *
+     * @param type The type of tethering to stop. Must be one of
+     *         {@link ConnectivityManager.TETHERING_WIFI},
+     *         {@link ConnectivityManager.TETHERING_USB}, or
+     *         {@link ConnectivityManager.TETHERING_BLUETOOTH}.
+     * @hide
+     */
+    @SystemApi
+    public void stopTethering(int type) {
+        try {
+            mService.stopTethering(type);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception trying to stop tethering.", e);
+        }
+    }
+
+    /**
      * Get the list of regular expressions that define any tetherable
      * USB network interfaces.  If USB tethering is not supported by the
      * device, this list should be empty.
@@ -1949,6 +2116,8 @@
     public static final int TETHER_ERROR_DISABLE_NAT_ERROR    = 9;
     /** {@hide} */
     public static final int TETHER_ERROR_IFACE_CFG_ERROR      = 10;
+    /** {@hide} */
+    public static final int TETHER_ERROR_PROVISION_FAILED     = 11;
 
     /**
      * Get a more detailed error code after a Tethering or Untethering
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 569468e..1a9c9ea 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -76,6 +76,10 @@
 
     boolean isTetheringSupported();
 
+    void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi);
+
+    void stopTethering(int type);
+
     String[] getTetherableIfaces();
 
     String[] getTetheredIfaces();
diff --git a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
index 66233b8..f5a2aae 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
@@ -29,12 +29,6 @@
 
 public class TetherUtil {
 
-    // Types of tethering.
-    public static final int TETHERING_INVALID   = -1;
-    public static final int TETHERING_WIFI      = 0;
-    public static final int TETHERING_USB       = 1;
-    public static final int TETHERING_BLUETOOTH = 2;
-
     // Extras used for communicating with the TetherService.
     public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
     public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
@@ -43,14 +37,6 @@
      * Tells the service to run a provision check now.
      */
     public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
-    /**
-     * Enables wifi tethering if the provision check is successful. Used by
-     * QS to enable tethering.
-     */
-    public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether";
-
-    public static ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
-            .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
 
     public static boolean setWifiTethering(boolean enable, Context context) {
         final WifiManager wifiManager =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 41aeac9..5719f76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -20,8 +20,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.ConnectivityManager;
 import android.net.wifi.WifiManager;
-import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.settingslib.TetherUtil;
@@ -34,21 +34,18 @@
 
     private static final String TAG = "HotspotController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final Intent TETHER_SERVICE_INTENT = new Intent()
-            .putExtra(TetherUtil.EXTRA_ADD_TETHER_TYPE, TetherUtil.TETHERING_WIFI)
-            .putExtra(TetherUtil.EXTRA_SET_ALARM, true)
-            .putExtra(TetherUtil.EXTRA_RUN_PROVISION, true)
-            .putExtra(TetherUtil.EXTRA_ENABLE_WIFI_TETHER, true)
-            .setComponent(TetherUtil.TETHER_SERVICE);
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     private final Receiver mReceiver = new Receiver();
+    private final ConnectivityManager mConnectivityManager;
     private final Context mContext;
 
     private int mHotspotState;
 
     public HotspotControllerImpl(Context context) {
         mContext = context;
+        mConnectivityManager = (ConnectivityManager)context.getSystemService(
+                Context.CONNECTIVITY_SERVICE);
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -72,6 +69,7 @@
         return null;
     }
 
+    @Override
     public void addCallback(Callback callback) {
         if (callback == null || mCallbacks.contains(callback)) return;
         if (DEBUG) Log.d(TAG, "addCallback " + callback);
@@ -79,6 +77,7 @@
         mReceiver.setListening(!mCallbacks.isEmpty());
     }
 
+    @Override
     public void removeCallback(Callback callback) {
         if (callback == null) return;
         if (DEBUG) Log.d(TAG, "removeCallback " + callback);
@@ -96,13 +95,24 @@
         return TetherUtil.isTetheringSupported(mContext);
     }
 
+    static final class OnStartTetheringCallback extends
+            ConnectivityManager.OnStartTetheringCallback {
+        @Override
+        public void onTetheringStarted() {}
+        @Override
+        public void onTetheringFailed() {
+          // TODO: Show error.
+        }
+    }
+
     @Override
     public void setHotspotEnabled(boolean enabled) {
-        // Call provisioning app which is called when enabling Tethering from Settings
-        if (enabled && TetherUtil.isProvisioningNeeded(mContext)) {
-            mContext.startServiceAsUser(TETHER_SERVICE_INTENT, UserHandle.CURRENT);
+        if (enabled) {
+            OnStartTetheringCallback callback = new OnStartTetheringCallback();
+            mConnectivityManager.startTethering(
+                    ConnectivityManager.TETHERING_WIFI, false, callback);
         } else {
-            TetherUtil.setWifiTethering(enabled, mContext);
+            mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
         }
     }
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 9927fd6..3c13630 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -87,6 +87,7 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -2686,6 +2687,21 @@
                 mTethering.getUpstreamIfaceTypes().length != 0);
     }
 
+    public void startTethering(int type, ResultReceiver receiver,
+            boolean showProvisioningUi) {
+        ConnectivityManager.enforceTetherChangePermission(mContext);
+        if (!isTetheringSupported()) {
+            receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
+            return;
+        }
+        mTethering.startTethering(type, receiver, showProvisioningUi);
+    }
+
+    public void stopTethering(int type) {
+        ConnectivityManager.enforceTetherChangePermission(mContext);
+        mTethering.stopTethering(type);
+    }
+
     // Called when we lose the default network and have no replacement yet.
     // This will automatically be cleared after X seconds or a new default network
     // becomes CONNECTED, whichever happens first.  The timer is started by the
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 3b43633..1d7e835 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -19,6 +19,10 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -38,20 +42,24 @@
 import android.net.NetworkRequest;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.wifi.WifiManager;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.telephony.IccCardConstants;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.IState;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.IoThread;
@@ -59,8 +67,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.net.InetAddress;
 import java.net.Inet4Address;
+import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -109,6 +117,10 @@
 
     private BroadcastReceiver mStateReceiver;
 
+    // {@link ComponentName} of the Service used to run tether provisioning.
+    private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
+            .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
+
     private static final String USB_NEAR_IFACE_ADDR      = "192.168.42.129";
     private static final int USB_PREFIX_LENGTH        = 24;
 
@@ -332,6 +344,206 @@
         }
     }
 
+    public void startTethering(int type, ResultReceiver receiver,
+            boolean showProvisioningUi) {
+        if (!isTetherProvisioningRequired()) {
+            enableTetheringInternal(type, true, receiver);
+            return;
+        }
+
+        if (showProvisioningUi) {
+            runUiTetherProvisioningAndEnable(type, receiver);
+        } else {
+            runSilentTetherProvisioningAndEnable(type, receiver);
+        }
+    }
+
+    public void stopTethering(int type) {
+        enableTetheringInternal(type, false, null);
+        if (isTetherProvisioningRequired()) {
+            cancelTetherProvisioningRechecks(type);
+        }
+    }
+
+    /**
+     * Check if the device requires a provisioning check in order to enable tethering.
+     *
+     * @return a boolean - {@code true} indicating tether provisioning is required by the carrier.
+     */
+    private boolean isTetherProvisioningRequired() {
+        String[] provisionApp = mContext.getResources().getStringArray(
+                com.android.internal.R.array.config_mobile_hotspot_provision_app);
+        if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)
+                || provisionApp == null) {
+            return false;
+        }
+
+        // Check carrier config for entitlement checks
+        final CarrierConfigManager configManager = (CarrierConfigManager) mContext
+             .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean(
+             CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
+
+        if (!isEntitlementCheckRequired) {
+            return false;
+        }
+        return (provisionApp.length == 2);
+    }
+
+    /**
+     * Enables or disables tethering for the given type. This should only be called once
+     * provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks
+     * for the specified interface.
+     */
+    private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) {
+        boolean isProvisioningRequired = isTetherProvisioningRequired();
+        switch (type) {
+            case ConnectivityManager.TETHERING_WIFI:
+                final WifiManager wifiManager =
+                        (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+                if (wifiManager.setWifiApEnabled(null, enable)) {
+                    sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_NO_ERROR);
+                    if (enable && isProvisioningRequired) {
+                        scheduleProvisioningRechecks(type);
+                    }
+                } else{
+                    sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
+                }
+                break;
+            case ConnectivityManager.TETHERING_USB:
+                int result = setUsbTethering(enable);
+                if (enable && isProvisioningRequired &&
+                        result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    scheduleProvisioningRechecks(type);
+                }
+                sendTetherResult(receiver, result);
+                break;
+            case ConnectivityManager.TETHERING_BLUETOOTH:
+                setBluetoothTethering(enable, receiver);
+                break;
+            default:
+                Log.w(TAG, "Invalid tether type.");
+                sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE);
+        }
+    }
+
+    private void sendTetherResult(ResultReceiver receiver, int result) {
+        if (receiver != null) {
+            receiver.send(result, null);
+        }
+    }
+
+    private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
+        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter == null || !adapter.isEnabled()) {
+            Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: " +
+                    (adapter == null));
+            sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL);
+            return;
+        }
+
+        adapter.getProfileProxy(mContext, new ServiceListener() {
+            @Override
+            public void onServiceDisconnected(int profile) { }
+
+            @Override
+            public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                ((BluetoothPan) proxy).setBluetoothTethering(enable);
+                // TODO: Enabling bluetooth tethering can fail asynchronously here.
+                // We should figure out a way to bubble up that failure instead of sending success.
+                int result = ((BluetoothPan) proxy).isTetheringOn() ?
+                        ConnectivityManager.TETHER_ERROR_NO_ERROR :
+                        ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+                sendTetherResult(receiver, result);
+                if (enable && isTetherProvisioningRequired()) {
+                    scheduleProvisioningRechecks(ConnectivityManager.TETHERING_BLUETOOTH);
+                }
+                adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);
+            }
+        }, BluetoothProfile.PAN);
+    }
+
+    private void runUiTetherProvisioningAndEnable(int type, ResultReceiver receiver) {
+        // TODO: Fill in for real.
+        enableTetheringInternal(type, true, receiver);
+    }
+
+    /**
+     * Creates a proxy {@link ResultReceiver} which enables tethering if the provsioning result is
+     * successful before firing back up to the wrapped receiver.
+     *
+     * @param type The type of tethering being enabled.
+     * @param receiver A ResultReceiver which will be called back with an int resultCode.
+     * @return The proxy receiver.
+     */
+    private ResultReceiver getProxyReceiver(final int type, final ResultReceiver receiver) {
+        ResultReceiver rr = new ResultReceiver(null) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                // If provisioning is successful, enable tethering, otherwise just send the error.
+                if (resultCode == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    enableTetheringInternal(type, true, receiver);
+                } else {
+                    sendTetherResult(receiver, resultCode);
+                }
+            }
+        };
+
+        // The following is necessary to avoid unmarshalling issues when sending the receiver
+        // across proccesses.
+        Parcel parcel = Parcel.obtain();
+        rr.writeToParcel(parcel,0);
+        parcel.setDataPosition(0);
+        ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+        return receiverForSending;
+    }
+
+    private void scheduleProvisioningRechecks(int type) {
+        Intent intent = new Intent();
+        intent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, type);
+        intent.putExtra(ConnectivityManager.EXTRA_SET_ALARM, true);
+        intent.setComponent(TETHER_SERVICE);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mContext.startServiceAsUser(intent, UserHandle.CURRENT);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void runSilentTetherProvisioningAndEnable(int type, ResultReceiver receiver) {
+        ResultReceiver proxyReceiver = getProxyReceiver(type, receiver);
+        sendSilentTetherProvisionIntent(type, proxyReceiver);
+    }
+
+    private void sendSilentTetherProvisionIntent(int type, ResultReceiver receiver) {
+        Intent intent = new Intent();
+        intent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, type);
+        intent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
+        intent.putExtra(ConnectivityManager.EXTRA_PROVISION_CALLBACK, receiver);
+        intent.setComponent(TETHER_SERVICE);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mContext.startServiceAsUser(intent, UserHandle.CURRENT);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void cancelTetherProvisioningRechecks(int type) {
+        if (getConnectivityManager().isTetheringSupported()) {
+            Intent intent = new Intent();
+            intent.putExtra(ConnectivityManager.EXTRA_REM_TETHER_TYPE, type);
+            intent.setComponent(TETHER_SERVICE);
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mContext.startServiceAsUser(intent, UserHandle.CURRENT);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
     public int tether(String iface) {
         if (DBG) Log.d(TAG, "Tethering " + iface);
         TetherInterfaceSM sm = null;
@@ -615,13 +827,23 @@
         synchronized (mPublicSync) {
             if (enable) {
                 if (mRndisEnabled) {
-                    tetherUsb(true);
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        tetherUsb(true);
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
                 } else {
                     mUsbTetherRequested = true;
                     usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS);
                 }
             } else {
-                tetherUsb(false);
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    tetherUsb(false);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
                 if (mRndisEnabled) {
                     usbManager.setCurrentFunction(null);
                 }
@@ -1407,15 +1629,6 @@
         private final AtomicInteger mSimBcastGenerationNumber = new AtomicInteger(0);
         private SimChangeBroadcastReceiver mBroadcastReceiver = null;
 
-        // keep consts in sync with packages/apps/Settings TetherSettings.java
-        private static final int WIFI_TETHERING      = 0;
-        private static final int USB_TETHERING       = 1;
-        private static final int BLUETOOTH_TETHERING = 2;
-
-        // keep consts in sync with packages/apps/Settings TetherService.java
-        private static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
-        private static final String EXTRA_RUN_PROVISION = "extraRunProvision";
-
         private void startListeningForSimChanges() {
             if (DBG) Log.d(TAG, "startListeningForSimChanges");
             if (mBroadcastReceiver == null) {
@@ -1472,8 +1685,6 @@
                     try {
                         if (mContext.getResources().getString(com.android.internal.R.string.
                                 config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) {
-                            final String tetherService = mContext.getResources().getString(
-                                    com.android.internal.R.string.config_wifi_tether_enable);
                             ArrayList<Integer> tethered = new ArrayList<Integer>();
                             synchronized (mPublicSync) {
                                 Set ifaces = mIfaces.keySet();
@@ -1481,21 +1692,25 @@
                                     TetherInterfaceSM sm = mIfaces.get(iface);
                                     if (sm != null && sm.isTethered()) {
                                         if (isUsb((String)iface)) {
-                                            tethered.add(new Integer(USB_TETHERING));
+                                            tethered.add(new Integer(
+                                                    ConnectivityManager.TETHERING_USB));
                                         } else if (isWifi((String)iface)) {
-                                            tethered.add(new Integer(WIFI_TETHERING));
+                                            tethered.add(new Integer(
+                                                    ConnectivityManager.TETHERING_WIFI));
                                         } else if (isBluetooth((String)iface)) {
-                                            tethered.add(new Integer(BLUETOOTH_TETHERING));
+                                            tethered.add(new Integer(
+                                                    ConnectivityManager.TETHERING_BLUETOOTH));
                                         }
                                     }
                                 }
                             }
                             for (int tetherType : tethered) {
                                 Intent startProvIntent = new Intent();
-                                startProvIntent.putExtra(EXTRA_ADD_TETHER_TYPE, tetherType);
-                                startProvIntent.putExtra(EXTRA_RUN_PROVISION, true);
-                                startProvIntent.setComponent(
-                                        ComponentName.unflattenFromString(tetherService));
+                                startProvIntent.putExtra(
+                                        ConnectivityManager.EXTRA_ADD_TETHER_TYPE, tetherType);
+                                startProvIntent.putExtra(
+                                        ConnectivityManager.EXTRA_RUN_PROVISION, true);
+                                startProvIntent.setComponent(TETHER_SERVICE);
                                 mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
                             }
                             Log.d(TAG, "re-evaluate provisioning");