|
|
|
|
@@ -15,94 +15,141 @@
|
|
|
|
|
*/
|
|
|
|
|
package android.net;
|
|
|
|
|
|
|
|
|
|
import static android.Manifest.permission.NETWORK_STACK;
|
|
|
|
|
import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
|
|
|
|
|
import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL;
|
|
|
|
|
|
|
|
|
|
import android.annotation.NonNull;
|
|
|
|
|
import android.annotation.Nullable;
|
|
|
|
|
import android.net.util.SharedLog;
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.net.ConnectivityManager.OnTetheringEventCallback;
|
|
|
|
|
import android.os.ConditionVariable;
|
|
|
|
|
import android.os.IBinder;
|
|
|
|
|
import android.os.RemoteCallbackList;
|
|
|
|
|
import android.os.RemoteException;
|
|
|
|
|
import android.os.ResultReceiver;
|
|
|
|
|
import android.util.Slog;
|
|
|
|
|
import android.util.ArrayMap;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
|
|
import com.android.internal.annotations.GuardedBy;
|
|
|
|
|
|
|
|
|
|
import java.io.PrintWriter;
|
|
|
|
|
import java.util.StringJoiner;
|
|
|
|
|
import java.util.concurrent.Executor;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Service used to communicate with the tethering, which is running in a separate module.
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
// TODO: make it @SystemApi
|
|
|
|
|
public class TetheringManager {
|
|
|
|
|
private static final String TAG = TetheringManager.class.getSimpleName();
|
|
|
|
|
private static final int DEFAULT_TIMEOUT_MS = 60_000;
|
|
|
|
|
|
|
|
|
|
private static TetheringManager sInstance;
|
|
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
|
private ITetheringConnector mConnector;
|
|
|
|
|
private TetherInternalCallback mCallback;
|
|
|
|
|
private Network mTetherUpstream;
|
|
|
|
|
private final ITetheringConnector mConnector;
|
|
|
|
|
private final TetheringCallbackInternal mCallback;
|
|
|
|
|
private final Context mContext;
|
|
|
|
|
private final ArrayMap<OnTetheringEventCallback, ITetheringEventCallback>
|
|
|
|
|
mTetheringEventCallbacks = new ArrayMap<>();
|
|
|
|
|
|
|
|
|
|
private TetheringConfigurationParcel mTetheringConfiguration;
|
|
|
|
|
private TetherStatesParcel mTetherStatesParcel;
|
|
|
|
|
|
|
|
|
|
private final RemoteCallbackList<ITetheringEventCallback> mTetheringEventCallbacks =
|
|
|
|
|
new RemoteCallbackList<>();
|
|
|
|
|
@GuardedBy("mLog")
|
|
|
|
|
private final SharedLog mLog = new SharedLog(TAG);
|
|
|
|
|
|
|
|
|
|
private TetheringManager() { }
|
|
|
|
|
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_MASTER_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_NAT_ERROR = 8;
|
|
|
|
|
public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9;
|
|
|
|
|
public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
|
|
|
|
|
public static final int TETHER_ERROR_PROVISION_FAILED = 11;
|
|
|
|
|
public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12;
|
|
|
|
|
public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13;
|
|
|
|
|
public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14;
|
|
|
|
|
public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the TetheringManager singleton instance.
|
|
|
|
|
* Create a TetheringManager object for interacting with the tethering service.
|
|
|
|
|
*/
|
|
|
|
|
public static synchronized TetheringManager getInstance() {
|
|
|
|
|
if (sInstance == null) {
|
|
|
|
|
sInstance = new TetheringManager();
|
|
|
|
|
}
|
|
|
|
|
return sInstance;
|
|
|
|
|
}
|
|
|
|
|
public TetheringManager(@NonNull final Context context, @NonNull final IBinder service) {
|
|
|
|
|
mContext = context;
|
|
|
|
|
mConnector = ITetheringConnector.Stub.asInterface(service);
|
|
|
|
|
mCallback = new TetheringCallbackInternal();
|
|
|
|
|
|
|
|
|
|
private class TetheringConnection implements
|
|
|
|
|
ConnectivityModuleConnector.ModuleServiceCallback {
|
|
|
|
|
@Override
|
|
|
|
|
public void onModuleServiceConnected(@NonNull IBinder service) {
|
|
|
|
|
logi("Tethering service connected");
|
|
|
|
|
registerTetheringService(service);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void registerTetheringService(@NonNull IBinder service) {
|
|
|
|
|
final ITetheringConnector connector = ITetheringConnector.Stub.asInterface(service);
|
|
|
|
|
|
|
|
|
|
log("Tethering service registered");
|
|
|
|
|
|
|
|
|
|
// Currently TetheringManager instance is only used by ConnectivityService and mConnector
|
|
|
|
|
// only expect to assign once when system server start and bind tethering service.
|
|
|
|
|
// STOPSHIP: Change mConnector to final before TetheringManager put into boot classpath.
|
|
|
|
|
mConnector = connector;
|
|
|
|
|
mCallback = new TetherInternalCallback();
|
|
|
|
|
final String pkgName = mContext.getOpPackageName();
|
|
|
|
|
Log.i(TAG, "registerTetheringEventCallback:" + pkgName);
|
|
|
|
|
try {
|
|
|
|
|
mConnector.registerTetherInternalCallback(mCallback);
|
|
|
|
|
mConnector.registerTetheringEventCallback(mCallback, pkgName);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
e.rethrowFromSystemServer();
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class TetherInternalCallback extends ITetherInternalCallback.Stub {
|
|
|
|
|
private final ConditionVariable mWaitForCallback = new ConditionVariable(false);
|
|
|
|
|
private static final int EVENT_CALLBACK_TIMEOUT_MS = 60_000;
|
|
|
|
|
private interface RequestHelper {
|
|
|
|
|
void runRequest(IIntResultListener listener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class RequestDispatcher {
|
|
|
|
|
private final ConditionVariable mWaiting;
|
|
|
|
|
public 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) {
|
|
|
|
|
request.runRequest(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 int mError = TETHER_ERROR_NO_ERROR;
|
|
|
|
|
private final ConditionVariable mWaitForCallback = new ConditionVariable();
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onUpstreamChanged(Network network) {
|
|
|
|
|
mTetherUpstream = network;
|
|
|
|
|
reportUpstreamChanged(network);
|
|
|
|
|
public void onCallbackStarted(Network network, TetheringConfigurationParcel config,
|
|
|
|
|
TetherStatesParcel states) {
|
|
|
|
|
mTetheringConfiguration = config;
|
|
|
|
|
mTetherStatesParcel = 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;
|
|
|
|
|
@@ -113,49 +160,10 @@ public class TetheringManager {
|
|
|
|
|
mTetherStatesParcel = states;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onCallbackCreated(Network network, TetheringConfigurationParcel config,
|
|
|
|
|
TetherStatesParcel states) {
|
|
|
|
|
mTetherUpstream = network;
|
|
|
|
|
mTetheringConfiguration = config;
|
|
|
|
|
mTetherStatesParcel = states;
|
|
|
|
|
mWaitForCallback.open();
|
|
|
|
|
public void waitForStarted() {
|
|
|
|
|
mWaitForCallback.block(DEFAULT_TIMEOUT_MS);
|
|
|
|
|
throwIfPermissionFailure(mError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
boolean awaitCallbackCreation() {
|
|
|
|
|
return mWaitForCallback.block(EVENT_CALLBACK_TIMEOUT_MS);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void reportUpstreamChanged(Network network) {
|
|
|
|
|
final int length = mTetheringEventCallbacks.beginBroadcast();
|
|
|
|
|
try {
|
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
|
|
|
try {
|
|
|
|
|
mTetheringEventCallbacks.getBroadcastItem(i).onUpstreamChanged(network);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
// Not really very much to do here.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
mTetheringEventCallbacks.finishBroadcast();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Start the tethering service. Should be called only once on device startup.
|
|
|
|
|
*
|
|
|
|
|
* <p>This method will start the tethering service either in the network stack process,
|
|
|
|
|
* or inside the system server on devices that do not support the tethering module.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
*/
|
|
|
|
|
public void start() {
|
|
|
|
|
// Using MAINLINE_NETWORK_STACK permission after cutting off the dpendency of system server.
|
|
|
|
|
ConnectivityModuleConnector.getInstance().startModuleService(
|
|
|
|
|
ITetheringConnector.class.getName(), NETWORK_STACK,
|
|
|
|
|
new TetheringConnection());
|
|
|
|
|
log("Tethering service start requested");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -165,108 +173,110 @@ public class TetheringManager {
|
|
|
|
|
* 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. Note: return value do not have any meaning. It is better to use
|
|
|
|
|
* #getTetherableIfaces() to ensure corresponding interface is available for
|
|
|
|
|
* tethering before calling #tether().
|
|
|
|
|
* active.
|
|
|
|
|
*
|
|
|
|
|
* @deprecated The only usages should be in PanService and Wifi P2P which
|
|
|
|
|
* need direct access.
|
|
|
|
|
* @deprecated The only usages is PanService. It uses this for legacy reasons
|
|
|
|
|
* and will migrate away as soon as possible.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @param iface the interface name to tether.
|
|
|
|
|
* @return error a {@code TETHER_ERROR} value indicating success or failure type
|
|
|
|
|
*/
|
|
|
|
|
@Deprecated
|
|
|
|
|
public int tether(@NonNull String iface) {
|
|
|
|
|
if (mConnector == null) {
|
|
|
|
|
Slog.wtf(TAG, "Tethering not ready yet");
|
|
|
|
|
return TETHER_ERROR_SERVICE_UNAVAIL;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
mConnector.tether(iface);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
e.rethrowFromSystemServer();
|
|
|
|
|
}
|
|
|
|
|
return TETHER_ERROR_NO_ERROR;
|
|
|
|
|
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(listener -> {
|
|
|
|
|
try {
|
|
|
|
|
mConnector.tether(iface, callerPkg, listener);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stop tethering the named interface.
|
|
|
|
|
*
|
|
|
|
|
* @deprecated
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @deprecated The only usages is PanService. It uses this for legacy reasons
|
|
|
|
|
* and will migrate away as soon as possible.
|
|
|
|
|
*/
|
|
|
|
|
@Deprecated
|
|
|
|
|
public int untether(@NonNull String iface) {
|
|
|
|
|
if (mConnector == null) {
|
|
|
|
|
Slog.wtf(TAG, "Tethering not ready yet");
|
|
|
|
|
return TETHER_ERROR_SERVICE_UNAVAIL;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
mConnector.untether(iface);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
e.rethrowFromSystemServer();
|
|
|
|
|
}
|
|
|
|
|
return TETHER_ERROR_NO_ERROR;
|
|
|
|
|
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(listener -> {
|
|
|
|
|
try {
|
|
|
|
|
mConnector.untether(iface, callerPkg, listener);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempt to both alter the mode of USB and Tethering of USB. WARNING: 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.
|
|
|
|
|
* Attempt to both alter the mode of USB and Tethering of USB.
|
|
|
|
|
*
|
|
|
|
|
* @deprecated
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @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.
|
|
|
|
|
*/
|
|
|
|
|
@Deprecated
|
|
|
|
|
public int setUsbTethering(boolean enable) {
|
|
|
|
|
if (mConnector == null) {
|
|
|
|
|
Slog.wtf(TAG, "Tethering not ready yet");
|
|
|
|
|
return TETHER_ERROR_SERVICE_UNAVAIL;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
mConnector.setUsbTethering(enable);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
e.rethrowFromSystemServer();
|
|
|
|
|
}
|
|
|
|
|
return TETHER_ERROR_NO_ERROR;
|
|
|
|
|
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(listener -> {
|
|
|
|
|
try {
|
|
|
|
|
mConnector.setUsbTethering(enable, callerPkg, listener);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Starts tethering and runs tether provisioning for the given type if needed. If provisioning
|
|
|
|
|
* fails, stopTethering will be called automatically.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
*/
|
|
|
|
|
// TODO: improve the usage of ResultReceiver, b/145096122
|
|
|
|
|
public void startTethering(int type, @NonNull ResultReceiver receiver,
|
|
|
|
|
boolean showProvisioningUi) {
|
|
|
|
|
if (mConnector == null) {
|
|
|
|
|
Slog.wtf(TAG, "Tethering not ready yet");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
public void startTethering(final int type, @NonNull final ResultReceiver receiver,
|
|
|
|
|
final boolean showProvisioningUi) {
|
|
|
|
|
final String callerPkg = mContext.getOpPackageName();
|
|
|
|
|
Log.i(TAG, "startTethering caller:" + callerPkg);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
mConnector.startTethering(type, receiver, showProvisioningUi);
|
|
|
|
|
mConnector.startTethering(type, receiver, showProvisioningUi, callerPkg);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
e.rethrowFromSystemServer();
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stops tethering for the given type. Also cancels any provisioning rechecks for that type if
|
|
|
|
|
* applicable.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
*/
|
|
|
|
|
public void stopTethering(int type) {
|
|
|
|
|
if (mConnector == null) {
|
|
|
|
|
Slog.wtf(TAG, "Tethering not ready yet");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
mConnector.stopTethering(type);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
e.rethrowFromSystemServer();
|
|
|
|
|
}
|
|
|
|
|
public void stopTethering(final int type) {
|
|
|
|
|
final String callerPkg = mContext.getOpPackageName();
|
|
|
|
|
Log.i(TAG, "stopTethering caller:" + callerPkg);
|
|
|
|
|
|
|
|
|
|
final RequestDispatcher dispatcher = new RequestDispatcher();
|
|
|
|
|
|
|
|
|
|
dispatcher.waitForResult(listener -> {
|
|
|
|
|
try {
|
|
|
|
|
mConnector.stopTethering(type, callerPkg, listener);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -277,47 +287,109 @@ public class TetheringManager {
|
|
|
|
|
* if it's really needed.
|
|
|
|
|
*/
|
|
|
|
|
// TODO: improve the usage of ResultReceiver, b/145096122
|
|
|
|
|
public void requestLatestTetheringEntitlementResult(int type, @NonNull ResultReceiver receiver,
|
|
|
|
|
boolean showEntitlementUi) {
|
|
|
|
|
if (mConnector == null) {
|
|
|
|
|
Slog.wtf(TAG, "Tethering not ready yet");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
public void requestLatestTetheringEntitlementResult(final int type,
|
|
|
|
|
@NonNull final ResultReceiver receiver, final boolean showEntitlementUi) {
|
|
|
|
|
final String callerPkg = mContext.getOpPackageName();
|
|
|
|
|
Log.i(TAG, "getLatestTetheringEntitlementResult caller:" + callerPkg);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
mConnector.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
|
|
|
|
|
mConnector.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi,
|
|
|
|
|
callerPkg);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
e.rethrowFromSystemServer();
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register tethering event callback.
|
|
|
|
|
* Start listening to tethering change events. Any new added callback will receive the last
|
|
|
|
|
* tethering status right away. If callback is registered,
|
|
|
|
|
* {@link OnTetheringEventCallback#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.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @param executor the executor on which callback will be invoked.
|
|
|
|
|
* @param callback the callback to be called when tethering has change events.
|
|
|
|
|
*/
|
|
|
|
|
public void registerTetheringEventCallback(@NonNull ITetheringEventCallback callback) {
|
|
|
|
|
mTetheringEventCallbacks.register(callback);
|
|
|
|
|
public void registerTetheringEventCallback(@NonNull Executor executor,
|
|
|
|
|
@NonNull OnTetheringEventCallback 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() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onUpstreamChanged(Network network) throws RemoteException {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
callback.onUpstreamChanged(network);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onCallbackStarted(Network network, TetheringConfigurationParcel config,
|
|
|
|
|
TetherStatesParcel states) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
callback.onUpstreamChanged(network);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onCallbackStopped(int errorCode) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
throwIfPermissionFailure(errorCode);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onConfigurationChanged(TetheringConfigurationParcel config) { }
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onTetherStatesChanged(TetherStatesParcel states) { }
|
|
|
|
|
};
|
|
|
|
|
try {
|
|
|
|
|
mConnector.registerTetheringEventCallback(remoteCallback, callerPkg);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
mTetheringEventCallbacks.put(callback, remoteCallback);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Unregister tethering event callback.
|
|
|
|
|
* Remove tethering event callback previously registered with
|
|
|
|
|
* {@link #registerTetheringEventCallback}.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @param callback previously registered callback.
|
|
|
|
|
*/
|
|
|
|
|
public void unregisterTetheringEventCallback(@NonNull ITetheringEventCallback callback) {
|
|
|
|
|
mTetheringEventCallbacks.unregister(callback);
|
|
|
|
|
public void unregisterTetheringEventCallback(@NonNull final OnTetheringEventCallback 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.");
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
mConnector.unregisterTetheringEventCallback(remoteCallback, callerPkg);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a more detailed error code after a Tethering or Untethering
|
|
|
|
|
* request asynchronously failed.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @param iface The name of the interface of interest
|
|
|
|
|
* @return error The error code of the last error tethering or untethering the named
|
|
|
|
|
* interface
|
|
|
|
|
*/
|
|
|
|
|
public int getLastTetherError(@NonNull String iface) {
|
|
|
|
|
if (!mCallback.awaitCallbackCreation()) {
|
|
|
|
|
throw new NullPointerException("callback was not ready yet");
|
|
|
|
|
}
|
|
|
|
|
public int getLastTetherError(@NonNull final String iface) {
|
|
|
|
|
mCallback.waitForStarted();
|
|
|
|
|
if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR;
|
|
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
@@ -334,12 +406,11 @@ public class TetheringManager {
|
|
|
|
|
* USB network interfaces. If USB tethering is not supported by the
|
|
|
|
|
* device, this list should be empty.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @return an array of 0 or more regular expression Strings defining
|
|
|
|
|
* what interfaces are considered tetherable usb interfaces.
|
|
|
|
|
*/
|
|
|
|
|
public @NonNull String[] getTetherableUsbRegexs() {
|
|
|
|
|
if (!mCallback.awaitCallbackCreation()) {
|
|
|
|
|
throw new NullPointerException("callback was not ready yet");
|
|
|
|
|
}
|
|
|
|
|
mCallback.waitForStarted();
|
|
|
|
|
return mTetheringConfiguration.tetherableUsbRegexs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -348,12 +419,11 @@ public class TetheringManager {
|
|
|
|
|
* Wifi network interfaces. If Wifi tethering is not supported by the
|
|
|
|
|
* device, this list should be empty.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @return an array of 0 or more regular expression Strings defining
|
|
|
|
|
* what interfaces are considered tetherable wifi interfaces.
|
|
|
|
|
*/
|
|
|
|
|
public @NonNull String[] getTetherableWifiRegexs() {
|
|
|
|
|
if (!mCallback.awaitCallbackCreation()) {
|
|
|
|
|
throw new NullPointerException("callback was not ready yet");
|
|
|
|
|
}
|
|
|
|
|
mCallback.waitForStarted();
|
|
|
|
|
return mTetheringConfiguration.tetherableWifiRegexs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -362,12 +432,11 @@ public class TetheringManager {
|
|
|
|
|
* Bluetooth network interfaces. If Bluetooth tethering is not supported by the
|
|
|
|
|
* device, this list should be empty.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @return an array of 0 or more regular expression Strings defining
|
|
|
|
|
* what interfaces are considered tetherable bluetooth interfaces.
|
|
|
|
|
*/
|
|
|
|
|
public @NonNull String[] getTetherableBluetoothRegexs() {
|
|
|
|
|
if (!mCallback.awaitCallbackCreation()) {
|
|
|
|
|
throw new NullPointerException("callback was not ready yet");
|
|
|
|
|
}
|
|
|
|
|
mCallback.waitForStarted();
|
|
|
|
|
return mTetheringConfiguration.tetherableBluetoothRegexs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -375,40 +444,42 @@ public class TetheringManager {
|
|
|
|
|
* Get the set of tetherable, available interfaces. This list is limited by
|
|
|
|
|
* device configuration and current interface existence.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @return an array of 0 or more Strings of tetherable interface names.
|
|
|
|
|
*/
|
|
|
|
|
public @NonNull String[] getTetherableIfaces() {
|
|
|
|
|
if (!mCallback.awaitCallbackCreation()) {
|
|
|
|
|
throw new NullPointerException("callback was not ready yet");
|
|
|
|
|
}
|
|
|
|
|
mCallback.waitForStarted();
|
|
|
|
|
if (mTetherStatesParcel == null) return new String[0];
|
|
|
|
|
|
|
|
|
|
return mTetherStatesParcel.availableList;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the set of tethered interfaces.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @return an array of 0 or more String of currently tethered interface names.
|
|
|
|
|
*/
|
|
|
|
|
public @NonNull String[] getTetheredIfaces() {
|
|
|
|
|
if (!mCallback.awaitCallbackCreation()) {
|
|
|
|
|
throw new NullPointerException("callback was not ready yet");
|
|
|
|
|
}
|
|
|
|
|
mCallback.waitForStarted();
|
|
|
|
|
if (mTetherStatesParcel == null) return new String[0];
|
|
|
|
|
|
|
|
|
|
return mTetherStatesParcel.tetheredList;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the set of interface names which attempted to tether but
|
|
|
|
|
* failed.
|
|
|
|
|
* 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 ConnectivityManager#getLastTetherError} can be used to get more
|
|
|
|
|
* information on the cause of the errors.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @return an array of 0 or more String indicating the interface names
|
|
|
|
|
* which failed to tether.
|
|
|
|
|
*/
|
|
|
|
|
public @NonNull String[] getTetheringErroredIfaces() {
|
|
|
|
|
if (!mCallback.awaitCallbackCreation()) {
|
|
|
|
|
throw new NullPointerException("callback was not ready yet");
|
|
|
|
|
}
|
|
|
|
|
mCallback.waitForStarted();
|
|
|
|
|
if (mTetherStatesParcel == null) return new String[0];
|
|
|
|
|
|
|
|
|
|
return mTetherStatesParcel.erroredIfaceList;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -416,123 +487,49 @@ public class TetheringManager {
|
|
|
|
|
* 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() {
|
|
|
|
|
if (!mCallback.awaitCallbackCreation()) {
|
|
|
|
|
throw new NullPointerException("callback was not ready yet");
|
|
|
|
|
}
|
|
|
|
|
mCallback.waitForStarted();
|
|
|
|
|
return mTetheringConfiguration.legacyDhcpRanges;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if the device allows for tethering.
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
|
|
|
|
* {@hide}
|
|
|
|
|
* @return a boolean - {@code true} indicating Tethering is supported.
|
|
|
|
|
*/
|
|
|
|
|
public boolean hasTetherableConfiguration() {
|
|
|
|
|
if (!mCallback.awaitCallbackCreation()) {
|
|
|
|
|
throw new NullPointerException("callback was not ready yet");
|
|
|
|
|
}
|
|
|
|
|
final boolean hasDownstreamConfiguration =
|
|
|
|
|
(mTetheringConfiguration.tetherableUsbRegexs.length != 0)
|
|
|
|
|
|| (mTetheringConfiguration.tetherableWifiRegexs.length != 0)
|
|
|
|
|
|| (mTetheringConfiguration.tetherableBluetoothRegexs.length != 0);
|
|
|
|
|
final boolean hasUpstreamConfiguration =
|
|
|
|
|
(mTetheringConfiguration.preferredUpstreamIfaceTypes.length != 0)
|
|
|
|
|
|| mTetheringConfiguration.chooseUpstreamAutomatically;
|
|
|
|
|
public boolean isTetheringSupported() {
|
|
|
|
|
final String callerPkg = mContext.getOpPackageName();
|
|
|
|
|
|
|
|
|
|
return hasDownstreamConfiguration && hasUpstreamConfiguration;
|
|
|
|
|
final RequestDispatcher dispatcher = new RequestDispatcher();
|
|
|
|
|
final int ret = dispatcher.waitForResult(listener -> {
|
|
|
|
|
try {
|
|
|
|
|
mConnector.isTetheringSupported(callerPkg, listener);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return ret == TETHER_ERROR_NO_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log a message in the local log.
|
|
|
|
|
* Stop all active tethering.
|
|
|
|
|
*/
|
|
|
|
|
private void log(@NonNull String message) {
|
|
|
|
|
synchronized (mLog) {
|
|
|
|
|
mLog.log(message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public void stopAllTethering() {
|
|
|
|
|
final String callerPkg = mContext.getOpPackageName();
|
|
|
|
|
Log.i(TAG, "stopAllTethering caller:" + callerPkg);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log a condition that should never happen.
|
|
|
|
|
*/
|
|
|
|
|
private void logWtf(@NonNull String message, @Nullable Throwable e) {
|
|
|
|
|
Slog.wtf(TAG, message);
|
|
|
|
|
synchronized (mLog) {
|
|
|
|
|
mLog.e(message, e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log a ERROR level message in the local and system logs.
|
|
|
|
|
*/
|
|
|
|
|
private void loge(@NonNull String message, @Nullable Throwable e) {
|
|
|
|
|
synchronized (mLog) {
|
|
|
|
|
mLog.e(message, e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Log a INFO level message in the local and system logs.
|
|
|
|
|
*/
|
|
|
|
|
private void logi(@NonNull String message) {
|
|
|
|
|
synchronized (mLog) {
|
|
|
|
|
mLog.i(message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Dump TetheringManager logs to the specified {@link PrintWriter}.
|
|
|
|
|
*/
|
|
|
|
|
public void dump(@NonNull PrintWriter pw) {
|
|
|
|
|
// dump is thread-safe on SharedLog
|
|
|
|
|
mLog.dump(null, pw, null);
|
|
|
|
|
|
|
|
|
|
pw.print("subId: ");
|
|
|
|
|
pw.println(mTetheringConfiguration.subId);
|
|
|
|
|
|
|
|
|
|
dumpStringArray(pw, "tetherableUsbRegexs",
|
|
|
|
|
mTetheringConfiguration.tetherableUsbRegexs);
|
|
|
|
|
dumpStringArray(pw, "tetherableWifiRegexs",
|
|
|
|
|
mTetheringConfiguration.tetherableWifiRegexs);
|
|
|
|
|
dumpStringArray(pw, "tetherableBluetoothRegexs",
|
|
|
|
|
mTetheringConfiguration.tetherableBluetoothRegexs);
|
|
|
|
|
|
|
|
|
|
pw.print("isDunRequired: ");
|
|
|
|
|
pw.println(mTetheringConfiguration.isDunRequired);
|
|
|
|
|
|
|
|
|
|
pw.print("chooseUpstreamAutomatically: ");
|
|
|
|
|
pw.println(mTetheringConfiguration.chooseUpstreamAutomatically);
|
|
|
|
|
|
|
|
|
|
dumpStringArray(pw, "legacyDhcpRanges", mTetheringConfiguration.legacyDhcpRanges);
|
|
|
|
|
dumpStringArray(pw, "defaultIPv4DNS", mTetheringConfiguration.defaultIPv4DNS);
|
|
|
|
|
|
|
|
|
|
dumpStringArray(pw, "provisioningApp", mTetheringConfiguration.provisioningApp);
|
|
|
|
|
pw.print("provisioningAppNoUi: ");
|
|
|
|
|
pw.println(mTetheringConfiguration.provisioningAppNoUi);
|
|
|
|
|
|
|
|
|
|
pw.print("enableLegacyDhcpServer: ");
|
|
|
|
|
pw.println(mTetheringConfiguration.enableLegacyDhcpServer);
|
|
|
|
|
|
|
|
|
|
pw.println();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void dumpStringArray(@NonNull PrintWriter pw, @NonNull String label,
|
|
|
|
|
@Nullable String[] values) {
|
|
|
|
|
pw.print(label);
|
|
|
|
|
pw.print(": ");
|
|
|
|
|
|
|
|
|
|
if (values != null) {
|
|
|
|
|
final StringJoiner sj = new StringJoiner(", ", "[", "]");
|
|
|
|
|
for (String value : values) sj.add(value);
|
|
|
|
|
|
|
|
|
|
pw.print(sj.toString());
|
|
|
|
|
} else {
|
|
|
|
|
pw.print("null");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pw.println();
|
|
|
|
|
final RequestDispatcher dispatcher = new RequestDispatcher();
|
|
|
|
|
dispatcher.waitForResult(listener -> {
|
|
|
|
|
try {
|
|
|
|
|
mConnector.stopAllTethering(callerPkg, listener);
|
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
|
throw new IllegalStateException(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|