/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.content.Context; import android.os.Bundle; import android.os.ConditionVariable; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Supplier; /** * This class provides the APIs to control the tethering service. *

The primary responsibilities of this class are to provide the APIs for applications to * start tethering, stop tethering, query configuration and query status. * * @hide */ @SystemApi public class TetheringManager { // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is // available here /** @hide */ public static class Flags { static final String TETHERING_REQUEST_WITH_SOFT_AP_CONFIG = "com.android.net.flags.tethering_request_with_soft_ap_config"; } private static final String TAG = TetheringManager.class.getSimpleName(); private static final int DEFAULT_TIMEOUT_MS = 60_000; private static final long CONNECTOR_POLL_INTERVAL_MILLIS = 200L; @GuardedBy("mConnectorWaitQueue") @Nullable private ITetheringConnector mConnector; @GuardedBy("mConnectorWaitQueue") @NonNull private final List mConnectorWaitQueue = new ArrayList<>(); private final Supplier mConnectorSupplier; private final TetheringCallbackInternal mCallback; private final Context mContext; private final ArrayMap mTetheringEventCallbacks = new ArrayMap<>(); private volatile TetheringConfigurationParcel mTetheringConfiguration; private volatile TetherStatesParcel mTetherStatesParcel; /** * Broadcast Action: A tetherable connection has come or gone. * Uses {@code TetheringManager.EXTRA_AVAILABLE_TETHER}, * {@code TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY}, * {@code TetheringManager.EXTRA_ACTIVE_TETHER}, and * {@code TetheringManager.EXTRA_ERRORED_TETHER} to indicate * the current state of tethering. Each include a list of * interface names in that state (may be empty). * * @deprecated New client should use TetheringEventCallback instead. */ @Deprecated public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"; /** * gives a String[] listing all the interfaces configured for * tethering and currently available for tethering. */ public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; /** * gives a String[] listing all the interfaces currently in local-only * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding) */ public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY"; /** * gives a String[] listing all the interfaces currently tethered * (ie, has DHCPv4 support and packets potentially forwarded/NATed) */ public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; /** * gives a String[] listing all the interfaces we tried to tether and * failed. Use {@link #getLastTetherError} to find the error code * for any interfaces listed here. */ public static final String EXTRA_ERRORED_TETHER = "erroredArray"; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = false, value = { TETHERING_WIFI, TETHERING_USB, TETHERING_BLUETOOTH, TETHERING_WIFI_P2P, TETHERING_NCM, TETHERING_ETHERNET, }) public @interface TetheringType { } /** * Invalid tethering type. * @see #startTethering. */ public static final int TETHERING_INVALID = -1; /** * Wifi tethering type. * @see #startTethering. */ public static final int TETHERING_WIFI = 0; /** * USB tethering type. * @see #startTethering. */ public static final int TETHERING_USB = 1; /** * Bluetooth tethering type. * @see #startTethering. */ public static final int TETHERING_BLUETOOTH = 2; /** * Wifi P2p tethering type. * Wifi P2p tethering is set through events automatically, and don't * need to start from #startTethering. */ public static final int TETHERING_WIFI_P2P = 3; /** * Ncm local tethering type. * @see #startTethering(TetheringRequest, Executor, StartTetheringCallback) */ public static final int TETHERING_NCM = 4; /** * Ethernet tethering type. * @see #startTethering(TetheringRequest, Executor, StartTetheringCallback) */ public static final int TETHERING_ETHERNET = 5; /** * WIGIG tethering type. Use a separate type to prevent * conflicts with TETHERING_WIFI * This type is only used internally by the tethering module * @hide */ public static final int TETHERING_WIGIG = 6; /** * The int value of last tethering type. * @hide */ public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { TETHER_ERROR_NO_ERROR, TETHER_ERROR_PROVISIONING_FAILED, TETHER_ERROR_ENTITLEMENT_UNKNOWN, }) public @interface EntitlementResult { } /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { TETHER_ERROR_NO_ERROR, TETHER_ERROR_UNKNOWN_IFACE, TETHER_ERROR_SERVICE_UNAVAIL, TETHER_ERROR_INTERNAL_ERROR, TETHER_ERROR_TETHER_IFACE_ERROR, TETHER_ERROR_ENABLE_FORWARDING_ERROR, TETHER_ERROR_DISABLE_FORWARDING_ERROR, TETHER_ERROR_IFACE_CFG_ERROR, TETHER_ERROR_DHCPSERVER_ERROR, }) public @interface TetheringIfaceError { } /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { TETHER_ERROR_SERVICE_UNAVAIL, TETHER_ERROR_INTERNAL_ERROR, TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, TETHER_ERROR_UNKNOWN_TYPE, }) public @interface StartTetheringError { } public static final int TETHER_ERROR_NO_ERROR = 0; public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; public static final int TETHER_ERROR_UNSUPPORTED = 3; public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; public static final int TETHER_ERROR_INTERNAL_ERROR = 5; public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; public static final int TETHER_ERROR_ENABLE_FORWARDING_ERROR = 8; public static final int TETHER_ERROR_DISABLE_FORWARDING_ERROR = 9; public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; public static final int TETHER_ERROR_PROVISIONING_FAILED = 11; public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; public static final int TETHER_ERROR_UNKNOWN_TYPE = 16; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = false, value = { TETHER_HARDWARE_OFFLOAD_STOPPED, TETHER_HARDWARE_OFFLOAD_STARTED, TETHER_HARDWARE_OFFLOAD_FAILED, }) public @interface TetherOffloadStatus { } /** Tethering offload status is stopped. */ public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; /** Tethering offload status is started. */ public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; /** Fail to start tethering offload. */ public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; /** * Create a TetheringManager object for interacting with the tethering service. * * @param context Context for the manager. * @param connectorSupplier Supplier for the manager connector; may return null while the * service is not connected. * {@hide} */ @SystemApi(client = MODULE_LIBRARIES) public TetheringManager(@NonNull final Context context, @NonNull Supplier connectorSupplier) { mContext = context; mCallback = new TetheringCallbackInternal(this); mConnectorSupplier = connectorSupplier; final String pkgName = mContext.getOpPackageName(); final IBinder connector = mConnectorSupplier.get(); // If the connector is available on start, do not start a polling thread. This introduces // differences in the thread that sends the oneway binder calls to the service between the // first few seconds after boot and later, but it avoids always having differences between // the first usage of TetheringManager from a process and subsequent usages (so the // difference is only on boot). On boot binder calls may be queued until the service comes // up and be sent from a worker thread; later, they are always sent from the caller thread. // Considering that it's just oneway binder calls, and ordering is preserved, this seems // better than inconsistent behavior persisting after boot. if (connector != null) { mConnector = ITetheringConnector.Stub.asInterface(connector); } else { startPollingForConnector(); } Log.i(TAG, "registerTetheringEventCallback:" + pkgName); getConnector(c -> c.registerTetheringEventCallback(mCallback, pkgName)); } /** @hide */ @Override protected void finalize() throws Throwable { final String pkgName = mContext.getOpPackageName(); Log.i(TAG, "unregisterTetheringEventCallback:" + pkgName); // 1. It's generally not recommended to perform long operations in finalize, but while // unregisterTetheringEventCallback does an IPC, it's a oneway IPC so should not block. // 2. If the connector is not yet connected, TetheringManager is impossible to finalize // because the connector polling thread strong reference the TetheringManager object. So // it's guaranteed that registerTetheringEventCallback was already called before calling // unregisterTetheringEventCallback in finalize. if (mConnector == null) Log.wtf(TAG, "null connector in finalize!"); getConnector(c -> c.unregisterTetheringEventCallback(mCallback, pkgName)); super.finalize(); } private void startPollingForConnector() { new Thread(() -> { while (true) { try { Thread.sleep(CONNECTOR_POLL_INTERVAL_MILLIS); } catch (InterruptedException e) { // Not much to do here, the system needs to wait for the connector } final IBinder connector = mConnectorSupplier.get(); if (connector != null) { onTetheringConnected(ITetheringConnector.Stub.asInterface(connector)); return; } } }).start(); } private interface ConnectorConsumer { void onConnectorAvailable(ITetheringConnector connector) throws RemoteException; } private void onTetheringConnected(ITetheringConnector connector) { // Process the connector wait queue in order, including any items that are added // while processing. // // 1. Copy the queue to a local variable under lock. // 2. Drain the local queue with the lock released (otherwise, enqueuing future commands // would block on the lock). // 3. Acquire the lock again. If any new tasks were queued during step 2, goto 1. // If not, set mConnector to non-null so future tasks are run immediately, not queued. // // For this to work, all calls to the tethering service must use getConnector(), which // ensures that tasks are added to the queue with the lock held. // // Once mConnector is set to non-null, it will never be null again. If the network stack // process crashes, no recovery is possible. // TODO: evaluate whether it is possible to recover from network stack process crashes // (though in most cases the system will have crashed when the network stack process // crashes). do { final List localWaitQueue; synchronized (mConnectorWaitQueue) { localWaitQueue = new ArrayList<>(mConnectorWaitQueue); mConnectorWaitQueue.clear(); } // Allow more tasks to be added at the end without blocking while draining the queue. for (ConnectorConsumer task : localWaitQueue) { try { task.onConnectorAvailable(connector); } catch (RemoteException e) { // Most likely the network stack process crashed, which is likely to crash the // system. Keep processing other requests but report the error loudly. Log.wtf(TAG, "Error processing request for the tethering connector", e); } } synchronized (mConnectorWaitQueue) { if (mConnectorWaitQueue.size() == 0) { mConnector = connector; return; } } } while (true); } /** * Asynchronously get the ITetheringConnector to execute some operation. * *

If the connector is already available, the operation will be executed on the caller's * thread. Otherwise it will be queued and executed on a worker thread. The operation should be * limited to performing oneway binder calls to minimize differences due to threading. */ private void getConnector(ConnectorConsumer consumer) { final ITetheringConnector connector; synchronized (mConnectorWaitQueue) { connector = mConnector; if (connector == null) { mConnectorWaitQueue.add(consumer); return; } } try { consumer.onConnectorAvailable(connector); } catch (RemoteException e) { throw new IllegalStateException(e); } } private interface RequestHelper { void runRequest(ITetheringConnector connector, IIntResultListener listener); } // Used to dispatch legacy ConnectivityManager methods that expect tethering to be able to // return results and perform operations synchronously. // TODO: remove once there are no callers of these legacy methods. private class RequestDispatcher { private final ConditionVariable mWaiting; public volatile int mRemoteResult; private final IIntResultListener mListener = new IIntResultListener.Stub() { @Override public void onResult(final int resultCode) { mRemoteResult = resultCode; mWaiting.open(); } }; RequestDispatcher() { mWaiting = new ConditionVariable(); } int waitForResult(final RequestHelper request) { getConnector(c -> request.runRequest(c, mListener)); if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) { throw new IllegalStateException("Callback timeout"); } throwIfPermissionFailure(mRemoteResult); return mRemoteResult; } } private static void throwIfPermissionFailure(final int errorCode) { switch (errorCode) { case TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION: throw new SecurityException("No android.permission.TETHER_PRIVILEGED" + " or android.permission.WRITE_SETTINGS permission"); case TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION: throw new SecurityException( "No android.permission.ACCESS_NETWORK_STATE permission"); } } /** * A request for a tethered interface. * * There are two reasons why this doesn't implement CLoseable: * 1. To consistency with the existing EthernetManager.TetheredInterfaceRequest, which is * already released. * 2. This is not synchronous, so it's not useful to use try-with-resources. * * {@hide} */ @SystemApi(client = MODULE_LIBRARIES) @SuppressLint("NotCloseable") public interface TetheredInterfaceRequest { /** * Release the request to tear down tethered interface. */ void release(); } /** * Callback for requestTetheredInterface. * * {@hide} */ @SystemApi(client = MODULE_LIBRARIES) public interface TetheredInterfaceCallback { /** * Called when the tethered interface is available. * @param iface The name of the interface. */ void onAvailable(@NonNull String iface); /** * Called when the tethered interface is now unavailable. */ void onUnavailable(); } private static class TetheringCallbackInternal extends ITetheringEventCallback.Stub { private volatile int mError = TETHER_ERROR_NO_ERROR; private final ConditionVariable mWaitForCallback = new ConditionVariable(); // This object is never garbage collected because the Tethering code running in // the system server always maintains a reference to it for as long as // mCallback is registered. // // Don't keep a strong reference to TetheringManager because otherwise // TetheringManager cannot be garbage collected, and because TetheringManager // stores the Context that it was created from, this will prevent the calling // Activity from being garbage collected as well. private final WeakReference mTetheringMgrRef; TetheringCallbackInternal(final TetheringManager tm) { mTetheringMgrRef = new WeakReference<>(tm); } @Override public void onCallbackStarted(TetheringCallbackStartedParcel parcel) { TetheringManager tetheringMgr = mTetheringMgrRef.get(); if (tetheringMgr != null) { tetheringMgr.mTetheringConfiguration = parcel.config; tetheringMgr.mTetherStatesParcel = parcel.states; mWaitForCallback.open(); } } @Override public void onCallbackStopped(int errorCode) { TetheringManager tetheringMgr = mTetheringMgrRef.get(); if (tetheringMgr != null) { mError = errorCode; mWaitForCallback.open(); } } @Override public void onSupportedTetheringTypes(long supportedBitmap) { } @Override public void onUpstreamChanged(Network network) { } @Override public void onConfigurationChanged(TetheringConfigurationParcel config) { TetheringManager tetheringMgr = mTetheringMgrRef.get(); if (tetheringMgr != null) tetheringMgr.mTetheringConfiguration = config; } @Override public void onTetherStatesChanged(TetherStatesParcel states) { TetheringManager tetheringMgr = mTetheringMgrRef.get(); if (tetheringMgr != null) tetheringMgr.mTetherStatesParcel = states; } @Override public void onTetherClientsChanged(List clients) { } @Override public void onOffloadStatusChanged(int status) { } public void waitForStarted() { mWaitForCallback.block(DEFAULT_TIMEOUT_MS); throwIfPermissionFailure(mError); } } /** * Attempt to tether the named interface. This will setup a dhcp server * on the interface, forward and NAT IP v4 packets and forward DNS requests * to the best active upstream network interface. Note that if no upstream * IP network interface is available, dhcp will still run and traffic will be * allowed between the tethered devices and this device, though upstream net * access will of course fail until an upstream network interface becomes * active. * * @deprecated The only usages is PanService. It uses this for legacy reasons * and will migrate away as soon as possible. * * @param iface the interface name to tether. * @return error a {@code TETHER_ERROR} value indicating success or failure type * * {@hide} */ @Deprecated @SystemApi(client = MODULE_LIBRARIES) public int tether(@NonNull final String iface) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "tether caller:" + callerPkg); final RequestDispatcher dispatcher = new RequestDispatcher(); return dispatcher.waitForResult((connector, listener) -> { try { connector.tether(iface, callerPkg, getAttributionTag(), listener); } catch (RemoteException e) { throw new IllegalStateException(e); } }); } /** * @return the context's attribution tag */ private @Nullable String getAttributionTag() { return mContext.getAttributionTag(); } /** * Stop tethering the named interface. * * @deprecated The only usages is PanService. It uses this for legacy reasons * and will migrate away as soon as possible. * * {@hide} */ @Deprecated @SystemApi(client = MODULE_LIBRARIES) public int untether(@NonNull final String iface) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "untether caller:" + callerPkg); final RequestDispatcher dispatcher = new RequestDispatcher(); return dispatcher.waitForResult((connector, listener) -> { try { connector.untether(iface, callerPkg, getAttributionTag(), listener); } catch (RemoteException e) { throw new IllegalStateException(e); } }); } /** * Attempt to both alter the mode of USB and Tethering of USB. * * @deprecated New clients should not use this API anymore. All clients should use * #startTethering or #stopTethering which encapsulate proper entitlement logic. If the API is * used and an entitlement check is needed, downstream USB tethering will be enabled but will * not have any upstream. * * {@hide} */ @Deprecated @SystemApi(client = MODULE_LIBRARIES) public int setUsbTethering(final boolean enable) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "setUsbTethering caller:" + callerPkg); final RequestDispatcher dispatcher = new RequestDispatcher(); return dispatcher.waitForResult((connector, listener) -> { try { connector.setUsbTethering(enable, callerPkg, getAttributionTag(), listener); } catch (RemoteException e) { throw new IllegalStateException(e); } }); } /** * Indicates that this tethering connection will provide connectivity beyond this device (e.g., * global Internet access). */ public static final int CONNECTIVITY_SCOPE_GLOBAL = 1; /** * Indicates that this tethering connection will only provide local connectivity. */ public static final int CONNECTIVITY_SCOPE_LOCAL = 2; /** * Connectivity scopes for {@link TetheringRequest.Builder#setConnectivityScope}. * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "CONNECTIVITY_SCOPE_", value = { CONNECTIVITY_SCOPE_GLOBAL, CONNECTIVITY_SCOPE_LOCAL, }) public @interface ConnectivityScope {} /** * Use with {@link #startTethering} to specify additional parameters when starting tethering. */ public static final class TetheringRequest implements Parcelable { /** A configuration set for TetheringRequest. */ private final TetheringRequestParcel mRequestParcel; private TetheringRequest(@NonNull final TetheringRequestParcel request) { mRequestParcel = request; } private TetheringRequest(@NonNull Parcel in) { mRequestParcel = in.readParcelable(TetheringRequestParcel.class.getClassLoader()); } @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG) @NonNull public static final Creator CREATOR = new Creator<>() { @Override public TetheringRequest createFromParcel(@NonNull Parcel in) { return new TetheringRequest(in); } @Override public TetheringRequest[] newArray(int size) { return new TetheringRequest[size]; } }; @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG) @Override public int describeContents() { return 0; } @FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG) @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeParcelable(mRequestParcel, flags); } /** Builder used to create TetheringRequest. */ public static class Builder { private final TetheringRequestParcel mBuilderParcel; /** Default constructor of Builder. */ public Builder(@TetheringType final int type) { mBuilderParcel = new TetheringRequestParcel(); mBuilderParcel.tetheringType = type; mBuilderParcel.localIPv4Address = null; mBuilderParcel.staticClientAddress = null; mBuilderParcel.exemptFromEntitlementCheck = false; mBuilderParcel.showProvisioningUi = true; mBuilderParcel.connectivityScope = getDefaultConnectivityScope(type); } /** * Configure tethering with static IPv4 assignment. * * A DHCP server will be started, but will only be able to offer the client address. * The two addresses must be in the same prefix. * * @param localIPv4Address The preferred local IPv4 link address to use. * @param clientAddress The static client address. */ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) @NonNull public Builder setStaticIpv4Addresses(@NonNull final LinkAddress localIPv4Address, @NonNull final LinkAddress clientAddress) { Objects.requireNonNull(localIPv4Address); Objects.requireNonNull(clientAddress); if (!checkStaticAddressConfiguration(localIPv4Address, clientAddress)) { throw new IllegalArgumentException("Invalid server or client addresses"); } mBuilderParcel.localIPv4Address = localIPv4Address; mBuilderParcel.staticClientAddress = clientAddress; return this; } /** Start tethering without entitlement checks. */ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) @NonNull public Builder setExemptFromEntitlementCheck(boolean exempt) { mBuilderParcel.exemptFromEntitlementCheck = exempt; return this; } /** * If an entitlement check is needed, sets whether to show the entitlement UI or to * perform a silent entitlement check. By default, the entitlement UI is shown. */ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) @NonNull public Builder setShouldShowEntitlementUi(boolean showUi) { mBuilderParcel.showProvisioningUi = showUi; return this; } /** * Sets the connectivity scope to be provided by this tethering downstream. */ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) @NonNull public Builder setConnectivityScope(@ConnectivityScope int scope) { if (!checkConnectivityScope(mBuilderParcel.tetheringType, scope)) { throw new IllegalArgumentException("Invalid connectivity scope " + scope); } mBuilderParcel.connectivityScope = scope; return this; } /** Build {@link TetheringRequest} with the currently set configuration. */ @NonNull public TetheringRequest build() { return new TetheringRequest(mBuilderParcel); } } /** * Get the local IPv4 address, if one was configured with * {@link Builder#setStaticIpv4Addresses}. */ @Nullable public LinkAddress getLocalIpv4Address() { return mRequestParcel.localIPv4Address; } /** * Get the static IPv4 address of the client, if one was configured with * {@link Builder#setStaticIpv4Addresses}. */ @Nullable public LinkAddress getClientStaticIpv4Address() { return mRequestParcel.staticClientAddress; } /** Get tethering type. */ @TetheringType public int getTetheringType() { return mRequestParcel.tetheringType; } /** Get connectivity type */ @ConnectivityScope public int getConnectivityScope() { return mRequestParcel.connectivityScope; } /** Check if exempt from entitlement check. */ public boolean isExemptFromEntitlementCheck() { return mRequestParcel.exemptFromEntitlementCheck; } /** Check if show entitlement ui. */ public boolean getShouldShowEntitlementUi() { return mRequestParcel.showProvisioningUi; } /** * Check whether the two addresses are ipv4 and in the same prefix. * @hide */ public static boolean checkStaticAddressConfiguration( @NonNull final LinkAddress localIPv4Address, @NonNull final LinkAddress clientAddress) { return localIPv4Address.getPrefixLength() == clientAddress.getPrefixLength() && localIPv4Address.isIpv4() && clientAddress.isIpv4() && new IpPrefix(localIPv4Address.toString()).equals( new IpPrefix(clientAddress.toString())); } /** * Returns the default connectivity scope for the given tethering type. Usually this is * CONNECTIVITY_SCOPE_GLOBAL, except for NCM which for historical reasons defaults to local. * @hide */ public static @ConnectivityScope int getDefaultConnectivityScope(int tetheringType) { return tetheringType != TETHERING_NCM ? CONNECTIVITY_SCOPE_GLOBAL : CONNECTIVITY_SCOPE_LOCAL; } /** * Checks whether the requested connectivity scope is allowed. * @hide */ private static boolean checkConnectivityScope(int type, int scope) { if (scope == CONNECTIVITY_SCOPE_GLOBAL) return true; return type == TETHERING_USB || type == TETHERING_ETHERNET || type == TETHERING_NCM; } /** * Get a TetheringRequestParcel from the configuration * @hide */ public TetheringRequestParcel getParcel() { return mRequestParcel; } /** String of TetheringRequest detail. */ public String toString() { return "TetheringRequest [ type= " + mRequestParcel.tetheringType + ", localIPv4Address= " + mRequestParcel.localIPv4Address + ", staticClientAddress= " + mRequestParcel.staticClientAddress + ", exemptFromEntitlementCheck= " + mRequestParcel.exemptFromEntitlementCheck + ", showProvisioningUi= " + mRequestParcel.showProvisioningUi + " ]"; } } /** * Callback for use with {@link #startTethering} to find out whether tethering succeeded. */ public interface StartTetheringCallback { /** * Called when tethering has been successfully started. */ default void onTetheringStarted() {} /** * Called when starting tethering failed. * * @param error The error that caused the failure. */ default void onTetheringFailed(@StartTetheringError final int error) {} } /** * Starts tethering and runs tether provisioning for the given type if needed. If provisioning * fails, stopTethering will be called automatically. * *

Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will * fail if a tethering entitlement check is required. * * @param request a {@link TetheringRequest} which can specify the preferred configuration. * @param executor {@link Executor} to specify the thread upon which the callback of * TetheringRequest will be invoked. * @param callback A callback that will be called to indicate the success status of the * tethering start request. */ @RequiresPermission(anyOf = { android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS }) public void startTethering(@NonNull final TetheringRequest request, @NonNull final Executor executor, @NonNull final StartTetheringCallback callback) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "startTethering caller:" + callerPkg); final IIntResultListener listener = new IIntResultListener.Stub() { @Override public void onResult(final int resultCode) { executor.execute(() -> { if (resultCode == TETHER_ERROR_NO_ERROR) { callback.onTetheringStarted(); } else { callback.onTetheringFailed(resultCode); } }); } }; getConnector(c -> c.startTethering(request.getParcel(), callerPkg, getAttributionTag(), listener)); } /** * Starts tethering and runs tether provisioning for the given type if needed. If provisioning * fails, stopTethering will be called automatically. * *

Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will * fail if a tethering entitlement check is required. * * @param type The tethering type, on of the {@code TetheringManager#TETHERING_*} constants. * @param executor {@link Executor} to specify the thread upon which the callback of * TetheringRequest will be invoked. * @hide */ @RequiresPermission(anyOf = { android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS }) @SystemApi(client = MODULE_LIBRARIES) public void startTethering(int type, @NonNull final Executor executor, @NonNull final StartTetheringCallback callback) { startTethering(new TetheringRequest.Builder(type).build(), executor, callback); } /** * Stops tethering for the given type. Also cancels any provisioning rechecks for that type if * applicable. * *

Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will * fail if a tethering entitlement check is required. */ @RequiresPermission(anyOf = { android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS }) public void stopTethering(@TetheringType final int type) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "stopTethering caller:" + callerPkg); getConnector(c -> c.stopTethering(type, callerPkg, getAttributionTag(), new IIntResultListener.Stub() { @Override public void onResult(int resultCode) { // TODO: provide an API to obtain result // This has never been possible as stopTethering has always been void and never // taken a callback object. The only indication that callers have is if the call // results in a TETHER_STATE_CHANGE broadcast. } })); } /** * Callback for use with {@link #getLatestTetheringEntitlementResult} to find out whether * entitlement succeeded. */ public interface OnTetheringEntitlementResultListener { /** * Called to notify entitlement result. * * @param resultCode an int value of entitlement result. It may be one of * {@link #TETHER_ERROR_NO_ERROR}, * {@link #TETHER_ERROR_PROVISIONING_FAILED}, or * {@link #TETHER_ERROR_ENTITLEMENT_UNKNOWN}. */ void onTetheringEntitlementResult(@EntitlementResult int result); } /** * Request the latest value of the tethering entitlement check. * *

This method will only return the latest entitlement result if it is available. If no * cached entitlement result is available, and {@code showEntitlementUi} is false, * {@link #TETHER_ERROR_ENTITLEMENT_UNKNOWN} will be returned. If {@code showEntitlementUi} is * true, entitlement will be run. * *

Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will * fail if a tethering entitlement check is required. * * @param type the downstream type of tethering. Must be one of {@code #TETHERING_*} constants. * @param showEntitlementUi a boolean indicating whether to check result for the UI-based * entitlement check or the silent entitlement check. * @param executor the executor on which callback will be invoked. * @param listener an {@link OnTetheringEntitlementResultListener} which will be called to * notify the caller of the result of entitlement check. The listener may be called zero * or one time. */ @RequiresPermission(anyOf = { android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS }) public void requestLatestTetheringEntitlementResult(@TetheringType int type, boolean showEntitlementUi, @NonNull Executor executor, @NonNull final OnTetheringEntitlementResultListener listener) { if (listener == null) { throw new IllegalArgumentException( "OnTetheringEntitlementResultListener cannot be null."); } ResultReceiver wrappedListener = new ResultReceiver(null /* handler */) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { executor.execute(() -> { listener.onTetheringEntitlementResult(resultCode); }); } }; requestLatestTetheringEntitlementResult(type, wrappedListener, showEntitlementUi); } /** * Helper function of #requestLatestTetheringEntitlementResult to remain backwards compatible * with ConnectivityManager#getLatestTetheringEntitlementResult * * {@hide} */ // TODO: improve the usage of ResultReceiver, b/145096122 @SystemApi(client = MODULE_LIBRARIES) public void requestLatestTetheringEntitlementResult(@TetheringType final int type, @NonNull final ResultReceiver receiver, final boolean showEntitlementUi) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "getLatestTetheringEntitlementResult caller:" + callerPkg); getConnector(c -> c.requestLatestTetheringEntitlementResult( type, receiver, showEntitlementUi, callerPkg, getAttributionTag())); } /** * Callback for use with {@link registerTetheringEventCallback} to find out tethering * upstream status. */ public interface TetheringEventCallback { /** * Called when tethering supported status changed. * *

This callback will be called immediately after the callback is * registered, and never be called if there is changes afterward. * *

Tethering may be disabled via system properties, device configuration, or device * policy restrictions. * * @param supported whether any tethering type is supported. */ default void onTetheringSupported(boolean supported) {} /** * Called when tethering supported status changed. * *

This will be called immediately after the callback is registered, and may be called * multiple times later upon changes. * *

Tethering may be disabled via system properties, device configuration, or device * policy restrictions. * * @param supportedTypes a set of @TetheringType which is supported. * @hide */ default void onSupportedTetheringTypes(@NonNull Set supportedTypes) {} /** * Called when tethering upstream changed. * *

This will be called immediately after the callback is registered, and may be called * multiple times later upon changes. * * @param network the {@link Network} of tethering upstream. Null means tethering doesn't * have any upstream. */ default void onUpstreamChanged(@Nullable Network network) {} /** * Called when there was a change in tethering interface regular expressions. * *

This will be called immediately after the callback is registered, and may be called * multiple times later upon changes. * @param reg The new regular expressions. * * @deprecated New clients should use the callbacks with {@link TetheringInterface} which * has the mapping between tethering type and interface. InterfaceRegex is no longer needed * to determine the mapping of tethering type and interface. * * @hide */ @Deprecated @SystemApi(client = MODULE_LIBRARIES) default void onTetherableInterfaceRegexpsChanged(@NonNull TetheringInterfaceRegexps reg) {} /** * Called when there was a change in the list of tetherable interfaces. Tetherable * interface means this interface is available and can be used for tethering. * *

This will be called immediately after the callback is registered, and may be called * multiple times later upon changes. * @param interfaces The list of tetherable interface names. */ default void onTetherableInterfacesChanged(@NonNull List interfaces) {} /** * Called when there was a change in the list of tetherable interfaces. Tetherable * interface means this interface is available and can be used for tethering. * *

This will be called immediately after the callback is registered, and may be called * multiple times later upon changes. * @param interfaces The set of TetheringInterface of currently tetherable interface. */ default void onTetherableInterfacesChanged(@NonNull Set interfaces) { // By default, the new callback calls the old callback, so apps // implementing the old callback just work. onTetherableInterfacesChanged(toIfaces(interfaces)); } /** * Called when there was a change in the list of tethered interfaces. * *

This will be called immediately after the callback is registered, and may be called * multiple times later upon changes. * @param interfaces The lit of 0 or more String of currently tethered interface names. */ default void onTetheredInterfacesChanged(@NonNull List interfaces) {} /** * Called when there was a change in the list of tethered interfaces. * *

This will be called immediately after the callback is registered, and may be called * multiple times later upon changes. * @param interfaces The set of 0 or more TetheringInterface of currently tethered * interface. */ default void onTetheredInterfacesChanged(@NonNull Set interfaces) { // By default, the new callback calls the old callback, so apps // implementing the old callback just work. onTetheredInterfacesChanged(toIfaces(interfaces)); } /** * Called when there was a change in the list of local-only interfaces. * *

This will be called immediately after the callback is registered, and may be called * multiple times later upon changes. * @param interfaces The list of 0 or more String of active local-only interface names. */ default void onLocalOnlyInterfacesChanged(@NonNull List interfaces) {} /** * Called when there was a change in the list of local-only interfaces. * *

This will be called immediately after the callback is registered, and may be called * multiple times later upon changes. * @param interfaces The set of 0 or more TetheringInterface of active local-only * interface. */ default void onLocalOnlyInterfacesChanged(@NonNull Set interfaces) { // By default, the new callback calls the old callback, so apps // implementing the old callback just work. onLocalOnlyInterfacesChanged(toIfaces(interfaces)); } /** * Called when an error occurred configuring tethering. * *

This will be called immediately after the callback is registered if the latest status * on the interface is an error, and may be called multiple times later upon changes. * @param ifName Name of the interface. * @param error One of {@code TetheringManager#TETHER_ERROR_*}. */ default void onError(@NonNull String ifName, @TetheringIfaceError int error) {} /** * Called when an error occurred configuring tethering. * *

This will be called immediately after the callback is registered if the latest status * on the interface is an error, and may be called multiple times later upon changes. * @param iface The interface that experienced the error. * @param error One of {@code TetheringManager#TETHER_ERROR_*}. */ default void onError(@NonNull TetheringInterface iface, @TetheringIfaceError int error) { // By default, the new callback calls the old callback, so apps // implementing the old callback just work. onError(iface.getInterface(), error); } /** * Called when the list of tethered clients changes. * *

This callback provides best-effort information on connected clients based on state * known to the system, however the list cannot be completely accurate (and should not be * used for security purposes). For example, clients behind a bridge and using static IP * assignments are not visible to the tethering device; or even when using DHCP, such * clients may still be reported by this callback after disconnection as the system cannot * determine if they are still connected. * @param clients The new set of tethered clients; the collection is not ordered. */ default void onClientsChanged(@NonNull Collection clients) {} /** * Called when tethering offload status changes. * *

This will be called immediately after the callback is registered. * @param status The offload status. */ default void onOffloadStatusChanged(@TetherOffloadStatus int status) {} } /** * Covert DownStreamInterface collection to interface String array list. Internal use only. * * @hide */ public static ArrayList toIfaces(Collection tetherIfaces) { final ArrayList ifaces = new ArrayList<>(); for (TetheringInterface tether : tetherIfaces) { ifaces.add(tether.getInterface()); } return ifaces; } private static String[] toIfaces(TetheringInterface[] tetherIfaces) { final String[] ifaces = new String[tetherIfaces.length]; for (int i = 0; i < tetherIfaces.length; i++) { ifaces[i] = tetherIfaces[i].getInterface(); } return ifaces; } /** * Regular expressions used to identify tethering interfaces. * * @deprecated Instead of using regex to determine tethering type. New client could use the * callbacks with {@link TetheringInterface} which has the mapping of type and interface. * @hide */ @Deprecated @SystemApi(client = MODULE_LIBRARIES) public static class TetheringInterfaceRegexps { private final String[] mTetherableBluetoothRegexs; private final String[] mTetherableUsbRegexs; private final String[] mTetherableWifiRegexs; /** @hide */ public TetheringInterfaceRegexps(@NonNull String[] tetherableBluetoothRegexs, @NonNull String[] tetherableUsbRegexs, @NonNull String[] tetherableWifiRegexs) { mTetherableBluetoothRegexs = tetherableBluetoothRegexs.clone(); mTetherableUsbRegexs = tetherableUsbRegexs.clone(); mTetherableWifiRegexs = tetherableWifiRegexs.clone(); } @NonNull public List getTetherableBluetoothRegexs() { return Collections.unmodifiableList(Arrays.asList(mTetherableBluetoothRegexs)); } @NonNull public List getTetherableUsbRegexs() { return Collections.unmodifiableList(Arrays.asList(mTetherableUsbRegexs)); } @NonNull public List getTetherableWifiRegexs() { return Collections.unmodifiableList(Arrays.asList(mTetherableWifiRegexs)); } @Override public int hashCode() { return Objects.hash( Arrays.hashCode(mTetherableBluetoothRegexs), Arrays.hashCode(mTetherableUsbRegexs), Arrays.hashCode(mTetherableWifiRegexs)); } @Override public boolean equals(@Nullable Object obj) { if (!(obj instanceof TetheringInterfaceRegexps)) return false; final TetheringInterfaceRegexps other = (TetheringInterfaceRegexps) obj; return Arrays.equals(mTetherableBluetoothRegexs, other.mTetherableBluetoothRegexs) && Arrays.equals(mTetherableUsbRegexs, other.mTetherableUsbRegexs) && Arrays.equals(mTetherableWifiRegexs, other.mTetherableWifiRegexs); } } /** * Start listening to tethering change events. Any new added callback will receive the last * tethering status right away. If callback is registered, * {@link TetheringEventCallback#onUpstreamChanged} will immediately be called. If tethering * has no upstream or disabled, the argument of callback will be null. The same callback object * cannot be registered twice. * * @param executor the executor on which callback will be invoked. * @param callback the callback to be called when tethering has change events. */ @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull Executor executor, @NonNull TetheringEventCallback callback) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "registerTetheringEventCallback caller:" + callerPkg); synchronized (mTetheringEventCallbacks) { if (mTetheringEventCallbacks.containsKey(callback)) { throw new IllegalArgumentException("callback was already registered."); } final ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() { // Only accessed with a lock on this object private final HashMap mErrorStates = new HashMap<>(); private TetheringInterface[] mLastTetherableInterfaces = null; private TetheringInterface[] mLastTetheredInterfaces = null; private TetheringInterface[] mLastLocalOnlyInterfaces = null; @Override public void onUpstreamChanged(Network network) throws RemoteException { executor.execute(() -> { callback.onUpstreamChanged(network); }); } private synchronized void sendErrorCallbacks(final TetherStatesParcel newStates) { for (int i = 0; i < newStates.erroredIfaceList.length; i++) { final TetheringInterface tetherIface = newStates.erroredIfaceList[i]; final Integer lastError = mErrorStates.get(tetherIface); final int newError = newStates.lastErrorList[i]; if (newError != TETHER_ERROR_NO_ERROR && !Objects.equals(lastError, newError)) { callback.onError(tetherIface, newError); } mErrorStates.put(tetherIface, newError); } } private synchronized void maybeSendTetherableIfacesChangedCallback( final TetherStatesParcel newStates) { if (Arrays.equals(mLastTetherableInterfaces, newStates.availableList)) return; mLastTetherableInterfaces = newStates.availableList.clone(); callback.onTetherableInterfacesChanged( Collections.unmodifiableSet((new ArraySet(mLastTetherableInterfaces)))); } private synchronized void maybeSendTetheredIfacesChangedCallback( final TetherStatesParcel newStates) { if (Arrays.equals(mLastTetheredInterfaces, newStates.tetheredList)) return; mLastTetheredInterfaces = newStates.tetheredList.clone(); callback.onTetheredInterfacesChanged( Collections.unmodifiableSet((new ArraySet(mLastTetheredInterfaces)))); } private synchronized void maybeSendLocalOnlyIfacesChangedCallback( final TetherStatesParcel newStates) { if (Arrays.equals(mLastLocalOnlyInterfaces, newStates.localOnlyList)) return; mLastLocalOnlyInterfaces = newStates.localOnlyList.clone(); callback.onLocalOnlyInterfacesChanged( Collections.unmodifiableSet((new ArraySet(mLastLocalOnlyInterfaces)))); } // Called immediately after the callbacks are registered. @Override public void onCallbackStarted(TetheringCallbackStartedParcel parcel) { executor.execute(() -> { callback.onSupportedTetheringTypes(unpackBits(parcel.supportedTypes)); callback.onTetheringSupported(parcel.supportedTypes != 0); callback.onUpstreamChanged(parcel.upstreamNetwork); sendErrorCallbacks(parcel.states); sendRegexpsChanged(parcel.config); maybeSendTetherableIfacesChangedCallback(parcel.states); maybeSendTetheredIfacesChangedCallback(parcel.states); maybeSendLocalOnlyIfacesChangedCallback(parcel.states); callback.onClientsChanged(parcel.tetheredClients); callback.onOffloadStatusChanged(parcel.offloadStatus); }); } @Override public void onCallbackStopped(int errorCode) { executor.execute(() -> { throwIfPermissionFailure(errorCode); }); } @Override public void onSupportedTetheringTypes(long supportedBitmap) { executor.execute(() -> { callback.onSupportedTetheringTypes(unpackBits(supportedBitmap)); }); } private void sendRegexpsChanged(TetheringConfigurationParcel parcel) { callback.onTetherableInterfaceRegexpsChanged(new TetheringInterfaceRegexps( parcel.tetherableBluetoothRegexs, parcel.tetherableUsbRegexs, parcel.tetherableWifiRegexs)); } @Override public void onConfigurationChanged(TetheringConfigurationParcel config) { executor.execute(() -> sendRegexpsChanged(config)); } @Override public void onTetherStatesChanged(TetherStatesParcel states) { executor.execute(() -> { sendErrorCallbacks(states); maybeSendTetherableIfacesChangedCallback(states); maybeSendTetheredIfacesChangedCallback(states); maybeSendLocalOnlyIfacesChangedCallback(states); }); } @Override public void onTetherClientsChanged(final List clients) { executor.execute(() -> callback.onClientsChanged(clients)); } @Override public void onOffloadStatusChanged(final int status) { executor.execute(() -> callback.onOffloadStatusChanged(status)); } }; getConnector(c -> c.registerTetheringEventCallback(remoteCallback, callerPkg)); mTetheringEventCallbacks.put(callback, remoteCallback); } } /** * Unpack bitmap to a set of bit position intergers. * @hide */ public static ArraySet unpackBits(long val) { final ArraySet result = new ArraySet<>(Long.bitCount(val)); int bitPos = 0; while (val != 0) { if ((val & 1) == 1) result.add(bitPos); val = val >>> 1; bitPos++; } return result; } /** * Remove tethering event callback previously registered with * {@link #registerTetheringEventCallback}. * * @param callback previously registered callback. */ @RequiresPermission(anyOf = { Manifest.permission.TETHER_PRIVILEGED, Manifest.permission.ACCESS_NETWORK_STATE }) public void unregisterTetheringEventCallback(@NonNull final TetheringEventCallback callback) { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "unregisterTetheringEventCallback caller:" + callerPkg); synchronized (mTetheringEventCallbacks) { ITetheringEventCallback remoteCallback = mTetheringEventCallbacks.remove(callback); if (remoteCallback == null) { throw new IllegalArgumentException("callback was not registered."); } getConnector(c -> c.unregisterTetheringEventCallback(remoteCallback, callerPkg)); } } /** * Get a more detailed error code after a Tethering or Untethering * request asynchronously failed. * * @param iface The name of the interface of interest * @return error The error code of the last error tethering or untethering the named * interface * @hide */ @SystemApi(client = MODULE_LIBRARIES) public int getLastTetherError(@NonNull final String iface) { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR; int i = 0; for (TetheringInterface errored : mTetherStatesParcel.erroredIfaceList) { if (iface.equals(errored.getInterface())) return mTetherStatesParcel.lastErrorList[i]; i++; } return TETHER_ERROR_NO_ERROR; } /** * 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. * * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable usb interfaces. * @hide */ @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableUsbRegexs() { mCallback.waitForStarted(); return mTetheringConfiguration.tetherableUsbRegexs; } /** * Get the list of regular expressions that define any tetherable * Wifi network interfaces. If Wifi tethering is not supported by the * device, this list should be empty. * * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable wifi interfaces. * @hide */ @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableWifiRegexs() { mCallback.waitForStarted(); return mTetheringConfiguration.tetherableWifiRegexs; } /** * Get the list of regular expressions that define any tetherable * Bluetooth network interfaces. If Bluetooth tethering is not supported by the * device, this list should be empty. * * @return an array of 0 or more regular expression Strings defining * what interfaces are considered tetherable bluetooth interfaces. * @hide */ @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableBluetoothRegexs() { mCallback.waitForStarted(); return mTetheringConfiguration.tetherableBluetoothRegexs; } /** * Get the set of tetherable, available interfaces. This list is limited by * device configuration and current interface existence. * * @return an array of 0 or more Strings of tetherable interface names. * @hide */ @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetherableIfaces() { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return new String[0]; return toIfaces(mTetherStatesParcel.availableList); } /** * Get the set of tethered interfaces. * * @return an array of 0 or more String of currently tethered interface names. * @hide */ @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetheredIfaces() { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return new String[0]; return toIfaces(mTetherStatesParcel.tetheredList); } /** * Get the set of interface names which attempted to tether but * failed. Re-attempting to tether may cause them to reset to the Tethered * state. Alternatively, causing the interface to be destroyed and recreated * may cause them to reset to the available state. * {@link TetheringManager#getLastTetherError} can be used to get more * information on the cause of the errors. * * @return an array of 0 or more String indicating the interface names * which failed to tether. * @hide */ @SystemApi(client = MODULE_LIBRARIES) public @NonNull String[] getTetheringErroredIfaces() { mCallback.waitForStarted(); if (mTetherStatesParcel == null) return new String[0]; return toIfaces(mTetherStatesParcel.erroredIfaceList); } /** * Get the set of tethered dhcp ranges. * * @deprecated This API just return the default value which is not used in DhcpServer. * @hide */ @Deprecated public @NonNull String[] getTetheredDhcpRanges() { mCallback.waitForStarted(); return mTetheringConfiguration.legacyDhcpRanges; } /** * Check if the device allows for tethering. It may be disabled via * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or * due to device configuration. * * @return a boolean - {@code true} indicating Tethering is supported. * @hide */ @SystemApi(client = MODULE_LIBRARIES) public boolean isTetheringSupported() { final String callerPkg = mContext.getOpPackageName(); return isTetheringSupported(callerPkg); } /** * Check if the device allows for tethering. It may be disabled via {@code ro.tether.denied} * system property, Settings.TETHER_SUPPORTED or due to device configuration. This is useful * for system components that query this API on behalf of an app. In particular, Bluetooth * has @UnsupportedAppUsage calls that will let apps turn on bluetooth tethering if they have * the right permissions, but such an app needs to know whether it can (permissions as well * as support from the device) turn on tethering in the first place to show the appropriate UI. * * @param callerPkg The caller package name, if it is not matching the calling uid, * SecurityException would be thrown. * @return a boolean - {@code true} indicating Tethering is supported. * @hide */ @SystemApi(client = MODULE_LIBRARIES) public boolean isTetheringSupported(@NonNull final String callerPkg) { final RequestDispatcher dispatcher = new RequestDispatcher(); final int ret = dispatcher.waitForResult((connector, listener) -> { try { connector.isTetheringSupported(callerPkg, getAttributionTag(), listener); } catch (RemoteException e) { throw new IllegalStateException(e); } }); return ret == TETHER_ERROR_NO_ERROR; } /** * Stop all active tethering. * *

Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will * fail if a tethering entitlement check is required. */ @RequiresPermission(anyOf = { android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS }) public void stopAllTethering() { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "stopAllTethering caller:" + callerPkg); getConnector(c -> c.stopAllTethering(callerPkg, getAttributionTag(), new IIntResultListener.Stub() { @Override public void onResult(int resultCode) { // TODO: add an API parameter to send result to caller. // This has never been possible as stopAllTethering has always been void // and never taken a callback object. The only indication that callers have // is if the call results in a TETHER_STATE_CHANGE broadcast. } })); } /** * Whether to treat networks that have TRANSPORT_TEST as Tethering upstreams. The effects of * this method apply to any test networks that are already present on the system. * * @throws SecurityException If the caller doesn't have the NETWORK_SETTINGS permission. * @hide */ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setPreferTestNetworks(final boolean prefer) { Log.i(TAG, "setPreferTestNetworks caller: " + mContext.getOpPackageName()); final RequestDispatcher dispatcher = new RequestDispatcher(); final int ret = dispatcher.waitForResult((connector, listener) -> { try { connector.setPreferTestNetworks(prefer, listener); } catch (RemoteException e) { throw new IllegalStateException(e); } }); } }