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

Commit 5bbcb151 authored by Mark Chien's avatar Mark Chien Committed by Automerger Merge Worker
Browse files

Merge "Allow to exempt from entitlement check" am: 7a2b6a8a am: 72d6c813

Change-Id: I34a405492bd2e80aa731e5ac5d56034f7a80cd45
parents e76c8516 72d6c813
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);
        });
    }
+49 −44
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;
@@ -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.
     */
+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.
}