diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp index a8d1239a3c..b0ef153994 100644 --- a/Tethering/common/TetheringLib/Android.bp +++ b/Tethering/common/TetheringLib/Android.bp @@ -70,6 +70,7 @@ filegroup { "src/android/net/ITetheringConnector.aidl", "src/android/net/TetheringCallbackStartedParcel.aidl", "src/android/net/TetheringConfigurationParcel.aidl", + "src/android/net/TetheringRequestParcel.aidl", "src/android/net/TetherStatesParcel.aidl", ], path: "src" diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl index d30c399869..5febe73288 100644 --- a/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl +++ b/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl @@ -17,6 +17,7 @@ package android.net; import android.net.IIntResultListener; import android.net.ITetheringEventCallback; +import android.net.TetheringRequestParcel; import android.os.ResultReceiver; /** @hide */ @@ -27,8 +28,8 @@ oneway interface ITetheringConnector { void setUsbTethering(boolean enable, String callerPkg, IIntResultListener receiver); - void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi, - String callerPkg); + void startTethering(in TetheringRequestParcel request, String callerPkg, + IIntResultListener receiver); void stopTethering(int type, String callerPkg, IIntResultListener receiver); diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index e1b9c16b81..58bc4e7633 100644 --- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -15,6 +15,7 @@ */ package android.net; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -325,28 +326,172 @@ public class TetheringManager { }); } + /** + * 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. - * @hide + * + *
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. */ - // TODO: improve the usage of ResultReceiver, b/145096122 - public void startTethering(final int type, @NonNull final ResultReceiver receiver, - final boolean showProvisioningUi) { + @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); + } + }); + } + }; try { - mConnector.startTethering(type, receiver, showProvisioningUi, callerPkg); + mConnector.startTethering(request.getParcel(), 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. + * + *
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); @@ -386,6 +531,9 @@ public class TetheringManager { * {@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. @@ -393,7 +541,10 @@ public class TetheringManager { * notify the caller of the result of entitlement check. The listener may be called zero * or one time. */ - @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + @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) { @@ -562,6 +713,7 @@ public class TetheringManager { * @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(); @@ -669,6 +821,10 @@ public class TetheringManager { * * @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); @@ -833,7 +989,14 @@ public class TetheringManager { /** * Stop all active tethering. + * + *
Without {@link android.Manifest.permission.TETHER_PRIVILEGED} permission, the call will + * fail if a tethering entitlement check is required. */ + @RequiresPermission(anyOf = { + android.Manifest.permission.TETHER_PRIVILEGED, + android.Manifest.permission.WRITE_SETTINGS + }) public void stopAllTethering() { final String callerPkg = mContext.getOpPackageName(); Log.i(TAG, "stopAllTethering caller:" + callerPkg); diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl new file mode 100644 index 0000000000..bf19d85f6a --- /dev/null +++ b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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 android.net.LinkAddress; + +/** + * Configuration details for requesting tethering. + * @hide + */ +parcelable TetheringRequestParcel { + int tetheringType; + LinkAddress localIPv4Address; + boolean exemptFromEntitlementCheck; + boolean showProvisioningUi; +} diff --git a/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index 9b7d7b0167..fdc045fb15 100644 --- a/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -66,6 +66,7 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; +import android.net.IIntResultListener; import android.net.INetd; import android.net.ITetheringEventCallback; import android.net.IpPrefix; @@ -76,6 +77,7 @@ import android.net.NetworkInfo; import android.net.TetherStatesParcel; import android.net.TetheringCallbackStartedParcel; import android.net.TetheringConfigurationParcel; +import android.net.TetheringRequestParcel; import android.net.ip.IpServer; import android.net.shared.NetdUtils; import android.net.util.BaseNetdUnsolicitedEventListener; @@ -423,9 +425,10 @@ public class Tethering { } } - void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) { - mEntitlementMgr.startProvisioningIfNeeded(type, showProvisioningUi); - enableTetheringInternal(type, true /* enabled */, receiver); + void startTethering(final TetheringRequestParcel request, final IIntResultListener listener) { + mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType, + request.showProvisioningUi); + enableTetheringInternal(request.tetheringType, true /* enabled */, listener); } void stopTethering(int type) { @@ -437,29 +440,32 @@ public class Tethering { * Enables or disables tethering for the given type. If provisioning is required, it will * schedule provisioning rechecks for the specified interface. */ - private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) { + private void enableTetheringInternal(int type, boolean enable, + final IIntResultListener listener) { int result; switch (type) { case TETHERING_WIFI: result = setWifiTethering(enable); - sendTetherResult(receiver, result); + sendTetherResult(listener, result); break; case TETHERING_USB: result = setUsbTethering(enable); - sendTetherResult(receiver, result); + sendTetherResult(listener, result); break; case TETHERING_BLUETOOTH: - setBluetoothTethering(enable, receiver); + setBluetoothTethering(enable, listener); break; default: Log.w(TAG, "Invalid tether type."); - sendTetherResult(receiver, TETHER_ERROR_UNKNOWN_IFACE); + sendTetherResult(listener, TETHER_ERROR_UNKNOWN_IFACE); } } - private void sendTetherResult(ResultReceiver receiver, int result) { - if (receiver != null) { - receiver.send(result, null); + private void sendTetherResult(final IIntResultListener listener, int result) { + if (listener != null) { + try { + listener.onResult(result); + } catch (RemoteException e) { } } } @@ -485,12 +491,12 @@ public class Tethering { return TETHER_ERROR_MASTER_ERROR; } - private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) { + private void setBluetoothTethering(final boolean enable, final IIntResultListener listener) { final BluetoothAdapter adapter = mDeps.getBluetoothAdapter(); if (adapter == null || !adapter.isEnabled()) { Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: " + (adapter == null)); - sendTetherResult(receiver, TETHER_ERROR_SERVICE_UNAVAIL); + sendTetherResult(listener, TETHER_ERROR_SERVICE_UNAVAIL); return; } @@ -519,7 +525,7 @@ public class Tethering { final int result = (((BluetoothPan) proxy).isTetheringOn() == enable) ? TETHER_ERROR_NO_ERROR : TETHER_ERROR_MASTER_ERROR; - sendTetherResult(receiver, result); + sendTetherResult(listener, result); adapter.closeProfileProxy(BluetoothProfile.PAN, proxy); } }, BluetoothProfile.PAN); diff --git a/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java index cb7d3920e6..7dc5c5f2db 100644 --- a/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java +++ b/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java @@ -33,6 +33,7 @@ import android.net.ITetheringConnector; import android.net.ITetheringEventCallback; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.net.TetheringRequestParcel; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.ip.IpServer; @@ -143,11 +144,11 @@ public class TetheringService extends Service { } @Override - public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi, - String callerPkg) { - if (checkAndNotifyCommonError(callerPkg, receiver)) return; + public void startTethering(TetheringRequestParcel request, String callerPkg, + IIntResultListener listener) { + if (checkAndNotifyCommonError(callerPkg, listener)) return; - mTethering.startTethering(type, receiver, showProvisioningUi); + mTethering.startTethering(request, listener); } @Override diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 5be6dc6e32..932fd50622 100644 --- a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -88,6 +88,7 @@ import android.net.RouteInfo; import android.net.TetherStatesParcel; import android.net.TetheringCallbackStartedParcel; import android.net.TetheringConfigurationParcel; +import android.net.TetheringRequestParcel; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServer; @@ -468,6 +469,16 @@ public class TetheringTest { return new Tethering(mTetheringDependencies); } + private TetheringRequestParcel createTetheringRquestParcel(final int type) { + final TetheringRequestParcel request = new TetheringRequestParcel(); + request.tetheringType = type; + request.localIPv4Address = null; + request.exemptFromEntitlementCheck = false; + request.showProvisioningUi = false; + + return request; + } + @After public void tearDown() { mServiceContext.unregisterReceiver(mBroadcastReceiver); @@ -571,7 +582,7 @@ public class TetheringTest { .thenReturn(upstreamState); // Emulate pressing the USB tethering button in Settings UI. - mTethering.startTethering(TETHERING_USB, null, false); + mTethering.startTethering(createTetheringRquestParcel(TETHERING_USB), null); mLooper.dispatchAll(); verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); @@ -817,7 +828,7 @@ public class TetheringTest { when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(TETHERING_WIFI, null, false); + mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null); mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); @@ -844,7 +855,7 @@ public class TetheringTest { when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(TETHERING_WIFI, null, false); + mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null); mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); @@ -921,7 +932,7 @@ public class TetheringTest { doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME); // Emulate pressing the WiFi tethering button. - mTethering.startTethering(TETHERING_WIFI, null, false); + mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null); mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); @@ -1186,7 +1197,7 @@ public class TetheringTest { tetherState = callback.pollTetherStatesChanged(); assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME}); - mTethering.startTethering(TETHERING_WIFI, null, false); + mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null); sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); mLooper.dispatchAll(); tetherState = callback.pollTetherStatesChanged();