Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1fe4d26b authored by Mark Chien's avatar Mark Chien Committed by Android (Google) Code Review
Browse files

Merge "Allow to exempt from entitlement check" into rvc-dev

parents f8ab9f52 5788f2a1
Loading
Loading
Loading
Loading
+31 −3
Original line number Diff line number Diff line
@@ -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) {
+6 −2
Original line number Diff line number Diff line
@@ -516,8 +516,12 @@ public class Tethering {
            }
            mActiveTetheringRequests.put(request.tetheringType, request);

            if (request.exemptFromEntitlementCheck) {
                mEntitlementMgr.setExemptedDownstreamType(request.tetheringType);
            } else {
                mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType,
                        request.showProvisioningUi);
            }
            enableTetheringInternal(request.tetheringType, true /* enabled */, listener);
        });
    }
+40 −40
Original line number Diff line number Diff line
@@ -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;
@@ -148,7 +150,11 @@ public class TetheringService extends Service {
        @Override
        public void startTethering(TetheringRequestParcel request, String callerPkg,
                IIntResultListener listener) {
            if (checkAndNotifyCommonError(callerPkg, listener)) return;
            if (checkAndNotifyCommonError(callerPkg,
                    request.exemptFromEntitlementCheck /* onlyAllowPrivileged */,
                    listener)) {
                return;
            }

            mTethering.startTethering(request, listener);
        }
@@ -175,7 +181,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;
                }
@@ -187,7 +193,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;
                }
@@ -221,8 +227,13 @@ public class TetheringService extends Service {
        }

        private boolean checkAndNotifyCommonError(String callerPkg, IIntResultListener listener) {
            return checkAndNotifyCommonError(callerPkg, false /* onlyAllowPrivileged */, listener);
        }

        private boolean checkAndNotifyCommonError(final String callerPkg,
                final boolean onlyAllowPrivileged, final IIntResultListener listener) {
            try {
                if (!mService.hasTetherChangePermission(callerPkg)) {
                if (!hasTetherChangePermission(callerPkg, onlyAllowPrivileged)) {
                    listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
                    return true;
                }
@@ -238,7 +249,7 @@ public class TetheringService extends Service {
        }

        private boolean checkAndNotifyCommonError(String callerPkg, ResultReceiver receiver) {
            if (!mService.hasTetherChangePermission(callerPkg)) {
            if (!hasTetherChangePermission(callerPkg, false /* onlyAllowPrivileged */)) {
                receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
                return true;
            }
@@ -250,6 +261,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 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 Settings.checkAndNoteWriteSettingsOperation(mService, uid, callerPkg,
                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
@@ -266,41 +301,6 @@ public class TetheringService extends Service {
        return tetherEnabledInSettings && mTethering.hasTetherableConfiguration();
    }

    private boolean hasTetherChangePermission(String callerPkg) {
        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 (Settings.checkAndNoteWriteSettingsOperation(mContext, uid, callerPkg,
                false /* throwException */)) {
            return true;
        }

        return false;
    }

    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.
     */
+29 −0
Original line number Diff line number Diff line
@@ -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());
    }
}
+73 −7
Original line number Diff line number Diff line
@@ -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.
}