/* * 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.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.content.Context; import android.os.Bundle; import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.Log; import com.android.internal.annotations.GuardedBy; 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.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
@SystemApi(client = MODULE_LIBRARIES)
@TestApi
public class TetheringManager {
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 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 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");
}
}
private class TetheringCallbackInternal extends ITetheringEventCallback.Stub {
private volatile int mError = TETHER_ERROR_NO_ERROR;
private final ConditionVariable mWaitForCallback = new ConditionVariable();
@Override
public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
mTetheringConfiguration = parcel.config;
mTetherStatesParcel = parcel.states;
mWaitForCallback.open();
}
@Override
public void onCallbackStopped(int errorCode) {
mError = errorCode;
mWaitForCallback.open();
}
@Override
public void onUpstreamChanged(Network network) { }
@Override
public void onConfigurationChanged(TetheringConfigurationParcel config) {
mTetheringConfiguration = config;
}
@Override
public void onTetherStatesChanged(TetherStatesParcel states) {
mTetherStatesParcel = states;
}
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, listener);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
});
}
/**
* 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, listener);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
});
}
/**
* Attempt to both alter the mode of USB and Tethering of USB.
*
* @deprecated New client 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, listener);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
});
}
/**
* Use with {@link #startTethering} to specify additional parameters when starting tethering.
*/
public static class TetheringRequest {
/** A configuration set for TetheringRequest. */
private final TetheringRequestParcel mRequestParcel;
private TetheringRequest(final TetheringRequestParcel request) {
mRequestParcel = request;
}
/** Builder used to create TetheringRequest. */
public static class Builder {
private final TetheringRequestParcel mBuilderParcel;
/** Default constructor of Builder. */
public Builder(final int type) {
mBuilderParcel = new TetheringRequestParcel();
mBuilderParcel.tetheringType = type;
mBuilderParcel.localIPv4Address = null;
mBuilderParcel.exemptFromEntitlementCheck = false;
mBuilderParcel.showProvisioningUi = true;
}
/**
* Configure tethering with static IPv4 assignment (with DHCP disabled).
*
* @param localIPv4Address The preferred local IPv4 address to use.
*/
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
@NonNull
public Builder useStaticIpv4Addresses(@NonNull final LinkAddress localIPv4Address) {
mBuilderParcel.localIPv4Address = localIPv4Address;
return this;
}
/** Start tethering without entitlement checks. */
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
@NonNull
public Builder setExemptFromEntitlementCheck(boolean exempt) {
mBuilderParcel.exemptFromEntitlementCheck = exempt;
return this;
}
/** Start tethering without showing the provisioning UI. */
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
@NonNull
public Builder setSilentProvisioning(boolean silent) {
mBuilderParcel.showProvisioningUi = silent;
return this;
}
/** Build {@link TetheringRequest] with the currently set configuration. */
@NonNull
public TetheringRequest build() {
return new TetheringRequest(mBuilderParcel);
}
}
/**
* 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
+ ", exemptFromEntitlementCheck= "
+ mRequestParcel.exemptFromEntitlementCheck + ", showProvisioningUi= "
+ mRequestParcel.showProvisioningUi + " ]";
}
}
/**
* Callback for use with {@link #startTethering} to find out whether tethering succeeded.
*/
public abstract static class StartTetheringCallback {
/**
* Called when tethering has been successfully started.
*/
public void onTetheringStarted() {}
/**
* Called when starting tethering failed.
*
* @param resultCode One of the {@code TETHER_ERROR_*} constants.
*/
public void onTetheringFailed(final int resultCode) {}
}
/**
* 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, 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.
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.TETHER_PRIVILEGED,
android.Manifest.permission.WRITE_SETTINGS
})
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(final int type) {
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "stopTethering caller:" + callerPkg);
getConnector(c -> c.stopTethering(type, callerPkg, 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_PROVISION_FAILED}, or
* {@link #TETHER_ERROR_ENTITLEMENT_UNKNOWN}.
*/
void onTetheringEntitlementResult(int resultCode);
}
/**
* 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 run UI-based 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(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(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));
}
/**
* Callback for use with {@link registerTetheringEventCallback} to find out tethering
* upstream status.
*/
public abstract static class TetheringEventCallback {
/**
* 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 supported The new supported status
*/
public void onTetheringSupported(boolean supported) {}
/**
* 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.
*/
public 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 Referencing interfaces by regular expressions is a deprecated mechanism.
*/
@Deprecated
public void onTetherableInterfaceRegexpsChanged(@NonNull TetheringInterfaceRegexps reg) {}
/**
* Called when there was a change in the list of tetherable 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 tetherable interfaces.
*/
public void onTetherableInterfacesChanged(@NonNull List This will be called immediately after the callback is registered, and may be called
* multiple times later upon changes.
* @param interfaces The list of tethered interfaces.
*/
public void onTetheredInterfacesChanged(@NonNull List 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_*}.
*/
public void onError(@NonNull String ifName, int 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.
*/
public void onClientsChanged(@NonNull Collection 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, 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.
}
}));
}
}