| /* |
| * 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. |
| * <p> 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<ConnectorConsumer> mConnectorWaitQueue = new ArrayList<>(); |
| private final Supplier<IBinder> mConnectorSupplier; |
| |
| private final TetheringCallbackInternal mCallback; |
| private final Context mContext; |
| private final ArrayMap<TetheringEventCallback, ITetheringEventCallback> |
| 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<IBinder> 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<ConnectorConsumer> 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. |
| * |
| * <p>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<TetheringManager> 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<TetheredClient> 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<TetheringRequest> 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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>This callback will be called immediately after the callback is |
| * registered, and never be called if there is changes afterward. |
| * |
| * <p>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. |
| * |
| * <p>This will be called immediately after the callback is registered, and may be called |
| * multiple times later upon changes. |
| * |
| * <p>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<Integer> supportedTypes) {} |
| |
| /** |
| * Called when tethering upstream changed. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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<String> 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. |
| * |
| * <p>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<TetheringInterface> 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. |
| * |
| * <p>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<String> interfaces) {} |
| |
| /** |
| * Called when there was a change in the list of tethered interfaces. |
| * |
| * <p>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<TetheringInterface> 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. |
| * |
| * <p>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<String> interfaces) {} |
| |
| /** |
| * Called when there was a change in the list of local-only interfaces. |
| * |
| * <p>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<TetheringInterface> 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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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<TetheredClient> clients) {} |
| |
| /** |
| * Called when tethering offload status changes. |
| * |
| * <p>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<String> toIfaces(Collection<TetheringInterface> tetherIfaces) { |
| final ArrayList<String> 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<String> getTetherableBluetoothRegexs() { |
| return Collections.unmodifiableList(Arrays.asList(mTetherableBluetoothRegexs)); |
| } |
| |
| @NonNull |
| public List<String> getTetherableUsbRegexs() { |
| return Collections.unmodifiableList(Arrays.asList(mTetherableUsbRegexs)); |
| } |
| |
| @NonNull |
| public List<String> 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<TetheringInterface, Integer> 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<TetheredClient> 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<Integer> unpackBits(long val) { |
| final ArraySet<Integer> 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. |
| * |
| * <p>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); |
| } |
| }); |
| } |
| } |