Merge "Remove WRITE_SETTINGS permission from Tethering APIs" into main

This commit is contained in:
Quang Luong 2024-12-18 18:42:49 -08:00 • committed by Gerrit Code Review
commit 915d138309
10 changed files with 157 additions and 52 deletions

View file

@ -63,6 +63,7 @@ java_defaults {
static_libs: [
"androidx.annotation_annotation",
"connectivity-net-module-utils-bpf",
"com.android.net.flags-aconfig-java",
"modules-utils-build",
"modules-utils-statemachine",
"networkstack-client",

View file

@ -22,7 +22,7 @@ package android.net {
method public boolean isTetheringSupported(@NonNull String);
method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean);
method @Deprecated public int setUsbTethering(boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
method @Deprecated public int tether(@NonNull String);
method @Deprecated public int untether(@NonNull String);
}

View file

@ -20,9 +20,9 @@ package android.net {
}
public class TetheringManager {
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopAllTethering();
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
field @Deprecated public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
field public static final int CONNECTIVITY_SCOPE_LOCAL = 2; // 0x2
field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";

View file

@ -1300,18 +1300,12 @@ public class TetheringManager {
* 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
})
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
@SystemApi(client = MODULE_LIBRARIES)
public void startTethering(int type, @NonNull final Executor executor,
@NonNull final StartTetheringCallback callback) {
@ -1322,14 +1316,9 @@ public class TetheringManager {
* 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.
* @hide
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.TETHER_PRIVILEGED,
android.Manifest.permission.WRITE_SETTINGS
})
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
@SystemApi
public void stopTethering(@TetheringType final int type) {
final String callerPkg = mContext.getOpPackageName();
@ -1384,9 +1373,6 @@ public class TetheringManager {
* {@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.
@ -1397,10 +1383,7 @@ public class TetheringManager {
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {
android.Manifest.permission.TETHER_PRIVILEGED,
android.Manifest.permission.WRITE_SETTINGS
})
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void requestLatestTetheringEntitlementResult(@TetheringType int type,
boolean showEntitlementUi,
@NonNull Executor executor,
@ -2083,10 +2066,7 @@ public class TetheringManager {
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {
android.Manifest.permission.TETHER_PRIVILEGED,
android.Manifest.permission.WRITE_SETTINGS
})
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void stopAllTethering() {
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "stopAllTethering caller:" + callerPkg);

View file

@ -451,6 +451,10 @@ public class Tethering {
return mSettingsObserver;
}
boolean isTetheringWithSoftApConfigEnabled() {
return mDeps.isTetheringWithSoftApConfigEnabled();
}
/**
* Start to register callbacks.
* Call this function when tethering is ready to handle callback events.

View file

@ -37,6 +37,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.flags.Flags;
import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.RoutingCoordinatorService;
import com.android.net.module.util.SharedLog;
@ -208,4 +209,12 @@ public abstract class TetheringDependencies {
public int getBinderCallingUid() {
return Binder.getCallingUid();
}
/**
* Wrapper for tethering_with_soft_ap_config feature flag.
*/
public boolean isTetheringWithSoftApConfigEnabled() {
return Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM
&& Flags.tetheringWithSoftApConfig();
}
}

View file

@ -310,6 +310,10 @@ public class TetheringService extends Service {
if (hasTetherPrivilegedPermission()) return true;
// After TetheringManager moves to public API, prevent third-party apps from being able
// to change tethering with only WRITE_SETTINGS permission.
if (mTethering.isTetheringWithSoftApConfigEnabled()) return false;
if (mTethering.isTetherProvisioningRequired()) return false;
int uid = getBinderCallingUid();

View file

@ -154,31 +154,43 @@ public final class TetheringServiceTest {
}
private void runAsNoPermission(final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, new String[0]);
runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
new String[0]);
}
private void runAsTetherPrivileged(final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, TETHER_PRIVILEGED);
runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
TETHER_PRIVILEGED);
}
private void runAsAccessNetworkState(final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, ACCESS_NETWORK_STATE);
runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
ACCESS_NETWORK_STATE);
}
private void runAsWriteSettings(final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, WRITE_SETTINGS);
runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
WRITE_SETTINGS);
}
private void runAsWriteSettingsWhenWriteSettingsAllowed(
final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, true /* isWriteSettingsAllowed */,
WRITE_SETTINGS);
}
private void runAsTetheringDisallowed(final TestTetheringCall test) throws Exception {
runTetheringCall(test, false /* isTetheringAllowed */, TETHER_PRIVILEGED);
runTetheringCall(test, false /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
TETHER_PRIVILEGED);
}
private void runAsNetworkSettings(final TestTetheringCall test) throws Exception {
runTetheringCall(test, true /* isTetheringAllowed */, NETWORK_SETTINGS, TETHER_PRIVILEGED);
runTetheringCall(test, true /* isTetheringAllowed */, false /* isWriteSettingsAllowed */,
NETWORK_SETTINGS, TETHER_PRIVILEGED);
}
private void runTetheringCall(final TestTetheringCall test, boolean isTetheringAllowed,
String... permissions) throws Exception {
boolean isWriteSettingsAllowed, String... permissions) throws Exception {
// Allow the test to run even if ACCESS_NETWORK_STATE was granted at the APK level
if (!CollectionUtils.contains(permissions, ACCESS_NETWORK_STATE)) {
mMockConnector.setPermission(ACCESS_NETWORK_STATE, PERMISSION_DENIED);
@ -188,6 +200,8 @@ public final class TetheringServiceTest {
try {
when(mTethering.isTetheringSupported()).thenReturn(true);
when(mTethering.isTetheringAllowed()).thenReturn(isTetheringAllowed);
when(mTethering.isTetheringWithSoftApConfigEnabled())
.thenReturn(!isWriteSettingsAllowed);
test.runTetheringCall(new TestTetheringResult());
} finally {
mUiAutomation.dropShellPermissionIdentity();
@ -213,7 +227,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetherProvisioningRequired();
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -224,7 +238,16 @@ public final class TetheringServiceTest {
});
runAsWriteSettings((result) -> {
mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runTether(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -252,7 +275,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetherProvisioningRequired();
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -263,7 +286,16 @@ public final class TetheringServiceTest {
});
runAsWriteSettings((result) -> {
mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runUnTether(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -297,7 +329,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetherProvisioningRequired();
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -308,7 +340,16 @@ public final class TetheringServiceTest {
});
runAsWriteSettings((result) -> {
mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runSetUsbTethering(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -341,7 +382,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetherProvisioningRequired();
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -361,7 +402,16 @@ public final class TetheringServiceTest {
});
runAsWriteSettings((result) -> {
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runStartTethering(result, request);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -446,7 +496,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetherProvisioningRequired();
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -457,7 +507,16 @@ public final class TetheringServiceTest {
});
runAsWriteSettings((result) -> {
mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runStopTethering(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -486,11 +545,13 @@ public final class TetheringServiceTest {
public void testRequestLatestTetheringEntitlementResult() throws Exception {
// Run as no permission.
final MyResultReceiver result = new MyResultReceiver(null);
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
verify(mTethering).isTetherProvisioningRequired();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractions(mTethering);
runAsNoPermission((none) -> {
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsTetherPrivileged((none) -> {
runRequestLatestTetheringEntitlementResult();
@ -501,22 +562,30 @@ public final class TetheringServiceTest {
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
true /* showEntitlementUi */, TEST_WRONG_PACKAGE, TEST_ATTRIBUTION_TAG);
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractions(mTethering);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettings((none) -> {
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((none) -> {
runRequestLatestTetheringEntitlementResult();
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
runAsTetheringDisallowed((none) -> {
final MyResultReceiver receiver = new MyResultReceiver(null);
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver,
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
verify(mTethering).isTetheringSupported();
verify(mTethering).isTetheringAllowed();
receiver.assertResult(TETHER_ERROR_UNSUPPORTED);
result.assertResult(TETHER_ERROR_UNSUPPORTED);
verifyNoMoreInteractionsForTethering();
});
}
@ -600,7 +669,7 @@ public final class TetheringServiceTest {
public void testStopAllTethering() throws Exception {
runAsNoPermission((result) -> {
mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetherProvisioningRequired();
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -611,7 +680,15 @@ public final class TetheringServiceTest {
});
runAsWriteSettings((result) -> {
mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runStopAllTethering(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});
@ -637,7 +714,7 @@ public final class TetheringServiceTest {
runAsNoPermission((result) -> {
mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetherProvisioningRequired();
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
@ -648,7 +725,16 @@ public final class TetheringServiceTest {
});
runAsWriteSettings((result) -> {
mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
});
runAsWriteSettingsWhenWriteSettingsAllowed((result) -> {
runIsTetheringSupported(result);
verify(mTethering).isTetheringWithSoftApConfigEnabled();
verify(mTethering).isTetherProvisioningRequired();
verifyNoMoreInteractionsForTethering();
});

View file

@ -34,6 +34,7 @@ java_defaults {
static_libs: [
"TetheringCommonTests",
"com.android.net.flags-aconfig-java",
"compatibility-device-util-axt",
"cts-net-utils",
"net-tests-utils",

View file

@ -17,6 +17,7 @@ package android.tethering.test;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.Manifest.permission.WRITE_SETTINGS;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@ -71,6 +72,7 @@ import android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiSsid;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.ResultReceiver;
@ -83,6 +85,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.flags.Flags;
import com.android.testutils.ParcelUtils;
import org.junit.After;
@ -457,12 +460,29 @@ public class TetheringManagerTest {
} catch (UnsupportedOperationException expect) { }
}
private boolean isTetheringWithSoftApConfigEnabled() {
return Build.VERSION.SDK_INT > Build.VERSION_CODES.VANILLA_ICE_CREAM
&& Flags.tetheringWithSoftApConfig();
}
@Test
public void testEnableTetheringPermission() throws Exception {
public void testStartTetheringNoPermission() throws Exception {
final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
// No permission
mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
c -> c.run() /* executor */, startTetheringCallback);
startTetheringCallback.expectTetheringFailed(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
// WRITE_SETTINGS not sufficient
if (isTetheringWithSoftApConfigEnabled()) {
runAsShell(WRITE_SETTINGS, () -> {
mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(),
c -> c.run() /* executor */, startTetheringCallback);
startTetheringCallback.expectTetheringFailed(
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
});
}
}
private class EntitlementResultListener implements OnTetheringEntitlementResultListener {