Merge "Allow to exempt from entitlement check"
This commit is contained in:
@@ -80,6 +80,7 @@ public class EntitlementManager {
|
||||
// {@link TetheringManager.TETHERING_USB}
|
||||
// {@link TetheringManager.TETHERING_BLUETOOTH}
|
||||
private final BitSet mCurrentDownstreams;
|
||||
private final BitSet mExemptedDownstreams;
|
||||
private final Context mContext;
|
||||
private final SharedLog mLog;
|
||||
private final SparseIntArray mEntitlementCacheValue;
|
||||
@@ -100,6 +101,7 @@ public class EntitlementManager {
|
||||
mContext = ctx;
|
||||
mLog = log.forSubComponent(TAG);
|
||||
mCurrentDownstreams = new BitSet();
|
||||
mExemptedDownstreams = new BitSet();
|
||||
mCurrentEntitlementResults = new SparseIntArray();
|
||||
mEntitlementCacheValue = new SparseIntArray();
|
||||
mPermissionChangeCallback = callback;
|
||||
@@ -150,13 +152,29 @@ public class EntitlementManager {
|
||||
private boolean isCellularUpstreamPermitted(final TetheringConfiguration config) {
|
||||
if (!isTetherProvisioningRequired(config)) return true;
|
||||
|
||||
// If provisioning is required and EntitlementManager doesn't know any downstreams,
|
||||
// cellular upstream should not be allowed.
|
||||
if (mCurrentDownstreams.isEmpty()) return false;
|
||||
// If provisioning is required and EntitlementManager doesn't know any downstreams, cellular
|
||||
// upstream should not be enabled. Enable cellular upstream for exempted downstreams only
|
||||
// when there is no non-exempted downstream.
|
||||
if (mCurrentDownstreams.isEmpty()) return !mExemptedDownstreams.isEmpty();
|
||||
|
||||
return mCurrentEntitlementResults.indexOfValue(TETHER_ERROR_NO_ERROR) > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set exempted downstream type. If there is only exempted downstream type active,
|
||||
* corresponding entitlement check will not be run and cellular upstream will be permitted
|
||||
* by default. If a privileged app enables tethering without a provisioning check, and then
|
||||
* another app enables tethering of the same type but does not disable the provisioning check,
|
||||
* then the downstream immediately loses exempt status and a provisioning check is run.
|
||||
* If any non-exempted downstream type is active, the cellular upstream will be gated by the
|
||||
* result of entitlement check from non-exempted downstreams. If entitlement check is still
|
||||
* in progress on non-exempt downstreams, ceullar upstream would default be disabled. When any
|
||||
* non-exempted downstream gets positive entitlement result, ceullar upstream will be enabled.
|
||||
*/
|
||||
public void setExemptedDownstreamType(final int type) {
|
||||
mExemptedDownstreams.set(type, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when tethering starts.
|
||||
* Launch provisioning app if upstream is cellular.
|
||||
@@ -170,6 +188,8 @@ public class EntitlementManager {
|
||||
|
||||
mCurrentDownstreams.set(downstreamType, true);
|
||||
|
||||
mExemptedDownstreams.set(downstreamType, false);
|
||||
|
||||
final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
|
||||
if (!isTetherProvisioningRequired(config)) return;
|
||||
|
||||
@@ -200,6 +220,7 @@ public class EntitlementManager {
|
||||
// "tethering supported" may change without without tethering being notified properly.
|
||||
// Remove the mapping all the time no matter provisioning is required or not.
|
||||
removeDownstreamMapping(downstreamType);
|
||||
mExemptedDownstreams.set(downstreamType, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -505,6 +526,13 @@ public class EntitlementManager {
|
||||
if (!mWaiting.block(DUMP_TIMEOUT)) {
|
||||
pw.println("... dump timed out after " + DUMP_TIMEOUT + "ms");
|
||||
}
|
||||
pw.print("Exempted: [");
|
||||
for (int type = mExemptedDownstreams.nextSetBit(0); type >= 0;
|
||||
type = mExemptedDownstreams.nextSetBit(type + 1)) {
|
||||
pw.print(typeString(type));
|
||||
pw.print(", ");
|
||||
}
|
||||
pw.println("]");
|
||||
}
|
||||
|
||||
private static String typeString(int type) {
|
||||
|
||||
@@ -516,8 +516,12 @@ public class Tethering {
|
||||
}
|
||||
mActiveTetheringRequests.put(request.tetheringType, request);
|
||||
|
||||
mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType,
|
||||
request.showProvisioningUi);
|
||||
if (request.exemptFromEntitlementCheck) {
|
||||
mEntitlementMgr.setExemptedDownstreamType(request.tetheringType);
|
||||
} else {
|
||||
mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType,
|
||||
request.showProvisioningUi);
|
||||
}
|
||||
enableTetheringInternal(request.tetheringType, true /* enabled */, listener);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.networkstack.tethering;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
|
||||
import static android.Manifest.permission.TETHER_PRIVILEGED;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
|
||||
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
|
||||
@@ -151,7 +153,12 @@ public class TetheringService extends Service {
|
||||
@Override
|
||||
public void startTethering(TetheringRequestParcel request, String callerPkg,
|
||||
String callingAttributionTag, IIntResultListener listener) {
|
||||
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
|
||||
if (checkAndNotifyCommonError(callerPkg,
|
||||
callingAttributionTag,
|
||||
request.exemptFromEntitlementCheck /* onlyAllowPrivileged */,
|
||||
listener)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mTethering.startTethering(request, listener);
|
||||
}
|
||||
@@ -179,7 +186,7 @@ public class TetheringService extends Service {
|
||||
public void registerTetheringEventCallback(ITetheringEventCallback callback,
|
||||
String callerPkg) {
|
||||
try {
|
||||
if (!mService.hasTetherAccessPermission()) {
|
||||
if (!hasTetherAccessPermission()) {
|
||||
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
|
||||
return;
|
||||
}
|
||||
@@ -191,7 +198,7 @@ public class TetheringService extends Service {
|
||||
public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
|
||||
String callerPkg) {
|
||||
try {
|
||||
if (!mService.hasTetherAccessPermission()) {
|
||||
if (!hasTetherAccessPermission()) {
|
||||
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
|
||||
return;
|
||||
}
|
||||
@@ -226,10 +233,18 @@ public class TetheringService extends Service {
|
||||
mTethering.dump(fd, writer, args);
|
||||
}
|
||||
|
||||
private boolean checkAndNotifyCommonError(String callerPkg, String callingAttributionTag,
|
||||
IIntResultListener listener) {
|
||||
private boolean checkAndNotifyCommonError(final String callerPkg,
|
||||
final String callingAttributionTag, final IIntResultListener listener) {
|
||||
return checkAndNotifyCommonError(callerPkg, callingAttributionTag,
|
||||
false /* onlyAllowPrivileged */, listener);
|
||||
}
|
||||
|
||||
private boolean checkAndNotifyCommonError(final String callerPkg,
|
||||
final String callingAttributionTag, final boolean onlyAllowPrivileged,
|
||||
final IIntResultListener listener) {
|
||||
try {
|
||||
if (!mService.hasTetherChangePermission(callerPkg, callingAttributionTag)) {
|
||||
if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
|
||||
onlyAllowPrivileged)) {
|
||||
listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
|
||||
return true;
|
||||
}
|
||||
@@ -244,9 +259,10 @@ public class TetheringService extends Service {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkAndNotifyCommonError(String callerPkg, String callingAttributionTag,
|
||||
ResultReceiver receiver) {
|
||||
if (!mService.hasTetherChangePermission(callerPkg, callingAttributionTag)) {
|
||||
private boolean checkAndNotifyCommonError(final String callerPkg,
|
||||
final String callingAttributionTag, final ResultReceiver receiver) {
|
||||
if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
|
||||
false /* onlyAllowPrivileged */)) {
|
||||
receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
|
||||
return true;
|
||||
}
|
||||
@@ -258,6 +274,30 @@ public class TetheringService extends Service {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasTetherPrivilegedPermission() {
|
||||
return mService.checkCallingOrSelfPermission(TETHER_PRIVILEGED) == PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private boolean hasTetherChangePermission(final String callerPkg,
|
||||
final String callingAttributionTag, final boolean onlyAllowPrivileged) {
|
||||
if (hasTetherPrivilegedPermission()) return true;
|
||||
|
||||
if (onlyAllowPrivileged || mTethering.isTetherProvisioningRequired()) return false;
|
||||
|
||||
int uid = Binder.getCallingUid();
|
||||
// If callerPkg's uid is not same as Binder.getCallingUid(),
|
||||
// checkAndNoteWriteSettingsOperation will return false and the operation will be
|
||||
// denied.
|
||||
return TetheringService.checkAndNoteWriteSettingsOperation(mService, uid, callerPkg,
|
||||
callingAttributionTag, false /* throwException */);
|
||||
}
|
||||
|
||||
private boolean hasTetherAccessPermission() {
|
||||
if (hasTetherPrivilegedPermission()) return true;
|
||||
|
||||
return mService.checkCallingOrSelfPermission(
|
||||
ACCESS_NETWORK_STATE) == PERMISSION_GRANTED;
|
||||
}
|
||||
}
|
||||
|
||||
// if ro.tether.denied = true we default to no tethering
|
||||
@@ -274,26 +314,6 @@ public class TetheringService extends Service {
|
||||
return tetherEnabledInSettings && mTethering.hasTetherableConfiguration();
|
||||
}
|
||||
|
||||
private boolean hasTetherChangePermission(String callerPkg, String callingAttributionTag) {
|
||||
if (checkCallingOrSelfPermission(
|
||||
android.Manifest.permission.TETHER_PRIVILEGED) == PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mTethering.isTetherProvisioningRequired()) return false;
|
||||
|
||||
|
||||
int uid = Binder.getCallingUid();
|
||||
// If callerPkg's uid is not same as Binder.getCallingUid(),
|
||||
// checkAndNoteWriteSettingsOperation will return false and the operation will be denied.
|
||||
if (checkAndNoteWriteSettingsOperation(mContext, uid, callerPkg,
|
||||
callingAttributionTag, false /* throwException */)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the package is a allowed to write settings. This also accounts that such an access
|
||||
* happened.
|
||||
@@ -308,21 +328,6 @@ public class TetheringService extends Service {
|
||||
throwException);
|
||||
}
|
||||
|
||||
private boolean hasTetherAccessPermission() {
|
||||
if (checkCallingOrSelfPermission(
|
||||
android.Manifest.permission.TETHER_PRIVILEGED) == PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (checkCallingOrSelfPermission(
|
||||
android.Manifest.permission.ACCESS_NETWORK_STATE) == PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An injection method for testing.
|
||||
*/
|
||||
|
||||
@@ -543,4 +543,33 @@ public final class EntitlementManagerTest {
|
||||
assertEquals(1, mEnMgr.uiProvisionCount);
|
||||
verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testsetExemptedDownstreamType() throws Exception {
|
||||
setupForRequiredProvisioning();
|
||||
// Cellular upstream is not permitted when no entitlement result.
|
||||
assertFalse(mEnMgr.isCellularUpstreamPermitted());
|
||||
|
||||
// If there is exempted downstream and no other non-exempted downstreams, cellular is
|
||||
// permitted.
|
||||
mEnMgr.setExemptedDownstreamType(TETHERING_WIFI);
|
||||
assertTrue(mEnMgr.isCellularUpstreamPermitted());
|
||||
|
||||
// If second downstream run entitlement check fail, cellular upstream is not permitted.
|
||||
mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
|
||||
mEnMgr.notifyUpstream(true);
|
||||
mLooper.dispatchAll();
|
||||
mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
|
||||
mLooper.dispatchAll();
|
||||
assertFalse(mEnMgr.isCellularUpstreamPermitted());
|
||||
|
||||
// When second downstream is down, exempted downstream can use cellular upstream.
|
||||
assertEquals(1, mEnMgr.uiProvisionCount);
|
||||
verify(mEntitlementFailedListener).onUiEntitlementFailed(TETHERING_USB);
|
||||
mEnMgr.stopProvisioningIfNeeded(TETHERING_USB);
|
||||
assertTrue(mEnMgr.isCellularUpstreamPermitted());
|
||||
|
||||
mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI);
|
||||
assertFalse(mEnMgr.isCellularUpstreamPermitted());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.notNull;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.any;
|
||||
@@ -172,6 +173,8 @@ public class TetheringTest {
|
||||
private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
|
||||
private static final String TEST_NCM_IFNAME = "test_ncm0";
|
||||
private static final String TETHERING_NAME = "Tethering";
|
||||
private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
|
||||
private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
|
||||
|
||||
private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
|
||||
|
||||
@@ -539,16 +542,16 @@ public class TetheringTest {
|
||||
}
|
||||
|
||||
private TetheringRequestParcel createTetheringRequestParcel(final int type) {
|
||||
return createTetheringRequestParcel(type, null, null);
|
||||
return createTetheringRequestParcel(type, null, null, false);
|
||||
}
|
||||
|
||||
private TetheringRequestParcel createTetheringRequestParcel(final int type,
|
||||
final LinkAddress serverAddr, final LinkAddress clientAddr) {
|
||||
final LinkAddress serverAddr, final LinkAddress clientAddr, final boolean exempt) {
|
||||
final TetheringRequestParcel request = new TetheringRequestParcel();
|
||||
request.tetheringType = type;
|
||||
request.localIPv4Address = serverAddr;
|
||||
request.staticClientAddress = clientAddr;
|
||||
request.exemptFromEntitlementCheck = false;
|
||||
request.exemptFromEntitlementCheck = exempt;
|
||||
request.showProvisioningUi = false;
|
||||
|
||||
return request;
|
||||
@@ -1659,7 +1662,7 @@ public class TetheringTest {
|
||||
|
||||
// Enable USB tethering and check that Tethering starts USB.
|
||||
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
|
||||
null, null), firstResult);
|
||||
null, null, false), firstResult);
|
||||
mLooper.dispatchAll();
|
||||
firstResult.assertHasResult();
|
||||
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
|
||||
@@ -1667,7 +1670,7 @@ public class TetheringTest {
|
||||
|
||||
// Enable USB tethering again with the same request and expect no change to USB.
|
||||
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
|
||||
null, null), secondResult);
|
||||
null, null, false), secondResult);
|
||||
mLooper.dispatchAll();
|
||||
secondResult.assertHasResult();
|
||||
verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE);
|
||||
@@ -1676,7 +1679,7 @@ public class TetheringTest {
|
||||
// Enable USB tethering with a different request and expect that USB is stopped and
|
||||
// started.
|
||||
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
|
||||
serverLinkAddr, clientLinkAddr), thirdResult);
|
||||
serverLinkAddr, clientLinkAddr, false), thirdResult);
|
||||
mLooper.dispatchAll();
|
||||
thirdResult.assertHasResult();
|
||||
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
|
||||
@@ -1700,7 +1703,7 @@ public class TetheringTest {
|
||||
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
|
||||
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
|
||||
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
|
||||
serverLinkAddr, clientLinkAddr), null);
|
||||
serverLinkAddr, clientLinkAddr, false), null);
|
||||
mLooper.dispatchAll();
|
||||
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
|
||||
mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
|
||||
@@ -1762,6 +1765,69 @@ public class TetheringTest {
|
||||
mLooper.stopAutoDispatch();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExemptFromEntitlementCheck() throws Exception {
|
||||
setupForRequiredProvisioning();
|
||||
final TetheringRequestParcel wifiNotExemptRequest =
|
||||
createTetheringRequestParcel(TETHERING_WIFI, null, null, false);
|
||||
mTethering.startTethering(wifiNotExemptRequest, null);
|
||||
mLooper.dispatchAll();
|
||||
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
|
||||
verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
|
||||
assertFalse(mEntitleMgr.isCellularUpstreamPermitted());
|
||||
mTethering.stopTethering(TETHERING_WIFI);
|
||||
mLooper.dispatchAll();
|
||||
verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI);
|
||||
reset(mEntitleMgr);
|
||||
|
||||
setupForRequiredProvisioning();
|
||||
final TetheringRequestParcel wifiExemptRequest =
|
||||
createTetheringRequestParcel(TETHERING_WIFI, null, null, true);
|
||||
mTethering.startTethering(wifiExemptRequest, null);
|
||||
mLooper.dispatchAll();
|
||||
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
|
||||
verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
|
||||
assertTrue(mEntitleMgr.isCellularUpstreamPermitted());
|
||||
mTethering.stopTethering(TETHERING_WIFI);
|
||||
mLooper.dispatchAll();
|
||||
verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI);
|
||||
reset(mEntitleMgr);
|
||||
|
||||
// If one app enables tethering without provisioning check first, then another app enables
|
||||
// tethering of the same type but does not disable the provisioning check.
|
||||
setupForRequiredProvisioning();
|
||||
mTethering.startTethering(wifiExemptRequest, null);
|
||||
mLooper.dispatchAll();
|
||||
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
|
||||
verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
|
||||
assertTrue(mEntitleMgr.isCellularUpstreamPermitted());
|
||||
reset(mEntitleMgr);
|
||||
setupForRequiredProvisioning();
|
||||
mTethering.startTethering(wifiNotExemptRequest, null);
|
||||
mLooper.dispatchAll();
|
||||
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
|
||||
verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
|
||||
assertFalse(mEntitleMgr.isCellularUpstreamPermitted());
|
||||
mTethering.stopTethering(TETHERING_WIFI);
|
||||
mLooper.dispatchAll();
|
||||
verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI);
|
||||
reset(mEntitleMgr);
|
||||
}
|
||||
|
||||
private void setupForRequiredProvisioning() {
|
||||
// Produce some acceptable looking provision app setting if requested.
|
||||
when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
|
||||
.thenReturn(PROVISIONING_APP_NAME);
|
||||
when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui))
|
||||
.thenReturn(PROVISIONING_NO_UI_APP_NAME);
|
||||
// Act like the CarrierConfigManager is present and ready unless told otherwise.
|
||||
when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
|
||||
.thenReturn(mCarrierConfigManager);
|
||||
when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig);
|
||||
mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
|
||||
mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
|
||||
sendConfigurationChanged();
|
||||
}
|
||||
// TODO: Test that a request for hotspot mode doesn't interfere with an
|
||||
// already operating tethering mode interface.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user